使用GPIO实现IIC Slave的方法讨论【1】
来源:互联网 发布:seven eleven 网络语 编辑:程序博客网 时间:2024/05/24 01:44
在本阶段的工作中,需要实现一个由GPIO模拟的I2C从机工程设计,以前只使用GPIO模拟I2C设计过主机,对于从机的设计,还是首次。下面就讲本次工作中从机设计思想做详细记录。
对于I2C信号,需要有START,STOP,ACK,NACK,以及接收DATA。接收DATA是在SCL的低电平可能发生跳变,START和STOP是在高电平跳变。当SCL保持高电平的时候,SDA从H跳变到L,即为START;当SCL保持高电平的时候,SDA从L跳变到H,即为STOP。
根据图5,在时钟节拍①中从机发送ACK信号以后,因为GPIO的采样频率远大于I2C的始终频率(设计中使用100K),所以需要在发送ACK后,要等待SCL变为L,在上面的代码中体现在c点,在时钟节拍②中,SDA可能会发生变化。最重要的时需要在时钟节拍③内采样SDA,判断是否有变化(即从f到g中连续采样SDA),如果发生了SDA从H到L,那么就认为接收到了STOP信号,跳出接收数据的函数;如果发生SDA从L到H,会被认为是一个异常的信号(START信号,但不应该在此时出现,上段代码中没有处理此异常情况,请注意),同样也需要跳出接收数据的函数;如果SDA没有发生任何变化,同时等待到SCL发生由H到L的变化,则意味着接收到了下一字节的bit7……
这样最核心的问题就变为怎样判断在时钟节拍③中SDA是否发生了变化,并且发生了怎样的变化。设计的思想为:在f点(上升沿)后取第一个SDA的采样值r0,遍历时钟节拍③直到g点(下降沿),连续采样SDA的下一个采样值r1,当在时钟节拍③内,只要发生了r1 != r0的时候,马上跳出接收数据程序。但如果r1 === r0,在检测到h点的时候,才把r1赋值给接收字节rxbyte.
以上采样能够成功的一个前提是:SDA在SCL每一时钟节拍的变化能够被采样到。
上图是比较理想的SCL的时钟周期信号,在每半个SCL的时钟周期中,有10个采样点,这样确保了SCL上升沿后的第一个GPIO采样到了r0=0,也能在后5个采样点中采到了r1=1,在这种情况下,不会发生任何错误。
但是,实际情况并非如此,在GPIO模拟I2C的SCL信号中,占空比并不是50%,如果此时SDA的变化在GPIO第一个采样沿之前就发生了变化,那么就无法采样到正确的电平变化信号。
所以,以上设计方法能够成功的前提为:SDA并不会在紧靠着SCL的上升沿或者下降沿而变化,也就是说SDA的任何变化,都能被GPIO的时钟采样到。
pSendBuf:发送数据缓存地址 wSendLen:发送数据的长度
在发送数据的时候需要注意,数据要在SCL的低电平时更新(SDA跳变),在SCL为高电平的时候保持不变。一个Byte发送完成后,需要等待主机发送的ACK信号,从机接收到ACK信号后根据需要发送的字节数,判断是否继续发送,还是要等待主机发送的STOP命令。
IIC的简单总结
对于I2C信号,需要有START,STOP,ACK,NACK,以及接收DATA。接收DATA是在SCL的低电平可能发生跳变,START和STOP是在高电平跳变。当SCL保持高电平的时候,SDA从H跳变到L,即为START;当SCL保持高电平的时候,SDA从L跳变到H,即为STOP。
- START信号:
图1
- STOP信号:
图2
- ACK信号:
图3
- 主机下发地址以及读写信号:
图4
程序设计以及分析
- 为所使用的硬件平台的寄存器配置
#define WAIT_IIC_SCL_HIGH while ( !GET_SCL_DAT )#define WAIT_IIC_SCL_LOW while ( GET_SCL_DAT )#define WAIT_IIC_SDA_HIGH while ( !GET_SDA_DAT )#define WAIT_IIC_SDA_LOW while ( GET_SDA_DAT )#define IIC_WAIT_START WAIT_IIC_SCL_HIGH; WAIT_IIC_SDA_LOW #define IIC_WAIT_STOP WAIT_IIC_SCL_LOW; SDA_IN; WAIT_IIC_SCL_HIGH; WAIT_IIC_SDA_HIGH #define IIC_SLAVE_SEND_LOW WAIT_IIC_SCL_LOW; SDA_OUT; SET_SDA_LOW; WAIT_IIC_SCL_HIGH #define IIC_SLAVE_SEND_HIGH WAIT_IIC_SCL_LOW; SDA_OUT; SET_SDA_HIGH; WAIT_IIC_SCL_HIGH#define IIC_SLAVE_SEND_ACK IIC_SLAVE_SEND_LOW#define IIC_SLAVE_SEND_NAK IIC_SLAVE_SEND_HIGH
- 初始化
void iic_init(void) // 完成GPIO作为I2C的初始化
完成GPIO时钟寄存机配置等功能。
- 接收地址以及读写命令模块
for(bitcount = 0; bitcount < 7; bitcount ++){ WAIT_IIC_SCL_LOW; WAIT_IIC_SCL_HIGH; iic_slv_addr <<= 1; //先移位,再读数 if(GET_SDA_DAT) iic_slv_addr |= 0x01; else iic_slv_addr |= 0x00;}iic_slv_addr <<= 1;// 读取7位地址WAIT_IIC_SCL_LOW;WAIT_IIC_SCL_HIGH;if(GET_SDA_DAT) iic_master_rw = 1;else iic_master_rw = 0;// 读写标志位if (iic_slv_addr == SLAVE_ADDR) IIC_SLAVE_SEND_ACK; // 地址正确,从机发送ACK信号 从机接收接口定义以及说明 uint8_t L_i2c_rx( uint8_t xdata *pDestBuf, uint16_t *wRecLen); pDestBuf:接收数据保存的目的地址 wRecLen:实际接收到的数据的长度
- 接收核心代码分析
while(!recFinish){ for(bitcount=0; bitcount<8; bitcount++) { while(GET_SCL_DAT); SDA_IN; while(!GET_SCL_DAT); r0 = GET_SDA_DAT; while(GET_SCL_DAT) { r1 = GET_SDA_DAT; if((r0 == 0) && (r1 == 1)) { recFinish = 1; return 0; } } rxbyte <<= 1; if(r1) rxbyte |= 0x01; else rxbyte |= 0x00; }buf[len] = rxbyte;len++;IIC_SLAVE_SEND_ACK;
在从机接收完地址以后,如果读写标志位是写(L),接下来,从机就会接收数据,接收数据完成的标志为接收到了STOP信号,就必须在从机发完ACK后的第一个SCL高电平时检测是否有SDA从H跳转到L,如果发生了,接收程序结束,如果没有发生跳变,继续接收数据的bit7,bit6,……bit0.
图5
根据图5,在时钟节拍①中从机发送ACK信号以后,因为GPIO的采样频率远大于I2C的始终频率(设计中使用100K),所以需要在发送ACK后,要等待SCL变为L,在上面的代码中体现在c点,在时钟节拍②中,SDA可能会发生变化。最重要的时需要在时钟节拍③内采样SDA,判断是否有变化(即从f到g中连续采样SDA),如果发生了SDA从H到L,那么就认为接收到了STOP信号,跳出接收数据的函数;如果发生SDA从L到H,会被认为是一个异常的信号(START信号,但不应该在此时出现,上段代码中没有处理此异常情况,请注意),同样也需要跳出接收数据的函数;如果SDA没有发生任何变化,同时等待到SCL发生由H到L的变化,则意味着接收到了下一字节的bit7……
这样最核心的问题就变为怎样判断在时钟节拍③中SDA是否发生了变化,并且发生了怎样的变化。设计的思想为:在f点(上升沿)后取第一个SDA的采样值r0,遍历时钟节拍③直到g点(下降沿),连续采样SDA的下一个采样值r1,当在时钟节拍③内,只要发生了r1 != r0的时候,马上跳出接收数据程序。但如果r1 === r0,在检测到h点的时候,才把r1赋值给接收字节rxbyte.
以上采样能够成功的一个前提是:SDA在SCL每一时钟节拍的变化能够被采样到。
在此假设GPIO的时钟为2MHz,SCL的传输速度为100KHz,时序关系如下图所示:
图6
上图是比较理想的SCL的时钟周期信号,在每半个SCL的时钟周期中,有10个采样点,这样确保了SCL上升沿后的第一个GPIO采样到了r0=0,也能在后5个采样点中采到了r1=1,在这种情况下,不会发生任何错误。
但是,实际情况并非如此,在GPIO模拟I2C的SCL信号中,占空比并不是50%,如果此时SDA的变化在GPIO第一个采样沿之前就发生了变化,那么就无法采样到正确的电平变化信号。
所以,以上设计方法能够成功的前提为:SDA并不会在紧靠着SCL的上升沿或者下降沿而变化,也就是说SDA的任何变化,都能被GPIO的时钟采样到。
- 从机发送接口定义以及说明
uint8_t L_i2c_tx(const uint8_t xdata * pSendBuf, uint16_t wSendLen);
pSendBuf:发送数据缓存地址 wSendLen:发送数据的长度
- 发送核心代码分析
for(bytecount = 0; bytecount < len; bytecount ++){ txmask = 0x80; txbyte = buf[bytecount]; for(bitcount = 0; bitcount < 8; bitcount ++) { WAIT_IIC_SCL_LOW; SDA_OUT; if ( txbyte & txmask ) SET_SDA_HIGH; else SET_SDA_LOW; WAIT_IIC_SCL_HIGH; txmask = txmask >> 1; } WAIT_IIC_SCL_LOW; SDA_IN; WAIT_IIC_SCL_HIGH; if ( GET_SDA_DAT ) break;}return (bytecount);
在发送数据的时候需要注意,数据要在SCL的低电平时更新(SDA跳变),在SCL为高电平的时候保持不变。一个Byte发送完成后,需要等待主机发送的ACK信号,从机接收到ACK信号后根据需要发送的字节数,判断是否继续发送,还是要等待主机发送的STOP命令。
主从机通信流程图
另:本篇博文原发表于另一博客账号,特迁移至此
阅读全文
0 0
- 使用GPIO实现IIC Slave的方法讨论【1】
- 使用GPIO模拟IIC的遇到的N个问题
- STM32Cube MX 下IIC的配置与使用--GPIO模拟
- 再议IIC协议与设计【2】--使用GPIO实现IIC从机通讯源码分析与测试
- GPIO模拟IIC过程中对IIC的理解
- 用s5pc100的GPIO模拟IIC
- 防止GPIO模拟IIC驱动被其他线程打断的方法
- 讨论equals方法的使用
- GPIO调用方法的实现流程
- STM32 GPIO IIC学习
- STM32-GPIO输入模式的上拉下拉设置(结合自己在IIC使用中的疑问)
- 对SPI、IIC、IIS、UART、CAN、SDIO、GPIO的解释
- 对SPI、IIC、IIS、UART、CAN、SDIO、GPIO的解释
- 对SPI、IIC、IIS、UART、CAN、SDIO、GPIO的解释
- 对SPI、IIC、IIS、UART、CAN、SDIO、GPIO的解释
- SPI、IIC、IIS、UART、CAN、SDIO、GPIO的解释
- vc实现定时任务的方法讨论
- msp430g2533之iic(gpio模拟iic)
- if else 和 switch 的区别
- 马斯克:AI是人类最大风险 要求政府干预和监管
- POJ 3055 Digital Friends 笔记
- Rstudio安装后打不开
- 理解Java的几张图
- 使用GPIO实现IIC Slave的方法讨论【1】
- CSS规范
- MFC界面库BCGControlBar v25.3新版亮点:支持Visual Studio 2017
- 练习1:百度前端技术学院
- Unity 场景分页插件 World Streamer 支持无限大地图的解决方案(一)
- 微信小程序-弹出自定义对话框
- Fence Repair
- File类
- UVA