STM32之IO口模拟IIC

本文介绍如何使用STM32标准外设库的GPIO端口模拟IIC,本例程使用PB6和PB7模拟一路IIC。

本文适合对单片机及C语言有一定基础的开发人员阅读,MCU使用STM32F103VE系列。

 

1.    简介

IIC (Inter-Integrated Circuit)总线,也可写作I2C,是PHILIPS 公司开发的两线式串行总线,用于多设备之间通讯,分为主机Master和从机Slave,主机和从机可以有多个,但一般情况下只有一个主机,从机之间可以通过地址进行区分,不同种类的设备地址不同,如果同时接入多个相同种类的设备,可以通过片选信号对从机进行选择。通讯只能由主机发起,支持的操作分为读取和写入,即主机读取从机的数据,以及向从机写入数据。

I2C两线分别是时钟线SCL和数据线SDA,其中SCL和SDA均由主机控制,可以设置成开漏输出模式。

2.    协议说明

2.1. 总线传输信号

  • 空闲状态:IIC空闲状态时SCL和SDA均输出高电平,初始状态以及发送结束信号之后均为空闲状态。
  • 开始信号:START,简写S,SCL为高电平,SDA由高电平向低电平跳变。
  • 结束信号:STOP,简写P,SCL为高电平,SDA由低电平向高电平跳变。
  • 从机地址:SLAVE_ADDRESS,每种从机都有一个表示该设备的地址,地址一般为7位,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机,紧跟设备地址的一个数据位用来表示数据传输方向(R/W位),数据方向位为“1”时表示主机由从机读数据,数据方向位为“0”时表示主机向从机写数据。从机接收到匹配的地址后,会返回一个应答(ACK)信号,只有接收到应答信号后,主机才能继续发送或接收数据。
  • 响应信号:ACK/NACK,简写A,响应包括应答(ACK)和非应答(NACK),当数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,即SDA为低电平,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,即SDA为高电平,发送方接收到该信号后会产生一个停止信号,结束信号传输。
  • 主机写入:SCL为高电平时,SDA有效,SDA为输出模式,主机控制SDA输出高电平时表示写入1,SDA输出低电平时表示写入0。
  • 主机读取:SCL为高电平时,SDA有效,SDA为输入模式,主机读取SDA高电平时表示输入1,SDA低电平时表示输入0。此时主机释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接收信号。

2.2. 基本读写过程

  • 写数据:主机先发一个开始信号(S),然后发送从机地址(SLAVE ADDRESS),后续跟上写信号(R/W位为0),然后等待从机的应答信号(ACK位为0),主机向从机传输数据(DATA),数据包为1个字节共8位,从高位到低位依次发送,主机每发送完1个字节数据,都要等待从机的应答信号(ACK),重复这个过程,可以向从机传输 N 个数据,当数据传输结束时,主机向从机发送一个停止传输信号(P),表示不再传输数据。
  • 读数据:主机先发一个开始信号(S),然后发送从机地址(SLAVE ADDRESS),后续跟上读信号(R/W位为1),然后等待从机的应答信号(ACK位为0),主机从从机读取数据(DATA),数据包为1个字节共8位,从高位到低位依次发送,从机每发送完1个字节数据,都要等待主机的应答信号(ACK),重复这个过程,可以从从机读取 N 个数据,当主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输,主机再向从机发送一个停止传输信号(P),表示不再传输数据。
  • 读和写数据:除了基本的读写,I2C 通讯还有一种是复合读写模式,该传输过程有两次起始信号(S)。一般在第一次传输中,主机通过 SLAVE_ADDRESS 寻找到从设备后,发送一段“数据”,这段数据通常用于表示从设备内部的寄存器或存储器地址(注意区分它与 SLAVE_ADDRESS 的区别);在第二次的传输中,对该地址的内容进行读或写。也就是说,第一次通讯是告诉从机读写地址,第二次则是读写的实际内容。

  

2.3. 速度模式

标准模式传输速率为100kbit/s,即10us可以传输一个bit,如果用GPIO模拟I2C时电平变换时需要增加适当的延时。

3.    初始化

初始化跟普通GPIO类似,只是输出模式设置为开漏输出。

其中SCL始终输出信号,但SDA需要支持输出信号和读取信号,当设置为开漏输出时,如果需要输出信号,则正常输出即可,如果需要读取信号,则MCU将SDA输出高电平,此时如果从机输出低电平,则SDA被拉低,此时MCU可以读到低电平。即GPIO引脚为开漏输出模式时,MCU输出高电平时,即释放了该引脚的控制,此时该引脚的电平取决于从机的输出,且MCU仍可以读取该引脚的电平。

GPIO初始化完成之后,可以将SCL和SDA置为高电平,即释放该引脚的控制,如果总线上有多个主机,则不会干扰其他设备的通讯。

4.    信号模拟

需要按照通讯信号的时序,实现START、STOP、ACK、NACK、Read、Write和WaitAck信号。

 

