USART框图
波特率计算方法
常用的串口相关寄存器
- USART_SR:状态寄存器
- USART_DR:数据寄存器
- USART_BRR:波特率寄存器
状态寄存器USART_SR
- RXNE(读数据寄存器非空):当该位被置1的时候,就是提示已经有数据被接收到并且可以读出来了。这时候我们要做的就是尽快去读取USART_DR,通过读USART_DR可以将该位清零,也可以向该位写0,直接清除。
- TC(发送完成):当该位被置位的时候,标识USART_DR内的数据已经被发送完成了。如果设置了这个位的中断,则会产生中断。该位也有两种清零方式:读USART_SR、写USART_DR或者直接向该位写0。
相关库函数:
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx,uint16_t USART_FLAG);
//获取状态标志位
数据寄存器USART_DR
相关库函数:
void USART_SendData(USART_TypeDef USARTx, uint16_t Data);
//发送数据到串口
uint16_t USART_ReceiveData(USART_TypeDef USARTx);
//接收数据
波特率寄存器USART_BRR
相关库函数:
void USART_Init(USART_TypeDef USARTx, USART_InitTypeDef USART_InitStruct);
//串口初始化:波特率、数据字长、奇偶检验、硬件流控以及收发使能
时钟使能寄存器APB2ENR
串口1在APB2ENR的第14位。除了串口1的时钟使能在APB2ENR,其它串口的时钟使能位都在APB1ENR。
时钟复位寄存器APB2RSTR
当外设出现异常的时候可以通过复位寄存器里面的对应位设置,实现该外设的复位,然后重新配置这个外设达到其重新工作的目的。
串口1的复位是通过配置APB2RSTR寄存器的第14位来实现的,APB2RSTR寄存器的各位描述如下:
从上图可以看出串口1的复位设置位在APB2RSTR的第14位,通过向该位写1复位串口1,写0结束复位。其它串口的复位设置位在APB1RSTR。
串口控制寄存器USART_CR1
STM32的每个串口都有3个控制寄存器USART_CR1~3,串口的很多配置都是通过这3个寄存器来设置的。这里只要用到USART_CR1就可以实现我们的功能了。USART_CR1寄存器的各位描述如下:
- UE为串口使能位,通过把该位置为1来使能串口。
- M为字长选择位,当该位为0的时候设置串口为8个字长外加n个停止位,停止位的个数n是根据USART_CR2寄存器的[13:12]位设置来决定的,默认为0。
- PCE为校验使能位,设置为0则禁止校验,否则使能校验。
- PS为校验位选择,设置为0则为偶校验,否则为奇校验。
- TXIE为发送缓冲区空中断使能位,设置该位为1,当USART_SR中的TXE位为1时,将产生串口中断。
- TCIE为发送完成中断使能位,设置该位为1,当USART_SR中的TC位为1时,将产生串口中断。
- RXNEIE为接收缓冲区非空中断使能,设置该位为1,当USART_SR中的ORE或RXNE位为1时,将产生串口中断。
- TE为发送使能位,设置为1,将开启串口的发送功能。
- RE为接收使能位,设置为1,将开启串口的接收功能。
串口操作相关库函数
(省略入口参数)
- void USART_Init():串口初始化:波特率、数据字长、奇偶检验、硬件流控以及收发使能
- void USART_Cmd():使能串口
- void USART_ITConfig():使能相关中断
- void USART_SendData:发送数据到串口,DR寄存器
- uint16_t USART_ReceiveData():接收数据,从DR读取接收到的数据
- FlagStatus USART_GetFlagStatus():获取状态标志位
- void USART_ClearFlag():清除状态标志位
- ITStatus USART_GetITStatus():获取中断状态标志位
- void USART_ClearITPendingBit():清除中断状态标志位
串口配置的一般步骤
- 串口时钟使能,GPIO时钟使能。
RCC_APB2PeriphClockCmd();
- 串口复位(不是必须的)。
USART_DeInit();
- GPIO端口模式设置。
GPIO_Init();
- 串口参数初始化。
USART_Init()
- 开启中断并且初始化NVIC(如果需要开启中断才需要此步骤)。
NVIC_Init();
USART_ITConfig(); - 使能串口。
USART_Cmd();
- 编写中断处理函数。
USARTx_IRQHandler();
- 串口数据收发。
USART_SendData();
USART_ReceiveData(); - 串口传输状态获取。
USART_GetFlagStatus();
USART_ClearITPendingBit();
usart文件夹介绍
usart文件夹内包含了usart.c和usart.h两个文件,这两个文件用于串口的初始化和中断接收 ,这里只是针对串口1,如果要用串口2或其他的串口,只要对代码稍作修改。
usart.c里面包含了2个函数,一个是void USART_IRQHandler(void);另外一个是void uart_init(u32 bound);里面还有一段对串口printf的支持代码,这段代码不可修改,去掉之后软件编译虽然不会报错,但是硬件上STM32是无法启动的。
printf函数
这段代码加入后便可以通过printf函数向串口发送我们需要的内容,方便开发过程中查看代码执行以及一些变量值。这段代码为(不需要看明白):
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
uart_init()函数
这个函数是串口1初始化函数,这样我们以后再用串口1函数的时候就不需要再从头写初始化函数了,另外如果需要其它串口也可以对此函数稍作修改即可。
by库函数
void uart_init(u32 bound)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
//使能USART1,GPIOA时钟
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART1, ENABLE); //使能串口1
}
by寄存器
//初始化IO 串口1
//pclk2:PCLK2时钟频率(Mhz)
//bound:波特率
void uart_init(u32 pclk2,u32 bound)
{
float temp;
u16 mantissa;
u16 fraction;
temp=(float)(pclk2*1000000)/(bound*16);//得到USARTDIV
mantissa=temp; //得到整数部分
fraction=(temp-mantissa)*16; //得到小数部分
mantissa<<=4;
mantissa+=fraction;
RCC->APB2ENR|=1<<2; //使能PORTA口时钟
RCC->APB2ENR|=1<<14; //使能串口时钟
GPIOA->CRH&=0XFFFFF00F;//IO状态设置
GPIOA->CRH|=0X000008B0;//IO状态设置
RCC->APB2RSTR|=1<<14; //复位串口1
RCC->APB2RSTR&=~(1<<14);//停止复位
//波特率设置
USART1->BRR=mantissa; // 波特率设置
USART1->CR1|=0X200C; //1位停止,无校验位.
#if EN_USART1_RX //如果使能了接收
//使能接收中断
USART1->CR1|=1<<5; //接收缓冲区非空中断使能
MY_NVIC_Init(3,3,USART1_IRQn,2);//组2,最低优先级
#endif
}
USART1_IROHandler函数
此函数是串口1的中断响应函数,当串口1发生了相应的中断后,就会跳到该函数执行。
中断相应函数的名字是不能随便定义的,一般都遵循MDK定义的函数名。这些函数名字在启动文件startup_stm32f10x_hd.s文件中可以找到。
这里设计了一个接受协议:通过这个函数,配合一个数组USART_RX_BUF[],一个接受状态寄存器USART_RX_STA(此寄存器其实就是一个全局变量,为作者自行添加。它起到类似寄存器的作用,此处称之为寄存器)实现对串口数据的接受管理。USART_RX_BUF的大小由USART_REC_LEN定义,也就是一次接受的数据最大不能超过USART_REC_LEN个字节。USART_RX_STA是一个接收状态寄存器,其各位的定义如下表:
设计思路如下:
当接收到上位机发来的数据,将数据保存在USART_RX_BUF中,同时在接收状态寄存器USART_RX_STA中计数接收到的有效数据个数,当收到回车(回车的表示由两个字节组成:0X0D和0X0A)的第一个字节0X0D时,计数器将不再增加,等待0X0A的到来,而如果0X0A没到,则认为这次接收失败,重新开始下一次接收。如果顺利接收到0X0A,则标记USART_RX_STA的第15位,这样完成一次接收,并等待该位被其他程序清除,从而开始下一次的接收,而如果迟迟没有收到0X0D,那么在接收数据超过USART_REC_LEN的时候,则会丢弃前面的数据,重新接收。
中断函数相应代码如下:
by库函数
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
//接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;
//接收数据错误,重新开始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
by寄存器
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记
void USART1_IRQHandler(void)
{
u8 res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
if(USART1->SR&(1<<5)) //接收到数据
{
res=USART1->DR;
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}else //还没收到0X0D
{
if(res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=res;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;
//接收数据错误,重新开始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
其中EN_USART1_RX和USART_REC_LEN都是在usart.h里面定义的:
#define USART_REC_LEN 200 //定义最大接收字节数 200
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收
当需要使用串口接收的时候,只要在usart.h里设置EN_USART1_RX为1即可;不使用的时候,设置为0即可,这样可以省出部分sram和flash;默认EN_USART1_RX是1。
库函数和寄存器代码的两处不同
第一处
USART_GetITStatus(USART1, USART_IT_RXNE) != RESET //by库函数
USART1->SR&(1<<5) //by寄存器
这两处意思都是读取状态寄存器RXNE(第五位)的电平,当接收到数据后,RXNE会为高电平,通过这样来判断串口是否接收到了数据。RESET就是低电平的意思。
第二处
Res =USART_ReceiveData(USART1); //by库函数
res=USART1->DR; //by寄存器
这两处的意思是把接收到的数据赋值给res(Res)变量,DR为数据寄存器,它包含了发送的和接收的数据,兼具读和写的功能。
一个简单的串口通信代码(by库函数)
因为有函数名字重复,所以需要把System文件夹中的usart.c先删除
main.c
#include "stm32f10x.h"
void My_USART1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能串口时钟
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//PA.9
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化PA.9
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//PA.10
GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化PA/10
USART_InitStructure.USART_BaudRate=115200;//波特率115200
USART_InitStructure.USART_WordLength=USART_WordLength_8b;//字长8bits
USART_InitStructure.USART_StopBits=USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity=USART_Parity_No;//无奇偶校验
USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
//不使用硬件流控制
USART_Init(USART1,&USART_InitStructure);//串口1初始化
USART_Cmd(USART1,ENABLE);//串口使能
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//打开接收中断
NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;//串口1通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;//响应优先级为1
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//开启中断
NVIC_Init(&NVIC_InitStructure);//中断初始化
}
void USART1_IRQHandler(void)
{
u8 res;
if(USART_GetITStatus(USART1,USART_IT_RXNE))//判断是否触发了串口接收中断
{
res = USART_ReceiveData(USART1);
USART_SendData(USART1,res);//串口发送数据
}
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC中断优先级分组
My_USART1_Init();//串口初始化函数
while(1);
}
串口通信实验
by库函数
main.c
#include "led.h"
#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
#include "usart.h"
int main(void)
{
u8 t;
u8 len;
u16 times=0;
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置NVIC中断分组为2
uart_init(115200);//串口1初始化,并设置波特率为115200
LED_Init();
while(1)
{
if(USART_RX_STA&0x8000)//判断是否接受完成
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n你发送的消息为\r\n\r\n");
for(t=0;t<len;t++)
{
USART_SendData(USART1,USART_RX_BUF[t]); //向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);//等待发送完成
}
printf("\r\n\r\n");
USART_RX_STA=0;
}else
{
times++;
if(times%5000==0)
{
printf("\r\n战舰STM32开发板 串口实验\r\n");
printf("串口实验库函数版本\r\n\r\n");
}
if(times%200==0) printf("请输入数据,以回车键结束\n");
if(times%30==0) LED0=!LED0; //LED0闪烁,提示系统正在运行
delay_ms(10);
}
}
}
led.h
#ifndef __LED_H
#define __LED_H
#include "sys.h"
#define LED0 PBout(5)
#define LED1 PEout(5)
void LED_Init(void);
#endif
led.c
#include "led.h"
void LED_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;
GPIO_Init(GPIOE,&GPIO_InitStructure);
}
by寄存器
main.c
#include "stm32f10x.h"
#include "delay.h"
#include "sys.h"
#include "led.h"
#include "usart.h"
int main(void)
{
u16 t;
u16 len;
u16 times=0;
Stm32_Clock_Init(9);
delay_init(72);
uart_init(72,115200);//串口1初始化,并设置波特率为115200
LED_Init();
while(1)
{
if(USART_RX_STA&0x8000)//判断是否接受完成
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n你发送的消息为\r\n\r\n");
for(t=0;t<len;t++)
{
USART1->DR=USART_RX_BUF[t]; //向串口1发送数据
while((USART1->SR&0X40)==0);//等待发送完成
}
printf("\r\n\r\n");
USART_RX_STA=0;
}else
{
times++;
if(times%5000==0)
{
printf("\r\n战舰STM32开发板 串口实验\r\n");
printf("串口实验寄存器版本\r\n\r\n");
}
if(times%200==0) printf("请输入数据,以回车键结束\n");
if(times%30==0) LED0=!LED0; //LED0闪烁,提示系统正在运行
delay_ms(10);
}
}
}
led.h
#ifndef __LED_H
#define __LED_H
#include "sys.h"
#define LED0 PBout(5)
#define LED1 PEout(5)
void LED_Init(void);
#endif
led.c
#include "led.h"
#include "sys.h"
void LED_Init()
{
//注意:寄存器不能直接等号!!需要加|或者&!!
RCC->APB2ENR|=1<<3;//PB使能
RCC->APB2ENR|=1<<6;//PE使能
GPIOB->CRL&=0XFF0FFFFF;
GPIOB->CRL|=0X00300000;
LED0=1;
GPIOE->CRL&=0XFF0FFFFF;
GPIOE->CRL|=0X00300000;
LED1=1;
}