OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 8151|回复: 10

STM32的GPIO模拟IIC时序——“手撸”时序

[复制链接]

27

主题

70

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
384
金钱
384
注册时间
2018-11-20
在线时间
62 小时
发表于 2022-1-14 16:17:05 | 显示全部楼层 |阅读模式
本帖最后由 硕果磊磊 于 2022-1-14 16:42 编辑

STM32的GPIO模拟IIC时序
背景:使用单片机(master)GPIO模拟IIC时序,读取CP3000(slave)状态字,CP3000是一种一次电源,支持PMbus总线协议。
说明:使用GPIO模拟IIC,GPIO模式必须设置为开漏模式。原因是slave产生clock stretch时会拉低SCL,master此时要拉高SCL(一般用来当做SCL的GPIO都有上拉电阻,不会短路),但如果GPIO是推挽模式,会产生一个半高时钟,不会将SCL完全拉低;只有GPIO配置为开漏模式,输出高阻作为高电平,在slave拉低SCL时才会真正的变为低。

1、IIC简介
I2C,即Inter-Integrated Circuit,是一种常用的串行通信协议,用于在器件之间——特别是两个或两个以上不同电路之间建立通信。I2C总线由一条数据线(SDA)和一条时钟线(SCL)组成。设备分主从,主设备提供时钟,并发起操作。
●    SDA是供主器件和节点发送和接收数据的线路。
●    SCL是承载时钟信号的线路。SCL总是由I2C主器件生成。规范对时钟信号的低相位和高相位有最短周期要求。
2、PMBus简介
PMBus是一种基于I2C而扩展出来的协议。它定义了电源控制和管理组件所需的一组命令和数据结构。有一些更为复杂的操作,但是原理都还是基于I2C的。

3、PMBus与IIC对比
                   PMBus                                                                  IIC
最大传输速度 100kHz                                         最大传输速度400kHz
最小传输速度 10kHz                                           无最小传输速度
高为1.4V,低为0.6V                                          高为0.7Vdd,低为0.3Vdd
35ms时钟低超时                                                无时钟超时
固定的逻辑电平                                                   逻辑电平由VDD决定
不同的地址类型(保留、动态等)                         7位、10位和广播呼叫从地址类型
不同的总线协议(快速命令、处理呼叫等)        无总线协议
4、时序
(1)等待clock stretch
slave在准备数据时会拉低SCL(时钟延伸clock stretch)。master检测clock stretch采用拉高SCL,同时读取SCL的值来判断是否结束了clock stretch。实际测试中发现,单独将检测clock strech操作拉出,会产生一个时钟,导致时序紊乱。在可能产生clock stretch的时序,如开始信号,读写操作都有将SCL拉高操作改为IIC_Wait_ClkStretch()。
  1. int IIC_Wait_ClkStretch(void)
  2. {
  3.     int time=0;
  4.         
  5.         IIC_SCL=1;
  6.         delay_us(2);//1

  7.     while( 0 == READ_SCL )
  8.     {
  9.         time++;
  10.                 delay_us(5);//1
  11.                 IIC_SCL=1;
  12.         if(time > 5000 )
  13.         {
  14.             return -1;
  15.         }
  16.     }
  17.     return 0;
  18. }
复制代码

(2)开始信号
开始时先将SCL和SDA拉低,钳住总线(也可以不用),然后在SCL高电平期间产生一个SDA低跳变。其中我将拉高SCL改为IIC_Wait_ClkStretch()。因为在发送第二段开始信号之前可能slave产生clock stretch(时钟延时)。
  1. int IIC_Start(void)
  2. {
  3.         int res = 0;
  4.         SDA_OUT();     //sda线输出
  5.     IIC_SDA=0;
  6.     IIC_SCL=0;
  7.     delay_us(2);
  8.         IIC_SDA=1;      
  9.         res = IIC_Wait_ClkStretch();
  10.         if(res < 0 )
  11.         {
  12.        return -1;
  13.         }
  14.         delay_us(5);
  15.          IIC_SDA=0;//START:when CLK is high,DATA change form high to low
  16.         delay_us(5);
  17.         IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
  18.         return 0;
  19. }         
