STM32 USART串口发送

单片机学习!


目录

前言

一、串口发送配置步骤

二、详细步骤

[2.1 RCC开启USART和GPIO时钟](#2.1 RCC开启USART和GPIO时钟)

[2.2 GPIO初始化](#2.2 GPIO初始化)

[2.3 配置USART](#2.3 配置USART)

[2.4 开启USART](#2.4 开启USART)

[2.5 初始化总代码](#2.5 初始化总代码)

三、发送数据函数设计

[3.1 发送一个字节数据函数](#3.1 发送一个字节数据函数)

[3.2 发送一个数组函数](#3.2 发送一个数组函数)

[3.3 发送字符串函数](#3.3 发送字符串函数)

[3.4 以字符串形式显示数字函数](#3.4 以字符串形式显示数字函数)

总结


前言

本文介绍了USART初始化配置、USART串口发送的基础内容。


一、串口发送配置步骤

初始化流程,可以从基本结构图来梳理:

第一步,开启时钟,把需要用的USART和GPIO的时钟打开。

第二步,GPIO初始化,把TX配置成复用输出,RX配置成输入。

第三步,配置USART,直接使用一个结构体就可以把所有需要的参数都配置好。

第四步 ,如果只需要发送的功能,就直接开启USART,初始化就结束了。如果需要接收的功能,可能还需要配置中断,那就在开启USART之前,再加上ITCongfig和NVIC的代码就行了。

得益于库函数的封装,内部各种细节问题就不需要再关心了。

初始化完成之后

  • 如果要发送数据,调用一个发送的函数就行了;
  • 如果要接收数据,就调用接收的函数;
  • 如果要获取发送和接收的状态,就调用获取标志位的函数。

以上就是USART外设的使用思路。

二、详细步骤

2.1 RCC开启USART和GPIO时钟

第一步开启时钟USART和GPIO的时钟。

代码示例:

cpp 复制代码
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA时钟

开启USART1的时钟,这里USART1是APB2的外设,其他都是APB1的外设。然后还需要开启GPIO的时钟,看引脚定义表,USART1的TX是PA9,RX是PA10.

2.2 GPIO初始化

第二步初始化GPIO引脚。这里初始化GPIOA,引脚选择Pin_9

代码示例:

cpp 复制代码
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//引脚模式选复用推挽输出
	GPIO_InitStruct.GPIO_Pin= GPIO_Pin_9;//引脚选择Pin_9
	GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA

引脚模式的配置选择:

  • TX引脚是USART外设控制的输出引脚,所以要选复用推挽输出。
  • RX引脚是USART外设数据的输入引脚,所以要选择输入模式。

输入模式并不分什么普通输入,复用输入。一根线只能有一个输出,但可以有多个输入。所以输入脚、外设和GPIO都可以同时用。一般RX配置是浮空输入或者上拉输入,因为串口波形空闲状态是高电平,所以不使用下拉输入。这里引脚模式的配置可以参考手册GPIO那一节推荐的配置表。本章程序只实现数据发送,所以只初始化TX就可以了。引脚模式选择GPIO_Mode_AF_PP复用推挽输出。

以上配置就是把PA9配置为复用推挽输出,供USART1的TX使用。

2.3 配置USART

第三步初始化USART

代码示例:

cpp 复制代码
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Tx;//串口模式
	USART_InitStructure.USART_Parity = USART_Parity_No;//校验位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
	USART_InitStructure.USART_WordLength =USART_WordLength_8b; //字长

	USART_Init(USART1,&USART_InitStructure);

USART_BaudRate 波特率,可以直接写一个波特率的数值。这里给9600,写完数值之后USART_Init函数内部会自动算好9600对应的分频系数,然后写到BRR寄存器。


USART_HardwareFlowControl 硬件流控制,这个参数的取值可以是:

  • USART_HardwareFlowControl_None不使用流控;
  • USART_HardwareFlowControl_CTS只用CTS;
  • USART_HardwareFlowControl_RTS只用RTS;
  • USART_HardwareFlowControl_RTS_CTS是CTS和RTS都使用。

这里不使用流控,所以选择USART_HardwareFlowControl_None这个参数。


USART_Mode串口模式,参数有:

  • USART_Mode_Tx是Tx发送模式;
  • USART_Mode_Rx是Rx接收模式。
  • 如果既需要接收又需要发送,那就用或符号把Tx和Rx或起来。

代码示例:

cpp 复制代码
USART_InitStructure.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;

这里程序只需要发送功能,所以就选择USART_Mode_Tx这一个参数就行了。


USART_Parity 校验位,参数有:

  • USART_Parity_No无校验;
  • USART_Parity_Odd奇校验;
  • USART_Parity_Even偶校验。

这里不需要校验,所以选择USART_Parity_No无校验。


USART_StopBits停止位,参数可以选择:

  • USART_StopBits_0_5 是0.5位停止位;
  • USART_StopBits_1 是1位停止位;
  • USART_StopBits_1_5 是1.5位停止位;
  • USART_StopBits_2 是2位停止位。

这里选择USART_StopBits_1参数,就是1位停止位。


USART_WordLength 字长,参数可以选择:

  • USART_WordLength_8b八位字长;
  • USART_WordLength_9b九位字长。

因为不需要校验,前面设置了无校验参数,这里就选择USART_WordLength_8b字长为8位。

以上结构体参数的初始化就完成了,对串口的配置是9600波特率、无流控、只有发送模式、无校验位、1位停止位、八位字长。

2.4 开启USART

第四步,开启USART,调用USART_Cmd函数,给指定的通道USART1使能。

代码示例:

cpp 复制代码
    USART_Cmd(USART1,ENABLE);

2.5 初始化总代码

代码示例:

cpp 复制代码
void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIO的时钟
	
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//引脚模式
	GPIO_InitStruct.GPIO_Pin= GPIO_Pin_9;//引脚选择Pin_9
	GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Tx;//串口模式
	USART_InitStructure.USART_Parity = USART_Parity_No;//校验位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
	USART_InitStructure.USART_WordLength =USART_WordLength_8b; //字长
	USART_Init(USART1,&USART_InitStructure);
	
	USART_Cmd(USART1,ENABLE);
	
}

三、发送数据函数设计

3.1 发送一个字节数据函数

这里设计一个发送数据的函数,调用这个函数就可以从TX引脚发送一个字节数据。

代码示例:

cpp 复制代码
void Serial_SendByte(uint8_t Byte)
{
	//函数里面需要调用串口的SendData函数
	USART_SendData(USART1,Byte);//第一个参数给USART1,第二个参数给Byte
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); //第一个参数给USART1,第二个参数需要使用USART_FLAG_TXE发送数据寄存器空标志位。因为需要等待TXE置1,所以给USART_GetFlagStatus函数套一个while循环。
	
}

Serial_SendByte函数里面需要调用USART_SendData函数,USART_SendData函数可以通过外设 USART1 发送单个数据。

USART_SendData(USART1,Byte);第一个参数给USART1,第二个参数给Byte,

这样可以在调用Serial_SendByte函数时把参数Byte的值通过外设 USART1 发送出去。


从USART_SendData函数定义内部代码可以更清楚的知道数据是怎么发送出去的。

USART_SendData函数源代码:

cpp 复制代码
/**
  * @brief  Transmits single data through the USARTx peripheral.
  * @param  USARTx: Select the USART or the UART peripheral. 
  *   This parameter can be one of the following values:
  *   USART1, USART2, USART3, UART4 or UART5.
  * @param  Data: the data to transmit.
  * @retval None
  */
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
{
  /* Check the parameters */
  assert_param(IS_USART_ALL_PERIPH(USARTx));
  assert_param(IS_USART_DATA(Data)); 
    
  /* Transmit Data */
  USARTx->DR = (Data & (uint16_t)0x01FF);
}

这里定义的参数Byte传递给Data这个变量,之后Data&0x01FF,就是把无关的高位清零,然后直接赋值给DR寄存器。因为这是写入DR,所以数据最终通向TDR,发送数据寄存器,TDR再传递给发送移位寄存器。最后一位一位地把数据移出到TX引脚,完成数据的发送。

cpp 复制代码
USARTx->DR = (Data & (uint16_t)0x01FF);

调用USART_SendData这一个库函数,Byte变量就写入到TDR了,写完之后还需要等待一下,等TDR的数据转移到移位寄存器了才算一次数据完整的转移完成。如果数据还在TDR进行等待,再写入数据就会产生数据覆盖。所以在发送之后,还需要等待一下标志位。这里用while循环里套一个USART_GetFlagStatus函数通过判断标志位来循环空出等待时间。

代码示例:

cpp 复制代码
    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); 

USART_GetFlagStatus函数的第一个参数给USART1,第二个参数需要使用USART_FLAG_TXE发送数据寄存器空标志位。

因为需要等待TXE置1,所以还是给USART_GetFlagStatus函数套一个while循环,如果TXE标志位==RESTE,就一直循环,直到为SET时,结束等待。

最后是标志位是否需要手动清除的问题,需要参考一下手册,在状态寄存器这里有对TXE这一位的描述。

当TDR寄存器中的数据被硬件转移到移位寄存器的时候,该位被硬件置位。如果USART_CR1寄存器中的TXEIE为1,就是允许中断,则产生中断。对USART_DR的写操作,将该位清零。

所以说这里标志位置1之后,不需要手动清零。当下一次再调用USART_SendData函数时,这个标志位会自动清零。以上Serial_SendByte函数就编写完成了。

3.2 发送一个数组函数

比如有一个很大的数组,需要通过串口发送到电脑。那就需要一个发送数组的函数。

代码示例:

cpp 复制代码
void Serial_SendArray(uint8_t *Array,uint16_t Length) 
{
	uint16_t i;
	for(i = 0 ; i < Length ; i++)
	{
		Serial_SendByte(Array[i]);
	}
}

Serial_SendArray函数

cpp 复制代码
void Serial_SendArray(uint8_t *Array,uint16_t Length) 
  • 第一个参数是一个uint8_t的指针类型,指向待发数组的首地址,传递数组需要使用指针。
  • 第二个参数uint16_t Length由于数组无法判断是否结束,所以需要再传递一个Length进来,Length的类型可以根据需求来定义,可以是16位的,也可以是32位的。

cpp 复制代码
   for(i = 0 ; i < Length ; i++)

这里for循环就会执行Length次,可以对Array数据进行遍历。

在for循环里就不断调用Serial_SendByte函数,发送Array[i],这样就是依次取出数组Array的每一项,通过Serial_SendByte函数进行发送。

cpp 复制代码
        Serial_SendByte(Array[i]);

3.3 发送字符串函数

设计一个发送字符串的函数。

代码示例:

cpp 复制代码
void Serial_SendString(char *String)
	uint8_t i;
	for(i = 0;String[i] != '\0';i++)
	{
		Serial_SendByte(String[i]);
	}
}

Serial_SendString函数

cpp 复制代码
void Serial_SendString(char *String)

这里参数类型给uint8_t *也可以,都是一样的效果。由于字符串自带一个结束标志位,所以就不需要再传递长度参数了。


cpp 复制代码
    for(i = 0;String[i] != '\0';i++)

这里循环条件的结束位就可以用字符串的结束标志位来判断了,这里数据0对应空字符,是字符串结束标志位。

  • 如果循环到的数据不等于0,就是还没结束,进行循环;
  • 如果循环到的数据等于0,就是结束了,停止循环。

这里数据0也可以写成字符的形式,就是'\0'空字符的转义字符表示形式,和直接写0最终效果是一样的。


cpp 复制代码
        Serial_SendByte(String[i]);

在for循环里不断调用Serial_SendByte函数,将String字符串一个个取出来,通过Serial_SendByte函数发送。


3.4 以字符串形式显示数字函数

给函数发送一个数字,最终这个数字能在电脑以字符串的形式显示。

代码示例:

cpp 复制代码
uint32_t Serial_Pow(uint32_t X,uint32_t Y)//这个函数的返回值就是X的Y次方
{
	uint32_t Result = 1;
	while(Y--)
	{
		Result *= X;
	}
	return Result;
}


void Serial_SendNumber(uint32_t Number,uint8_t Length)
{
	uint8_t i;
	for(i = 0;i < Length;i++)
	{
		Serial_SendByte(Number / Serial_Pow(10,Length - i - 1) %10 + '0');
	}
}

cpp 复制代码
void Serial_SendNumber(uint32_t Number,uint8_t Length)

在函数Serial_SendNumber里面需要把Number的个位、十位、百位等位数以十进制拆分开。然后转换成字符数字对应的数据,依次发送出去。

以十进制拆分方法,用数字12345来举例:

  • 取万位就是12345除以10000的值1再对10取余 (12345/10000)%10=1;
  • 取千位就是12345除以1000的值12再对10取余 (12345/1000)%10=2;
  • 取百位就是12345除以100的值123再对10取余 (12345/100)%10=3;
  • 取十位就是12345除以10的值1234再对10取余 (12345/10)%10=4;
  • 取个位就是12345除以1的值再对10取余 (12345/1)%10=5.

总结:

取某一位就是数字除以10的x次方再对10取余(数字/10^x)%10,除以10的x次方就是把这一位数的右边去掉,再对10取余就是把这一位数的左边去掉。

需要先写一个次方函数Serial_Pow函数,此函数用于十进制拆分逻辑,可供Serial_SendNumber函数里调用。

cpp 复制代码
uint32_t Serial_Pow(uint32_t X,uint32_t Y)//这个函数的返回值就是X的Y次方
{
    uint32_t Result = 1;
    while(Y--)//循环Y次,就是Result累乘Y次X,也就是X的Y次方
    {
        Result *= X;
    }
    return Result;
}

Serial_SendNumber函数也是依次发送数字的每一位这个逻辑。

代码示例:

cpp 复制代码
void Serial_SendNumber(uint32_t Number,uint8_t Length)
{
	uint8_t i;
	for(i = 0;i < Length;i++)
	{
		Serial_SendByte(Number / Serial_Pow(10,Length - i - 1) %10 + '0');
	}
}

这里Serial_Pow函数的给值需要注意:

cpp 复制代码
        Serial_SendByte(Number / Serial_Pow(10,Length - i - 1) %10 + '0');

按照十进制拆分方法的公式取出Number的每一位。for循环中从i开始遍历是从0开始的,10的0次方是个位,第一数据应该是最高位而不是个位,所以要让遍历方向反过来Serial_Pow函数的第二个参数要是Length - i - 1,这样循环起来参数就会以十进制从高位到低位依次发送。不过最终要以字符的形式显示,查询ASCII表发现字符0对应的数据是0x30,所以最后还需要加0x30或者以字符的形式写就是'0'


总结

以上就是今天要讲的内容,本文仅仅简单介绍了USART初始化配置以及一些配置代码的细节。

相关推荐
ShiinaKaze1 分钟前
【MCU】微控制器的编程技术:ISP 与 IAP
单片机·嵌入式硬件·mcu·iap·isp
北京迅为12 分钟前
【北京迅为】iTOP-4412全能版使用手册-第十二章 Linux系统编程简介
linux·嵌入式硬件·4412开发板
不能只会打代码1 小时前
51单片机从入门到精通:理论与实践指南入门篇(二)
单片机·嵌入式硬件·51单片机
憧憬一下3 小时前
IMX 平台UART驱动情景分析:read篇--从硬件驱动到行规程的全链路剖析
arm开发·嵌入式硬件·嵌入式·linux驱动开发
cd_farsight4 小时前
单片机位数对性能会产生什么影响?!
单片机·嵌入式硬件
honey ball8 小时前
LLC与反激电路设计【学习笔记】
单片机·嵌入式硬件·学习
Graceful_scenery14 小时前
STM32F103外部中断配置
stm32·单片机·嵌入式硬件
猿来不是梦17 小时前
RT_Thread内核源码分析(三)——线程
嵌入式硬件·系统架构·rt_thread操作系统
白书宇17 小时前
19.QT程序简单的运行脚本
linux·arm开发·嵌入式硬件·物联网·arm