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