复制代码
(3)停止信号
在SCL高电平期间产生一个SDA低跳变。
  1. void IIC_Stop(void)
  2. {
  3.         //int res;
  4.         SDA_OUT();//sda线输出
  5.         IIC_SCL=0;
  6.         IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
  7.          delay_us(5);
  8.         IIC_SCL=1;
  9.         delay_us(1);        
  10.         IIC_SDA=1;//发送I2C总线结束信号
  11.         delay_us(4);                                                                  
  12. }
复制代码
(4)等待ACK
在SCL高电平期间,读取SDA电平,SDA为高表示NACK,SDA为低表示ACK。此函数采用超时等待ACK,否则发送停止信号。
  1. int IIC_Wait_Ack(void)
  2. {
  3.         u8 ucErrTime=0;
  4.         SDA_IN();      //SDA设置为输入  
  5.         IIC_SDA=1;delay_us(4);           
  6.         IIC_SCL=1;delay_us(4);         
  7.         while(READ_SDA)
  8.         {
  9.                 ucErrTime++;
  10.                 if(ucErrTime>250)
  11.                 {
  12.                         IIC_Stop();
  13.                         return -1;
  14.                 }
  15.         }
  16.         IIC_SCL=0;//时钟输出0
  17.         delay_us(4);        
  18.         return 0;  
  19. }
复制代码
(5)应答信号
在SCL高电平期间,SDA为高表示NACK,SDA为低表示ACK。
  1. //产生ACK应答
  2. void IIC_Ack(void)
  3. {
  4.         IIC_SCL=0;
  5.         SDA_OUT();
  6.         IIC_SDA=0;
  7.         delay_us(4);
  8.         IIC_SCL=1;
  9.         delay_us(4);
  10.         IIC_SCL=0;
  11. }
  12. //不产生ACK应答                    
  13. void IIC_NAck(void)
  14. {
  15.         IIC_SCL=0;
  16.         SDA_OUT();
  17.         IIC_SDA=1;
  18.         delay_us(4);
  19.         IIC_SCL=1;
  20.         delay_us(4);
  21.         IIC_SCL=0;
  22. }                                                                              
复制代码
(6)读操作
读一个字节,每次读取时先将SCL拉低钳住总线,再拉高SCL,延时一段,在时钟中段读取SDA。
  1. //读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
  2. int IIC_Read_Byte(unsigned char ack)
  3. {
  4.         int res;
  5.         unsigned char i,receive=0;
  6.         SDA_IN();//SDA设置为输入
  7.     for(i=0;i<8;i++ )
  8.         {
  9.         IIC_SCL=0;
  10.         delay_us(3);//2
  11.                 res = IIC_Wait_ClkStretch();
  12.                 if(res < 0 )
  13.                 {
  14.                         return -1;
  15.                 }   
  16.                 delay_us(1);//2
  17.         receive<<=1;
  18.         if(READ_SDA)receive++;   
  19.                 delay_us(1);//1
  20.     }                                         
  21.     if (!ack)
  22.         IIC_NAck();//发送nACK
  23.     else
  24.         IIC_Ack(); //发送ACK   
  25.     return receive;
  26. }
复制代码
(7)写操作
写一个字节,每次先准备数据,准备好数据后,延时一段,再操控SCL产生一个时钟,保证SCL高电平期间,SDA电平达到稳定状态。
  1. int IIC_Send_Byte(u8 txd)
  2. {        
  3.         int res;
  4.     u8 t;
  5.         SDA_OUT();            
  6.     IIC_SCL=0;//拉低时钟开始数据传输
  7.     for(t=0;t<8;t++)
  8.     {              
  9.         //IIC_SDA=(txd&0x80)>>7;
  10.                 if((txd&0x80)>>7)
  11.                         IIC_SDA=1;
  12.                 else
  13.                         IIC_SDA=0;
  14.                 txd<<=1;           
  15.                 delay_us(2);   //对TEA5767这三个延时都是必须的
  16.                 //IIC_SCL=1;     //修改为等待clock stretch
  17.                 res = IIC_Wait_ClkStretch();
  18.                 if(res < 0 )
  19.                 {
  20.                         return -1;
  21.                 }
  22.                 IIC_SCL=0;        
  23.                 delay_us(4);
  24.     }         
  25.         return 0;
  26. }            
