SPI-Neopixel/alt.c
2013-07-29 22:08:23 -07:00

500 lines
17 KiB
C

// Experiemtnal alternate ATtiny85 NeoPixel bridge code based on
// spineopixel.c and caffeine. No interrupts (all polling-based),
// Handles 4 MHz SPI on 16 MHz MCU. No global state machine stuff,
// max space for MOAR PIXELS.
#if !defined(F_CPU)
#error F_CPU not defined
#endif
#if !defined(BAUDRATE)
#error BAUDRATE not defined
#endif
#if !defined(I2C_ADDR) || (I2C_ADDR < 1) || (I2C_ADDR > 127)
#error I2C_ADDR not defined, or invalid range (must be 1-127)
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <string.h>
#include "reverse_bits.h"
// Clock/data/latch input pins
#define IN_CLK_PORTx PORTB
#define IN_CLK_DDRx DDRB
#define IN_CLK_PINx PINB
#define IN_CLK_BIT 2
#define IN_CLK_MASK (1 << IN_CLK_BIT)
#define IN_DATA_PORTx PORTB
#define IN_DATA_DDRx DDRB
#define IN_DATA_PINx PINB
#define IN_DATA_BIT 0
#define IN_DATA_MASK (1 << IN_DATA_BIT)
#define IN_LATCH_PORTx PORTB
#define IN_LATCH_DDRx DDRB
#define IN_LATCH_PINx PINB
#define IN_LATCH_BIT 3
#define IN_LATCH_MASK (1 << IN_LATCH_BIT)
// NeoPixel output pin
#define OUT_PORTx PORTB
#define OUT_DDRx DDRB
#define OUT_PINx PINB
#define OUT_BIT 4
#define OUT_MASK (1 << OUT_BIT)
// Mode select jumper: ->VCC = SPI, ->1M->GND = I2C, N/C = UART
#define JMP_PORTx PORTB
#define JMP_DDRx DDRB
#define JMP_PINx PINB
#define JMP_MASK (1 << 1)
// Drive latch low to indicate busy state, release for idle state
#define LATCH_BUSY \
IN_LATCH_DDRx |= IN_LATCH_MASK; \
IN_LATCH_PORTx &= ~IN_LATCH_MASK; // Output mode, set low
#define LATCH_IDLE \
IN_LATCH_DDRx &= ~IN_LATCH_MASK; \
IN_LATCH_PORTx |= IN_LATCH_MASK; // Input mode w/internal pullup
static void
spi_handler(void) ,
i2c_handler(void) ,
uart_handler(void),
send_to_neopixel(void);
#define MAX_LEDS 150
#define BUFFERSIZE (MAX_LEDS * 3)
static uint8_t buffer[BUFFERSIZE];
static volatile uint16_t byteCount;
int main(void) {
LATCH_BUSY
cli(); // No interrupts EVAR
OUT_DDRx |= OUT_MASK; // NeoPixel output
OUT_PORTx &= ~OUT_MASK; // Default low
// Set all pixels to initial 'off' state
byteCount = sizeof(buffer);
memset(buffer, 0, byteCount);
send_to_neopixel();
// For testing, call a handler directly:
// spi_handler();
// i2c_handler();
// uart_handler();
// Determine input mode from jumper, call specific handler.
// This allows for tighter control loops for each peripheral
// type rather than a unified state thing (and associated
// variables). Code space is more prevalent than RAM.
USICR = 0; // USI off
JMP_DDRx &= ~JMP_MASK; // Jumper pin = input
JMP_PORTx &= ~JMP_MASK; // No pullup
asm volatile ("rjmp .+0"); // Small delay
// If reading logic high even though there's an external pull-down
// resistor, pin is tied to VCC. Jump to SPI-specific code.
if(JMP_PINx & JMP_MASK) spi_handler();
JMP_PORTx |= JMP_MASK; // Activate pullup
asm volatile ("rjmp .+0"); // Small delay
// If reading logic low even though there's a pull-up resistor,
// pin is tied to ground. Jump to I2C-specific code.
// Note: external pull-down resistor MUST have magnitudes
// higher resistance than the internal pull-up resistor.
if(!(JMP_PINx & JMP_MASK)) i2c_handler();
// Pin is floating. Jump to UART-specific code.
JMP_PORTx &= ~JMP_MASK; // Disable pullup
uart_handler();
return 0;
}
static void inline __attribute__ ((always_inline)) spi_handler(void) {
IN_CLK_DDRx &= ~IN_CLK_MASK; // Clock, data inputs
IN_DATA_DDRx &= ~IN_DATA_MASK;
IN_CLK_PORTx &= ~IN_CLK_MASK; // No pullups on clock, data
IN_DATA_PORTx &= ~IN_DATA_MASK;
USICR = _BV(USIWM0) | _BV(USICS1); // 3 wire, ext clock, pos edge
for(;;) { // Forever
LATCH_IDLE
USISR = _BV(USIOIF); // Reset frame, important
while(IN_LATCH_PINx & IN_LATCH_MASK); // Latch is high, wait
do { // else latch low...
if(USISR & _BV(USIOIF)) { // New data ready?
USISR = _BV(USIOIF); // Clear flag & counter
if(byteCount < sizeof(buffer)) // If room avail
buffer[byteCount++] = USIDR; // store next byte
}
} while(!(IN_LATCH_PINx & IN_LATCH_MASK)); // While low
if(byteCount) send_to_neopixel();
}
}
static void inline __attribute__ ((always_inline)) i2c_ack(void) {
IN_DATA_DDRx |= IN_DATA_MASK; // SDA output (pulls SDA low = ACK)
// Clear overflow bit (also releases clock stretch),
// set counter to shift one bit out.
USISR = _BV(USIOIF) | (0xE << USICNT0);
while(!(USISR & _BV(USIOIF))); // Wait for bit
IN_DATA_DDRx &= ~IN_DATA_MASK; // Back to input
USISR = _BV(USIOIF); // Reset counter & overflow flag again
}
#define ADDR_8BIT (I2C_ADDR << 1) // Actual 8-bit I2C addr on the wire
// Important change: I2C stop condition does NOT latch. If using an
// Arduino as the I2C master, that platform's Wire library only supports
// up to 32 bytes per transfer, while we can receive up to 450. Using
// the latch pin allows smaller packets to be coalesced. It's not "pure"
// I2C, but a necessary evil.
static void inline __attribute__ ((always_inline)) i2c_handler(void) {
uint8_t x;
IN_DATA_DDRx &= ~IN_DATA_MASK; // SDA input
IN_CLK_DDRx &= ~IN_CLK_MASK; // SCL input
IN_DATA_PORTx &= ~IN_CLK_MASK; // Pullups disabled in TWI mode,
IN_CLK_PORTx &= ~IN_DATA_MASK; // external pullups required
// TWI mode, external clock, negative edge
USICR = _BV(USIWM1) | _BV(USIWM0) | _BV(USICS1) | _BV(USICS0);
// Setting USIWM0 holds SCL low when a counter overflow occurs --
// basically, automatic clock-stretching. It's released when
// the overflow flag is reset.
for(;;) { // Forever
LATCH_IDLE
while(IN_LATCH_PINx & IN_LATCH_MASK); // Latch is high, wait
do { // else latch low...
USISR = 0xF0; // Reset flags, counter
while((!(USISR & _BV(USISIF))) && // Wait for start cond.
(!(IN_LATCH_PINx & IN_LATCH_MASK))); // or latch high
if(IN_DATA_PINx & IN_DATA_MASK) break; // Latch high
#if(0)
while( (IN_CLK_PINx & IN_CLK_MASK ) && // Ensure start
(!(IN_DATA_PINx & IN_DATA_MASK )) && // condition completes
(!(IN_LATCH_PINx & IN_LATCH_MASK)));
#else
// Condition is simplified when they're on the same PINx register:
while((IN_CLK_PINx &
(IN_CLK_MASK | IN_DATA_MASK | IN_LATCH_MASK)) == IN_CLK_MASK);
#endif
if(IN_LATCH_PINx & IN_LATCH_MASK) break; // Latch high
if(IN_DATA_PINx & IN_DATA_MASK ) continue; // Stop condition
// Start condition detected
USISR = _BV(USIOIF); // Reset overflow flag, clear counter
while((!(USISR & (_BV(USIOIF) | // Wait for data
_BV(USIPF )))) && // or stop condition
(!(IN_LATCH_PINx & IN_LATCH_MASK))); // or latch high
if(IN_LATCH_PINx & IN_LATCH_MASK) break; // Latch high
if(USISR & _BV(USIPF) ) continue; // Stop condition
if(USIDR != ADDR_8BIT) continue; // Not my address
i2c_ack(); // It's a me!
while((!(USISR & _BV(USIPF) )) && // Wait for stop cond
(!(IN_LATCH_PINx & IN_LATCH_MASK))) { // or latch high
if(USISR & _BV(USIOIF)) { // New data ready?
x = USIDR; // Read byte in
i2c_ack(); // Thanks
if(byteCount < sizeof(buffer)) // If room avail
buffer[byteCount++] = x; // store next byte
}
}
} while(!(IN_LATCH_PINx & IN_LATCH_MASK)); // While latch low
if(byteCount) send_to_neopixel();
}
}
// Determine valid Timer0 prescale setting for F_CPU/BAUDRATE combo
#if ((F_CPU / BAUDRATE) < 256)
#define PRESCALE 1
#define PRESCALE_MASK _BV(CS00)
#elif ((F_CPU / 8 / BAUDRATE) < 256)
#define PRESCALE 8
#define PRESCALE_MASK _BV(CS01)
#elif ((F_CPU / 64 / BAUDRATE) < 256)
#define PRESCALE 64
#define PRESCALE_MASK (_BV(CS01) | _BV(CS00))
#else
// Could escalate with more prescales if needed, but for now...
#error F_CPU, BAUDRATE combo incompatible with supported Timer0 prescales
#endif
#define BITTIME ((F_CPU / PRESCALE) / BAUDRATE)
static void inline __attribute__ ((always_inline)) uart_handler(void) {
IN_DATA_DDRx &= ~IN_DATA_MASK; // Data input (clock is unused)
IN_DATA_PORTx &= ~IN_DATA_MASK; // No pullup
// WGM Mode 7: Fast PWM, OC0A/B disconnected
TCCR0A = _BV(WGM01) | _BV(WGM00);
TCCR0B = _BV(WGM02) | PRESCALE_MASK;
OCR0A = BITTIME;
// Three-wire mode w/Timer0 compare match = ersatz UART
USICR = _BV(USIWM0) | _BV(USICS0);
for(;;) { // Forever
LATCH_IDLE
while(IN_LATCH_PINx & IN_LATCH_MASK); // Latch is high, wait
do { // else latch low...
while((IN_DATA_PINx & IN_DATA_MASK ) && // Wait for start bit
!(IN_LATCH_PINx & IN_LATCH_MASK)); // or latch high
if(IN_LATCH_PINx & IN_LATCH_MASK) break; // Latch went high
// Now at beginning of start bit. Need to start sampling
// at middle of bit...set TCNT0 halfway to overflow and
// USI counter to read 1 bit.
TCNT0 = BITTIME / 2;
USISR = _BV(USIOIF) | (0xF << USICNT0);
while(!(USISR & _BV(USIOIF))); // Wait for bit
// Now at middle of bit start bit.
// Reset overflow and set counter for next 8 bits.
USISR = _BV(USIOIF) | (0x8 << USICNT0);
while(!(USISR & _BV(USIOIF))); // Wait for byte
if(byteCount < sizeof(buffer)) // Store if not full
buffer[byteCount++] = pgm_read_byte(&reverse_bits[USIDR]);
// Now in middle of last bit. Idle for one more (stop bit).
USISR = _BV(USIOIF) | (0xF << USICNT0);
while(!(USISR & _BV(USIOIF)));
// Now in middle of stop bit (logic high).
// Next iteration resumes watching for falling edge.
} while(!(IN_LATCH_PINx & IN_LATCH_MASK)); // While latch low
if(byteCount) send_to_neopixel();
}
}
// ------------------------------------------------------------------------
// Code below is adapted from Adafruit_NeoPixel.cpp, with minor tweaks
// for this project (and comments stripped for brevity). The full PORT
// writes with hi/lo/next vars is an artifact of that library's runtime
// pin selection...while fixed bit set/clear instructions would suffice
// for this project, the timing is extremely critical and it was easier
// to use the more flexible code than to write all new stuff here just
// to maybe save a handful of bytes.
// Global variable byteCount is altered by this function. It will
// always equal zero on return, indicating an empty buffer.
static void __attribute__ ((noinline)) send_to_neopixel(void) {
LATCH_BUSY
volatile uint8_t
*ptr = buffer, // Pointer to next byte
b = *ptr++; // Current byte value
uint8_t
hi = OUT_PORTx | OUT_MASK, // PORT w/output bit set high
lo = OUT_PORTx & ~OUT_MASK; // PORT w/output bit set low
// 8 MHz(ish) AVR ---------------------------------------------------------
#if (F_CPU >= 7400000UL) && (F_CPU <= 9500000UL)
volatile uint8_t n2 = 0, n1 = lo;
if(b & 0x80) n1 = hi;
asm volatile(
"headB:" "\n\t"
"out %[port] , %[hi]" "\n\t"
"mov %[n2] , %[lo]" "\n\t"
"out %[port] , %[n1]" "\n\t"
"rjmp .+0" "\n\t"
"sbrc %[byte] , 6" "\n\t"
"mov %[n2] , %[hi]" "\n\t"
"out %[port] , %[lo]" "\n\t"
"rjmp .+0" "\n\t"
"out %[port] , %[hi]" "\n\t"
"mov %[n1] , %[lo]" "\n\t"
"out %[port] , %[n2]" "\n\t"
"rjmp .+0" "\n\t"
"sbrc %[byte] , 5" "\n\t"
"mov %[n1] , %[hi]" "\n\t"
"out %[port] , %[lo]" "\n\t"
"rjmp .+0" "\n\t"
"out %[port] , %[hi]" "\n\t"
"mov %[n2] , %[lo]" "\n\t"
"out %[port] , %[n1]" "\n\t"
"rjmp .+0" "\n\t"
"sbrc %[byte] , 4" "\n\t"
"mov %[n2] , %[hi]" "\n\t"
"out %[port] , %[lo]" "\n\t"
"rjmp .+0" "\n\t"
"out %[port] , %[hi]" "\n\t"
"mov %[n1] , %[lo]" "\n\t"
"out %[port] , %[n2]" "\n\t"
"rjmp .+0" "\n\t"
"sbrc %[byte] , 3" "\n\t"
"mov %[n1] , %[hi]" "\n\t"
"out %[port] , %[lo]" "\n\t"
"rjmp .+0" "\n\t"
"out %[port] , %[hi]" "\n\t"
"mov %[n2] , %[lo]" "\n\t"
"out %[port] , %[n1]" "\n\t"
"rjmp .+0" "\n\t"
"sbrc %[byte] , 2" "\n\t"
"mov %[n2] , %[hi]" "\n\t"
"out %[port] , %[lo]" "\n\t"
"rjmp .+0" "\n\t"
"out %[port] , %[hi]" "\n\t"
"mov %[n1] , %[lo]" "\n\t"
"out %[port] , %[n2]" "\n\t"
"rjmp .+0" "\n\t"
"sbrc %[byte] , 1" "\n\t"
"mov %[n1] , %[hi]" "\n\t"
"out %[port] , %[lo]" "\n\t"
"rjmp .+0" "\n\t"
"out %[port] , %[hi]" "\n\t"
"mov %[n2] , %[lo]" "\n\t"
"out %[port] , %[n1]" "\n\t"
"rjmp .+0" "\n\t"
"sbrc %[byte] , 0" "\n\t"
"mov %[n2] , %[hi]" "\n\t"
"out %[port] , %[lo]" "\n\t"
"sbiw %[count], 1" "\n\t"
"out %[port] , %[hi]" "\n\t"
"mov %[n1] , %[lo]" "\n\t"
"out %[port] , %[n2]" "\n\t"
"ld %[byte] , %a[ptr]+" "\n\t"
"sbrc %[byte] , 7" "\n\t"
"mov %[n1] , %[hi]" "\n\t"
"out %[port] , %[lo]" "\n\t"
"brne headB" "\n"
: [count] "+w" (byteCount),
[n1] "+r" (n1),
[n2] "+r" (n2),
[byte] "+r" (b)
:
[ptr] "e" (ptr),
[port] "I" (_SFR_IO_ADDR(OUT_PORTx)),
[hi] "r" (hi),
[lo] "r" (lo));
// 12 MHz(ish) AVR --------------------------------------------------------
#elif (F_CPU >= 11100000UL) && (F_CPU <= 14300000UL)
volatile uint8_t next = lo;
if(b & 0x80) next = hi;
asm volatile(
"headB:" "\n\t"
"out %[port], %[hi]" "\n\t"
"rcall bitTimeB" "\n\t"
"out %[port], %[hi]" "\n\t"
"rcall bitTimeB" "\n\t"
"out %[port], %[hi]" "\n\t"
"rcall bitTimeB" "\n\t"
"out %[port], %[hi]" "\n\t"
"rcall bitTimeB" "\n\t"
"out %[port], %[hi]" "\n\t"
"rcall bitTimeB" "\n\t"
"out %[port], %[hi]" "\n\t"
"rcall bitTimeB" "\n\t"
"out %[port], %[hi]" "\n\t"
"rcall bitTimeB" "\n\t"
"out %[port] , %[hi]" "\n\t"
"rjmp .+0" "\n\t"
"ld %[byte] , %a[ptr]+" "\n\t"
"out %[port] , %[next]" "\n\t"
"mov %[next] , %[lo]" "\n\t"
"sbrc %[byte] , 7" "\n\t"
"mov %[next] , %[hi]" "\n\t"
"nop" "\n\t"
"out %[port] , %[lo]" "\n\t"
"sbiw %[count], 1" "\n\t"
"brne headB" "\n\t"
"rjmp doneB" "\n\t"
"bitTimeB:" "\n\t"
"out %[port] , %[next]" "\n\t"
"mov %[next] , %[lo]" "\n\t"
"rol %[byte]" "\n\t"
"sbrc %[byte] , 7" "\n\t"
"mov %[next] , %[hi]" "\n\t"
"nop" "\n\t"
"out %[port] , %[lo]" "\n\t"
"ret" "\n\t"
"doneB:" "\n"
: [count] "+w" (byteCount),
[next] "+r" (next),
[byte] "+r" (b)
: [ptr] "e" (ptr),
[port] "I" (_SFR_IO_ADDR(OUT_PORTx)),
[hi] "r" (hi),
[lo] "r" (lo));
// 16 MHz(ish) AVR --------------------------------------------------------
#elif (F_CPU >= 15400000UL) && (F_CPU <= 19000000L)
volatile uint8_t next = lo, bit = 8;
asm volatile(
"head20:" "\n\t"
"out %[port], %[hi]" "\n\t"
"sbrc %[byte], 7" "\n\t"
"mov %[next], %[hi]" "\n\t"
"dec %[bit]" "\n\t"
"out %[port], %[next]" "\n\t"
"mov %[next], %[lo]" "\n\t"
"breq nextbyte20" "\n\t"
"rol %[byte]" "\n\t"
"rjmp .+0" "\n\t"
"nop" "\n\t"
"out %[port], %[lo]" "\n\t"
"nop" "\n\t"
"rjmp .+0" "\n\t"
"rjmp head20" "\n\t"
"nextbyte20:" "\n\t"
"ldi %[bit] , 8" "\n\t"
"ld %[byte], %a[ptr]+" "\n\t"
"out %[port], %[lo]" "\n\t"
"nop" "\n\t"
"sbiw %[count], 1" "\n\t"
"brne head20" "\n"
: [count] "+w" (byteCount),
[next] "+r" (next),
[byte] "+r" (b),
[bit] "+r" (bit)
: [ptr] "e" (ptr),
[port] "I" (_SFR_IO_ADDR(OUT_PORTx)),
[hi] "r" (hi),
[lo] "r" (lo));
#else
#error "CPU SPEED NOT SUPPORTED"
#endif
// ~ 50 uS delay
volatile uint16_t x = (F_CPU / 1000000L * 50L) / 6;
while(x--); // 6 cycles per iteration
}