rt-thread全局中断屏蔽改为设置BASEPRI屏蔽部分中断,解决内核频繁开关全局中断影响精密计时问题

2 篇文章 0 订阅
1 篇文章 0 订阅

带rtt-nano实时操作系统的小板子需要读取单总线设备,使用软件延时吧,总是由于时隙不精确,通信不稳定。按说不稳定情况也不频繁,但考虑到未来需要对上百、上千米外的单总线设备通信,开发的时候偷个懒,到应用上就是个大麻烦。再加上强迫症发作,遂决定花时间怼开这个问题。

软件延时不稳定,咱理解:通信通到一半,内核调度器掐表你时间到了,干别的去;就算摁死内核不让动,还有别的中断响应会破坏软件延时时长,干脆,给个定时器,在中断响应里收发位数据,位组成字节,字节组成字节块实现通信。

但改成硬件定时器溢出中断,又有问题。定时器的延时时间长度居然不稳定!5us的延迟,一会儿变4微秒一会儿变8微秒,偶尔还给你搞个二三十微秒,这谁受得了。

我猜,应该还是rtos内核调度器在作妖,提高定时器中断优先级,无效;掐断内核,不大可行,因为内核是由中断驱动,掐内核=掐中断,一掐中断,定时器的溢出中断也没办法响应。

去rtthread查一查,发现不少人吐槽rtt这问题,内核调度器管着中断,一进临界区第一步掐全局中断。虽然rtt自己解释进入临界区时间很短,可在精密计时面前,时间很短=一定会碰到=计时出错。得改。再往里看,有大神提出arm-cortex-M3、M4内核自带屏蔽部分中断功能,那能不能修改rtt中断管理函数,把屏蔽全局中断改成屏蔽部分中断呢?可以!rtt论坛有大神做到这个,可以抄作业了!

需要改的重点是cortex_rvds.S文件。这两个函数,rt_hw_interrupt_disable/enable,加个[WEAK]描述,方便我们在外部修改。然后在外部随便哪个源文件重写这两个函数,建议在main文件,方便好改。

重写之后的全局中断控制函数,从操作primask改成操作basepri。写入什么数值取决于读者自己设置的抢占优先级/响应优先级分组。basepri是屏蔽抢占优先级低于写入内容的中断请求。而且basepri生效的是高四位。举个例子,笔者抢占/响应优先级分组为2/2,将抢占优先级0组设为不需屏蔽。则basepri的屏蔽值就是0100,再左移4位放到高八位,得到0x40,最后送给basepri。

这样0组小于1,不能被屏蔽,1、2、3组大于等于1,被屏蔽。且basepri不会考虑响应优先级,只看抢占优先级。

除了重写函数之外,文件中还有一处需要修改。按上文修改完毕,烧入芯片后只要发生线程调度,就会进hardfault。

需要额外增加这两行汇编,大概意思应该是将basepri设为0,禁止屏蔽。这里似乎是内核的线程调度入口。这里如果没有解除屏蔽,似乎会导致rtt内核调度失效。笔者水平有限,没有能深入研究此处,希望抛砖引玉,积极评论。

修改之前,由于内核调度屏蔽全局中断,导致微秒延时不稳定。

修改之后,定时器工作、发生溢出中断、翻转IO不受内核调度影响,微秒级翻转非常稳定。

使用[weak]后缀的好处是,s文件就算更换工程,没有重写的内核中断控制函数,也不会导致系统死机。笔者这里使用keil自带的rtt-nano文件,需要对禁止编辑的cortex-rvds.S文件进行修改,方法是右击-找到文件所在路径,右击s文件-属性,关闭只读选项,增加“weak”修饰符、增加basepri赋值汇编代码,保存,恢复只读,这样cortex-rvds.S文件修改完毕。任意位置重写全局中断控制函数即可使用该功能,不重写就和原版一样。

本文基于rtt-nanoV3.1.5版本修改,欢迎讨论。

ps:最后吐槽一下rtt的内核调度,搜索出135处全局中断控制函数,也是醉了,这么频繁地切中断,那微秒级甚至纳秒级延时、io控制能精确才见鬼。

