// ===================================================================================
// Project:   TinyNeoController - NeoPixel Controller based on ATtiny13A
// Version:   v1.0
// Year:      2021
// Author:    Stefan Wagner
// Github:    https://github.com/wagiminator
// EasyEDA:   https://easyeda.com/wagiminator
// License:   http://creativecommons.org/licenses/by-sa/3.0/
// ===================================================================================
//
// Description:
// ------------
// An ATtiny13 is more than sufficient to control almost any number of NeoPixels
// via an IR remote. The NeoController was originally developed as a tester for
// 800kHz NeoPixel strings. Since there was still so much flash left in the
// ATtiny13, an IR receiver was integrated so that some parameters can be
// controlled with an IR remote control. In this way, it is also suitable as a
// simple and cheap remote-controlled control unit for NeoPixels. Due to its
// small size, it can be soldered directly to the LED strip without any problems.
// The power supply via a USB-C connection enables currents of up to 3A. There is
// still more than a third of the flash memory left for additional ideas.
//
// References:
// -----------
// The Neopixel implementation is based on NeoCandle.
// https://github.com/wagiminator/ATtiny85-TinyCandle
//
// The IR receiver implementation (NEC protocol) is based on TinyDecoder
// https://github.com/wagiminator/ATtiny13-TinyDecoder
//
// Wiring:
// -------
//                                +-\/-+
//             --- RST ADC0 PB5  1|°   |8  Vcc
//   NEOPIXELS ------- ADC3 PB3  2|    |7  PB2 ADC1 -------- 
// IR RECEIVER ------- ADC2 PB4  3|    |6  PB1 AIN1 OC0B --- 
//                          GND  4|    |5  PB0 AIN0 OC0A --- 
//                                +----+
//
// Compilation Settings:
// ---------------------
// Controller:  ATtiny13A
// Core:        MicroCore (https://github.com/MCUdude/MicroCore)
// Clockspeed:  9.6 MHz internal
// BOD:         BOD disabled
// Timing:      Micros disabled
//
// Leave the rest on default settings. Don't forget to "Burn bootloader"!
// No Arduino core functions or libraries are used. Use the makefile if 
// you want to compile without Arduino IDE.
//
// Fuse settings: -U lfuse:w:0x3a:m -U hfuse:w:0xff:m


// ===================================================================================
// Libraries and Definitions
// ===================================================================================

// Libraries
#include <avr/io.h>           // for GPIO
#include <avr/sleep.h>        // for sleep functions
#include <avr/interrupt.h>    // for interrupts
#include <util/delay.h>       // for delays

// Pin definitions
#define NEO_PIN       PB3     // Pin for neopixels
#define IR_PIN        PB4     // Pin for IR receiver

// NeoPixel parameter
#define NEO_GRB               // type of pixel: NEO_GRB, NEO_RGB or NEO_RGBW
#define NEO_PIXELS    255     // number of pixels in the string (max 255)

// IR codes
#define IR_ADDR       0x1A    // IR device address
#define IR_POWER      0x01    // IR code for power on/off
#define IR_BRIGHT     0x02    // IR code for brightness
#define IR_SPEED      0x03    // IR code for animation speed
#define IR_DENSE      0x04    // IR code for color density
#define IR_FAIL       0xFF    // IR fail code

// Global variables
uint8_t NEO_brightness = 0;   // 0..2

// ===================================================================================
// Neopixel Implementation for 9.6 MHz MCU Clock and 800 kHz Pixels
// ===================================================================================

// NeoPixel parameter and macros
#define NEO_init()    DDRB |= (1<<NEO_PIN)      // set pixel DATA pin as output
#define NEO_latch()   _delay_us(281)            // delay to show shifted colors

// Send a byte to the pixels string
void NEO_sendByte(uint8_t byte) {               // CLK  comment
  for(uint8_t bit=8; bit; bit--) asm volatile(  //  3   8 bits, MSB first
    "sbi  %[port], %[pin]   \n\t"               //  2   DATA HIGH
    "sbrs %[byte], 7        \n\t"               // 1-2  if "1"-bit skip next instruction
    "cbi  %[port], %[pin]   \n\t"               //  2   "0"-bit: DATA LOW after 3 cycles
    "rjmp .+0               \n\t"               //  2   delay 2 cycles
    "add  %[byte], %[byte]  \n\t"               //  1   byte <<= 1
    "cbi  %[port], %[pin]   \n\t"               //  2   "1"-bit: DATA LOW after 7 cycles
    ::
    [port]  "I"   (_SFR_IO_ADDR(PORTB)),
    [pin]   "I"   (NEO_PIN),
    [byte]  "r"   (byte)
  );
}

// Write color to a single pixel
void NEO_writeColor(uint8_t r, uint8_t g, uint8_t b) {
  #if defined (NEO_GRB)
    NEO_sendByte(g); NEO_sendByte(r); NEO_sendByte(b);
  #elif defined (NEO_RGB)
    NEO_sendByte(r); NEO_sendByte(g); NEO_sendByte(b);
  #elif defined (NEO_RGBW)
    NEO_sendByte(r); NEO_sendByte(g); NEO_sendByte(b); NEO_sendByte(0);
  #else
    #error Wrong or missing NeoPixel type definition!
  #endif
}

// Switch off all pixels
void NEO_clear(void) {
  for(uint8_t i = NEO_PIXELS; i; i--) NEO_writeColor(0, 0, 0);
}

