一,概述
二,信号概念
三,24C02
1.地址
2,写数据是时序图
3,读数据时时序图
#include <stdio.h> #include "stm32f4xx.h" #include "sys.h" #include "string.h" static GPIO_InitTypeDef GPIO_InitStructure; static NVIC_InitTypeDef NVIC_InitStructure; static USART_InitTypeDef USART_InitStructure; #define SCL PBout(8) #define SDA_W PBout(9) #define SDA_R PBin(9) //重定义fputc函数 int fputc(int ch, FILE *f) { USART_SendData(USART1,ch); while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET); return ch; } void delay_us(uint32_t nus) { uint32_t temp; SysTick->LOAD =SystemCoreClock/8/1000000*nus; //时间加载 SysTick->VAL =0x00; //清空计数器 SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //使能滴答定时器开始倒数 do { temp=SysTick->CTRL; }while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达 SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器 SysTick->VAL =0X00; //清空计数器 } void delay_ms(uint16_t nms) { uint32_t temp; SysTick->LOAD=SystemCoreClock/8/1000*nms; //时间加载(SysTick->LOAD为24bit) SysTick->VAL =0x00; //清空计数器 SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //能滴答定时器开始倒数 do { temp=SysTick->CTRL; }while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达 SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器 SysTick->VAL =0X00; //清空计数器 } void USART1_Init(uint32_t baud) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //使能USART1时钟 //串口1对应引脚复用映射 GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1 GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1 //USART1端口配置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10 //USART1 初始化设置 USART_InitStructure.USART_BaudRate = baud; //波特率设置 USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式 USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位 USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式 USART_Init(USART1, &USART_InitStructure); //初始化串口 USART_Cmd(USART1, ENABLE); //使能串口1 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启相关中断 //Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //串口1中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3; //抢占优先级3 NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 } void i2c_init(void) { //设置PB8与PB9引脚为输出模式 /*!< Enable GPIO clocks ,使能GPIOB的硬件时钟*/ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); /* 配置片选引脚PB8 PB9为输出模式*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &GPIO_InitStructure); //设置SCL和SDA引脚为高电平 SCL=1; SDA_W=1; } //用于方便切换sda引脚的输入和输出模式 void i2c_sda_mode(uint32_t mode) { /* 配置片选引脚PB9为输入模式*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = mode; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &GPIO_InitStructure); } void i2c_start(void) { //sda引脚为输出模式 i2c_sda_mode(GPIO_Mode_OUT); //SCL与SDA引脚默认是高电平 SCL=1; SDA_W=1; //延时5us delay_us(5); //SDA输出低电平 SDA_W=0; //延时5us delay_us(5); //SCL引脚输出低电平 SCL=0; } void i2c_stop(void) { //sda引脚为输出模式 i2c_sda_mode(GPIO_Mode_OUT); //SCL与SDA引脚默认是低电平 SCL=0; SDA_W=0; //延时5us delay_us(5); //SCL输出高电平 SCL=1; //延时5us delay_us(5); //SDA输出高电平 SDA_W=1; //延时5us delay_us(5); } void i2c_send_byte(uint8_t txd) { uint32_t i=0; //sda引脚为输出模式 i2c_sda_mode(GPIO_Mode_OUT); //保证SCL引脚为低电平,允许SDA引脚电平发生改变 SCL=0; //延时5us delay_us(5); //发送txd这个参数,分8个bit进行发送,MSB格式 for(i=0; i<8; i++) { //MSB,最高有效位发送数据 if(txd & (1<<(7-i))) SDA_W =1; else SDA_W =0; //因为SDA线比SCL引脚电平提前变化,需要延时 delay_us(5); //设置SCL高电平 SCL=1; delay_us(5); //设置SCL低电平 SCL=0; delay_us(5); } } uint8_t i2c_recv_byte(void) { uint32_t i=0; uint8_t rxd=0; //保证SDA引脚为输出模式 i2c_sda_mode(GPIO_Mode_IN); //保证SCL引脚开始的时候为低电平,允许数据的改变 SCL =0; delay_us(5); //连续接收8个bit,采用最高有效位优先进行接收 for(i=0; i<8; i++) { delay_us(5); //锁存数据 SCL=1; delay_us(5); if(SDA_R) rxd|=1<<(7-i); //允许改变数据 SCL=0; delay_us(5); } return rxd; } void i2c_ack(uint8_t ack) { //保证SDA引脚为输出模式 i2c_sda_mode(GPIO_Mode_OUT); //保证SCL引脚开始的时候为低电平,允许数据的改变 SCL =0; delay_us(5); if(ack) SDA_W=1; else SDA_W=0; delay_us(5); //锁存数据,让从机进行识别 SCL=1; delay_us(5); //允许改变数据,从机无视该数据 SCL=0; delay_us(5); } uint8_t i2c_wait_ack(void) { uint8_t ack=0; //sda引脚为输入模式 i2c_sda_mode(GPIO_Mode_IN); //SCL输出高电平 SCL=1; //延时5us delay_us(5); //判断SDA引脚的电平,如果为1,就返回1,就是无应答;若为0,就返回0,意味着是有应答 if(SDA_R) { //调用该函数,将SCL和SDA引脚恢复到原来高电平的状态 i2c_stop(); ack=1; } else ack=0; //SCL输出低电平,保持占用i2c总线 SCL=0; //延时5us delay_us(5); return ack; } void at24c02_write(uint8_t addr,uint8_t *pbuf,uint32_t len) { uint8_t ack=0; //发送起始信号 i2c_start(); //发送寻址地址0xA0 i2c_send_byte(0xA0); //等待应答 ack=i2c_wait_ack(); if(ack) { printf("24c02 ack device address fail "); return ; } printf("24c02 is online "); //发送数据存储地址 i2c_send_byte(addr); //等待应答 ack=i2c_wait_ack(); if(ack) { printf("24c02 ack word address fail "); return ; } printf("24c02 word address ok "); //发送数据内容 while(len--) { //发送数据内容 i2c_send_byte(*pbuf++); //等待应答 ack=i2c_wait_ack(); if(ack) { printf("24c02 ack data fail "); return ; } } //发送停止信号 i2c_stop(); } void at24c02_read(uint8_t addr,uint8_t *pbuf,uint8_t len) { uint8_t ack=0; //发送启动信号 i2c_start(); //发送寻址地址为0xA0,写访问操作 i2c_send_byte(0xA0); //等待应答 ack = i2c_wait_ack(); if(ack) { printf("24c02 ack device address fail "); return; } printf("24c02 is online "); //发送数据存储地址 i2c_send_byte(addr); //等待应答 ack = i2c_wait_ack(); if(ack) { printf("24c02 ack word address 1 fail "); return; } printf("24c02 word address ok "); //重新发送启动信号 i2c_start(); //发送寻址地址为0xA1,读访问操作 i2c_send_byte(0xA1); //等待应答 ack = i2c_wait_ack(); if(ack) { printf("24c02 ack device address 2 fail "); return; } len=len-1; while(len--) { //接收数据 *pbuf++=i2c_recv_byte(); //主动发送应答给从机 i2c_ack(0); } //接收数据 *pbuf=i2c_recv_byte(); //主动发送无应答给从机 i2c_ack(1); //发送停止信号,整个通信过程结束 i2c_stop(); printf("24c02 read ok "); } int main(void) { uint16_t w25qxx_id=0; uint8_t buf_wr[64]={0}; uint8_t buf_rd[64]={0}; uint32_t i=0; //系统定时器初始化,时钟源来自HCLK,且进行8分频, //系统定时器时钟频率=168MHz/8=21MHz SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //设置中断优先级分组2 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //串口1,波特率115200bps,开启接收中断 USART1_Init(115200); i2c_init(); //写入数据 memset(buf_wr,1,sizeof buf_wr); printf("at24c02_write all is 1 "); at24c02_write(0,buf_wr,8); //读写之间必须得加延时,如果写完之后立即读取就会产生读取失败 delay_ms(500); //读取数据 memset(buf_rd,0,sizeof buf_rd); printf("at24c02_read "); at24c02_read(0,buf_rd,8); for(i=0; i<8; i++) { printf("%02X ",buf_rd[i]); } printf(" "); while(1) { } }
思考题1:OLED屏的设备地址是多少?
回答:设备地址为0x78.
思考题2:在24c02控制时钟的时候,为什么要进行5us的延时,小于5us延时或许大于5us的延时是否可以,示例代码如下:
//设置SCL高电平 SCL=1; delay_us(5); //设置SCL低电平 SCL=0; delay_us(5);
回答:大于5us是可以的,如果使用5ms也是可以的。但是延时是不能低于1.2us,详细描述如下图。