STM32学习笔记——串口通信

9.1 STM32串口简介
    9.2 硬件设计
    9.3 软件设计
    9.4 下载验证

1)STM32F1串口资源介绍: 
    串口作为MCU的重要外部接口,同时也是软件开发重要的调试手段,其重要性不言而喻。现在基本上所有的
MCU都带有串口。
    STM32的串口资源相当丰富,功能很强劲。STM32F103ZET6最多可提供5路串口,有分数波特率发生器、支持
同步单线通信和半双工单线通信、支持LIN、支持调制解调器操作、智能卡协议和IrDA SIR ENDEC规范、具有DMA等。
2)串口管脚在芯片上的位置和特性:
    表21 USART管脚配置
————————————————

8.3.8 USART复用功能重映射
参见复用重映射和调试I/O配置寄存器(AFIO_MAPR)
表45 USART3重映射
(注:部分重映射只适用于64、100和144脚的封装;完全重映射只适用于100和144脚的封装)

 表46 USART2重映射(重映射只适用于100和144脚的封装)

 表47 USART1重映射

 

4)设置串口:
    对于复用功能的IO口,首先要使能GPIO时钟,然后使能复用功能时钟,同时,把GPIO模式设置为复用功能
对应的模式。之后就是初始化设置串口参数,包括波特率、停止位等。接下来使能串口。同时,如果开启了
串口的中断,要初始化NVIC设置中断优先级,最后编写中断服务函数。
     串口设置的一般步骤总结:
① 使能串口时钟,使能GPIO时钟;
② 复位串口;
③ 设置GPIO端口模式;
④ 初始化串口参数;
⑤ 开启中断并初始化NVIC(如果需要开启中断才需要这个步骤);
⑥ 使能串口;
⑦ 编写中断处理函数。
 
    下面简单介绍一下与串口基本配置直接相关的几个固件库函数。
    ① 串口时钟使能。
    串口是挂在APB2下面的外设,所以使能函数为:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1);
    ② 串口复位。
    当外设出现异常时,需要通过复位设置,实现该外设的复位,然后重新配置这个外设以达到使其重新工作
的目的。一般在系统刚开始配置外设时,先执行复位该外设的操作。复位是在USART_DeInit()函数中完成的。
void USART_DeInit(USART_TypeDef* USARTx); //串口复位
比如要复位串口1,调用:
USART_DeInit(USART1); //串口1复位
    ③ 串口参数初始化。
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
    这个函数的第一个参数是指定初始化的串口标号,这里选择USART1。
    第二个参数是一个USART_InitTypeDef类型的结构体指针,其成员变量用来配置串口的一些参数。一般格式如下:
USART_InitStructure.USART_BaudRate = bound; //设置波特率;
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; //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); //初始化串口
    从上面的初始化格式可以看出,初始化需要配置参数为:波特率、字长、停止位、奇偶校验位、硬件数据流控制、模式(收/发)。我们可以根据需要设置这些参数。
    ④ 数据发送和接收。
    STM32的发送和接收是通过数据寄存器USART_DR来实现的,这是一个双寄存器,包含了TDR和RDR。
当向该寄存器写数据时,串口就会自动发送;当收到数据时,也存在该寄存器中。
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);    //向USART_DR寄存器写入一个数据
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);            //读取串口接收到的数据
    ⑤ 串口状态。
    串口的状态可以通过寄存器USART_SR读取,各位描述如下:
31~10 9 8 7 6 5 4 3 2 1 0
保留 CTS LBD TXE TC RXNE IDLE ORE NE FE PE
    这里关注两位:Bit5:RXNE;Bit6:TC
    RXNE(读数据寄存器非空):当该位被置一时,说明已经有数据被接收了,并且可以读出来了。此时应尽快读取USART_DR。
    读取USART_DR或向该位写0,都可以清除该位。
    TC(发送完成):当该位被置位时,说明USART_DR中的数据已经被发送完成了。如果设置了这个位的中断,则会产生中断。
    清零该位的两种方法:a、读取USART_SR,写USART_DR。b、直接向该位写0。
    读取串口状态的函数:
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
函数的第二个参数很关键,它表示我们要查看串口的哪个状态,比如要判断读寄存器是否为非kong(RXNE),执行:
USART_GetFlagStatus(USART1, USART_FLAG_RXNE);
如果要判断发送是否完成(TC),执行:
USART_GetFlagStatus(USART1, USART_FLAG_TC);
这些标志号在MDK里面通过宏定义实现:
#define USART_IT_PE ((uint16_t)0x0028)
#define USART_IT_TXE ((uint16_t)0x0727)
#define USART_IT_TC ((uint16_t)0x0626)
#define USART_IT_RXNE ((uint16_t)0x0525)
#define USART_IT_IDLE ((uint16_t)0x0424)
#define USART_IT_LBD ((uint16_t)0x0846)
#define USART_IT_CTS ((uint16_t)0x096A)
#define USART_IT_ERR ((uint16_t)0x0060)
#define USART_IT_ORE ((uint16_t)0x0360)
#define USART_IT_NE ((uint16_t)0x0260)
#define USART_IT_FE ((uint16_t)0x0160)
    ⑥ 串口使能。
