STM32学习笔记(四)——STM32的USART和串口通信
STM32学习笔记(四)——STM32的USART和串口通信
一、串口通信
1.1 串口简介
Tx:Transmit-发送
Rx:Receive-接收
1.2 串口接线
1.3 串口的数据帧格式
1.3.1 数据帧的概念
通过01传输数据,为了防止一次性传很多数据时混乱,将数据分组传输,一组叫一个数据帧
一个数据帧率的构成:8个bit加上帧头和帧尾
1.3.2 数据帧的具体格式
起始位-帧头
数据位-传输的数据
停止位-帧尾
1.3.2.1 空闲
串口中数据传输最基本的单元为数据帧
数据帧1,停顿,数据帧2;3;4,停顿…
这些停顿就是空闲,用高电平表示
1.3.2.2 起始位
起始位:表示数据帧的开始,实际中用1位低电平表示起始位
1.3.2.3 数据位
数据位:用于数据传输
总是先传低位
可选用传8bit数据/9bit数据
1.3.2.4 校验位
校验位:为了检验数据传输是否出错而设立的比特位
校验位在数据位的内部
校验原理----奇偶校验
奇校验:数据位+校验位 有奇数个1
1.3.2.5 停止位
停止位:表示数据帧的结束,高电平,长度可以位0.5/1/1.5/2
1.3.3 单个数据发送
eg:发送0x5b 9位数据位,偶校验位,1位停止位
1.3.3 连续发送
前一个数据的停止位结束后紧接着下一个数据的起始位
1.4 异步通信和波特率
1.4.1 同步通信
同步通信:收发双方共享时钟
eg:IIC
1.4.2 异步通信
异步通信:收发双方不共享时钟
eg:串口通信
1.4.3 波特率
- 异步通信才会考虑波特率
- 波特率:每秒传输码元的个数
- 每一个高低电平 称做一个码元
1.5 硬件流控
概念:
- 若A一直发,不管B有没有收到,会发送数据错乱
- 故A发送一句,B在接收完以后,回复一句收到,A再发下一句,这样有条不紊,不会出错
二、STM32的USART
2.1 USART简介
2.1.1 简介
USART这个片上外设 就是stm32内部的串口
2.1.2 USART和UART的区别
USART:有同步和异步功能
UART:仅有异步功能
2.1.3 USART的使用
通过CPU,将要发送数据的字节,写入发送数据寄存器(TDR),串口收到数据后,会将数据通过TX引脚发送出去
2.2 USART的工作原理
2.2.1 串并转换电路
串行通信:使用一根数据线,逐个发送
并行通信:多根数据线,一次发送多个bit位
串行转并行电路:输入是串行,输出是并行
为什么要进行串并转换?
CPU对数据是并行写入和读取,串口发送接收则是串行的
2.2.2 USART的完整框图
TDR:发送数据寄存器:
RDR:接收数据寄存器
CR:配置寄存器,配置传输数据的参数,如数据位采用9个bit还是8个bit,是否使用校验位,停止位长度等
SR:状态寄存器:读取USART的运行状态,如如何知道,写入的数据被发送完成了?查询SR寄存器中某位的值。
BRR:波特率寄存器
IER:中断使能寄存器
2.2.3 USART参数配置
2.2.3.1 数据帧格式设置
配置寄存器(CR):
- M:数据位长度:M=0 8位;M=1 9位
- PCE:奇偶校验使能,数据位的最后一位被使能位校验位 PCE=0 禁用;PCE=1 使能
- PS:奇偶选择 PS=0 偶校验;PS=1 奇校验
- STOP:停止位长度:
- STOP=00 1位
- STOP=01 0.5位
- STOP=10 2位
- STOP=11 1.5位
2.2.3.2 数据传输方向的选择
通过配置寄存器,再软件层面决定数据传输的方向,是A传到B、B传到A、还是A和B互相都传输
TXE:0断开;1闭合
RXE:0断开;1闭合
2.2.3.3 波特率设置
USART是一个片上外设,需要使能时钟,以片上外设的时钟为基础,通过不同的分配,产生不同的时钟频率,从而得到不同的波特率
假设片上时钟为72000000,我们想得到9600,只需要让灰色的部分(分频系数)为7500即可,所以分频器上的值为468.75,这样72000000➗468.75➗16=9600
怎么让分频器上值为468.75呢?
- 用到波特率寄存器BRR:一共16位 (上图),再对应位置写1即可
2.2.3.4 USART的总开关
利用UE bit位
2.2.4 数据的发送过程
2.2.4.1 双缓冲与连续发送
拥有两级缓冲,解决了连续发送的问题, 但是会有两个问题
- 数据发送过快,导致数据覆盖问题?
- 数据发送什么完成的问题?
- 通过读取SR状态寄存器的值来解决,查询当前数据是否发送完成等状态
2.2.4.2 TXE标志位
为了避免数据覆盖,每次发送数据前,都查询TXE的寄存器是否为0
TXE(Transmit Data Register Empty)发送数据寄存器空 空为1;非空为0
2.2.4.3 TC标志位
作用同上
红色部分为空 TC=1;红色为非空 TC=0
2.2.4.4 单个数据的发送
- 等待TDR的清空
- 向TDR写入数据
- 等待数据发送完成
2.2.4.5 数据的连续发送
注意!右侧的代码才正确
2.2.5 数据的接收过程
读数据不能太早也不能太晚,我们应该选择恰当的实际读取数据
2.2.5.1 RXNE标志位
状态寄存器中的RXEN标志位
RXEN=0 RDR寄存器中无数据
RXEN=1 RDR寄存器中有数据
有数据时应该立马将RDR中数据读出来
2.2.5.2 接收单个数据
- 等待RDR中有数据
- 将数据提取出来
2.2.5.3 接收多个数据
固定的代码 记住!
2.3 错误标志位
如何判断数据的传输是否有错误呢
检验错误的程序,只会发生再接收程序里,故如何使用呢?
三、USART的标准库编程
3.1 实验简介
使用STM32的USART外设,与电脑进行通信
- 1.定位使用STM32上的哪个引脚
- 法1:查看最小系统板引脚分布图,找复用功能中的USART
- 法2: 查找复用功能重映射表
- 2.完整的接线图
3.2 标准库编程接口
介绍五大常用编程接口:
3.2.1 USART_Init()
USART_Init():设置配置寄存器,和波特率寄存器
- 参数1:串口几
- 参数2:初始化调查问卷:
- USART_WordLength:数据位个数(8位/9位)
- USART_Parity:奇偶校验选择(odd奇、Even偶、No不校验)
- USART_StopBits:停止位长度(0.5/1/1.5/2)
- USART_Mode:通信方向(收/发/双向通信)
- USART_BaudRate:波特率(9600/115200…)
- USART_HardwareFlowControl:硬件流控
3.2.2 USART_Cmd()
USART_Init():设置总开关
- 参数1:串口几
- 参数2:开/关
3.2.3 USART_SendData()
USART_SendData():设置总开关
- 参数1:串口几
- 参数2:发送的数据(8个bit)
3.2.4 USART_ReceiveData()
USART_ReceiveData():设置总开关
- 参数1:串口几
3.2.5 USART_GetFlagStatus()
USART_GetFlagStatus():设置总开关
- 参数1:串口几
- 参数2:要读取的标志位名称(如USART_FLAG_TXE…)
3.3 USART的初始化
- 初始化IO引脚:用到了TX和RX引脚,来和外部设备的TX和RX引脚交叉连接,或其它用到的引脚,如时钟,硬件流控引脚…
- 使能USART时钟:USART是STM32的一个片上外设,为了节能,大部分片上外设默认都是关闭的,所以要先使能时钟
- 配置USART参数:配置CR寄存器(选择数据帧格式)、配置BRR寄存器(波特率)
- 闭合总开关
3.3.1 IO引脚初始化
1.猜IO引脚参数
- TX:USART间接控制的引脚,选复用;且可输出高、低,选复用推挽;输出速度根据手册10MHZ
- RX:选上拉模式,因为串口数据帧格式的空闲状态默认为高电平
2.查看权威手册,验证猜想
查看中文参考手册的----8.1.11 外设的GPIO配置表格解析:
- 双工:可发送数据也可接收数据
- 全双工:数据的接收和发送可同时进行
- 半双工:接收数据时,不能发送数据 (很少用到,如上图)
- 完整参数表格
- 复用功能的重映射
重映射用到了AFIO外设
- 开启AFIO时钟
- 重映射代码
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
int main(void)
{
PAL_Init();
//1.初始化TX PB6 AF__PP 10MHZ
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
GPIO_Init(GPIOB,&GPIOInitStruct);
//1.初始化RX PB7 输入IPU
GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
GPIO_Init(GPIOB,&GPIOInitStruct);
//1.重映射USART的TX和RX引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
while(1){
}
}
3.3.2 使能USART的时钟
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
int main(void)
{
PAL_Init();
//1.初始化TX PB6 AF__PP 10MHZ
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
GPIO_Init(GPIOB,&GPIOInitStruct);
//1.初始化RX PB7 输入IPU
GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
GPIO_Init(GPIOB,&GPIOInitStruct);
//1.重映射USART的TX和RX引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
//2.使能USART时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
while(1){
}
}
3.3.3 配置USART参数
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
int main(void)
{
PAL_Init();
//1.初始化TX PB6 AF__PP 10MHZ
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
GPIO_Init(GPIOB,&GPIOInitStruct);
//1.初始化RX PB7 输入IPU
GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
GPIO_Init(GPIOB,&GPIOInitStruct);
//1.重映射USART的TX和RX引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
//2.使能USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//3.设置UASRT1参数
USART_InitTypeDef USARTInitStruct;
USARTInitStruct.USART_BaudRate = 9600;
USARTInitStruct.USART_WordLength = USART_WordLength_8b;
USARTInitStruct.USART_StopBits = USART_StopBits_1;
USARTInitStruct.USART_Parity = USART_Parity_No;
USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1,&USARTInitStruct);
while(1){
}
}
3.3.4 闭合总开关
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
int main(void)
{
PAL_Init();
//1.初始化TX PB6 AF__PP 10MHZ
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
GPIO_Init(GPIOB,&GPIOInitStruct);
//1.初始化RX PB7 输入IPU
GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
GPIO_Init(GPIOB,&GPIOInitStruct);
//1.重映射USART的TX和RX引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
//2.使能USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//3.设置UASRT1参数
USART_InitTypeDef USARTInitStruct;
USARTInitStruct.USART_BaudRate = 9600;
USARTInitStruct.USART_WordLength = USART_WordLength_8b;
USARTInitStruct.USART_StopBits = USART_StopBits_1;
USARTInitStruct.USART_Parity = USART_Parity_No;
USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1,&USARTInitStruct);
//4.闭合UASRT1总开关
USART_Cmd(USART1,ENABLE);
while(1){
}
}
3.4 发送数据
配置串口调试助手和单片机参数保持一致
HEX 十六进制显示模式
ASCII 字符显示模式
如:发送65
HEX模式:0x41
ASCII模式:A
3.4.1 发送单个字节
- 等待TDR数据清空(查询SR寄存器中TXE标准位 1为空)
- 将要发送的数据写入TDR寄存器
- 等待数据发送完成(查询SR寄存器中TC标准位)
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
int main(void)
{
PAL_Init();
//初始化TX PB6 AF__PP 10MHZ
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
GPIO_Init(GPIOB,&GPIOInitStruct);
//初始化RX PB7 输入IPU
GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
GPIO_Init(GPIOB,&GPIOInitStruct);
//重映射USART的TX和RX引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
//使能USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//设置UASRT1参数
USART_InitTypeDef USARTInitStruct;
USARTInitStruct.USART_BaudRate = 9600;
USARTInitStruct.USART_WordLength = USART_WordLength_8b;
USARTInitStruct.USART_StopBits = USART_StopBits_1;
USARTInitStruct.USART_Parity = USART_Parity_No;
USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1,&USARTInitStruct);
//闭合UASRT1总开关
USART_Cmd(USART1,ENABLE);
//发送单个字节
//1.等待TDR寄存器清空
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET){
}//等于0就一直循环,不等于0执行下一部操作
//2.将要发送的数据写入TDR寄存器
USART_SendData(USART1,0x5a);
//3.等待数据发送完成
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET){
}
while(1){
}
}
3.4.2 发送字节数组
- 声明要发送的数组变量
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
int main(void)
{
PAL_Init();
//初始化TX PB6 AF__PP 10MHZ
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
GPIO_Init(GPIOB,&GPIOInitStruct);
//初始化RX PB7 输入IPU
GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
GPIO_Init(GPIOB,&GPIOInitStruct);
//重映射USART的TX和RX引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
//使能USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//设置UASRT1参数
USART_InitTypeDef USARTInitStruct;
USARTInitStruct.USART_BaudRate = 9600;
USARTInitStruct.USART_WordLength = USART_WordLength_8b;
USARTInitStruct.USART_StopBits = USART_StopBits_1;
USARTInitStruct.USART_Parity = USART_Parity_No;
USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1,&USARTInitStruct);
//闭合UASRT1总开关
USART_Cmd(USART1,ENABLE);
//发送字节数组
//1.声明发送的数组变量
uint8_t a[] = {
0,1,2,3,4,5};
uint32_t i;
for(i=0;i< sizeof(a)/sizeof(uint8_t);i++)
{
//1.等待TDR寄存器清空
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET){
}
//2.将要发送的数据写入TDR寄存器
USART_SendData(USART1,a[i]);
}
//3.等待数据发送完成//写在while外面 保证数据是连续发送的
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET){
}
while(1){
}
}
3.4.3 发送字符和字符串
字符在计算机中以ASCLL码存储
- 可以直接发送对应ASLL码
- 记不住的话可以这样发送 ‘a’
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
int main(void)
{
PAL_Init();
//初始化TX PB6 AF__PP 10MHZ
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
GPIO_Init(GPIOB,&GPIOInitStruct);
//初始化RX PB7 输入IPU
GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
GPIO_Init(GPIOB,&GPIOInitStruct);
//重映射USART的TX和RX引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
//使能USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//设置UASRT1参数
USART_InitTypeDef USARTInitStruct;
USARTInitStruct.USART_BaudRate = 9600;
USARTInitStruct.USART_WordLength = USART_WordLength_8b;
USARTInitStruct.USART_StopBits = USART_StopBits_1;
USARTInitStruct.USART_Parity = USART_Parity_No;
USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1,&USARTInitStruct);
//闭合UASRT1总开关
USART_Cmd(USART1,ENABLE);
//发送单个字符
//1.发送字符
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET){
}
//2.将要发送的数据写入TDR寄存器
USART_SendData(USART1,'a');
//3.等待数据发送完成
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET){
}
while(1){
}
}
字符串在计算中以字符数组形式存储
- 如Hello world 有11个元素,将对应ASCLL码依次发送即可
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
#include "string.h"
int main(void)
{
PAL_Init();
//初始化TX PB6 AF__PP 10MHZ
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
GPIO_Init(GPIOB,&GPIOInitStruct);
//初始化RX PB7 输入IPU
GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
GPIO_Init(GPIOB,&GPIOInitStruct);
//重映射USART的TX和RX引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
//使能USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//设置UASRT1参数
USART_InitTypeDef USARTInitStruct;
USARTInitStruct.USART_BaudRate = 9600;
USARTInitStruct.USART_WordLength = USART_WordLength_8b;
USARTInitStruct.USART_StopBits = USART_StopBits_1;
USARTInitStruct.USART_Parity = USART_Parity_No;
USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1,&USARTInitStruct);
//闭合UASRT1总开关
USART_Cmd(USART1,ENABLE);
//发送字符串
const char *str ="Hello word"; //在使用字符串时,我们通常使用char*类型的变量来表示一个字符串
uint32_t i;
for(i=0;i< strlen(str);i++)
{
//1.等待TDR寄存器清空
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET){
}
//2.将要发送的数据写入TDR寄存器
USART_SendData(USART1,str[i]);
}
//3.等待数据发送完成//写在while外面 保证数据是连续发送的
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET){
}
while(1){
}
}
3.5 接收数据
接收原理:
查看RXNE标志位,当EXNE标志位为1时,将RDR寄存器里的值读取出来
实验测试:
- 若接收到0 熄灭LED;
- 若接收到1 点亮LED;
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
#include "string.h"
int main(void)
{
PAL_Init();
//初始化TX PB6 AF__PP 10MHZ
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
GPIO_Init(GPIOB,&GPIOInitStruct);
//初始化RX PB7 输入IPU
GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
GPIO_Init(GPIOB,&GPIOInitStruct);
//重映射USART的TX和RX引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
//使能USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//设置UASRT1参数
USART_InitTypeDef USARTInitStruct;
USARTInitStruct.USART_BaudRate = 9600;
USARTInitStruct.USART_WordLength = USART_WordLength_8b;
USARTInitStruct.USART_StopBits = USART_StopBits_1;
USARTInitStruct.USART_Parity = USART_Parity_No;
USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1,&USARTInitStruct);
//初始化PC13
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIOInitStruct.GPIO_Pin = GPIO_Pin_13;
GPIOInitStruct.GPIO_Mode = GPIO_Mode_Out_OD;//电路有外接的上拉电阻
GPIOInitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOC,&GPIOInitStruct);
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);//最开始熄灭
uint8_t c;
while(1)
{
//1.等待RXNE标志位为1 表示RDR寄存器中有数据
while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET){
}
//2.存储到变量C中
c = USART_ReceiveData(USART1);
if(c == '0')
{
//熄灭LED
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}
else if(c == '1')
{
//点亮LED
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
}
else
{
}
}
}
四、USART的标准库编程—中断方式数据接收和发送
4.1 中断方式接收简介
串口数据的接收有两种方式
- 轮询方式:不符合裸机多任务模型,做不到多任务并行执行的要求
- 中断方式:当RX引脚接收到数据时,就触发一次中断
4.2 配置中断源
状态寄存器也能中的每一位都可以产生一路中断,通过或门将多路合并成一路中断,作为串口的中断,为了能做到分别屏蔽每个标志位的中断,要加一个开关,采用中断使能位与上中断标志位的电路实现
USART_ITConfig():
- 使能/禁止中断
- 通过配置中断使能寄存器实现(IER)
USART_GetITStatus()
- 查询SR寄存器中的状态
USART_ClearITPendingBit()
- 清除中断标志位
- 向SR寄存器中的标志位写0
4.3 中断接收的编程思路
若触发了中断,表明有数据来了,则接收数据
接收数据后,还可能进行数据的处理,为了满足裸机多任务模型,应注重数据处理的时间,从而形成了两套思路
- 数据处理能瞬时完成
- 数据处理耗时较长
4.3.1 数据处理能瞬时完成
在中断响应函数中处理
4.3.2 数据处理耗时较长
- 初始化函数
- 中断响应函数
- 执行数据的接收
- 执行数据的转存
- 进程函数
- 从缓冲区中一个字节一个字节拿数据,并处理
4.4 中断法的串口接收实验
- 编写USART_Recv_Init()函数
- 编写USART1_IRQHandler()中断响应函数
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
static void USART_Recv_Init(void);
int main(void)
{
GPIO_InitTypeDef GPIOInitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//4.2.1 中断优先级分组
PAL_Init();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIOInitStruct.GPIO_Pin = GPIO_Pin_13;
GPIOInitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
GPIOInitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOC,&GPIOInitStruct);
USART_Recv_Init();
while(1){
}
}
static void USART_Recv_Init(void)
{
//1.初始化IO引脚
//PB6 TX PB7 RX
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
GPIOInitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIOInitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOB,&GPIOInitStruct);
GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
GPIOInitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB,&GPIOInitStruct);
//使用AFIO外设,将USART1引脚重映射到PB6 PB7
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
//2.使能USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//3.配置UASRT1的参数
//9600 数据位8 无奇偶校验 不硬件流控 停止位1 TX|RX
USART_InitTypeDef USARTInitStruct;
USARTInitStruct.USART_BaudRate = 9600;
USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USARTInitStruct.USART_Parity = USART_Parity_No;
USARTInitStruct.USART_StopBits = USART_StopBits_1;
USARTInitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&USARTInitStruct);
//4.配置中断
//4.1 配置中断源
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//4.2 配置NVIC中断管理器
//4.2.1 中断优先级分组
//4.2.2 初始化参数设置
NVIC_InitTypeDef NVICInitStruct;
NVICInitStruct.NVIC_IRQChannel = USART1_IRQn;
NVICInitStruct.NVIC_IRQChannelCmd = ENABLE;
NVICInitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVICInitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVICInitStruct);
//5.闭合USART1的总开关
USART_Cmd(USART1,ENABLE);
}
void USART1_IRQHandler(void)
{
uint8_t c;
//1.判断中断是谁产生的
if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
{
c = USART_ReceiveData(USART1);//清除中断;也读取了数据
if(c == '0')
{
//熄灯
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}
else if(c == '1')
{
//亮灯
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
}
}
}
4.5 串口的回声实验(Echo)
4.5.1 编程思路
- 就地处理:不符合
- 延时处理:
延时处理需要将中断中收到的数据接收到缓冲区,什么来做缓冲区呢?----队列这种数据结构
预备知识—队列:
队列(Queue):一种常用的数据结构,经常作为缓冲区来使用,由于具有"先入先出"的特点,也叫FIFO(First In Frist Out)
队列的C语言实现:
//queue.c文件
#include "queue.h"
void Queue_Init(Queue_HandleTypeDef *hQueue)
{
hQueue->Tail = 0;
}
void Queue_Enqueue(Queue_HandleTypeDef *hQueue,uint8_t Element)
{
hQueue->Data[hQueue->Tail++] = Element;
}
ErrorStatus Queue_Dequeue(Queue_HandleTypeDef *hQueue,uint8_t *pElement)
{
uint32_t i;
if(hQueue->Tail == 0) return ERROR;//队列空,操作无效
*pElement = hQueue->Data[0];
for(i=0;i<hQueue->Tail-1;i++)
{
hQueue->Data[i] = hQueue->Data[i+1];
}
hQueue->Tail--;
return SUCCESS;
}
//queue.h文件
#ifndef _QUEUE_H_
#define _QUEUE_H_
#include "stm32f10x.h"
typedef struct
{
//存储队列里的元素
uint8_t Data[100];
//队尾指针
uint16_t Tail;
}Queue_HandleTypeDef;
void Queue_Init(Queue_HandleTypeDef *hQueue);
void Queue_Enqueue(Queue_HandleTypeDef *hQueue,uint8_t Element);
ErrorStatus Queue_Dequeue(Queue_HandleTypeDef *hQueue,uint8_t *pElement);
#endif //防止头文件被重复引用
//应用代码
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
#include "queue.h"
static Queue_HandleTypeDef hQueue;
int main(void)
{
uint8_t elementOut;
PAL_Init();
Queue_Init(&hQueue);
Queue_Enqueue(&hQueue,'H');
if(Queue_Dequeue(&hQueue,&elementOut) == ERROR)
{
//错误时的处理代码
}
else
{
//此时 elementOut == 'H'
}
while(1){
}
}
延时处理函数:
在中断响应函数中不直接进行数据处理,而是把收到的数据,存储到缓冲区里,由进程函数将缓冲区中的数据提取出来,进行处理
//延时处理代码
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
#include "queue.h"
#include "string.h"
static Queue_HandleTypeDef hQueue;
static void USART_Echo_Init(void);//串口初始化函数
static void USART_Echo_Proc(void);//串口进程函数
static void USART1_SendString(const char *str);//串口发送数据函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//miss.c中
PAL_Init();
USART_Echo_Init();
while(1)
{
USART_Echo_Proc();
}
}
void USART1_IRQHandler(void)
{
uint8_t c;
if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
{
c = USART_ReceiveData(USART1);
Queue_Enqueue(&hQueue,c);
}
}
static void USART_Echo_Init(void)
{
Queue_Init(&hQueue);
//1.初始化IO引脚
//PB6-TX PB7-RX
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
GPIOInitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIOInitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOB,&GPIOInitStruct);
GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
GPIOInitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB,&GPIOInitStruct);
//利用AFIO外设进行重映射
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
//2.初始化USART1外设
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
USART_InitTypeDef USARTInitStruct;
USARTInitStruct.USART_BaudRate = 9600;
USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None ;
USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USARTInitStruct.USART_Parity = USART_Parity_No;
USARTInitStruct.USART_StopBits = USART_StopBits_1;
USARTInitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&USARTInitStruct);
//3.配置USART1中断
//3.1 开启USART1的中断源
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
//3.2配置NVIC
//3.2.1中断优先级分组
//3.2.2 初始化NVIC
NVIC_InitTypeDef NVICInitStruct;
NVICInitStruct.NVIC_IRQChannel = USART1_IRQn;
NVICInitStruct.NVIC_IRQChannelCmd = ENABLE;
NVICInitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVICInitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVICInitStruct);
//4.闭合USART1的总开关
USART_Cmd(USART1,ENABLE);
}
static uint8_t a[100];//数组
static uint16_t cursor = 0;//游标
static void USART_Echo_Proc(void)
{
uint8_t c;
if(Queue_Dequeue(&hQueue,&c) == SUCCESS)
{
a[cursor++] = c;
if(cursor>2 && a[cursor-2] == '\r'&& a[cursor-1] == '\n')//收到新行
{
//发送出去
a[cursor] = 0;
USART1_SendString((const char *)a);
cursor = 0;
}
}
}
static void USART1_SendString(const char *str)
{
uint32_t i;
for(i=0;i<strlen(str);i++)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET ){
}//等待发送数据寄存器清空
USART_SendData(USART1,str[i]);
}
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET ){
}
}
4.6 中断方式的数据发送
让发送数据(TXE)标志位产生中断,当要发送数据时,产生中断,在中断响应函数中编写,向串口发送数据的代码。
为满足裸机多任务模型:
- TXE原本关闭的
- 产生发送数据中断时,先将数据写入缓存
- 打开TXE中断
- 这时中断响应函数被调用
- 在中断响应函数中,将字符重缓存中取出,依次写入TDR寄存器发送
调用static void USART_SendString(const char *Str)发送字符串
- 将TXE中断关闭
- 数据入队
- 开启中断(触发了中断响应函数)
void USART1_IRQHandler(void)执行
- 进行数据出队 并发送
- 数据发完关闭TXE中断
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
#include "queue.h"
#include <string.h>
static Queue_HandleTypeDef hQueue;
static void USART_Tx_Init(void);
static void USART_SendString(const char *Str);
int main(void)
{
PAL_Init();
USART_Tx_Init();
USART_SendString("Hello world\r\n");
USART_SendString("I love stm32\r\n");
while(1){
}
}
void USART1_IRQHandler(void)//串口1的中断响应函数
{
uint8_t c;
if(USART_GetITStatus(USART1, USART_IT_TXE) == SET)//从队列取元素 不空就放到TDR中发送
{
if(Queue_Dequeue(&hQueue, &c) == SUCCESS)
{
USART_SendData(USART1, c);
}
else
{
USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
}
}
}
static void USART_SendString(const char *Str)//发送一个字符串
{
uint32_t i;
USART_ITConfig(USART1, USART_IT_TXE, DISABLE);//关中断
for(i=0;i<strlen(Str);i++)//入队
{
Queue_Enqueue(&hQueue, Str[i]);
}
USART_ITConfig(USART1, USART_IT_TXE, ENABLE);//开中断
}
static void USART_Tx_Init(void)//串口的初始化
{
GPIO_InitTypeDef GPIOInitStruct;
Queue_Init(&hQueue);
// 1. 初始化IO引脚
// PB6 PB7
// Tx
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
GPIOInitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIOInitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOB, &GPIOInitStruct);
// Rx
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
GPIOInitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &GPIOInitStruct);
// AFIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
// 2. 使能USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
// 3. 配置USART1的参数
USART_InitTypeDef USARTInitStruct;
USARTInitStruct.USART_BaudRate = 9600;
USARTInitStruct.USART_WordLength = USART_WordLength_8b;
USARTInitStruct.USART_Parity = USART_Parity_No;
USARTInitStruct.USART_StopBits = USART_StopBits_1;
USARTInitStruct.USART_Mode = USART_Mode_Tx;
USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1, &USARTInitStruct);
// 4. 配置中断
// USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
NVIC_InitTypeDef NVICInitStruct;
NVICInitStruct.NVIC_IRQChannel = USART1_IRQn;
NVICInitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVICInitStruct.NVIC_IRQChannelSubPriority = 0;
NVICInitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVICInitStruct);
// 5. 闭合USART1的总开关
USART_Cmd(USART1, ENABLE);
}