// Write hue value (0..191) to a single pixel
void NEO_writeHue(uint8_t hue) {
  uint8_t phase = hue >> 6;
  uint8_t step  = (hue & 63) << NEO_brightness;
  uint8_t nstep = (63 << NEO_brightness) - step;
  switch(phase) {
    case 0:   NEO_writeColor(nstep,  step,     0); break;
    case 1:   NEO_writeColor(    0, nstep,  step); break;
    case 2:   NEO_writeColor( step,     0, nstep); break;
    default:  break;
  }
}

// ===================================================================================
// IR Receiver Implementation (NEC Protocol)
// ===================================================================================

// IR receiver definitions and macros
#define IR_init()       PORTB |= (1<<IR_PIN)  // pullup on IR pin
#define IR_available()  (~PINB & (1<<IR_PIN)) // return true if IR line is low

// IR wait for signal change and measure duration
uint8_t IR_waitChange(uint8_t timeout) {
  uint8_t pinState = PINB & (1<<IR_PIN);      // get current signal state
  uint8_t dur = 0;                            // variable for measuring duration
  while((PINB & (1<<IR_PIN)) == pinState) {   // measure length of signal
    if(dur++ > timeout) return 0;             // exit if timeout
    _delay_us(100);                           // count every 100us
  }
  return dur;                                 // return time in 100us
}

// IR read data byte
uint8_t IR_readByte(void) {
  uint8_t result;
  uint8_t dur;
  for(uint8_t i=8; i; i--) {                  // 8 bits
    result >>= 1;                             // LSB first
    if(IR_waitChange(11) < 3) return IR_FAIL; // exit if wrong burst length
    dur = IR_waitChange(21);                  // measure length of pause
    if(dur <  3) return IR_FAIL;              // exit if wrong pause length
    if(dur > 11) result |= 0x80;              // bit "0" or "1" depends on pause duration
  }
  return result;                              // return received byte
}

// IR read data according to NEC protocol
uint8_t IR_read(void) {
  uint16_t addr;                              // variable for received address
  if(!IR_available())        return IR_FAIL;  // exit if no signal
  if(!IR_waitChange(100))    return IR_FAIL;  // exit if wrong start burst length
  if(IR_waitChange(55) < 35) return IR_FAIL;  // exit if wrong start pause length

  uint8_t addr1 = IR_readByte();              // get first  address byte
  uint8_t addr2 = IR_readByte();              // get second address byte
  uint8_t cmd1  = IR_readByte();              // get first  command byte
  uint8_t cmd2  = IR_readByte();              // get second command byte

  if(IR_waitChange(11) < 3)  return IR_FAIL;  // exit if wrong final burst length
  if((cmd1 + cmd2) < 255)    return IR_FAIL;  // exit if command bytes are not inverse
  if((addr1 + addr2) == 255) addr = addr1;    // check if it's extended NEC-protocol ...
  else addr = ((uint16_t)addr2 << 8) | addr1; // ... and get the correct address
  if(addr != IR_ADDR)        return IR_FAIL;  // wrong address
  return cmd1;                                // return command code
}

// ===================================================================================
// Standby Implementation
// ===================================================================================

// Go to standby mode
void standby(void) {
  NEO_clear();                                // turn off NeoPixels
  while(1) {
    GIFR  |= (1<<PCIF);                       // clear any outstanding interrupts
    sei();                                    // enable interrupts
    sleep_mode();                             // sleep until IR interrupt
    cli();                                    // disable interrupts
    if( (IR_available()) && (IR_read() == IR_POWER) ) break;  // exit on power button
  }
}

// Pin change interrupt service routine
EMPTY_INTERRUPT(PCINT0_vect);                 // just wake up from sleep

// ===================================================================================
// Main Function
// ===================================================================================

int main(void) {
  // Local variables
  uint8_t start = 0;
  uint8_t speed = 3;
  uint8_t dense = 2;

  // Disable unused peripherals and prepare sleep mode to save power
  ACSR   =  (1<<ACD);                         // disable analog comperator
  DIDR0  = ~(1<<IR_PIN) & 0x1F;               // disable digital intput buffer except IR pin
  PRR    =  (1<<PRADC);                       // shut down ADC
  GIMSK |=  (1<<PCIE);                        // enable pin change interrupts
  PCMSK |=  (1<<IR_PIN);                      // enable interrupt on IR pin
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);        // set sleep mode to power down

  // Setup
  NEO_init();                                 // init Neopixels
  IR_init();                                  // init IR receiver

  // Loop
  while(1) {
    // Set new start value
    uint8_t current = start;
    start += speed;
    if(start >= 192) start -= 192;
    
    // Set the NeoPixels
    for(uint8_t i = NEO_PIXELS; i; i--) {
      NEO_writeHue(current);
      current += dense;
      if(current >= 192) current -= 192;
    }
    
    // Check IR receiver and change parameters; delay 80ms
    for(uint8_t i=80; i; i--) {
      if(IR_available()) {
        uint8_t command = IR_read();
        switch(command) {
          case IR_POWER:    standby(); break;
          case IR_BRIGHT:   if(++NEO_brightness > 2) NEO_brightness = 0; break;
          case IR_SPEED:    speed <<= 1; if(++speed > 7) speed = 0; break;
          case IR_DENSE:    dense += 2; if(dense > 7) dense = 0; break;
          default:          break;
        }       
      }
      _delay_ms(1);
    }
  }
}
ATTINY8520PU