【STM32】USART串口通讯

2024-06-26 1990阅读

1.USART简介

STM32芯片具有多个USART外设用于串口通讯,它是 Universal Synchronous Asynchronous Receiver and Transmitter的缩写, 即通用同步异步收发器可以灵活地与外部设备进行全双工数据交换。有别于USART, 它还有具有UART外设(Universal Asynchronous Receiver and Transmitter),它是在USART基础上裁剪掉了同步通信功能, 只有异步通信。简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是UART。

USART在STM32应用最多莫过于“打印”程序信息,一般在硬件设计时都会预留一个USART通信接口连接电脑, 用于在调试程序是可以把一些调试信息“打印”在电脑端的串口调试助手工具上,从而了解程序运行是否正确、指出运行出错位置等等。

USART只需两根信号线(TX和RX)即可完成双向通信,对硬件要求低,使得很多模块都预留USART接口来实现与其他模块或者控制器进行数据传输, 比如GSM模块,WIFI模块、蓝牙模块等等。在硬件设计时,注意还需要一根“共地线”。

2.USART标准库简介

typedef struct {
    uint32_t USART_BaudRate;            // 波特率
    uint16_t USART_WordLength;          // 字长
    uint16_t USART_StopBits;            // 停止位
    uint16_t USART_Parity;              // 校验位
    uint16_t USART_Mode;                // USART模式
    uint16_t USART_HardwareFlowControl; // 硬件流控制
} USART_InitTypeDef;
  1. USART_BaudRate:波特率设置。一般设置为2400、9600、19200、115200。标准库函数会根据设定值计算得到USARTDIV值, 并设置USART_BRR寄存器值。

  2. USART_WordLength:数据帧字长,可选8位或9位。它设定USART_CR1寄存器的M位的值。如果没有使能奇偶校验控制, 一般使用8数据位;如果使能了奇偶校验则一般设置为9数据位。

  3. USART_StopBits:停止位设置,可选0.5个、1个、1.5个和2个停止位, 它设定USART_CR2寄存器的STOP[1:0]位的值,一般我们选择1个停止位。

  4. USART_Parity:奇偶校验控制选择,可选USART_Parity_No(无校验)、 USART_Parity_Even(偶校验)以及USART_Parity_Odd(奇校验),它设定USART_CR1寄存器的PCE位和PS位的值。

  5. USART_Mode:USART模式选择,有USART_Mode_Rx和USART_Mode_Tx, 允许使用逻辑或运算选择两个,它设定USART_CR1寄存器的RE位和TE位。

  6. USART_HardwareFlowControl:硬件流控制选择,只有在硬件流控制模式才有效, 可选有使能RTS、使能CTS、同时使能RTS和CTS、不使能硬件流。

当使用同步模式时需要配置SCLK引脚输出脉冲的属性,标准库使用一个时钟初始化结构体USART_ClockInitTypeDef来设置, 因此该结构体内容也只有在同步模式才需要设置。

typedef struct {
    uint16_t USART_Clock;    // 时钟使能控制
    uint16_t USART_CPOL;     // 时钟极性
    uint16_t USART_CPHA;     // 时钟相位
    uint16_t USART_LastBit;  // 最尾位时钟脉冲
} USART_ClockInitTypeDef;
  1. USART_Clock:同步模式下SCLK引脚上时钟输出使能控制,可选禁止时钟输出(USART_Clock_Disable)或开启时钟输出(USART_Clock_Enable); 如果使用同步模式发送,一般都需要开启时钟。它设定USART_CR2寄存器的CLKEN位的值。

  2. USART_CPOL:同步模式下SCLK引脚上输出时钟极性设置,可设置在空闲时SCLK引脚为低电平(USART_CPOL_Low)或高电平(USART_CPOL_High)。 它设定USART_CR2寄存器的CPOL位的值。

  3. USART_CPHA:同步模式下SCLK引脚上输出时钟相位设置,可设置在时钟第一个变化沿捕获数据(USART_CPHA_1Edge)或在时钟第二个变化沿捕获数据。 它设定USART_CR2寄存器的CPHA位的值。USART_CPHA与USART_CPOL配合使用可以获得多种模式时钟关系。

  4. USART_LastBit:选择在发送最后一个数据位的时候时钟脉冲是否在SCLK引脚输出,可以是不输出脉冲(USART_LastBit_Disable)、 输出脉冲(USART_LastBit_Enable)。它设定USART_CR2寄存器的LBCL位的值。

