目录
STM32NVIC中断系统:
因为咱们的串口需要用到中断,所以我们先研究一下中断;
中断处理机制
stm32G431总共有
111
个中断源,所以有时难免有两个或者两个以上的中断一起来临,或者正在处理一个中断服务函数时突然又有一个中断来临,以上种种情况微控制器要怎样运行呢?所以微控制器都有一个处理中断的机制。
stm32
系列芯片用到的机制是:
NVIC
。
NVIC
即嵌套向量中断控制器
(Nested Vectored Interrupt Controller)
。
STM32
的中有一个强大而方便的
NVIC
,它是属于
CM4
内核的器件。
NVIC
控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。但是各个芯片厂商在设计芯片的时候会对
CM4
内核里面的
NVIC
进行裁剪,把不需要的部分去掉,所以说
STM32
的
NVIC
是
CM4
的
NVIC
的一个子集。
NVIC
寄存器定义在
core_cm4.h
文件中,
CM4
内核支持
256
个中断,其中包含了
16
个系统异常和
240
个外部中断,并且具有
256
级的可编程中断设置。但
STM32
并没有使用
CM4
内核的全部东西,而是只用了它的一部分。
stm32G431
芯片有
111
个中断,包括
9
个内核中断和
102
个可屏蔽中断,具有
16
级可编程的中断优先级,我们常用的就是这
102
个可屏蔽中断。
回忆系统运行的逻辑:
首先先会进入中断向量表
先执行第一条 第一条是指向堆栈的栈顶,然后进入第二条复位,
复位函数就控制运行到main函数里边,发生中断了会执行相应的中断函数
具体怎么用请参考中断向量表
Cortex-M4优先级设定:
支持
3
个固定的高优先级和多达
256
级的可编程优先级,支持
128
级抢占。
每个中断的优先级由一个
8
位的寄存器来设定,分为高低两个位段。高位段表示抢占优先级,低位段表示响应(子)优先级。
CM4
允许最少使用位数为
3
个位,即至少要支持
8
级优先级。
优先级以
MSB
对齐,简化程序的跨器件移植。
优先级分组:
STM32
中有两个优先级的概念:抢占式优先级和响应优先级,响应优先级也称子优先级,每个中断源都需要被指定这两种优先级。
具有
高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应
,即中断嵌套,或者说高抢占式优先级的中断可以嵌套在低抢占式优先级的中断中。
当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,
当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理
。
当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,
当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理
。
NVIC相关函数–设置中断优先级分组
HAL_NVIC_SetPriority
函数用于设置一个中断的优先级,它有三个形参,第一个为
IRQn_Type
类型参数,指定中断源。
第二个和第三个形参分别设定中断的抢占式优先级和响应优先级,这个的设置要与中断组配合使用。
HAL_NVIC_SetPriority
函数实际是调用定义在
core_cm4.h
文件的
NVIC_SetPriority
函数实现功能的,该函数通过设置
SCB_SHP
寄存器或者
NVIC_IPRx
寄存器实现功能。
HAL_NVIC_EnableIRQ
函数用于在
NVIC
控制器中使能指定中断,它有一个形参,是
IRQn_Type
类型参数。
HAL_NVIC_EnableIRQ
函数实际是通过调用定义在
core_cm4.h
文件中的
NVIC_EnableIRQ
函数实现功能,
NVIC_EnableIRQ
函数设置了
NVIC_ISER
寄存器内容。
HAL_NVIC_DisableIRQ
函数是在
NVIC
控制器中禁用指定中断,用法与
HAL_NVIC_EnableIRQ
函数相同。最终通过设置
NVIC_ICER
寄存器值实现功能
HAL_NVIC_SystemReset
函数用于初始化一个
MCU
复位要求,它设计调用
NVIC_SystemReset
函数实现功能。
EXTI—外部中断、事件控制器
STM32G431
每个引脚都可以设置为外部线中断输入。实际上,
stm32
芯片集成了一个外部中断
/
事件控制器
(EXTI)
,由
23
个能产生事件
/
中断请求的边沿检测器组成。每个输入线可以独立地配置输入类型
(
事件或中断
)
和对应的触发事件
(
上升沿或下降沿或者双边沿都触发
)
。每个输入线都可以独立地被屏蔽。挂起寄存器保持着状态线的中断请求。
23个中断/实践请求包括:
IO
可以做为
EXTI
线
(0..15)【
常用
】
EXTI
线
16
连接到
PVD
(可编程电压监测器,用于掉电检测)
输出
EXTI
线
17
连接到
RTC
闹钟事件
EXTI
线
18
连接到
USB OTG FS
唤醒事件
EXTI
线
19
连接到以太网唤醒事件
EXTI
线
20
连接到
USB OTG HS
(在
FS
中配置)唤醒事件
EXTI
线
21
连接到
RTC
入侵和时间戳事件
EXTI
线
22
连接到
RTC
唤醒事件
使用外部线中断需要开启
AFIO中对应的中断功能。
引脚对应的外部中断线
每个引脚都可以设置为外部线中断输入(即为上图的可配置事件)
中断配置实例—按键中断;
在对应文件中建立按键中断的.c和.h文件
预期:把按键当作外中断引脚,当按键按下去的时候,在中断函数当中使灯反转一次
引脚;PA0
打开cubeMX:将GPIO配置成如下图的模式:
在回到GPIO中将模式修改为下降沿触发:
回到Systrm Core 的NVIC当中配置优先级分组
其中EXTI Line0 interrupt 中的1一般根据情况来定,
下边就是移植相应的程序
找到gpio.c将所有相关按键中断的初始化移植到按键中断.c文件的函数当中。
void Key_Exit_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin : PA0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}
初始化完中断,我们肯定是要继续赵中断服务函数了,跳转到”stm32g4xx_it.c”文件里边
/**
* @brief This function handles EXTI line0 interrupt.
*/
void EXTI0_IRQHandler(void)
{
/* USER CODE BEGIN EXTI0_IRQn 0 */
/* USER CODE END EXTI0_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
/* USER CODE BEGIN EXTI0_IRQn 1 */
/* USER CODE END EXTI0_IRQn 1 */
}
找到该代码,往代码底层转跳
/**
* @brief Handle EXTI interrupt request.
* @param GPIO_Pin Specifies the port pin connected to corresponding EXTI line.
* @retval None
*/
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
转跳到中断请求函数当中有
HAL_GPIO_EXTI_Callback(GPIO_Pin);//中断回调函数
转跳的这个函数里边是说EXTI线路检测回调,及就是中断回调,然而转跳到测函数当中前边有个__weak;弱定义
/**
* @brief EXTI line detection callback.
* @param GPIO_Pin: Specifies the port pin connected to corresponding EXTI line.
* @retval None
*/
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(GPIO_Pin);
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
解释是:不应修改此函数,当需要回调时,可以在用户文件中实现HAL_GPIO_EXTI_callback
意思就是如果使用该函数的话,需要自己在编写,我写的在下边
/*
* @brief 中断服务函数//HAL库中断函数
* @param
* @return
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_PIN)
{
static int ucLed;
if(GPIO_PIN == GPIO_PIN_0)
{
ucLed^=0xff;
LED_Display(ucLed);
}
}
将初始化函数在.h中声明加到main文件中即可调用
中断配置实例—串口通信;
串口介绍:
串口通信一般最简单的有两个中断:串口接收中断,串口发送中断。实际上常用串口接收中断,这样可以在检测到有外部设备对本机通过串口发送数据时就可以马上进入中断服务函数,在服务函数中接收数据
通用异步收发传输器简称:UART。
STM32上的USART外设可以实现同步传输功能,所以外设名为USART,比UART多了一个S,即synchronous(同步)。
UART主要是用来产生相关接口的协议信号,如RS232/RS485等串行接口标准规范和总线标准规范使用传输数据这些接口就要按照规定的协议信号发送数据。所以UART广泛用于串口通信中,扮演传输器的角色
mode:
asynchronous :异步
synchronous:同步
G421中有4个串口。在比赛中只会用到串口1PA9和PA10;
串口使用解析:
UART HAL驱动程序可按如下方式使用:
1。声明UART_HandleTypeDef句柄结构。
2.通过实现HAL_UART_MspInit()API初始化UART低级资源:
a.启用USARTx接口时钟。
b、 UART引脚配置:-启用UART GPIO的时钟。-配置USART引脚(TX作为备用功能上拉,RX作为备用功能输入)。
c、 如果需要使用中断处理(HAL_UART_Transmit_IT()和HAL_UART_Receive_IT(API)),NVIC配置:-配置USARTx中断优先级。-启用NVIC USART IRQ手柄。
d、 如果需要使用DMA进程(HAL_UART_Transmit_DMA()和HAL_UART_Receive_DMA(API),则DMA配置:-为Tx\/Rx信道声明DMA句柄结构。-启用DMAx接口时钟。-使用所需的Tx\/Rx参数配置声明的DMA句柄结构。-配置DMA Tx\/Rx信道。-将初始化的DMA句柄与UART DMA Tx\/Rx句柄关联。-配置优先级并启用DMA Tx\/Rx通道上传输完成中断的NVIC。-配置USARTx中断优先级并启用NVIC USART IRQ句柄(用于DMA非循环模式下的最后字节发送完成检测)
3。在huart Init结构中编程波特率、字长、停止位、奇偶校验、硬件流控制和模式(接收器/发射器)。
4.对于UART异步模式,通过调用HAL_UART_Init()API初始化UART寄存器。
5.对于UART半双工模式,通过调用HAL_HalfDuplex_Init()API初始化UART寄存器。
6.对于LIN模式,通过调用HAL_LIN_Init()API初始化UART寄存器。
7.对于多处理器模式,通过调用HAL_MultiProcessor_Init()API初始化UART寄存器。
此驱动程序有三种工作模式:
1、轮询模式IO口操作
·使用HAL_UART_Transmit()以阻塞模式发送大量数据
·使用HAL_UART _Receive()以阻断模式接收大量数据
2、中断模式IO口操作
使用HAL_UART_Transmit_IT()以非阻塞模式发送大量的数据、
在传输的传输端执行HAL_UART_RxCpltCall(已经被弱定义) back,用户可以通过自定义函数指针HAL_UART_txCpltCall back来添加自己的代码
在传输错误的情况下,将执行HAL_UART_ErrorCallback()函数,用户可以通过自定义功能指针HAL_UART _ErrorCallback来添加自己自己的代码
3、DMA模式IO操作
·使用HAL_USART_Transmit_DMA()以非阻塞模式(DMA)发送大量数据。在半传输的传输端执行HAL_USART-TxHalfCpltCallback,用户可以通过自定义函数指针HAL_USART.TxHalfCpltCallback添加自己的代码指针HAL_USART_TxCpltCallback·使用HAL_USART_Receive_DMA()以非阻塞模式(DMA)接收大量数据·在半传输的接收端,执行HAL_USART-RxHalfCpltCallback,用户可以通过自定义函数指针HAL_USART_RxHalfCpltCallback来添加自己的代码·在传输的接收末端,执行HAL_USART_RxCpltCallback,用户可添加自己的编码通过自定义函数指针HAL_USART_RxCpltCallback·在传输错误的情况下,执行HAL_USART.ErrorCallback()函数,用户可以通过自定义函数指示器HAL_USART_ErrorCallback添加自己的代码·使用HAL_USART-DMAPause()暂停DMA传输·使用HAL-USART_DMAResume()恢复DMA传输·
串口内部结构:
常用的PA9(TX)和PA10(RX),就比如说TX引脚,因为是串行接口他是用来发送数据,RX 是用来接收数据的,再往里看有个TX Shift Reg(发送移位寄存器),这个之所以能够按照时间的顺序发送每次吐出1或0,就靠的是移位寄存器,哪发送移位寄存器中的数是从哪里来的呢?顺着内部结构可知是从TxFIFO 中,而TxFIFO中的数是由TDR的内容来的
接收也是这样将接收到的数据放到移位寄存器等等
cubeMX配置;
基础时钟配置完成之后打开Connectivity
点击异步收发模式
默认没有配置在PA10和PA9上,自行找到该引脚修改即可
基本配置:
其余高级配置默认即可,因为需要到串口1的中断,需要使能中断,然后配置分组
GPIO设置:根据官方例程可知,模式为开漏输出。
然后配置串口时钟:
点击生成工程:
将生成的usart.c内的函数移植到自己的文件中,利用滴答定时器进行计时经行将该函数名称放到主函数的while中发送
/********************利用滴答定时器对串口进行刷新***************************/
__IO uint32_t uwTick_USRT_last;//控制串口的刷新执行速度
unsigned int j;//测试变量
char USRT_String_Dis[40];
void UART_Scanning(void)
{
if(uwTick-uwTick_USRT_last<500) return ;//减速函数每隔多少时间扫描一下
uwTick_USRT_last=uwTick;
j++;
sprintf((char *)USRT_String_Dis,"usart:i=%04d",j);
HAL_UART_Transmit(&huart1,(unsigned char *)USRT_String_Dis,strlen(USRT_String_Dis),50);//阻塞性发送数据
if(j>=10000)
{
j=0;
}
}
/******************************END************************************/
打开官方给的串口, 先进行基本配置,
串口接收:
具体怎么写先来参考官方例程
例程也是先对串口经行初始化,
紧接着又出现一个配置
HAL库的一个底层函数,这是一个已中断接收的函数,通过串口1接收一个字节放到rx(自定义变量)。该函数不是用来接收数据的他是用来打开中断,配置串口中断的
//中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
LED_Display(0xff);
HAL_Delay(300);
LED_Display(0x00);
//开启中断接收
HAL_UART_Receive_IT(&huart1,&rx_buffer,1);//这个操作必须要有
//需要注意的是在接收完成的回调函数中,为了下次能够继续接收数据,需要再次开启接受中断,重新写入HAL_UART_Receive_IT函数。
//这样就可以直接在回调函数中进行操作,而不用在中断服务函数内部操作。
}
这个操作是当接收到数据的时候开发板上的LED灯会有闪烁效果。
串口通信回显编程思路 :
1、使能RX和TX引脚GPIO时钟USART时钟
2、初始化GPIO
3、配置USART参数
以上被CubeMX 配置好了
4、配置中断控制器并使能USART
5、使能USART接受中断
6、在USART接受中断服务函数实现数据域的接受和回显
/******************************通信回显*************************************/
/*
* @brief 先将数据从接受寄存器中取出,然后再发PC端
**/
//回显接收
uint8_t Rxflag;
uint8_t ucTemp;
void User_USART_IRQHandler(void)
{
if(__HAL_UART_GET_IT(&huart1,UART_IT_RXNE)!=RESET)
{
Rxflag=1;
HAL_UART_Receive(&huart1,(uint8_t *)&ucTemp,1,100);
}
}
//回显显示
unsigned int usRxbuf[40];
uint16_t usRxcount;
void USART_Dis()
{
if(Rxflag)
{
if(usRxcount < sizeof(usRxbuf))
{
usRxbuf[usRxcount++]=ucTemp;
}
else
{
usRxcount++;
}
if(ucTemp==0x0a)//换行字符
{
/*检测到有回车就将数据发送给上机位*/
HAL_UART_Transmit(&huart1,(uint8_t *)usRxbuf,1,500);
usRxcount++;
}
Rxflag=0;
}
}
/******************************END************************************/
将USART_Dis();函数直接放在主循环的while中写即可