目录
前言
理论学习
一、USART介绍
1.1、USART简介
1.2、USART主要特征
1.3、USART功能说明
1.4、USART模式配置(串口的波特率、状态、控制)
二、IO口复用功能输入/输出
2.1 复用功能简介
2.2 复用功能配置
2.3 USART1对应的复用功能映射引脚
三、NVIC中断优先级管理(串口中断)
3.1 NVIC特征
3.2 中断管理方法
3.3 中断设置相关寄存器
3.3 中断优先级设置步骤
3.4 USART相关的中断事件
实战演练
一、设计规划
1.1 实验目标
1.2 硬件资源
二、程序设计
2.1 建立工程
2.2 串口配置讲解
2.3 主函数text.c
三、在线调试
四、上板验证
使用的是正点原子的探索者开发板进行学习,芯片:STM32F407ZGTx
学习说明此文档为本人的学习笔记,注重实践,关于理论部分会给出相应的学习链接。
本文档添加了对代码的在线调试功能,有助于大家更好理解相关寄存器的变化。
不用正点原子提供的中断文件uart文件,手把手从头自己编写串口通讯。
关于串口的理论可以参考,本人的另一篇博客
1、串口(UART/COM/TTL/RS232/RS485)_追逐者-桥的博客-CSDN博客嵌入式MCU串口通信的基础知识https://blog.csdn.net/ARM_qiao/article/details/125103127在嵌入式中所说的串口也就是USART,因此本片博文是直接对USART做简略介绍。
这里是对STM32F4系列的串口进行介绍。
通用同步异步收发器(USART)是全双工数据交换,满足外部设备对工业标准NRZ异步串行数据格式要求。通过小数波特率发生器提过多种波特率。支持同步单向通信和半双工单线通信;还支持LIN(局域互连网络)、智能卡协议与IrDA(红外数据协会)SIR ENDEC规范;调制解调器操作(CTS/RTS);多处理器通讯;可以通过配置多个缓冲区使用DMA实现高速数据通讯。
1.3.1 传输的数据结构
可通过对 USART_CR1 寄存器中的 M 位进行编程来选择 8 位或 9 位的字长。
1.3.3 实现的功能
这里不对功能进行详细介绍,如果想深入了解请参考芯片的参考手册。
发送器、接收器、小数波特率生成、接收器对时钟偏差的容差、多处理器通讯、奇偶校验控制、LIN(局域互连网络)模式、同步模式、单线半双工通讯、智能卡、IrDA SIR END EC模块、使用DMA进行连续通讯、硬件流控制。
本博客只是简单的实现串口的基本异步通讯功能,USB串口和电脑通讯。不做过多深入的介绍。 串口最基本的设置,就是波特率的设置。STM32F4的串口,只要开启串口时钟、并设置相应IO口的模式、配置一下波特率、数据长度、奇偶校验位等信息,就可以使用了。
以上是相关的寄存器,寄存器具体相关位配置请参考芯片的参考手册
NVIC简介_嗯哦对的博客-CSDN博客_什么是nvic
STM30F40X具有92个中断(10个内核中断,82个可屏蔽中断),16个可编程优先级(使用了4位中断优先级),低延迟异常和中断处理。
首先将STM32中断分为0~4组,每组都有一个抢占优先级和响应优先级。通过寄存器SCB->AIRCR来配置。
注:常用 ISER、ICER 和 IP 这三个寄存器,ISER 用来使能中断,ICER 用来失能中断,IP 用来设置中断优先级。
与之相匹配的结构体:
240个8位寄存器,每个中断使用一个寄存器来确定优先级,STM32F40x系列一共82个可屏蔽中断,使用IP[81]~IP[0]。每个IP寄存器的高4位用来设置抢占和响应优先级(根据分组),低4位没有用到。 例如:NVIC->IP[37] = 0xf0; //对应将USART1的中断,设置位抢占2响应2.
这里的37是指中断向量表中的位置
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);
一般情况下,系统代码执行过程中,只执行一次中断优先级分组。
(1)指示灯DS0;(2)串口1
本实验用到了串口1,但PCB上并没有与USB链接,需要使用跳线帽将其硬件连接。
为了更加深入的学习串口的配置,这里单独建立了USART1的配置文件,配置用来学习。
为什么要单独配置,在实际应用串口时,不仅仅只用串口1,但是正点原子只提供了USART1的配置文件,如果不深入只是走马观花而已。
参考资料:
stm32f407之USART(操作寄存器)_w471176877的博客-CSDN博客
STM32F407 纯寄存器操作GPIO,串口,中断(专治花里胡哨)_大大U的博客-CSDN博客
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、中断优先级、串口中断服务函数、主函数
#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
#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,如过这里出错将无法往寄存器中写值。
此时已经将串口配置完成,也声明了接收数据的变量。要将接收到的数据发送回去,就需要在主函数中进行发送即,将接收的数据重新写入到串口的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;jSR & (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常量表示静默状态(既不发送,也不接收)