蓝桥杯-嵌入式3-USART
STM32外设之USART
对于STM32G4系列微控制器来说,共有三个USART(USART1…3)和一个UART(UART4)。在我们板子上用到的是USART1。其中PA9引脚对应USART_TX、PA10引脚对应USART_RX。
当串口接收数据时,数据通过RX引脚,进入到(RX Shift Reg)移位寄存器中,然后通过移位将数据传入到RxFIFO中,然后再传到USART_RDR中。
当串口发送数据时,数据从USART_TDR中传到TxFIFO中,然后再传到(TX Shift Reg)移位寄存器中,然后再一位一位的将数据从TX引脚发送出去。
其中USART_RDR和USART_TDR都是常用的8位寄存器,通过CR1控制寄存器的M1和M0位控制7/8/9位。
如下图所示为发送步骤时序图
步骤分别为:
如下图所示为接收的步骤和时序图:
从上述发送与接收的步骤和时序中可以看出,发送时不需要用到中断,而接收时需要用到中断。如下图所示:
收发程序编写
程序编写一共包括两个部分,一部分是STM32CUBEMX的配置,另外一部分是Keil5中的程序编写。
STM32CUBEMX配置
由于USART_TX和USART_RX分别对应PA9和PA10引脚,所以如下图所示,选中这两个引脚。
一个为开漏输入一个为开漏输出,两个引脚都为无上下拉。
配置此中断优先级为1,和抢占优先级为0。
配置的波特率为9600Bit/s,数据长度为8,其他都为默认。
时钟树配置80Mhz。
生成代码即可,接下来为Keil5编写代码。
Keil5程序编写
STM32CUBEMX生成如下初始化函数:
#include "usart\bsp_usart.h"
UART_HandleTypeDef huart1;
void USART1_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 9600;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART1)
{
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
}
在串口的头文件中定义;
#include "main.h"
extern UART_HandleTypeDef huart1;
void USART1_Init(void);
在stm32g4xx_it.c中,多了一个串口的中断
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
}
由于此函数中的huart1在其他c文件中有定义所以直接把其他头文件引过来即可
#include "usart\bsp_usart.h"
然后进入bsp_usart.h中调用的main.h,然后在进入main.h中的stm32g4xx_hal.h,然后再进入stm32g4xx_hal_conf.h,在这里面,由于串口没有使用,要把串口使能,如下代码所示:
#define HAL_UART_MODULE_ENABLED
接下来是在主函数中写程序:
首先要控制串口的执行速度,此处用到了滴答定时器,如下所示;
__IO uint32_t uwTick_Usart_Set_Point =0;//控制Usart_Proc的执行速度
//*串口专用变量
int counter = 0;
char str[40];
unsigned char rx_buffer;
if ((uwTick - uwTick_Usart_Set_Point) <= 500)
{
return;
}
uwTick_Usart_Set_Point = uwTick;
发数据
首先是初始化,然后进行向外打印数据
USART1_Init();
void Usart_Proc(void)
{
if ((uwTick - uwTick_Usart_Set_Point) <= 500)
{
return;
}
uwTick_Usart_Set_Point = uwTick;
sprintf(str,"%04d:Hello,world.\r\n",counter);
HAL_UART_Transmit(&huart1,(unsigned char *)str,strlen(str),50);//发送的消息的地址,内容,长度,时间
HAL_Delay(500);
if(++counter == 10000)
{
counter = 0;
}
}
接数据
同样是先初始化,然后一个中断接收函数,当程序运行时,运行到中断接收函数是会一直在while中等待,知道单片机接收到数据,会跳入到中断处理函数USART1_IRQHandler,跳入到中断处理函数后,会直接调用一个回调函数,所以写一个中断回调函数,此回调函数中用led演示为例子。代码如下所示;
unsigned char rx_buffer;//定义一个变量用来存放数据
HAL_UART_Receive_IT(&huart1,&rx_buffer,1);//打开中断,当有数据时,一个字节存入到rx_buffer这个地址上,再来一个再存入这覆盖掉,一直接收
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);//当串口数据接收时,直接进入此处理函数
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//然后跳入此中断回调函数,执行此中程序。
{
LED_Disp(0xff);
HAL_Delay(300);
LED_Disp(0x00);
HAL_UART_Receive_IT(&huart1,&rx_buffer,1);
}//当执行完后再次进入主函数中
滴答定时器补充内容
由于在此接收程序中使用到了滴答定时器延时,即在接收中断中嵌套了滴答定时器,如果想要滴答定时器打断接收中断让回调函数可以运行,那么滴答定时器的优先级一定要高于串口中断优先级,否则程序将无法运行。滴答定时器的优先级可在如下函数内修改:
HAL_Init();
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);//此设置优先级组,此设置为4个抢占优先级,无子优先级
HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK
#define TICK_INT_PRIORITY (0UL)//此处将滴答定时器的优先级改为0,即抢占优先级最高