From 14432a74f2a30c12eaa3fbb20716f6eee07ce6f5 Mon Sep 17 00:00:00 2001 From: "Jean-Philippe (JP) Cornil" Date: Fri, 19 Apr 2024 22:35:34 +0200 Subject: [PATCH 1/3] Add support for SH1106 OLED display driver + associated example/demo code --- examples/board_sh1106/Makefile | 49 +++ examples/board_sh1106/README.md | 68 ++++ examples/board_sh1106/sh1106demo.c | 619 +++++++++++++++++++++++++++++ examples/parts/sh1106_glut.c | 165 ++++++++ examples/parts/sh1106_glut.h | 39 ++ examples/parts/sh1106_virt.c | 438 ++++++++++++++++++++ examples/parts/sh1106_virt.h | 188 +++++++++ 7 files changed, 1566 insertions(+) create mode 100644 examples/board_sh1106/Makefile create mode 100644 examples/board_sh1106/README.md create mode 100644 examples/board_sh1106/sh1106demo.c create mode 100644 examples/parts/sh1106_glut.c create mode 100644 examples/parts/sh1106_glut.h create mode 100644 examples/parts/sh1106_virt.c create mode 100644 examples/parts/sh1106_virt.h diff --git a/examples/board_sh1106/Makefile b/examples/board_sh1106/Makefile new file mode 100644 index 000000000..8ff59311a --- /dev/null +++ b/examples/board_sh1106/Makefile @@ -0,0 +1,49 @@ +# +# Copyright 2008-2011 Michel Pollet +# +# This file is part of simavr. +# +# simavr is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# simavr is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with simavr. If not, see . + +target= sh1106demo +simavr = ../../ + +IPATH = . +IPATH += ../parts +IPATH += ${simavr}/include +IPATH += ${simavr}/simavr/sim + +VPATH = . +VPATH += ../parts + +LDFLAGS += -lpthread + +include ../Makefile.opengl + +all: obj ${target} + +include ${simavr}/Makefile.common + +board = ${OBJ}/${target}.elf + +${board} : ${OBJ}/sh1106_virt.o +${board} : ${OBJ}/sh1106_glut.o +${board} : ${OBJ}/button.o +${board} : ${OBJ}/${target}.o + +${target}: ${board} + @echo $@ done + +clean: clean-${OBJ} + rm -rf *.hex *.a *.axf ${target} *.vcd .*.swo .*.swp .*.swm .*.swn diff --git a/examples/board_sh1106/README.md b/examples/board_sh1106/README.md new file mode 100644 index 000000000..8b1c1bc62 --- /dev/null +++ b/examples/board_sh1106/README.md @@ -0,0 +1,68 @@ +# SH1106 OLED demo (navigation menu for a motor controller) + +See detailed description of the emulated system here https://github.com/jpcornil-git/Cone2Hank + +- Use arrows to navigate menu +- \ to select +- \ to start,pause or stop (long press) motor +- 'v' to start/stop VCD traces +- 'q' or ESC to quit + +**NOTE**: Emulation may be slightly slower than realtime and a keypress has to be long enough to cope with that. + +## Usage +Command line options: +``` +$ sh1106demo.elf --help +Usage: sh1106demo.elf [...] + [--help|-h|-?] Display this usage message and exit + [--list-cores] List all supported AVR cores and exit + [-v] Raise verbosity level + (can be passed more than once) + [--freq|-f ] Sets the frequency for an .hex firmware + [--mcu|-m ] Sets the MCU type for an .hex firmware + [--gdb|-g []] Listen for gdb connection on (default 1234) + [--output|-o ] VCD file to save signal traces (default gtkwave_trace.vcd) + [--start-vcd|-s Start VCD output from reset + [--pc-trace|-p Add PC to VCD traces + [--add-trace|-at ] + Add signal to be included in VCD output + An ELF file (can include debugging syms) +``` +## Examples +### Execute firmware.elf (with no .mmcu section -> -m and -f required) on system +``` +$ sh1106demo.elf -m atmega32u4 -f 16000000 firmware_no_mmcu.elf +``` + +### Start system and wait for gdb to connect, load firmware, ... +``` +$ sh1106demo.elf -m atmega32u4 -f 16000000 -g +``` + +### Execute firmware.elf on system and trace signals in a VCD file +- .mmcu section of the firmware includes something like: + ``` + #include "avr_mcu_section.h" + + extern void *__brkval; + + AVR_MCU (F_CPU, "atmega32u4" ); + AVR_MCU_VOLTAGES(3300, 3300, 3300); + AVR_MCU_VCD_FILE("simavr.vcd", 10000000); + + const struct avr_mmcu_vcd_trace_t _mytrace[] _MMCU_ = { + { AVR_MCU_VCD_SYMBOL("Encoder"), .what = (void*) &PIND, .mask=(1< + Copyright 2011 Michel Pollet + Copyright 2014 Doug Szumski + + This file is part of simavr. + + simavr is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simavr is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simavr. If not, see . + */ + +// System interfaces +#include +#include +#include +#include +#include +#if __APPLE__ +#include +#else +#include +#endif + +// simavr interfaces +#include "sim_avr.h" +#include "sim_time.h" +#include "avr_ioport.h" +#include "avr_adc.h" +#include "sim_elf.h" +#include "sim_gdb.h" +#include "sim_vcd_file.h" + +// Hardware interfaces +#include "button.h" +#include "sh1106_glut.h" + +// Global objects +// simavr +avr_t * avr = NULL; + +elf_firmware_t firmware = {{0}}; +int loglevel = LOG_ERROR; +int gdb = 0; +int gdb_port = 1234; +int trace_pc = 0; +int vcd_enabled = 0; + +pthread_t avr_thread; +int avr_thread_running; + +// Hardware (see main) +sh1106_t sh1106; +button_t start_sw; +button_t button_sw; +uint32_t vx, vy; +avr_irq_t *adcbase_irq; +button_t encoder_pin; +int encoder_value; + +// Graphic +int win_width, win_height; + +// Event queue from graphic to avr threads +enum event_type { + BUTTON_SELECT, + BUTTON_START, + VCD_DUMP, +}; + +struct event_queue_t { + int index_read; // updated by read thread + int index_write; // updated by graphic thread + struct { + enum event_type type; + int value; + } items[8]; +} event_queue = {.index_read=0, .index_write=0}; + +int queue_push(struct event_queue_t *queue, enum event_type type, int value) { + int index_write_next = (queue->index_write + 1) % ARRAY_SIZE(queue->items); + + if (index_write_next == queue->index_read) { + fprintf(stderr, "WARNING: Event queue full !\n"); + return 0; + } + queue->items[queue->index_write].type=type; + queue->items[queue->index_write].value=value; + + queue->index_write = index_write_next; + return 1; +} + +int queue_pop(struct event_queue_t *queue, enum event_type *type, int *value) { + + if (queue->index_read == queue->index_write) { //queue empty + return 0; + } + *type = queue->items[queue->index_read].type; + *value = queue->items[queue->index_read].value; + + queue->index_read = (queue->index_read + 1) % ARRAY_SIZE(queue->items); + return 1; +} + +void vcd_trace_enable(int enable) { + if (avr->vcd) { + enable ? avr_vcd_start(avr->vcd) : avr_vcd_stop(avr->vcd); + printf("VCD trace %s (%s)\n", avr->vcd->filename, enable ? "enabled":"disabled"); + } +} + +static void * +avr_thread_start (void *arg) +{ + int state = cpu_Running; + avr_cycle_count_t lastChange = avr->cycle; + encoder_value = 0; + avr_irq_t vcd_irq_pc; + + // Initialize VCD for PC-only traces + if (!avr->vcd && trace_pc) { + avr->vcd = malloc(sizeof(*avr->vcd)); + avr_vcd_init(avr, + firmware.tracename[0] ? firmware.tracename: "simavr.vcd", + avr->vcd, + 100000 /* usec */ + ); + } + + // Add Program Counter (PC) to vcd file + if (trace_pc) { + avr_vcd_add_signal(avr->vcd, &vcd_irq_pc, 16, "PC"); + } + + vcd_trace_enable(vcd_enabled); + + while (avr_thread_running && (state != cpu_Done) && (state != cpu_Crashed)) + { + // Process events from graphic thread + enum event_type src; + int value; + while (queue_pop(&event_queue, &src, &value)) { + switch (src) { + case BUTTON_SELECT: + avr_raise_irq(button_sw.irq + IRQ_BUTTON_OUT, value); + break; + case BUTTON_START: + avr_raise_irq(start_sw.irq + IRQ_BUTTON_OUT, value); + break; + case VCD_DUMP: + vcd_trace_enable(vcd_enabled); + break; + } + } + + // Emulate optical encoder input + if ( avr_cycles_to_usec(avr, avr->cycle - lastChange) > (1000000 / 32)) { + lastChange = avr->cycle; + encoder_value = !encoder_value; + avr_raise_irq(encoder_pin.irq + IRQ_BUTTON_OUT, encoder_value ? 1:0); + } + + // Trigger IRQ for a PC trace + if (trace_pc && avr->vcd->output) { + avr_raise_irq(&vcd_irq_pc, avr->pc); + } + + // Run one AVR cycle + state = avr_run(avr); + } + + avr_terminate(avr); + + return NULL; +} + +/* Callback for A-D conversion sampling. */ +static void adcTriggerCB(struct avr_irq_t *irq, uint32_t value, void *param) +{ + union { + avr_adc_mux_t request; + uint32_t v; + } u = { .v = value }; + + switch (u.request.src) { + case ADC_IRQ_ADC6: + avr_raise_irq(adcbase_irq + 6, vx); + break; + case ADC_IRQ_ADC7: + avr_raise_irq(adcbase_irq + 7, vy); + break; + default: + fprintf(stderr, "Unexpected ADC_IRQ_OUT_TRIGGER request [0x%04x]\n", u.v); + } +} + +/* Called on a key press */ +void +keyUpCB (unsigned char key, int x, int y) +{ + switch (key) + { + case '\r': + queue_push(&event_queue, BUTTON_SELECT, 1); + break; + case ' ': + queue_push(&event_queue, BUTTON_START, 1); + break; + } +} + +void +keyCB (unsigned char key, int x, int y) +{ + switch (key) + { + case 0x1b: + case 'q': + // Terminate the AVR thread ... + avr_thread_running = 0; + pthread_join(avr_thread, NULL); + // ... and exit + exit(0); + break; + case 'v': + if (avr->vcd) { + vcd_enabled = ! vcd_enabled; + queue_push(&event_queue, VCD_DUMP, vcd_enabled); + } + break; + case '\r': + queue_push(&event_queue, BUTTON_SELECT, 0); + break; + case ' ': + queue_push(&event_queue, BUTTON_START, 0); + break; + default: + break; + } +} + +void +specialkeyUpCB (int key, int x, int y) +{ + switch (key) + { + case GLUT_KEY_UP: + case GLUT_KEY_DOWN: + vx = 1650; + break; + case GLUT_KEY_LEFT: + case GLUT_KEY_RIGHT: + vy = 1650; + break; + default: + break; + } +} + +void +specialkeyCB (int key, int x, int y) +{ + switch (key) + { + case GLUT_KEY_UP: + vx = 0; + break; + case GLUT_KEY_DOWN: + vx = 3300; + break; + case GLUT_KEY_LEFT: + vy = 0; + break; + case GLUT_KEY_RIGHT: + vy = 3300; + break; + default: + break; + } +} + +/* Function called whenever redisplay needed */ +void +displayCB (void) +{ + const uint8_t seg_remap_default = 1 - sh1106_get_flag ( + &sh1106, SH1106_FLAG_SEGMENT_REMAP_0); + const uint8_t seg_comscan_default = 1 - sh1106_get_flag ( + &sh1106, SH1106_FLAG_COM_SCAN_NORMAL); + + glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Set up projection matrix + glMatrixMode (GL_PROJECTION); + // Start with an identity matrix + glLoadIdentity (); + glOrtho (0, win_width, 0, win_height, 0, 10); + // Apply vertical and horizontal display mirroring + glScalef (seg_remap_default ? 1 : -1, seg_comscan_default ? -1 : 1, 1); + glTranslatef (seg_remap_default ? 0 : -win_width, seg_comscan_default ? -win_height : 0, 0); + + // Select modelview matrix + glMatrixMode (GL_MODELVIEW); + glPushMatrix (); + // Start with an identity matrix + glLoadIdentity (); + sh1106_gl_draw (&sh1106); + glPopMatrix (); + glutSwapBuffers (); +} + +// gl timer. if the lcd is dirty, refresh display +void +timerCB (int i) +{ + // restart timer + glutTimerFunc (1000 / 64, timerCB, 0); + glutPostRedisplay (); +} + +int +initGL (int w, int h, float pix_size) +{ + win_width = w * pix_size; + win_height = h * pix_size; + + // Double buffered, RGB disp mode. + glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE); + glutInitWindowSize (win_width, win_height); + glutCreateWindow ("SH1106 132x64 OLED"); + + // Set window's display callback + glutDisplayFunc (displayCB); + // Set window's key callback + glutKeyboardFunc (keyCB); + glutKeyboardUpFunc (keyUpCB); + glutSpecialFunc (specialkeyCB); + glutSpecialUpFunc(specialkeyUpCB); + glutIgnoreKeyRepeat(1); + + glutTimerFunc (1000 / 24, timerCB, 0); + + sh1106_gl_init (pix_size, SH1106_GL_WHITE); + + return 1; +} + +extern avr_kind_t *avr_kind[]; + +static void +list_cores() +{ + printf( "Supported AVR cores:\n"); + for (int i = 0; avr_kind[i]; i++) { + printf(" "); + for (int ti = 0; ti < 4 && avr_kind[i]->names[ti]; ti++) + printf("%s ", avr_kind[i]->names[ti]); + printf("\n"); + } + exit(1); +} + +static void +display_usage( + const char * app) +{ + printf("Usage: %s [...] \n", app); + printf( + " [--help|-h|-?] Display this usage message and exit\n" + " [--list-cores] List all supported AVR cores and exit\n" + " [-v] Raise verbosity level\n" + " (can be passed more than once)\n" + " [--freq|-f ] Sets the frequency for an .hex firmware\n" + " [--mcu|-m ] Sets the MCU type for an .hex firmware\n" + " [--gdb|-g []] Listen for gdb connection on " + "(default 1234)\n" + " [--output|-o ] VCD file to save signal traces\n" + " [--start-vcd|-s Start VCD output from reset\n" + " [--pc-trace|-p Add PC to VCD traces\n" + " [--add-trace|-at ]\n" + " Add signal to be included in VCD output\n" + " An ELF file (can include debugging syms)\n" + "\n"); + exit(1); +} + +void +parse_arguments(int argc, char *argv[]) +{ + if (argc == 1) + display_usage(basename(argv[0])); + + for (int pi = 1; pi < argc; pi++) { + if (!strcmp(argv[pi], "--list-cores")) { + list_cores(); + } else if (!strcmp(argv[pi], "-v")) { + loglevel++; + } else if (!strcmp(argv[pi], "-h") || !strcmp(argv[pi], "--help")) { + display_usage(basename(argv[0])); + } else if (!strcmp(argv[pi], "-f") || !strcmp(argv[pi], "--freq")) { + if (pi < argc-1) { + firmware.frequency = atoi(argv[++pi]); + } else { + display_usage(basename(argv[0])); + } + } else if (!strcmp(argv[pi], "-m") || !strcmp(argv[pi], "--mcu")) { + if (pi < argc-1) { + snprintf(firmware.mmcu, sizeof(firmware.mmcu), "%s", argv[++pi]); + } else { + display_usage(basename(argv[0])); + } + } else if (!strcmp(argv[pi], "-g") || + !strcmp(argv[pi], "--gdb")) { + gdb++; + if (pi < (argc-2) && argv[pi+1][0] != '-' ) + gdb_port = atoi(argv[++pi]); + } else if (!strcmp(argv[pi], "-o") || + !strcmp(argv[pi], "--output")) { + if (pi + 1 >= argc) { + fprintf(stderr, "%s: missing mandatory argument for %s.\n", argv[0], argv[pi]); + exit(1); + } + snprintf(firmware.tracename, sizeof(firmware.tracename), "%s", argv[++pi]); + } else if (!strcmp(argv[pi], "-s") || + !strcmp(argv[pi], "--start-vcd")) { + vcd_enabled = 1; + } else if (!strcmp(argv[pi], "-p") || + !strcmp(argv[pi], "--pc-trace")) { + trace_pc = 1; + } else if (!strcmp(argv[pi], "-at") || + !strcmp(argv[pi], "--add-trace")) { + if (pi + 1 >= argc) { + fprintf(stderr, "%s: missing mandatory argument for %s.\n", argv[0], argv[pi]); + exit(1); + } + ++pi; + struct { + char kind[64]; + uint8_t mask; + uint16_t addr; + char name[64]; + } trace; + const int n_args = sscanf( + argv[pi], + "%63[^=]=%63[^@]@0x%hx/0x%hhx", + &trace.name[0], + &trace.kind[0], + &trace.addr, + &trace.mask + ); + switch (n_args) { + case 4: + break; + case 3: + if (!strcmp(trace.kind, "sram8") || !strcmp(trace.kind, "sram16")) { + break; + } + [[fallthrough]]; + default: + --pi; + fprintf(stderr, "%s: format for %s is name=kind@addr.\n", argv[0], argv[pi]); + exit(1); + } + + if (!strcmp(trace.kind, "portpin")) { + firmware.trace[firmware.tracecount].kind = AVR_MMCU_TAG_VCD_PORTPIN; + } else if (!strcmp(trace.kind, "irq")) { + firmware.trace[firmware.tracecount].kind = AVR_MMCU_TAG_VCD_IRQ; + } else if (!strcmp(trace.kind, "trace")) { + firmware.trace[firmware.tracecount].kind = AVR_MMCU_TAG_VCD_TRACE; + } else if (!strcmp(trace.kind, "sram8")) { + firmware.trace[firmware.tracecount].kind = AVR_MMCU_TAG_VCD_SRAM_8; + } else if (!strcmp(trace.kind, "sram16")) { + firmware.trace[firmware.tracecount].kind = AVR_MMCU_TAG_VCD_SRAM_16; + } else { + fprintf( + stderr, + "%s: unknown trace kind '%s', not one of 'portpin', 'irq', 'trace', 'sram8' or 'sram16'.\n", + argv[0], + trace.kind + ); + exit(1); + } + firmware.trace[firmware.tracecount].mask = trace.mask; + firmware.trace[firmware.tracecount].addr = trace.addr; + strncpy(firmware.trace[firmware.tracecount].name, trace.name, sizeof(firmware.trace[firmware.tracecount].name)); + + printf( + "ARGS: Adding %s trace on address 0x%04x, mask 0x%02x ('%s')\n", + firmware.trace[firmware.tracecount].kind == AVR_MMCU_TAG_VCD_PORTPIN ? "portpin" + : firmware.trace[firmware.tracecount].kind == AVR_MMCU_TAG_VCD_IRQ ? "irq" + : firmware.trace[firmware.tracecount].kind == AVR_MMCU_TAG_VCD_TRACE ? "trace" + : firmware.trace[firmware.tracecount].kind == AVR_MMCU_TAG_VCD_SRAM_8 ? "sram8" + : firmware.trace[firmware.tracecount].kind == AVR_MMCU_TAG_VCD_SRAM_16 ? "sram16" + : "unknown", + firmware.trace[firmware.tracecount].addr, + firmware.trace[firmware.tracecount].mask, + firmware.trace[firmware.tracecount].name + ); + + ++firmware.tracecount; + } else if (argv[pi][0] != '-') { + if (elf_read_firmware(argv[pi], &firmware) == -1) { + fprintf(stderr, "%s: Unable to load firmware from file %s\n", + argv[0], argv[pi]); + exit(1); + } + printf ("%s loaded (f=%d mmcu=%s)\n", argv[pi], (int) firmware.frequency, firmware.mmcu); + } + } +} + +int +main (int argc, char *argv[]) +{ + printf ( + "---------------------------------------------------------\n" + "SH1106 OLED demo (navigation menu for a motor controller)\n" + "- Use arrows to navigate menu\n" + "- to select,\n" + "- to start,pause or stop (long press) motor\n" + "- 'v' to start/stop VCD traces" + "- 'q' or ESC to quit\n\n" + "NOTE: Emulation may be slightly slower than realtime and\n" + " a keypress has to be long enough to cope with that\n" + "----------------------------------------------------------\n" + ); + + parse_arguments(argc, argv); + + avr = avr_make_mcu_by_name(firmware.mmcu); + if (!avr) { + fprintf (stderr, "%s: AVR '%s' not known\n", argv[0], firmware.mmcu); + exit (1); + } + + avr_init (avr); + avr->log = (loglevel > LOG_TRACE ? LOG_TRACE : loglevel); + + avr_load_firmware (avr, &firmware); + if (firmware.flashbase) { + printf("Attempt to load a bootloader at %04x\n", firmware.flashbase); + avr->pc = firmware.flashbase; + } + + avr->gdb_port = gdb_port; + if (gdb) { + avr->state = cpu_Stopped; + avr_gdb_init(avr); + } + + avr_gdb_init(avr); + + // System Hardware Description + /* SH1106 I2C 132x64 OLED Display + */ + sh1106_init (avr, &sh1106, 132, 64); + sh1106_connect_twi (&sh1106, NULL); //TODO: specify SCL/SDA pins in wiring ? + + /* Joystick Navigation + - X & Y analog inputs connected to A1 (ADC6 on 32u4) & A0 (ADC7 on 32u4) respectively + - Push button input connected to D4 (PD.4 on 32u4) + */ + vx = vy = 1650; // 3V3 -> 1650mV = center + adcbase_irq = avr_io_getirq(avr, AVR_IOCTL_ADC_GETIRQ, 0); + avr_irq_register_notify(adcbase_irq + ADC_IRQ_OUT_TRIGGER, adcTriggerCB, NULL); + + button_init(avr, &button_sw, "Button Joystick"); + avr_connect_irq( + button_sw.irq + IRQ_BUTTON_OUT, + avr_io_getirq (avr, AVR_IOCTL_IOPORT_GETIRQ ('D'), 4) + ); + + /* Start button with led + - switch input connected to D11 (PB.7 on 32u4) + - led output connected to D12 (PD.6 on 32u4) + */ + button_init(avr, &start_sw, "Button Start"); + avr_connect_irq( + start_sw.irq + IRQ_BUTTON_OUT, + avr_io_getirq (avr, AVR_IOCTL_IOPORT_GETIRQ ('B'), 7) + ); + + /* Optical detector input + - Detector input connected to D0 (PD.2 on 32u4) + */ + button_init(avr, &encoder_pin, "Encoder output"); + avr_connect_irq( + encoder_pin.irq + IRQ_BUTTON_OUT, + avr_io_getirq (avr, AVR_IOCTL_IOPORT_GETIRQ ('D'), 2) + ); + + /* Motor outputs + - PWM output -> D6 (PD.7 on 32u4) + - Enable outputs -> D5 & D7 (PC.6 & PE.6 on 32u4) + */ + + // Start AVR thread (Graphic subsystem uses the main thread) + avr_thread_running = 1; + pthread_create (&avr_thread, NULL, avr_thread_start, NULL); + + // Initialize & run Graphic/GLUT system + glutInit (&argc, argv); + initGL (sh1106.columns, sh1106.rows, 2.0); + + glutMainLoop (); +} diff --git a/examples/parts/sh1106_glut.c b/examples/parts/sh1106_glut.c new file mode 100644 index 000000000..56d0b5fd9 --- /dev/null +++ b/examples/parts/sh1106_glut.c @@ -0,0 +1,165 @@ +/* + sh1106_glut.c + + Copyright 2014 Doug Szumski + + Based on the hd44780 part: + Copyright Luki + Copyright 2011 Michel Pollet + + This file is part of simavr. + + simavr is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simavr is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simavr. If not, see . + */ + +#include +#include +#include +#include "sh1106_glut.h" + +#if __APPLE__ +#include +#else +#include +#endif + +sh1106_colour_t oled_colour_g; +float sh1106_pix_size_g = 1.0; +float sh1106_pix_gap_g = 0.0; + +// Keep colours in sync with enum in header. +float sh1106_colours[][3] = +{ + { 1.0f, 1.0f, 1.0f }, // White + { 0.5f, 0.9f, 1.0f }, // Blue +}; + +void +sh1106_gl_init (float pix_size, sh1106_colour_t oled_colour) +{ + sh1106_pix_size_g = pix_size; + oled_colour_g = oled_colour; +} + +void +sh1106_gl_set_colour (uint8_t invert, float opacity) +{ + if (invert) + { + glColor4f (sh1106_colours[oled_colour_g][0], + sh1106_colours[oled_colour_g][1], + sh1106_colours[oled_colour_g][2], + opacity); + } else + { + glColor4f (0.0f, 0.0f, 0.0f, 1.0f); + } +} + +float +sh1106_gl_get_pixel_opacity (uint8_t contrast) +{ + // Typically the screen will be clearly visible even at 0 contrast + return contrast / 512.0 + 0.5; +} + +uint8_t +sh1106_gl_reverse_byte (uint8_t byte) +{ + byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4; + byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2; + byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1; + return byte; +} + +void +sh1106_gl_put_pixel_column (uint8_t block_pixel_column, float pixel_opacity, + int invert) +{ + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBegin (GL_QUADS); + + sh1106_gl_set_colour (!invert, pixel_opacity); + + for (int i = 0; i < 8; ++i) + { + if (block_pixel_column & (1 << i)) + { + glVertex2f (sh1106_pix_size_g, sh1106_pix_size_g * (i + 1)); + glVertex2f (0, sh1106_pix_size_g * (i + 1)); + glVertex2f (0, sh1106_pix_size_g * i); + glVertex2f (sh1106_pix_size_g, sh1106_pix_size_g * i); + } + } + glEnd (); +} + +/* + * Controls the mapping between the VRAM and the display. + */ +static uint8_t +sh1106_gl_get_vram_byte (sh1106_t *part, uint8_t page, uint8_t column) +{ + return part->vram[page][column]; +} + +static void +sh1106_gl_draw_pixels (sh1106_t *part, float opacity, uint8_t invert) +{ + for (int p = 0; p < part->pages; p++) + { + glPushMatrix (); + for (int c = 0; c < part->columns; c++) + { + uint8_t vram_byte = sh1106_gl_get_vram_byte (part, p, c); + sh1106_gl_put_pixel_column (vram_byte, opacity, invert); + // Next column + glTranslatef (sh1106_pix_size_g + sh1106_pix_gap_g, 0, 0); + } + glPopMatrix (); + // Next page + glTranslatef (0, + (part->rows / part->pages) * sh1106_pix_size_g + sh1106_pix_gap_g, + 0); + } +} + +void +sh1106_gl_draw (sh1106_t *part) +{ + sh1106_set_flag (part, SH1106_FLAG_DIRTY, 0); + + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Draw background + float opacity = sh1106_gl_get_pixel_opacity (part->contrast_register); + int invert = sh1106_get_flag (part, SH1106_FLAG_DISPLAY_INVERTED); + sh1106_gl_set_colour (invert, opacity); + + glTranslatef (0, 0, 0); + glBegin (GL_QUADS); + glVertex2f (0, part->rows*sh1106_pix_size_g); + glVertex2f (0, 0); + glVertex2f (part->columns*sh1106_pix_size_g, 0); + glVertex2f (part->columns*sh1106_pix_size_g, part->rows*sh1106_pix_size_g); + glEnd (); + + // Draw pixels + if (sh1106_get_flag (part, SH1106_FLAG_DISPLAY_ON)) + { + sh1106_gl_draw_pixels (part, opacity, invert); + } +} diff --git a/examples/parts/sh1106_glut.h b/examples/parts/sh1106_glut.h new file mode 100644 index 000000000..916ab999e --- /dev/null +++ b/examples/parts/sh1106_glut.h @@ -0,0 +1,39 @@ +/* + sh1106_glut.h + + Copyright 2014 Doug Szumski + + This file is part of simavr. + + simavr is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simavr is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simavr. If not, see . + */ + +#ifndef __SH1106_GLUT_H__ +#define __SH1106_GLUT_H__ + +#include "sh1106_virt.h" + +// Keep colours in sync with array +typedef enum +{ + SH1106_GL_WHITE, SH1106_GL_BLUE +} sh1106_colour_t; + +void +sh1106_gl_draw (sh1106_t *part); + +void +sh1106_gl_init (float pix_size, sh1106_colour_t oled_colour); + +#endif diff --git a/examples/parts/sh1106_virt.c b/examples/parts/sh1106_virt.c new file mode 100644 index 000000000..1782fc203 --- /dev/null +++ b/examples/parts/sh1106_virt.c @@ -0,0 +1,438 @@ +/* + sh1106_virt.c + + Copyright 2011 Michel Pollet + Copyright 2014 Doug Szumski + + This file is part of simavr. + + simavr is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simavr is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simavr. If not, see . + */ + +#include +#include +#include +#include "sim_time.h" + +#include "sh1106_virt.h" +#include "avr_spi.h" +#include "avr_twi.h" +#include "avr_ioport.h" + +/* + * Write a byte at the current cursor location and then scroll the cursor. + */ +static void +sh1106_write_data (sh1106_t *part) +{ + part->vram[part->cursor.page][part->cursor.column] = part->spi_data; + //printf ("SH1106: Display @%d,%d = %02x\n", part->cursor.page, part->cursor.column, part->spi_data); + if (++(part->cursor.column) >= SH1106_VIRT_COLUMNS) + { + part->cursor.column = 0; + } + + sh1106_set_flag (part, SH1106_FLAG_DIRTY, 1); +} + +/* + * Called on the first command byte sent. For setting single + * byte commands and initiating multi-byte commands. + */ +void +sh1106_update_command_register (sh1106_t *part) +{ + switch (part->spi_data) + { + case SH1106_VIRT_SET_CONTRAST: + part->command_register = part->spi_data; + //printf ("SH1106: CONTRAST SET COMMAND: 0x%02x\n", part->spi_data); + return; + case SH1106_VIRT_DISP_NORMAL: + sh1106_set_flag (part, SH1106_FLAG_DISPLAY_INVERTED, + 0); + sh1106_set_flag (part, SH1106_FLAG_DIRTY, 1); + //printf ("SH1106: DISPLAY NORMAL\n"); + SH1106_CLEAR_COMMAND_REG(part); + return; + case SH1106_VIRT_DISP_INVERTED: + sh1106_set_flag (part, SH1106_FLAG_DISPLAY_INVERTED, + 1); + sh1106_set_flag (part, SH1106_FLAG_DIRTY, 1); + //printf ("SH1106: DISPLAY INVERTED\n"); + SH1106_CLEAR_COMMAND_REG(part); + return; + case SH1106_VIRT_DISP_SUSPEND: + sh1106_set_flag (part, SH1106_FLAG_DISPLAY_ON, 0); + sh1106_set_flag (part, SH1106_FLAG_DIRTY, 1); + //printf ("SH1106: DISPLAY SUSPENDED\n"); + SH1106_CLEAR_COMMAND_REG(part); + return; + case SH1106_VIRT_DISP_ON: + sh1106_set_flag (part, SH1106_FLAG_DISPLAY_ON, 1); + sh1106_set_flag (part, SH1106_FLAG_DIRTY, 1); + //printf ("SH1106: DISPLAY ON\n"); + SH1106_CLEAR_COMMAND_REG(part); + return; + case SH1106_VIRT_SET_PAGE_START_ADDR + ... SH1106_VIRT_SET_PAGE_START_ADDR + + SH1106_VIRT_PAGES - 1: + part->cursor.page = part->spi_data + - SH1106_VIRT_SET_PAGE_START_ADDR; + //printf ("SH1106: SET PAGE ADDRESS: 0x%02x\n", part->cursor.page); + SH1106_CLEAR_COMMAND_REG(part); + return; + case SH1106_VIRT_SET_COLUMN_LOW_NIBBLE + ... SH1106_VIRT_SET_COLUMN_LOW_NIBBLE + 0xF: + part->spi_data -= SH1106_VIRT_SET_COLUMN_LOW_NIBBLE; + part->cursor.column = (part->cursor.column & 0xF0) | (part->spi_data & 0xF); + //printf ("SH1106: SET COLUMN LOW NIBBLE: 0x%02x\n",part->spi_data); + SH1106_CLEAR_COMMAND_REG(part); + return; + case SH1106_VIRT_SET_COLUMN_HIGH_NIBBLE + ... SH1106_VIRT_SET_COLUMN_HIGH_NIBBLE + 0xF: + part->spi_data -= SH1106_VIRT_SET_COLUMN_HIGH_NIBBLE; + part->cursor.column = (part->cursor.column & 0xF) | ((part->spi_data & 0xF) << 4); + //printf ("SH1106: SET COLUMN HIGH NIBBLE: 0x%02x\n", part->spi_data); + SH1106_CLEAR_COMMAND_REG(part); + return; + case SH1106_VIRT_SET_SEG_REMAP_0: + sh1106_set_flag (part, SH1106_FLAG_SEGMENT_REMAP_0, + 1); + //printf ("SH1106: SET COLUMN ADDRESS 0 TO OLED SEG0 to \n"); + SH1106_CLEAR_COMMAND_REG(part); + return; + case SH1106_VIRT_SET_SEG_REMAP_131: + sh1106_set_flag (part, SH1106_FLAG_SEGMENT_REMAP_0, + 0); + //printf ("SH1106: SET COLUMN ADDRESS 131 TO OLED SEG0 to \n"); + SH1106_CLEAR_COMMAND_REG(part); + return; + case SH1106_VIRT_SET_COM_SCAN_NORMAL: + sh1106_set_flag (part, SH1106_FLAG_COM_SCAN_NORMAL, + 1); + //printf ("SH1106: SET COM OUTPUT SCAN DIRECTION NORMAL \n"); + SH1106_CLEAR_COMMAND_REG(part); + return; + case SH1106_VIRT_SET_COM_SCAN_INVERTED: + sh1106_set_flag (part, SH1106_FLAG_COM_SCAN_NORMAL, + 0); + //printf ("SH1106: SET COM OUTPUT SCAN DIRECTION REMAPPED \n"); + SH1106_CLEAR_COMMAND_REG(part); + return; + case SH1106_VIRT_SET_LINE: + //printf ("SH1106: SET DISPLAY STARt LINE to 0x%02x\n", part->spi_data & 0x3f); + return; + case SH1106_VIRT_SET_RATIO_OSC: + case SH1106_VIRT_MULTIPLEX: + case SH1106_VIRT_SET_OFFSET: + case SH1106_VIRT_SET_PADS: + case SH1106_VIRT_SET_CHARGE: + case SH1106_VIRT_SET_VCOM: + part->command_register = part->spi_data; + //printf ("SH1106: SET COMMAND 0x%02x \n", part->spi_data); + return; + case SH1106_VIRT_RESUME_TO_RAM_CONTENT: + SH1106_CLEAR_COMMAND_REG(part); + return; + default: + printf ("SH1106: WARNING: unknown/not implemented command %x\n", part->spi_data); + // Unknown command + return; + } +} + +/* + * Multi-byte command setting + */ +void +sh1106_update_setting (sh1106_t *part) +{ + switch (part->command_register) + { + case SH1106_VIRT_SET_CONTRAST: + part->contrast_register = part->spi_data; + sh1106_set_flag (part, SH1106_FLAG_DIRTY, 1); + SH1106_CLEAR_COMMAND_REG(part); + //printf ("SH1106: CONTRAST SET: 0x%02x\n", part->contrast_register); + return; + case SH1106_VIRT_SET_RATIO_OSC: + case SH1106_VIRT_MULTIPLEX: + case SH1106_VIRT_SET_OFFSET: + case SH1106_VIRT_SET_PADS: + case SH1106_VIRT_SET_CHARGE: + case SH1106_VIRT_SET_VCOM: + SH1106_CLEAR_COMMAND_REG(part); + //printf ("SH1106: SET DATA 0x%02x \n", part->spi_data); + return; + default: + // Unknown command + printf("SH1106: error: unknown update command %x\n",part->command_register); + return; + } +} + +/* + * Determines whether a new command has been sent, or + * whether we are in the process of setting a multi- + * byte command. + */ +static void +sh1106_write_command (sh1106_t *part) +{ + if (!part->command_register) + { + // Single byte or start of multi-byte command + sh1106_update_command_register (part); + } else + { + // Multi-byte command setting + sh1106_update_setting (part); + } +} + +/* + * Called when a TWI byte is sent + */ +static void +sh1106_twi_hook (struct avr_irq_t * irq, uint32_t value, void * param) +{ + sh1106_t * p = (sh1106_t*) param; + avr_twi_msg_irq_t v; + v.u.v = value; + + if (v.u.twi.msg & TWI_COND_STOP) + p->twi_selected = 0; + + if (v.u.twi.msg & TWI_COND_START) { + p->twi_selected = 0; + p->twi_index = 0; + if (((v.u.twi.addr>>1) & SH1106_I2C_ADDRESS_MASK) == SH1106_I2C_ADDRESS) { + p->twi_selected = v.u.twi.addr; + avr_raise_irq(p->irq + IRQ_SH1106_TWI_IN, + avr_twi_irq_msg(TWI_COND_ACK, p->twi_selected, 1)); + } + } + + if (p->twi_selected) { + if (v.u.twi.msg & TWI_COND_WRITE) { + avr_raise_irq(p->irq + IRQ_SH1106_TWI_IN, + avr_twi_irq_msg(TWI_COND_ACK, p->twi_selected, 1)); + + if (p->twi_index == 0) { // control byte + if ((v.u.twi.data & (~(1<<6))) != 0) { + printf("%s COND_WRITE %x\n", __FUNCTION__, v.u.twi.data); + printf("%s ALERT: unhandled Co bit\n", __FUNCTION__); + abort(); + } + p->di_pin = v.u.twi.data ? SH1106_VIRT_DATA : SH1106_VIRT_INSTRUCTION; + } else { + p->spi_data = v.u.twi.data; + + switch (p->di_pin) + { + case SH1106_VIRT_DATA: + sh1106_write_data (p); + break; + case SH1106_VIRT_INSTRUCTION: + sh1106_write_command (p); + break; + default: + // Invalid value + break; + } + } + p->twi_index++; + } + + // SH1106 doesn't support read on serial interfaces + // just return 0 + if (v.u.twi.msg & TWI_COND_READ) { + uint8_t data = 0; + avr_raise_irq(p->irq + IRQ_SH1106_TWI_IN, + avr_twi_irq_msg(TWI_COND_READ, p->twi_selected, data)); + p->twi_index++; + printf("%s ALERT: SH1106 doesn't support read on I2C interface\n", __FUNCTION__); + } + } +} + +/* + * Called when a SPI byte is sent + */ +static void +sh1106_spi_in_hook (struct avr_irq_t * irq, uint32_t value, void * param) +{ + sh1106_t * part = (sh1106_t*) param; + + // Chip select should be pulled low to enable + if (part->cs_pin) + return; + + part->spi_data = value & 0xFF; + + switch (part->di_pin) + { + case SH1106_VIRT_DATA: + sh1106_write_data (part); + break; + case SH1106_VIRT_INSTRUCTION: + sh1106_write_command (part); + break; + default: + // Invalid value + break; + } +} + +/* + * Called when chip select changes + */ +static void +sh1106_cs_hook (struct avr_irq_t * irq, uint32_t value, void * param) +{ + sh1106_t * p = (sh1106_t*) param; + p->cs_pin = value & 0xFF; + //printf ("SH1106: CHIP SELECT: 0x%02x\n", value); + +} + +/* + * Called when data/instruction changes + */ +static void +sh1106_di_hook (struct avr_irq_t * irq, uint32_t value, void * param) +{ + sh1106_t * part = (sh1106_t*) param; + part->di_pin = value & 0xFF; + //printf ("SH1106: DATA / INSTRUCTION: 0x%08x\n", value); +} + +/* + * Called when a RESET signal is sent + */ +static void +sh1106_reset_hook (struct avr_irq_t * irq, uint32_t value, void * param) +{ + //printf ("SH1106: RESET\n"); + sh1106_t * part = (sh1106_t*) param; + if (irq->value && !value) + { + // Falling edge + memset (part->vram, 0, part->rows * part->pages); + part->cursor.column = 0x80; + part->cursor.page = 0; + part->flags = 0; + part->command_register = 0x00; + part->contrast_register = 0x80; + sh1106_set_flag (part, SH1106_FLAG_COM_SCAN_NORMAL, 1); + sh1106_set_flag (part, SH1106_FLAG_SEGMENT_REMAP_0, 1); + } + +} + +static const char *irq_names[IRQ_SH1106_COUNT] = { + [IRQ_SH1106_SPI_BYTE_IN] = "=sh1106.SDIN", + [IRQ_SH1106_RESET ] = "avr, AVR_IOCTL_SPI_GETIRQ(0), + SPI_IRQ_OUTPUT), + part->irq + IRQ_SH1106_SPI_BYTE_IN); + + avr_connect_irq ( + avr_io_getirq (part->avr, + AVR_IOCTL_IOPORT_GETIRQ( + wiring->chip_select.port), + wiring->chip_select.pin), + part->irq + IRQ_SH1106_ENABLE); + + avr_connect_irq ( + avr_io_getirq (part->avr, + AVR_IOCTL_IOPORT_GETIRQ( + wiring->data_instruction.port), + wiring->data_instruction.pin), + part->irq + IRQ_SH1106_DATA_INSTRUCTION); + + avr_connect_irq ( + avr_io_getirq (part->avr, + AVR_IOCTL_IOPORT_GETIRQ( + wiring->reset.port), + wiring->reset.pin), + part->irq + IRQ_SH1106_RESET); +} + +void +sh1106_connect_twi (sh1106_t * part, sh1106_wiring_t * wiring) +{ + avr_connect_irq ( + avr_io_getirq (part->avr, AVR_IOCTL_TWI_GETIRQ(0), TWI_IRQ_OUTPUT), + part->irq + IRQ_SH1106_TWI_OUT); + + avr_connect_irq ( + part->irq + IRQ_SH1106_TWI_IN, + avr_io_getirq (part->avr, AVR_IOCTL_TWI_GETIRQ(0), TWI_IRQ_INPUT)); + + if (wiring) + { + avr_connect_irq ( + avr_io_getirq (part->avr, + AVR_IOCTL_IOPORT_GETIRQ( + wiring->reset.port), + wiring->reset.pin), + part->irq + IRQ_SH1106_RESET); + } +} + +void +sh1106_init (struct avr_t *avr, struct sh1106_t * part, int width, int height) +{ + if (!avr || !part) + return; + + memset (part, 0, sizeof(*part)); + part->avr = avr; + part->columns = width; + part->rows = height; + part->pages = height / 8; // 8 pixels per page + part->write_cursor_end.page = SH1106_VIRT_PAGES-1; + part->write_cursor_end.column = SH1106_VIRT_COLUMNS-1; + + AVR_LOG(avr, LOG_OUTPUT, "SH1106: size %dx%d (flags=0x%04x)\n", part->columns, part->rows, part->flags); + /* + * Register callbacks on all our IRQs + */ + part->irq = avr_alloc_irq (&avr->irq_pool, 0, IRQ_SH1106_COUNT, + irq_names); + + avr_irq_register_notify (part->irq + IRQ_SH1106_SPI_BYTE_IN, + sh1106_spi_in_hook, part); + avr_irq_register_notify (part->irq + IRQ_SH1106_RESET, + sh1106_reset_hook, part); + avr_irq_register_notify (part->irq + IRQ_SH1106_ENABLE, + sh1106_cs_hook, part); + avr_irq_register_notify (part->irq + IRQ_SH1106_DATA_INSTRUCTION, + sh1106_di_hook, part); + avr_irq_register_notify (part->irq + IRQ_SH1106_TWI_OUT, + sh1106_twi_hook, part); +} diff --git a/examples/parts/sh1106_virt.h b/examples/parts/sh1106_virt.h new file mode 100644 index 000000000..1d27b68de --- /dev/null +++ b/examples/parts/sh1106_virt.h @@ -0,0 +1,188 @@ +/* + sh1106_virt.h + + Copyright 2011 Michel Pollet + Copyright 2014 Doug Szumski + + This file is part of simavr. + + simavr is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simavr is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simavr. If not, see . + */ + +/* + * This "Part" simulates the SH1106 OLED display driver. + * + * The following functions are currently supported: + * + * > Display reset + * > Display on / suspend + * > Setting of the contrast + * > Inversion of the display + * > Rotation of the display + * > Writing to the VRAM using horizontal addressing mode + * + */ + +#ifndef __SH1106_VIRT_H__ +#define __SH1106_VIRT_H__ + +#include "sim_irq.h" + +#define SH1106_VIRT_DATA 1 +#define SH1106_VIRT_INSTRUCTION 0 + +#define SH1106_I2C_ADDRESS 0x3C +#define SH1106_I2C_ADDRESS_MASK 0xfe + +#define SH1106_VIRT_PAGES 8 +#define SH1106_VIRT_COLUMNS 132 + +/* Address setting commands */ +#define SH1106_VIRT_SET_COLUMN_LOW_NIBBLE 0x00 +#define SH1106_VIRT_SET_COLUMN_HIGH_NIBBLE 0x10 +#define SH1106_VIRT_SET_PAGE_START_ADDR 0xB0 + +/* Charge pump command table */ +#define SH1106_VIRT_CHARGE_PUMP_VOLTAGE 0x30 + +/* Hardware config. commands */ +#define SH1106_VIRT_SET_LINE 0x40 +#define SH1106_VIRT_SET_SEG_REMAP_0 0xA0 +#define SH1106_VIRT_SET_SEG_REMAP_131 0xA1 +#define SH1106_VIRT_SET_COM_SCAN_NORMAL 0xC0 +#define SH1106_VIRT_SET_COM_SCAN_INVERTED 0xC8 +#define SH1106_VIRT_SET_OFFSET 0xD3 +#define SH1106_VIRT_SET_PADS 0xDA + +/* Fundamental commands. */ +#define SH1106_VIRT_SET_CONTRAST 0x81 +#define SH1106_VIRT_RESUME_TO_RAM_CONTENT 0xA4 +#define SH1106_VIRT_IGNORE_RAM_CONTENT 0xA5 +#define SH1106_VIRT_DISP_NORMAL 0xA6 +#define SH1106_VIRT_DISP_INVERTED 0xA7 +#define SH1106_VIRT_MULTIPLEX 0xA8 +#define SH1106_VIRT_DISP_SUSPEND 0xAE +#define SH1106_VIRT_DISP_ON 0xAF + +/* DCDC command table */ +#define SH1106_VIRT_DCDC_CONTROL_MODE 0xAD +#define SH1106_VIRT_DCDC_OFF 0x8A +#define SH1106_VIRT_DCDC_ON 0x8B + +/* Timing & driving scheme setting commands */ +#define SH1106_VIRT_SET_RATIO_OSC 0xD5 +#define SH1106_VIRT_SET_CHARGE 0xD9 +#define SH1106_VIRT_SET_VCOM 0xDB +#define SH1106_VIRT_NOP 0xE3 + +/* Read-Modify-Write commands */ +#define SH1106_VIRT_READ_MODIFY_WRITE_START 0xE0 +#define SH1106_VIRT_READ_MODIFY_WRITE_END 0xEE + +#define SH1106_CLEAR_COMMAND_REG(part) part->command_register = 0x00 + +enum +{ + //IRQ_SH1106_ALL = 0, + IRQ_SH1106_SPI_BYTE_IN, + IRQ_SH1106_ENABLE, + IRQ_SH1106_RESET, + IRQ_SH1106_DATA_INSTRUCTION, + //IRQ_SH1106_INPUT_COUNT, + IRQ_SH1106_ADDR, // << For VCD + IRQ_SH1106_TWI_IN, + IRQ_SH1106_TWI_OUT, + IRQ_SH1106_COUNT +//TODO: Add IRQs for VCD: Internal state etc. +}; + +enum +{ + SH1106_FLAG_DISPLAY_INVERTED = 0, + SH1106_FLAG_DISPLAY_ON, + SH1106_FLAG_SEGMENT_REMAP_0, + SH1106_FLAG_COM_SCAN_NORMAL, + + /* + * Internal flags, not SH1106 + */ + SH1106_FLAG_BUSY, // 1: Busy between instruction, 0: ready + SH1106_FLAG_REENTRANT, // 1: Do not update pins + SH1106_FLAG_DIRTY, // 1: Needs redisplay... +}; + +/* + * Cursor position in VRAM + */ +struct sh1106_virt_cursor_t +{ + uint8_t page; + uint8_t column; +}; + +typedef struct sh1106_t +{ + avr_irq_t * irq; + struct avr_t * avr; + uint8_t columns, rows, pages; + struct sh1106_virt_cursor_t cursor, write_cursor_start, write_cursor_end; + uint8_t vram[SH1106_VIRT_PAGES][SH1106_VIRT_COLUMNS]; + uint16_t flags; + uint8_t command_register; + uint8_t contrast_register; + uint8_t cs_pin; + uint8_t di_pin; + uint8_t spi_data; + + uint8_t twi_selected; + uint8_t twi_index; +} sh1106_t; + +typedef struct sh1106_pin_t +{ + char port; + uint8_t pin; +} sh1106_pin_t; + +typedef struct sh1106_wiring_t +{ + sh1106_pin_t chip_select; + sh1106_pin_t data_instruction; + sh1106_pin_t reset; +} sh1106_wiring_t; + +void +sh1106_init (struct avr_t *avr, struct sh1106_t * b, int width, int height); + +static inline int +sh1106_set_flag (sh1106_t *b, uint16_t bit, int val) +{ + int old = b->flags & (1 << bit); + b->flags = (b->flags & ~(1 << bit)) | (val ? (1 << bit) : 0); + return old != 0; +} + +static inline int +sh1106_get_flag (sh1106_t *b, uint16_t bit) +{ + return (b->flags & (1 << bit)) != 0; +} + +void +sh1106_connect (sh1106_t * part, sh1106_wiring_t * wiring); + +void +sh1106_connect_twi (sh1106_t * part, sh1106_wiring_t * wiring); + +#endif From 769b1933c513f962c50039e0b036638325a9f019 Mon Sep 17 00:00:00 2001 From: "Jean-Philippe (JP) Cornil" Date: Sun, 6 Oct 2024 23:01:46 +0200 Subject: [PATCH 2/3] Add component name to one global variable --- examples/parts/sh1106_glut.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/parts/sh1106_glut.c b/examples/parts/sh1106_glut.c index 56d0b5fd9..c62fac647 100644 --- a/examples/parts/sh1106_glut.c +++ b/examples/parts/sh1106_glut.c @@ -34,7 +34,7 @@ #include #endif -sh1106_colour_t oled_colour_g; +sh1106_colour_t sh1106_oled_colour_g; float sh1106_pix_size_g = 1.0; float sh1106_pix_gap_g = 0.0; @@ -49,7 +49,7 @@ void sh1106_gl_init (float pix_size, sh1106_colour_t oled_colour) { sh1106_pix_size_g = pix_size; - oled_colour_g = oled_colour; + sh1106_oled_colour_g = oled_colour; } void @@ -57,9 +57,9 @@ sh1106_gl_set_colour (uint8_t invert, float opacity) { if (invert) { - glColor4f (sh1106_colours[oled_colour_g][0], - sh1106_colours[oled_colour_g][1], - sh1106_colours[oled_colour_g][2], + glColor4f (sh1106_colours[sh1106_oled_colour_g][0], + sh1106_colours[sh1106_oled_colour_g][1], + sh1106_colours[sh1106_oled_colour_g][2], opacity); } else { From 69e0f7fe42ec95e242d3d04d2a1ef5077cda6274 Mon Sep 17 00:00:00 2001 From: "Jean-Philippe (JP) Cornil" Date: Sun, 3 Nov 2024 22:48:47 +0100 Subject: [PATCH 3/3] Add firmware example + some formating cleanup --- examples/board_sh1106/README | 65 ++++++++++++++++ examples/board_sh1106/firmware_no_mmcu.elf | Bin 0 -> 51864 bytes examples/parts/sh1106_virt.c | 86 ++++++++++----------- 3 files changed, 108 insertions(+), 43 deletions(-) create mode 100644 examples/board_sh1106/README create mode 100755 examples/board_sh1106/firmware_no_mmcu.elf diff --git a/examples/board_sh1106/README b/examples/board_sh1106/README new file mode 100644 index 000000000..dfae12e75 --- /dev/null +++ b/examples/board_sh1106/README @@ -0,0 +1,65 @@ +SH1106 OLED demo (navigation menu for a motor controller) +========================================================= +See detailed description of the emulated system here https://github.com/jpcornil-git/Cone2Hank + +- Use arrows to navigate menu +- to select +- to start,pause or stop (long press) motor +- 'v' to start/stop VCD traces +- 'q' or ESC to quit + +NOTE: Emulation may be slightly slower than realtime and a keypress has to be long enough to cope with that. + +Usage +===== +Command line options: + +$ sh1106demo.elf --help +Usage: sh1106demo.elf [...] + [--help|-h|-?] Display this usage message and exit + [--list-cores] List all supported AVR cores and exit + [-v] Raise verbosity level + (can be passed more than once) + [--freq|-f ] Sets the frequency for an .hex firmware + [--mcu|-m ] Sets the MCU type for an .hex firmware + [--gdb|-g []] Listen for gdb connection on (default 1234) + [--output|-o ] VCD file to save signal traces (default gtkwave_trace.vcd) + [--start-vcd|-s Start VCD output from reset + [--pc-trace|-p Add PC to VCD traces + [--add-trace|-at ] + Add signal to be included in VCD output + An ELF file (can include debugging syms) + +Examples +======== +Execute firmware.elf (with no .mmcu section -> -m and -f required) on system + +$ sh1106demo.elf -m atmega32u4 -f 16000000 firmware_no_mmcu.elf + +Start system and wait for gdb to connect, load firmware, ... + +$ sh1106demo.elf -m atmega32u4 -f 16000000 -g + +Execute firmware.elf on system and trace signals in a VCD file + +1. .mmcu section of the firmware includes something like: + + #include "avr_mcu_section.h" + + extern void *__brkval; + + AVR_MCU (F_CPU, "atmega32u4" ); + AVR_MCU_VOLTAGES(3300, 3300, 3300); + AVR_MCU_VCD_FILE("simavr.vcd", 10000000); + + const struct avr_mmcu_vcd_trace_t _mytrace[] _MMCU_ = { + { AVR_MCU_VCD_SYMBOL("Encoder"), .what = (void*) &PIND, .mask=(1<1yVe${wQ4uOeP8RXT9uiE0AZ0#79wgz zv}(1zt+usRtvYtKy{)5Nu6A`$TXCrj5(E|Hea=h*F>QbM-p}X#ql1|_^F8N0=Q-y* zXM4_b#)2`E#v+6W_vJ%OCfsiW0Mwl#2w!*(AXJ2eNFtPkLh!e_Bf#_P+2Rj}^MN0Z zKW7ehkb>VS0PxSx<%j*%^8oW9g&({UKYI8pYzur9Hp_qUo7P=n`wV`67IsvRKgaLH z{yF|vtUTL}uWe5hI?>dUnK! zg;@(n#Ee)sA|`71hT%~$Q`b+8iFu*mg%~1gcoflo<)bU@L{uVn{dlEaJARm7aPPir z`u5fp`1<<8tyjh~!>+u`Vn$Y+V74F(NZFGrj97@vXyD2oCdcLa_BM00w+P#}-)JGM7aNtd7kB*}oV8F4C| zDlS7eM3*r$Toyi4B3&(&4Qs@07rq;2l+@bbsZt`ml3L`o?5LdNE$tQ z$dJ*aM<>B8sbI)cPdyby$TrA*qF#M<>*@lTRJxiNyS}4n#_1$cVy2gz_aS9DQk3! z9#5_a8#-mml<7kuwdq5rJoVHxf*7+dlkgd{ek6g$tkbQB3Rur4^-AK`JvC(v&hNUX z)&w}`x*7xq)SCa&{ zzP+{MAJ?gd2*L)pA)1g=ZyeEtBn`Mr5HDlF=@%X!4>#Eq0{-^lzxFBc2j$UDfYi4W zAXV7aPPB)F@F6_BwgJ`x-tezIJlrX53lEPBi3@>$ao`(=qwx@*00@KlH3ZYcF5zky zUO{?+01g&O5N%Q?rV@$iIk86zkiu7s;ED`)lXrp(*uc@^a2mJ`w80H9U@`<43U7xX zf+v9O_~P*ePY4R6+m-kNp&2r|ZQ{g<12*{h`1$#4fWMhjCS=Y8kKt0?22~Fg27Rh` zUD;I~({Dn*m`N+pib>juk3X)}j$b`q`@xK7#y&a%s*%_LqrirK_!>Prcl2mo&cx9= z-ROxqI!FvjI1aS3>6z6$Pw>gb-R?I}9Ol2=ySMVLkJdoHF>m zOA%DMi5~{)JeE?Tx|pzMMRSBcj(Otl2GKi-~Aw{j_?yo7h?*LL+}?$ z7r)CC1TZ7(U?@aN_=ONr9SmrF2%jh-1d|mmm5h*@2^rLacX-j|W8$c}d(B8l|Xl0I(n zfp^BQJ2BBEK2X|8x>>=*C+>GC(U*BuP8d7i=j#gbyQ=`C#587>62il9w*)YDC(z>VDu^DewRc%8Pc5y%X7W^`E?zu3M$&I4H4=2NMnbqkmS`U z*Ska(EYl%6pa_f31iYpBr$rD+ZP7VZwsZ95Cgs^*DWYXM94}=g!xF{ec+Nx@ZC?iq*4sPxafmIbfhjs1vgUWBSoZ66#^Iuh)xB>d64)9ex<_Ln@I= z!@#G(2k9~s29X3pJ`$93GIX}2h?aaPuP z!AhJV&U|&~mqS|(Wrk>fOtj!n6oJ-Q${C_vgM?Z zn!;r!!*vny6)H+3D~LQU(7e^HQ6PIlx`-)+p0+Nx^9`GZZUh-fH-w+YbS6 zbB_h*#LQBxiZM|o2|XKMr#_&5r`zaIhqSQS$ffq=wA>yvK4^OEL9{4nee73AZvV3| zucApo&&M7@3xZx2!W^NF{spyH(G$_R^8ZLJ`gwULwa~w$g;PC^Z)zRfyQn?ED$U~G1)8PfXeHflZZKakv*r`#x6C!>N^^mEsX5o2 zWzH~b%rbi$Qc>z-LGSuMxX-E&!kYnOB>l6bBq6jh<4~(j&ym)vgo?gJ=rv|8Dk9o1 z+=*+xBw;9@v}yEjI_pmo9oAa6LmMO!99kYdw*1LT0aQsA-VdH9*NAP%_i? zgh@lLu(wMYGlV8Do4RWt9R4He(-JB)h{0)oAt{9Wza$}+ui*Ekgo3xzlF}PiPG-mQ z7-zxKIN$IS!t&umHc?z^CYQ|}M@w@HvgCH98JB>}E~ZLCN+k)=eCZ_;_>$R!ZJ+p= zAEMNT3XOK7GAIX4lalOS0)tBr5tt ziI)CIGBR?q|GQ3Hw)SOpP55cKAI*@~MuLQpW(bL@DpywbF(pSkH#Xoh=)}#|Pb8%E zV<=&$0r)-K?$4`B(RK7Px*Xix<8;>xzSH@055kxSsE`h}E%xtOMTI(9WkX!UDfFoFA*HB**f8L`$#kxENZQOBR z%CsWF7;3!1nkdgQ%+J<>)HM=Vc2QL;o3yF$Hxgu0W~Z_%wK9GjQ?KEC{kPyMW{tkh zIqO?^a@n`DKBm7+sO729U|YNY4dDEyvOVr5;JfH|=^OO#3ApXaog^npHwkZdS+R%y zziIB}BQy_Fy^$kQtpusNL5GIQm^1cY5`MB)o)BwNX8$F*mBrkexi*2vM4HU5$^v<% zOAeFPrJ_py`Z* zQHinI7|NNF<$Or}ZM2;%O{Eg56RkEXluk*Z9;Ke3a;dzF%dZtf$p_n7Q+zZBQZA|+ zQwZo?K7;YqM_y_yE|BwCOog#B!^Ce+rJ>GnjhVXU@D0XRQ>Cdo;cTOsf;<>7<*TN< zCX%YI!0(+@HbU&7{B=_s_-(2%CCK>${cuQ#S3%lM38Vps2<301{d&wZ=6Ey{stgT1 z!ix2$^y>R%b~7c$q*Fd&|2$R9^8^={nDfKD$mbtZmOD?d9^5(KXAksyj}=q6!baGS zvVS&L$ln<-Iy||g8sIA8;jO89Yz7da#RA>OR&$bWV z^0{Rk?q7m!LjrEH<&m*|p}c}G*Wk8GTJW5pp6GSohkM6&AlAO?=0ua>$#S*yn{F`;;UkBGhjE2bTZ2&gUnb=6*qvt` zEL~HF416~52lP2IX3)A?-vhqO zc#=++(wwOeX-$_hmj2Q+mH|@EGEmA}1_2c{P|*MtEl^4OBb7n4FQ&4ZXDn)nsezbU zh?yp(IJTpe;?cL24YBNyhFEH)GW(ykA6(02NUH1_cOAWe-i$gSVQVh>=PdSCrmJR$q@Rz{) zz*~XC75J|$&`%Mf=%(lcZjGYwAUUt_nE$a(%6N1oXTT_XtXUX$$$2D<#b>N0g0Yqm zSbj)?oFQUmfRz$;0#*W2jl)#{R!*D)%uL|8X27Ze+eCaNVh|T&TL9Zkd?a8vF2-H~ zY%6gXFh+4u&Lg)LLzZs#|kCus;!8ly~oO~Xt-p?@K=I~}+q@SVV9YB<$Xo93Iz$HV;& z?=DxF9jA_cC}*HC#+YgxVI(7I{~iAC_|I!P-610i+f;>;iZb>!4l_2e-XWP=G5YTM zde+^#M1JtBmxMh1B3p!75hb|RflCjr$7COJ?KjzoTigY|0nU?LzlIMZ&cgbS;Tmq< zjJU`>@cV#2l?UUNlgY<O$>uU)-lAH5sTv!$*cX)>Fz~y1#-UjkIxIM1qVA5sBj^nQsbIfAQ}6p%eZ=8(zp&S}d`x{umDij>6*c>Vw!$wo@heoM zbj0nc-M{NK<>9z%=(A-%_rZO3IYJDf{K9^%rJN>{%dIvSt#<@kNB$>Th$#^$k@l^$ zDdBq;rJVw$eg6}shq&tVE^98(EbE)BGAv~AI(O!>Ic2z%dQ*d`eC<$vrnWCn7n8;P zi{p!B_OxPEv9(waF3o?TAHl291rFtVt+roID35=2VsoXba;;&$VIulk9aXF}3`QTM z{Dh`grP9OcXB<7dLf!LSPexVYIZ(7il*@A`y9w{2RGv9GDJ3W6J?>xI@`9Gfh9<5n zURWFv5$UJkKRS60dfmIlSBtH}`d~AZNo!d{F^jNt;5E8}KX7ty#gU3>FbASSrOb(I z$GXa#-`FrF>Qj6Qn?Rp}wG;=rAGhHV;3{>OYBO9nq^0ys*@}8k&d3^4sVSS7n$5jX z_IO!?skP!_P*dz^dYisNkF`AWskPLMpp~&}%jTCAf^J}ISy|cDin|rtsRNX*UC%Uf zC&HF8Z-p&pVuab9h8kiN^)mBKYfYw#%@uMxoSZOuJ-h{V@XFpn1FCFvx?>Fe6#ad-Lr`bBpd65jJE#{ufF53!Ty_`r zG7m7{Md?*9n$xNV&_m18t7d^~v^kTT)HRBq*j2_CFmqDR+&|XOLz{*=iIin_6^00i`SSw zi;(d-YzlJE;kx$jCvSu zYl=4(>HZq6?85wV!E{t@uoKF~Y zjhOlnZl}=Zaesm7UMsm*A~Qvsu>T_CdgEynY)cGIF=0Amxx^Zg&MU>`#mCtN+W7*9 zZD>fJBBg6uDh+moz~iQjDx<7kUq!hJJOIpr^(oZKuE0<%bcmPqKA{|5R}<<~?Dg ze%Ke&!id;Qb!47oL*mty7lS$9A3SOFMf<4)#yvO{tHe z4>jjC+fY*UP=S(Pc?(e5jow8+uJKtJQks3S6#bw%)*E%w_ikf ze?nYuecJV$?{iMECW{fS%i{>IzIs+QupKEiVIz!}a+~JAn4{r6EO8fWIJAQihEzkF{33nmX(HYkv_+pHIycZ>Os9?*l5_AT9LXC z&DX5fD7wb_KF7a8ZKUvg1tU0Ik6oa^RFZON9M0WEMN^D2H!kaY7Vl{cGxRll%l7^s=Qwy@Mn%gd zy_Nm@^pESAp5}YQsiLLQHe?3vP@;A|ujgd;?GU0T{RSGB^L*B;%7dB%u_;zmRTR0Y zi3Fc{$~>!Fg7+5i9z%#l3VILHDgiymFOqytFX-p;IfL-LdY$J;YGVOuAgKztms}>T z?-NG2e*oHC7PQecXSUY+lz?uMq2DLY&_`hX{Q*%5EA5e+&@o!n~?f{ zECIfhWmt*>Jq~x0)pI9U19!|zKXJkO1KO@U5O)LJpzc%GBJcT!IL%xlvlhm2+)~C< zIb60iK&rE*0N*12D7g`K?KZ<&8TV7-&f*>zw_y~HxZ{UfD3H^DaGIk+e_%}-@0`l) zcx`v*mS=3dFL@Z^BZvI<*X)1OQ{mNQKabVqIz(QsFq}gX!r1mX@~?8hI(??FB3=Ni zzmnn>lt>MvMyj!10{cxC{GI{7Y3g&Ry6Q{wq;Q$@Lv;%pv+Lq|A20X}YPFy^nn&q^Qa*52{?Ib;zAFnL^S|9^lGA%iT>Mt>0`& zHk2D$P@f8IZD#G0wRzl&7YiH2ofK70nmkjUBYzsS9D{+93fgQ4=zy^vY;?!Y?m6_l zutpBH-3|X)b%5WLavt*aO3M6{E2=7RxZR3FG2chkv*(Z% z%I{J7ar!yB%JHAlo@~2nylYekXY^RZRvY?(-9sJ+`t7|~gEz{9pJ!eCoRH^t_&*M- z=7Z=Q>hwDgQflMQq0LoWtsAY?j;+FTiv#Z^Zxh_FIL-*q8b`nXxsS$UB<>rDGwvOh zg@!gE|AgZJk4t`mpijqTIkJOyTs99|3&`wwoR~v#FMTnronbeo!UpAdLVlIahrRtd z*A%u@5p%D#xwM;QV5w2dmr%H04v>-_Evd7mm^rbf;NJg7*w6m~eqw9GJ#^9URS@?a z^ojqL6btU}!F?Wfq{|F=C9*$a6YO1{W`AZm`bX$fe~{EiYMne+Kly$Z2|M!8s|!{J zW|uD>?KVY@cV30vLmWm$X}OwQv-L;G=LqlD{s4OgKT1s9s{=cC3*05R4m7HzNJR%o z;}cB$b?7HP4*sskmtBAMoex^}b3&gWYS}-77Tv_of<1;;yZmvVLL95yRth4$JFruo zDpjRV<3(L>e?zmP#t6k1?*F&8bMHQ7&TPShdQ~#dE&A9K!B>k4o&4gW>o;TCD zp~XqXcolWcc+V)eUv<@Iz44B*)VKu@td(69mt7m$ zZaiSbtt-?~WOA6!a14h-t>wxiU-8f6dsdmyGFa0tuhGK#t(l#rt>&jW^R+!2XK3d) z>M5(ht7scBPrHWCOWWJhMIWZW#U?5T#ub|ECViyU|GB1A>u_s5JF@%aK)uOo60~Ja zk6WL!{=&u^`y27x7>9fRHmY)z6_tI%5)RWXxl`-@ffxpw?x=+`A@=r<(# z_E4~h3AU97VkyS$+89{i2^&cwWtRG`}W0Xf^yo6Y<&?a~Etw+HSj} zcgO7Rz8h0Pj%|A?_H3kTz_sc1$~`#!&N;GYIj;lV!84#Wz%3`(HZQtG*nxMoAz^Kg zM_*OFmYV>rM_g$tD1O6^0?&rO@ssQ9cS{rF7>lFwt%szqgH-Ej(0wNr3#pNw?VHGPxYtf zLmm>6L!EP|mnelzrhRy)?t--(#4z-!&_1>P~#8-73Z;Ls4Ee|i9I z>vxwuk=S;y-biNu6ql&>9E!EXSfce&`kU+)*h!p4tpZ8@Gv05&uJ;|G<=z$USYl1d z6j&@Y1bkWebK%Xx_KuxEw{?ZAxe|JTjqodjUn%?yoJGi4oK-3G2o;C69g+!rZUa6m z{)Ep3)XUT#$9Bx2yVMtQD;_HK&RxfHgvY;D)M%~*%?!+9Qd%Q=@=H^(`~8%jRiA3l z`cf^OG9$F<;YDKO1`2k!FM=f35tSQA-~n5bY}`QRlh)5AHFPBnbI0RKbDTNKPPVsz zJmNUmSBwMhjyP1_uWV=TJS9_O&9G)!bD@te0GX??zGXdOZLqcrvspZXE^=T?XPk3v zJz{9@#ms}2ItDB``! z#VWA#)PkL79~jk!!z!!7;7K9G`jLdNekj3xvuGzp!YUTqU!L?|!@7OFtuf`!oRgwG zrj%?3yOQH*;R^*>*d7z_Rg5?(T4Y{ho4EF{UhtQT8=E1G194R7 zc$T_Ig4B*Fk+KAgmwJai5}3*`-(4a`(_^I@@JqDrX&_4LQzk z()I+)Wh?;%TMZ?B|FmJ5oPV3We7%vQxHPWnQcpTQVL6ZOowdO21r+K?p;gu!1a4=Q zxy9NwEgP?p)@x8N*N93JY5iI92iKO zI}S>J7PlN`a__J-ATP3p^!d(8)2533V98Hf31qe+wB721_FE>bITN1BET8F&}~GW<&cGBWThZ;zT&uoXF4REN08P}B_(xRI6NIQpj>RKj@nFX|Isd|G@P`;B(B2^){VC)+kGPr9kGth>0&A%U0cN^A0MngtT8y>H zKj)U&+8#h!9|U{?t)@L7#PT3u*lD)}xNU&t&id1$)QRJEr_@oQ38zJwyUq%`+|KT& zzdqdv(s%sy%xh~osYf4!`xSS8LV}f;8LY&%gO%8RuoBbKhb0MIf--gRv}V!1Ph~v} z{6i=!gt9^?>oHhoK5P>xh9s?kTESA#3YLP_lM;7-f%}QhzM*%0g3?DA@xS)s$-A#( zVNQqpJJ318Y&lh8qKexk%w~$t-fSpQnhYfrwxmIhG$)K;xupgZMdg_ccwI+_#xliP z9jqlmry|VdUIE>T+JJj8JgT@k^X$nwIn-;XwRG2C{SVw>&Pe(&QBpt-k~=3dd~*qC z5%(}r&19O~xrKS(iQD`grUY7;jrptovoqBah*iRn={YS}hZ@e11LV$E7@d>sf4Hfn z|DD?yHQw0IkOSpTBU1wRDyC#W_3dEWBi+uhB}3)Ta%M<#$>6?xwzFhVC{GUVd)ojl z2Q;&mWXV*fWQg4PGD8lH=E)&){t%2w$r2K@y2}|3TokuaNm>D+j}kohYi?T>X?T<& z9~sJXPI8#sxrQkjCg-=Z_~Zem7yv!4T*A@+mhkjBi5%oz4(Ah;4DLnb;CIzW&XB|9 zu*X>f_nmCXBe<1wbbu?R00~&0=U^QL*7lkK#}xW9SVN2vQExkoHPZ-e2np$?$Z5?S zxJ5pZ6fEhU1RklYK(iASxfgs4oiR_{GS;8}%Wxzu0WJ1(pe-e>zeozN;TbBPb@q1Y zj}l)(3rGR_KXVqDSz(0+ZS13FgJSc+TK(VE}XQVqow zft5Bs9e_3Zi~ZTuvzi;h_tlLt`24_O-oL#|$N7OM4IYOU`%?+{BtgSNmdp4A!SBFv zGkg7#lf!y|lABiOC--xO2D@Rm;gCUnJ&j8+S47ffNun)ntR){NF zxZ}8@3&#~**xqq1@c1*x$?vUj}Xb1u8N9@Kn+}IUjeFNquuS-fhsOsx5 zRYRRfwNRi6J3DovzQRpyh6l9}>zhF1pORu&C5oJtVfqHQ#83u{VW;6;!wjwr{BfH5 zA^cksLr3_ZP@API@ZHtXp4eoIsA{uBbmSvU$WN#v80;`T`Fw7@)prq`QSQ2drx18_vAM44>X3k+KGMp`9im+W#^*N+WFMZ**pKSGh}z<4$JG*>$cYouUEZpe7$8~S-p*;-y+`%d$Z)S*ams) z`x4H2NW#$vpf7n_QV6R|+?IVf~%#4zhU=QaBq=5KxtJ42=5YXQ0Jy#dE1`?X9TS4g_)$EHq>`-3$}hGX$S1I zZIUKM4)velEaI-DHmB~n`Ywmy)MP1WOGuX-tV1Oo60!D5v3fb@P`4pkPwJ_Q7S3)c zG;D+?E=AK-$RXGYb}0=jErx!{URGGPliR00kn&E^j-oirJKRF`Qp?6mr__4xy!ryH zrBAu^QFu*#9!(Ww>S;$8;Th%t?UaFlp2{u1KcUd7t(J|J;>#AUtFSK+?kN1#!bJ-1 z2*;MICbf;LR!0k=q8wPaHd*j{(t%58ir{|A(Y@*e%bS*NjyO2o>CHu}pvT4XaSj0= z)?jG68(poi4HeakHD~-y%y|VoanAMEH`QVJu@@B?deQwdL8 z3z7N=ed(`T#2P3H137lbj~3!bLB6q0shh)Fx5Qht63(KLENEB;tqYg-kO0nnNWz=n zmvmbHokI>mzqAQXr^ubp!!8)}#@RPchMxtC!=D*RMYy2XlRMWk;#rjmJU(S2_Hg*z z3D%^#_q-A-PB`81pofZ0PIrmpTTS4eZ0?s7I7Yy0sDFZUU$8}!wLo?ur`L0|VG z^pW_vLskwwclj~6&niC#cKNQ9NWl+N#rp(m$%W$-^oY+w5A5ED+95m6oU2Nq&p}Uy z^>$qyNuU+%D`*7&YA3M`JptBmSzrnGf?y4Yd((4hlORnFTpIiu*lTS;`nT}z8mt4I zXP9U`*8TNK9hf?yE}L6$bvbZ&H@pPST;Nj$?owWQ4eQZXMU=ZFY!aMS(ZIZ13#S+l z!JPbk$+sZ$!>wv-7a_GUN2E}P5e_H9mdg12mbe>>EuZn40;gWXh#EO@t*{OCejy|4 zO*Hz2+^o5&t5W55F)r)`faLzj7P-KuAOXa&zZu+~=Y zEQ49j|FT;JvS1MUpa?uGUc}=3&Wpx-epnXqo*#a0>8K6Rz6v>g#StOYPb8d9bMFB@ z4x@9SQRw}V-dI*})|n@~W4SU4r?fgHF1ADBioi$iymuO(oZ2C4IgM%j%7Ttx*tK-m zPD{s*UMl--her2XVZ9v+Jqgx*;{L=v9=O|5w7`E9Xbf>a!X2UYb?Po$qZ(aOsU@6N zE&sI(XXIXkKJX-1j@o{kuMW0NOG)}I4Xm%Sfum1TT2a1rxwR6?Wm?K^WQXUcDRZ`& z(cEo#N9c2~^*jr`_bb*|#6>j1UOU$Bd7s%aTlPsv%U+3*yL96gwl>`z1zJ2uKp&RLpXc8y@Bm^AwX5PBltj!_VPzysl5EQ7*n+U z!ZF-Dx%(j#jLUICt@m(X3B~f#ThKp?F6SS#%yRp@n=3n7QPUQGhNNp@G}|GSf)=L7 z5nNMd>kb$XYo#T1+y1b2cZbGp_ci-=^FJl~&2LIRfpqsN2>LBa2)!S)O5!;waZdJ_ ztd_$Xp6xJ)>RK9J+S}MK5^HuoEc&MCa#3^9ZkVndgdX!f3ARWmrz0G2TAYsnsUTr= zH^b;2Y@4HC%IC;K9wnVzRk$^#SB)K z-di^0MhCgUJqPZ8u{{VuY+*+lCmUxOR~aKvAC%Z_V8GabsR3`JTJ#v`-12>w`^uc} zuxZ9L?O}dj!d~9U4XZkj;;P)zaUMMl92y}{81LGFSF^2XoH5&!Z(3pc7;Mmg3upzeCPL@UyEP1)07?X=g}}a89=%11>%2VhJnUwyvn;W6 zapdEfv@m+)0iSvbj{=1r_4{uKiQ8TmN0<;J)PYMk6Y6m<(A)>>&S2ZU0NndbW%r;( z*H>`tM`F5iek}99rcBb+AZMGU`1Jl|;k55HXeoHS$2JVm{(+ZJ;3UQpCd^~HK+ZuP zteO*^Jd^Z0AQSIO+E6(3wJY@bdd#uPyjP+%?~@SpKY{Z%J0$Qt%tXrRF3?AAg}n(A z$XzoN4m26dU^qE91WwKkmGYKh(pEH+I_ug6>I(g)oJSp}Dey=ko+x#X6yg{mYBBNI zxM*i1>Z%Xdlk{N7;SjLE8VVLz!=$IWeIIZw;9kIix_?C+y7;4CowQLZbEcfc^Kh|T zUq9U_v{2l$=hTl;Pv)kmdtU0#WmKvwC;U2@o4#}W&aZ_&HPm5*bAJk_^km`Fw7kN) z74{q+?hWf}@6;|gjiE5%psTpM_jd`*Ll0?JKn%A?iCrh&`QXe~EYb zUln>t@e3X}XX#aUcGmAyS7cPEK_@Mg&$Y?i=&n=XJuoW>TB4sv_pfkMnJwj-V>e^x#Lb|D8g7$VX*s~lAml z2*-RL&Uzy}%ZvxB0BjL}zXpJ3n^M7gqLH1a{TxE$6FJeY5g0k}3L(UaN21QSslY$R zbhh_P*G_Vw&ut35XyGSRl!%Ca^+bz zqAlzn_g2Nwp8Ky4@2$H1-Q$jXZ*~5)6(Gx0)C}kw+$mB~XJJ1N+igTU@HsQQd)to_ zcX|g#<#~mS5JE;e9uD;gtbH3(@Jehf_pSP@Yc65r#;bEurWB1XYAO1yNIc!t-9aj8 z;WU1jQ1-Yq#C_$zn#qYf7IM4z?Bse;#rlfUiY*oR^wj)D73|i=CroH`rCl0QDt?D% za_Pv@l0<`zPALz5rAJ9nbu3+n;;{VhV z#M%2_)uyM^OSgj|w96{T|56C2hLV^FGlM0-v)tZ@81gKMPwf?6H-jBxu&pngTgJ14 z#TRG6>SeaDauM;@(O8gZ(OTb6u=PoR^1#+T*iPTQ!+#QJ$f88aI{4KAZXt}+2VlqW zbr`wcaE)A8moTpa`luD#Aa!%xMhV3OipLZ`UHqbc zrXG*aQ@zILJ!lrRDLhIS{tM3lwnmfxI_Db8bdE_axOd@U4#dG6=pyK_y}9@fMqsR| z!ny-IQpQ-CQG`BH9|G%fab5@dGnhp@25W`LqL9*1kl~)fH!W1KLY!QcVx8>3^Pr5X zZXL6rKhBH9xgS2K|6Xa+mUyPe5e`Oj9!7H8Z+)Q@GU0S$AHB9Hv*^j9grWgOV~U0FXsOc0llotu*74$F9`3s)MxaTkU+aE2$h#U&qh%Wl~^pz*z+fW{5 z-SCCm-UiPdL!8)3h@)$yJiT421*=B9gQj&#uv39g@h8Fw+w;wMuLRpcnHuj2 z-xYX(LKz*EL-|l$C~P~%@%Uct3~}VccPJE~1HrnG^ePs)_vU4G!dfE@v2K?-Sxi5N z$KTr-!tX^tqRr?Sx`0~INwxvqVa*VRgs@++570$rQygWbt?^)wHq?-0ILE5!SEO2c zn=~Og6Kv>%k(O2>t#zAJWqk$gycO^sgfRU~C$>-0(IF@sw&WAL4-DiWCJ!+$MmG6x zcdEN*1mf{%HU9>B0;D@Ja-jcs{t4)&vVocosNr;LSf1m!t%6k_;ke4f=_?1;^=yYy z+YhDoJHq7_;;41H$0Rp@n6{kHXLz8-0X19>GW#`jI`V>lee*=>S!x@~?3&|ypyevM zXSimVKu@PXR(=FA(+zoYn zg?y^;2;iCa(A!!^PIHK5JM6&#ufWB2;Nk=J8^ebNTyvQAsmSjkEu8xjs+tk_-zRu9vz03(Op1K5)OO0T@jYz z|NopZmN0b2(gksraKu}>!cIHp@Q>W(S%DiXaAO5-tX;v9_D=N!)^dzG*1Ea64|z~# z@2$Z|+!2haj$v)u-OYD4E05!^Sox|Q_j))9Td?it6gXpk2HQINw@J=C;Zy*UZP(eg z_MY~PtgNiuEcjB5owP5r=i3XiDzj?fJ=oSQbr;)w%3!{ zYcF` zLTb21k0BQFv;*ww-hnY>C)n0eaCVZ8eY?XYvt#Nxb^ZeN-I0g6Am;@NuZnT-KPZNvY7#=6$_!? zt$|h_uk0T;I&NRw-ll@w%}&I8(UN@Tfu`>n@#{QC!6R5q9by{xLFn(J`4t$jbr;St2&A8mOX~efpMIHI6$^E>gL`lcO6w&Uu zBeU1JzW8%APUPhXl=+PALJ0-lj4Ns3gxro)&$As6@`Cdot101Yrf%X7YpVMk%GuA? z@$Rn%b>ojfIfpX`v^12(V9f!r=*G1-7fz9t^m#vbD1SKPOBXlq*uQVV^7C-JsJF=( z$31Vn8nhpGG*wrR_OaS;1LtDxG={dacb!L-q0YSa?hIaI;#;w4O{;;t0b^uhfDM12Aj(hf zC%%P6bny%K^MyCDPGdFan_y#zJKCpaD$n=(J%gKY!;iVCJgO9>Dv5u4m?+EOdPbDI zOZLt76)oftt#h;89q#KE^LT~rphm8i*a$@^*dRQ~A64p{C&2nK556nt&KsOLv~unK z%#LefTDjM8>k5X}r8G-ysLed>#x(@#hgruy9I5e@q&yI^I09&$QId`gYHRYliBNSNBhj_^HQIGfvGvJ_Z*Pqhug6o zN?Me)ch#YZSmJOCUe;upqtm)qeyAt9GYx076#_FLS7idBgYo=!FHuH^q zNy0=P_crbpjN8iI7A*hd{~`ZZ>@~S3TOK5Pwt*VZV|)fB=-+lT7q{t}j5O#SR2{v* z!(m1H>vphl!F?>&BQ((MFgN{9qXFBxEU>Xt0CoZF{jgPLJDiNaV0jspqj@$z<_C2H z#K_S+K=rm=bT4`!J%*k_?{32A($ojYfnGyDKrem)M$&dOEGl7Cy#O;6tPyRs;jf$i zgb=gXnhAO3;QL(nU=Dz_frP3K-rj^c#MTiYo*L-<11Nm;FFDpfpUH(lZ0+@S@7M~R$*N> zAHK>ksLh92+ECDdS3ipMNl=@ETlFY_QNT*N{n8zSxE`Mk^*Xq<|ASQK$rSv}v*pcG zRC4D$_7K85GPoq3vE2skEJ>LR!4UH@`+uht2`TB(cOLQk*^F0zzV{7Cp;dI&wws251ZyF@$Ut(I}bnZe1pJ~8zSIl!$YWqJj)DAdV z^KcK0^VJX5le-|FPo|$ze-TxlGAd;d>>$+JbgiF<>zo4~_%i8j5~eBj!G$xO0Jl>%m+S9$**Sa5TqQR{yCh1D`md0olU7}_#AYY_=|bBk=`SxZ-gI{ z9%#v8ajrVFhmbQ1`%Ox{?OP!i#=%1y6H@yYe66k=gc`>?O9t-cTZFy0$>6;$-$Ls1Y+tvk>a<)CxV~+@%?^MQ&3a8y-g&jEE}ipX@V8c4;d>p*hf1=u zq#lKI2e-}@I5J~P2jGThM}p)SY^zy=&0t%_7N!=hmOGcTw>|3AVAF>)O*ttm*(Xz` zMd=~uOW0Od$d}o&ii>(~0;X4$*=KSnsIuvqVn6qtE z*2LlO0STD-Vbs0qjMB(8*HIZo-Ip_Q4ax=z&Us&e{zg2lq;C&r@Hq_OWCpCBhCA`O z0~T68UIB#|gcBB3HE?qL5-Xfl(O@lfZPO3zU9_)Hk~XJNl}0tz+m>3F6fAL#%x`5K z;6|Kzmm7X&u5xvpDQSk)P5Gqw!6AJ@AkG*KI^eLl_l z%WXWD9jh6)O!s`I^&L)m=JS@@>{v|}yie>O#ZP!X?Y{2GOsmhCy)D<+ce&ldnFKsf zz%vrUA?&6Iv*G}?pKYq^L?-sbUsx%Ov&U_Z{qMhv^hNg{13UND<+gq$Lrcb&b*J`GhpE%bv$36Veuvz&+S*zC{}&K-gzMTVg>PoN z+!I1_1D8bZ?9#6IG^`=)v#?8H=fZA>eHx|=-WyR7_EeZM>{jU7(C(qB(Ky0?8~U-X zn&=mV7&oHS-QSITBNE3h=(e)kOOeY3_s`w_-ED8=rpPzHBVWt-hS2@9toGCoVO0C~ zaX;$%?^cDpN}MDq90{%)ehUJE{C)f<`-l2#{4tUd85X%D^3$-u$kvFf5mS~C6J{-% zO#}rHQ&!GaMMbM#oHu)kYWVCWFV0>ug6OqU)oUfbC-qv{*A=2$_lLtgwQ9wZl`4Ea zsv@3PwRpzt6{@9kR4-zf=+$dwuU@@o%_1f*n>~9Lkm-vty;YF#ic7$D=|(? zS*97i^ra=l;{rBw>5>;$EL}uQ&i}`3;idv)&iuKnR^-1pf9VndnngT0d*$pG#k*=$ z_7q~25Qb1`RK%03mModSWG<1NziK5=|3{iSoftQ2)G$@jxF@FcRSj16S8MU-@d@~z zJDhHTU*^+K!W}LWei*>tPRADn!e7G|>OB9&#k1$;_fKCnK=6uzyAR=nuft zxP6Enz_J*B?VtKT9_ser0sfHLj!TT++Trg9CH2Sr?F@6r&x88$_`d~Q=|9oM?Y{~9 z2X#<~|Iq|$)<<}k3e}IDK9Cy%$t1WT`uI!3;o;jIZtW7nPX$kch=(8Yl@J6)`1chM z90-s+0zUE#Vh5KCNd`nNrszWY0TS?tXPCZ#1nNAINq{KEcp{Gj5;V~hc><8&EKlS# zAj+pakyC(#JnM;k4oK(>Pvi?g!sZJI&L!mHOF+6T^$ar=knn$ahRFn^>&u>Dbbv&V zo=65Dkw#BsJRnhJp2#>rqG?ZLEFj$+p2!$Lx^MGDMgtPF%M)1wNbDP)$Us1PyzPmM z0wnHzPXx?o3DpTt1Wap*o}YRm7C_>^5)fDU*Z@iR#uM2DNaFXN$Ywx#UGPM<0@C|e zPoxTvKGy`qB^%2DNrICkaJi5nfb?zgL`XpTJ@7;*K$3k0`E|w81CpW;5EsvdfTV_b zA|-&RqdbvPKr}r(k#az^iJnLiAZdLC#Ff@kK+?6I$RI%a5AsBE02%OzCo%<)ftjAj zQ-BN_=ZVY*Wbosj$O1ryO!h<`1!U-SPh>bC!(Q-276S6fY)|A7K!z{$MCJkV=yFel z0c6B#Pvj&Z83mpQm{1eCB2NSit%=MEPvm1jM%p})0OB4+^^cfb?*8j$gaJdqWEOgQd|3jlW9)1F8oAdi3J zi6j8>#CcC736QK`Jdr+tWMA?`dIOSE?}?}ZdGe+wk_yP=CQl>c^{DJT|AKw0C`6I|ASm~4g>ORtY;Yb$PV#byeD!5 zkmvh&BI^KoA=MKBlX@a|fPlDKj9bhm?wf`&3e)k z$p&QhR8OP;kU7tLBDsLfogpADex3(p-fYh>xSh^l=!xKVx?s5{G6#@_t38pqfGjHT zMDhVyZ1hAX1G1#l6L}7hrInt@V}LBH@l&e6Zr&?mp<`CCIGVfOHbrUK-PTYi8ujS`@JX91jx&rCvpprbyqx* zMnKlz^h9_-HZ*%84S*Ef_e5?2LJBjHj;8xNKokms$w>!v10X%>>Iq#3$be!zp=$sY zqIgf}DnLe*=Bjpy`V!%wZ& zbo`q;bEam|{2BNmu1hre|5$;Mv^lGm%v`CSOlamUT|8T}VD^%Q^OvmDytH)1!j;SN zXU@jqQXyLE$`|wJ!umwBD1XN6MVeW&XUxxEGB|bhkU_bF254}A8LQ?mnkA5hC<9%o z5wLhsWlmP5tJ4P&h!k1fAh5a)SjEc=yoBiB6C8glRxJXn6QN(mY~t0(-ud()r$hFxEH}};+5~;metz= ztN#X8F`IZ1{TA9Pf!V~WiN7tY=>n_&@Dtg@T9Usl zt78RLM}Q&{v-#%Vnbl_mR^JC!ai1FVckWl02&_&7JtbCgVKvs@Rsi$@tK*@|!ffK% z)!&d+6pDHUhd_JsNhBn}fq^5S@+F;4DMAxKY3SRNn9xPz3-U1lLJbRiByd8P3^kMpr1FK?i?H_*o$Nb&~ydISBufn;wW z#T!WV2Grhw#v9Oj18Lqsx;N0@8yMgX4D<#Dc>{yJfg#?&P;X$EH}HrzFx(q>)EgM# z4PE`i$%GJj>5tblFX`^ry!U!$f-J=7 zYF04TBBHRhD-qX1RLP`Ur7N37%R*L^!fc8HQ43dM`Gszg{hj;KefxC}vgPpK zzEs|OzjMw#_uO;OJ@>wkev=LoW`cxFknrvxAsZy*f`ojKFdHOn3lipngzZ7Xl|jOe zAmOSYVP}x=o*?1sAi)e0b_EHAAfXr}l!AnEkYEJ~l^|h0NLUCG>>y!xkgz96s0InO zAfX;4I6*=qNVq0QaDxOdNbrLMJZ)x>L)mTmaKWzmc(4*}Tf50E=`@`v{;7_;r(IXE0znXJm zwC~{j(WNzG`dv%X#@2*rQ67HgjJ|N_{cUoeK}qiyN!wA-X>r zzL!T!?>r&Kj}{@aHT|$=WxsNHVejhZO1k?PK5iwGhXOfQ z#HQ%MwVEw@&f#$_Ec*Sb_9@+S4F7Ju@*+RT_zSH_ zc=;CnUnT$KcGJUWEcZ%yemscRUz>K_A2oe`G)8b@nwHgYof?}<Sv;%s&-3sx>OO>!&UA{Q7U>3DW%OdR4kr1L|h2OQdSokMXpg>#{V z4&ww+4kq%0C~k=21{DN{_PjXgC~k=2bQlMiiNo~i_$&;;o*D$17~hX!4{Na>hqvO~ z@Jw6)2=OYTqUNZmIZ8Fhv(bb&BymXNen>tZog_0LOnYt&MClxoR0m|BH_8%_rhXZt zei@^F!45c1I8GQl;3VNBVeEj%36B#VXZg9n@YqaB$!hgRk1&`fZ$ES8v_-Htsv zFGdG(iVCL^=n~pT6e&g>OceBXHaUSV@9@-2E*_7iST-KdXHzVm+L}S=Orypmh0{c4 zY6hzK!4QW*mZ8YOZji)t`4~SWQ-(Z3AeEv@aTw3Oo6SN~9{aKQy4KKh9~17#s)$rL?D76Y&|G=f>oL z({f{E^z7su5qoZZ`#3o)-Y9R0gt5e-?cvZqehf4IY6?5luP`2(YFn7C(GXG)!{Z8V z3o|^p(zY;vi@i+)L2bk<4{y6Y=pbyW{P)C^U0zLvvo+nK2 zd;S3Omq_`Wfcx=wytptAu{(gz1*Y<(?;+p|fG?ByG2p9#CnbIs__M%KiOIoF0PmFe z3>f|m)KB(P{paE2>l%I|a1jo#Nckz?5xk$Cl9=A!+YelncnSDD=(I7eFU07LyQjfF zSMqNLei8VS64RS>x8m*mti+!Kz8vMxm*u|>e7=T{0#g_%{%?SPi7-QxV1J0MLrDJw z`c(VqrL(6sKD|qpz}x)Gvc5s!?`!-R@B`p0|D}MBfPbpgUjgpl(C&|A;B$e?@GJGl zKLcNnAiYcKe+KxC2)1`g{2cIuT7QSo@eT0HlD`i4G2m0AzVm_mF`lWv`|!^I#y600 zCi-g(_+bs_fsdZa*x6FwH1PdeeOCeh2K)yl|3ko=``hvNN#G$3-vt~2R`neQ9?t82PA$6aQ+;|?v^+U{37s1R7n0`0zMB(f$ArGJMc!}w@Ca|;7QA!CKh z;B(*5#@_|L30T?tbO86C)X9H6@IlCLlKLir4+E?Ic7Xp)!?yw-1*Y*q?WK1Oo&(+? z+xG zV5Z@I8r#5|n5geu;A5~);kN?gtM;%(@P~od_P57N0(cFu;_n20m4=srPtowlfPeL7 zyqdy9e>@C)D}pnR0HFSP7Wiiv3=5Kvd$26~ws!xG0Pg};@pL6H{&rxi{wDAf8on9$ z`@m2SnK1h-@DDWnFTjsNpBitE0Mq(llazl7_<8W(DDC+*@L2^|Td!G{|-{|ms6X#D$u zKLI}VFO`1?_*+{1eh>H|#M~L2A7W1d-v=!8h1hR^)vq5Zf3N*B#_omwlel~zI};f1 zG0XK;AA18Z{-z7+67|tq=1%An_J&v*7=JUR#lH&p5@0ocEZ_w6p-Ujs$F2ij1Qz!6 zv5x}p*YH08AJp)df#0s_`zG*!R^Kt;+aZrBjq6{#p0O*zzgXJ49@x;D_*-vEEo zc%=TKwWivrJ$DjD4iQ_3eHr*g#Dl^|0`iXo-;EpKicf3JuW0y%fc!}a+_NY-@-4(( z3;aKt{F^DTR|cSdYWkmY zy>;g+tpD&Utp7^^{SN|v*HR7&v2O#@97o$GjISRC@H4>ccZvME0sQ9zeEKD^EBk*v z_$MRrsNXcN4~5v7z`Fj!SUcadow2`1eWY&__`6N2o#w0%iv;8^2fpKk;vYXT3rx2U zk-{12-vfN}RpR=Q^!dQY-XrE)!Z!fx_1_4*xU=1!j|0C2lUV}#Nd6EoJwBlPMEVZ{ z9}(4a`@RlLzipw7?EQCOdLPs$Gh51>D& zJ}N&7d_UspNr}gR4{pYq2?>|@X<%hPjh9(qwQa(Q4XoGS1ilOYp^e(RANYfpwd4OF z@B;i#>jl#Pi9q=~1NboTPRP^zO!8j_J^*`F`@afIkC-WKQ~VwQK3i)aU5o4X9RtRU zN;{NU!+s9@Y1sQHFzNp-@HY6D^b!68@TponorY`gAMd2nBokui0IQAgg}}SuZ&f}D ztk-`9u)2S@9`U(VD^LF22}}>b)OcJ7l=lMo1}cxfqK(GS-vZNvC-N8h<8I(bwfOr= zp!_$0kAbh^?|Z;^qy1@=r}qCB@OALNiua!aU#j)*uYud`!5vE|3hipkuLC~o3e2&P zBYmd<>;5zW|&@eakUjyISzYmD|yPt({FGh_|iof*%d>*j+rU;e4 z7?>{-P@eoX2&}i~GT?T9_OWsFn_gc$AfFE49l-yJ@ktxma|`B6dRd6JO{jk!{HHK} zt_3E4G=VAJX(Rit1^!h3K5!_1huJ=0dSM|U`5yuP0LoJcQu*6~X=O+o#s9~&`bgis z;Op)C8t^?Mhyln``Tq#;A18en@3hhQd^*5?9yrXoqP~;pLXBH1@J8VETD}+n*6Y6% z_#V|>l#2)WGr;N_BxHXPxV#JZ=uj!yy9apf9L~{X!VTaCIRhK{`@O`+`UYXb{rM4K zdewyTE$PD(yj;edG)-}m5+`^t!%zHL!*s2AetxaxItuJa8iKv;*cgazVdjqXXf87quNFLS)OQPYuL8l$;_iV) z!S(Tgu`NAB(xZ4#VrH$<;zDN8)|kW0YU7W zLc5@q9U?0>=a-y0+qH}lr=GR0XbUno{B z)I?<_wgrjFA*Z+-oR#S5&CqEVs^;*LYx`Dmf!C~Pm1@&lkk&|=vAM8Vu<`TXsx|Jo zbG(o+R|-U3t5EJN6UCR3_H>fAw6t(~$}q@L@FsmeiHt~%n5A05-9x9tW(iC>i$q&K z^&7q8*CKBw~& zXtC>!rl82GxL28CJ568lQSG!-wpy$j09RG07pl%YH&L7_LrM#UdfloDJFIG9@3dWV z9nV6LmpwE&ZH%^t5JpA9qWIH>1x61a77;Caqk}6IltF~P*qt*>ec%m^uo|zKai0yE z&LX;@>MWThr&;$|nQSAqfp{cU)H6neMqA3RTT}Il!-rn3i7#+raQOw_V)^#SlNHqL zYSrdV94#emuL1o=)b?^E*RtxfjWS9li(0FOM;KCqFGZ7!_>LGS#Htv0LY@_?c6|@0 z>S|!$mQtgMw1m%kWfqyY*YmBKunR33&LC2K%{A1_krMx4*eFWy?~93n7cHf%@`x#h zA|F+P;yd&6Rhbq9r70S~Www;w<)BKtv}Z&o8=G4_JK+}g&LA~yp#+(+m#r!?Bl$I$ zL5J)*`laQBVh<4mCTATu9p5t(R(EFMQJ#bs3Y2^ocJJncG8L_0WOpmmlTHsU7>z!q z@Ls0!YA7C75-fs-7Nd+EUv(q*Qhv)^ccP6OBQ9 zOu_ZF6xzujMR2dkG=_MPjQ_DVM1|NNYeZYDvlX2d8G~HgO9apsE}$GC`c1~a@`Jx* zn|#`>**@c7d8}E%9GtT1^FA^!_;3uyw9e$fuFE1GANNi}QnOYAqsSb%Wiz?f#nKi0v63TQHytG#>xJ&$uave3S>2X-5wGqar>0o-2 z9?MYyA>Q%Ts zZI2Xa4#qmEY?s8mvnUtqXy^DxXrqp zJmH?qXen7QVYTTh?}{{y6ih7$1{t-SuE5ap75SZ>ff7BKf(nAXuEJf8Z&z;^;_Xn= z!(c{-nG>m*u^6r~r_7Pz;h|x3(8LR-X06mTu`Z@%%|L0u85r7vC!*q-25rc1Qwvi8 z*3iP?Bw!Rc6fc03l(&t{W&qfm&XV(E$5 z*aUI}&2Pzix#1vMY2}(S28+0w+Ow@tZBl&UDru?gmpqp@h2G#ZMOJQK22I5hYK2{K z3ni?%@Es?=P%lw=1XNdi2y%z}1S#03f~t1}a~;Q5*+s0h)rGZTP+M?aXG;pNW;S{dftG8+tW@P(*gy{ zjZRwCq0vyb7jaeQ*-?}++sLe2^N|jMwT#7#+3b{WL(_sXjcp3`QeZ*qZ^I6Gu%*0E zuf$u3%rRQ#ka&lEMBw&$JV93Ak;O{uw1Ue~@)j52&GdYyVd4hMyj!S2>mr7c4dA?g};2KY{Bcn_XOulF|U4!JCwPtm}_H3-Zlpq;eX_z(KMcHFkC{z&4iaA7H z#e1_c6Ss?|X3}OVnV!gtTS->K##M3CqD2u5vOInZ2~O}Tq#in5TzuljLQ7ec$o8^V z@o>w41aRGG`y|}Ei5sK;+^@r!wUSsS@u zwa%c1z(Dks*`V8XEyiY?!&~(Nsh9pWG`~o`U9TYddyvr+gR;!bvE86eUXCi*k(QCYP{PN(ENRyO0!6bs0$(%4PI5 z1{O{x3 ziBalp%ta#e)0&UczM8&R1-JhofWGx)YQy-QuR2BC1LsZ$lcGxW5gYLK)-V8Y^j&dW zP-ooj13c{yAd=ZY9qTsC>9~VXtI^#<5PWN3zTO=J2&|1POHs{R(0%_ht zK><5hMJ)Ul6^AAob&lHUsmjaSUI#IH3dNrd;rUOcg6C-Bu@qOJPG2wO=y?onx8T2e zUw|+@_oD4h_)pI`Ngq9r8(3iMhe`=(>huy#?ohvwQ#!zE$wlS)AQ7>VK0GRGZKRL> z<54Al_f9dcfZP=d;3yxJ^7ki{4aD9Dxnmk(oh(C-{xmY%wVSaQ=$!_+Dc&`}>YP|t zUXS-`<#TJoig$x{PV`?KfPFpWZVbr1{bP(>qaNI8Jo14?W}o{O>IF@0s$M$ke!T-i g^z4nEi~kn?$uIQGAFuy*=v)6!jNJw~wJCl73-Jj>RR910 literal 0 HcmV?d00001 diff --git a/examples/parts/sh1106_virt.c b/examples/parts/sh1106_virt.c index 1782fc203..b813cbee4 100644 --- a/examples/parts/sh1106_virt.c +++ b/examples/parts/sh1106_virt.c @@ -61,14 +61,14 @@ sh1106_update_command_register (sh1106_t *part) return; case SH1106_VIRT_DISP_NORMAL: sh1106_set_flag (part, SH1106_FLAG_DISPLAY_INVERTED, - 0); + 0); sh1106_set_flag (part, SH1106_FLAG_DIRTY, 1); //printf ("SH1106: DISPLAY NORMAL\n"); SH1106_CLEAR_COMMAND_REG(part); return; case SH1106_VIRT_DISP_INVERTED: sh1106_set_flag (part, SH1106_FLAG_DISPLAY_INVERTED, - 1); + 1); sh1106_set_flag (part, SH1106_FLAG_DIRTY, 1); //printf ("SH1106: DISPLAY INVERTED\n"); SH1106_CLEAR_COMMAND_REG(part); @@ -86,22 +86,22 @@ sh1106_update_command_register (sh1106_t *part) SH1106_CLEAR_COMMAND_REG(part); return; case SH1106_VIRT_SET_PAGE_START_ADDR - ... SH1106_VIRT_SET_PAGE_START_ADDR - + SH1106_VIRT_PAGES - 1: + ... SH1106_VIRT_SET_PAGE_START_ADDR + + SH1106_VIRT_PAGES - 1: part->cursor.page = part->spi_data - - SH1106_VIRT_SET_PAGE_START_ADDR; + - SH1106_VIRT_SET_PAGE_START_ADDR; //printf ("SH1106: SET PAGE ADDRESS: 0x%02x\n", part->cursor.page); SH1106_CLEAR_COMMAND_REG(part); return; case SH1106_VIRT_SET_COLUMN_LOW_NIBBLE - ... SH1106_VIRT_SET_COLUMN_LOW_NIBBLE + 0xF: + ... SH1106_VIRT_SET_COLUMN_LOW_NIBBLE + 0xF: part->spi_data -= SH1106_VIRT_SET_COLUMN_LOW_NIBBLE; part->cursor.column = (part->cursor.column & 0xF0) | (part->spi_data & 0xF); //printf ("SH1106: SET COLUMN LOW NIBBLE: 0x%02x\n",part->spi_data); SH1106_CLEAR_COMMAND_REG(part); return; case SH1106_VIRT_SET_COLUMN_HIGH_NIBBLE - ... SH1106_VIRT_SET_COLUMN_HIGH_NIBBLE + 0xF: + ... SH1106_VIRT_SET_COLUMN_HIGH_NIBBLE + 0xF: part->spi_data -= SH1106_VIRT_SET_COLUMN_HIGH_NIBBLE; part->cursor.column = (part->cursor.column & 0xF) | ((part->spi_data & 0xF) << 4); //printf ("SH1106: SET COLUMN HIGH NIBBLE: 0x%02x\n", part->spi_data); @@ -109,25 +109,25 @@ sh1106_update_command_register (sh1106_t *part) return; case SH1106_VIRT_SET_SEG_REMAP_0: sh1106_set_flag (part, SH1106_FLAG_SEGMENT_REMAP_0, - 1); + 1); //printf ("SH1106: SET COLUMN ADDRESS 0 TO OLED SEG0 to \n"); SH1106_CLEAR_COMMAND_REG(part); return; case SH1106_VIRT_SET_SEG_REMAP_131: sh1106_set_flag (part, SH1106_FLAG_SEGMENT_REMAP_0, - 0); + 0); //printf ("SH1106: SET COLUMN ADDRESS 131 TO OLED SEG0 to \n"); SH1106_CLEAR_COMMAND_REG(part); return; case SH1106_VIRT_SET_COM_SCAN_NORMAL: sh1106_set_flag (part, SH1106_FLAG_COM_SCAN_NORMAL, - 1); + 1); //printf ("SH1106: SET COM OUTPUT SCAN DIRECTION NORMAL \n"); SH1106_CLEAR_COMMAND_REG(part); return; case SH1106_VIRT_SET_COM_SCAN_INVERTED: sh1106_set_flag (part, SH1106_FLAG_COM_SCAN_NORMAL, - 0); + 0); //printf ("SH1106: SET COM OUTPUT SCAN DIRECTION REMAPPED \n"); SH1106_CLEAR_COMMAND_REG(part); return; @@ -356,51 +356,51 @@ void sh1106_connect (sh1106_t * part, sh1106_wiring_t * wiring) { avr_connect_irq ( - avr_io_getirq (part->avr, AVR_IOCTL_SPI_GETIRQ(0), - SPI_IRQ_OUTPUT), - part->irq + IRQ_SH1106_SPI_BYTE_IN); + avr_io_getirq (part->avr, AVR_IOCTL_SPI_GETIRQ(0), + SPI_IRQ_OUTPUT), + part->irq + IRQ_SH1106_SPI_BYTE_IN); avr_connect_irq ( - avr_io_getirq (part->avr, - AVR_IOCTL_IOPORT_GETIRQ( - wiring->chip_select.port), - wiring->chip_select.pin), - part->irq + IRQ_SH1106_ENABLE); + avr_io_getirq (part->avr, + AVR_IOCTL_IOPORT_GETIRQ( + wiring->chip_select.port), + wiring->chip_select.pin), + part->irq + IRQ_SH1106_ENABLE); avr_connect_irq ( - avr_io_getirq (part->avr, - AVR_IOCTL_IOPORT_GETIRQ( - wiring->data_instruction.port), - wiring->data_instruction.pin), - part->irq + IRQ_SH1106_DATA_INSTRUCTION); + avr_io_getirq (part->avr, + AVR_IOCTL_IOPORT_GETIRQ( + wiring->data_instruction.port), + wiring->data_instruction.pin), + part->irq + IRQ_SH1106_DATA_INSTRUCTION); avr_connect_irq ( - avr_io_getirq (part->avr, - AVR_IOCTL_IOPORT_GETIRQ( - wiring->reset.port), - wiring->reset.pin), - part->irq + IRQ_SH1106_RESET); + avr_io_getirq (part->avr, + AVR_IOCTL_IOPORT_GETIRQ( + wiring->reset.port), + wiring->reset.pin), + part->irq + IRQ_SH1106_RESET); } void sh1106_connect_twi (sh1106_t * part, sh1106_wiring_t * wiring) { avr_connect_irq ( - avr_io_getirq (part->avr, AVR_IOCTL_TWI_GETIRQ(0), TWI_IRQ_OUTPUT), - part->irq + IRQ_SH1106_TWI_OUT); + avr_io_getirq (part->avr, AVR_IOCTL_TWI_GETIRQ(0), TWI_IRQ_OUTPUT), + part->irq + IRQ_SH1106_TWI_OUT); avr_connect_irq ( - part->irq + IRQ_SH1106_TWI_IN, - avr_io_getirq (part->avr, AVR_IOCTL_TWI_GETIRQ(0), TWI_IRQ_INPUT)); + part->irq + IRQ_SH1106_TWI_IN, + avr_io_getirq (part->avr, AVR_IOCTL_TWI_GETIRQ(0), TWI_IRQ_INPUT)); if (wiring) { avr_connect_irq ( avr_io_getirq (part->avr, - AVR_IOCTL_IOPORT_GETIRQ( - wiring->reset.port), - wiring->reset.pin), - part->irq + IRQ_SH1106_RESET); + AVR_IOCTL_IOPORT_GETIRQ( + wiring->reset.port), + wiring->reset.pin), + part->irq + IRQ_SH1106_RESET); } } @@ -418,7 +418,7 @@ sh1106_init (struct avr_t *avr, struct sh1106_t * part, int width, int height) part->write_cursor_end.page = SH1106_VIRT_PAGES-1; part->write_cursor_end.column = SH1106_VIRT_COLUMNS-1; - AVR_LOG(avr, LOG_OUTPUT, "SH1106: size %dx%d (flags=0x%04x)\n", part->columns, part->rows, part->flags); + AVR_LOG(avr, LOG_OUTPUT, "SH1106: size %dx%d (flags=0x%04x)\n", part->columns, part->rows, part->flags); /* * Register callbacks on all our IRQs */ @@ -426,13 +426,13 @@ sh1106_init (struct avr_t *avr, struct sh1106_t * part, int width, int height) irq_names); avr_irq_register_notify (part->irq + IRQ_SH1106_SPI_BYTE_IN, - sh1106_spi_in_hook, part); + sh1106_spi_in_hook, part); avr_irq_register_notify (part->irq + IRQ_SH1106_RESET, - sh1106_reset_hook, part); + sh1106_reset_hook, part); avr_irq_register_notify (part->irq + IRQ_SH1106_ENABLE, - sh1106_cs_hook, part); + sh1106_cs_hook, part); avr_irq_register_notify (part->irq + IRQ_SH1106_DATA_INSTRUCTION, - sh1106_di_hook, part); + sh1106_di_hook, part); avr_irq_register_notify (part->irq + IRQ_SH1106_TWI_OUT, - sh1106_twi_hook, part); + sh1106_twi_hook, part); }