对了,再加个PS。修改过优先级、导致rtt内核切中断时无法强行关闭的中断,其响应函数中绝对禁止操作rtt的任何IPC组件。基本上一操作内核就要卡死。给个建议,可以在精密延时结束之后把中断优先级改回来,然后再加一次延时溢出,最后在那个多出来的溢出响应函数中操作IPC组件,内核就能稳定不崩溃了。

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
可以按照以下步骤实现STM32F103利用rt-thread外部中断接收nrf24l01的数据: 1. 首先,需要配置nrf24l01的SPI接口,并初始化nrf24l01的寄存器。 2. 配置外部中断,使其对应nrf24l01的IRQ引脚。在中断服务函数中,读取nrf24l01的状态寄存器,判断是否有数据接收完成。 3. 如果有数据接收完成,从nrf24l01的接收缓冲区中读取数据,并将数据传递给rt-thread的消息队列。 4. 在rt-thread的线程中,从消息队列中读取数据,并进行处理。 以下是一个简单的示例代码: ``` #include "rtthread.h" #include "drv_spi.h" #include "drv_nrf24l01.h" #define NRF24L01_IRQ_PIN GPIO_PIN_0 #define NRF24L01_IRQ_PORT GPIOA static rt_mq_t nrf24l01_mq; void nrf24l01_irq_handler(void) { if (nrf24l01_rx_data_ready()) { uint8_t data[32]; nrf24l01_read_rx_payload(data, sizeof(data)); rt_mq_send(&nrf24l01_mq, data, sizeof(data)); } } void nrf24l01_thread_entry(void* parameter) { rt_uint8_t data[32]; while (1) { rt_err_t result = rt_mq_recv(&nrf24l01_mq, data, sizeof(data), RT_WAITING_FOREVER); if (result == RT_EOK) { // 处理接收到的数据 } } } static void nrf24l01_init(void) { // 配置SPI接口 spi_init(); // 初始化nrf24l01寄存器 nrf24l01_init(); // 配置外部中断 GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = NRF24L01_IRQ_PIN; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(NRF24L01_IRQ_PORT, &GPIO_InitStruct); // 配置中断服务函数 HAL_NVIC_SetPriority(EXTI0_IRQn, 5, 0); HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 创建消息队列 rt_mq_init(&nrf24l01_mq, "nrf24l01_mq", data, sizeof(data), 32, RT_IPC_FLAG_FIFO); } int rt_application_init(void) { // 创建nrf24l01线程 rt_thread_t nrf24l01_thread = rt_thread_create("nrf24l01", nrf24l01_thread_entry, RT_NULL, 1024, 25, 10); if (nrf24l01_thread != RT_NULL) { rt_thread_startup(nrf24l01_thread); } // 初始化nrf24l01 nrf24l01_init(); return 0; } ``` 在上面的代码中,nrf24l01_irq_handler()函数是中断服务函数,会在nrf24l01的IRQ引脚触发外部中断时被调用。在该函数中,读取nrf24l01的状态寄存器,判断是否有数据接收完成,如果有,则从nrf24l01的接收缓冲区中读取数据,并将数据传递给rt-thread的消息队列。 nrf24l01_thread_entry()函数是rt-thread的线程函数,会从消息队列中读取数据,并进行处理。在该函数中,调用rt_mq_recv()函数从消息队列中读取数据。如果读取成功,则可以对接收到的数据进行处理。 在rt_application_init()函数中,创建nrf24l01线程,并初始化nrf24l01。在初始化nrf24l01时,会配置nrf24l01的SPI接口,并初始化nrf24l01的寄存器。同时,会配置外部中断,使其对应nrf24l01的IRQ引脚。创建消息队列时,需要指定消息队列的名称、消息缓冲区、消息长度和消息数量等参数。 如果需要发送数据,可以调用nrf24l01_write_tx_payload()函数将数据写入nrf24l01的发送缓冲区,然后调用nrf24l01_transmit()函数启动发送过程。发送完成后,会触发中断,可以在中断服务函数中处理发送完成的事件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值