STM32(M3)-通信协议
# I2C协议 - AT24C02
# I2C总线介绍
- I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线
- 两根通信线:SCL(Serial Clock)、SDA(Serial Data)
- 同步、半双工,带数据应答
- 通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度
- 所有I2C设备的SCL连在一起,SDA连在一起
# 总线结构
# I2C协议
- 空闲状态
- 开始信号
- 停止信号
- 应答信号
- 数据的有效性
- 数据传输
# 空闲状态
2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
# 起始信号与停止信号
起始条件:SCL高电平期间,SDA从高电平切换到低电平
终止条件:SCL高电平期间,SDA从低电平切换到高电平
# 应答信号
发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
# 数据有效性
I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
即:数据在SCL的上升沿到来之前就需准备好。并在在下降沿到来之前必须稳定。
# 数据传输
# 发送一个字节
发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
# 接收一个字节
接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
# I2C数据帧
# 接收一帧数据
# 发送一帧数据
# 先发送再接收数据帧(复合格式)
# AT24C02
- AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息
- 存储介质:E2PROM
- 通讯接口:I2C总线
- 容量:256字节
# 引脚及应用电路
# AT24C02数据帧
字节写:在WORD ADDRESS处写入数据DATA
随机读:读出在WORD ADDRESS处的数据DATA
AT24C02的固定地址为1010,可配置地址本开发板上为000 所以SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS+R为0xA1
# 32中代码实现
# IIC.h
#ifndef __IIC_H
#define __IIC_H
#include "sys.h"
#define IIC_Port GPIOC // 定义port
#define SDA GPIO_Pin_11 // SDA数据引脚
#define SCL GPIO_Pin_12 // SCL时钟引脚
#define SDA_HIGH GPIO_SetBits(IIC_Port,SDA) // SDA高电平
#define SDA_LOW GPIO_ResetBits(IIC_Port,SDA) // SDA低电平
#define SCL_HIGH GPIO_SetBits(IIC_Port,SCL) // SCL高电平
#define SCL_LOW GPIO_ResetBits(IIC_Port,SCL) // SCL高电平
#define SDA_READ GPIO_ReadInputDataBit(IIC_Port,SDA) // 读取SDA电平
// 为0时表示输入 1时表示输出
void IIC_SDA_IO(u8 io);
// 初始化
void IIC_Init(void);
// 开始
void IIC_Start(void);
// 截止
void IIC_Stop(void);
// 等待应答 1 非应答 0 应答
u8 IIC_Wait_Ack();
// 产生ACK
void IIC_Ack(void);
// 产生非ACK
void IIC_NAck();
// 发送数据
void IIC_SendByte(u8 Data);
// 接收数据
u8 IIC_Read_Byte();
#endif
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
# I2C.c
#include "iic.h"
#include "delay.h"
#include "usart.h"
// io 为0时表示输入 1时表示输出
void IIC_SDA_IO(u8 io){
GPIO_InitTypeDef GPIO_InitStructure;
if(io == 0) { // 输入
GPIO_InitStructure.GPIO_Pin = SDA;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(IIC_Port, &GPIO_InitStructure);
}else{ // 输出
GPIO_InitStructure.GPIO_Pin = SDA;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IIC_Port, &GPIO_InitStructure);
}
}
// 初始化
void IIC_Init(){
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitStructure.GPIO_Pin = SDA | SCL;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IIC_Port, &GPIO_InitStructure);
GPIO_SetBits(IIC_Port,SDA | SCL);
SCL_HIGH;
SDA_HIGH;
}
// 开始
void IIC_Start(){
IIC_SDA_IO(1); // SDA输出
SCL_HIGH;
SDA_HIGH;
delay_us(4);
SDA_LOW;
delay_us(4);
SCL_LOW; // 钳住IIC总线,准备发送数据
}
// 截止
void IIC_Stop(void){
IIC_SDA_IO(1); // SDA输出
SCL_LOW;
SDA_LOW;
delay_us(4);
SCL_HIGH;
SDA_HIGH;
delay_us(4);
}
// 发送数据
void IIC_SendByte(u8 Data){
u8 i;
IIC_SDA_IO(1); // SDA输出
SCL_LOW; // 拉低时钟开始数据传输
for(i = 0 ; i < 8 ; i++) {
if((Data & 0x80) >> 7) {
SDA_HIGH;
}else {
SDA_LOW;
}
Data <<= 1; // 左移一位
delay_us(2);
SCL_HIGH;
delay_us(2);
SCL_LOW;
delay_us(2);
}
}
// 接收数据
u8 IIC_Read_Byte(){
u8 i;
u8 Data = 0x00;
IIC_SDA_IO(0); // SDA输入
for(i = 0 ; i < 8 ; i++) {
SCL_LOW;
delay_us(2);
SCL_HIGH;
Data <<= 1;
if(SDA_READ) { // 数据为1
Data++;
}
}
return Data;
}
// 等待应答 1 非应答 0 应答
u8 IIC_Wait_Ack() {
u8 Timer_Ack;
IIC_SDA_IO(0); // SDA输入
SDA_HIGH; delay_us(1);
SCL_HIGH;delay_us(1);
while(SDA_READ) {
Timer_Ack++;
if( Timer_Ack > 250) {
return 1;
}
}
SCL_LOW;
return 0;
}
// 产生ACK
void IIC_Ack(void) {
IIC_SDA_IO(1); // SDA输出
SCL_LOW;
SDA_LOW;
delay_us(2);
SCL_HIGH;
delay_us(2);
SCL_LOW;
}
// 产生非ACK
void IIC_NAck(){
IIC_SDA_IO(1); // SDA输出
SCL_LOW;
SDA_HIGH;
delay_us(2);
SCL_HIGH;
delay_us(2);
SCL_LOW;
}
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# AT24C02.h
#ifndef __AT24C02_H
#define __AT24C02_H
#include "sys.h"
#define WRITE 0xA0
#define READ 0xA1
// 初始化
void AT24C02_Init(void);
// 发送一个字节
void AT24C02_SendByte(u8 Address, u8 Data);
// 读一个字节
u8 AT24C02_ReadByte(u8 Address);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# AT24C02.c
#include "iic.h"
#include "delay.h"
#include "at24c02.h"
#include "usart.h"
u8 ack;
// 初始化
void AT24C02_Init(void){
IIC_Init();
}
// 发送一个字节
void AT24C02_SendByte(u8 Address,u8 Data){
IIC_Start();// 开始
IIC_SendByte(WRITE); // 写命令
ack = IIC_Wait_Ack(); // 等待ACK
printf("1: %d\r\n",ack);
IIC_SendByte(Address); // 数据地址
ack = IIC_Wait_Ack(); // 等待ACK
printf("2: %d\r\n",ack);
IIC_SendByte(Data); // 写数据
ack = IIC_Wait_Ack(); // 等待ACK
printf("3: %d\r\n",ack);
IIC_Stop();// 截止
delay_ms(10);
}
// 读一个字节
u8 AT24C02_ReadByte(u8 Address){
u8 Data;
IIC_Start();// 开始
IIC_SendByte(WRITE); // 写命令
ack = IIC_Wait_Ack(); // 等待ACK
printf("4: %d\r\n",ack);
IIC_SendByte(Address); // 地址
ack = IIC_Wait_Ack(); // 等待ACK
printf("5: %d\r\n",ack);
IIC_Start();// 开始
IIC_SendByte(READ); // 读命令
ack = IIC_Wait_Ack(); // 等待ACK
printf("6: %d\r\n",ack);
Data = IIC_Read_Byte(); // 读取数据
IIC_NAck(); // 相应Ack
IIC_Stop();// 截止
return Data;
}
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
# mian.c
实现的效果:每隔一秒递增向E2PROM中存储数据,再读取数据
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "lcd.h"
#include "at24c02.h"
u8 Data = 0;
u8 Data_Address = 0;
int main(void)
{
delay_init(); //延时函数初始化
uart_init(9600); //串口初始化为9600
LED_Init(); //初始化与LED连接的硬件接口
LCD_Init();
AT24C02_Init();
while(1)
{
AT24C02_SendByte(Data_Address % 100,Data_Address % 100);
Data = AT24C02_ReadByte(Data_Address % 100);
LCD_ShowString(30,20,200,12,24,"EPPROM:");
LCD_ShowNum(30,60,Data,3,12);
Data ++;
Data_Address ++;
delay_ms(1000);
}
}
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