记录一下,方便以后翻阅~
主要内容:
1) 串行通信接口背景知识;
2) STM32F1串口框图讲解;
3) STM32串口部分常用寄存器和库函数;
4) 串口配置一般方法;
5) ALIENTEK提供的公用代码usart.c和usart.h解读。
官方资料:《STM32中文参考手册V10》第25章——通用同步异步收发器(USART)
1. 处理器与外部设备通信的两种方式
1.1 并行通信,传输原理:数据各个位同时传输。优点:速度快。缺点:占用引脚资源多。
1.2 串行通信,传输原理:数据按位顺序传输。优点:占用引脚资源少。缺点:速度相对较慢。
2. 串行通信(按照数据传送方向可分为)
2.1 单工:数据传输只支持数据在一个方向上传输(图a);
2.2 半双工:允许数据在两个方向上传输,但在某一时刻,只允许数据在一个方向上传输,实际上是一种切换方向的单工通信(图b);
2.3 全双工:允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力(图c)。
3. 串行通信的通信方式
3.1 同步通信:带时钟同步信号传输,包括SPI,IIC通信接口;
3.2 异步通信:不带时钟同步信号,包括UART(通用异步收发器,双方需事先约定好波特率),单总线。
4. 常见的串行通讯接口
5. STM32的串口通信接口
5.1 UART:通用异步收发器;
5.2 USART:通用同步异步收发器。
备注:大容量STM32F10x系列芯片,包含3个USART和2个UART。
6. UART异步通信方式引脚连接方法
6.1 RXD:数据输入引脚。数据接受;
6.2 TXD:数据发送引脚。数据发送。
6.3 开发版上的串口引脚如下表所示:
7. UART异步通信方式特点
7.1 全双工异步通信;
7.2 分数波特率发生器系统,提供精确的波特率。发送和接受共用的可编程波特率,最高可达4.5Mbits/s;
7.3 可编程的数据字长度(8位或者9位);
7.4 可配置的停止位(支持1或者2位停止位);
7.5 可配置的使用DMA多缓冲器通信;
7.6 单独的发送器和接收器使能位;
7.7 检测标志:① 接受缓冲器 ②发送缓冲器空 ③传输结束标志;
7.8 多个带标志的中断源。触发中断;
7.9 其他:校验控制,四个错误检测标志。
8. 串口通信过程
9. STM32串口异步通信需要定义的参数
9.1 起始位;
9.2 数据位(8位或者9位);
9.3 奇偶校验位(第9位);
9.4 停止位(1,15,2位);
9.5 波特率设置。
10. STM32串口框图
10.1 对于大容量的STM32F10x芯片,有5个串口,串口1时钟来源PCLK2,串口2~4时钟来源PCLK1。
11. 常用的串口相关寄存器
11.1 USART_SR状态寄存器(串口通信的状态位);
11.2 USART_DR数据寄存器(读写都通过该寄存器);
11.3 USART_BRR波特率寄存器。
12. 串口操作相关库函数
void USART_Init(); //串口初始化:波特率,数据字长,奇偶校验,硬件流控以及收发使能//
void USART_Cmd(); //使能串口//
void USART_ITConfig(); //使能相关中断//
void USART_SendData(); //发送数据到串口,针对DR寄存器//
uint16_t USART_ReceiveData(); //接受数据,从DR读取接受到的数据,针对DR寄存器//
FlagStatus USART_GetFlagStatus(); //获取状态标志位,针对SR寄存器//
void USART_ClearFlag(); //清除状态标志位,针对SR寄存器//
ITStatus USART_GetITStatus(); //获取中断状态标志位,针对SR寄存器//
void USART_ClearITPendingBit(); //清除中断状态标志位,针对SR寄存器//
13. 串口常用寄存器解读
13.1 状态寄存器(USART_SR)
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
13.2 数据寄存器(USART_DR)
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
13.3 波特比率寄存器(USART_BRR)
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
14. 波特率计算方法
15. 串口配置一般步骤
15.1 串口时钟使能,GPIO时钟使能;
RCC_APB2PeriphClockCmd(); //USART1位于RCC_APB2PeriphClockCmd中//
15.2 串口复位;
USART_DeInit(); //这一步不是必须的//
15.3 GPIO端口模式设置;
GPIO_Init(); //TX模式设置为GPIO_Mode_AF_PP,RX模式设置为GPIO_Mode_IN_FLOATING //
15.4 串口参数初始化;
USART_Init();
15.5 开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤);
NVIC_Init();
USART_ITConfig();
15.6 使能串口;
USART_Cmd();
15.7 编写中断处理函数;
USARTx_IRQHandler();
15.8 串口数据收发;
void USART_SendData(); //发送数据到串口,DR//
uint16_t USART_ReceiveData(); //接受数据,从DR读取接受到的数据//
15.9 串口传输状态获取;
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
16. 串口配置案例
//案例说明:设置PA9和PA10位USART1的TX和RX,开启USART_IT_RXNE接收中断,中断服务函数目的为,当USART_IT_RXNE发生,USART1接收数据并发送回去,需配合串口调试助手进行实验//
#include "stm32f10x.h"
//编写我的初始化函数//
void My_USART1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
//第一步,使能相关时钟//
//使能GPIOA时钟,RCC_APB2PeriphClockCmd()位于stm32f10x.rcc.c文件中//
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//使能USART1时钟//
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
//第二步,串口复位//
//将外设 USARTx 寄存器重设为缺省值,复位USART1,USART_DeInit()位于stm32f10x.usart.c文件中//
USART_DeInit(USART1);
//第三步,GPIO端口模式设置,PA9对应RXD,PA10对应TXD//
//先对PA9进行设置,模式为复用推挽输出,引脚9,速度随意//
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出//
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9; //引脚9//
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_10MHz; //随意设置//
//根据上述GPIO_InitStruct中指定的参数初始化外设GPIOA寄存器,,GPIO_Init()位于stm32f10x.gpio.c文件中//
GPIO_Init(GPIOA,&GPIO_InitStruct);
//再对PA10进行设置,模式为浮空输入或带上拉输入,引脚10,速度随意//
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING; //浮空输入//
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10; //引脚9//
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_10MHz; //随意设置//
//根据GPIO_InitStruct中指定的参数初始化外设GPIOA寄存器,GPIO_Init()位于stm32f10x.gpio.c文件中//
GPIO_Init(GPIOA,&GPIO_InitStruct);
//第四步,串口参数初始化,在电脑上进行串口通信时,串口调试软件也需按下述参数设置//
USART_InitStruct.USART_BaudRate=115200; //波特率为115200//
USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流控制//
USART_InitStruct.USART_Mode=USART_Mode_Rx | USART_Mode_Tx; //收发都使能,可用或运算//
USART_InitStruct.USART_Parity=USART_Parity_No; //不用奇偶校验位//
USART_InitStruct.USART_StopBits=USART_StopBits_1; //设置1个停止位//
USART_InitStruct.USART_WordLength=USART_WordLength_8b; //设字长为8,因为不用奇偶校验//
//根据上述USART_InitStruct中指定的参数初始化外设USART1寄存器,USART_Init()位于stm32f10x.usart.c文件中//
USART_Init(USART1,&USART_InitStruct);
//第五步,开启指定中断,并初始化NVIC//
//NVIC初始化//
NVIC_InitStruct.NVIC_IRQChannel=USART1_IRQn; //选择位于stm32f10x.h文件中STM32F10X_HD中的USART1_IRQn//
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //使能上述中断通道//
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1; //因为没有别的中断,根据中断分组2,参数可设0~3之间//
NVIC_InitStruct.NVIC_IRQChannelSubPriority=1; //因为没有别的中断,根据中断分组2,参数可设0~3之间//
//根据上述NVIC_InitStruct中指定的参数初始化外设NVIC寄存器,NVIC_Init()位于misc.c文件中//
NVIC_Init(&NVIC_InitStruct);
//使能USART1的USART_IT_RXNE中断,RXNE是状态寄存器USART_SR的第5位,意思是接收中断//
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
//第六步,使能串口//
//使能USART1外设,USART_Cmd()位于stm32f10x.usart.c文件中//
USART_Cmd(USART1,ENABLE);
}
//第七步,编写中断服务函数//
//函数名称USART1_IRQHandler是由位于启动文件startup_stm32f10x_hs.s文件里定义的//
void USART1_IRQHandler(void)
{
u8 data;
//第八步,串口状态获取//
//检查USART1的接受中断USART_IT_RXNE发生与否,USART_GetITStatus()函数位于stm32f10x_usart.c文件中//
if(USART_GetITStatus(USART1,USART_IT_RXNE))
{
//第九步,串口数据收发,较为常用//
//返回USART1最近接收到的数据,USART_ReceiveData()函数位于stm32f10x_usart.c文件中//
data=USART_ReceiveData(USART1);
//通过外设USART1发送单个数据,USART_SendData()函数位于stm32f10x_usart.c文件中//
USART_SendData(USART1,data);
}
}
//编写主函数//
int main(void)
{
//设置优先级分组:先占优先级和从优先级//
//设置分组2,即2位抢占优先级,2位响应优先级,NVIC_PriorityGroupConfig()位于misc.c文件中//
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
My_USART1_Init();
while(1)
{
}
}
17. usart.h代码讲解
#ifndef __USART_H
#define __USART_H
#include "stdio.h"
#include "sys.h"
#define USART_REC_LEN 200 //定义最大接收字节数 200//
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收//
extern u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符//
extern u16 USART_RX_STA; //接收状态标记//
//如果想串口中断接收,请不要注释以下宏定义//
void uart_init(u32 bound); //设置波特率//
#endif
18. usart.c里中断服务函数USART1_IRQHandler函数代码讲解
void USART1_IRQHandler(void) //串口1中断服务程序//
{
u8 Res; //定义一个变量Res,用来接收数据//
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS//
OSIntEnter();
#endif
//*判断接收中断USART_IT_RXNE是否发生,发生则为1,不发生为*0//
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //RESET=0//
{
//*若接收中断USART_IT_RXNE发生,Res读取接收到的数据*//
Res =USART_ReceiveData(USART1);
//判断变量U16 USART_RX_STA的位15是否为0,当USART_RX_STA的位15为0时,则与运算后值为0,if返回1(真),反之if返回0(假)*//
if((USART_RX_STA&0x8000)==0)
{
//*若USART_RX_STA的位15为0时,判断USART_RX_STA的位14是否为0*//
if(USART_RX_STA&0x4000)
{
//*若USART_RX_STA的位14为1时,则判断Res接收的数据是否为0x0A,0x0A为换行标志*//
if(Res!=0x0A)
//*若Res接收的数据不是换行标志,则接收错误,重新开始*//
USART_RX_STA=0;
//*若Res接收的数据是换行标志,则USART_RX_STA的位15至1* //
else USART_RX_STA|=0x8000;
}
//*若USART_RX_STA的位14为0时,执行else *//
else
{
//*判断Res是否等于(接收到)0x0D,0x0D为回车标志*//
if(Res==0x0d)
//*若Res接收到回车标志,if为1(真),则USART_RX_STA位14至1 //
USART_RX_STA|=0x4000;
//*若Res未接收到回车标志,if为0(假),执行else//
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res;
USART_RX_STA++;
//*若USART_RX_STA值大于所设最大接收字节数,则接收数据错误,重新开始接收*//
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;
}
}
}
}
上述代码中,变量U16 USART_RX_STA用法如下图所示:
19. main.c文件里主函数代码讲解
int main(void)
{
u16 t;
u16 len;
u16 times=0;
delay_init(); //*延时函数初始化*//
//设置NVIC中断分组2:2位抢占优先级,2位响应优先级//
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200); //串口初始化为115200//
LED_Init(); //LED端口初始化//
KEY_Init(); //初始化与按键连接的硬件接口//
while(1)
{
//*判断USART_RX_STA的位15是否为1*//
if(USART_RX_STA&0x8000)
{
//*若USART_RX_STA的位15为1*//
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发送第t个数据//
//*等待发送结束,USART_FLAG_TC意思是Transmission Complete flag*//
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);
}
printf("\r\n\r\n"); //插入换行//
USART_RX_STA=0;
}else
{
times++;
if(times%5000==0) //*times为5000的整数倍时*//
{
printf("\r\n战舰STM32开发板 串口实验\r\n");
printf("正点原子@ALIENTEK\r\n\r\n");
}
if(times%200==0)printf("请输入数据,以回车键结束\n"); //*times为200的整数倍时*//
//*times为30的整数倍时,闪烁LED,提示系统正在运行*//
if(times%30==0)LED0=!LED0;
delay_ms(10);
}
}
}
20. usart.c里中断服务函数printf()函数代码讲解
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数//
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式//
_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
//若要对其他串口进行操作,只需将上述代码中的USART1改成USARTx即可。这段代码方便开发过程中查看代码执行情况以及一些变量值,该代码不需要修改,引入到 usart.h 即可//
旧知识点:
1)理解新建工程模板中, ALIENTEK提供的公用代码usart.c和usart.h的大致含义,可参考STM32学习心得二:新建工程模板
2)复习基于库函数的初始化函数的一般格式,可参考STM32学习心得三:GPIO实验-基于库函数;
3)复习寄存器地址,可参考STM32学习心得四:GPIO实验-基于寄存器和STM32学习心得五:GPIO实验-基于位操作;
4)复习寄存器地址名称映射,可参考STM32学习心得六:相关C语言学习及寄存器地址名称映射解读;
5)复习时钟使能相关函数,可参考STM32学习心得七:STM32时钟系统框图解读及相关函数;
6)复习如何对GPIO进行复用,可参考STM32学习心得十二:端口复用和重映射;
7)复习中断相关知识,可参考STM32学习心得十三:NVIC中断优先级管理。