【STM32】USART串口通讯

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标准库简介

c 复制代码
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来设置, 因此该结构体内容也只有在同步模式才需要设置。

c 复制代码
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芯片来实现这个功能,这个芯片需要安装驱动。

3.2编程要点
  1. 使能RX和TX引脚GPIO时钟和USART时钟;
  2. 初始化GPIO,并将GPIO复用到USART上;
  3. 配置USART参数;
  4. 配置中断控制器并使能USART接收中断;
  5. 使能USART;
  6. 在USART接收中断服务函数实现数据接收和发送。
c 复制代码
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);
}
c 复制代码
void USART1_IRQHandler(void)
{
	uint8_t ucTemp;
	if(SET==USART_GetITStatus(USART1,USART_IT_RXNE))
	{
		ucTemp = USART_ReceiveData(USART1);
        USART_SendData(USART1,ucTemp);
	}
}
c 复制代码
int main(void)
{
	//重新设置系统时钟
	HSE_SetSysClock(8, 336, 2, 7);

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

  }
}

但是,出问题了!!!

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

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

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

c 复制代码
int main(void)
{
	//设置中断分组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
	
	Init_USART();
	
  /* Infinite loop */
  while (1)
  {

  }
}

同时也发现无乱码了。

4.使用USART打印日志

c 复制代码
#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
c 复制代码
#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);
}
c 复制代码
#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头文件。

相关推荐
森焱森20 分钟前
水下航行器外形分类详解
c语言·单片机·算法·架构·无人机
小殷学长2 小时前
【单片机毕业设计17-基于stm32c8t6的智能倒车监测系统】
stm32·单片机·课程设计
TESmart碲视4 小时前
HKS201-M24 大师版 8K60Hz USB 3.0 适用于 2 台 PC 1台显示器 无缝切换 KVM 切换器
单片机·嵌入式硬件·物联网·游戏·计算机外设·电脑·智能硬件
花落已飘5 小时前
STM32中实现shell控制台(shell窗口输入实现)
stm32·单片机·嵌入式硬件
花落已飘6 小时前
STM32中实现shell控制台(命令解析实现)
stm32·shell
没有钱的钱仔6 小时前
STM32低功耗模式全面指南
css·stm32·css3
牵牛老人7 小时前
Qt处理USB摄像头开发说明与QtMultimedia与V4L2融合应用
stm32·单片机·qt
宇钶宇夕9 小时前
针对工业触摸屏维修的系统指南和资源获取途径
单片机·嵌入式硬件·自动化
和风化雨9 小时前
stm32的三种开发方式
stm32·单片机·嵌入式硬件
kanhao10010 小时前
三态逻辑详解:单片机GPIO、计算机总线系统举例
单片机·嵌入式硬件