一、理论基础
1、外部中断(EXTI)
1.1、外部中断(EXTI)简介
外部中断,管理了控制器的 20个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。
1.2、 外部中断功能框图
EXTI 的功能框图包含了 EXTI 最核心内容,掌握了功能框图,对 EXTI 就有一个整体的把握,在编程时思路就非常清晰。EXTI功能框图见图。
在图可以看到很多在信号线上打一个斜杠并标注“20”字样,这个表示在控制器内部类似的信号线路有 20 个,这与 EXTI 总共有 20 个中断/事件线是吻合的。所以我们只要明白其中一个的原理,那其他 19 个线路原理也就知道了。
EXTI 可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件上就有所不同。
首先我们来看图中红色虚线指示的电路流程。它是一个产生中断的线路,最终信号流入到 NVIC 控制器内。
编号 1 是输入线,EXTI 控制器有 19 个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个 GPIO,也可以是一些外设的事件,这部分内容我们将在后面专门讲解。输入线一般是存在电平变化的信号。
编号 2 是一个边沿检测电路,它会根据上升沿触发选择寄存(EXTI_RTSR)和下降沿触发选择寄存器(EXTI_FTSR)对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给编号 3 电路,否则输出无效信号0。而 EXTI_RTSR 和 EXTI_FTSR 两个寄存器可以控制器需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。
编号 3 电路实际就是一个或门电路,它一个输入来自编号 2 电路,另外一个输入来自软件中断事件寄存器(EXTI_SWIER)。EXTI_SWIER允许我们通过程序控制就可以启动中断/事件线,这在某些地方非常有用。我们知道或门的作用就是有 1 就为 1,所以这两个输入随便一个有有效信号 1就可以输出 1 给编号 4和编号 6电路。
编号 4 电路是一个与门电路,它一个输入是编号 3 电路,另外一个输入来自中断屏蔽寄存器(EXTI_IMR)。与门电路要求输入都为 1 才输出 1,导致的结果是如果 EXTI_IMR 设置为 0 时,那不管编号 3 电路的输出信号是 1 还是 0,最终编号 4 电路输出的信号都为 0;
如果EXTI_IMR设置为1时,最终编号4电路输出的信号才由编号3电路的输出信号决定,这样我们可以简单的控制 EXTI_IMR 来实现是否产生中断的目的。编号 4 电路输出的信号会被保存到挂起寄存器(EXTI_PR)内,如果确定编号 4 电路输出为 1 就会把 EXTI_PR 对应位置 1。
编号 5 是将 EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制。
接下来我们来看看绿色虚线指示的电路流程。它是一个产生事件的线路,最终输出一个脉冲信号。
产生事件线路是在编号3电路之后与中断线路有所不同,之前电路都是共用的。
编号6电路是一个与门,它一个输入来自编号 3 电路,另外一个输入来自事件屏蔽寄存器(EXTI_EMR)。如果 EXTI_EMR设置为 0时,那不管编号 3电路的输出信号是 1还是 0,最终编号 6 电路输出的信号都为 0;如果 EXTI_EMR 设置为 1 时,最终编号 6 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_EMR 来实现是否产生
事件的目的。
编号 7 是一个脉冲发生器电路,当它的输入端,即编号 6 电路的输出端,是一个有效信号 1 时就会产生一个脉冲;如果输入端是无效信号就不会输出脉冲。
编号 8 是一个脉冲信号,就是产生事件的线路最终的产物,这个脉冲信号可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器 ADC等等,这样的脉冲信号一般用来触发 TIM 或者 ADC开始转换。
产生中断线路目的是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。
2、串口通信
2.1、HAL库UART函数库介绍
2.1.1、串口发送/接收函数
HAL_UART_Transmit();串口发送数据,使用超时管理机制
HAL_UART_Receive();串口接收数据,使用超时管理机制
HAL_UART_Transmit_IT();串口中断模式发送
HAL_UART_Receive_IT();串口中断模式接收
HAL_UART_Transmit_DMA();串口DMA模式发送
HAL_UART_Transmit_DMA();串口DMA模式接收
2.1.2、串口中断函数
HAL_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断处理函数
HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //串口发送中断回调函数
HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); //串口发送一半中断回调函数(用的较少)
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //串口接收中断回调函数
HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//串口接收一半回调函数(用的较少)
HAL_UART_ErrorCallback();串口接收错误函数
3、串口DMA
DMA定义:
DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU的干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作。
DMA传输方式:
DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:
- 外设到内存 Peripheral To Memory
- 内存到外设 Memory To Peripheral
- 内存到内存 Memory To Memory
- 外设到外设 Peripheral To Peripheral
二、基于外部中断操作LED
1、新建工程
1.1、点击框图内新建工程
1.2、选择芯片
1.3、时钟配置
1.3.1、在Pinout&Configuration–System Core–RCC选择外部晶振,如下图所示
1.3.2、配置时钟,设时钟频率为72MHZ
1.4、设置引脚
PA6引脚和外部中断EXTI1,PB1
1.5、中断设置
1.5.1、给中断设置下降沿触发,并且上拉电阻。
1.5.2、 配置中断优先级,因为此处只有一个中断,因此它的优先级为0,为最高,如果有多个中断则可设为0,1,2。
1.6、建立项目
1.6.1、输入项目名称和项目地址,在Toolchain/IDE选择MDK-ARM
1.6.2、保存后打开文件所在地
1.6.3、直接打开生成的project文件
2、编译与写程序
可以看到生成的中断服务函数 void EXTI1_IRQHandler(void), 该函数又调用了HAL_GPIO_EXTI_IRQHandler(),于是继续跳转到下一函数
可以看到在这之中调用了HAL_GPIO_EXTI_Callback(),接下来的函数可以看到是_weak开头,则需要用户自己写函数
此时到main.c中书写callback程序,用到的库函数是HAL_GPIO_TogglePin(),该函数的作用是翻转电平,即中断一产生,则翻转一次电平。
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
{HAL_GPIO_TogglePin(led1_GPIO_Port,led1_Pin);
}
}
环境设置
进行编译
烧录
3、实验结果
三、串口中断实现串口通信
1.创建工程
此项目RCC和SYS和CLOCK设置如上面一样
下面设置串口USART1,在MODE下选择Asynchronous(异步通信模式),并且使得USART1中断使能
然后直接保存
2、重定向printf和scanf
打开该文件
在 stm32f1xx_hal.c中包含#include <stdio.h>
#include <stdio.h>
extern UART_HandleTypeDef huart1; //声明串口
在stm32f1xx_hal.c 中重写fget和fput函数
/**
* 函数功能: 重定向c库函数printf到DEBUG_USARTx
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
/**
* 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
return ch;
}
在main.c中添加
#define RXBUFFERSIZE 256
char RxBuffer[RXBUFFERSIZE];
printf("hello world\n");
HAL_Delay(1000);
在target勾选Use MicroLIB,需要调用微型库
编译
然后进行烧录得到的运行结果
四、串口DMA接收发数据
1、创建工程
下面我们在CubeMx中配置工程,系统、时钟等跳过,只看串口和DMA设置这一块。
在Pinout&Configuration–Connectivity–USART1–Mode中打开串口1,配置为Asynchronous
点击add,选择USART_RX USART_TX 传输速率设置为中速 ,右侧点击System Core 点击DMA
点击add添加MENTOMEN
然后保存,并打开文件夹
2、测试
在main.c中添加
uint8_t Senbuff[] = "hello world";
定义一个数组
while循环中添加
HAL_UART_Transmit_DMA(&huart1, Senbuff, sizeof(Senbuff));
HAL_Delay(1000);
然后进行编译
烧录后得到测试结果