单片机学习!
目录
[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初始化配置以及一些配置代码的细节。