stm32串口通信协议(标准库 and HAL库)

刚入门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为单位

相关推荐
网易独家音乐人Mike Zhou5 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
zy张起灵5 小时前
48v72v-100v转12v 10A大功率转换电源方案CSM3100SK
经验分享·嵌入式硬件·硬件工程
PegasusYu8 小时前
STM32CUBEIDE FreeRTOS操作教程(九):eventgroup事件标志组
stm32·教程·rtos·stm32cubeide·free-rtos·eventgroup·时间标志组
lantiandianzi12 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件
文弱书生65612 小时前
输出比较简介
stm32
哔哥哔特商务网12 小时前
高集成的MCU方案已成电机应用趋势?
单片机·嵌入式硬件
跟着杰哥学嵌入式12 小时前
单片机进阶硬件部分_day2_项目实践
单片机·嵌入式硬件
电子科技圈13 小时前
IAR与鸿轩科技共同推进汽车未来
科技·嵌入式硬件·mcu·汽车
东芝、铠侠总代1361006839314 小时前
浅谈TLP184小型平面光耦
单片机·嵌入式硬件·物联网·平面
lantiandianzi14 小时前
基于单片机中医药柜管理系统的设计
单片机·嵌入式硬件