max7219.c (9629B)
1 /* 2 * Copyright (c) 2021 Zach White <skullydazed@gmail.com> 3 * Copyright (c) 2007 Eberhard Fahle 4 * 5 * max7219.c - A library for controling Leds with a MAX7219/MAX7221 6 * 7 * Permission is hereby granted, free of charge, to any person 8 * obtaining a copy of this software and associated documentation 9 * files (the "Software"), to deal in the Software without 10 * restriction, including without limitation the rights to use, 11 * copy, modify, merge, publish, distribute, sublicense, and/or sell 12 * copies of the Software, and to permit persons to whom the 13 * Software is furnished to do so, subject to the following 14 * conditions: 15 * 16 * This permission notice shall be included in all copies or 17 * substantial portions of the Software. 18 * 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 * OTHER DEALINGS IN THE SOFTWARE. 27 */ 28 29 /* 30 * This driver started as a port of Arduino's LedControl to QMK. The 31 * original Arduino code can be found here: 32 * 33 * https://github.com/wayoda/LedControl 34 * 35 * Unlike LedControl we are using the native SPI support, you will need to 36 * use the native SPI pins for your MCU. You can set the CS pin with 37 * `#define MAX7219_LOAD <pin>`. 38 * 39 * This has only been tested on AVR, specifically a Teensy 2.0++. 40 */ 41 42 #include "max7219.h" 43 #include "spi_master.h" 44 #include "debug.h" 45 #include "gpio.h" 46 #include "wait.h" 47 #include "font.h" 48 49 // Datastructures 50 bool max7219_led_scrolling = true; 51 uint16_t max7219_buffer_end = 0; 52 uint8_t max7219_spidata[MAX_BYTES]; 53 uint8_t max7219_led_a[8][MAX7219_BUFFER_SIZE]; 54 55 /* Write max7219_spidata to all the max7219's 56 */ 57 void max7219_write_all(void) { 58 dprintf("max7219_write_all()\n"); 59 if (spi_start(MAX7219_LOAD, false, 0, 8)) { 60 for(int i = MAX_BYTES; i>0; i--) { 61 dprintf("spi_write(%d)\n", max7219_spidata[i-1]); 62 spi_write(max7219_spidata[i-1]); 63 } 64 spi_stop(); 65 } else { 66 xprintf("Could not spi_start!\n"); 67 } 68 } 69 70 /* Write the current frame in max7219_led_a to all the max7219's 71 */ 72 void max7219_write_frame(void) { 73 dprintf("max7219_write_frame()\n"); 74 75 // Set our opcode and data 76 for (int col=0; col<8; col++) { 77 for (int device_num=0; device_num<MAX7219_CONTROLLERS; device_num++) { 78 int offset=device_num*2; 79 max7219_spidata[offset] = max7219_led_a[col][device_num]; 80 max7219_spidata[offset+1] = col+1; 81 } 82 max7219_write_all(); 83 } 84 } 85 86 /* Stores a message in the sign buffer. 87 * 88 * message should be a 2d array with the outer array having a length of your 89 * message and the inner array having a length of 6. Use the CHR_<letter> 90 * macros from font.h to populate your array. 91 * 92 * Example: 93 * 94 * uint8_t message[10][6] = {CHR_INTERROBANG, CHR_C, CHR_l, CHR_u, CHR_e, CHR_b, CHR_o, CHR_a, CHR_r, CHR_d}; 95 * max7219_message(message, 10); 96 */ 97 void max7219_message_sign(uint8_t message[][6], size_t message_len) { 98 uint8_t letter_num = 0; 99 uint8_t letter_col = 0; 100 max7219_buffer_end = message_len * 6 + 32; 101 102 for (int device_num=0; device_num<MAX7219_BUFFER_SIZE; device_num++) { 103 for (int col=0; col<8; col++) { 104 if (letter_num >= message_len) { 105 max7219_led_a[col][device_num] = 0b00000000; 106 } else { 107 max7219_led_a[col][device_num] = message[letter_num][letter_col]; 108 if (letter_col == 5) { 109 letter_num++; 110 letter_col = 0; 111 } else { 112 letter_col++; 113 } 114 } 115 } 116 } 117 118 max7219_write_frame(); 119 } 120 121 /* Scroll the content on the sign left by 1 column. 122 * 123 * When loop_message is true columns that slide off the left will be added 124 * to the right to be displayed again. 125 */ 126 void max7219_message_sign_task(bool loop_message) { 127 uint8_t left_col = 0b00000000; 128 129 if (!max7219_led_scrolling) { 130 return; 131 } 132 133 if (loop_message) { 134 left_col = max7219_led_a[0][0]; 135 } 136 137 int i=0; 138 139 for (int device_num=0; device_num<MAX7219_BUFFER_SIZE; device_num++) { 140 for (int col=0; col<8; col++) { 141 i++; 142 143 if (i == max7219_buffer_end) { 144 max7219_led_a[col][device_num] = left_col; 145 device_num=MAX7219_BUFFER_SIZE; 146 break; 147 } else if (col < 7) { 148 max7219_led_a[col][device_num] = max7219_led_a[col+1][device_num]; 149 } else if (device_num == MAX7219_BUFFER_SIZE-1) { 150 max7219_led_a[col][device_num] = left_col; 151 } else { 152 max7219_led_a[col][device_num] = max7219_led_a[0][device_num+1]; 153 } 154 } 155 } 156 max7219_write_frame(); 157 } 158 159 /* Write data to a single max7219 160 */ 161 void max7219_write(int device_num, volatile uint8_t opcode, volatile uint8_t data) { 162 dprintf("max7219_write(%d, %d, %d)\n", device_num, opcode, data); 163 164 // Clear the data array 165 for(int i = MAX_BYTES; i>0; i--) { 166 max7219_spidata[i-1]=0; 167 } 168 169 // Set our opcode and data 170 uint8_t offset = device_num*2; 171 max7219_spidata[offset] = data; 172 max7219_spidata[offset+1] = opcode; 173 174 // Write the data 175 max7219_write_all(); 176 } 177 178 /* Turn off all the LEDs 179 */ 180 void max7219_clear_display(void) { 181 dprintf("max7219_clear_display();\n"); 182 183 for (int col=0; col<8; col++) { 184 for (int device_num=0; device_num<MAX7219_BUFFER_SIZE; device_num++) { 185 max7219_led_a[col][device_num] = 0b00000000; 186 } 187 } 188 max7219_write_frame(); 189 } 190 191 /* Enable the display test (IE turn on all 64 LEDs) 192 */ 193 void max7219_display_test(int device_num, bool enabled) { 194 dprintf("max7219_display_test(%d, %d);\n", device_num, enabled); 195 196 if (device_num<0 || device_num >= MAX7219_CONTROLLERS) { 197 return; 198 } 199 200 max7219_write(device_num, OP_DISPLAYTEST, enabled); 201 } 202 203 /* Initialize the max7219 system and set the controller(s) to a default state. 204 */ 205 void max7219_init(void) { 206 wait_ms(1500); 207 dprintf("max7219_init()\n"); 208 209 gpio_set_pin_output(MAX7219_LOAD); 210 gpio_write_pin_high(MAX7219_LOAD); 211 spi_init(); 212 213 for (int i=0; i<MAX7219_CONTROLLERS; i++) { 214 max7219_shutdown(i, true); 215 } 216 217 for (int i=0; i<MAX7219_CONTROLLERS; i++) { 218 // Reset everything to defaults and enable the display 219 max7219_display_test(i, false); 220 max7219_set_scan_limit(i, 7); 221 max7219_set_decode_mode(i, 0); 222 max7219_set_intensity(i, MAX7219_LED_INTENSITY); 223 } 224 225 max7219_clear_display(); 226 227 #ifndef MAX7219_NO_STARTUP_TEST 228 for (int i=0; i<MAX7219_CONTROLLERS; i++) { 229 // Test this display 230 max7219_display_test(i, true); 231 wait_ms(75); 232 max7219_display_test(i, false); 233 } 234 #endif 235 236 for (int i=0; i<MAX7219_CONTROLLERS; i++) { 237 max7219_shutdown(i, false); 238 } 239 } 240 241 /* Set the decode mode of the controller. You probably don't want to change this. 242 */ 243 void max7219_set_decode_mode(int device_num, int mode) { 244 dprintf("max7219_set_decode_mode(%d, %d);\n", device_num, mode); 245 246 if (device_num<0 || device_num >= MAX7219_CONTROLLERS) { 247 return; 248 } 249 250 max7219_write(device_num, OP_DECODEMODE, mode); 251 } 252 253 /* Set the intensity (brightness) for the LEDs. 254 */ 255 void max7219_set_intensity(int device_num, int intensity) { 256 dprintf("max7219_set_intensity(%d, %d);\n", device_num, intensity); 257 258 if (device_num<0 || device_num >= MAX7219_CONTROLLERS) { 259 return; 260 } 261 262 if (intensity >= 0 && intensity<16) { 263 max7219_write(device_num, OP_INTENSITY, intensity); 264 } 265 } 266 267 /* Control a single LED. 268 */ 269 void max7219_set_led(int row, int column, bool state) { 270 dprintf("max7219_set_led(%d, %d, %d);\n", row, column, state); 271 272 if (column<0 || column>8*MAX7219_CONTROLLERS) { 273 xprintf("max7219_set_led: column (%d) out of bounds\n", column); 274 return; 275 } 276 277 if (row<0 || row>7) { 278 xprintf("max7219_set_led: row (%d) out of bounds\n", row); 279 return; 280 } 281 282 /* At this point we reverse the sense of row and column to match the 283 * physical layout of my LEDs. 284 */ 285 uint8_t device_num = column / 8; 286 uint8_t col = column % 8; 287 uint8_t val = 0b10000000 >> row; 288 289 if (state) { 290 max7219_led_a[col][device_num] = max7219_led_a[col][device_num]|val; 291 } else { 292 val = ~val; 293 max7219_led_a[col][device_num] = max7219_led_a[col][device_num]&val; 294 } 295 max7219_write(device_num, col+1, max7219_led_a[col][device_num]); 296 } 297 298 /* Set the number of digits (rows) to be scanned. 299 */ 300 void max7219_set_scan_limit(int device_num, int limit) { 301 dprintf("max7219_set_scan_limit(%d, %d);\n", device_num, limit); 302 303 if (device_num<0 || device_num >= MAX7219_CONTROLLERS) { 304 return; 305 } 306 307 if (limit >= 0 && limit < 8) { 308 max7219_write(device_num, OP_SCANLIMIT, limit); 309 } 310 } 311 312 /* Enable (true) or disable (false) the controller. 313 */ 314 void max7219_shutdown(int device_num, bool shutdown) { 315 dprintf("max7219_shutdown(%d, %d);\n", device_num, shutdown); 316 317 if (device_num<0 || device_num >= MAX7219_CONTROLLERS) { 318 return; 319 } 320 321 max7219_write(device_num, OP_SHUTDOWN, !shutdown); 322 }