You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Zephyr创建线程触发MPU故障(数据访问违例)问题排查

Zephyr+nRF52840 DK BLE Central RSSI记录线程触发MPU故障排查

问题背景

基于Zephyr和nRF52840 DK开发BLE Central设备,需求为每秒记录当前连接的RSSI值。最初在connected回调末尾直接调用RSSI获取函数,虽能正常运行,但30秒后会被外设断开连接,推测是该函数阻塞主线程导致配对及服务发现受阻。

计划在connected回调末尾创建独立线程处理RSSI记录逻辑,并在disconnected回调中终止线程,但创建线程时触发MPU故障(数据访问违例),报错信息如下:

[00:00:04.303,436] <err> os: ***** MPU FAULT *****
[00:00:04.303,466] <err> os:   Data Access Violation
[00:00:04.303,466] <err> os:   MMFAR Address: 0x58
[00:00:04.303,466] <err> os: r0/a1:  0x00000000  r1/a2:  0x20005000  r2/a3:  0x00000800
[00:00:04.303,497] <err> os: r3/a4:  0x00000058 r12/ip:  0x00000000 r14/lr:  0x000260f3
[00:00:04.303,497] <err> os:  xpsr:  0x410d0000
[00:00:04.303,497] <err> os: Faulting instruction address (r15/pc): 0x00026074
[00:00:04.303,527] <err> os: >>> ZEPHYR FATAL ERROR 19: Unknown error on CPU 0
[00:00:04.303,588] <err> os: Current thread: 0x20001088 (unknown)
[00:00:04.378,540] <err> os: Halting system

通过addr2line定位到故障指令位于Zephyr内核的dlist.h文件第204行,而非业务代码。以下是线程创建及相关函数实现(已简化):

#define THREAD_STACK_SIZE_RSSI 2048
#define THREAD_PRIORITY_RSSI 1

static void thread_entrypoint_rssi(void *p1, void *p2, void *p3);

uint16_t conn_handle;
struct k_thread *thread_data_rssi;
k_tid_t thread_id_rssi;
K_THREAD_STACK_DEFINE(thread_stack_rssi, THREAD_STACK_SIZE_RSSI);

static void connected_cb(struct bt_conn *conn, uint8_t conn_err)
{
  if (conn_err) { /* Some error handling */ }

  if (conn == default_conn)
  {
    int err = bt_conn_set_security(conn, BT_SECURITY_L3);
    if (err) { /* Some error handling */ }

    // Setting the discovering parameters and start to discover
    ...
    err = bt_gatt_discover(conn, &discover_params);
    if (err) { /* Some error handling */ }

    // HERE IS MY PROBLEM
    thread_id_rssi = k_thread_create(thread_data_rssi,
        thread_stack_rssi,
        THREAD_STACK_SIZE_RSSI,
        thread_entrypoint_rssi,
        conn,
        NULL,
        NULL,
        K_PRIO_PREEMPT(1),
        0,
        K_NO_WAIT);

    // This works for 30 seconds (after what the peripheral close the connection)
    // thread_entrypoint_rssi(conn);
  }
}

static bool read_conn_rssi(int8_t *rssi)
{
  struct net_buf *buf, *rsp = NULL;
  struct bt_hci_cp_read_rssi *cp;
  struct bt_hci_rp_read_rssi *rp;

  int err;

  buf = bt_hci_cmd_create(BT_HCI_OP_READ_RSSI, sizeof(*cp));
  if (!buf) { /* Some error handling returning false */ }

  cp = net_buf_add(buf, sizeof(*cp));
  cp->handle = sys_cpu_to_le16(conn_handle);

  err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_RSSI, buf, &rsp);
  if (err) { /* Some error handling returning false */ }

  rp = (void *)rsp->data;
  *rssi = rp->rssi;

  net_buf_unref(rsp);
  return true;
}

static bool set_conn_handle(struct bt_conn *conn)
{
  int ret = bt_hci_get_conn_handle(conn, &conn_handle);

  if (ret)
    return false;

  return true;
}

