stm32的串口USART编程要点
先初始化串口所用到的GPIO;
初始化串口,配置pUSART_InitTypeDef结构体;
配置中断NVIC(接收中断,中断优先级);
使能串口;
编写发送和接收函数;
编写中断服务函数;
接下在看具体的代码实现过程:
USART初始化配置函数,不难但是过程挺多的,容易遗漏,代码如下:
// 串口1 USART1
#define DEBUG_USARTx USART1
#define DEBUG_USART_CLK RCC_APB2Periph_USART1
#define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
// USART GPIO引脚宏定义
#define DEBUG_UASRT_GPIO_CLK RCC_APB2Periph_GPIOA
#define DEBUG_UASRT_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_UASRT_TX_GPIO_PORT GPIOA
#define DEBUG_UASRT_TX_GPIO_PIN GPIO_Pin_9
#define DEBUG_UASRT_RX_GPIO_PORT GPIOA
#define DEBUG_UASRT_RX_GPIO_PIN GPIO_Pin_10
#define DEBUG_UASRT_IRQn USART1_IRQn
#define DEBUG_UASRT_IRQHandler USART1_IRQHandler
void NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStruct.NVIC_IRQChannel = DEBUG_UASRT_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
void USART_config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
// 开启串口的GPIO时钟
DEBUG_UASRT_GPIO_APBxClkCmd(DEBUG_UASRT_GPIO_CLK, ENABLE);
// USART的TX配置为复用推挽输出
GPIO_InitStruct.GPIO_Pin = DEBUG_UASRT_TX_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_UASRT_TX_GPIO_PORT, &GPIO_InitStruct);
// USART的RX配置为浮空输入(由中文参考手册查询)
GPIO_InitStruct.GPIO_Pin = DEBUG_UASRT_RX_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_UASRT_RX_GPIO_PORT, &GPIO_InitStruct);
// 开启串口时钟
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
// 配置串口参数(波特率、8位数据、1位停止位、无校验、发送接收模式、无硬件流控)
USART_InitStruct.USART_BaudRate = DEBUG_USART_BAUDRATE;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(DEBUG_USARTx, &USART_InitStruct);
// 设置NVIC
NVIC_Config();
// 使能串口接收中断
USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
// 使能串口
USART_Cmd(DEBUG_USARTx, ENABLE);
}
完成串口初始化配置后,就可以进行串口收发数据的测试
串口发送函数
void USART_SendByte(USART_TypeDef* pUSARTx, uint8_t data)
{
USART_SendData(pUSARTx, data);
// 当发送数据时,发送数据寄存器非空,TXE标志位首先为0
// 然后程序便会等待,直至数据从DR转移到移位寄存器,此时TXE = 1,TC = 0
// 当数据全部从移位寄存器发出后,TC = 1
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
这里值得注意的是,串口调试助手有一个特点,不管接收到什么数据,显示的都是字符。当我测试发送数据100时,显示的是字符d,如下图所示,这是为什么?关键还是在于ASCII码。
USART_SendByte(DEBUG_USARTx, 100);
这是串口助手打印出来的信息(默认不√HEX显示,这样就是输出的字符,对应ASCII),和我们预期的打印输出100完全不同,接下来分析原因。
如下是ASCII表。可以看到,串口调试助手,将接收到的数据(100)转换成字符d(ASCII值)并显示,所以,我们如果是发送数据0X64,串口助手同样会打印字符d。同理,如果是电脑的串口助手给单片机发数据,比如发1,单片机在解析时,要认为这是字符' 1 ',而不是数值1,这一点要非常注意。
如果我们勾选了hex显示,那么串口助手就会显示接收到的数据(100)对应的十六进制数(64),如下图:
既然串口调试助手默认显示字符,那我们就可以直接打印字符(使用单引号' A '),如下,字符正确显示。
USART_SendByte(DEBUG_USARTx, 'A');
发送16位数据函数
/* 发送两个字节的数据 */
void USART_SendHalfWord(USART_TypeDef* pUSARTx, uint16_t data)
{
uint8_t temp_h, temp_l; // 16位数据的高8位和低8位
temp_h = (data & 0xff00) >> 8;
temp_l = data & 0xff;
USART_SendData(pUSARTx, temp_h);
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
USART_SendData(pUSARTx, temp_l);
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
发送8位数组的函数
等待最后一个8位数据从移位寄存器中发送完毕,然后TC从0变为1(如果是0则一直等待)
之前单个字节其实也应该再加一个TC判断,之所以没加,是为了提高效率
单字节读数据到DR,转移到移位寄存器,然后马上再读数据到DR(不用等到移位完毕再读取数据)
/* 发送8位数据的数组 */
void USART_SendArray(USART_TypeDef* pUSARTx, uint8_t *array, uint8_t num)
{
uint8_t i;
// 直接发送num次8位数据
for (i = 0; i < num; i++)
{
USART_SendByte(pUSARTx, array[i]);
}
// 等待最后一个8位数据从移位寄存器中发送完毕,然后TC从0变为1(如果是0则一直等待)
// 之前单个字节其实也应该再加一个TC判断,之所以没加,是为了提高效率
// 单字节读数据到DR,转移到移位寄存器,然后马上再读数据到DR(不用等到移位完毕再读取数据)
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
}
为了使用printf,我们要重定义fputc和fgetc函数,将文件流向串口,具体如下:
// fputc,是函数。函数功能: 将字符ch写到文件指针fp所指向的文件的当前写指针的位置。
// 函数格式:int fputc (int c, FILE *fp)。fp为文件指针,它的值是执行fopen()打开文件时获得的。
/* 重定向c库函数printf到串口 */
int fputc(int ch, FILE *f)
{
/* 把ch发送到串口 */
USART_SendData(DEBUG_USARTx, (uint8_t)ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return ch;
}
/* 重定向fgetc库函数到scanf串口 */
int fgetc(FILE *f)
{
/* 等待串口输入数据 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(DEBUG_USARTx);
}
附(bsp_usart.c和bsp_usart.h)
/************************************* USART.C *******************************/
#include "bsp_usart.h"
#include void NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStruct; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStruct.NVIC_IRQChannel = DEBUG_UASRT_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); } void USART_config(void) { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; // 开启串口的GPIO时钟 DEBUG_UASRT_GPIO_APBxClkCmd(DEBUG_UASRT_GPIO_CLK, ENABLE); // USART的TX配置为复用推挽输出 GPIO_InitStruct.GPIO_Pin = DEBUG_UASRT_TX_GPIO_PIN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DEBUG_UASRT_TX_GPIO_PORT, &GPIO_InitStruct); // USART的RX配置为浮空输入(由中文参考手册查询) GPIO_InitStruct.GPIO_Pin = DEBUG_UASRT_RX_GPIO_PIN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(DEBUG_UASRT_RX_GPIO_PORT, &GPIO_InitStruct); // 开启串口时钟 DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE); // 配置串口参数(波特率、8位数据、1位停止位、无校验、发送接收模式、无硬件流控) USART_InitStruct.USART_BaudRate = DEBUG_USART_BAUDRATE; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_InitStruct.USART_StopBits = USART_StopBits_1; USART_InitStruct.USART_Parity = USART_Parity_No; USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_Init(DEBUG_USARTx, &USART_InitStruct); // 设置NVIC NVIC_Config(); // 使能串口接收中断 USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE); // 使能串口 USART_Cmd(DEBUG_USARTx, ENABLE); } /* 发送一个字节的数据 */ void USART_SendByte(USART_TypeDef* pUSARTx, uint8_t data) { USART_SendData(pUSARTx, data); // 当发送数据时,发送数据寄存器非空,TXE标志位首先为0 // 然后程序便会等待,直至数据从DR转移到移位寄存器,此时TXE = 1,TC = 0 // 当数据全部从移位寄存器发出后,TC = 1 while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); } /* 发送两个字节的数据 */ void USART_SendHalfWord(USART_TypeDef* pUSARTx, uint16_t data) { uint8_t temp_h, temp_l; // 16位数据的高8位和低8位 temp_h = (data & 0xff00) >> 8; temp_l = data & 0xff; USART_SendData(pUSARTx, temp_h); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); USART_SendData(pUSARTx, temp_l); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); } /* 发送8位数据的数组 */ void USART_SendArray(USART_TypeDef* pUSARTx, uint8_t *array, uint8_t num) { uint8_t i; // 直接发送num次8位数据 for (i = 0; i < num; i++) { USART_SendByte(pUSARTx, array[i]); } // 等待最后一个8位数据从移位寄存器中发送完毕,然后TC从0变为1(如果是0则一直等待) // 之前单个字节其实也应该再加一个TC判断,之所以没加,是为了提高效率 // 单字节读数据到DR,转移到移位寄存器,然后马上再读数据到DR(不用等到移位完毕再读取数据) while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET); } /* 发送字符串 */ void USART_SendString(USART_TypeDef* pUSARTx, uint8_t *str) { uint8_t i = 0; do { USART_SendByte(pUSARTx, *(str + i)); i++; } while (*(str + i) != '