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

Zephyr中nRF9160(Arm)平台C代码nop汇编延迟时序不一致问题

修复nRF9160上WS2812 GPIO位bang驱动的时序不一致问题

我正在修改Zephyr的WS2812 GPIO位bang驱动,适配nRF9160(Arm Cortex-M33)开发板。目前遇到的问题是,传输开始时用汇编实现的延迟无法产生一致的脉冲时序。

脉冲时序波形图

从波形图可以看到,第一个零比特脉冲的宽度约为第二个的两倍,第七、第八个本该一致的一比特脉冲也存在时序差异。

以下是调试用的相关代码:

/*
 * Copyright (c) 2018 Intel Corporation
 * Copyright (c) 2019 Nordic Semiconductor ASA
 * Copyright (c) 2021 Seagate Technology LLC
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#define DT_DRV_COMPAT worldsemi_ws2812_gpio

#include <zephyr/drivers/led_strip.h>

#include <string.h>

#define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(ws2812_gpio);

#include <zephyr/kernel.h>
#include <soc.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/device.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/nrf_clock_control.h>
#include <zephyr/dt-bindings/led/led.h>

struct ws2812_gpio_cfg {
    struct gpio_dt_spec in_gpio;
    uint8_t num_colors;
    const uint8_t *color_mapping;
};

/*
 * T1H: 1 bit high pulse delay: 12 cycles == .75 usec
 * T0H: 0 bit high pulse delay: 4 cycles == .25 usec
 *
 * We can't use k_busy_wait() here: its argument is in microseconds,
 * and we need roughly .05 microsecond resolution.
 */
#define DELAY_T1H                                                                                  \
    "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"                                       \
    "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"                                       \
    "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"                                       \
    "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"                                       \
    "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"                                       \
    "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"                                       \
    "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"                                       \
    "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"                                       \
    "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"

#define DELAY_T0H                                                                                  \
    "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"                                       \
    "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"                                       \
    "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"

/*
 * GPIO set/clear.
 *
 * We should be able to make this portable using the results of
 * https://github.com/zephyrproject-rtos/zephyr/issues/11917.
 *
 * We already have the GPIO device stashed in ws2812_gpio_config, so
 * this driver can be used as a test case for the optimized API.
 *
 * Per Arm docs, both Rd and Rn must be r0-r7, so we use the "l"
 * constraint in the below assembly.
 */
#define SET_HIGH "str %[p], [%[s], #0]\n" /* OUTSET = BIT(LED_PIN) */
#define SET_LOW  "str %[p], [%[c], #0]\n" /* OUTCLR = BIT(LED_PIN) */

/* Send out a 1 bit's pulse */
#define ONE_BIT(set, clear, pin)                                                                   \
    __asm volatile(SET_HIGH DELAY_T1H SET_LOW DELAY_T0H ::[s] "l"(set), [c] "l"(clear),        \
               [p] "l"(pin));

/* Send out a 0 bit's pulse */
#define ZERO_BIT(set, clear, pin)                                                                  \
    __asm volatile(SET_HIGH DELAY_T0H SET_LOW DELAY_T1H ::[s] "l"(set), [c] "l"(clear),        \
               [p] "l"(pin));

/*
 * Latch current color values on strip and reset its state machines.
 */
static inline void ws2812_led_strip_reset_delay(uint16_t delay)
{
    k_usleep(delay);
}

static int send_buf(const struct device *dev, uint8_t *buf, size_t len)
{
    const struct ws2812_gpio_cfg *config = dev->config;
    volatile uint32_t *set = (uint32_t *)&NRF_P0->OUTSET;
    volatile uint32_t *clear = (uint32_t *)&NRF_P0->OUTCLR;
    const uint32_t pin = BIT(config->in_gpio.pin);
    unsigned int key;

    struct onoff_manager *mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF);
    struct onoff_client cli;
    int rc;

    sys_notify_init_spinwait(&cli.notify);
    rc = onoff_request(mgr, &cli);
    if (rc < 0) {
        return rc;
    }

    while (sys_notify_fetch_result(&cli.notify, &rc)) {
        /* pend until clock is up and running */
    }

    size_t i;
    int j = 0;
    key = irq_lock();

    while (len--) {
        /*
         * Generate signal out of the bits, MSbit first.
         *
         * Accumulator maintenance and branching mean the
         * inter-bit time will be longer than TxL, but the
         * wp.josh.com blog post says we have at least 5 usec
         * of slack time between bits before we risk the
         * signal getting latched, so this will be fine as
         * long as the compiler does something minimally
         * reasonable.
         */
        for (i = 0; i < 8; i++) {
            if (0b10000000 & (*(buf + j) << i)) {
                ONE_BIT(set, clear, pin);
            } else {
                ZERO_BIT(set, clear, pin);
            }
        }
        j++;
    }

    irq_unlock(key);

    rc = onoff_release(mgr);
    /* Returns non-negative value on success. Cap to 0 as API states. */
    rc = MIN(rc, 0);

    return rc;
}

看起来首次调用nop延迟时会额外消耗若干时钟周期,但不清楚如何消除这个导致时序异常的因素。之前没在C代码里用过汇编,不确定问题出在RTOS还是汇编器。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 02:37:01