FirePendant.ino
  1  // SPDX-FileCopyrightText: 2019 Phillip Burgess/paintyourdragon for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  //--------------------------------------------------------------------------
  6  // Animated flame for Adafruit Pro Trinket.  Uses the following parts:
  7  //   - Pro Trinket microcontroller (adafruit.com/product/2010 or 2000)
  8  //     (#2010 = 3V/12MHz for longest battery life, but 5V/16MHz works OK)
  9  //   - Charlieplex LED Matrix Driver (2946)
 10  //   - Charlieplex LED Matrix (2947, 2948, 2972, 2973 or 2974)
 11  //   - 350 mAh LiPoly battery (2750)
 12  //   - LiPoly backpack (2124)
 13  //   - SPDT Slide Switch (805)
 14  //
 15  // This is NOT good "learn from" code for the IS31FL3731; it is "squeeze
 16  // every last byte from the Pro Trinket" code.  If you're starting out,
 17  // download the Adafruit_IS31FL3731 and Adafruit_GFX libraries, which
 18  // provide functions for drawing pixels, lines, etc.  This sketch also
 19  // uses some ATmega-specific tricks and will not run as-is on other chips.
 20  //--------------------------------------------------------------------------
 21  
 22  #include <Wire.h>           // For I2C communication
 23  #include "data.h"           // Flame animation data
 24  #include <avr/power.h>      // Peripheral control and
 25  #include <avr/sleep.h>      // sleep to minimize current draw
 26  
 27  #define I2C_ADDR 0x74       // I2C address of Charlieplex matrix
 28  
 29  uint8_t        page = 0;    // Front/back buffer control
 30  const uint8_t *ptr  = anim; // Current pointer into animation data
 31  uint8_t        img[9 * 16]; // Buffer for rendering image
 32  
 33  // UTILITY FUNCTIONS -------------------------------------------------------
 34  
 35  // The full IS31FL3731 library is NOT used by this code. Instead, 'raw'
 36  // writes are made to the matrix driver.  This is to maximize the space
 37  // available for animation data.  Use the Adafruit_IS31FL3731 and
 38  // Adafruit_GFX libraries if you need to do actual graphics stuff.
 39  
 40  // Begin I2C transmission and write register address (data then follows)
 41  void writeRegister(uint8_t n) {
 42    Wire.beginTransmission(I2C_ADDR);
 43    Wire.write(n);
 44    // Transmission is left open for additional writes
 45  }
 46  
 47  // Select one of eight IS31FL3731 pages, or Function Registers
 48  void pageSelect(uint8_t n) {
 49    writeRegister(0xFD); // Command Register
 50    Wire.write(n);       // Page number (or 0xB = Function Registers)
 51    Wire.endTransmission();
 52  }
 53  
 54  // SETUP FUNCTION - RUNS ONCE AT STARTUP -----------------------------------
 55  
 56  void setup() {
 57    uint8_t i, p, byteCounter;
 58  
 59    power_all_disable(); // Stop peripherals: ADC, timers, etc. to save power
 60    power_twi_enable();  // But switch I2C back on; need it for display
 61    DIDR0 = 0x0F;        // Digital input disable on A0-A3
 62  
 63    // The Arduino Wire library runs I2C at 100 KHz by default.
 64    // IS31FL3731 can run at 400 KHz.  To ensure fast animation,
 65    // override the I2C speed settings after init...
 66    Wire.begin();                            // Initialize I2C
 67    TWSR = 0;                                // I2C prescaler = 1
 68    TWBR = (F_CPU / 400000 - 16) / 2;        // 400 KHz I2C
 69    // The TWSR/TWBR lines are AVR-specific and won't work on other MCUs.
 70  
 71    pageSelect(0x0B);                        // Access the Function Registers
 72    writeRegister(0);                        // Starting from first...
 73    for(i=0; i<13; i++) Wire.write(10 == i); // Clear all except Shutdown
 74    Wire.endTransmission();
 75    for(p=0; p<2; p++) {                     // For each page used (0 & 1)...
 76      pageSelect(p);                         // Access the Frame Registers
 77      writeRegister(0);                      // Start from 1st LED control reg
 78      for(i=0; i<18; i++) Wire.write(0xFF);  // Enable all LEDs (18*8=144)
 79      for(byteCounter = i+1; i<0xB4; i++) {  // For blink & PWM registers...
 80        Wire.write(0);                       // Clear all
 81        if(++byteCounter >= 32) {            // Every 32 bytes...
 82          byteCounter = 1;                   // End I2C transmission and
 83          Wire.endTransmission();            // start a new one because
 84          writeRegister(i);                  // Wire buf is only 32 bytes.
 85        }
 86      }
 87      Wire.endTransmission();
 88    }
 89  
 90    // Enable the watchdog timer, set to a ~32 ms interval (about 31 Hz)
 91    // This provides a sufficiently steady time reference for animation,
 92    // allows timer/counter peripherals to remain off (for power saving)
 93    // and can power-down the chip after processing each frame.
 94    set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Deepest sleep mode (WDT wakes)
 95    noInterrupts();
 96    MCUSR  &= ~_BV(WDRF);
 97    WDTCSR  =  _BV(WDCE) | _BV(WDE);     // WDT change enable
 98    WDTCSR  =  _BV(WDIE) | _BV(WDP0);    // Interrupt enable, ~32 ms
 99    interrupts();
100    // Peripheral and sleep savings only amount to about 10 mA, but this
101    // may provide nearly an extra hour of run time before battery depletes.
102  }
103  
104  // LOOP FUNCTION - RUNS EVERY FRAME ----------------------------------------
105  
106  void loop() {
107    uint8_t  a, x1, y1, x2, y2, x, y;
108  
109    power_twi_enable();
110    // Datasheet recommends that I2C should be re-initialized after enable,
111    // but Wire.begin() is slow.  Seems to work OK without.
112  
113    // Display frame rendered on prior pass.  This is done at function start
114    // (rather than after rendering) to ensire more uniform animation timing.
115    pageSelect(0x0B);    // Function registers
116    writeRegister(0x01); // Picture Display reg
117    Wire.write(page);    // Page #
118    Wire.endTransmission();
119  
120    page ^= 1; // Flip front/back buffer index
121  
122    // Then render NEXT frame.  Start by getting bounding rect for new frame:
123    a = pgm_read_byte(ptr++);     // New frame X1/Y1
124    if(a >= 0x90) {               // EOD marker? (valid X1 never exceeds 8)
125      ptr = anim;                 // Reset animation data pointer to start
126      a   = pgm_read_byte(ptr++); // and take first value
127    }
128    x1 = a >> 4;                  // X1 = high 4 bits
129    y1 = a & 0x0F;                // Y1 = low 4 bits
130    a  = pgm_read_byte(ptr++);    // New frame X2/Y2
131    x2 = a >> 4;                  // X2 = high 4 bits
132    y2 = a & 0x0F;                // Y2 = low 4 bits
133  
134    // Read rectangle of data from anim[] into portion of img[] buffer
135    for(x=x1; x<=x2; x++) { // Column-major
136      for(y=y1; y<=y2; y++) img[(x << 4) + y] = pgm_read_byte(ptr++);
137    }
138  
139    // Write img[] to matrix (not actually displayed until next pass)
140    pageSelect(page);    // Select background buffer
141    writeRegister(0x24); // First byte of PWM data
142    uint8_t i = 0, byteCounter = 1;
143    for(uint8_t x=0; x<9; x++) {
144      for(uint8_t y=0; y<16; y++) {
145        Wire.write(img[i++]);      // Write each byte to matrix
146        if(++byteCounter >= 32) {  // Every 32 bytes...
147          Wire.endTransmission();  // end transmission and
148          writeRegister(0x24 + i); // start a new one (Wire lib limits)
149        }
150      }
151    }
152    Wire.endTransmission();
153  
154    power_twi_disable(); // I2C off (see comment at top of function)
155    sleep_enable();
156    interrupts();
157    sleep_mode();        // Power-down MCU.
158    // Code will resume here on wake; loop() returns and is called again
159  }
160  
161  ISR(WDT_vect) { } // Watchdog timer interrupt (does nothing, but required)
162