3.USART示例

3.1硬件设计

为利用USART实现开发板与电脑通信,需要用到一个USB转USART的IC,我们选择CH340芯片来实现这个功能,这个芯片需要安装驱动。

【STM32】USART串口通讯

3.2编程要点
  1. 使能RX和TX引脚GPIO时钟和USART时钟;
  2. 初始化GPIO,并将GPIO复用到USART上;
  3. 配置USART参数;
  4. 配置中断控制器并使能USART接收中断;
  5. 使能USART;
  6. 在USART接收中断服务函数实现数据接收和发送。
void Init_USART(void)
{
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);//使能GPIOA时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
	
    //USART1对应引脚复用映射
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource9,GPIO_AF_USART1);//PA9复用为USART1
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource10,GPIO_AF_USART1);//PA10复用为USART1
	
	//USART1端口配置
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9|GPIO_Pin_10;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;//复用功能
	GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;//推挽复用输出
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_UP;//上拉
	GPIO_InitStruct.GPIO_Speed=GPIO_Fast_Speed;//速度50MHz
	GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化PA9,PA10
		
	//配置USART参数
	USART_InitTypeDef USART_Init_Struct;
	USART_Init_Struct.USART_BaudRate=115200;
	USART_Init_Struct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	USART_Init_Struct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
	USART_Init_Struct.USART_Parity=USART_Parity_No;
	USART_Init_Struct.USART_StopBits=USART_StopBits_1;
	USART_Init_Struct.USART_WordLength=USART_WordLength_8b;
	USART_Init(USART1,&USART_Init_Struct);
	
	//配置中断控制器并使能USART接收中断
	NVIC_InitTypeDef NVIC_Init_Struct;
	NVIC_Init_Struct.NVIC_IRQChannel=USART1_IRQn;
	NVIC_Init_Struct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init_Struct.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_Init_Struct.NVIC_IRQChannelSubPriority=0;
	NVIC_Init(&NVIC_Init_Struct);
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	//使能USART
	USART_Cmd(USART1,ENABLE);
}
void USART1_IRQHandler(void)
{
	uint8_t ucTemp;
	if(SET==USART_GetITStatus(USART1,USART_IT_RXNE))
	{
		ucTemp = USART_ReceiveData(USART1);
        USART_SendData(USART1,ucTemp);
	}
}
int main(void)
{
	//重新设置系统时钟
	HSE_SetSysClock(8, 336, 2, 7);
	//设置中断分组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
	
	Init_USART();
	
  /* Infinite loop */
  while (1)
  {
  }
}

但是,出问题了!!!

【STM32】USART串口通讯

出现乱码了!!!到底是哪里出问题了呢???

最后,我们在USART_Init函数中发现蹊跷了。关于波特率的设置,它通过配置值115200,然后通过系统时钟去计算应该配置的寄存器的值。但是,系统时钟的获取函数好像有点问题,因为之前那个宏定义的值不对,所以我们在main函数中又自己实现了系统时钟配置函数。详细可以看【STM32】时钟树系统-CSDN博客。现在看来,我们还是的修改标准库文件了,因为它的函数接口都在使用那个错误的宏定义。

所以,修改了标准库文件中的两个宏定义后,我们就可以在main函数中省略自己配置系统时钟函数了。

int main(void)
{
	//设置中断分组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
	
	Init_USART();
	
  /* Infinite loop */
  while (1)
  {
  }
}

同时也发现无乱码了。

【STM32】USART串口通讯

4.使用USART打印日志

