STM32之IO口模拟IIC

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 }

相关文章

fun函数在c语言中如何使用
Bet体育365验证提款

fun函数在c语言中如何使用

⌛ 07-28 👁️ 6246
Lineage 介绍
Bet体育365验证提款

Lineage 介绍

⌛ 07-13 👁️ 1565
汽滤怎么更换(汽滤可以自己换吗)
Bet体育365验证提款

汽滤怎么更换(汽滤可以自己换吗)

⌛ 08-08 👁️ 8647