STM32-通信协议

一、通信协议

通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统

通信协议:制定通信的规则,通信双方按照协议规则进行数据收发

名称 引脚 双工 时钟 电平 设备
USART TX(Transmit Exchange)、RX 全双工 异步 单端 点对点
I2C SCL(Serial Clock)、SDA(Serial Data) 半双工 同步 单端 多设备
SPI SCLK、MOSI、MISO、CS 全双工 同步 单端 多设备
CAN CAN_H、CAN_L 半双工 异步 差分 多设备
USB DP(Data Positive)、DM (Data Minus) 半双工 异步 差分 点对点
  • 全双工:一般具有两根数据线,同时允许数据的接收和发送
  • 半双工:只有一根数据线,接收和发送只允许一个
  • 单工:数据只能从一个设备到另一个设备
  • 时钟特性:决定了数据采集的时机
  • 同步通信:双方在同一个时钟信号的控制下,进行数据的接收和发送
  • 异步通信:发送的字符之间的时间间隔可以是任意的
  • 单端信号:必须要有GND线
  • 差分信号:可以不用GND线

二、串口通信

串口是一种应用十分广泛的通讯接口,可实现两个设备的互相通信(RS232,RS485)。

这个外设的作用就是,按照串口协议来产生和接收高低电平信号实现串口通信。

1、硬件电路

  • 简单双向串口通信必须有两根通信线(发送端TX和接收端RX)
  • TX与RX要交叉连接(一个设备的发送接另一个设备的接收)
示例图片

2、电平标准

电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:

  • TTL电平(USART):+3.3V或+5V表示1,0V表示0
  • RS232电平:-3-15V表示1,+3+15V表示0
  • RS485电平:两线压差+2+6V表示1,-2-6V表示0(差分信号)

3、串口参数及时序(数据帧)

  • 波特率:串口通信的速率(防止数据不对,决定了每隔多少时间发送一个数据)
  • 起始位:标志一个数据帧的开始,固定为低电平(数据线空闲状态为高电平)
  • 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
  • 校验位:用于数据验证,根据数据位计算得来
  • 停止位:用于数据帧间隔,固定为高电平

无校验位(10):

示例图片

有校验位(11):

示例图片

三、USART(CH340)

CH340:USB转串口,这里的串口就是UASRT

1、USART简介

  • USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器
  • USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
  • 低位先行(低位数据在时序图的前面)

2、USART基本结构

  • 波特率发生器:用于产生约定的通讯速率
  • 时钟来源:PCLK2/1
  • 发送数据寄存器:数据存储的地方(低位先行)
  • 发送移位寄存器:控制数据一位一位的传输
示例图片

3、配置流程(USART发送+接收数据包)

  • 开启USART和TX和RX对应的GPIO引脚的外设时钟

  • 初始化GPIO结构体GPIO_InitTypeDef(TX引脚—复用推挽输出和RX引脚—浮空输入模式)

  • 初始化USART结构体USART_InitTypeDef

  • 开关控制

  • 接收数据的话,需要判断串口是否接收到了数据,可以利用查询和中断机制

    查询:在主函数里不断判断接受标志位

    中断:中断注册函数

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
124
125
126
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;

/* 嵌套向量中断控制器组选择 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

/* 配置USART为中断源 */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
/* 抢断优先级*/
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
/*子优先级 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
/* 使能中断 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
/* 初始化配置NVIC */
NVIC_Init(&NVIC_InitStructure);
}


void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;

//打开串口外设和GPIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);

//将USART TX的GPIO配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

//将USART RX的GPIO配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);

//配置串口的工作参数
//配置波特率
USART_InitStructure.USART_BaudRate = 115200;
//配置字长(8位或者9位)
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
//配置停止位
USART_InitStructure.USART_StopBits = USART_StopBits_1;
// 配置无校验位
USART_InitStructure.USART_Parity = USART_Parity_No ;
//配置无硬件流控制
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
// 配置工作模式,收发一起
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
// 完成串口初始化配置
USART_Init(USART1, &USART_InitStructure);

// 串口中断优先级配置
//NVIC_Configuration();

// 串口接收中断使能,开启中断
//USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