#ifndef __BSP_USART_H
#define __BSP_USART_H
#ifdef __cplusplus
extern "C"{
#endif
#include "stm32f4xx.h"
#include "stdio.h"
#define LOGGER_USART              USART1
#define LOGGER_USART_BAUDRATE     115200
#define LOGGER_USART_CLK          RCC_APB2Periph_USART1
#define LOGGER_USART_IRQHandler   USART1_IRQHandler
#define LOGGER_USART_IRQ          USART1_IRQn
#define USART1_TX_PIN             GPIO_Pin_9
#define USART1_TX_GPIO_Port       GPIOA
#define USART1_TX_GPIO_CLK        RCC_AHB1Periph_GPIOA
#define USART1_TX_AF              GPIO_AF_USART1
#define USART1_TX_SOURCE          GPIO_PinSource9
#define USART1_RX_PIN             GPIO_Pin_10
#define USART1_RX_GPIO_Port       GPIOA
#define USART1_RX_GPIO_CLK        RCC_AHB1Periph_GPIOA
#define USART1_RX_AF              GPIO_AF_USART1
#define USART1_RX_SOURCE          GPIO_PinSource10
void Init_USART(void);
	
#ifdef __cplusplus
}
#endif
#endif
#include "bsp_usart.h"
void Init_USART(void)
{
	RCC_AHB1PeriphClockCmd(USART1_TX_GPIO_CLK|USART1_RX_GPIO_CLK,ENABLE);//使能GPIOA时钟
	RCC_APB2PeriphClockCmd(LOGGER_USART_CLK,ENABLE);//使能USART1时钟
	
    //USART1对应引脚复用映射
	GPIO_PinAFConfig(USART1_TX_GPIO_Port, USART1_TX_SOURCE,USART1_TX_AF);//PA9复用为USART1
	GPIO_PinAFConfig(USART1_RX_GPIO_Port, USART1_RX_SOURCE,USART1_RX_AF);//PA10复用为USART1
	
	//USART1端口配置
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Pin=USART1_TX_PIN;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;//复用功能
	GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;//推挽复用输出
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_UP;//上拉
	GPIO_InitStruct.GPIO_Speed=GPIO_Fast_Speed;//速度50MHz
	GPIO_Init(USART1_TX_GPIO_Port,&GPIO_InitStruct);//初始化PA9
	
	GPIO_InitStruct.GPIO_Pin=USART1_RX_PIN;
	GPIO_Init(USART1_RX_GPIO_Port,&GPIO_InitStruct);//初始化PA10
		
	//配置USART参数
	USART_InitTypeDef USART_Init_Struct;
	USART_Init_Struct.USART_BaudRate=LOGGER_USART_BAUDRATE;
	USART_Init_Struct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	USART_Init_Struct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
	USART_Init_Struct.USART_Parity=USART_Parity_No;
	USART_Init_Struct.USART_StopBits=USART_StopBits_1;
	USART_Init_Struct.USART_WordLength=USART_WordLength_8b;
	USART_Init(LOGGER_USART,&USART_Init_Struct);
	
	//配置中断控制器并使能USART接收中断
	NVIC_InitTypeDef NVIC_Init_Struct;
	NVIC_Init_Struct.NVIC_IRQChannel=LOGGER_USART_IRQ;
	NVIC_Init_Struct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init_Struct.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_Init_Struct.NVIC_IRQChannelSubPriority=0;
	NVIC_Init(&NVIC_Init_Struct);
	USART_ITConfig(LOGGER_USART,USART_IT_RXNE,ENABLE);
	
	//使能USART
	USART_Cmd(LOGGER_USART,ENABLE);
}
void LOGGER_USART_IRQHandler(void)
{
	uint8_t ucTemp;
	if(SET==USART_GetITStatus(LOGGER_USART,USART_IT_RXNE))
	{
		ucTemp = USART_ReceiveData(LOGGER_USART);
        USART_SendData(LOGGER_USART,ucTemp);
	}
}
//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
    /* 发送一个字节数据到串口 */
    USART_SendData(LOGGER_USART, (uint8_t) ch);
    /* 等待发送完毕 */
    while (USART_GetFlagStatus(LOGGER_USART, USART_FLAG_TXE) == RESET);
    return (ch);
}
#include "bsp_usart.h"
void delay(uint32_t cnt)
{
	while(cnt--);
}
int main(void)
{
	//设置中断分组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
	
	Init_USART();
	
  /* Infinite loop */
  while (1)
  {
	  printf("hello,i am logger!\r\n");
	  delay(0xfffffff);
  }
}

这样,我们就可以使用printf函数直接输出到串口上了。

在C语言标准库中,fputc函数是printf函数内部的一个函数,功能是将字符ch写入到文件指针f所指向文件的当前写指针位置, 简单理解就是把字符写入到特定文件中。我们使用USART函数重新修改fputc函数内容,达到类似“写入”的功能。

fgetc函数与fputc函数非常相似,实现字符读取功能。在使用scanf函数时需要注意字符输入格式。

还有一点需要注意的,使用fput和fgetc函数达到重定向C语言标准库输入输出函数必须在MDK的工程选项把“Use MicroLIB”勾选上, MicoroLIB是缺省C库的备选库,它对标准C库进行了高度优化使代码更少,占用更少资源。

为使用printf、scanf函数需要在文件中包含stdio.h头文件。

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]