复制代码
5、IIC时序波形

GPIO模拟IIC波形图

GPIO模拟IIC波形图

总结:
此次操作难点在于clock stretch识别,最开始使用树莓派GPIO模拟IIC,由于树莓派GPIO无法设置开漏模式,导致电平无法输出高阻,形成半高时钟,最后被识别为高电平,导致时序紊乱;不过最后使用STM32单片机完成了这一“壮举”,虽说网上GPIO模拟IIC代码到处都是,但是亲自按照协议“撸”时序还是会遇到很多困难,本次模拟还是值得记录的。



正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

5

主题

61

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
473
金钱
473
注册时间
2021-11-22
在线时间
154 小时
发表于 2022-1-14 16:27:00 | 显示全部楼层
回复 支持 反对

使用道具 举报

13

主题

644

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1976
金钱
1976
注册时间
2021-4-16
在线时间
505 小时
发表于 2022-1-17 12:26:06 | 显示全部楼层
谢谢分享  
回复 支持 反对

使用道具 举报

0

主题

13

帖子

0

精华

初级会员

Rank: 2

积分
168
金钱
168
注册时间
2021-11-15
在线时间
32 小时
发表于 2022-1-21 17:14:37 | 显示全部楼层
楼主用的什么示波器啊,很方便的样子
回复 支持 反对

使用道具 举报

27

主题

70

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
384
金钱
384
注册时间
2018-11-20
在线时间
62 小时
 楼主| 发表于 2022-1-25 14:20:58 | 显示全部楼层
duolunkeji 发表于 2022-1-21 17:14
楼主用的什么示波器啊,很方便的样子

teledyne lecroy;4 GHz 混合信号示波器
回复 支持 反对

使用道具 举报

0

主题

2

帖子

0

精华

新手上路

积分
21
金钱
21
注册时间
2022-2-12
在线时间
2 小时
发表于 2022-2-12 16:52:33 | 显示全部楼层
if(ucErrTime>250)等待应答信号,为啥要大于250?这个数字怎么来的?还是?
回复 支持 反对

使用道具 举报

52

主题

247

帖子

0

精华

高级会员

Rank: 4

积分
997
金钱
997
注册时间
2017-8-19
在线时间
160 小时
发表于 2022-2-14 17:22:37 | 显示全部楼层
楼主是不是做电源的,PMbus好像是开关电源那块的?
回复 支持 反对

使用道具 举报

27

主题

70

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
384
金钱
384
注册时间
2018-11-20
在线时间
62 小时
 楼主| 发表于 2022-3-23 10:22:52 | 显示全部楼层
ryan094 发表于 2022-2-12 16:52
if(ucErrTime>250)等待应答信号,为啥要大于250?这个数字怎么来的?还是?

对,参考百度
回复 支持 反对

使用道具 举报

2

主题

7

帖子

0

精华

新手上路

积分
27
金钱
27
注册时间
2022-2-22
在线时间
7 小时
发表于 2022-4-4 16:51:29 | 显示全部楼层
你好楼主, 我想问一下SDA_IN( ) 和 SDA_OUT( )的寄存器该如何设置  比如我用PB13和PB14两个引脚做SCL和SDA
回复 支持 反对

使用道具 举报

27

主题

70

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
384
金钱
384
注册时间
2018-11-20
在线时间
62 小时
 楼主| 发表于 2022-6-15 17:37:26 | 显示全部楼层
我就看看不学习 发表于 2022-4-4 16:51
你好楼主, 我想问一下SDA_IN( ) 和 SDA_OUT( )的寄存器该如何设置  比如我用PB13和PB14两个引脚做SCL和SDA

简单的设置GPIO口输出方向就行,IN输入,OUT输出
回复 支持 反对

使用道具 举报

1

主题

2

帖子

0

精华

新手入门

积分
5
金钱
5
注册时间
2022-9-27
在线时间
0 小时
发表于 2022-9-27 15:50:56 | 显示全部楼层
有一点不明白,为什么需要等待ACK这个函数,在某些时候,等待ACK和产生ACK实际作用是等效的吗
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-2-26 02:51

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表