STM32学习10---串口
简介:
基于STM32标准库,主要涉及初始化、收发原理、中断收发、常用配置。STM32F103 有2个USART,其中,USART1位于APB2总线,USART2/3是APB1总线,支持同步/异步模式,常用异步串口UART模式。参考江科大视频,如有错误,欢迎指正,侵删!
一、串口核心知识点
1、总线时钟
USART1挂APB2,预分频器不分频,72MHz,串口时钟=APB2时钟。
USART2/3挂APB1,APB1最大36MHz,串口时钟=APB1时钟*2(APB1预分频不为1时加倍)。STM32标准库自动计算波特率分频值,不用手动算。
2、串口引脚复用功能
默认复用引脚:
USART1:TX-PA9、RX-PA10
USART2:TX-PA2 、RX-PA3
USART3:TX-PB10、RX-PB11
注:必须开启GPIO复用时钟,USART外设时钟,GPIO配置为复用推挽输出。
3、串口
1)工作模式:异步串口:只需要TX(发送)、RX(接收)2根线,无时钟线。
帧格式:起始位1bit +数据位8/9bit +停止位0.5/1.5/2bit +奇偶检验(可选),常见配置:8N1,9600/115200波特率。
2)硬件

注:RX与Tx交叉连接,只需担心数据传输,可以只接一根线。电平标准不一致时,加电平转换芯片。
3)常用串口电平标准如下:
TTL电平:+3.3V或+5V表示1,0V表示0(最常见,单片机常用)
RS232电平:-3~-15V表示1,+3~+15V表示0
RS485电平:两线压差+2~+6V表示1,-2~-6V表示
4)串口参数及时序
波特率:串口通信速率
起始位:标志一个数据帧的开始,固定为低电平;
数据位:数据帧的有效载荷,1为高电平,0为低电平;
校验位:数据验证,根据数据位计算;
停止位:数据帧间隔,固定位高电平;

