——Feb,21,2021
目录
本次项目采用正点原子STM32F407探索者开发版和STM32cubeIDE软件开发
- I2C协议介绍
- STM32上I2C的实现
- STM32F4探索者I2C实验
一、I2C协议介绍
1、I2C通信原理
-
I2C通讯协议(Inter-Integrated Circuit)引脚少,硬件实现简单,可扩展性强,不需要USART、CAN等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。支持双向传输,但是由于公用数据线只能实现半双工通信,大部分I2C设备支持100KHz和400KHz模式,一些设备还支持3.4MbHz高速模式。
-
I2C通信硬件十分简单,最少仅需要2条数据线:
-
SDA (Serial Data, 串行数据 )
-
SCL (Serial Clock, 串行时钟 )
-
在一些情况下需要额外的数据线寻址。
-
物理层示意图:
-
-
设备连接和寻找:每个连接到总线的设备都有一个独立的地址(7位或10位),主机可以利用这个地址进行不同设备之间的访问。其中地址若是7位二进制数表示的话,高四位为设备固有地址,后三位地址由用户设置,因此同一个设备最多可以连接8个。在通信开始后,主机发送的第一个字节为寻址信号,当从机的地址与寻址信号相匹配时才会与主机建立通信。
-
通信进程:
S
—>Slave Address
—>R/W
—>A
—>Data
->…->NA
—>P
(S:开始信号,A:应答信号,保证通信过程没有断开,NA:非应答信号,P:停止信号)
2、I2C时序图
-
起始信号(S):当时钟线 (SCL线)为高时,数据线(SDA 线)从高到低;停止信号§:当时钟线 (SCL线)是高时,数据线(SDA 线)由低到高切换。
-
传输:I2C使用SDA信号线来传输数据,使用SCL信号线进行数据同步。SDA数据线在SCL的每个时钟周期传输一位数据。传输时,SCL为高时SDA数据有效,即此时的SDA为高电平时表示数据“1”,为低电平时表示数据“0”。当SCL为低时,SDA数据无效,一般在这个时候SDA进行电平切换,为下一次表示数据做好准备。
-
应答:I2C的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。一般数据都是以字节(8bits)的形式发送在第九个CLK周期时,主机为接收模式等待从机的应答信号,若收到低电平为A信号,高电平为NA信号
二、STM32上I2C的实现
1、利用IO口模拟I2C实现通信
-
优点:可以用在任意引脚,方便外接外设;流程清楚,稳定;
-
缺点:占用CPU,效率不如;用IO口模拟,速度较慢;
-
实现方法:根据时序图软件编程
- 初始化两个IO口分别作为SDA和SCL
- S:主机发送模式,SCL为高时,SDA由高到低;P:主机发送模式,SCL为高时,SDA由低到高;
- A:主机切换为接收模式,传输一字节后SDA先拉低,在拉高SCL,高电平在持续时间中重新拉高SDA。NA则SDA不拉低。
- 数据传输:SCL为时钟,在SCL为高时数据有效。
(软件实现要注意高低电平的建立时间和持续时间保证传输正确)
-
代码可以参考正点原子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123//代码由正点原子提供
//IO方向设置
//IO操作
void IIC_Init(void) //IIC初始化
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOB_CLK_ENABLE(); //使能GPIOB时钟
//PH4,5初始化设置
GPIO_Initure.Pin=GPIO_PIN_8|GPIO_PIN_9;
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST; //快速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
IIC_SDA=1; //SDA输出1
IIC_SCL=1; //SCL输出1
}
void IIC_Start(void) //产生IIC起始信号
{
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
void IIC_Stop(void) //产生IIC停止信号
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//发送I2C总线结束信号
delay_us(4);
}
//等待应答信号到来
//返回值:1,接收应答失败;0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA){
ucErrTime++;
if(ucErrTime>250){
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
IIC_SDA=(txd&0x80)>>7;
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
2、直接利用STM32自带的硬件I2C
- 优点:传输时候为硬件传输,不占用CPU;传输稳定;速度快;支持DMA传输;
- 缺点:只能采用固定IO口;配置寄存器比较复杂;据说旧版标准库中I2C的调用存在bug,HAL库目前没有遇到问题;
- 实现方法:配置好I2C后直接调用HAL库函数即可
三、STM32F4探索者I2C实验
实验目的:利用硬件I2C实现对24c02存储芯片进行读写
1、配置相关设置
- 外部时钟开启——>时钟树配置
- LCD显示(FSMC,PB15)配置
- 按键控制配置
- 硬件I2C配置
(1)外部晶振打开,时钟树配置如下:
(2)LCD显示相关配置
详情见 STM32单片机HAL库学习MacOS(4)——LCD显示
(3)按键我们只需要用到key1与key0
(4)硬件I2C配置与硬件连接(connectivity -> I2C1)
如下图配置即可。注意:由于I2C硬件接口有多个,当我们打开I2C1接口时,默认的引脚可能不是我们所要用到的(第一次没注意还以为程序除了bug啥都没显示)根据开发版所给的资料显示,I2C接口处于PB8和PB9的IO口,因此我们需要在STM32cubeMX配置过程点击PB8和PB9手动调整。
配置好后生成代码~
2、24c02芯片连接以及读写
-
地址:24c02芯片为I2C接口,地址为7-bit格式,在开发板上A2-A0为0,最低位为读写控制。因此写的时候地址为0xA0,读地址为0xA1。
-
存储方式:查阅24c02的芯片手册可以知道24c02中数据由32页存储,每页存储8字节,寻址格式为8bit。同时24c02最多支持页写入,而读取是没有限制的。
-
读写:在芯片手册中有提到,写入最多一口气写8字节(页写入),且写入后需要等待10ms才会把数据存储好。
3、HAL库函数介绍
-
HAL库中自带I2C调用函数,可以在
“stm32f4xx_hal_i2c.c”
中查看,在不用DMA和中断情况下,主要有以下函数:-
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout); <!--code1-->
-
改写正点原子24c02的用处在于以后用到正点原子相关外设(例如触摸模块)涉及到24c02时不用额外编写新代码,可以直接兼容使用。
(2)HAL库实现
如果仔细发现上面改写的代码可能就会已经明白HAL库的直接写法,例子如下:
1 | //读取全部存储空间 |
(3)加上按键控制和显示完善例程
最后加上按键的操作就可以实现key1写入,key0读出,最后再用LCD显示。以下是完整代码。
Here~ 点击下载
END