|
| 1 | +/* |
| 2 | + * The MIT License (MIT) |
| 3 | + * |
| 4 | + * Copyright (c) 2024 Bradán Lane STUDIO |
| 5 | + * |
| 6 | + * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 7 | + * of this software and associated documentation files (the "Software"), to deal |
| 8 | + * in the Software without restriction, including without limitation the rights |
| 9 | + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 10 | + * copies of the Software, and to permit persons to whom the Software is |
| 11 | + * furnished to do so, subject to the following conditions: |
| 12 | + * |
| 13 | + * The above copyright notice and this permission notice shall be included in |
| 14 | + * all copies or substantial portions of the Software. |
| 15 | + * |
| 16 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 17 | + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 18 | + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 19 | + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 20 | + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 21 | + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 22 | + * THE SOFTWARE. |
| 23 | + */ |
| 24 | + |
| 25 | +/* |
| 26 | + The Explorer Badge(s) have more than one specific hardware configuration. |
| 27 | + This is a result of small changes in parts availability. The changes are |
| 28 | + insignificant to the end user but require changes to the initialization. |
| 29 | + The hardware revisions use a voltage divider connected to pin GP29 to |
| 30 | + indicate which hardware configuration exists. The code generates a |
| 31 | + "version ID" or VID which is used by the code to perform the correct |
| 32 | + initialization. The VID is also exposed to the end user in case there is |
| 33 | + a need - either for documentation, support requests, or tutorial materials. |
| 34 | +*/ |
| 35 | + |
| 36 | +#include "mpconfigboard.h" |
| 37 | + |
| 38 | +#include "supervisor/board.h" |
| 39 | +#include "shared-bindings/board/__init__.h" |
| 40 | + |
| 41 | +#include "shared-bindings/busio/SPI.h" |
| 42 | +#include "shared-bindings/fourwire/FourWire.h" |
| 43 | +#include "shared-bindings/microcontroller/Pin.h" |
| 44 | +#include "shared-module/displayio/__init__.h" |
| 45 | +#include "supervisor/shared/board.h" |
| 46 | + |
| 47 | +#include "src/rp2_common/hardware_gpio/include/hardware/gpio.h" |
| 48 | + |
| 49 | +#include "src/rp2_common/hardware_adc/include/hardware/adc.h" |
| 50 | +#define ADC_FIRST_PIN_NUMBER 26 |
| 51 | +#define ADC_PIN_COUNT 4 |
| 52 | +extern void common_hal_mcu_delay_us(uint32_t); |
| 53 | + |
| 54 | +#define HEIGHT 200 |
| 55 | +#define WIDTH 200 |
| 56 | + |
| 57 | +#define DELAY_FLAG 0x80 |
| 58 | + |
| 59 | +#define EPD_RAM_BW 0x10 |
| 60 | +#define EPD_RAM_RED 0x13 |
| 61 | + |
| 62 | +#define DISPLAY_EN_PIN 8 |
| 63 | + |
| 64 | +// These commands are the combination of SSD1608 and SSD1681 and not all commands are supported for each controller |
| 65 | +#define SSD_DRIVER_CONTROL 0x01 |
| 66 | +#define SSD_GATE_VOLTAGE 0x03 |
| 67 | +#define SSD_SOURCE_VOLTAGE 0x04 |
| 68 | +#define SSD_DISPLAY_CONTROL 0x07 |
| 69 | +#define SSD_PROGOTP_INITIAL 0x08 |
| 70 | +#define SSD_WRITEREG_INITIAL 0x09 |
| 71 | +#define SSD_READREG_INITIAL 0x0A |
| 72 | +#define SSD_NON_OVERLAP 0x0B |
| 73 | +#define SSD_BOOST_SOFT_START 0x0C |
| 74 | +#define SSD_GATE_SCAN_START 0x0F |
| 75 | +#define SSD_DEEP_SLEEP 0x10 |
| 76 | +#define SSD_DATA_MODE 0x11 |
| 77 | +#define SSD_SW_RESET 0x12 |
| 78 | +#define SSD_HV_DETECT 0x14 |
| 79 | +#define SSD_VCI_DETECT 0x15 |
| 80 | +#define SSD_TEMP_CONTROL_1681 0x18 |
| 81 | +#define SSD_TEMP_CONTROL_1608 0x1C |
| 82 | +#define SSD_TEMP_WRITE 0x1A |
| 83 | +#define SSD_TEMP_READ 0x1B |
| 84 | +#define SSD_TEMP_EXTERN 0x1C |
| 85 | +#define SSD_MASTER_ACTIVATE 0x20 |
| 86 | +#define SSD_DISP_CTRL1 0x21 |
| 87 | +#define SSD_DISP_CTRL2 0x22 |
| 88 | +#define SSD_WRITE_RAM_BLK 0x24 |
| 89 | +#define SSD_READ_RAM_BLK 0x25 |
| 90 | +#define SSD_WRITE_RAM_RED 0x26 |
| 91 | +#define SSD_READ_RAM_RED 0x27 |
| 92 | +#define SSD_VCOM_SENSE 0x28 |
| 93 | +#define SSD_VCOM_DURRATION 0x29 |
| 94 | +#define SSD_PROG_VCOM 0x2A |
| 95 | +#define SSD_CTRL_VCOM 0x2B |
| 96 | +#define SSD_WRITE_VCOM 0x2C |
| 97 | +#define SSD_READ_OTP 0x2D |
| 98 | +#define SSD_READ_ID 0x2E |
| 99 | +#define SSD_READ_STATUS 0x2F |
| 100 | +#define SSD_WRITE_LUT 0x32 |
| 101 | +#define SSD_WRITE_DUMMY 0x3A |
| 102 | +#define SSD_WRITE_GATELINE_1608 0x3B |
| 103 | +#define SSD_WRITE_BORDER 0x3C |
| 104 | +#define SSD_SET_RAMXPOS 0x44 |
| 105 | +#define SSD_SET_RAMYPOS 0x45 |
| 106 | +#define SSD_SET_RAMXCOUNT 0x4E |
| 107 | +#define SSD_SET_RAMYCOUNT 0x4F |
| 108 | +#define SSD_NOP 0xFF |
| 109 | + |
| 110 | +const uint8_t _start_sequence_ssd1681[] = { |
| 111 | + SSD_SW_RESET, DELAY_FLAG + 0, 20, // soft reset and wait 20ms |
| 112 | + SSD_DATA_MODE, 1, 0x03, // Data entry sequence |
| 113 | + SSD_WRITE_BORDER, 1, 0x05, // border color |
| 114 | + SSD_TEMP_CONTROL_1681, 1, 0x80, // Temperature control |
| 115 | + SSD_SET_RAMXCOUNT, 1, 0x00, |
| 116 | + SSD_SET_RAMYCOUNT, 2, 0x00, 0x00, |
| 117 | + SSD_DRIVER_CONTROL, 3, ((WIDTH - 1) & 0xFF), (((WIDTH >> 8) - 1) & 0xFF), 0x00, // set display size |
| 118 | + SSD_DISP_CTRL2, 1, 0xf7, // Set DISP only full refreshes |
| 119 | +}; |
| 120 | +const uint8_t _stop_sequence_ssd1681[] = { |
| 121 | + SSD_DEEP_SLEEP, DELAY_FLAG + 1, 0x01, 0x64 // Enter deep sleep |
| 122 | +}; |
| 123 | +const uint8_t _refresh_sequence_ssd1681[] = { |
| 124 | + SSD_MASTER_ACTIVATE, 0, |
| 125 | +}; |
| 126 | + |
| 127 | + |
| 128 | +const uint8_t _start_sequence_ssd1608[] = { |
| 129 | + SSD_SW_RESET, DELAY_FLAG + 0, 20, // soft reset and wait 20ms |
| 130 | + SSD_DRIVER_CONTROL, 3, ((WIDTH - 1) & 0xFF), (((WIDTH - 1) >> 8) & 0xFF), 0x00, // set display size |
| 131 | + SSD_WRITE_GATELINE_1608, 1, 0x0B, // gate line width |
| 132 | + SSD_DATA_MODE, 1, 0x03, // Data entry sequence |
| 133 | + SSD_WRITE_VCOM, 1, 0x70, |
| 134 | + SSD_WRITE_LUT, 30, 0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22, 0x66, 0x69, |
| 135 | + 0x69, 0x59, 0x58, 0x99, 0x99, 0x88, 0x00, 0x00, 0x00, 0x00, |
| 136 | + 0xf8, 0xb4, 0x13, 0x51, 0x35, 0x51, 0x51, 0x19, 0x01, 0x00, |
| 137 | + SSD_DISP_CTRL2, 1, 0xC7, // Set DISP only full refreshes |
| 138 | +}; |
| 139 | +const uint8_t _stop_sequence_ssd1608[] = { |
| 140 | + SSD_DEEP_SLEEP, DELAY_FLAG + 1, 0x01, 0x64 // Enter deep sleep |
| 141 | +}; |
| 142 | +const uint8_t _refresh_sequence_ssd1608[] = { |
| 143 | + SSD_MASTER_ACTIVATE, 0, |
| 144 | +}; |
| 145 | + |
| 146 | + |
| 147 | +extern uint16_t vid_setting; // declared in pins.c |
| 148 | +// _set_vid() uses the GPIO29 analog pin (which is connected to a voltage divider) to computer a revision code for the board |
| 149 | +// this allows multiple similar boards to chare the same CP build |
| 150 | +static int _set_vid(void) { |
| 151 | + vid_setting = 9999; |
| 152 | + |
| 153 | + #define DCK01_VID_PIN 29 |
| 154 | + uint16_t value; |
| 155 | + adc_init(); |
| 156 | + adc_gpio_init(DCK01_VID_PIN); |
| 157 | + adc_select_input(DCK01_VID_PIN - ADC_FIRST_PIN_NUMBER); // the VID pin is 29 and the first ADC pin is 26 |
| 158 | + common_hal_mcu_delay_us(100); |
| 159 | + uint32_t accum = 0; |
| 160 | + for (int i = 0; i < 10; i++) { |
| 161 | + accum += adc_read(); |
| 162 | + } |
| 163 | + value = accum / 10; // average the readings |
| 164 | + vid_setting = value; |
| 165 | + /* |
| 166 | + Voltage Divider with 3.3V: (1241 * V) |
| 167 | + 10K/ 15K = 1.98V = 2458 |
| 168 | + 15K/ 10K = 1.32V = 1638 |
| 169 | + 15K/4.7K = 0.79V = 980 |
| 170 | + 15K/ 2K = 1.32V = 482 |
| 171 | + 15K/ 1K = 1.32V = 256 |
| 172 | + Note: extreme values (using 100K or greater) will not create a strong enough current for the ADC to read accurately |
| 173 | + Note: we do not get a usable value when the voltage divider is missing |
| 174 | + */ |
| 175 | + |
| 176 | + // TODO change to min/max to tighten up the ranges (requires sampling of the initial boards) |
| 177 | + if (value > 2800) { |
| 178 | + vid_setting = 9; |
| 179 | + } else if (value > 2000) { |
| 180 | + vid_setting = 5; |
| 181 | + } else if (value > 1200) { |
| 182 | + vid_setting = 4; |
| 183 | + } else if (value > 600) { |
| 184 | + vid_setting = 3; |
| 185 | + } else if (value > 300) { |
| 186 | + vid_setting = 2; |
| 187 | + } else if (value > 150) { |
| 188 | + vid_setting = 1; |
| 189 | + } else { |
| 190 | + vid_setting = 0; |
| 191 | + } |
| 192 | + |
| 193 | + return vid_setting; |
| 194 | +} |
| 195 | + |
| 196 | + |
| 197 | +// Note: board_reset_pin_number() is new to CP9 and allows a board to handle pin resets on a per-pin basis |
| 198 | +// we use it to prevent the DISPLAY_EN pin from automatically changing state |
| 199 | +bool board_reset_pin_number(uint8_t pin_number) { |
| 200 | + static bool _display_pin_inited = false; |
| 201 | + |
| 202 | + if (pin_number == DISPLAY_EN_PIN) { |
| 203 | + // init the pin the first time; do nothing after that |
| 204 | + if (!_display_pin_inited) { |
| 205 | + _display_pin_inited = true; |
| 206 | + // doing this (rather than gpio_init) in this specific order ensures no |
| 207 | + // glitch if pin was already configured as a high output. gpio_init() temporarily |
| 208 | + // configures the pin as an input, so the power enable value would potentially |
| 209 | + // glitch. |
| 210 | + gpio_put(pin_number, 1); |
| 211 | + gpio_set_dir(pin_number, GPIO_OUT); |
| 212 | + hw_write_masked(&padsbank0_hw->io[pin_number], PADS_BANK0_GPIO0_DRIVE_VALUE_12MA << PADS_BANK0_GPIO0_DRIVE_LSB, PADS_BANK0_GPIO0_DRIVE_BITS); |
| 213 | + gpio_set_function(pin_number, GPIO_FUNC_SIO); |
| 214 | + } |
| 215 | + return true; |
| 216 | + } |
| 217 | + return false; |
| 218 | +} |
| 219 | + |
| 220 | +void board_init(void) { |
| 221 | + board_reset_pin_number(DISPLAY_EN_PIN); |
| 222 | + _set_vid(); // sets vid_setting global |
| 223 | + |
| 224 | + fourwire_fourwire_obj_t *bus = &allocate_display_bus()->fourwire_bus; |
| 225 | + busio_spi_obj_t *spi = &bus->inline_bus; |
| 226 | + common_hal_busio_spi_construct(spi, &pin_GPIO14, &pin_GPIO15, NULL, false); |
| 227 | + common_hal_busio_spi_never_reset(spi); |
| 228 | + |
| 229 | + bus->base.type = &fourwire_fourwire_type; |
| 230 | + common_hal_fourwire_fourwire_construct(bus, |
| 231 | + spi, |
| 232 | + &pin_GPIO11, // DEFAULT_SPI_BUS_DC, // EPD_DC Command or data |
| 233 | + &pin_GPIO13, // DEFAULT_SPI_BUS_CS, // EPD_CS Chip select |
| 234 | + &pin_GPIO10, // DEFAULT_SPI_BUS_RESET, // EPD_RST Reset |
| 235 | + 1000000, // Baudrate |
| 236 | + 0, // Polarity |
| 237 | + 0); // Phase |
| 238 | + |
| 239 | + // board_vid is a computed flag to let us know what hardware is active |
| 240 | + // currently, we only know two codes '1' and '2' and these indicate what ePaper display is installed |
| 241 | + |
| 242 | + epaperdisplay_epaperdisplay_obj_t *display = NULL; |
| 243 | + |
| 244 | + // Set up the DisplayIO epaper object |
| 245 | + display = &allocate_display()->epaper_display; |
| 246 | + display->base.type = &epaperdisplay_epaperdisplay_type; |
| 247 | + |
| 248 | + // VID codes: 1 = tricolor ePaper (BWR), 2 = monochrome ePaper (BW), other codes are TBD |
| 249 | + if (vid_setting == 1) { |
| 250 | + common_hal_epaperdisplay_epaperdisplay_construct( |
| 251 | + display, |
| 252 | + bus, |
| 253 | + _start_sequence_ssd1681, sizeof(_start_sequence_ssd1681), |
| 254 | + 1.0, // start up time |
| 255 | + _stop_sequence_ssd1681, sizeof(_stop_sequence_ssd1681), |
| 256 | + WIDTH, // width |
| 257 | + HEIGHT, // height |
| 258 | + WIDTH, // ram_width |
| 259 | + HEIGHT + 0x60, // ram_height RAM is actually only 200 bits high but we use 296 to match the 9 bits |
| 260 | + 0, // colstart |
| 261 | + 0, // rowstart |
| 262 | + 270, // rotation |
| 263 | + SSD_SET_RAMXPOS, // set_column_window_command |
| 264 | + SSD_SET_RAMYPOS, // set_row_window_command |
| 265 | + SSD_SET_RAMXCOUNT, // set_current_column_command |
| 266 | + SSD_SET_RAMYCOUNT, // set_current_row_command |
| 267 | + SSD_WRITE_RAM_BLK, // write_black_ram_command |
| 268 | + false, // black_bits_inverted |
| 269 | + SSD_WRITE_RAM_RED, // write_color_ram_command |
| 270 | + false, // color_bits_inverted |
| 271 | + 0xFF0000, // highlight_color (RED for tri-color display) |
| 272 | + _refresh_sequence_ssd1681, sizeof(_refresh_sequence_ssd1681), // refresh_display_command |
| 273 | + 15.0, // refresh_time |
| 274 | + &pin_GPIO9, // DEFAULT_SPI_BUS_BUSY, // busy_pin |
| 275 | + true, // busy_state |
| 276 | + 20.0, // seconds_per_frame (does not seem the user can change this) |
| 277 | + true, // always_toggle_chip_select |
| 278 | + false, // not grayscale |
| 279 | + false, // not acep |
| 280 | + false, // not two_byte_sequence_length |
| 281 | + true); // address_little_endian |
| 282 | + } else if (vid_setting == 2) { |
| 283 | + common_hal_epaperdisplay_epaperdisplay_construct( |
| 284 | + display, |
| 285 | + bus, |
| 286 | + _start_sequence_ssd1608, sizeof(_start_sequence_ssd1608), |
| 287 | + 1.0, // start up time |
| 288 | + _stop_sequence_ssd1608, sizeof(_stop_sequence_ssd1608), |
| 289 | + WIDTH, // width |
| 290 | + HEIGHT, // height |
| 291 | + WIDTH, // ram_width |
| 292 | + HEIGHT /* + 0x60 */, // ram_height RAM is actually only 200 bits high but we use 296 to match the 9 bits |
| 293 | + 0, // colstart |
| 294 | + 0, // rowstart |
| 295 | + 0, // rotation |
| 296 | + SSD_SET_RAMXPOS, // set_column_window_command |
| 297 | + SSD_SET_RAMYPOS, // set_row_window_command |
| 298 | + SSD_SET_RAMXCOUNT, // set_current_column_command |
| 299 | + SSD_SET_RAMYCOUNT, // set_current_row_command |
| 300 | + SSD_WRITE_RAM_BLK, // write_black_ram_command |
| 301 | + false, // black_bits_inverted |
| 302 | + NO_COMMAND, // write_color_ram_command |
| 303 | + false, // color_bits_inverted |
| 304 | + 0x000000, // highlight_color (RED for tri-color display) |
| 305 | + _refresh_sequence_ssd1608, sizeof(_refresh_sequence_ssd1608), // refresh_display_command |
| 306 | + 1.0, // refresh_time |
| 307 | + &pin_GPIO9, // DEFAULT_SPI_BUS_BUSY, // busy_pin |
| 308 | + true, // busy_state |
| 309 | + 5.0, // seconds_per_frame (does not seem the user can change this) |
| 310 | + true, // always_toggle_chip_select |
| 311 | + false, // not grayscale |
| 312 | + false, // not acep |
| 313 | + false, // not two_byte_sequence_length |
| 314 | + true); // address_little_endian |
| 315 | + } else { |
| 316 | + // what should happen if this firmware is installed on some other board? |
| 317 | + // currently, we mark the display as None |
| 318 | + display->base.type = &mp_type_NoneType; |
| 319 | + } |
| 320 | + |
| 321 | +} |
| 322 | + |
| 323 | +void board_deinit(void) { |
| 324 | + if ((vid_setting == 1) || (vid_setting == 2)) { |
| 325 | + // we initialized an ePaper display so we can de-init it |
| 326 | + epaperdisplay_epaperdisplay_obj_t *display = &displays[0].epaper_display; |
| 327 | + if (display->base.type == &epaperdisplay_epaperdisplay_type) { |
| 328 | + while (common_hal_epaperdisplay_epaperdisplay_get_busy(display)) { |
| 329 | + // RUN_BACKGROUND_TASKS; |
| 330 | + } |
| 331 | + } |
| 332 | + common_hal_displayio_release_displays(); |
| 333 | + } |
| 334 | +} |
| 335 | + |
| 336 | +// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. |
0 commit comments