完整代码(仅自己编写的部分)

  1 #define IIC_SCL_1    GPIO_SetBits(GPIOB, GPIO_Pin_6)            /* SCL = 1 */
  2 #define IIC_SCL_0    GPIO_ResetBits(GPIOB, GPIO_Pin_6)        /* SCL = 0 */
  3 
  4 #define IIC_SDA_1    GPIO_SetBits(GPIOB, GPIO_Pin_7)            /* SDA = 1 */
  5 #define IIC_SDA_0    GPIO_ResetBits(GPIOB, GPIO_Pin_7)        /* SDA = 0 */
  6 
  7 #define IIC_READ_SDA()    GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7)    /* 读SDA口线状态 */
  8 
  9 //初始化IIC
 10 void IIC_Init(void)
 11 {                         
 12     GPIO_InitTypeDef GPIO_InitStructure;
 13 
 14     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);    
 15        
 16     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
 17     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD ;   //开漏输出
 18     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 19     GPIO_Init(GPIOB, &GPIO_InitStructure);
 20  
 21     IIC_Stop();
 22 }
 23 
 24 //产生IIC起始信号
 25 //SCL为高电平时SDA由高变低
 26 /*
 27 SCL: ̄ ̄ ̄ ̄ ̄\_
 28 SDA: ̄ ̄\____
 29 */
 30 void IIC_Start(void)
 31 {
 32     IIC_SDA_1;
 33     IIC_SCL_1;
 34     delay_us(4);
 35      IIC_SDA_0;
 36     delay_us(4);
 37     IIC_SCL_0;
 38 }      
 39 
 40 //产生IIC停止信号
 41 //SCL为高电平时SDA由低变高
 42 //IIC空闲时SCL和SDA均输出高电平,这样不会干扰其他设备的收发
 43 /*
 44 SCL: ̄ ̄ ̄ ̄
 45 SDA:__/ ̄
 46 */
 47 void IIC_Stop(void)
 48 {
 49     IIC_SDA_0;
 50     IIC_SCL_1;
 51     delay_us(4);
 52     IIC_SDA_1;
 53 }
 54 
 55 //等待应答信号到来
 56 //返回值:1,接收应答失败
 57 //        0,接收应答成功
 58 uint8_t IIC_WaitAck(void)
 59 {
 60     uint8_t errCount = 0;
 61     uint8_t ack = 0;
 62     
 63     IIC_SDA_1;
 64     delay_us(4);
 65     IIC_SCL_1;
 66     delay_us(4);     
 67 
 68     while(IIC_READ_SDA())
 69     {
 70         errCount++;
 71         if(errCount > 250){
 72             ack = 1;
 73             break;
 74         }
 75     }
 76     IIC_SCL_0;
 77     
 78     return ack;
 79 } 
 80 
 81 //产生应答ACK
 82 //SCL为高电平时SDA为低电平表示应答
 83 /*
 84 SCL:     ̄ ̄\____
 85 SDA:_______/ ̄
 86 */
 87 void IIC_Ack(void)
 88 {
 89     IIC_SDA_0;
 90     delay_us(4);
 91     IIC_SCL_1;
 92     delay_us(4);
 93     IIC_SCL_0;
 94     delay_us(4);
 95     IIC_SDA_1;        //释放SDA
 96 }
 97 
 98 //产生非应答NACK
 99 //SCL为高电平时SDA为高电平表示非应答
100 /*
101 SCL:     ̄ ̄\__
102 SDA: ̄ ̄ ̄ ̄ ̄ ̄ ̄
103 */
104 void IIC_NAck(void)
105 {
106     IIC_SDA_1;
107     delay_us(4);
108     IIC_SCL_1;
109     delay_us(4);
110     IIC_SCL_0;
111     delay_us(4);
112 }        
113 
114 //IIC发送一个字节
115 /*
116 SCL:_  _/ ̄\__  _/ ̄ ̄\__  _/ ̄ ̄\__  _/ ̄ ̄\__  _/ ̄ ̄\__  _/ ̄ ̄\__  _/ ̄ ̄\__  _/ ̄ ̄\__
117 SDA:--  ------------  --------------  --------------  --------------  --------------  --------------  --------------  --------------
118 */
119 void IIC_WriteByte(uint8_t txd)
120 {                        
121     uint8_t i;   
122 
123     IIC_SCL_0;
124     for(i = 0; i < 8; i++)
125     {   
126         (txd & 0x80) ? IIC_SDA_1 : IIC_SDA_0;
127         txd <<= 1;
128 
129         delay_us(4);
130         IIC_SCL_1;
131         delay_us(4);
132         IIC_SCL_0;
133         delay_us(4);
134     }
135     IIC_SDA_1;
136 }        
137 
138 //读1个字节,ack=1时,发送ACK,ack=0,发送NACK
139 /*
140 SCL: ̄ ̄\__  / ̄ ̄\__  / ̄ ̄\__  / ̄ ̄\__  / ̄ ̄\__  / ̄ ̄\__  / ̄ ̄\__  / ̄ ̄\__
141 SDA:==========  ============  ============  ============  ============  ============  ============  ============  
142 */
143 uint8_t IIC_ReadByte(uint8_t ack)
144 {
145     uint8_t i, rcv = 0;
146     
147     for(i = 0; i < 8; i++)
148     {
149         rcv <<= 1;
150         IIC_SCL_1;
151         delay_us(4); 
152         if(IIC_READ_SDA()){
153             rcv++;   
154         }
155         IIC_SCL_0; 
156         delay_us(4);
157     }
158     
159     ack ? IIC_Ack() : IIC_NAck();
160     
161     return rcv;
162 }

 

posted @ 2020-08-15 10:00  心灵航帆  阅读(6725)  评论(0编辑  收藏  举报