static void thread_entrypoint_rssi(void *p1, void *p2, void *p3)
{
  struct bt_conn *conn = (struct bt_conn *)p1;
  int8_t rssi = 0xFF;

  if (!set_conn_handle(conn)) { /* Some error handling */ }

  while(1)
  {
    if (!read_conn_rssi(&rssi))
      break;
    LOG_WRN("RSSI = %d", rssi);
    k_sleep(K_SECONDS(1));
  }
}

static void disconnected_cb(strcut bt_conn *conn, uint8_t reason)
{
  k_thread_abort(thread_id_rssi);
  ...
}

已尝试的排查手段:

  • 将线程优先级改为负值,无效果
  • 增大栈空间(包括prj.conf中的配置及线程栈定义),未解决问题
  • 使用K_THREAD_DEFINE宏定义线程编译失败,提示"initializer element is not constant"

相关prj.conf配置如下:

CONFIG_BT=y
CONFIG_BT_DEVICE_NAME="BLE CENTRAL TEST"
CONFIG_BT_CENTRAL=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_CONSOLE=y
CONFIG_GPIO=y
CONFIG_SERIAL=y
CONFIG_EVENTS=y

CONFIG_HW_STACK_PROTECTION=y
# Need to fine tune these values, for now they are random
CONFIG_MAIN_STACK_SIZE=100500
CONFIG_HEAP_MEM_POOL_SIZE=10000

CONFIG_BT_ATT_TX_COUNT=255
CONFIG_BT_BUF_ACL_TX_COUNT=255

CONFIG_BT_CTLR=y
CONFIG_BT_CTLR_CONN_RSSI=y

CONFIG_BT_SMP=y
CONFIG_BT_BONDABLE=y
...

故障原因及解决方法

核心故障原因

代码中struct k_thread *thread_data_rssi;仅声明了指针但未分配内存,k_thread_create需要传入一个已初始化的struct k_thread对象来存储线程的元数据(如链表节点、状态等),传入空指针会导致内核访问非法地址,触发MPU数据访问违例。

解决方法

方法一:静态分配线程控制块(推荐,嵌入式场景更可靠)

将线程控制块从指针改为静态分配的结构体,确保内存合法:

// 替换原来的 struct k_thread *thread_data_rssi;
static struct k_thread thread_data_rssi;

修改后k_thread_create有合法的内存存储线程数据,不会触发MPU故障。

方法二:动态分配线程控制块(不推荐)

若必须动态分配,使用Zephyr提供的内存分配函数:

thread_data_rssi = k_malloc(sizeof(struct k_thread));
if (!thread_data_rssi) {
    // 内存分配失败的错误处理逻辑
}

线程终止后需调用k_free(thread_data_rssi)释放内存,但嵌入式系统中静态分配更稳定,避免内存碎片问题。

额外优化建议

  • 使用官方API简化RSSI获取:替代手动构造HCI命令,直接使用Zephyr提供的bt_conn_get_rssiAPI,减少代码复杂度并避免全局变量conn_handle的风险:
    static void thread_entrypoint_rssi(void *p1, void *p2, void *p3)
    {
        struct bt_conn *conn = (struct bt_conn *)p1;
        int8_t rssi;
        int err;
    
        while (1) {
            err = bt_conn_get_rssi(conn, &rssi);
            if (!err) {
                LOG_WRN("RSSI = %d", rssi);
            }
            k_sleep(K_SECONDS(1));
        }
    }
    
  • 安全终止线程:在disconnected_cb中先检查线程ID是否有效,避免空指针操作:
    static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
    {
        if (thread_id_rssi) {
            k_thread_abort(thread_id_rssi);
            thread_id_rssi = NULL; // 重置标记,防止重复操作
        }
        // 其他断开连接的处理逻辑
    }
    
  • 修复拼写错误:disconnected_cb的参数声明中strcut应为struct,避免编译警告。
  • 栈空间优化:启用CONFIG_THREAD_STACK_INFO配置,统计线程实际栈使用情况,按需调整栈大小,避免内存浪费。

内容的提问来源于stack exchange,提问作者Groux

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 18:57:03