Source file: /~heha/Mikrocontroller/LEDs/WS2812.zip/firmware/WS2812.c

/* 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-80