1

我正在尝试使用 PyBoard v1.1使用 Micropython 的内联汇编器来驱动 Adafruit 的 NeoPixel LED SK6812RGBW的协议。

协议

从链接的数据表中可以看出,单个 LED 是通过组合 4 个 8 位 rgbw 值来驱动的。每个高位由 0.6us 的模拟高,然后是 0.6us 的数字低组成,低位的比例为 0.3us 高和 0.9us 低。这使得在 4 字节 LED 颜色值中使用的每个数据位都是 4 个模拟位,每个 0.3 us,或者总共 128 个位超过 38.4 us。发送到第一个 LED 的字节流也包含所有后续 LED 的值,它会将除自己之外的所有 LED 传递到下一个 LED,依此类推。

执行

使用 pyboard 的 SPI 接口可以很容易地实现该协议。一旦生成了数据流(每个 LED 有效 16 个字节)并计算了波特率(1s / 0.3 us = ca 3333333),只需创建一个 pyb.SPI 实例并使用字节作为参数调用其发送方法.

任务

现在到手头的任务:我想用一个 PyBoard 驱动三个不同的 LED 灯条。但是只有 2 条 SPI 总线可用。因此,在尝试使用 pyb.Pin 和循环对协议进行 bitbang 后,我很快意识到这不起作用,最小切换速度为 54 us,这比我需要的 0.3 us 略低...

实施 V2

在尝试了一些优化步骤后,我转向了 Micropython 的内联汇编器。几个小时后,我设法在用示波器测量的 23 ns 轻松切换给定引脚。这很好,但我不需要盲目地切换引脚,我需要根据比特流遵循精确的协议来切换引脚。又过了几个小时,我完成了以下实现:

@micropython.asm_thumb
def send_bits_on_x9(r0):
    # r0 0th word contains the data array address
    # r0 1st word contains length of data array

    # Store the GPIOB address in r3
    movwt(r3, stm.GPIOB)
    # Store the bit mask for PB6 (the pin X9)
    movw(r4, 1 << 6)

    # Load address into r5
    ldr(r5, [r0, 0])
    # Load array length into r6
    ldr(r6, [r0, 4])

    # Jump to condition evaluation
    b(loop_entry)

    # Main loop
    label(loop)

    # Get current "bit" word
    ldr(r0, [r5, 0])
    # Shift address to next "bit" for next time
    add(r5, r5, 4)

    # Evaluating the bit and toggling accordingly
    cmp(r0, 1)
    ite(eq)
    strh(r4, [r3, stm.GPIO_BSRRL])  # Turn LED on
    strh(r4, [r3, stm.GPIO_BSRRH])  # Turn LED off

    # Delay for a bit
    movwt(r7, 8)  # 20948000 cycles is about 1s
    label(delay)
    sub(r7, r7, 1)
    cmp(r7, 0)
    bgt(delay)

    # Eval loop; using data array length as initial counter value
    sub(r6, r6, 1)
    label(loop_entry)
    cmp(r6, 0)
    bgt(loop)

B6 是引脚 X9 的 CPU 名称,我将其用作与 LED 的数据连接。

为了运行,我将它嵌入到一个演示 python 脚本中:

import array
import uctypes
import micropython
import stm

@micropython.asm_thumb
def send_bits_on_x9(r0):
    ...

send_buffer = array.array("i", [1, 0, 1, 1, 0, 0, 1, 0])
send_bits_on_x9(array.array("i", [uctypes.addressof(send_buffer), len(send_buffer)]))

问题

这工作得很好,但是当使用它代替 SPI 流媒体时,每隔几次执行就可以看到 LED 偶尔出现的伪影。以下是我用示波器查看时的图像: 带有工件的示波器日志 可以看出,有一个地方由于某种原因它似乎停止切换恰好 2 个值位:缺少铅笔的侧面 这似乎发生随机地,在比特流的任何部分,有时从上升的边沿开始,有时从下降的边沿开始。

问题

现在显然我的问题是为什么会发生这种情况。SPI 不会发生这种情况,尽管我假设 C 实现注意不要让任何东西中断流。我尝试在调用 send_bits_on_x9 之前禁用垃圾收集器并在之后重新启用,但这没有帮助。我还改变了延迟周期的数量,也没有改变任何东西。

我注意到的第二件事是,当有多个尾随零字节时(根据协议定义的 80 us 重置周期),该周期似乎将在大约四分之一的时间内执行。将尾随字节更改为 0xff 时,它们保留了预期的持续时间,LED 似乎并不介意。

现在,如果有人可以向我指出官方内联汇编程序文档以外的资源,甚至提供一些见解,我将不胜感激。干杯!

4

1 回答 1

0

在 STM32 平台上编写时间敏感代码时,绝对需要阅读该特定 MCU 的用户参考手册。(如果我没记错的话,Pyboard 有一个 STM32F4 处理器)。

您的问题不是 python 本身,因为您正确禁用了垃圾收集。

然而,当代码运行很长时间时,中断最终会出现,中断 main() 代码,跳转到 ISR 函数,执行需要做的事情,然后返回到 main() 代码。

因此,您的解决方案对于 micropython 中的内联汇编器来说是基本的:

@micropython.asm_thumb
def f():
  # start
  cpsid('i') # set the Priority Mask Register - disable interrupts.
  ...
  ...
  cpsie('i') #clear the Priority Mask Register - enable interrupts.

警告!不要指望这会在你的函数中间有 1 秒的延迟时间,......在某些情况下,结果可能无法保证。

更好的解决方案:

  1. 在 @micropython.asm_thumb 中定义时间关键代码并关闭中断
  2. 在 python 或单独的汇编器部分中定义延迟和非关键代码
  3. 根据需要混合搭配
于 2021-09-02T19:11:29.687 回答