//开关控制,使能串口
USART_Cmd(USART1, ENABLE);
}

/* 发送一个字节数据 */
void Usart_SendByte(uint8_t data)
{
USART_SendData(USART1, data);
//判断发送数据寄存器TDR的数据是否转移到发送移位寄存器中了
while( USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET );
}
uint8_t Serial_RxData;
uint8_t Serial_RxFlag;

uint8_t Serial_GetRxFlag(void)
{
if(Serial_RxFlag==1)
{
Serial_RxFlag=0;
return 1;
}
return 0;
}

uint8_t Serial_GetRxDate(void)
{
return Serial_RxData;
}

void USART1_IRQHandler(void)
{
uint8_t Rxdata;
if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
{
Serial_RxData=USART_ReceiveData(USART1); //读DR会自动清除标志位
Serial_RxFlag=1;
USART_ClearITPendingBit(USART1,USART_IT_RXNE); //可清可不清
}
}

int main(void)
{
uint8_t Rxdata;


USART_Config();
Usart_SendByte(0X41);

while (1)
{
//普通查询
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET) //收到数据了
{
Rxdata=USART_ReceiveData(USART1); //读DR会自动清除标志位
printf("Rxdata1=%x\n",Rxdata);
}

//中断处理
if(Serial_GetRxFlag()==1) //收到数据了
{
Rxdata=Serial_GetRxDate();
printf("Rxdata2=%x\n",Rxdata);
}
}
}

四、IIC

1、IIC简介

  • I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线
  • 两根通信线:SCL(Serial Clock)、SDA(Serial Data)
  • 同步,半双工
  • 带数据应答
  • 高位先行(高位数据在时序图的前面)
  • 支持总线挂载多设备(一主多从、多主多从)

2、硬件电路(一主多从)

  • 所有I2C设备的SCL连在一起,SDA连在一起
  • 设备(主机)的SCL和SDA均要配置成开漏输出模式
  • SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右(保持SCL和SDA起始为高电平)
示例图片

3、IIC时序基本单元(决定了数据帧)

  1. 起始条件:SCL高电平期间,SDA从高电平切换到低电平(起始信号由主机产生)
示例图片
  1. 终止条件:SCL高电平期间,SDA从低电平切换到高电平(终止信号由主机产生)
示例图片
  1. 发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位(SCL处于高电平时,SDA上是什么电平,那么就发送什么电平到从机),所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节(主机放数据,从机读数据)
示例图片
  1. 接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)(从机放数据,主机读数据)
示例图片
  1. 发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据(主机接收从机发来的数据,会向从机发送应答位),数据0表示应答,数据1表示非应答
示例图片
  1. 接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据(主机向从机发送数据,会接收到从机发来的应答位),判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
示例图片

4、读写

4.1、指定地址写

对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)

R/W:读写位,0=读

RA:应答位,应答=0

时序图:

时序图的意思(高位先行):对于从机地址为0xD0的设备,向其内部0x19的地址的寄存器中,写入0xAA的数据

示例图片
4.2、当前地址读

对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)

时序图:

示例图片
4.3、指定地址读(指定地址写+当前地址读的时序图的组合)

对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)

Sr:重复起一个起始条件,是为了切换读写方向

时序图:

对于从机地址为0xD1的设备,在其内部0x19的地址的寄存器下,读取0xAA的数据

示例图片

5、配置流程

5.1、硬件IIC(首选)

硬件IIC是由STM32内部的硬件模块实现的,使用CPU的时钟信号来控制数据传输和时序

  • 开启GPIO和IIC的外设时钟
  • 配置GPIO结构体GPIO_InitTypeDef
  • 配置IIc结构体 I2C_InitTypeDef
  • 开关控制:I2C_Cmd
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
#include "stm32f10x.h"

void i2c_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;

// 打开GPIOB和I2C1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

// 配置PB6和PB7为复用推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

// 配置I2C1控制器
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 100000; // I2C时钟频率为100kHz
I2C_Init(I2C1, &I2C_InitStructure);

// 启动I2C1控制器
I2C_Cmd(I2C1, ENABLE);
}

