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、