I am working on a small LED board project. The LED board is a 16x16 panel of WS2818B LEDs; they are also known as Neopixels. Controlling these thing is a bit of a pain, and I thought I would look and see if there were some sort SPI interface. I struck gold at Wayne Holder's page who provides a small assembly language program for the ATTiny10, and this looks almost perfect.
The problem here is that I want to do this with through-hole parts. So I bought some ATTiny85s off of Amazon. I don't know much about AVRs beyond some basic Arduino projects, but I think I ported Holder's program over correctly:
; +====+
; Vcc -> PB5 |* | Vcc
; NC <- PB3 | | PB2 <- CLK
; LEDs -- PB4 | | PB1 -- NC
; GND | | PB0 <- MOSI
; +====+
MOSI = 0x00 ; PB0
SCLK = 0x02 ; PB2
DOUT = 0x04 ; PB4
CLKPR = 0x26
PORTB = 0x18
DDRB = 0x17
PINB = 0x16
.global main ; It's just easier to call it main
main:
eor r17, r17 ; Set Clock Prescaler to 1:1 (CLKPR = 0)
out CLKPR, r17
sbi DDRB, DOUT ; Set PB2 as Output
waithi:
sbis PINB, SCLK ; Wait for CLK to go HIGH (rising edge)
rjmp waithi
sbi PORTB, DOUT ; Set Output HIGH to begin pulse to WS2818b and
nop ; and pause for a cycle
sbis PINB, MOSI ; Skip if DIN "1" bit
cbi PORTB, DOUT ; Set Output LOW to end "0" bit pulse to WS2818b
nop ; and pause for three cycles
nop
nop
cbi PORTB, DOUT ; Set Output LOW to end "1" bit pulse to WS2818b
waitlo:
sbic PINB, SCLK ; Wait for CLK to go LOW (falling edge)
rjmp waitlo
rjmp waithi ; Return to wait for SCLK to go HIGH
The idea is I should be able to drive this with an Arduino. Using an old Arduino Leonardo clone I had laying around, I connected it up. The problem is, no matter what I send out on the SPI to the ATTiny85, it lights up something around the first 12-20 lights in bright white. This is the Leonardo code:
#include <SPI.h>
#define VALU 0x20
#define PIXELS 256
#define CLKRATE 500000
#define LED_PIN 13
void setPixel(RGB color);
void setup() {
SPI.begin();
noInterrupts();
SPI.beginTransaction(SPISettings(CLKRATE, MSBFIRST, SPI_MODE0));
for (int ii = 0; ii < PIXELS; ii++) {
setPixel(RGB(0x00, 0x00, 0x00)); // Blk
}
SPI.endTransaction();
interrupts();
delay(1000);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
digitalWrite(LED_PIN, HIGH);
noInterrupts();
SPI.beginTransaction(SPISettings(CLKRATE, MSBFIRST, SPI_MODE0));
for (int ii = 0; ii < PIXELS; ii++) {
SPI.transfer(0);
SPI.transfer(0x10);
SPI.transfer(0);
}
SPI.endTransaction();
interrupts();
digitalWrite(LED_PIN, LOW);
delay(1000);
digitalWrite(LED_PIN, HIGH);
noInterrupts();
SPI.beginTransaction(SPISettings(CLKRATE, MSBFIRST, SPI_MODE0));
for (int ii = 0; ii < PIXELS; ii++) {
SPI.transfer(0);
SPI.transfer(0);
SPI.transfer(0);
}
SPI.endTransaction();
interrupts();
digitalWrite(LED_PIN, LOW);
delay(1000);
}
I flash the LED on the Leonardo just so I can see when it does something. Here's a picture of the panel doing what it does:
Now, I have a KKMoon multimeter, and if I am reading this correctly, it is showing a pulse width on the output pin from the ATTiny85 to the LED matrix of like 4us. This is the short pulse. The longer one is longer. But the appropriate length for the short pulse for the WS2818B is 0.4us.
At this point, I feel like I have a timing issue. As a last-ditch effort, I checked the fuses, and the values shown are:
fuses_lo = 0x62
fuses_hi = 0xdf
fuses_ext = 0xff
lock_byte = 0xff
Checking back against the ATTiny85 datasheet, the chip should be running at 8MHz. That should mean a NOP length of 150ns. Obviously, something is not working the way I think it should, but I am lost now. I would appreciate any advice as to what is going wrong and how to fix it.
Thanks!