USART_Cmd(USART1, ENABLE); //使能串口
    ⑦ 开启串口响应中断。
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
    函数第二个参数很关键,它表示使能串口的类型,即使能哪种串口中断,因为串口中断有很多种。比如:
在接收到数据时(RXNE读寄存器非空),产生中断,则开启中断的方法为:
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启中断,接收到数据中断
在发送数据结束时(TC,发送完成)要产生中断,则开启中断的方法为:
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
    ⑧ 获取相应中断状态。
     我们使能了某个中断,则当中断发生时,就会设置状态寄存器中的某个标志位。
    在中断处理函数中,要判断哪种中断是否发生,可以判断对应的某个标志位。使用的函数是:
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
    比如,使能了串口发送完成中断,则当中断发生时,可以在中断处理函数中使用下面语句来判断是否发生串口发送完成中断。
USART_GetITStatus(USART1, USART_IT_TC);
返回值是SET,说明串口发送完成中断发生了。
9.2 硬件设计
    本实验用到的硬件资源有:
1)指示灯DS0;
2)串口1。
9.3 软件设计
本章代码设计比较简单,因为串口初始化和接收的代码用的是SYSTEM文件夹下面的串口内容。这里对代码部分稍作讲解。
//初始化IO串口1
//bound:波特率
void uart_init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//① 串口时钟使能,GPIO时钟使能,复用时钟使能:在使用一个内置外设的时候,首先要使能相应的GPIO时钟,然后使能复用功能时钟和内置外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); //使能USART1和GPIOA时钟
//② 串口复位
USART_DeInit(USART1); //复位串口1
//③ GPIO端口模式配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //USART1_TX PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //USART1_RX PA.10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA.10
//④ 串口参数初始化
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); //初始化串口
#ifEN_USART1_RX //如果使能了接收
//⑤ 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); //初始化中断优先级
//⑥ 开启中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启中断
//⑦ 使能串口
USART_Cmd(USART1, ENABLE); //使能串口
}
    从代码中看出,初始化串口的过程,和前面介绍的一致,用标号①~⑦表示了顺序:
① 串口时钟使能,GPIO时钟使能;
② 串口复位;
③ GPIO端口模式设置;
④ 串口参数初始化;
⑤ 初始化NVIC;
⑥ 开启中断;
⑦ 使能串口。
    复用功能下GPIO模式怎么判定:
    需要查看《中文参考手册V10》P110的表格“8.1.11外设的GPIO配置”,这个我们在前面的端口复用章节有提到,这里再讲解一下。
    串口GPIO模式配置表:
序号 USART引脚 配置 GPIO配置
1 USARTx_TX 全双工模式 推挽复用输出
半双工模式 推挽复用输出
1 USARTx_Rx 全双工模式 浮空输入或带上拉输入
半双工模式 未用,可作为通用I/O
    查看手册得知,配置全双工的串口1,则TX(PA9)需要配置为推挽复用输出,RX(PA10)配置为浮空输入或带上拉输入。
 中断处理函数
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;

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(USART_GetITStatus(USART1,
USART_IT_RXNE)!=
RESET)
判断是否是接收中断,如果是,则读取串口接收到的数据:
Res=USART_ReceiveData(USART1);
    接下来对数据进行分析:
    这里设计了一个小小的接收协议:通过这个函数,配合一个数组USART_RX_BUF[],一个接收状态标志
USART_RX_STA实现对串口数据的接收管理。
    USART_RX_STA各位定义如下:
                                              USART_RX_STA
bit15 bit14 bit13~0
接收完成标志 接收到0x0d标志 接收到的有效数据个数
    USART_RX_BUF的大小由USART_REC_LEN定义。
    设计思路如下:
    当接收到从电脑发过来的数据后,把接收到的数据保存在USART_RX_BUF中,同时在接收状态标志
USART_RX_STA中计算接收到的有效数据个数,当收到回车(0x0d、0x0a)的第一个字节0x0d时,计时器
将不再增加,等待0x0a的到来。
    如果0x0a没有到,则认为此次接收失败,重新开始下一次接收。
    如果顺利接收到0x0a,则标记USART_RX_STA的第15位,这样完成一次接收,并等待该位被其他程序
清除,从而开始下一次接收。
    如果在收到数据超过 USART_REC_LEN的时候,还没有收到0x0d,则会丢弃前面的数据,重新接收。
9.4 下载验证

————————————————
版权声明:本文为CSDN博主「sz189981」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sz189981/article/details/69487046

猜你喜欢

转载自www.cnblogs.com/wuhoudezhenyu/p/11839697.html