STM32 USART串口发送
单片机学习!
目录
前言
一、串口发送配置步骤
二、详细步骤
2.1 RCC开启USART和GPIO时钟
2.2 GPIO初始化
2.3 配置USART
2.4 开启USART
2.5 初始化总代码
三、发送数据函数设计
3.1 发送一个字节数据函数
3.2 发送一个数组函数
3.3 发送字符串函数
3.4 以字符串形式显示数字函数
总结
前言
本文介绍了USART初始化配置、USART串口发送的基础内容。
一、串口发送配置步骤
初始化流程,可以从基本结构图来梳理:
第一步,开启时钟,把需要用的USART和GPIO的时钟打开。
第二步,GPIO初始化,把TX配置成复用输出,RX配置成输入。
第三步,配置USART,直接使用一个结构体就可以把所有需要的参数都配置好。
第四步,如果只需要发送的功能,就直接开启USART,初始化就结束了。如果需要接收的功能,可能还需要配置中断,那就在开启USART之前,再加上ITCongfig和NVIC的代码就行了。
得益于库函数的封装,内部各种细节问题就不需要再关心了。
初始化完成之后
- 如果要发送数据,调用一个发送的函数就行了;
- 如果要接收数据,就调用接收的函数;
- 如果要获取发送和接收的状态,就调用获取标志位的函数。
以上就是USART外设的使用思路。
二、详细步骤
2.1 RCC开启USART和GPIO时钟
第一步开启时钟USART和GPIO的时钟。
代码示例:
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
代码示例:
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
代码示例:
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或起来。
代码示例:
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使能。
代码示例:
USART_Cmd(USART1,ENABLE);
2.5 初始化总代码
代码示例:
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引脚发送一个字节数据。
代码示例:
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函数源代码:
/**
* @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引脚,完成数据的发送。
USARTx->DR = (Data & (uint16_t)0x01FF);
调用USART_SendData这一个库函数,Byte变量就写入到TDR了,写完之后还需要等待一下,等TDR的数据转移到移位寄存器了才算一次数据完整的转移完成。如果数据还在TDR进行等待,再写入数据就会产生数据覆盖。所以在发送之后,还需要等待一下标志位。这里用while循环里套一个USART_GetFlagStatus函数通过判断标志位来循环空出等待时间。
代码示例:
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 发送一个数组函数
比如有一个很大的数组,需要通过串口发送到电脑。那就需要一个发送数组的函数。
代码示例:
void Serial_SendArray(uint8_t *Array,uint16_t Length)
{
uint16_t i;
for(i = 0 ; i < Length ; i++)
{
Serial_SendByte(Array[i]);
}
}
Serial_SendArray函数
void Serial_SendArray(uint8_t *Array,uint16_t Length)
- 第一个参数是一个uint8_t的指针类型,指向待发数组的首地址,传递数组需要使用指针。
- 第二个参数uint16_t Length由于数组无法判断是否结束,所以需要再传递一个Length进来,Length的类型可以根据需求来定义,可以是16位的,也可以是32位的。
for(i = 0 ; i < Length ; i++)
这里for循环就会执行Length次,可以对Array数据进行遍历。
在for循环里就不断调用Serial_SendByte函数,发送Array[i],这样就是依次取出数组Array的每一项,通过Serial_SendByte函数进行发送。
Serial_SendByte(Array[i]);
3.3 发送字符串函数
设计一个发送字符串的函数。
代码示例:
void Serial_SendString(char *String)
uint8_t i;
for(i = 0;String[i] != '\0';i++)
{
Serial_SendByte(String[i]);
}
}
Serial_SendString函数
void Serial_SendString(char *String)
这里参数类型给uint8_t *也可以,都是一样的效果。由于字符串自带一个结束标志位,所以就不需要再传递长度参数了。
for(i = 0;String[i] != '\0';i++)
这里循环条件的结束位就可以用字符串的结束标志位来判断了,这里数据0对应空字符,是字符串结束标志位。
- 如果循环到的数据不等于0,就是还没结束,进行循环;
- 如果循环到的数据等于0,就是结束了,停止循环。
这里数据0也可以写成字符的形式,就是'\0'空字符的转义字符表示形式,和直接写0最终效果是一样的。
Serial_SendByte(String[i]);
在for循环里不断调用Serial_SendByte函数,将String字符串一个个取出来,通过Serial_SendByte函数发送。
3.4 以字符串形式显示数字函数
给函数发送一个数字,最终这个数字能在电脑以字符串的形式显示。
代码示例:
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');
}
}
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函数里调用。
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函数也是依次发送数字的每一位这个逻辑。
代码示例:
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函数的给值需要注意:
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初始化配置以及一些配置代码的细节。