void i2c_write_byte(uint8_t addr, uint8_t reg, uint8_t data)
{
// 发送起始信号
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

// 发送设备地址和写命令
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

// 发送寄存器地址
I2C_SendData(I2C1, reg);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

// 发送数据
I2C_SendData(I2C1, data);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

// 发送停止信号
I2C_GenerateSTOP(I2C1, ENABLE);
}

uint8_t i2c_read_byte(uint8_t addr, uint8_t reg)
{
uint8_t data;

// 发送起始信号
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

// 发送设备地址和写命令
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

// 发送寄存器地址
I2C_SendData(I2C1, reg);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

// 发送重复起始信号
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

// 发送设备地址和读命令
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Receiver);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));

// 读取数据
I2C_AcknowledgeConfig(I2C1, DISABLE);
I2C_GenerateSTOP(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
data = I2C_ReceiveData(I2C1);

return data;
}
5.2、软件IIC

软件IIC是由CPU的GPIO模拟实现的,通过CPU的软件来控制时序和数据传输

五、SPI

1、SPI简介

  • SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
  • 四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)
  • 同步,全双工
  • 高位先行(高位数据在时序图的前面)
  • 支持总线挂载多设备(一主多从)

2、SPI基本结构

  • 接收数据寄存器RDR和发送数据寄存器TDR:数据存放的区域
  • 数据控制器:控制所有电路的运行
  • 波特率发生器:产生时钟到SCK
  • 开关控制:SPI_Cmd
示例图片

3、SPI硬件电路

  • 所有SPI设备的SCK、MOSI、MISO分别连在一起
  • 主机另外引出多条SS控制线,分别接到各从机的SS引脚
  • 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
示例图片

4、移位示意图

移位寄存器:数据左移

数据交换前:

示例图片

交换一位后:

将数据放到通信线上

示例图片

将数据放到移位寄存器中

示例图片

5、SPI时序基本单元

1、起始条件:SS从高电平切换到低电平(低电平有效)

示例图片

2、终止条件:SS从低电平切换到高电平

示例图片

3、交换一个字节(模式0)(用的最多)

  • CPOL=0:空闲状态时,SCK为低电平(SCK为高电平开始输入输出)
  • CPHA=0:SCK第一个边沿移入数据(第奇次上升沿进行数据采样),第二个边沿移出数据(先采集数据,后输出数据)
  • 通信开始后,需要立马开始移出数据
示例图片

4、交换一个字节(模式1)

  • CPOL=0:空闲状态时,SCK为低电平
  • CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据(第偶次上升沿进行数据采样)(先输出数据,后采集数据)
示例图片

5、交换一个字节(模式2)

  • CPOL=1:空闲状态时,SCK为高电平(SCK为低电平开始输入输出)
  • CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
  • 通信开始后,需要立马开始移出数据
示例图片

6、交换一个字节(模式3)

  • CPOL=1:空闲状态时,SCK为高电平
  • CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
示例图片

6、读写

6.1、发送指令

进行通信开始前,会向SS指定的设备,发送指令(0x06)–(指令码)

示例图片
6.2、指定地址写

向SS指定的设备,发送写指令(0x02),随后在指定地址(Address[23:0])下,写入指定数据(Data)

示例图片
6.3、指定地址读

向SS指定的设备,发送读指令(0x03),随后在指定地址(Address[23:0])下,读取从机数据(Data)

示例图片

7、配置流程

7.1、硬件SPI
  • 开始GPIO和SPI’的外设时钟RCC
  • 初始化GPIO的结构体GPIO_InitTypeDef
  • 初始化SPI的结构体SPI_InitTypeDef
  • 开关控制:SPI_Cmd
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
void GPIO_Config()
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_AFIODeInit();

//SPI-Clock:PA5 SPI-MISO:PA6 SPI-MOSI:PA7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}

void SPI_Config()
{
SPI_InitTypeDef SPI_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;

SPI_Init(SPI1, &SPI_InitStructure);

// SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, ENABLE);
// SPI_I2S_ClearFlag(SPI1, SPI_I2S_IT_RXNE | SPI_I2S_IT_TXE);

SPI_Cmd(SPI1, ENABLE);
}
7.2、软件SPI

六、CAN

1、CAN总结

2、