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
相关产品推荐
相关产品推荐

