/* Name: WS2812.c; TABSIZE=8, ENCODING=UTF-8, LINESEP=LF
* Projekt: Kette aus WS2812-RGB-LEDs auf letzten Wert einstellen; EEPROM umprogrammieren
* Mikrocontroller: ATtiny25 oder größer (ATtiny45, 85)
* Stein des Anstoßes war die Nicht-HID-Implementierung von LittleWire,
* was soll denn dieser Murks?
* Autor: Henrik Haftmann
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
#include <string.h> // memcpy
#include "usbdrv.c"
/*
Pin-Zuordnung (entsprechend vorhandener Platine):
PB0 (5) = LED-Kette
PB1 (6) = frei
PB2 (7) = frei
PB3 (2) = USB D+
PB4 (3) = USB D-
PB5 (1) = Reset
*/
#define LEDCNT 64 // ATtiny25
static uchar LedData[LEDCNT*3];
// USB Report-Deskriptor
PROGMEM char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = {
0x05, 0x01, // G USAGE_PAGE (Generic Desktop)
0x09, 0x09, // L USAGE (Tablet PC)
0xA1, 0x01, // M COLLECTION (Application)
0x05, 0x08, // G USAGE_PAGE (LED)
0x75, 8, // G REPORT_SIZE (8)
0x95, 4, // G REPORT_COUNT (4)
0x15, 0, // G LOGICAL_MINIMUM (0) (Win98 streikt bei „0x14“, lädt Treiber nicht)
0x26, 255,0, // G LOGICAL_MAXIMUM (255)
0x19, 0x48, // L USAGE_MINIMUM (Indicator Red) Rot-Anteil (Byte 0)
0x29, 0x4B, // L USAGE_MAXIMUM (Generic Indicator) LED-Index, 0-basiert
0x91, 0x02, // M OUTPUT (Var)
0x75, 24, // G REPORT_SIZE (8)
0x95, LEDCNT, // G REPORT_COUNT
0x05, 0x01, // G USAGE_PAGE (Generic Desktop)
0x09, 0x3A, // L USAGE (Counted Buffer)
0xB1, 0x02, // M FEATURE (Var)
0xC0}; // M END_COLLECTION
static uchar IoType;
static uchar*IoPtr;
static uchar IoLen; // uchar nur bei LedCount<=85!
// LedData[] auf WS2812-kompatible LED-Kette ausgeben
// Interrupts müssen gesperrt sein!
// Pro LED 30 µs (?)
void WS2812update(void) {
const uchar*data=LedData;
uchar len=sizeof LedData;
do{
uchar curbyte=*data++,ctr;
asm volatile(
" ldi %0,8 \n" // 0
"1: sbi %2,0 \n" // 2
" lsl %1 \n" // 3
" dec %0 \n" // 4
" nop \n" // 5
" brcs 2f \n" // 6l / 7h
" cbi %2,0 \n" // 8l / -
"2: brcc . \n" // 9l / 9h
" nop \n" // 10
" cbi %2,0 \n" // 12
" breq 3f \n" // 13 nt. 14 taken
" nop \n" // 15
" rjmp . \n" // 16
" rjmp . \n" // 18
" rjmp 1b \n" // 20
"3: \n"
:"=&d" (ctr)
:"r" (curbyte), "I" (_SFR_IO_ADDR(PORTB))
); // loop overhead including byte load is 6+1 cycles
}while (--len);
}
uchar usbFunctionWrite(uchar *data, uchar len) {
if (IoType==2) { // OUT-Transfer für genau 1 LED
IoType=0;
if (IoLen>=4 && len>=4 && data[3]<LEDCNT) {
memcpy(LedData+data[3]*3,data,3);
IoType=0xFF; // Ausgabe auf LED-Kette später
}
return 1;
}
if (IoType==3 && IoLen && len) {
if (len > IoLen) len = IoLen;
memcpy(IoPtr,data,len);
IoPtr+=len;
IoLen-=len;
if (!IoLen) {
IoType=0xFF; // Ausgabe auf LED-Kette später
return 1; // 1 für Transmissions-Ende
}
return 0;
}
return 0xFF; // Stall, unerwartete Daten
}
uchar usbFunctionSetup(uchar data[8]) {
usbRequest_t *rq = (void *)data;
/* class request type (x01x xxxx) */
if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
if (rq->bRequest == USBRQ_HID_GET_REPORT) { // wValueH: ReportType, wValueL: ReportID
usbMsgPtr = LedData;
return sizeof LedData;
}
if (rq->bRequest == USBRQ_HID_SET_REPORT) { // wValueH: ReportType, wValueL: ReportID
IoType=rq->wValue.bytes[1];
IoPtr=LedData;
IoLen=rq->wLength.word;
return 0xFF;
}
} /* no vendor specific requests implemented */
return 0;
}
static void tuneOscillator(void) {
#define SOLL 14 // usbSofDiff should be 16500/8%256 = 14.5
#define MABW 7 // Deviation of less than ±6 is OK and silently ignored
schar t = usbSofDiff-SOLL; // center result around signed zero
if (t<-MABW) OSCCAL++; // make faster
if (t> MABW) OSCCAL--; // make slower
}
int main(void) __attribute__((noreturn));
int main(void) {
uchar SofCmp=0;
// uchar keychange=0;
uchar mcusr = MCUSR;
MCUSR = 0;
ACSR |= 0x80; // Analogvergleicher abschalten: –70µA
WDTCR = 0x18; // Watchdog überlebt RESET: abschalten!
WDTCR = 0;
PCMSK = 1<<USB_CFG_DMINUS_BIT;
GIFR = 1<<PCIF;
if (mcusr & (1 << WDRF)) {
if (USBIN & USBMASK) { // no SE0 state?
GIMSK = 1<<PCIE;
MCUCR = 0x30; // SLEEP_MODE_PWR_DOWN and level interrupt for INT0
sei(); // Ohne Interruptfreigabe kein WakeUp
sleep_cpu(); // Alle Takte abschalten
cli();
}
}
MCUCR = 0x20; // SLEEP_MODE_STANDBY (wir brauchen T0)
PRR = 0b00001011; // USI, ADC, Timer1 aus
TCCR0B= 0x02; // Timer0 mit Achtel-Taktfrequenz (zum Synchronisieren)
usbInit();
WDTCR = 0x08; // Watchdog mit kürzestmöglicher Zeit (16 ms) aktivieren
PORTB = 0b00100110; // Pull-Up an allen offenen Anschlüssen
DDRB |= 0x01;
eeprom_read_block(LedData,0,sizeof LedData);
WS2812update();
sei();
for (;;) { // Endlos-Hauptschleife, wird höchstens vom Watchdog abgebrochen
uchar t,d;
if (USBIN&USBMASK) sleep_cpu(); // schlafen, AUSSER bei SE0, bis SOF
t=usbSofCount; // atomarer Zugriff auf Volatile-Variable
wdt_reset(); // Watchdog sollte lt. USB nach 3 ms zuschnappen, Minimum des µC ist 15 ms
usbPoll();
d=t-SofCmp; // Zeitdifferenz in ms (sollte 0 oder 1 herauskommen!)
if (d) {
SofCmp=t;
tuneOscillator();
if (IoType==0xFF) {
cli();
WS2812update(); // Nur nach SOF (sollte fürs Update genügend Zeit sein)
sei();
IoType=0;
}else if (!(EECR&2)) { // EEPROM ist nicht beschäftigt?
uchar a=EEARL+1; // Nächste Adresse angucken
if (a>=sizeof LedData) a=0;
EEAR=a;
EECR|=1; // Byte lesen
if (EEDR!=LedData[a]) { // vergleichen
EEDR=LedData[a]; // schreiben wenn unterschiedlich
cli();
EECR|=0x04;
EECR|=0x02; // schreibt im Hintergrund
sei();
}
}
}
}
}
Detected encoding: UTF-8 | 0
|