/********************************************************************************************************* * 模块名称: I2C.c * 摘 要: I2C模块 * 当前版本: 1.0.0 * 作 者:Leyutek(COPYRIGHT 2018 - 2021 Leyutek. All rights reserved.) * 完成日期:2021年07月01日 * 内 容: * 注 意: none ********************************************************************************************************** * 取代版本: * 作 者: * 完成日期: * 修改内容: * 修改文件: *********************************************************************************************************/ /********************************************************************************************************* * 包含头文件 *********************************************************************************************************/ #include "I2c.h" #include "SysTick.h" /********************************************************************************************************* * 宏定义 *********************************************************************************************************/ //读取SDA端口 #define READ_I2CSDA() gpio_input_bit_get(GPIOB, GPIO_PIN_7) //时钟线SCL输出高电平 #define SET_I2C_SCL() gpio_bit_set(GPIOB, GPIO_PIN_6) //时钟线SCL输出低电平 #define CLR_I2C_SCL() gpio_bit_reset(GPIOB, GPIO_PIN_6) //数据线SDA输出高电平 #define SET_I2C_SDA() gpio_bit_set(GPIOB, GPIO_PIN_7) //数据线SDA输出低电平 #define CLR_I2C_SDA() gpio_bit_reset(GPIOB, GPIO_PIN_7) /********************************************************************************************************* * 内部变量 *********************************************************************************************************/ /********************************************************************************************************* * 内部函数声明 *********************************************************************************************************/ static void ConfigI2CGPIO(void); //配置I2C的GPIO static void SetI2CSDAAsInput(void); //将SDA端设置为输入 static void SetI2CSDAAsOutput(void); //将SDA端设置为输出 /********************************************************************************************************* * 内部函数实现 *********************************************************************************************************/ /********************************************************************************************************* * 函数名称: ConfigI2CGPIO * 函数功能: 配置I2C的GPIO * 输入参数: void * 输出参数: void * 返 回 值: void * 创建日期: 2021年07月01日 * 注 意: *********************************************************************************************************/ static void ConfigI2CGPIO(void) { //配置PC13 rcu_periph_clock_enable(RCU_GPIOB); //使能GPIOC的时钟 gpio_init(GPIOB,GPIO_MODE_OUT_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_5); gpio_bit_reset(GPIOB,GPIO_PIN_5); //配置PB7 gpio_init(GPIOB,GPIO_MODE_OUT_OD,GPIO_OSPEED_50MHZ,GPIO_PIN_7); //设置GPIO输出模式及速度 SDA gpio_bit_set(GPIOB,GPIO_PIN_7); //配置PB6 gpio_init(GPIOB,GPIO_MODE_OUT_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_6); //设置GPIO输出模式及速度 SCL gpio_bit_set(GPIOB,GPIO_PIN_6); } /********************************************************************************************************* * 函数名称: SetI2CSDAAsInput * 函数功能: 将SDA端设置为输入 * 输入参数: void * 输出参数: void * 返 回 值: void * 创建日期: 2021年07月01日 * 注 意: *********************************************************************************************************/ static void SetI2CSDAAsInput(void) { //gpio_mode_set(GPIOF, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_0); //配置PF0为输入模式 } /********************************************************************************************************* * 函数名称: SetI2CSDAAsOutput * 函数功能: 将SDA端设置为输出 * 输入参数: void * 输出参数: void * 返 回 值: void * 创建日期: 2021年07月01日 * 注 意: *********************************************************************************************************/ static void SetI2CSDAAsOutput(void) { // gpio_mode_set(GPIOF, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_0); //设置PF0为输出模式 // gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0); //设置输出模式及速度 // gpio_bit_set(GPIOF, GPIO_PIN_0); //状态默认设置为高电平 } /********************************************************************************************************* * API函数实现 *********************************************************************************************************/ /********************************************************************************************************* * 函数名称: InitI2C * 函数功能: 初始化IIC * 输入参数: void * 输出参数: void * 返 回 值: void * 创建日期: 2021年07月01日 * 注 意: *********************************************************************************************************/ void InitI2C(void) { ConfigI2CGPIO(); //配置I2C的GPIO } /********************************************************************************************************* * 函数名称: GenI2CStartSig * 函数功能: 产生IIC起始时序,准备发送或接收数据前必须由起始序列开始 * 输入参数: void * 输出参数: void * 返 回 值: void * 创建日期: 2021年07月01日 * 注 意: SCL为高电平时,SDA由高电平向低电平跳变,开始传输数据 * 生成下图所示的波形图,即为起始时序 * 1 2 3 4 * __________ * SCL : __/ \_____ * ________ * SDA : \___________ *********************************************************************************************************/ void GenI2CStartSig(void) { SetI2CSDAAsOutput(); //将数据线SDA设置为输出 SET_I2C_SDA(); //1#数据线SDA输出高电平 SET_I2C_SCL(); //2#时钟线SCL输出高电平,2-3之间的间隔须>4.7us DelayNus(4); //延时4us CLR_I2C_SDA(); //3#数据线SDA输出低电平,3-4之间的间隔须>4.0us DelayNus(4); //延时4us CLR_I2C_SCL(); //4#时钟线SCL输出低电平,保持I2C的时钟线SCL为低电平,准备发送或接收数据 } /********************************************************************************************************* * 函数名称: GenI2CStopSig * 函数功能: 产生IIC停止时序 * 输入参数: void * 输出参数: void * 返 回 值: void * 创建日期: 2021年07月01日 * 注 意: SCL为高电平时,SDA由低电平向高电平跳变,结束传输数据 * 生成下图所示的波形图,即为停止时序 * 1 2 3 4 * _______________ * SCL : ______/ * __ ____________ * SDA: \______/ *********************************************************************************************************/ void GenI2CStopSig(void) { SetI2CSDAAsOutput(); //将数据线SDA设置为输出 CLR_I2C_SCL(); //1#时钟线SCL输出低电平 CLR_I2C_SDA(); //2#数据线SDA输出低电平 DelayNus(4); //延时4us SET_I2C_SCL(); //3#时钟线SCL输出高电平,3-4之间的间隔须>4.7us SET_I2C_SDA(); //4#数据线SDA输出高电平,发送I2C总线结束信号,4之后SDA须保持不小于4.0us的高电平 DelayNus(4); //延时4us } /********************************************************************************************************* * 函数名称: I2CSendByte * 函数功能: 发送一字节,数据从高位开始发送出去 * 输入参数: txd * 输出参数: void * 返 回 值: void * 创建日期: 2021年07月01日 * 注 意: 下面是具体的时序图 * 1 2 3 4 * ______ * SCL: ________/ \______ * ______________________ * SDA: \\\___________________ *********************************************************************************************************/ void I2CSendByte(unsigned char txd) { unsigned char t; //循环计数器 SetI2CSDAAsOutput(); //将数据线SDA设置为输出 CLR_I2C_SCL(); //1#时钟线SCL输出低电平,开始数据传输 for(t = 0; t < 8; t++) //循环8次,从高到低取出字节的8个位 { if((txd&0x80) >> 7) //2#取出字节最高位,并判断为‘0’还是‘1’,从而做出相应的操作 { SET_I2C_SDA(); //数据线SDA输出高电平,数据位为‘1’ } else { CLR_I2C_SDA(); //数据线SDA输出低电平,数据位为‘0’ } txd <<= 1; //左移一位,次高位移到最高位 DelayNus(2); //延时2us SET_I2C_SCL(); //3#时钟线SCL输出高电平 DelayNus(2); //延时2us CLR_I2C_SCL(); //4#时钟线SCL输出低电平 DelayNus(2); //延时2us } } /********************************************************************************************************* * 函数名称: I2CReadByte * 函数功能: 读取一字节数据,当ack=1时,发送ACK,当ack=0时,发送nACK * 输入参数: txd * 输出参数: void * 返 回 值: void * 创建日期: 2021年07月01日 * 注 意: 下面是具体的时序图 * ______ * SCL: ______/ \___ * ____________________ * SDA: \\\\______________\\\ *********************************************************************************************************/ unsigned char I2CReadByte(unsigned char ack) { unsigned char i = 0; //i为循环计数器 unsigned char receive = 0; //receive用来存放接收的数据 SetI2CSDAAsInput(); //1#将数据线SDA设置为输入 for(i = 0; i < 8; i++ ) //循环8次,从高到低读取字节的8个位 { CLR_I2C_SCL(); //2#时钟线SCL输出低电平 DelayNus(2); //延时2us SET_I2C_SCL(); //3#时钟线SCL输出高电平 receive <<= 1; //左移一位,空出新的最低位 if(READ_I2CSDA()) //4#读取数据线SDA的数据位 { receive++; //在SCL的上升沿后,数据已经稳定,因此可以取该数据,存入最低位 } DelayNus(1); //延时1us } if(NACK == ack) //如果ack为NACK { SendI2CNAck(); //发送NACK,无应答 } else //如果ack为ACK { SendI2CAck(); //发送ACK,应答 } return receive; //返回读取到的数据 } /********************************************************************************************************* * 函数名称: I2CWaitAck * 函数功能: 等待应答信号到来 * 输入参数: void * 输出参数: void * 返 回 值: 1,接收应答失败;0,接收应答成功 * 创建日期: 2021年07月01日 * 注 意: 当SDA拉低后,表示接收到ACK信号,然后,拉低SCL,根据实际情况做出是否继续传递信号的判断 * 此处表示发送端收到接收端的ACK * _______|____ * SCL: | \_________ * _______| * SDA: \_____________ *********************************************************************************************************/ unsigned char I2CWaitAck(void) { unsigned char ucErrTime = 0; SetI2CSDAAsInput(); //将数据线SDA设置为输出 SET_I2C_SCL(); //时钟线SCL输出高电平 DelayNus(1); //延时1us while(READ_I2CSDA()) //读回来的数据如果是高电平,即接收端没有应答 { ucErrTime++; //计数器加1 if(ucErrTime > 250) //如果超过250次,则判断为接收端出现故障,因此发送结束信号 { GenI2CStopSig(); //产生一个停止信号 return 1; //返回值为1,表示没有收到应答信号 } } CLR_I2C_SCL(); //表示已收到应答信号,时钟线SCL输出低电平,钳住I2C总线 return 0; //返回值为0,表示接收应答成功 } /********************************************************************************************************* * 函数名称: SendI2CAck * 函数功能: 发送应答信号 * 输入参数: void * 输出参数: void * 返 回 值: void * 创建日期: 2021年07月01日 * 注 意: 下面是具体的时序图 * 1 2 3 4 * ______ * SCL: ________/ \______ * __ * SDA: \___________________ *********************************************************************************************************/ void SendI2CAck(void) { CLR_I2C_SCL(); //1#时钟线SCL输出低电平,开始传送应答信号 SetI2CSDAAsOutput(); //将数据线SDA设置为输出 CLR_I2C_SDA(); //2#数据线SDA输出低电平 DelayNus(2); //延时2us SET_I2C_SCL(); //3#时钟线SCL输出高电平,在SCL上升沿前就要把SDA拉低,为应答信号 DelayNus(2); //延时2us CLR_I2C_SCL(); //4#时钟线SCL输出低电平,钳住I2C总线 } /********************************************************************************************************* * 函数名称: SendI2CNAck * 函数功能: 不发送应答信号 * 输入参数: void * 输出参数: void * 返 回 值: void * 创建日期: 2021年07月01日 * 注 意: 下面是具体的时序图 * 1 2 3 4 * ______ * SCL: ________/ \______ * __ ___________________ * SDA: __/ *********************************************************************************************************/ void SendI2CNAck(void) { CLR_I2C_SCL(); //1#拉低时钟线,开始传送非应答信号 SetI2CSDAAsOutput(); //将数据线SDA设置为输出 SET_I2C_SDA(); //2#数据线SDA输出高电平 DelayNus(2); //延时2us SET_I2C_SCL(); //3#时钟线SCL输出高电平,在SCL上升沿前就要把SDA拉高,为非应答信号 DelayNus(2); //延时2us CLR_I2C_SCL(); //4#时钟线SCL输出低电平,钳住I2C总线 }