完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
`上一章节讲了中断的原理和控制流程,今天我们进入基于MM32 MCU的实战演练环节。 >>>查看上一章 四. RT-Thread中断管理接口为了把操作系统和系统底层的异常、中断硬件隔离开来,RT-Thread 把中断和异常封装为一组抽象接口,如下图1所示: 系统把用户的中断服务程序 (handler) 和指定的中断号关联起来,可调用如下的接口挂载一个新的中断服务程序: rt_isr_handler_t rt_hw_interrupt_install(int vector, rt_isr_handler_t handler, void *param, char *name); 调用rt_hw_interrupt_install()后,当这个中断源产生中断时,系统将自动调用装载的中断服务程序。下表描述了此函数的输入参数和返回值: rt_hw_interrupt_install() 的输入参数和返回值 注意:这个 API 并不会出现在每一个移植分支中,例如通常 Cortex-M0/M3/M4 的移植分支中就没有这个 API。 中断服务程序是一种需要特别注意的运行环境,它运行在非线程的执行环境下(一般为芯片的一种特殊运行模式(特权模式)),在这个运行环境中不能使用挂起当前线程的操作,因为当前线程并不存在,执行相关的操作会有类似打印提示信息,“Function [abc_func] shall not used in ISR”,含义是不应该在中断服务程序中调用的函数)。 02中断源管理 通常在 ISR 准备处理某个中断信号之前,我们需要先屏蔽该中断源,在 ISR 处理完状态或数据以后,及时的打开之前被屏蔽的中断源。屏蔽中断源可以保证在接下来的处理过程中硬件状态或者数据不会受到干扰,可调用下面这个函数接口: void rt_hw_interrupt_mask(int vector); 调用 rt_hw_interrupt_mask 函数接口后,相应的中断将会被屏蔽(通常当这个中断触发时,中断状态寄存器会有相应的变化,但并不送达到处理器进行处理)。下表描述了此函数的输入参数: rt_hw_interrupt_mask() 的输入参数
注意:这个 API 并不会出现在每一个移植分支中,例如通常 Cortex-M0/M3/M4 的移植分支中就没有这个 API。 为了尽可能的不丢失硬件中断信号,可调用下面的函数接口打开被屏蔽的中断源: void rt_hw_interrupt_umask(int vector); 调用rt_hw_interrupt_umask函数接口后,如果中断(及对应外设)被配置正确时,中断触发后,将送到到处理器进行处理。rt_hw_interrupt_umask() 的输入参数是要打开屏蔽的中断号。 注意:这个 API 并不会出现在每一个移植分支中,例如通常 Cortex-M0/M3/M4 的移植分支中就没有这个 API。 03全局中断开关 全局中断开关也称为中断锁,是禁止多线程访问临界区最简单的一种方式,即通过关闭中断的方式,来保证当前线程不会被其他事件打断(因为整个系统已经不再响应那些可以触发线程重新调度的外部事件),也就是当前线程不会被抢占,除非这个线程主动放弃了处理器控制权。当需要关闭整个系统的中断时,可调用下面的函数接口: rt_base_t rt_hw_interrupt_disable(void);下表描述了此函数的返回值:rt_hw_interrupt_disable() 的返回值 恢复中断也称开中断。rt_hw_interrupt_enable() 这个函数用于 “使能” 中断,它恢复了调用rt_hw_interrupt_disable() 函数前的中断状态。如果调用 rt_hw_interrupt_disable() 函数前是关中断状态,那么调用此函数后依然是关中断状态。恢复中断往往是和关闭中断成对使用的,调用的函数接口如下: void rt_hw_interrupt_enable(rt_base_t level); 下表描述了此函数的输入参数:rt_hw_interrupt_enable() 的输入参数
使用中断锁来操作临界区的方法可以应用于任何场合,且其他几类同步方式都是依赖于中断锁而实现的,可以说中断锁是最强大的和最高效的同步方法。只是使用中断锁最主要的问题在于,在中断关闭期间系统将不再响应任何中断,也就不能响应外部的事件。所以中断锁对系统的实时性影响非常巨大,当使用不当的时候会导致系统完全无实时性可言(可能导致系统完全偏离要求的时间需求);而使用得当,则会变成一种快速、高效的同步方式。 例如,为了保证一行代码(例如赋值)的互斥运行,最快速的方法是使用中断锁而不是信号量或互斥量:
在使用中断锁时,需要确保关闭中断的时间非常短,例如上面代码中的 a = a + value; 也可换成另外一种方式,例如使用信号量:
段代码在rt_sem_take、rt_sem_release 的实现中,已经存在使用中断锁保护信号量内部变量的行为,所以对于简单如 a = a + value; 的操作,使用中断锁将更为简洁快速。 rt_base_t rt_hw_interrupt_disable(void) 和void rt_hw_interrupt_enable(rt_base_tlevel) 函数一般需要配对使用,从而保证正确的中断状态。在 RT-Thread 中,开关全局中断的 API 支持多级嵌套使用,这个特性可以给代码的开发带来很大的便利。例如在某个函数里关闭了中断,然后调用某些子函数,再打开中断。这些子函数里面也可能存在开关中断的代码。由于全局中断的 API 支持嵌套使用,用户无需为这些代码做特殊处理。 04中断通知 当整个系统被中断打断,进入中断处理函数时,需要通知内核当前已经进入到中断状态。针对这种情况,可通过以下接口: void rt_interrupt_enter(void); void rt_interrupt_leave(void); 这两个接口分别用在中断前导程序和中断后续程序中,均会对 rt_interrupt_nest(中断嵌套深度)的值进行修改: 每当进入中断时,可以调用rt_interrupt_enter()函数,用于通知内核,当前已经进入了中断状态,并减少中断嵌套深度(执行 rt_interrupt_nest –)。注意不要在应用程序中调用这两个接口函数。 使用rt_interrupt_enter/leave()的作用是,在中断服务程序中,如果调用了内核相关的函数(如释放信号量等操作),则可以通过判断当前中断状态,让内核及时调整相应的行为。例如:在中断中释放了一个信号量,唤醒了某线程,但通过判断发现当前系统处于中断上下文环境中,那么在进行线程切换时应该采取中断中线程切换的策略,而不是立即进行切换。 但如果中断服务程序不会调用内核相关的函数(释放信号量等操作),这个时候,也可以不调用rt_interrupt_enter/leave() 函数。 在上层应用中,在内核需要知道当前已经进入到中断状态或当前嵌套的中断深度时,可调用rt_interrupt_get_nest() 接口,它会返回 rt_interrupt_nest。如下: rt_uint8_t rt_interrupt_get_nest(void); 下表描述了 rt_interrupt_get_nest() 的返回值 五.RT-Thread中断管理Demo演示我们在前几节课堂中已经学习过RT-Thread OS Nano的移植以及UART1串口控制台的打印功能,这里我们直接进入RT-Thread的中断管理Demo实验,具体的实验步骤如下: 1)这里以移植好的RT_Thread_Nano_MM32L073PF核心板为例。 在其工程目录HARDWARE文件夹下新建一个名为LED的文件夹,然后在该工程Keil MDK集成开发环境下我们新建一个led.c和led.h文件保存在HARDWARE里面的LED文件夹下,在MDK开发环境中的HARDWARE分组下添加led.c文件并包含led.h文件,在led.h文件中用宏定义的方法定义LED1的端口和管脚,定义LED1 ON、LED1 OFF、LED1 TOGGLE功能,编写LED初始化函数声明:void LED_Init(void)。在LED.c文件中编写LED初始化函数:void LED_Init(void)如下所示: #ifndef __LED_H__ #define __LED_H__ #include "HAL_conf.h" //LED1 GPIOB #define LED1_PORT GPIOB //LED1 GPIO_Pin5 #define LED1_PIN GPIO_Pin_5 //PB5 LED1 ON #define LED1_ON() GPIO_ResetBits(LED1_PORT,LED1_PIN) //PB5 LED1 OFF #define LED1_OFF() GPIO_SetBits(LED1_PORT,LED1_PIN) //PB5 LED1 TOGGLE #defineLED1_TOGGLE() (GPIO_ReadOutputDataBit(LED1_PORT,LED1_PIN))?(GPIO_ResetBits(LED1_PORT,LED1_PIN)):(GPIO_SetBits(LED1_PORT,LED1_PIN)) //初始化LED函数声明 void LED_Init(void); #endif #include "led.h" /** ********************************************************************************** * @name : LED_Init * @Brief : LED Init GPIOB.5 * @param : None * @retval : None ********************************************************************************** */ void LED_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; //Enable GPIOB Clock RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOB, &GPIO_InitStructure); //LED1 ON LED1_ON(); } 2)在main.c文件中包含led.h头文件,声明创建初始化并启动中断管理线程函数,宏定义线程的优先级、线程的时间片大小、4字节栈空间对齐、线程的栈空间大小1024字节、线程控制块、编写线程入口函数,创建一个静态线程,定义一个动态信号量dynamic_sem用于线程中创建信号量,然后在线程中永久等待获取Timer14每2s释放的信号量,当获取到Timer14释放的信号量时LED1灯状态翻转,演示RT-Thread中断管理控制LED1灯状态翻转功能,最后UART1打印输出提示语。代码详情如下所示: #include "delay.h" #include "sys.h" #include "led.h" #include //声明创建初始化并启动中断管理线程函数 int Inter_Management_sample(void); //线程的时间片大小5个操作系统时钟节拍 #define THREAD_TIME_SLICE 5 //线程优先级 #define THREAD_PRIORITY 25 //4字节栈空间对齐 ALIGN(RT_ALIGN_SIZE) //线程的栈空间大小1024字节 static char thread_stack[1024]; //线程控制块 static struct rt_thread thread; //创建1个动态信号量dynamic_sem static rt_sem_t dynamic_sem = RT_NULL; /** ******************************************************************************* * @name : static void thread2_entry(void *param) * @brief : 线程的入口函数 * @param : *parame:线程入口函数参数 * @retval : None ******************************************************************************* */ static void thread_entry(void *param) { static rt_err_t result; while(1) { //永久方式等待信号量,获取到TIM14每2秒释放的信号量LED1状态翻转 result = rt_sem_take(dynamic_sem,RT_WAITING_FOREVER); if(result != RT_EOK) { //打印提示获取信号量失败 rt_kprintf("thread2 take a dynamic semaphore,failed. "); //获取信号量失败删除信号量 rt_sem_delete(dynamic_sem); return; } else { //获取到信号量LED1状态翻转 LED1_TOGGLE(); //UART1打印提示LED1状态翻转 rt_kprintf("Get the semaphore released by Timer14:LED1 TOGGLE. "); } } } /** ********************************************************************************** * @name : Inter_Management_sample * @brief : 中断管理样例,创建信号量,动态初始化线程并启动线程 * 线程等待获取到TIM14每2s释放的信号量后LED1状态翻转。 * @param : None * @retval : return 0 ********************************************************************************** */ int Inter_Management_sample(void) { //创建一个动态信号量,初始值是'0' dynamic_sem = rt_sem_create("dsem",0,RT_IPC_FLAG_FIFO); /* 静态创建线程名称是thread,入口是thread_entry并初始化线程 */ rt_thread_init( &thread, //线程句柄 "thread", //线程名称 thread_entry, //线程入口函数 RT_NULL, //线程入口函数参数 &thread_stack[0], //线程栈起始地址 sizeof(thread_stack), //线程栈大小单位是字节 THREAD_PRIORITY, //线程的优先级 THREAD_TIME_SLICE ); //线程时间片大小 //启动线程 rt_thread_startup(&thread); return 0; } 3)继续在main.c文件中初始化Timer14定时中断5ms,编写定时中断函数,然后定义一个计时变量ucTIM14_2s_Cnt,每计时到2s释放之前定义的信号量。代码如下所示: //TIM14 2s计时变量 u16 ucTIM14_2s_Cnt = 0; /** ******************************************************************************* * @name : void bsp_TIM14_Init(u16 Prescaler,u16 Period) * @brief : Timer14 Init * @param : None * @retval : None ******************************************************************************* */ void bsp_TIM14_Init(u16 Prescaler,u16 Period) { TIM_TimeBaseInitTypeDef TIM_StructInit; NVIC_InitTypeDef NVIC_StructInit; /*使能TIM14时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM14, ENABLE); //ARR寄存器值(重装值) TIM_StructInit.TIM_Period = Period; //TIM14预分频值 TIM_StructInit.TIM_Prescaler = Prescaler; //数字滤波器采样频率,不影响定时器时钟 TIM_StructInit.TIM_ClockDivision = TIM_CKD_DIV1; //向上计数模式 TIM_StructInit.TIM_CounterMode = TIM_CounterMode_Up; //TIM14定时器向上计数更新时间事件中断发生后从0开始计数 TIM_StructInit.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM14, &TIM_StructInit); /* 配置定时器TIM14中断通道及优先级并使能中断优先级 */ NVIC_StructInit.NVIC_IRQChannel = TIM14_IRQn; NVIC_StructInit.NVIC_IRQChannelPriority = 2; NVIC_StructInit.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_StructInit); //更新定时器时会产生更新时间,初始化时清除标志位 TIM_ClearFlag(TIM14, TIM_FLAG_Update); //允许定时器TIM14更新中断 TIM_ITConfig(TIM14, TIM_IT_Update, ENABLE); //使能TIM14定时器 TIM_Cmd(TIM14, ENABLE); } /** ******************************************************************************* * @name : TIM14_IRQHandler * @brief : TIM14 Interrupt service function * @param : None * @retval : None ******************************************************************************* */ void TIM14_IRQHandler(void) { //TIM14 向上计数更新中断 if(TIM_GetITStatus(TIM14, TIM_IT_Update) != RESET) { //清定时器Timer14中断向上计时标志位 TIM_ClearITPendingBit(TIM14, TIM_IT_Update); //TIM14每5ms定时中断一次,ucTIM14_2s_Cnt每5ms自加1 ucTIM14_2s_Cnt++; //TIM14 3s计时变量计数600次即3秒 if(ucTIM14_2s_Cnt >= 400) { //3s时间到清TIM14 2s计时变量 ucTIM14_2s_Cnt = 0; //TIM14每2s定时中断1次并释放信号量 rt_sem_release(dynamic_sem); } } } 4)在board.c文件中引用extern void bsp_TIM14_Init(u16 Prescaler,u16 Period);函数然后包含#include "led.h"头文件。如下图2所示:
图2|函数调用5)在board.c文件中的void rt_hw_board_init()函数里面初始化调用初始化LED1函数LED_Init();和初始化定时器中断Timer14函数bsp_TIM14_Init(480-1,499); 如下图3所示:
图3|外设初始化配置6)在main.c文件中调用创建,初始化并启动中断管理线程Inter_Management_sample(); 如下图4所示: 图4|初始化并启动中断管理线程7)中断管理Demo演示。 通过USB线把MM32L073PF核心板上的UART1的USB口连接到计算机USB口上,然后用一根杜邦线把PB5端口连接到LED1端口,连接Jlink编译代码,烧录代码到MM32L073PF核心板上。 打开串口调试助手波特率设置为115200,8位数据位1位停止位,无奇偶校验位,无流控。可以直观的看到串口调试助手每2s打印一次提示语:Get the semaphore released by Timer14:LED1 TOGGLE,同时核心板上的绿色LED1灯的状态也是每2s翻转一次,即Timer14定时中断每2s释放一次信号量,主线程调用子线程永久等待获取Timer14每2s释放的信号量。 实验Demo现象如下图5和图6所示: |
|
相关推荐
|
|
只有小组成员才能发言,加入小组>>
2249个成员聚集在这个小组
加入小组灵动微电子MM32全系列MCU产品应用手册,库函数和例程和选型表
11703 浏览 3 评论
【MM32 eMiniBoard试用连载】+基于OLED12864的GUI---U8G2
5930 浏览 1 评论
【MM32 eMiniBoard试用连载】移植RT-Thread至MM32L373PS
10965 浏览 0 评论
【MM32 eMiniBoard测评报告】+ 开箱 + 初探
4577 浏览 1 评论
灵动微课堂(第106讲) | MM32 USB功能学习笔记 —— WinUSB设备
4302 浏览 1 评论
[MM32软件] MM32F002使用内部flash存储数据怎么操作?
981浏览 1评论
806浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-27 22:04 , Processed in 0.405501 second(s), Total 37, Slave 29 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号