【发布时间】:2017-12-10 23:14:07
【问题描述】:
我正在尝试使用 Arduino Nano 并行更新四个 RGBW LED 灯条。 这些条连接到数字引脚 0-3,它等于 I/O 寄存器 PORTD 的位 0-3。 (Image: LEDs wired to Arduino)
条带类型是SK6812 RGBW,但我不认为这是一个非常重要的信息。 (Datasheet)
重要的是,为了更新一个 LED,您需要按照数据表中的说明快速连续地为其提供 32 位数据。 我已经设法通过准备一个 32 位数组(名为 LED[32] )来做到这一点,该数组保存每个条带的一个 LED 的信息。然后将这些值加载到 I/O 寄存器 PORTD 以驱动引脚为高电平和低电平。 LED[32] 阵列如下所示:
(从 LSB 到 MSB 的顺序: w(白色) b(蓝色) r(红色) g(绿色)
引脚 4-7 在开始时被保存,并将在每一帧 (X) 中加载以保持它们原样)
<table border="1" <tr>
<td>bit7 </td>
<td>bit6 </td>
<td>bit5 </td>
<td>bit4 </td>
<td>bit3 </td>
<td>bit2 </td>
<td>bit1 </td>
<td>bit0 </td>
</tr>
<tr>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>W3_0</td>
<td>W2_0</td>
<td>W1_0</td>
<td>W0_0</td>
</tr>
<tr>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>W3_1</td>
<td>W2_1</td>
<td>W1_1</td>
<td>W0_1</td>
</tr>
<tr>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>W3_2</td>
<td>W2_2</td>
<td>W1_2</td>
<td>W0_2</td>
</tr>
<tr>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>W3_3</td>
<td>W2_3</td>
<td>W1_3</td>
<td>W0_3</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
<tr>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>r3_4</td>
<td>r2_4</td>
<td>r1_4</td>
<td>r0_4</td>
</tr>
<tr>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>r3_5</td>
<td>r2_5</td>
<td>r1_5</td>
<td>r0_5</td>
</tr>
<tr>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>r3_6</td>
<td>r2_6</td>
<td>r1_6</td>
<td>r0_6</td>
</tr>
<tr>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>r3_7</td>
<td>r2_7</td>
<td>r1_7</td>
<td>r0_7</td>
</tr>
</table>
此信息需要在 LED 的写入过程之前计算。 两次写入过程之间的时间必须小于 80uS! 对于 16 MHz Arduino,即 1280 个周期。
此时我的计算速度还不够快。
LED 的信息存储在名为 LEDs"Number of LEDs"4 的数组中 每个 LED 的阵列为一维,四种颜色为第二维,四种不同灯条为最后一维。
我要写入所有 LED 的代码:
static inline __attribute__ ((always_inline)) void showPixel() {
// Send the 32 Bits down every row. Remember that each pixel is 32 bits wide (8 bits each for R,G, B & W)
uint8_t bit;
uint8_t onPixel,offPixel; //output of PORTD when high or low is being written
cli(); //no interrupts
offPixel = PIXEL_PORT; //safe output of Port D
offPixel &= 0xf0; //create Bitmask for setting bit 4-7 of Port D to original value and leds off 0bxxxx0000
onPixel = offPixel | 0x0f; //led pins high plus IO pins as they were 0bxxxx1111
for(uint8_t ledNr=0; ledNr < NUM_LEDS; ledNr++) {
shuffle(0,LEDs[ledNr][3][0],LEDs[ledNr][3][1],LEDs[ledNr][3][2],LEDs[ledNr][3][3],offPixel);//white
shuffle(8,LEDs[ledNr][2][0],LEDs[ledNr][2][1],LEDs[ledNr][2][2],LEDs[ledNr][2][3],offPixel);//blue
shuffle(16,LEDs[ledNr][0][0],LEDs[ledNr][0][1],LEDs[ledNr][0][2],LEDs[ledNr][0][3],offPixel);//red
shuffle(24,LEDs[ledNr][1][0],LEDs[ledNr][1][1],LEDs[ledNr][1][2],LEDs[ledNr][1][3],offPixel);//green
bit=32;
while (bit--) { //send out the 32 bytes
sendBitX4_lower( LED[bit] ,onPixel,offPixel);
}
}
sei(); //activate interrupts
}
我的随机播放功能:
static inline __attribute__ ((always_inline)) void shuffle(uint8_t bit, uint8_t v0, uint8_t v1,uint8_t v2, uint8_t v3,uint8_t IOpins){
uint8_t res,pos,mask=8;
pos=bit+8;
//LED[bit]=0; EDIT: this was a test
//LED[bit++]=0; to see if decreasing the resolution
//LED[bit++]=0; speeds it up enough to work
//bit++; at 5 bit resolution it was barely fast enough
while(bit<pos){
if(v3 & mask) res=8;
else res=0;
if(v2 & mask) res|=4;
if(v1 & mask) res|=2;
if(v0 & mask) res|=1;
mask<<=1;
res|=IOpins; //Set bits 0-3 to the output that was present
LED[bit]=res;
bit++;
}
理解 suffle 函数需要做什么可能有点困难。我试着画了它,所以也许你可以更容易理解(attachment shuffle.pdf)。 本质上,每种颜色的计算分为 4 个部分。每次 shuffle 将写入 LED[32] 阵列的 8 个字节。这个过程看起来有点像一个正在反转的矩阵。 LED[32] 的每个字节都有 LED 阵列的 4 个不同字节的元素。从 LED[0] 的 LSB 开始,到 LED[8] 的 MSB,以此类推。
我尝试了不同的例子。有些带有位移,有些带有穿过数组的指针,但这是最快的。
我的问题是:物理上是否可以在这么多周期中进行此计算?如果是,如何? 可能使用内联汇编器,但我刚刚进入... 谢谢你的帮助。如果您有兴趣,我们可以对其进行改进并让所有人都可以访问:)
更新: 我认为不可能绕过洗牌,因为每个 LED 输出 32 位信息的时间很关键。在我的代码中,sendBitX4_lower() 函数被调用了 32 次。 发送一位信息的时间是 1.25µs±600ns,比如说 1.9µs,最多 30 个周期。
如果你有兴趣,这是代码:
static inline __attribute__ ((always_inline)) void sendBitX4_lower( uint8_t bits ,uint8_t onBits,uint8_t offBits ) {
asm volatile (
"out %[port], %[onBits] \n\t" // 1st step - send T0H high
".rept %[T0HCycles] \n\t" // Execute NOPs to delay exactly the specified number of cycles
"nop \n\t"
".endr \n\t"
"out %[port], %[bits] \n\t" // set the output bits to thier values for T0H-T1H
".rept %[dataCycles] \n\t" // Execute NOPs to delay exactly the specified number of cycles
"nop \n\t"
".endr \n\t"
"out %[port],%[offBits] \n\t" // last step - T1L all bits low
// Don't need an explicit delay here since the overhead that follows will always be long enough
::
[port] "I" (_SFR_IO_ADDR(PIXEL_PORT)),
[bits] "d" (bits),
[onBits] "d" (onBits),
[offBits] "d" (offBits),
[T0HCycles] "I" (NS_TO_CYCLES(T0H) - 2), // 1-bit width less overhead for the actual bit setting, note that this delay could be longer and everything would still work
[dataCycles] "I" (NS_TO_CYCLES((T1H-T0H)) - 2)// Minimum interbit delay. Note that we probably don't need this at all since the loop overhead will be enough, but here for correctness
);
// Note that the inter-bit gap can be as long as you want as long as it doesn't exceed the reset timeout (which is A long time)
}
我猜想每一帧都有一点时间可以用来做部分计算,但我怀疑它的全部。那将是 960 个周期。它可能会起作用,因为现在不需要内存节省部分,但另一方面需要完成对端口的写入。 所以一帧的所有计算都需要在这个序列中找到时间:Timing Overview 这将涉及从 RAM 加载以及可能导致条件跳转(sbrc 1-2 个周期)的四个“if”。 我查看了 Adafruit (https://github.com/adafruit/Adafruit_NeoPixel) 的图书馆以获取灵感。
【问题讨论】:
-
为什么要先发送 Wx_0(根据那个 HTML 表),而数据表显示 “发送数据的顺序(R7 - R6 - ... W0)”?我是不是误会了什么? .... 和 “从 LSB 到 MSB 顺序:w(白色)b(蓝色)r(红色)g(绿色)” - 真的很奇特的 WBRG,或者它只是拼写错误,你有更多常见的WBGR还是WRGB?
-
sendBitX4_lower是什么?为什么要在内存中对其进行混洗,然后再输出混洗后的数据,而无需将混洗后的值写入内存即可简单地立即输出。 IMO 写的东西要花很多钱。无论如何,1280 个周期听起来像是可用的年龄(请注意,在 ZX Spectrum 上,1 个显示帧有 70000 个周期(是的,那只有 70k),每条指令确实花费了 4 到 23 个周期,甚至没有 1 个),你有优化吗编译那个 C 时打开? .. 编辑:哦,这是LEDvsLEDs.. :/ -
最后,在
shuffle:为什么你将前两位设置为零LED[bit]=0;(通过LED[bit++]=0;覆盖第一位两次),然后通过bit++;完全跳过第三位没有设置它,然后你从颜色值中设置剩余的 5 位......很奇怪。为什么不把每种颜色都填满 8 位? - 你的 Arduino 有什么 CPU? -
@Ped7g 对不起,这是我的错。这不完全是我想要发布的代码。你现在看到的是通过降低分辨率来“加速”。这是一个实验,看看条带在什么时候起作用。如果只有 5 位被“洗牌”,它就可以工作(足够快),所以我得到 2^5=32 的分辨率。所以原始代码删除了这 4 行。
-
阵列 LED 无故从 32 发出下降。所以白色实际上是在最后发送的。我还注意到数据表和我的芯片的区别。用我的芯片,红色和绿色的顺序似乎互换了。
标签: assembly arduino inline-assembly