目录
前言
使用的是正点原子的探索者开发板进行学习,芯片:STM32F407ZGTx
学习说明此文档为本人的学习笔记,注重实践,关于理论部分会给出相应的学习链接。
本文档添加了对代码的在线调试功能,有助于大家更好理解相关寄存器的变化。
不用正点原子提供的中断文件uart文件,手把手从头自己编写串口通讯。
理论学习
一、USART介绍
关于串口的理论可以参考,本人的另一篇博客
1、串口(UART/COM/TTL/RS232/RS485)_追逐者-桥的博客-CSDN博客嵌入式MCU串口通信的基础知识https://blog.csdn.net/ARM_qiao/article/details/125103127在嵌入式中所说的串口也就是USART,因此本片博文是直接对USART做简略介绍。
这里是对STM32F4系列的串口进行介绍。
1.1、USART简介
通用同步异步收发器(USART)是全双工数据交换,满足外部设备对工业标准NRZ异步串行数据格式要求。通过小数波特率发生器提过多种波特率。支持同步单向通信和半双工单线通信;还支持LIN(局域互连网络)、智能卡协议与IrDA(红外数据协会)SIR ENDEC规范;调制解调器操作(CTS/RTS);多处理器通讯;可以通过配置多个缓冲区使用DMA实现高速数据通讯。
1.2、USART主要特征
- 全双工异步通讯、单线半双工通信
- 具有小数波特率发生器,可编程收发波特率
- 可配置16倍过采样或8倍过采样,因而速度容差与时钟容差的灵活配置提供可能
- 数据长度可编程(8位或9位),停止位可配置(1位或2位)
- 可以用于同步发送的发送器时钟输出
- 具有4个错误标志和10个标志位的中断源
- 两个接收器唤醒模式:地址位(MSB,第9位),线路空闲
1.3、USART功能说明
- 引脚说明:RX-接受数据引脚;TX-发送数据引脚;SCLK-发送器时钟输出;nCTS-"清除以发送"用于在当前传输结束时阻止数据发送(高电平时);nRTS-"请求以发送"用于指示USART已准备好接收数据(低电平)。
- 正常USART模式下(异步全双工),RX、TX以帧的形式发送和接收串行数据:1、发送或接收前保持空闲线路;2、起始位;3、数据(字长 8 位或 9 位);4、最低有效位在前、用于指示帧传输已完成的 0.5 个、1 个、1.5 个、2 个停止位;5、该接口使用小数波特率发生器 - 带 12 位尾数和 4 位小数;6、状态寄存器 (USART_SR);7、数据寄存器 (USART_DR);8、波特率寄存器 (USART_BRR) - 12 位尾数和 4 位小数;9、智能卡模式下的保护时间寄存器 (USART_GTPR)。
- 同步模式下:RX、TX、SCLK引脚
- 硬件流控制模式下:RX、TX、SCLK、nCTS、nRTS
1.3.1 传输的数据结构
- 起始位
- 数据位(8位或9位)
- 奇偶校验位(第9位)
- 停止位(1,15,2位)
可通过对 USART_CR1 寄存器中的 M 位进行编程来选择 8 位或 9 位的字长。
1.3.3 实现的功能
这里不对功能进行详细介绍,如果想深入了解请参考芯片的参考手册。
发送器、接收器、小数波特率生成、接收器对时钟偏差的容差、多处理器通讯、奇偶校验控制、LIN(局域互连网络)模式、同步模式、单线半双工通讯、智能卡、IrDA SIR END EC模块、使用DMA进行连续通讯、硬件流控制。
1.4、USART模式配置(串口的波特率、状态、控制)
本博客只是简单的实现串口的基本异步通讯功能,USB串口和电脑通讯。不做过多深入的介绍。 串口最基本的设置,就是波特率的设置。STM32F4的串口,只要开启串口时钟、并设置相应IO口的模式、配置一下波特率、数据长度、奇偶校验位等信息,就可以使用了。
- 串口时钟使能:APB1/2ENR
- 串口控制:每个串口都有3个控制寄存器USART_CR1~3
- 数据发送与接收:数据寄存器USART_DR(双寄存器),TDR(写入)和RDR(读取)
- 串口状态:状态寄存器USART_SR
- 串口波特率设置:波特率寄存器USART_BRR
以上是相关的寄存器,寄存器具体相关位配置请参考芯片的参考手册
二、IO口复用功能输入/输出
2.1 复用功能简介
![](https://i-blog.csdnimg.cn/blog_migrate/78dc3feb9ebf24f748a8db8a610a6d96.png)
2.2 复用功能配置
除ADC和DAC配置为模拟通道以外其他一律为复用功能。
2.3 USART1对应的复用功能映射引脚
三、NVIC中断优先级管理(串口中断)
3.1 NVIC特征
STM30F40X具有92个中断(10个内核中断,82个可屏蔽中断),16个可编程优先级(使用了4位中断优先级),低延迟异常和中断处理。
3.2 中断管理方法
首先将STM32中断分为0~4组,每组都有一个抢占优先级和响应优先级。通过寄存器SCB->AIRCR来配置。
- 高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。
- 抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断。
- 抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行。
- 如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;
注:常用 ISER、ICER 和 IP 这三个寄存器,ISER 用来使能中断,ICER 用来失能中断,IP 用来设置中断优先级。
3.3 中断设置相关寄存器
与之相匹配的结构体:
- __IO uint8_t IP[240]; //中断优先级控制的寄存器组,Interrupt Priority Registers
240个8位寄存器,每个中断使用一个寄存器来确定优先级,STM32F40x系列一共82个可屏蔽中断,使用IP[81]~IP[0]。每个IP寄存器的高4位用来设置抢占和响应优先级(根据分组),低4位没有用到。 例如:NVIC->IP[37] = 0xf0; //对应将USART1的中断,设置位抢占2响应2.
这里的37是指中断向量表中的位置
- __IO uint32_t ISER[8]; //中断使能寄存器组
32位寄存器,每个位控制一个中断的使能。STM32F40x只有82个可屏蔽中断,所以只使用了其中的ISER[0]~ISER[2]。 ISER[0]的bit0~bit31分别对应中断0~31。ISER[1]的bit0~27对应中断32~63;ISER[2]的bit0~17对应中断64~81; 例如:NVIC->ISER[1] = 1<<(37%32);
- __IO uint32_t ICER[8]; //中断失能寄存器组,配置方法与ISER相同
- __IO uint32_t ISPR[8]; //中断挂起寄存器组
- __IO uint32_t ICPR[8]; //中断解挂寄存器组
- __IO uint32_t IABR[8]; //中断激活标志位寄存器组,只读,为1时,说明中断正在执行。
3.3 中断优先级设置步骤
一般情况下,系统代码执行过程中,只执行一次中断优先级分组。
- 设置中断优先级分组
- 针对每个中断,设置对应的抢占优先级和响应优先级
- 如果需要挂起和解挂,查看中断当前激活状态,分别调用相关函数。
3.4 USART相关的中断事件
实战演练
一、设计规划
1.1 实验目标
1.2 硬件资源
(1)指示灯DS0;(2)串口1
本实验用到了串口1,但PCB上并没有与USB链接,需要使用跳线帽将其硬件连接。
二、程序设计
2.1 建立工程
为了更加深入的学习串口的配置,这里单独建立了USART1的配置文件,配置用来学习。
为什么要单独配置,在实际应用串口时,不仅仅只用串口1,但是正点原子只提供了USART1的配置文件,如果不深入只是走马观花而已。
2.2 串口配置讲解
参考资料:
stm32f407之USART(操作寄存器)_w471176877的博客-CSDN博客
STM32F407 纯寄存器操作GPIO,串口,中断(专治花里胡哨)_大大U的博客-CSDN博客
- 常用的串口相关寄存器:USART_SR(状态)、DR(数据)、BRR(波特率)、CR1~3(控制)
- 设置步骤:
1. 设置中断优先级分组(如果之前没有设置),这个最好一个程序里只在开头设置一次。
2. 使能相关时钟:GPIO(PA9/10)、串口(USART1)。
3. 设置相关GPIO引脚为复用模式【MODE】,引脚的速度,方式。
5. 选择相关GPIO引脚的复用功能(【AFR】映射到USART1)。
6. 设置波特率【BRR】, 设置控制寄存器【CR1~3】。
8. 如果要用到中断,设置USART中断优先级,使能USART中断。
10. 如果要用中断,编写中断服务函数(函数名是固定的)。
11. 中断服务函数里检查是哪个中断。
12. 编写相应服务程序。
对以上步骤进行总结:GPIO、USART、中断优先级、串口中断服务函数、主函数
- usart1.h
#ifndef __USART1_H
#define __USART1_H
#include "sys.h"
#include "stdio.h"
#define USART1_MAX_REC_LEN 200
extern u8 USART1_RX_BUF[USART1_MAX_REC_LEN]; //接收数据缓存区
extern u8 RX_data_counter; //接收字节计数器
extern u8 RX_stat_fin; //串口接收状态,1接收完成,0接收未完成
void usart1_init(u32 pclk2,u32 bound);
#endif
- usart1.c
#include "usart1.h"
#include "sys.h"
u8 USART1_RX_BUF[USART1_MAX_REC_LEN];
u8 RX_data_counter=0;
u8 RX_stat_fin=0;
//USART1中断服务函数
void USART1_IRQHandler(void)
{
if(USART1->SR & (1<<5)) //判断是否接收完成
{
USART1_RX_BUF[RX_data_counter] = USART1->DR;
RX_data_counter++;
if(USART1_RX_BUF[RX_data_counter-1] == 0X0D) //0X0D:结束标志,'\r'回车键 '\n'
{
RX_data_counter = 0;
USART1->CR1 &= ~(1<<5); //清除中断使能
RX_stat_fin = 1; //接收完成标志
}
}
}
//串口1初始化:pclk2:APB2的时钟 84M bound:波特率 115200
void usart1_init(u32 pclk2,u32 bound)
{
//求除波特率对用的BRR相关位的值
float temp;
u16 mantissa;
u16 fraction;
temp=(float)(pclk2*1000000)/(bound*16);
mantissa=temp;
fraction=(temp-mantissa)*16;
mantissa<<=4;
mantissa+=fraction;
//时钟使能
RCC->AHB1ENR |= 1<<0; //GPIOA
RCC->APB2ENR |= 1<<4; //USART1
//GPIO: AF PP 50M PU
GPIOA->MODER &= 0XFFC3FFFF;
GPIOA->MODER |= 0X00280000; //PA9/10 AF
GPIOA->OSPEEDR &= 0XFFC3FFFF;
GPIOA->OSPEEDR |= 0X00280000; //PA9/10 50M
GPIOA->PUPDR &= 0XFFC3FFFF;
GPIOA->PUPDR |= 0X00140000; //PA9/10 PU
//GPIOA->AF(USART1)
GPIOA->AFR[1] &= 0XFFFFF00F;
GPIOA->AFR[1] |= 0X00000770;
//波特率寄存器设置
USART1->BRR=mantissa;
USART1->CR1&=~(1<<15);
//USART1控制
USART1->CR1 |= 1<<3; //串口发送使能
USART1->CR1 |= 1<<2; //串口接收使能
USART1->CR1 |= 1<<5; //接收缓冲区非空中断使能,ORE=1或RXNE=1时,自动置为,生成中断
USART1->CR1 |= 1<<13; //串口使能
//中断优先级设置,这里未进行分组,将分组放到了主函数(分组最好只分一次)
NVIC->IP[37] = 0xf0; //相关寄存器配置请参考前面的理论学习部分
NVIC->ISER[1] = 1<<(37%32);
这里注意时钟的使能:RCC->APB2ENR |= 1<<4; //USART1 ,外设是挂在再APB总线上。
而不是AHB2ENR |= 1<<4,如过这里出错将无法往寄存器中写值。
2.3 主函数text.c
此时已经将串口配置完成,也声明了接收数据的变量。要将接收到的数据发送回去,就需要在主函数中进行发送即,将接收的数据重新写入到串口的DR寄存器中即可。
这里说一下数据格式:串口中断服务函数,在接收数据时判断是否有回车键作为接收完成标志, //0X0D:结束标志,'\r'回车键 (在调试的时候会观察数据)。
#include "sys.h"
#include "delay.h"
#include "usart1.h"
#include "led.h"
int main(void)
{
char TX_data_counter = 0; //发送计数器
Stm32_Clock_Init(336,8,2,7);
delay_init(168);
SCB->AIRCR |= 0X05FA0000 | 0x500; //在这里设置中断组号
usart1_init(84,115200);
LED_Init();
while(1)
{
if(RX_stat_fin)
{
LED0 = 1;
LED1 = 0; //嵌入式发送指示灯
delay_ms(10);
//for(j=0;j<strlen((char*)USART3_RX_BUF);j++) //另一种判断数据发送结束方法
if(USART1->SR & (1<<7))
{
USART1->DR = USART1_RX_BUF[TX_data_counter];
TX_data_counter++;
if(USART1_RX_BUF[TX_data_counter] == '\r') //将回车键'r'传回
{
USART1->DR = USART1_RX_BUF[TX_data_counter];
TX_data_counter = 0;
USART1->CR1 |= 1<<5;
RX_stat_fin = 0;
}
}
}
else
{
LED0=0;
LED1=1;
}
}
}
三、在线调试
下载完成完成后,打开调试,点击运行,并调出RCC、GPIOA、USART1相关的寄存器,,添加变量USART1_RX_BUF观察接收的数据。
在串口初始化函数处打断点,并进入函数观察寄存器的变化,点击
点击进入串口初始化函数内部,并在函数内部结束处打一个断点观察寄存器的变化。
时钟响应的位已经使能:
根据GPIOA相关位配置对照,也并无问题:
串口USART1相关的寄存器配置也并无问题:
观察接收变量:地址 名称 值
当打开串口调试助手向单片机发送4256可以观察到接收变量其值的变化
四、上板验证
将程序编译并下载到开发板中
- 打开串口调试助手
向从串口调试助手向开发板发送4256,其也会返回4256的值。
同时在串口助手向开发板发送数据时,也出出现DS0和DS1闪烁的现象。DS0常量表示静默状态(既不发送,也不接收)