Secret Konami Cheat Code to high resolution PWM on your myki Light
Ever want to use 16-bit PWM or 10-bit PWM on your SaikoLED myki, Arduino Leonardo, or other ATmega32U4 based device and feel like interpreting the datasheet is about as easy as figuring out the Konami Cheat Code by reading the assembler code?
Yeah, you and everyone else. I’ve been working with the ATmega platform for seven years, and it still took me forever. To help prevent you the same headache, I spent a few days scouring the datasheet and put together the following highly commented explanation on how to use the registers to implement this totally awesome feature on your own ATmega32U4 based system. This includes the SaikoLED myki, of course, as well as the Arduino Leonardo.
The below code explains at the register level how to use this secret Konami Cheat Code. I realize that there are some libraries out there that do this, but I have not found them nearly as easy to use as just doing it directly this way in a dozen lines of code. Additionally, I am hopeful that learning how the registers actually work will be useful for other people interested in adding new features as a walkthrough of how to read the ATmega datasheets.
For more information, please see below the break.
And please, if you arrived here via a site like hacker news, and think others might find the information useful or interesting, remember to upvote it!
Ultimately, this code sets the prescaler to none (16MHz PWM clock frequency), activates all three 16-bit PWM outputs on timer 1 (OCR1A, OCR1B, OCR1C, the “A”, “B”, and “C” output compare registers which control the hardware PWM output for timer 1), the 10-bit PWM output on channel A of timer 4 (OCR4A, the “A” output compare register for timer 4), and demonstrates writing to 10-bit registers on the SaikoLED myki (as well as the Arduino Leonardo) to get awesome performance out of LEDs.
All said, this lets the SaikoLED myki use full 48-bit “Deep Color” with an added white pixel for enhancing pastels.
The trickiest part was probably 10-bit register access – it was not at all obvious that the same register would be used as the high byte register for all 10-bit write operations (I was expecting a separate high-byte register for each 10-bit register).
Source Code for myki Next Gen Fade
Ultimate PWM frequency is 244Hz with a clock frequency of 16MHz for each of the 3 independent 16-bit PWM outputs. Similar techniques can be used on OCR3A to get a fourth channel as on the final version of the SaikoLED myki white LED channel. This should be expandable to use OCR4B and OCR4D for an additional two 10-bit PWM outputs as well!
Most code is cut for simplicity from the linked source file.
#define RED OCR1A #define GREEN OCR1B #define BLUE OCR1C #define WHITE OCR4A int whitevalue; void setup() { // This section of setup ignores Arduino conventions // because they are too confusing to follow. Instead, // I am figuring this out directly from the datasheet // for the ATmega32u4. // Pin Setup // Change the data direction on B5, B6, and B7 to // output without overwriting the directions of B0-4. // B5/OC1A = RED, B6/OC1B = GREEN, and B7/OC1C = BLUE DDRB |= (1<<7)|(1<<6)|(1<<5); // PWM Setup Stuff // TCCR1A is the Timer/Counter 1 Control Register A. // TCCR1A[7:6] = COM1A[1:0] (Mode for Channel A) // TCCR1A[5:4] = COM1B[1:0] (Mode for Channel B) // TCCR1A[3:2] = COM1C[1:0] (Mode for Channel C) // COM1x[1:0] = 0b10 (Clear on match. Set at TOP) // Clock Prescaler Stuff // TCCR1B[2:0] = CS1[2:0] (Clock Select for Timer 1) // CS1[2:0] = 0b001 (No Prescaler) // Waveform Generation Mode Setup Stuff // TCCR1A[1:0] = WGM1[1:0] (WGM for Timer 1 LSB) // TCCR1B[4:3] = WGM1[3:2] (WGM for Timer 1 MSB) // WGM[3:0] = 0b1110 (i.e. Mode 14) // Mode 14 - Fast PWM, TOP from ICR1, Set OC1x at // TOP, clear at OCR1x. // In quasi-English: // ICR1 is a 16-bit register that should be written // with the desired value for TOP. This is the value // at which the PWM cycle restarts and defines the // resolution. // OCR1x means either OCR1A (RED), OCR1B (GREEN), or // OCR1C (BLUE). Each of these is a 16-bit register // that contains the number at which the corresponding // pin OC1A, OC1B, or OC1C (pins 29, 30, and 12 // respectively on the ATmega32U4A). // The combination of values in ICR1 and OCR1x makes // it so that at the beginning of a timer count, OC1A, // OC1B, and OC1C are turned on. Then the counter // starts going up at 16MHz. When the counter value is // equal to OCR1A, pin OC1A turns off. Similarly with // OCR1B and OCR1C. This means that the average length // of time each pin is turned on is OCR1x/ICR1. This // is the duty cycle. // The following command writes 0xFFFF to the ICR1 // register, providing the source for TOP. If you // instead wrote 0x00FF, this would become an 8-bit // PWM output. If you wrote instead 0xFFF, it would // become a 12-bit PWM output. Completely // reconfigurable, even to values that are not a // multiple of 2 if you want clean decimal // fractional duty cycles. // For 16-bit resolution, we need to program ICR1 with // 0xFFFF. ICR1 = 0xFFFF; // Unimportant Stuff // TCCR1B7 = ICNC1 (Input Capture Noise Canceler) // TCCR1B6 = ICES1 (Input Capture Edge Select) // TCCR1B5 = Reserved // Put all of the above register explanation together // and... tada! TCCR1A = 0b10101010; TCCR1B = 0b00011001; // Example of setting 16-bit PWM value. // Initial RGB values. RED = 0x0000; GREEN = 0x0000; BLUE = 0x0000; // Similar setup for White. Mostly a temporary hack // for the rev7 since the rev11 will have white // connected to timer 3's 16-bit PWM output. DDRC |= (1<<7); // White LED PWM Port. // Should enable OCR4A, enable the PWM on A, and set // prescale to 256. // Magic sauce to do 10-bit register writing. // Write TC4H first also when writing to white OCR4A. TC4H = 0x03; OCR4C = 0xFF; // Configuration of Timer 4 Registers. TCCR4A = 0b10000010; TCCR4B = 0b00000001; // Jeez that's a lot of registers. What a fancy timer. // Initial white value (10 bit storage location) whitevalue = 0x000; // First write the high 2 bits of white PWM write to // the magic TC4H register used for 10-bit register // access. TC4H = whitevalue >> 8; // Next, write the bottom 8 bits to the white PWM // register (OCR3A aliased to WHITE in the define // statements at the top of the code block). WHITE = 0xFF & whitevalue; // Now you have written 0x0000, 0x0000, 0x0000, // and 0x000 respectively to RED, GREEN, BLUE, and // WHITE registers (16, 16, 16, and 10 bits). // To update the color values, for instance by // setting red and blue to 0x4000 and white to 0x200 // (50% saturated magenta), here is an example. After // setting the OCR1A, OCR1B, OCR1C, and OCR3A // registers this will continue indefinitely. RED = 0x4000; GREEN = 0x0000; BLUE = 0x4000; // Remember, special weird technique for writing to // 10-bit registers. whitevalue = 0x200; TC4H = whitevalue >> 8; WHITE = 0xFF & whitevalue; while(1); // And for this example, now halt to show off magenta! }