刚入门32,接触的第一个通信协议就是串口通信,所谓通信协议,就是指两个设备之间互联,将两个设备关联起来,使其可以相互之间传输数据,利用串口通信最常见的例子就是蓝牙模块。
下面为学习串口之后的一些简单的小总结:
1.串口一般都有固定的引脚,比如c8t6上USART1的TX为PA9,RX为PA10,当然,也可以将某些特定的引脚复用成USART1的TX,RX,两个设备之间进行串口通信时,TX与RX要互相交叉连接,且一定要共地,如果两个设备有单独供电的话,可以不用连接VCC
2.使用串口的时候,如果要与电脑端通信将数据打印再串口调试助手上,则电脑端要安装CH340驱动,且stm32端如果没有内置CH340模块的话,则必须用CH340将32与电脑端连接
3.串口基本结构:
4.串口初始化:
开启时钟,将所需的USART与GPIO的时钟打开,然后GPIO初始化,将TX配置为复用输出,RX为输入,最后利用USART的初始化结构体,将其余参数全部配置好,如果选择通过中断的方式接受,则需要配置NVIC中断优先级与开启中断
cpp
void USART_Init(void){
//GPIO初始化结构体
GPIO_InitTypeDef GPIO_InitStructure;
//USART初始化结构体
USART_InitTypeDef USART_InitStructure;
// 打开串口GPIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
// 打开串口外设的时钟
RCC_APB2PeriphClockCmd(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;
// 配置 针数据字长
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(DEBUG_USARTx, &USART_InitStructure);
/*//当需要中断的时候
// 串口中断优先级配置
NVIC_Configuration();
// 使能串口接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
*/
// 使能串口
USART_Cmd(USART1, ENABLE);
}
5.串口发送一个字节
cpp
void Usart_SendByte(USART_TypeDef* pUSARTx,uint8_t data){
//调用库函数发送一个字节的数据
USART_SendData(pUSARTx, data);
//判断发送标志位,若数据没来得及发送,下一个数据就过来了,则会产生数据覆盖,所以要用标志位来
//判断是否能进行下一次发送
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET);
}
6.将fputc函数重定向,使用print打印数据
cpp
int fgetc(FILE *f){
/* 等待串口输入数据 */
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
使用print时,注意,需要在魔术棒里面勾选使用微库,并在USART头文件里引用头文件<stdio.h>
只能重定向fputc到一个串口,若多个串口需要使用print,则可以使用sprint
cpp
char String[100];
sprintf(String,"Num=%d\r\n",666);
Usart_SendString(String);
7.利用阻塞的方式,读取串口数据(没成功读取数据就一直占用CPU)
cpp
while(1){
if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET){
RxData = USART_ReceiveData(USART1);
}
}
读完DR之后不用手动清除标志位
8.通过中断的方式读取串口数据
在初始化USART时,配置NVIC,开启中断
cpp
void NVIC_Configuration(void){
NVIC_InitTypeDef NVIC_InitStructure;
/* 嵌套向量中断控制器组选择 */
/* 提示 NVIC_PriorityGroupConfig() 在整个工程只需要调用一次来配置优先级分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* 配置USART为中断源 */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
/* 抢断优先级*/
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 子优先级 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中断 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
/* 初始化配置NVIC */
NVIC_Init(&NVIC_InitStructure);
}
cpp
// 串口中断优先级配置
NVIC_Configuration();
// 使能串口接收中断(USART_IT_RXNE标志位一旦置1,就会向NVIC申请中断,然后在中断服务函数里面接收数据)
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
之后在中断服务函数里面执行用户所需代码
cpp
void USART1_IRQHandler(void){
if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET){
//执行用户所需中断代码(可以直接读取DR)
//RxData = USART_ReceiveData(USART1);
//清除标志位
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
9.通过将数据打包进行发送,和将打包的数据解码出来
根据内容,数据包通常分为HEX数据包与文本数据包;根据长度,数据包分为固定包长的数据包与可变包长的数据包。
发送HEX数据包:
cpp
//已经打包好的数据包
uint8_t USART_TxPacket[4];
void USART_SendPacket(){
//发送帧头
Usart_SendByte(0xFF);
//发送数据包
Usart_SendArray(USART_TxPacket,4);
//发送帧尾
Usart_SendByte(0xFE);
}
接受HEX数据包(定义中断服务函数):
cpp
void USART1_IRQHandler(){
//定义静态变量,使用状态机,来控制数据的接受
static uint8_t RxState =0;
static uint8_t pRxPacket =0;
if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)){
uint8_t RxData = USART_ReceiveData(USART1);
//判断帧头
if(RxState == 0 && RxData == 0xFF){
RxState = 1;
pRxPacket = 0;
}
//接收数据
else if(RxState == 1){
USART_RxPacket[pRxPacket] = RxData;
pRxPacket++;
if(pRxPacket >= 4)
RxState = 2;
}
//判断帧尾
else if(RxState == 2 && RxData == 0xFE){
RxState = 0;
}
//清除标志位
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
接受文本数据包(与接受HEX数据包类似):
cpp
void USART1_IRQHandler(){
//定义静态变量,使用状态机,来控制数据的接受
static uint8_t RxState =0;
static uint8_t pRxPacket =0;
if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)){
uint8_t RxData = USART_ReceiveData(USART1);
//判断帧头
if(RxState == 0 && RxData == '@'){
RxState = 1;
pRxPacket = 0;
}
//接收数据
else if(RxState == 1){
if(RxData == '\r'){
RxState = 2;
}
else{
USART_RxPacket[pRxPacket] = RxData;
pRxPacket++;
}
}
//判断帧尾
else if(RxState == 2 && RxData == '\n'){
RxState = 0;
//在数据末尾加上'\0',将数据处理成完整的字符串
USART_RxPacket[pRxPacket] = '\0';
}
//清除标志位
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
HAL库外设初始化MSP回调机制:
在HAL库里,一般采用HAL_USART_Init()来初始化某一个具体的外设,HAL_USART_Init()待传入的参数是一个USART的句柄结构体,其最主要的部分就是Instance成员和Init成员,Instance作为一个指针指向一个具体的外设,比如
cpp
UartHandle.Instance = USART1;
而Init就是USART初始化结构体,将参数配置好后,调用Init函数即可完成初始化。
cpp
void USART_Config(void){
UartHandle.Instance = USART1;
UartHandle.Init.BaudRate = 115200;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&UartHandle);
/*使能串口接收断 */
__HAL_UART_ENABLE_IT(&UartHandle,UART_IT_RXNE);
}
在HAL库里调用HAL_UART_Init()之后,程序会自动调用HAL_UART_MspInit()(在HAL库被__weak修饰,用户可自行选择是否定义),用户可在该函数里面配置UART外设所用到的硬件:GPIO,NVIC以及时钟
cpp
void HAL_UART_MspInit(UART_HandleTypeDef *huart){
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
/* 配置Tx引脚为复用功能 */
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 配置Rx引脚为复用功能 */
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode=GPIO_MODE_AF_INPUT; //模式要设置为复用输入模式!
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(USART1_IRQn ,0,1); //抢占优先级0,子优先级1
HAL_NVIC_EnableIRQ(USART1_IRQn ); //使能USART1中断通道
}
HAL库中断回调机制:
HAL_UART_IRQHandler():用户在中断服务函数中调用HAL库共用中断函数,然后再调用一系列中断回调函数(都被__weak修饰,用户自行选择定义与否)
HAL库配置串口步骤:
开启串口接受中断:
cpp
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef * huart,uint8_t * pData,uint16_t Size)
发送数据(以阻塞的方式,占用CPU):
cpp
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size, uint32_t Timeout)
第一个参数为一个串口句柄结构体,第二个参数为指向发送数据缓冲区,第三个参数为要发送的数据大小,以字节为单位,第四个参数为超时时间,以ms为单位