nRF52832+FreeRTOS发现一处罕见的死机(HardFalut)问题

前言

开发nRF52832程序,由于之前的代码是基于裸机程序开发的,使用app_timer和app_sched来调度任务。

但由于项目中有一个功能对任务实时性有需求,所以移植了freeRTOS,用一个单独的高优先级线程处理这个高实时性要求的任务。

为了提高代码复用性,尽量不改动现有的基于app_timer机制的代码。

所以在freeRTOS环境下,要实现app_timer和app_sched的功能,nodic的SDK中已经有了app_timer_freertos.c。

但app_sched模块还没有,所以需要自行实现这个模块,可以创建一个低优先级线程和一个队列来实现此功能。

官方提供的app_time_freertos并没有使用app_timer_appsh,这样一来app_timer和app_sched处于不同的工作线程,

app_timer的回调函数和app_sched的回调函数存在竞争风险,因此需要修改app_time_freertos,使其调用app_timer_appsh模块。

由app_timer_appsh模块把定时器事件放入app_sched来调用。

 

 

正文

好了,大功告成,原先的代码基本不用修改,就能在freeRTOS下面运行了。

然而,程序刚运行就出现了HardFault,只好打开DEBUG寻找原因。

发现代码最后死在了app_timer_appsh.c里面,这里面代码很少,先帖上来。

#include "app_timer_appsh.h"
#include "app_scheduler.h"

static void app_timer_evt_get(void * p_event_data, uint16_t event_size)
{
    app_timer_event_t * p_timer_event = (app_timer_event_t *)p_event_data;

    APP_ERROR_CHECK_BOOL(event_size == sizeof(app_timer_event_t));
    p_timer_event->timeout_handler(p_timer_event->p_context);
}

uint32_t app_timer_evt_schedule(app_timer_timeout_handler_t timeout_handler,
                                void *                      p_context)
{
    app_timer_event_t timer_event;

    timer_event.timeout_handler = timeout_handler;
    timer_event.p_context       = p_context;

    return app_sched_event_put(&timer_event, sizeof(timer_event), app_timer_evt_get);
}

问题就出在app_timer_evt_get这个函数,以为是指针错误?指针越界?长度错误?指针指向的内存数据不正确?

NO! NO!NO!  都不是!!!

这里的参数p_event_data和下面app_timer_evt_schedule保存进去的内容是完全一致的,没有任何问题。

通过查看反汇编代码,单步跟踪运行,发现最后程序在执行LDRD指令的时候,直接就挂了。如下图:

反复测试,都是死在LDRD这条指令上面。

而LDRD指令是干什么的呢?

它是一条加载64位数据的指令。

如图中所示:LDRD r1,r0,[r6,#0]。

表示从R6+0这个地址加载一个64位的整数,并把低32位放入R1,高32位放入R0。

R6是p_timer_event,是个结构体指针,这个结构体刚好是8字节(64位),

低32位就是函数指针p_timer_event->timeout_handler。

高32位就是p_timer_event->p_context,这样一条指令就完成了对这个结构体的读取。

后面的BLX r1,就是调用timeout_handler了,参数就是r0。

这样一分析,好像并没有什么问题啊,编译器非常巧妙并“正确”地利用了这个指令。

R6指向的地址也是正确的,里面的内容都是可以读取的,那为什么还会报错??????????

只好寻找ARM汇编指令集文档,深入研究一下了。

唉?在这里找到了答案:http://scc.qibebt.cas.cn/docs/optimization/VTune%28TM%29%20User%27s%20Guide/mergedProjects/analyzer_ec/mergedProjects/reference_olh/xscinstruct_hh/INST_LDRD.htm

这个文档里有一句话:加载地址必须双字对齐

果然,我们的R6的值是0x20005836,很明显不满足字节对齐要求,所以导致指令执行异常。

知道原因就好解决了,要么让R6的址地8字节对齐,要么不要使用这条指令。

因为R6是来自函数参数,这个函数其实是从freeRTOS的队列取出来的,简单看了一下源码,没有简单的方法让它8字节对齐。

所以采取方案二,不让编译器使用这条指令,只要改一下C代码,把这个函数调用拆分为先读取再调用的方式就可以了。

如下图所示,添加一个局部变量来保存函数指针,然后我们再看反汇编代码,就变成了两条LDR指令了,问题至此解决。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值