建议:不需要校验位选8位数据,需要选9位数据。
二、标准库库函数
1、USART初始化结构体 USART_InitTypeDef()
typedef struct{
uint32_t USART_BaudRate;//波特率
uint16_t USART_WordLength;/数据位长度
uint16_t USART_StopBits;//停止位
uint16_t USART_Parity; //奇偶校验
uint16_t USART_Mode;//收发模式:只发/只收/收发
uint16_t USART_HardwareFlowControl;//硬件流控制,一般关闭
}USART_InitTypeDef;
2、常用宏定义:
USART_WordLength_8b //8位数据
USART_StopBits_1 //1位停止位
USART_Parity_No //无校验
USART_Mode_Tx |USART_Mode_Rx //收发全开
USART_HardwareFlowControl_None //关闭硬件流控制
3、中断配置结构体USART_ITConfig
串口常用中断:
USART_IT_RXNE:接收数据寄存器非空中断(收到一个字节就触发中断,最常用);
USART_IT_TXE:发送数据寄存器空中断;
USART_IT_TC:发送完成中断;
4、关键库函数
USART_Init(USART_TypeDef* USARTx,USART_InitTypeDef*USART_InitStruct);//串口初始化函数
USART_Cmd(USART_TypeDef*USARTx,FunctionState NewState); //使能/关闭串口外设
USART_SendData(USART_TypeDef*USARTx,uint16_t Data);//发送一个字节
uint16_t USART_ReceiveData(USART_TypeDef*USARTx);//读取接收到的一个字节
FlagStatus USART_GetFlagStatus(USART_TypeDef*USARTx,uint16_t USART_FlAG);//查询标志位
void USART_ITConfig(USART_TypeDef*USARTx ,uint16_t USART_IT,FunctionState NewState)//开启串口对应中断
USART_FLAG_RXNE(Read data register not empty):读数据寄存器非空,接收标志(有数据可读);
USART_FLAG_TXE(Transmit data register empty):发送数据寄存器空,可以写数据;
USART_FLAG_TC(Transmission complete):一帧数据全部发送完成;
三、收发过程
1、概述
单片机串口:硬件独立工作,CPU只管写数据、读数据,收发是USART外设自行移位处理,不用CPU一位位搬运。分2部分:单片机发送数据、电脑/外设给单片机发送数据(单片机接收)。
2、发送过程(Tx接PA9)
寄存器核心:DR(数据寄存器)、TXE标志位、移位寄存器。
步骤1:CPU执行 USART_SendData(USART1,data);
函数本质:把你传入的8位数据,写入USART的DR数据保持寄存器。此时移位寄存器是空的,硬件自动把DR里的数据搬到移位寄存器,开始一位位往外发送。
步骤2:TXE标志位置1
数据从DR搬走后,硬件自动置位USART_FLAG_TXE。含义:DR寄存器已经空了,可以再写下一个字节。
查询发送:while(!TXE)或者while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) ==RESET);就是等待这个标志,等它置1就可以发送下一个字节。
如果开启中断,USART_ITConfig(USART1,USART_IT_TXE,ENABLE),此时会触发 发送空 中断。
步骤3:移位寄存器自动串行输出
USART硬件自行处理时序:先发一位起始位(低电平)→依次发送8位数据位→1位停止位(高电平),波特率时钟由外设自己产生,CPU不用管。一帧数据全部发送完毕后,硬件置位 TC发送完成标志位。
整个流程:CPU写DR→DR数据送入移位寄存器→TXE置1→硬件串行移位输出引脚TX→全部发送完TC置1。
3、单片机接收过程(Rx引脚PA10,外设主动发送CPU)
步骤1、外部设备(电脑串口助手)往Rx引脚发高低电平,Rx引脚不断检测电平变化,当检测到一个下降沿(起始位),串口外设开始同步接收。
步骤2、硬件移位采样数据
USART按照波特率,连续采样8位数据,存入内部移位寄存器,8位数据全部收齐之后,硬件自动把移位寄存器的数据复制到DR数据寄存器。
步骤3、硬件置位RXNE标志位。DR寄存器装入新数据后,硬件自动置位USART_FLAG_RXNE,即:接收寄存器非空,有新数据,CPU要来读取。此时2种方式可以实现:
1)只查询方式,没开中断(不用USART_ITConfig)
USART_GetFlagStatus(USART1,USART_FLAG_RXNE),查询到标志为1,就去读DR寄存器。
2)开启RXNE接收中断(使用USART_ITConfig)
RXNE置1,由于开启了中断,串口外设立即向NVIC发出中断请求,NVIC已经提前配置USART1中断,CPU暂停主循环程序,跳入固定中断函数:USART1_IRQHandler();
步骤4、CPU读取DR数据
函数:USART_ReceiveData()读取DR,这个读操作硬件会自动清除RXNE标志位。标志位清零后,串口外设才可以继续接收下一个字节。
注意:如果进入中断,没有用USART_ReceiveData()读取DR,RXNE标志一直是1,会一直不断重复进入中断,卡死程序。
综上:接收过程是:外部电平→Rx引脚硬件移位接收→数据存入DR→RXNE置1→开启中断就触发USART1中断→CPU进中断服务函数→读取DR→硬件自动清除RXNE标志位→等待下一个字节。
四、代码编写
配置:STM32F103C8T6,72MHz,9600bit/s,PA9-Tx,PA10-Rx
练习1、只发送数据Tx--PA9
1、开启时钟,GPIO配置为复用推挽输出
void Serial_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//GPIO初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//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_Cmd(USART1,ENABLE);
}
2、发送一个字节和发送字符串函数(先发送低位)
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1,Byte);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) ==RESET);
}
//发送字符串
void Serial_SendString(char* String)
{
while(*String )
{
Serial_SendByte(*String++);
}
}
3、测试
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
int main()
{
OLED_Init();
Serial_Init();
Serial_SendByte(0x34);
Serial_SendString("hello\r\n");
while(1)
{
}
}
练习2:收发数据
1、开启时钟,GPIO配置PA9复用推挽输出,PA10浮空输入,
void Serial_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx |USART_Mode_Rx;
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);
}
2、接收数据并发送,在OLED上显示
方式1:接步骤1的代码,采用查询的方式:不断检测USART_FLAG_RXNE标志位,置1就读取数据。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
uint8_t Data;
int main()
{
OLED_Init();
Serial_Init();
while(1)
{
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) ==SET)
{
Data =USART_ReceiveData(USART1);
Serial_SendByte(Data);
OLED_ShowHexNum(1,1,Data,2);
}
}
}
方法2、中断方式接收(项目常用)
原理:外设设备发来一个字节,硬件置位RXNE标志,触发USART1中断,进入中断函数读取数据。完整代码如下:
Serial.c代码
#include "Serial.h"
uint8_t Receive_Flag,Data;
void Serial_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx |USART_Mode_Rx;
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);
}
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1,Byte);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) ==RESET);
}
void Serial_SendString(char* String)
{
while(*String )
{
Serial_SendByte(*String++);
}
}
uint8_t Serial_RxFlag1()
{
if(Receive_Flag ==1)
{
Receive_Flag =0;
return 1;
}
return 0;
}
uint8_t Rx_GetData1()
{
return Data;
}
void USART1_IRQHandler()
{
if(USART_GetITStatus(USART1,USART_IT_RXNE) ==SET)
{
Data =USART_ReceiveData(USART1);
Receive_Flag =1;
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
main.c代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
uint8_t GetData;
int main()
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
OLED_Init();
Serial_Init();
while(1)
{
if(Serial_RxFlag1()==1)
{
GetData =Rx_GetData1();
Serial_SendByte(GetData);
OLED_ShowHexNum(1,1,GetData,2);
}
}
}
初始化时,也可以将波特率设为变量 uint32_t BaudRate 。
总结:标准库配置流程(中断):
1、开启GPIO时钟、开串口外设时钟;
2、GPIO配置:Tx复用推挽,Rx浮空输入;
3、USART1结构体初始化:波特率、8N1格式;
4、开启外设中内部断使能(USART_ITConfig必写);
5、NVIC优先级分组,设置一次全局即可;
6、NVIC通道初始化,打开CPU中断响应;
7、使能USART外设;
8、编写中断服务函数;
五、关于中断USART_ITConfig
1、USART_ITConfig()是单独打开串口某一个中断开关,外设本身有中断功能,默认是关闭,这个函数就是打开对应的中断使能开关,不写的话,硬件不会触发串口中断。
STM32有2道开关,都得开启,缺一不可:
开关1:外设内部中断使能 USART_ITConfig(),有很多中断源,默认都关闭。(给串口外设开门)

比如:USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);就是告诉USART1外设,收到数据(RXNE事件发生),要向NVIC中断控制器发出中断请求信号。
开关2:NVIC总开关---NVIC_Init() (3、给CPU开门)
NVIC是芯片统一中断控制器,配置NVIC,是允许CPU响应这个中断通道。
总之:USART_ITConfig()允许外设产生中断请求→NVIC_Init()CPU允许响应这个请求。
2、USART_ITConfig()第2个参数:
| 参数 | 含义 |
| USART_IT_RXNE | 接收数据寄存器非空 中断 |
| USART_IT_TXE | 发送数据寄存器空 中断 |
| USART_IT_TC | 一帧全部发送完成 中断 |
| USART_IT_PE | 奇偶检验错误 中断 |
3、三种情况要不要写USART_ITConfig()
1)查询方式接收(while轮询标志位),不用写;
2)开启串口Rx接收中断,必须写;
3)只用串口发送,不开启任何中断,不用写;
六、printf重定向
1、原理:printf()底层会调用一个标准库函数fputc(),默认这个函数往电脑控制台输出,我们要重写这个函数让它把每一个字符通过串口发送出去。
2、开启步骤
步骤1、在keil里,魔术棒→Target勾选UseMircroLIB,否则printf无法输出。

步骤2、在串口初始化的c文件中编写重定向即可。本案例是Serial.c,包含头文件<stdio.h>
#include <stdio.h>
int fputc(int ch,FILE*f)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) ==RESET);
USART_SendData(USART1,ch);
return ch;
}
这样就定向到了串口1。
3、注意事项
1)printf内部是等待TXE标志逐个发送字符,会阻塞CPU允许。
2)不要在中断服务函数里调用printf。中断里运行printf会造成程序卡死、时序混乱。
3)主循环里随便使用printf,但打印大量数据会占用CPU时间。
4、printf重定向什么时候用?什么时候不用?
使用:
场景1、调试程序,查看变量数值,程序运行状态,比如,可直接写:printf("温度:%d\r\n",temp);
串口助手直接看到打印的数值等。
场景2:快速打印字符串+数字混合内容,不然打印数字很麻烦,要拆分百、十、个位等
场景3:工程复杂,多处需打印日志信息。程序模块多,采集、通讯、电机控制等多处需要输出信息,统一printf,代码简洁。
不使用:
场景1:产品已经调试完毕,关闭printf接收flash空间。
场景2:只用串口简单收发指令,只接收上位机命令,不需要打印日志。
场景3:单片机资源紧张。
更多推荐


所有评论(0)