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时序基本单元(决定了数据帧)
- 起始条件:SCL高电平期间,SDA从高电平切换到低电平(起始信号由主机产生)
- 终止条件:SCL高电平期间,SDA从低电平切换到高电平(终止信号由主机产生)
- 发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位(SCL处于高电平时,SDA上是什么电平,那么就发送什么电平到从机),所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节(主机放数据,从机读数据)
- 接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)(从机放数据,主机读数据)
- 发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据(主机接收从机发来的数据,会向从机发送应答位),数据0表示应答,数据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、