我正在尝试使用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的信息存储在名为LED "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++;
}
在双重函数中需要做什么可能有点难。我试图绘制它,所以也许你可以更容易理解(attachment shuffle.pdf)。 基本上,每种颜色的计算分为4个部分。每个shuffle将写入LED [32]数组的8个字节。这个过程看起来有点像被倒置的矩阵。 LED [32]的每个字节具有4个不同字节的LED阵列的元件。从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的加载以及可能导致条件跳转的四个“if”(sbrc 1-2个循环)。 我已经看过Adafruit的图书馆(https://github.com/adafruit/Adafruit_NeoPixel)以获得灵感。
答案 0 :(得分:1)
如何(盲目尝试,因为我没有arduino IDE,我也没有尝试使用任何C编译器进行编译,因此您可能需要修复语法):
static void showPixel() {
const uint8_t colorOffsets[4] = { 1, 0, 2, 3 }; // move somewhere into constants?
... set up "offPixel" here
cli(); //no interrupts
for(uint8_t ledNr=0; ledNr < NUM_LEDS; ++ledNr) {
for (uint8_t colorIdx = 0; colorIdx < 4; ++colorIdx) {
const uint8_t* v_ptr = LEDs[ledNr][colorOffsets[colorIdx]];
uint8_t bitMask = 0x80;
do {
uint8_t toSend = offPixel; // upper 4 bits preserved PORTD, lower 4 bits cleared
// set lower 4 bits by the colour values
if (v_ptr[0] & bitMask) toSend |= 1;
if (v_ptr[1] & bitMask) toSend |= 2;
if (v_ptr[2] & bitMask) toSend |= 4;
if (v_ptr[3] & bitMask) toSend |= 8;
//TODO send "toSend" to PORTD
???
PIXEL_PORT = toSend; // guessing it
bitMask >>= 1; // next bit of values
} while (bitMask); // all 8 bits of color value
}
}
sei(); //activate interrupts
}
我不打算那个,为什么?它通过所有LED发送数据,听起来像是一个足够大的单元,只需在代码存储器中使用一次。
它应该从每个条带的顶部位扫描绿色,红色,蓝色,白色,构建要发送到PORTD的字节(LED数据中的引脚0-3,来自offPixel
的4-7个松树)。然后你应该发送它。内存中没有任何混乱,读取它们应该发出的模式中的正确位。
它也会发送完整的8位颜色,如果你想强制将某些位设置为零,你可以将while (bitMask)
更改为某个特定的位测试,然后再发出offPixel
个值总共发射8位的时间。
编辑:
我在godbolt set to AVR gcc中试了一下这个来源,我有这些观察......
首先使用内部源(上面的godbolt链接上的完整源代码。我不确定Arduino如何定义PORTD,所以我把它放在一些易失性内存绝对地址,应该足够接近):
for(uint8_t ledNr=0; ledNr < NUM_LEDS; ++ledNr) {
for (uint8_t colorIdx = 0; colorIdx < 4; ++colorIdx) {
const uint8_t* v_ptr = LEDs[ledNr][colorOffsets[colorIdx]];
const uint8_t v0 = v_ptr[0];
const uint8_t v1 = v_ptr[1];
const uint8_t v2 = v_ptr[2];
const uint8_t v3 = v_ptr[3];
uint8_t bitMask = 0x80;
do {
PIXEL_PORT = offPixel; // set the LED bits to low
asm volatile("": : :"memory");
// uint8_t toSend = offPixel; // upper 4 bits preserved PORTD, lower 4 bits cleared
// // set lower 4 bits by the colour values
// if (v0 & bitMask) toSend |= 1;
uint8_t toSend = offPixel | ((v0 & bitMask) ? 1 : 0);
if (v1 & bitMask) toSend |= 2;
if (v2 & bitMask) toSend |= 4;
if (v3 & bitMask) toSend |= 8;
PIXEL_PORT = toSend; // set the LED bits to high
asm volatile("": : :"memory");
bitMask >>= 1; // next bit of values
} while (bitMask); // all 8 bits of color value
} //for (uint8_t colorIdx = 0; colorIdx < 4; ++colorIdx) {
} //for(uint8_t ledNr=0; ledNr < NUM_LEDS; ++ledNr)
生成的程序集看起来像是可以使用的基础,它将循环展开8次(对于bitMask
)并将bitMask
测试转换为特定的sbrc + ori
对,这是有意义的对我来说。问题是,端口上的off + on位设置得太短(它会将位切换为ON,然后通过在下一条指令中将它们关闭来立即启动下一位,并且没有太多工作要做向下,除了添加nop
延迟循环)。
主要问题是,为了在所有32个LED上获得固定时序,您需要在展开的循环之前准备初始状态,并在第7/8位测试结束时继续准备下一个状态。延迟,所以下一个LED将在固定时间内启动,就像下一个LED一样。
C输出直接看起来不可用,但对于你自己的展开循环来说可能是合理的模板(如果你在汇编时已经足够好了,我不想写满{{1}例程,因为我从来没有进行AVR汇编,而且我不知道如何读取/写入端口,而且编写这种大小的展开循环非常繁琐。
核心代码(由我评论)(在此部分中测试了第4位,即showPixels()
):
bitMask == 0x10
我会用与剩余比特测试相同的方式手工编写初始部分,即(使人类阅读更容易让所有比特由相同的时尚代码处理):
out 52-0x20,r20 // PORTD = offPixel
ldi r23,lo8(1)
sbrs r24,4 // 4th bit (the number goes from 7 to 0)
ldi r23,lo8(0) // toSend = 0/1 (? (v0 & bitMask))
or r23,r20 // toSend |= offPixel
sbrc r25,4
ori r23,lo8(2) // if (v1 & bitMask) toSend |= 2
sbrc r21,4
ori r23,lo8(4) // if (v2 & bitMask) toSend |= 4
sbrc r22,4
ori r23,lo8(8) // if (v3 & bitMask) toSend |= 8
out 52-0x20,r23 // PORTD = toSend
(我尝试修改C以提示,但是gcc改为插入两个分支 out 52-0x20,r20 //1c // PORTD = offPixel
mov r23,r20 //1c // toSend = offPixel
sbrc r24,4 //1/2c // 4th bit (the number goes from 7 to 0)
ori r23,lo8(1) //1c // if (v0 & bitMask) toSend |= 1
sbrc r25,4 //1/2c
ori r23,lo8(2) //1c // if (v1 & bitMask) toSend |= 2
sbrc r21,4 //1/2c
ori r23,lo8(4) //1c // if (v2 & bitMask) toSend |= 4
sbrc r22,4 //1/2c
ori r23,lo8(8) //1c // if (v3 & bitMask) toSend |= 8
out 52-0x20,r23 //1c // PORTD = toSend
来直接加载rjmp
或offPixel
值的寄存器,烦人......)
对于跳过/设置条件,offPixel+1
对将固定2个时钟,因此在将sbrc + ori
写入端口后,将需要10个时钟周期才能写入ON状态。如果我正确地阅读您的时间概述,那看起来稍微偏离了可接受的范围。因此,您可以稍后将某个OFF移出,例如在第二个offPixel
之前,这将使其在OFF和ON sbrc
之间有7个时钟。 (实际上手持gb on godbolt作品:https://godbolt.org/g/V2vf2X)
然后你有4 + 12用于下一个循环...新的开始部分(直到第二个out
已经吃4c,并且你有12c通过内务或人工延迟填补。
在内务/延迟部分结束(位2,1,0 ...),您需要将新的LED值提取到sbrc
寄存器中,为简单起见,我可能会使用两个寄存器范围,如{ {1}}用于第一次传递,v0/v1/v2/v3
用于第二次传递,并执行16次循环(可能需要仔细的寄存器使用设计以适应可用的备用寄存器)。
考虑到这一点,条纹的r18+
值的加载也可以向前移动指针,即r26+
,大约2个周期(我不确定哪个时钟申请,在XMEGA上不访问SRAM你可以是1个周期,但我认为你正在用vX
?)访问SRAM,即4x2 = 8个周期。这可以完全适合12c延迟和备用周期来调整ldd rX,Z+
(从绿色[1]到红色[0]以及从红色[0]到蓝色[2],即第一种情况下的LEDs
并且Z
秒,然后sub v_ptr,8
应该在蓝色[2]完成后指向白色[3]。如果add v_ptr,4
排列良好,你可以{{1只是Z
)的低部分。
所以总代码架构就像:
LEDs
准备到sub/add
(Z
reg)ledptr
(整个代码的固定注册表)LEDs[0][1]
=指向nextColorPtr表Z
offPixel
Y
(在{ -8, +4, +0, +0 }
的表格中添加-8 / + 4/0/0到Z+
(太懒了,不能亲自去看看究竟是什么时钟将是)(也取决于你是否Z += [Y+]
对齐,所以它足以调整低Z
部分,或者你需要对整个{Y
进行适当的调整{1}})。LEDs
延迟将剩余时间填入12c总计Z
延迟add
延迟Z
并且如果这是来自主要32个循环的最后一个并且分支到最后一个位代码的3个变体,则一个变体循环到&#34; //循环4次&#34;继续(必须延迟加分支到4c循环,总共16c直到OFF状态),第二个变量循环回&#34; //循环32次&#34;继续下一个LED,必须在2c完成,总共16c直到OFF状态(即只是分支== 2c),第三个变量是继续退出程序(处理32个LED),由{{1启动每个变量延迟填写最多12c nop
nop
...
我不会尝试为gcc编写内联ASM,因为我在指定被破坏的寄存器/等时完全丢失了..即使其成为有效的gcc内联。而且这让我花费的时间超出了我的预期。 (如果我自己这样做,我会把它写成独立的装配)
但是,如果我理解你的时间绘制正确,它看起来很可行。一般般,但可以4条。如果你需要超过4个条带,那么在这里仍有一些延迟可能会在最大值时将其撞到8个条带,但这需要在几个条件下更积极地展开/交错循环的开始/结束比特(对于8个条带,它可能需要在整个8位代码上交错管理,即根本没有复制/粘贴,每个延迟由下一个LED准备代码组成,并且需要2组工作注册)
如果我没有忽略任何东西,这应该产生固定的7个时钟OFF状态,16个ON状态时钟(两个位之间总共23个时钟),用于整个32个LED x 4种颜色(128个字节发送到端口)
目前建议的源代码已经超过100行代码,编写,调试和维护非常繁琐,但是因为你需要固定时序,看起来是最合理的方法。