我正在尝试修改这个bit-banging ws2812 gpio Zephy 驱动程序以在nrf9160(Arm Cortex M33)板上运行。问题是我无法在传输开始时使用汇编指令来获得一致的脉冲定时。
您可以看到第一个脉冲的宽度(时间)大约是第二个脉冲的两倍,尽管它们应该是相同的(零位)。 第 7 个和第 8 个脉冲也应该相同(一位)。
这是我一直在搞乱/调试的相关代码:
/*
* 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 还是汇编器。
在 nRF5340 上,这可以通过在事务之前将
0
写入
CACHE.ENABLE
寄存器,然后将其恢复到原始状态来实现。这看起来像以下修改:// Disable CACHE.ENABLE to ensure clean timing including for first pulse
volatile uint32_t* icachecnf = (uint32_t*)(0x50001000 + 0x500);
uint32_t old = *icachecnf;
*icachecnf = 0;
while (len--) {
// omitted for brevity...
}
*icachecnf = old;
完成此操作后,我需要通过使用示波器查看波形并将其线性调整到正确的长度来调整时序配置。对于我来说,在 128MHz 的 nRF5340 上,结果是:
CONFIG_DELAY_T0H=41
CONFIG_DELAY_T0L=48
CONFIG_DELAY_T1H=87
CONFIG_DELAY_T1L=46
您需要找到 uC 的等效寄存器并进行时序测量。简要查看 nRF9160 的产品表,看起来
NVMC.ICACHECNF
可能会针对该部件执行此操作,这意味着将
0x50001000 + 0x500
替换为 0x50039000 + 0x540
。 (我没有 nRF9160,所以无法对此进行测试,还要注意,如果您在非安全模式下运行,情况会有所不同 - 请参阅产品规格。)