├── LICENSE ├── README.md ├── examples ├── clglcd_qr_clock │ ├── choof_qr_code.c │ ├── clglcd.cpp │ ├── clglcd.h │ ├── clglcd_config.h │ ├── clglcd_font.h │ └── clglcd_qr_clock.ino ├── clglcd_simple │ ├── bitmap.h │ └── clglcd_simple.ino ├── clglcd_soft │ ├── clglcd.cpp │ ├── clglcd.h │ ├── clglcd_config.h │ ├── clglcd_font.h │ └── clglcd_soft.ino └── clglcd_text │ ├── clglcd.cpp │ ├── clglcd.h │ ├── clglcd_config.h │ ├── clglcd_font.h │ └── clglcd_text.ino ├── misc └── gen_font_header.py ├── src ├── clglcd.cpp ├── clglcd.h └── clglcd_config.h ├── timings ├── big-picture.png ├── cl2-data.png └── flm_latch.png └── usb_bridge ├── README.md ├── clglcd_bridge ├── clglcd_bridge.ino ├── clglcd_config.h ├── clglcd_macros.h ├── clglcd_usb_wcid.cpp └── clglcd_usb_wcid.h ├── clglcd_usb_host ├── CLGLCD_WinUSB_Host.cbp ├── Makefile ├── clglcd_exc.h ├── clglcd_ipc.cpp ├── clglcd_ipc.h ├── clglcd_winusb.cpp ├── clglcd_winusb.h └── main.cpp └── demo ├── SampleVideo_640x360_10mb.mp4 ├── clglcd.py ├── lcd_shm_demo.py ├── lena320x240.png └── standby320.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ivan Kostoski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino driver for 4-bit controllerless graphics LCD 2 | 3 | ## Summary 4 | 5 | Repository contains code samples for driving 4-bit parallel controllerless graphics LCD (CLGLCD) module with AVR MCU on an Arduino board, using minimal external components and staying within Arduino IDE. 6 | 7 | ## 4-bit Controllerless Graphics LCD modules 8 | 9 | Controllerless graphics LCD modules are antiques that can be salvaged from old copiers, tape libraries, etc... They commonly are missing, well, the controller chip, the one with the memory. Don't go buying one of these, for Arduino usage, even if you find them on sale. They are usually industrial, have poor viewing angles, generally slow response time, and pain to work-with. There, I said my peace... But if you already have one, their size (i.e. 5.7in) or simplicity can have its uses and beauty. 10 | 11 | I have tested this code with 320x240 STN LCD monochrome module marked as F-51543NFU-LW-ADN / PWB51543C-2-V0, salvaged some time ago from retired tape library, without the controller module (which it appears is based on FPGA and wouldn't be of much use anyway). 12 | 13 | The same type of interface (4-bit data) with various signal names is present on many industrial modules based on multiplexed column and common row LCD drivers, like LC79401/LC79431. Or this is what is behind the controller IC. They all have some variations like LCD drive voltage (positive or negative, depending on temperature and size of the module), backlight (LED/CCFL), some logic quirks (i.e. _CL2_ is ignored while _CL1_ is up, etc...), so maybe this code can be adapted to other controllerless modules. Module's datasheet is necessity for the connector pinouts and timing requirements. Some modules may even generate LCD drive voltage internally, and outputting it on a pin so actual _V0_ driving voltage can be adjusted. 14 | 15 | ## Control signal names to recognize these modules 16 | - _FLM_ - First Line Marker, a.k.a. FRAME, VSYNC, etc.. 17 | - _CL1_ - Row latch pulse, a.k.a. LOAD, HSYNC, etc.. 18 | - _CL2_ - Pixel shift clock, a.k.a. CP, etc.. 19 | - _M_ - Alternate signal for LCD drive, a.k.a. BIAS, etc... 20 | - _D0_-_D3_ - 4-bit parallel data signals 21 | - Various pins for GND, power, backlight (i.e. VLED+/-), and LCD drive voltage (i.e. VEE, V0) 22 | 23 | ## Driving CLGLCD 24 | 25 | CLGLCD devices are actually quite simple: Shift-in single row (horizontal resolution) of 4-bits/_CL2_ pulse data to the column drivers with falling edge of _CL2_ clock. Then, latch the data to the active row with falling edge of _CL1_ pulse. This also shifts-in the vertical row bit, which is provided by _FLM_ (First Line Marker) signal at start of the frame and then shifted 'down' trough common drivers with falling edge of _CL1_ pulse. There is example schematic in the LC79401 datasheet. That's basically it, you just need to do it fast enough for specified refresh rate and keep _CL1_ pulses evenly spaced... 26 | 27 | Additionally the _M_ signal needs to be toggled, typically on start of every frame, which gives you 1/N duty cycle (specified in datasheet) for the LCD bias. LCD crystals need to be driven by AC voltage (or approximation of it) to prevent them remaining stuck in one orientation (damage to the display, see i.e. https://www.youtube.com/watch?v=ZP0KxZl5N2o). Flipping the _M_ signal at required duty cycle usually is all that is needed and rest is taken care of inside the module itself (i.e. LCD drive voltage is varied in several voltage steps). This can also be achieved with external flip-flip that will toggle the _M_ signal on _CL1_ pulse, when _FLM_ signal is up. 28 | 29 | Please see 'clglcd_simple' example, for basic principles of how you can display static image (in flash) on the module. 30 | 31 | ## Driving CLGLCD with AVR MCU 32 | 33 | AVR MCUs don't have required SRAM to hold full frame of graphics data (i.e. 9600 bytes for 320x240 GLCD), nor DMA engine to push the data without CPU intervention. However if you considering adding external RAM, this will probably complicate the setup to the point that more powerfull MCU (i.e. STM32, ESP32) with embedded SRAM and DMA are more cost effective. If you insist, consider 23LC512 type of external SRAM which you might use as kind-of DMA engine in SQI mode. 34 | 35 | If we consider some form of procedural generation (i.e. font generator), there just might be enough SRAM and speed on 8-bit AVR MCU. Repository contains character generator code for driving this type of module in text mode, with font stored in flash and text screen buffer in SRAM. 36 | 37 | ### Text mode 38 | 39 | First thing to note is that the pixel clock (_CL2_), according to the i.e. LC79401 datasheet, can actually run up to 6MHz. That is something I use to shift-in the single row data as-fast-as-possible, and then latch the data and move the row selector common bit on timer schedule. 40 | 41 | With character generation, for 320 pixels horizontal resolution, I manage to do this in 20µs (4MHz _CL2_ clock x 80 pulses) which leaves 39.5µs to do other things (typical _CL1_ pulse period for tested module is 59.5µs for 70Hz refresh rate). There is some ISR and "management" overhead, so it is more likely AVR is being busy 45% of available time just with the display. On the positive side, text screen buffer is memory mapped and can be written very quickly. 42 | 43 | If you need more available "user" time, you can reduce the refresh rate (my module worked OK down to 50Hz) and/or you can increase AVR clock to i.e. 20MHz, both of which will increase the 952 ticks period between interrupts and give you more time for your stuff. 44 | 45 | I use Output Compare timer pin with period of 4 FCLK ticks to toggle _CL2_ pin (4MHz with 50% duty cycle) and synchronously place the data on GPIO pins of single port (i.e. PORTB, connected to _D0_-_D3_ on the module), aligning that data is changed on rising edge and stable on falling edge of the _CL2_ signal. At this rate, assembler is required to use the tricks AVR has up its sleeve. 46 | 47 | The heart of the ISR code is this: 48 | ```assembly 49 | ... 50 | lpm r24, Z 51 | ;---------- (CL2 rising edge) 52 | out %[data_port], r24 53 | ld r30, X+ 54 | swap r24; (CL2 rising edge) 55 | out %[data_port], r24 56 | lpm r24, Z 57 | ;---------- (CL2 rising edge) 58 | out %[data_port], r24 59 | ... 60 | ``` 61 | 62 | Which takes exactly 8 FCLK ticks to shift-in two 4-bit nibbles in an unrolled loop. The current line of the screen buffer is pointed by X and ZH points to the current line of the flash-based font. 63 | 64 | One other tricky part is to stop the timer exactly after 80 pulses, which may or may not work with some timers (i.e. driving _CL2_ with Timer4 on 32u4 can be problematic). 65 | 66 | For _CL1_ pulse, I use another timer/FastPWM pin with period of 952 ticks (or 119 with /8 prescaller @16MHz) for 59.5µs pulse (240 lines x 59.5µs ~= 70Hz refresh), which also triggers ISR where I shift-in the next row data and setup control lines for the next _CL1_ pulse. The same timer is used to toggle _M_ pin in output compare mode, but only on first _CL1_ pulse of the frame. It might not be strictly necessary but I do like toggling _M_ pin together with CL1 falling edge. 67 | 68 | The timing of the signals should resemble something like this: 69 | 70 | ![Big picture](/timings/big-picture.png?raw=true "Big picture") 71 | ![FLM latch](/timings/flm_latch.png?raw=true "FLM Latching and CL1 toggle") 72 | ![Data shift](/timings/cl2-data.png?raw=true "Data shifting on CL2 clock") 73 | 74 | The code uses font character data (256 characters) stored in flash and screen buffer in SRAM (40bytes x number of lines), one byte for each character. Horizontal size of the font is fixed to 8 pixels, i.e. 40 characters per line. Vertical size of the font is configurable, as long as you can fit whole number of lines in display's vertical resolution. For i.e. 240 lines vertical resolution, supported font sizes would be i.e. 6, 8, 10, 12, 15, 16, etc... The smaller the font, more SRAM is needed to hold the screen buffer (and less flash). For 8x8 fonts, you need 40x30 = 1200 bytes of SRAM and 256x8 bytes of flash. With 8x16 font (IMHO, best looking on 320x240) you need 600 bytes of SRAM and 4KB flash for the font. Code can be modified if you need more than one font stored in flash (starting font address can be changed to be variable instead of compile time constant), but only one font can be active at a time or you will need to implement changing the font base inside the ISR. 75 | 76 | Font data is organized as top vertical line of all characters (256 bytes) first, than second line of all characters, etc... until vertical size of the font. Font data must be aligned on 256-byte boundary in the flash. There is a Python helper script in the "misc" directory which can be used to convert Windows TTF font into C header file (clglcd_font.h), with PROGMEM byte array in correct format. You can find good selection of bitmap fonts at https://int10h.org/oldschool-pc-fonts/ and elsewhere. 77 | 78 | ### Soft fonts 79 | 80 | Another useful feature would be to have some amount of character data in memory instead all of it in flash, and being able to change characters on the fly and/or draw smaller graphics on part of the screen. Depending on the available memory, code supports (I haven't tested with more) up to 64 'soft' characters. Soft characters can be defined as starting character codes (0..n) or as ending codes (255-n..255). Like with the fixed (flash based) fonts, order in memory is first line (1 byte) of all characters, than second line of all characters, etc... up to the vertical size of the font. Data must be aligned on 'number of soft characters' bytes boundary. The bigger the font, more SRAM you need for each 'soft' character. For i.e. 64 8x16 characters you will need 1KB of SRAM, aligned on 64 byte boundary. 81 | 82 | See the demo/example of how to use soft fonts, 64 characters as 128x64 graphics. 83 | 84 | Note that using mixed soft and fixed characters, in addition of eating SRAM, slows down shifting of row data by 50% (12 FCLK ticks vs 8 ticks for 2x4-bit nibbles), i.e. from 20µs to 30µs per row @16Mhz, leading to about 60% of time spent in the ISR. It is a trade-off. If Arduino is mostly just driving the display, it may be useful. 85 | 86 | ### Graphics mode with grayscale 87 | 88 | If you want to connect the LCD module to a PC via USB, i.e. to display information from the PC world, please see the the [USB bridge](usb_bridge/README.md) demo. It only works with USB MCUs (i.e ATmega32u4), but it can do graphics mode and grayscale. 89 | 90 | 91 | ## Hardware 92 | 93 | It is up to you to provide connectors to the display module, power for backlight and LCD drive voltage, starting from i.e. 3x9V batteries for testing, to ICs like MAX749 or equivalent. You should also pull-up or pull-down power control signals and DISPOFF (important to be pulled down and not pulsed during i.e. programming or reset of the board). 94 | 95 | Whatever you do, please make sure that LCD drive voltage is applied after (or simultaneously with) logic supply voltage (i.e. +5V) and DISPOFF pin is brought up only after control signals are in place (i.e. there is only one active bit in common drivers and CL1 is pulsing). Otherwise, you may damage the LCD module. 96 | 97 | Module will require minimum of 9 control signals: 98 | - _D0_-_D3_, code needs these to be mapped to same port, Px4-Px7 pins respectively. It will also make rest of the Px0-Px3 unusable as GPIO outputs. You can use alternate peripheral function for the pins (i.e. Timer, UART, etc...). If you use them as inputs, be careful as the pull-up resistor may be toggled at random, unless you globally disable pull-ups (PUD) 99 | - _M_, on a timer (PWM) OC pin, 100 | - _CL1_, on a same timer as _M_, but different OC pin, 101 | - _CL2_, on different timer OC pin, 102 | - _FLM_, on any digital pin 103 | - _DISPOFF_, on any digital pin. 104 | 105 | Rest depends on how you will power the module. I like to control the backlight and VEE separately. 106 | 107 | ## Using the driver code 108 | 109 | Copy clglcd.h and clglcd.cpp files into your Arduino sketch. 110 | 111 | Copy and **edit** clglcd_config.h to match you hardware configuration, including pinout connections and features enabled (i.e. soft characters, etc...). Pin names are not Arduino pin numbers, rather actual AVR pin names (i.e. B,5 is PB5 pin). You may need to lookup in the board pinout/schematics. Also timer outputs are actual OCnX lines, i.e. 2,B is OC2B which is on i.e. PD3 on Arduino UNO). Examples have working pinout assignments for boards I have tested with. 112 | 113 | Generate clglcd_font.h file for your sketch (see "Misc" directory for python script that can do this) 114 | 115 | See examples how to init, start and stop the display and use _screen_ array to place characters on the screen. 116 | 117 | Compile and upload the sketch, but before connecting the actual LCD module to the Arduino, I recommend using logic analyzer to check if output of the control pins is what you expect (should be similar to images in "timings" directory) and check LCD drive voltages with multimeter. 118 | 119 | This is just a basic driver and character generator. The rest should be your code, i.e. your emulation of serial connected LCD can go in the main loop. Please share. 120 | 121 | ## Interrupts while display is ON 122 | 123 | Display needs to be constantly refreshed, which is done in the ISR. While using the display, disabling of the interrupts for anything more than ~30µs will at best produce visual glitches, and if you are unlucky to disable interrupts for more than 60µs while _FLM_ signal is up, you can cause damage to the LCD module (more than one active bit can be shifted in common drivers, making absolute mess with unpredictable consequences). You can disable the display (_DISPOFF_) before you disable interrupts for longer periods. However, disabling the display every 2 seconds to read a DHT sensor will probably look bad. 124 | 125 | Any one-wire (ds18b20, DHTxx), clock-less signal (i.e. ws2812b strips), motors, etc., are likely incompatible with using the same Arduino board, or may require significant effort to integrate (i.e. using USART for one-wire protocol). Pro-Micro clones are so cheap that you can probably dedicate one to the display and talk to it i.e. via I2C/USB/UART making it just a LCD controller board. 126 | 127 | ## Communication 128 | 129 | Communication like hardware serial (up to 115200bps), I2C or SPI in master mode should work. In slave mode, it will depend if the master/protocol can tolerate device which is periodically unresponsive for 25-35µs, or if you implement some kind of acknowledgement scheme... 130 | 131 | It also depends on the pins you have leftover after I have taken all the good ones. 132 | 133 | USB on 32u4 is fine, as long as you don't query the control endpoint too often (slow ISR code). The CDC driver and its API seem to be quick enough. 134 | 135 | ## Similar projects 136 | 137 | Something similar has already been done with different MCUs: 138 | - ATmega8515 https://www.mikrocontroller.net/topic/98321#1881082, 139 | - ATmega8515 https://www.mikrocontroller.net/topic/25099?page=single, 140 | - PIC http://www.pcbheaven.com/exppages/Reverse-Engineering_an_LCD_Display/?p=0, 141 | - ESP32 https://github.com/har-in-air/ESP32-LCD-I2S 142 | 143 | -------------------------------------------------------------------------------- /examples/clglcd_qr_clock/clglcd.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Character generator for Controllerless Graphics LCD Display 3 | * 4 | * Copyright (c) 2019 Ivan Kostoski 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 all 14 | * 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 THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include "clglcd.h" 27 | 28 | #define BIT_(p,b) (b) 29 | #define BIT(cfg) BIT_(cfg) 30 | #define PORT_(p,b) (PORT ## p) 31 | #define PORT(cfg) PORT_(cfg) 32 | #define PIN_(p,b) (PIN ## p) 33 | #define PIN(cfg) PIN_(cfg) 34 | #define DDR_(p,b) (DDR ## p) 35 | #define DDR(cfg) DDR_(cfg) 36 | 37 | #define SET(cfg) PORT_(cfg) |= _BV(BIT_(cfg)) 38 | #define CLEAR(cfg) PORT_(cfg) &= ~_BV(BIT_(cfg)) 39 | #define TOGGLE(cfg) PIN_(cfg) = _BV(BIT_(cfg)) 40 | 41 | #define TM_BASE_(n,x) (n) 42 | #define TM_BASE(cfg) TM_BASE_(cfg) 43 | #define TM_LINE_A 1 44 | #define TM_LINE_B 2 45 | #define TM_LINE_C 3 46 | #define TM_LINE_D 4 47 | #define TM_LINE_(n,x) (TM_LINE_ ## x) 48 | #define TM_LINE(cfg) TM_LINE_(cfg) 49 | #define TM_CR_(n,x) (TCCR ## n ## x) 50 | #define TM_CR(cfg) TM_CR_(cfg) 51 | #define TM_OCR_(n,x) (OCR ## n ## x) 52 | #define TM_OCR(cfg) TM_OCR_(cfg) 53 | 54 | #define TM_FPWM_MODE2_(n,x) (1< 0 130 | uint8_t volatile soft_font[CLGLCD_FONT_LINES][CLGLCD_SOFT_CHARS] __attribute__((aligned(CLGLCD_SOFT_CHARS))); 131 | #endif 132 | 133 | // 134 | // Character generator ISR 135 | // 136 | 137 | #if CLGLCD_SOFT_CHARS < 1 // Fixed 138 | 139 | // Outputs 2 nibbles in 8 Fclk ticks 140 | #define SHIFT_OUT_BYTE() \ 141 | "out %[data_port], r24" "\n\t" \ 142 | "ld r30, X+" "\n\t" \ 143 | "swap r24" "\n\t" \ 144 | "out %[data_port], r24" "\n\t" \ 145 | "lpm r24, Z\n\t" 146 | 147 | #else // Fixed + Soft 148 | 149 | #define SHIFT_OUT_BYTE_SCR_LOAD() \ 150 | "ld r30, X+" "\n\t" \ 151 | 152 | #if CLGLCD_SOFT_UPPER == 1 153 | #define SHIFT_OUT_BYTE_BRANCH_3F() \ 154 | "cpi r30, %[sfc_upper]" "\n\t" \ 155 | "brcc 3f" "\n\t" 156 | #else 157 | #define SHIFT_OUT_BYTE_BRANCH_3F() \ 158 | "cpi r30, %[sfc_num]" "\n\t" \ 159 | "brcs 3f" "\n\t" 160 | #endif 161 | 162 | #define SHIFT_OUT_BYTE_FIXED_LOAD() \ 163 | "lpm r28, Z" "\n\t" \ 164 | 165 | #if CLGLCD_SOFT_UPPER == 1 166 | #define SHIFT_OUT_BYTE_SOFT_OFFSET() \ 167 | "mov r28, r30" "\n\t" \ 168 | "and r28, r0" "\n\t" 169 | #else 170 | #define SHIFT_OUT_BYTE_SOFT_OFFSET() \ 171 | "mov r28, r30" "\n\t" \ 172 | "or r28, r0" "\n\t" 173 | #endif 174 | 175 | #define SHIFT_OUT_BYTE_SOFT_LOAD() \ 176 | "ld r28, Y" "\n\t" 177 | 178 | // Outputs 2 nibbles in 12 Fclk ticks 179 | #define SHIFT_OUT_BYTE() \ 180 | "out %[data_port], r28" "\n\t" \ 181 | SHIFT_OUT_BYTE_SCR_LOAD() \ 182 | "swap r28" "\n\t" \ 183 | SHIFT_OUT_BYTE_BRANCH_3F() \ 184 | "out %[data_port], r28" "\n\t" \ 185 | SHIFT_OUT_BYTE_FIXED_LOAD() \ 186 | "rjmp 4f" "\n\t" \ 187 | "3:" \ 188 | /* We are a tick late with this nibble */ \ 189 | "out %[data_port], r28" "\n\t" \ 190 | SHIFT_OUT_BYTE_SOFT_OFFSET() \ 191 | SHIFT_OUT_BYTE_SOFT_LOAD() \ 192 | "4:" 193 | 194 | #endif 195 | 196 | // Common shifting macros 197 | #define SHIFT_OUT_3X_BYTE() \ 198 | SHIFT_OUT_BYTE() \ 199 | SHIFT_OUT_BYTE() \ 200 | SHIFT_OUT_BYTE() 201 | 202 | #define SHIFT_OUT_9X_BYTE() \ 203 | SHIFT_OUT_3X_BYTE() \ 204 | SHIFT_OUT_3X_BYTE() \ 205 | SHIFT_OUT_3X_BYTE() 206 | 207 | // Set vector based on config 208 | #if TM_BASE(CLGLCD_CL1_OCnX) == 1 209 | ISR(TIMER1_OVF_vect, ISR_NAKED) { 210 | #elif TM_BASE(CLGLCD_CL1_OCnX) == 2 211 | ISR(TIMER2_OVF_vect, ISR_NAKED) { 212 | #elif TM_BASE(CLGLCD_CL1_OCnX) == 3 213 | ISR(TIMER3_OVF_vect, ISR_NAKED) { 214 | #elif TM_BASE(CLGLCD_CL1_OCnX) == 4 215 | ISR(TIMER4_OVF_vect, ISR_NAKED) { 216 | #else 217 | #error "CL1 timer not implemented" 218 | #endif 219 | __asm__ ( 220 | ".equ font_end, %[font_start]+%[font_size]" "\n\t" 221 | ".equ screen_start, %[screen]" "\n\t" 222 | ".equ screen_end, %[screen]+%[screen_size]" "\n\t" 223 | #if CLGLCD_SOFT_CHARS > 0 224 | ".equ soft_offset, %[soft_font]" "\n\t" 225 | #endif 226 | 227 | "push r24" "\n\t" 228 | "in r24, 0x3f" "\n\t" 229 | "push r24" "\n\t" 230 | "push r26" "\n\t" 231 | "push r27" "\n\t" 232 | "push r30" "\n\t" 233 | "push r31\n\t" 234 | #if CLGLCD_SOFT_CHARS > 0 235 | "push r0" "\n\t" 236 | "push r1" "\n\t" 237 | "push r28" "\n\t" 238 | "push r29" "\n\t" 239 | #endif 240 | 241 | // Clear FLM 242 | "cbi %[flm_port], %[flm_bit]" "\n\t" 243 | 244 | "1:" 245 | // Pickup where we left off 246 | "lds r31, %[font_line]" "\n\t" 247 | "lds r26, %[scr_pos]" "\n\t" 248 | "lds r27, %[scr_pos]+1" "\n\t" 249 | 250 | // Check if we need to go to top of screen 251 | "ldi r30, hi8(screen_end)" "\n\t" 252 | "cpi r26, lo8(screen_end)" "\n\t" 253 | "cpc r27, r30" "\n\t" 254 | "brcs 2f" "\n\t" 255 | // We have sent all screen lines, ... 256 | // ... reset screen pointer, ... 257 | "ldi r26, lo8(screen_start)" "\n\t" 258 | "ldi r27, hi8(screen_start)" "\n\t" 259 | "sts %[scr_pos], r26" "\n\t" 260 | "sts %[scr_pos]+1, r27\n\t" 261 | // ... reset font line, ... 262 | "ldi r31, hi8(%[font_start])" "\n\t" 263 | // ... set FLM line UP, ... 264 | "sbi %[flm_port], %[flm_bit]" "\n\t" 265 | // ... and set ALT_M_OCnX to be flipped on 266 | // next CL1 timer match. 267 | "ldi r24, %[cl1_set_m]" "\n\t" 268 | "sbis %[alt_m_portin], %[alt_m_bit]" "\n\t" 269 | "ldi r24, %[cl1_clear_m]" "\n\t" 270 | "sts %[cl1_tccr], r24" "\n\t" 271 | 272 | "2:" 273 | #if CLGLCD_SOFT_CHARS < 1 // Fixed 274 | // Preload first byte in r24 275 | "ld r30, X+" "\n\t" 276 | "lpm r24, Z" "\n\t" 277 | #else // Soft + Fixed 278 | // Calculate YH and YL top bits from r31 font line 279 | "mov r29, r31" "\n\t" 280 | "subi r29, hi8(%[font_start])" "\n\t" 281 | "ldi r28, %[sfc_num]" "\n\t" 282 | "mul r28, r29" "\n\t" 283 | "ldi r28, lo8(soft_offset)" "\n\t" 284 | "ldi r29, hi8(soft_offset)" "\n\t" 285 | "add r0, r28" "\n\t" 286 | #if CLGLCD_SOFT_UPPER == 1 287 | "ldi r28, %[sfc_mask]\n\t" 288 | "or r0, r28" "\n\t" 289 | #endif 290 | "adc r29, r1" "\n\t" 291 | // Keep r0 as now it contains bitmask offset to soft font data 292 | 293 | // Preload first byte in r28 294 | SHIFT_OUT_BYTE_SCR_LOAD() 295 | SHIFT_OUT_BYTE_BRANCH_3F() 296 | SHIFT_OUT_BYTE_FIXED_LOAD() 297 | "rjmp 4f" "\n\t" 298 | "3:" 299 | SHIFT_OUT_BYTE_SOFT_OFFSET() 300 | SHIFT_OUT_BYTE_SOFT_LOAD() 301 | "4:" 302 | #endif // Fixed/Soft 303 | 304 | // Start timer 305 | "eor r30, r30" "\n\t" 306 | "sts %[cl2_tcnt], r30\n\t" 307 | "ldi r30, %[cl2_tccr_val]" "\n\t" 308 | "sts %[cl2_tccr], r30" "\n\t" 309 | CL2_TIMER_SYNC() 310 | 311 | // Shift out 39 (4x9+3) bytes 312 | SHIFT_OUT_9X_BYTE() 313 | SHIFT_OUT_9X_BYTE() 314 | SHIFT_OUT_9X_BYTE() 315 | SHIFT_OUT_9X_BYTE() 316 | SHIFT_OUT_3X_BYTE() 317 | 318 | // And for the last byte: 319 | // - Do not load next screen byte 320 | // - Stop timer ASAP, 321 | 322 | #if CLGLCD_SOFT_CHARS < 1 // Fixed font 323 | 324 | "out %[data_port], r24" "\n\t" 325 | "swap r24" "\n\t" 326 | "eor r30, r30" "\n\t" 327 | "nop" "\n\t" 328 | "out %[data_port], r24" "\n\t" 329 | // Stop the timer just as CL2 goes down (STS takes 2 cycles) 330 | "sts %[cl2_tccr], r30" "\n\t" 331 | 332 | #else // Fixed+Soft 333 | 334 | "out %[data_port], r28" "\n\t" 335 | "swap r28" "\n\t" 336 | "nop" "\n\t" 337 | "nop" "\n\t" 338 | //"ldi r24, %[cl1_bv]" "\n\t" 339 | "nop" "\n\t" 340 | "nop" "\n\t" 341 | // Output last nibble 342 | "out %[data_port], r28" "\n\t" 343 | // Stop the timer just as CL2 goes down (STS takes 2 cycles) 344 | "nop" "\n\t" 345 | "eor r1, r1" "\n\t" 346 | "sts %[cl2_tccr], r1" "\n\t" 347 | 348 | #endif // Fixed/Soft 349 | 350 | "subi r31, 0xFF" "\n\t" 351 | "cpi r31, hi8(font_end)" "\n\t" 352 | "brcs 5f" "\n\t" 353 | // We have sent one full screen line (CLGLCD_FONT_SIZE) 354 | // Store screen position (in X register), for next interrupt 355 | "sts %[scr_pos], r26" "\n\t" 356 | "sts %[scr_pos]+1, r27\n\t" 357 | // Rreset the font line 358 | "ldi r31, hi8(%[font_start])" "\n\t" 359 | "5:" 360 | // Store current font line for next interrupt 361 | "sts %[font_line], r31\n\t" 362 | 363 | #if CLGLCD_SOFT_CHARS > 0 364 | "pop r29" "\n\t" 365 | "pop r28" "\n\t" 366 | "pop r1" "\n\t" 367 | "pop r0" "\n\t" 368 | #endif 369 | "pop r31" "\n\t" 370 | "pop r30" "\n\t" 371 | "pop r27" "\n\t" 372 | "pop r26" "\n\t" 373 | "pop r24" "\n\t" 374 | "out 0x3f, r24" "\n\t" 375 | "pop r24" "\n\t" 376 | "reti" "\n\t" 377 | 378 | :: 379 | [font_start] "" (fixed_font), 380 | [font_size] "" (256 * CLGLCD_FONT_LINES), 381 | [screen] "" (screen), 382 | [screen_size] "" (40 * CLGLCD_Y_LINES), 383 | [scr_pos] "" (screen_pos), 384 | [font_line] "" (font_line), 385 | #if CLGLCD_SOFT_CHARS > 0 386 | [soft_font] "" (soft_font), 387 | [sfc_num] "" (CLGLCD_SOFT_CHARS), 388 | [sfc_mask] "" (CLGLCD_SOFT_CHARS-1), 389 | [sfc_upper] "" (256-CLGLCD_SOFT_CHARS), 390 | #endif 391 | [cl2_tcnt] "" (CL2_TIMER_CNT), 392 | [cl2_tccr_val] "" (CL2_TIMER_VAL), 393 | [cl2_tccr] "" (&CL2_TIMER_CR), 394 | [cl1_set_m] "" (CL1_SET_M), 395 | [cl1_clear_m] "" (CL1_CLEAR_M), 396 | [cl1_tccr] "" (&CL1_TIMER_CR), 397 | [data_port] "I" (_SFR_IO_ADDR(PORT(CLGLCD_DATA))), 398 | [flm_port] "I" (_SFR_IO_ADDR(PORT(CLGLCD_FLM))), 399 | [flm_bit] "" (BIT(CLGLCD_FLM)), 400 | [alt_m_port] "" (_SFR_IO_ADDR(PORT(CLGLCD_ALT_M))), 401 | [alt_m_portin] "" (_SFR_IO_ADDR(PIN(CLGLCD_ALT_M))), 402 | [alt_m_bit] "" (BIT(CLGLCD_ALT_M)) 403 | ); 404 | } 405 | 406 | // 407 | // CLGLCD API 408 | // 409 | 410 | void CLGLCD_init() { 411 | CLEAR(CLGLCD_DISPOFF); 412 | OUTPUT_PIN(CLGLCD_DISPOFF); 413 | #if defined(CLGLCD_BCKL_CTRL) 414 | CLEAR(CLGLCD_BCKL_CTRL); 415 | OUTPUT_PIN(CLGLCD_BCKL_CTRL); 416 | #endif 417 | #if defined(CLGLCD_VEE_CTRL) 418 | CLEAR(CLGLCD_VEE_CTRL); 419 | OUTPUT_PIN(CLGLCD_VEE_CTRL); 420 | #endif 421 | 422 | DDR(CLGLCD_DATA) = 0xF0; 423 | OUTPUT_PIN(CLGLCD_ALT_M); 424 | OUTPUT_PIN(CLGLCD_CL2); 425 | OUTPUT_PIN(CLGLCD_CL1); 426 | OUTPUT_PIN(CLGLCD_FLM); 427 | 428 | #if CLGLCD_SOFT_CHARS > 0 429 | for(uint8_t y=0; y> 8); 444 | 445 | // Shift out the row selector bit (FLM) from common drivers, 446 | // so we don't end up with two active bits 447 | CLEAR(CLGLCD_CL1); 448 | CLEAR(CLGLCD_FLM); 449 | for (uint8_t y=0; y<240; y++) { 450 | TOGGLE(CLGLCD_CL1); 451 | _NOP(); 452 | TOGGLE(CLGLCD_CL1); 453 | } 454 | 455 | // Setup the CL1 timer 456 | #if (TM_BASE(CLGLCD_CL1_OCnX) == 1) && (TM_BASE(CLGLCD_ALT_M_OCnX) == 1) 457 | TCCR1A = 0; 458 | TCCR1B = 0; 459 | TCNT1 = 0; 460 | ICR1 = CLGLCD_CL1_TICKS / 8 - 1; 461 | TM_OCR(CLGLCD_ALT_M_OCnX) = CLGLCD_CL1_TICKS / 8 - 1; 462 | TM_OCR(CLGLCD_CL1_OCnX) = CLGLCD_CL1_TICKS / 8 - 2; 463 | TCCR1A = CL1_CLEAR_M; 464 | TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11); // WGM13:0=15, prescaller 8 465 | TIFR1 |= (1 << TOV1); 466 | TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt 467 | #elif (TM_BASE(CLGLCD_CL1_OCnX) == 4 ) && (TM_BASE(CLGLCD_ALT_M_OCnX) == 4) 468 | TIMSK4 = 0; 469 | TCCR4A = 0; 470 | TCCR4B = 0; 471 | TCCR4C = 0; 472 | TCCR4D = 0; 473 | TCCR4E = 0; 474 | TC4H = 0; 475 | TCNT4 = 0; 476 | //TC4H = highByte(CLGLCD_CL1_TICKS / 8 - 1); 477 | OCR4C = lowByte(CLGLCD_CL1_TICKS / 8 - 1); // TOP 478 | TM_OCR(CLGLCD_ALT_M_OCnX) = lowByte(CLGLCD_CL1_TICKS / 8); 479 | TM_OCR(CLGLCD_CL1_OCnX) = lowByte(CLGLCD_CL1_TICKS / 8 - 2); 480 | TCCR4A = (1< 30 | 31 | #ifndef CLGLCD_CONFIG 32 | #include "clglcd_config.h" 33 | #endif 34 | #ifndef CLGLCD_FONT_LINES 35 | #include "clglcd_font.h" 36 | #endif 37 | 38 | // Screen SRAM buffer: screen[y][x] 39 | extern uint8_t volatile screen[CLGLCD_Y_LINES][40]; 40 | 41 | // SRAM font buffer: soft_font[character_line][character_offset] 42 | #if CLGLCD_SOFT_CHARS > 0 43 | extern uint8_t volatile soft_font[CLGLCD_FONT_LINES][CLGLCD_SOFT_CHARS] __attribute__((aligned(CLGLCD_SOFT_CHARS))); 44 | #endif 45 | 46 | // Setup LCD output pins and clear screen 47 | void CLGLCD_init(); 48 | 49 | // Turn on the LCD and start driving interrupt 50 | void CLGLCD_on(); 51 | 52 | // Turn off the LCD and stop driving interrupt 53 | void CLGLCD_off(); 54 | 55 | // Clear the screen, fill the screen buffer with zeroes 56 | void CLGLCD_clear_screen(); 57 | 58 | // Check if FLM signal is up, usefull when disabling interrputs 59 | bool CLGLCD_FLM_is_up(); 60 | 61 | 62 | #endif // CLGLCD_H 63 | -------------------------------------------------------------------------------- /examples/clglcd_qr_clock/clglcd_config.h: -------------------------------------------------------------------------------- 1 | // 2 | // Hardware configuration 3 | // 4 | 5 | #ifndef CLGLCD_CONFIG 6 | #define CLGLCD_CONFIG 7 | 8 | // 9 | // F-51543NFU <> Arduino 10 | // 11 | // 1 V LED- => (GND trough backlight MOSFET) 12 | // 2 V LED+ <= (Power, +5V) 13 | // 3 DISPOFF <= (Needs pull-down to GND) 14 | // 4 D3 <= (x,7) 15 | // 5 D2 <= (x,6) 16 | // 6 D1 <= (x,5) 17 | // 7 D0 <= (x,4) 18 | // 8 VEE => (Power, -24V) 19 | // 9 VSS == (GND) 20 | // 10 VDD <= (Power, +5V) 21 | // 11 V0 => (Power, -16.8V from trimmer) 22 | // 12 M <= (PWM, same timer as CL1) 23 | // 13 CL2 <= (PWM) 24 | // 14 CL1 <= (PWM, same timer as M) 25 | // 15 FLM <= (any) 26 | // 27 | 28 | #if defined(ARDUINO_AVR_UNO) 29 | 30 | #define CLGLCD_BCKL_CTRL B,5 31 | #define CLGLCD_DISPOFF B,4 32 | #define CLGLCD_DATA D,4-7 33 | #define CLGLCD_VEE_CTRL B,3 34 | #define CLGLCD_ALT_M B,1 35 | #define CLGLCD_ALT_M_OCnX 1,A 36 | #define CLGLCD_CL2 D,3 37 | #define CLGLCD_CL2_OCnX 2,B 38 | #define CLGLCD_CL1 B,2 39 | #define CLGLCD_CL1_OCnX 1,B 40 | #define CLGLCD_FLM B,0 41 | 42 | #elif defined(ARDUINO_AVR_LEONARDO) 43 | 44 | #define CLGLCD_BCKL_CTRL D,6 45 | #define CLGLCD_DISPOFF D,3 46 | #define CLGLCD_DATA B,4-7 47 | #define CLGLCD_VEE_CTRL E,6 48 | #define CLGLCD_ALT_M D,7 49 | #define CLGLCD_ALT_M_OCnX 4,D 50 | #define CLGLCD_CL2 C,6 51 | #define CLGLCD_CL2_OCnX 3,A 52 | #define CLGLCD_CL1 C,7 53 | #define CLGLCD_CL1_OCnX 4,A 54 | #define CLGLCD_FLM D,4 55 | 56 | #elif defined(ARDUINO_AVR_PROMICRO) 57 | 58 | #define CLGLCD_BCKL_CTRL D,2 59 | #define CLGLCD_DISPOFF D,3 60 | #define CLGLCD_DATA F,4-7 61 | #define CLGLCD_VEE_CTRL E,6 62 | #define CLGLCD_ALT_M B,5 63 | #define CLGLCD_ALT_M_OCnX 1,A 64 | #define CLGLCD_CL2 C,6 65 | #define CLGLCD_CL2_OCnX 3,A 66 | #define CLGLCD_CL1 B,6 67 | #define CLGLCD_CL1_OCnX 1,B 68 | #define CLGLCD_FLM B,2 69 | 70 | #else 71 | #error "No config for this board" 72 | #endif // Board config 73 | 74 | // Define the number of soft (runtime modifiable) characters 75 | // NOTE: Defining soft characters will eat SRAM and 76 | // slow down main code significantly 77 | // 78 | // Muse be power of 2. Supported values 8, 16, 32, 64 79 | //#define CLGLCD_SOFT_CHARS 32 80 | 81 | // Define to use upper codes (255-CLGLCD_SOFT_CHARS..255) 82 | // otherwise it it will use (0..CLGLCD_SOFT_CHARS) 83 | //#define CLGLCD_SOFT_UPPER 1 84 | 85 | #endif //CLGLCD_CONFIG 86 | -------------------------------------------------------------------------------- /examples/clglcd_qr_clock/clglcd_font.h: -------------------------------------------------------------------------------- 1 | // 2 | // QR drawing font for 'Controllerless GLCD' 3 | // 4 | // Only first 16 characters are defined 5 | // 6 | 7 | #define CLGLCD_FONT_LINES 10 8 | #define CLGLCD_Y_LINES 24 9 | 10 | // Layout is 8 bits of horizontal pixels of all 256 chracters 11 | // (256 bytes), mutiplied by number of vertial lines for the 12 | // characters in the font. 13 | 14 | const unsigned char fixed_font[CLGLCD_FONT_LINES * 256] __attribute__((progmem, aligned (256))) = { 15 | 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 18 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 20 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 21 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 22 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 23 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 24 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 25 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 26 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 28 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 31 | 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 37 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 38 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 39 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 40 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 41 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 43 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 44 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 46 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 47 | 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 49 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 50 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 51 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 54 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 55 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 56 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 57 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 58 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 59 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 60 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 61 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 62 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 63 | 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 64 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 65 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 66 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 67 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 68 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 69 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 70 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 71 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 72 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 73 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 74 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 75 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 76 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 77 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 78 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 79 | 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 80 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 81 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 82 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 83 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 84 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 85 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 86 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 87 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 88 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 89 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 90 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 91 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 92 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 93 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 94 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 95 | 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 96 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 97 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 98 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 99 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 100 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 101 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 102 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 103 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 104 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 105 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 106 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 107 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 108 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 109 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 110 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 111 | 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 112 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 113 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 114 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 115 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 116 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 117 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 118 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 119 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 120 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 121 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 122 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 123 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 124 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 125 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 126 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 127 | 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 128 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 129 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 130 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 131 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 132 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 133 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 134 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 135 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 136 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 137 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 138 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 139 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 140 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 141 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 142 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 143 | 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 144 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 145 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 146 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 147 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 148 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 149 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 150 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 151 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 152 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 153 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 154 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 155 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 156 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 157 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 158 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 159 | 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 0xff, 0xf0, 0x0f, 0x00, 160 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 161 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 162 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 163 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 164 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 165 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 166 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 167 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 168 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 169 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 170 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 171 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 172 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 173 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 174 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 175 | }; 176 | -------------------------------------------------------------------------------- /examples/clglcd_qr_clock/clglcd_qr_clock.ino: -------------------------------------------------------------------------------- 1 | // 2 | // QR clock on Controllerless LCD. 3 | // 4 | // Uses I2C DS1307 RTC module and RTCLib library 5 | // Uses Adafruit (4-wire resistive) TouchScreen library 6 | // 7 | // Original idea and sources used 8 | // http://ch00ftech.com/2013/04/23/optimizing-the-qr-clock/ 9 | // https://github.com/arduinoenigma/EnigmaQRClock 10 | // 11 | 12 | #include 13 | #include "RTClib.h" 14 | #include "TouchScreen.h" 15 | 16 | #include "clglcd_config.h" 17 | #include "clglcd_font.h" 18 | #include "clglcd.h" 19 | 20 | // Touch panel config 21 | #define TP_XL A0 22 | #define TP_YU A1 23 | #define TP_XR A2 24 | #define TP_YD A3 25 | #define TP_MIN_Z 200 26 | 27 | // Touch buttons position and size 28 | #define TP_VB_AREA 50 29 | #define TP_SWITCH_X 735 30 | #define TP_SWITCH_Y 275 31 | #define TP_INVERT_X 270 32 | #define TP_INVERT_Y 275 33 | #define TP_ROTATE_X 270 34 | #define TP_ROTATE_Y 735 35 | 36 | 37 | 38 | // Based on the font used 39 | #define WHITE 0x00 40 | #define BLACK 0x0F 41 | 42 | // Adjust defaults if needed 43 | uint8_t enable = 1; 44 | uint8_t invert = 1; 45 | uint8_t rotate = 0; 46 | 47 | TouchScreen ts = TouchScreen(TP_XL, TP_YU, TP_XR, TP_YD, 0); 48 | bool touch_sense = true; 49 | 50 | RTC_DS1307 rtc; 51 | unsigned char last_second; 52 | char adjust_buffer[32]; 53 | uint8_t adjust_cnt = 0; 54 | 55 | char display_string[18]; 56 | unsigned char outputmatrix[56]; 57 | 58 | // Defined in "choof_qr_code.c" 59 | extern "C" { 60 | unsigned char getbit(unsigned char * array, int pointer); 61 | void generate_qr_code (const char *input, unsigned char *outputmatrix); 62 | } 63 | 64 | void clear_screen() { 65 | if (invert) { 66 | memset((void*)&screen, WHITE, sizeof(screen)); 67 | } else { 68 | memset((void*)&screen, BLACK, sizeof(screen)); 69 | } 70 | } 71 | 72 | // Draws 21x21 QR code using characters 0-15 setup to 73 | // represent bitmap of four quadrants of character 74 | // box (8x10). For the X axis, QR bits are 3:2 expanded 75 | // (i.e. each QR bit is drawn with 1.5 charactes, or 3 quadrants). 76 | // Since QR code has uneven number of points, the Y offset 77 | // on 24 character lines was noticable, and everything is drawn 78 | // shifted down by half character. 79 | void draw_matrix(unsigned char *matrix) { 80 | unsigned char x, y; 81 | unsigned char c1, c2; 82 | unsigned char *scr_pos; 83 | unsigned char prev_row[11], c1p, c2p; 84 | unsigned char inv_mask = (1-invert); 85 | 86 | memset(&prev_row, 3*inv_mask, 10); 87 | prev_row[10] = inv_mask; 88 | 89 | for (y=0; y<21; y++) { 90 | scr_pos = (unsigned char*)&screen[y+1][4]; 91 | for (x = 0; x < 10; x++) { 92 | c1p = prev_row[x] >> 1; 93 | c2p = prev_row[x] & 1; 94 | if (rotate) { 95 | c1 = getbit(matrix, 42*x + y) ^ invert; 96 | c2 = getbit(matrix, 42*x + 21 + y) ^ invert; 97 | } else { 98 | c1 = getbit(matrix, 21*y + 2*x) ^ invert; 99 | c2 = getbit(matrix, 21*y + 2*x + 1) ^ invert; 100 | } 101 | *scr_pos++ = 12*c1p + 3*c1; 102 | *scr_pos++ = 8*c1p + 4*c2p + 2*c1 + c2; 103 | *scr_pos++ = 12*c2p + 3*c2; 104 | prev_row[x] = 2*c1 + c2; 105 | } 106 | 107 | c1p = prev_row[10]; 108 | if (rotate) { 109 | c1 = getbit(matrix, 21*20 + y) ^ invert; 110 | } else { 111 | c1 = getbit(matrix, 21*y + 20) ^ invert; 112 | } 113 | *scr_pos++ = 12*c1p + 3*c1; 114 | *scr_pos = 8*c1p + 2*c1 + 5*inv_mask; 115 | prev_row[10] = c1; 116 | } 117 | 118 | // Last half-row 119 | scr_pos = (unsigned char*)&screen[22][4]; 120 | for (x = 0; x < 10; x++) { 121 | c1p = prev_row[x] >> 1; 122 | c2p = prev_row[x] & 1; 123 | *scr_pos++ = 12*c1p + 3*inv_mask; 124 | *scr_pos++ = 8*c1p + 4*c2p + 3*inv_mask; 125 | *scr_pos++ = 12*c2p + 3*inv_mask; 126 | } 127 | c1p = prev_row[10]; 128 | *scr_pos++ = 12*c1p + 3*inv_mask; 129 | *scr_pos = 8*c1p + 7*inv_mask; 130 | } 131 | 132 | void setup() { 133 | CLGLCD_init(); 134 | 135 | delay(1000); 136 | Serial.begin(9600); 137 | clear_screen(); 138 | if (enable) CLGLCD_on(); 139 | 140 | memset(display_string, 0, sizeof(display_string)); 141 | 142 | if (!rtc.begin()) { 143 | sprintf(display_string, "RTC INIT ERR;"); 144 | generate_qr_code(display_string, outputmatrix); 145 | draw_matrix(outputmatrix); 146 | while (true); 147 | } 148 | } 149 | 150 | 151 | void loop() { 152 | DateTime dt; 153 | 154 | if (rtc.isrunning()) { 155 | 156 | do { 157 | delay(50); 158 | 159 | // Check for touch 160 | TSPoint p = ts.getPoint(); 161 | if (p.z > 200) { 162 | if (touch_sense) { 163 | if ((abs(TP_INVERT_X - p.x) < TP_VB_AREA) && (abs(TP_INVERT_Y - p.y) < TP_VB_AREA)) { 164 | invert ^= 1; 165 | clear_screen(); 166 | draw_matrix(outputmatrix); 167 | } 168 | if ((abs(TP_ROTATE_X - p.x) < TP_VB_AREA) && (abs(TP_ROTATE_Y - p.y) < TP_VB_AREA)) { 169 | rotate ^= 1; 170 | clear_screen(); 171 | draw_matrix(outputmatrix); 172 | } 173 | if ((abs(TP_SWITCH_X - p.x) < TP_VB_AREA) && (abs(TP_SWITCH_Y - p.y) < TP_VB_AREA)) { 174 | enable ^= 1; 175 | if (enable) { 176 | CLGLCD_on(); 177 | } else { 178 | CLGLCD_off(); 179 | } 180 | } 181 | touch_sense = false; 182 | } 183 | } else { 184 | touch_sense = true; 185 | } 186 | 187 | // Monitor Serial for time adjust string 188 | // sample input: date = "Dec-26-2009", time = "12:34:56", see RTCLib 189 | while (Serial.available()) { 190 | char c = Serial.read(); 191 | if (adjust_cnt < (sizeof(adjust_buffer) - 1)) { 192 | adjust_buffer[adjust_cnt++] = c; 193 | } 194 | if (c == '\r') { 195 | adjust_buffer[adjust_cnt] = '\0'; 196 | adjust_cnt = 0; 197 | char *sdate = strtok(adjust_buffer, " "); 198 | char *stime = strtok(NULL, "\r\n\0"); 199 | if ((sdate != NULL) && (stime != NULL)) { 200 | Serial.print("Set "); Serial.print(sdate); 201 | Serial.print(" "); Serial.println(stime); 202 | rtc.adjust(DateTime(sdate, stime)); 203 | } 204 | } 205 | } 206 | 207 | // Read RTC 208 | dt = rtc.now(); 209 | } while (dt.second() == last_second); 210 | last_second = dt.second(); 211 | 212 | sprintf(display_string, "T %02d:%02d:%02d;", dt.hour(), dt.minute(), dt.second()); 213 | generate_qr_code(display_string, outputmatrix); 214 | draw_matrix(outputmatrix); 215 | 216 | } else { 217 | 218 | sprintf(display_string, "RTC STOPPED;"); 219 | generate_qr_code(display_string, outputmatrix); 220 | draw_matrix(outputmatrix); 221 | delay(1000); 222 | 223 | } 224 | 225 | } 226 | -------------------------------------------------------------------------------- /examples/clglcd_simple/clglcd_simple.ino: -------------------------------------------------------------------------------- 1 | // Simple CLGLCD test 2 | 3 | // Display fixed bitmap in flash on 4 | // Controllerless Graphics LCD module 5 | 6 | #include "bitmap.h" 7 | 8 | #define X_RES 320 // pixels 9 | #define Y_RES 240 // pixels 10 | #define REFRESH_RATE 70 // Hz 11 | 12 | // Pin connection (Arduino Leonardo) 13 | #define DISPOFF D,6 // 12 14 | #define DATA B,7-4 // 11-8 15 | #define ALT_M E,6 // 7 16 | #define CL2 D,7 // 6 17 | #define CL1 C,6 // 5 18 | #define FLM D,4 // 4 19 | 20 | // End of config 21 | 22 | // 16MHz / 70Hz refresh / 240 lines ~= 952 ticks ~= 59.5us 23 | #define CL1_TICKS F_CPU / REFRESH_RATE / Y_RES 24 | 25 | #define BIT_(p,b) (b) 26 | #define BIT(cfg) BIT_(cfg) 27 | #define PORT_(p,b) (PORT ## p) 28 | #define PORT(cfg) PORT_(cfg) 29 | #define PIN_(p,b) (PIN ## p) 30 | #define PIN(cfg) PIN_(cfg) 31 | #define DDR_(p,b) (DDR ## p) 32 | #define DDR(cfg) DDR_(cfg) 33 | 34 | #define SET(cfg) PORT_(cfg) |= _BV(BIT_(cfg)) 35 | #define CLEAR(cfg) PORT_(cfg) &= ~_BV(BIT_(cfg)) 36 | #define TOGGLE(cfg) PIN_(cfg) = _BV(BIT_(cfg)) 37 | #define SWAP(b) b = (b << 4) | (b >> 4) 38 | 39 | void setup() { 40 | CLEAR(DISPOFF); 41 | DDR(DISPOFF) |= _BV(BIT(DISPOFF)); 42 | delay(2000); // Safety 43 | 44 | // Another way to say pinMode(x, OUTPUT) 45 | DDR(DATA) = 0xF0; 46 | DDR(ALT_M) |= _BV(BIT(ALT_M)); 47 | DDR(CL2) |= _BV(BIT(CL2)); 48 | DDR(CL1) |= _BV(BIT(CL1)); 49 | DDR(FLM) |= _BV(BIT(FLM)); 50 | 51 | // Reset clocks and empty common drivers 52 | CLEAR(CL1); 53 | CLEAR(CL2); 54 | CLEAR(FLM); 55 | for(uint8_t y = 0; y < Y_RES; y++) { 56 | TOGGLE(CL1); 57 | _NOP(); 58 | TOGGLE(CL1); 59 | } 60 | 61 | // Setup Timer1 62 | TCCR1A = 0; 63 | TCCR1B = 0; 64 | TCNT1 = 0; 65 | OCR1A = CL1_TICKS - 1; 66 | // CTC mode 4, prescaller 1 67 | TCCR1B = (1< 0 130 | uint8_t volatile soft_font[CLGLCD_FONT_LINES][CLGLCD_SOFT_CHARS] __attribute__((aligned(CLGLCD_SOFT_CHARS))); 131 | #endif 132 | 133 | // 134 | // Character generator ISR 135 | // 136 | 137 | #if CLGLCD_SOFT_CHARS < 1 // Fixed 138 | 139 | // Outputs 2 nibbles in 8 Fclk ticks 140 | #define SHIFT_OUT_BYTE() \ 141 | "out %[data_port], r24" "\n\t" \ 142 | "ld r30, X+" "\n\t" \ 143 | "swap r24" "\n\t" \ 144 | "out %[data_port], r24" "\n\t" \ 145 | "lpm r24, Z\n\t" 146 | 147 | #else // Fixed + Soft 148 | 149 | #define SHIFT_OUT_BYTE_SCR_LOAD() \ 150 | "ld r30, X+" "\n\t" \ 151 | 152 | #if CLGLCD_SOFT_UPPER == 1 153 | #define SHIFT_OUT_BYTE_BRANCH_3F() \ 154 | "cpi r30, %[sfc_upper]" "\n\t" \ 155 | "brcc 3f" "\n\t" 156 | #else 157 | #define SHIFT_OUT_BYTE_BRANCH_3F() \ 158 | "cpi r30, %[sfc_num]" "\n\t" \ 159 | "brcs 3f" "\n\t" 160 | #endif 161 | 162 | #define SHIFT_OUT_BYTE_FIXED_LOAD() \ 163 | "lpm r28, Z" "\n\t" \ 164 | 165 | #if CLGLCD_SOFT_UPPER == 1 166 | #define SHIFT_OUT_BYTE_SOFT_OFFSET() \ 167 | "mov r28, r30" "\n\t" \ 168 | "and r28, r0" "\n\t" 169 | #else 170 | #define SHIFT_OUT_BYTE_SOFT_OFFSET() \ 171 | "mov r28, r30" "\n\t" \ 172 | "or r28, r0" "\n\t" 173 | #endif 174 | 175 | #define SHIFT_OUT_BYTE_SOFT_LOAD() \ 176 | "ld r28, Y" "\n\t" 177 | 178 | // Outputs 2 nibbles in 12 Fclk ticks 179 | #define SHIFT_OUT_BYTE() \ 180 | "out %[data_port], r28" "\n\t" \ 181 | SHIFT_OUT_BYTE_SCR_LOAD() \ 182 | "swap r28" "\n\t" \ 183 | SHIFT_OUT_BYTE_BRANCH_3F() \ 184 | "out %[data_port], r28" "\n\t" \ 185 | SHIFT_OUT_BYTE_FIXED_LOAD() \ 186 | "rjmp 4f" "\n\t" \ 187 | "3:" \ 188 | /* We are a tick late with this nibble */ \ 189 | "out %[data_port], r28" "\n\t" \ 190 | SHIFT_OUT_BYTE_SOFT_OFFSET() \ 191 | SHIFT_OUT_BYTE_SOFT_LOAD() \ 192 | "4:" 193 | 194 | #endif 195 | 196 | // Common shifting macros 197 | #define SHIFT_OUT_3X_BYTE() \ 198 | SHIFT_OUT_BYTE() \ 199 | SHIFT_OUT_BYTE() \ 200 | SHIFT_OUT_BYTE() 201 | 202 | #define SHIFT_OUT_9X_BYTE() \ 203 | SHIFT_OUT_3X_BYTE() \ 204 | SHIFT_OUT_3X_BYTE() \ 205 | SHIFT_OUT_3X_BYTE() 206 | 207 | // Set vector based on config 208 | #if TM_BASE(CLGLCD_CL1_OCnX) == 1 209 | ISR(TIMER1_OVF_vect, ISR_NAKED) { 210 | #elif TM_BASE(CLGLCD_CL1_OCnX) == 2 211 | ISR(TIMER2_OVF_vect, ISR_NAKED) { 212 | #elif TM_BASE(CLGLCD_CL1_OCnX) == 3 213 | ISR(TIMER3_OVF_vect, ISR_NAKED) { 214 | #elif TM_BASE(CLGLCD_CL1_OCnX) == 4 215 | ISR(TIMER4_OVF_vect, ISR_NAKED) { 216 | #else 217 | #error "CL1 timer not implemented" 218 | #endif 219 | __asm__ ( 220 | ".equ font_end, %[font_start]+%[font_size]" "\n\t" 221 | ".equ screen_start, %[screen]" "\n\t" 222 | ".equ screen_end, %[screen]+%[screen_size]" "\n\t" 223 | #if CLGLCD_SOFT_CHARS > 0 224 | ".equ soft_offset, %[soft_font]" "\n\t" 225 | #endif 226 | 227 | "push r24" "\n\t" 228 | "in r24, 0x3f" "\n\t" 229 | "push r24" "\n\t" 230 | "push r26" "\n\t" 231 | "push r27" "\n\t" 232 | "push r30" "\n\t" 233 | "push r31\n\t" 234 | #if CLGLCD_SOFT_CHARS > 0 235 | "push r0" "\n\t" 236 | "push r1" "\n\t" 237 | "push r28" "\n\t" 238 | "push r29" "\n\t" 239 | #endif 240 | 241 | // Clear FLM 242 | "cbi %[flm_port], %[flm_bit]" "\n\t" 243 | 244 | "1:" 245 | // Pickup where we left off 246 | "lds r31, %[font_line]" "\n\t" 247 | "lds r26, %[scr_pos]" "\n\t" 248 | "lds r27, %[scr_pos]+1" "\n\t" 249 | 250 | // Check if we need to go to top of screen 251 | "ldi r30, hi8(screen_end)" "\n\t" 252 | "cpi r26, lo8(screen_end)" "\n\t" 253 | "cpc r27, r30" "\n\t" 254 | "brcs 2f" "\n\t" 255 | // We have sent all screen lines, ... 256 | // ... reset screen pointer, ... 257 | "ldi r26, lo8(screen_start)" "\n\t" 258 | "ldi r27, hi8(screen_start)" "\n\t" 259 | "sts %[scr_pos], r26" "\n\t" 260 | "sts %[scr_pos]+1, r27\n\t" 261 | // ... reset font line, ... 262 | "ldi r31, hi8(%[font_start])" "\n\t" 263 | // ... set FLM line UP, ... 264 | "sbi %[flm_port], %[flm_bit]" "\n\t" 265 | // ... and set ALT_M_OCnX to be flipped on 266 | // next CL1 timer match. 267 | "ldi r24, %[cl1_set_m]" "\n\t" 268 | "sbis %[alt_m_portin], %[alt_m_bit]" "\n\t" 269 | "ldi r24, %[cl1_clear_m]" "\n\t" 270 | "sts %[cl1_tccr], r24" "\n\t" 271 | 272 | "2:" 273 | #if CLGLCD_SOFT_CHARS < 1 // Fixed 274 | // Preload first byte in r24 275 | "ld r30, X+" "\n\t" 276 | "lpm r24, Z" "\n\t" 277 | #else // Soft + Fixed 278 | // Calculate YH and YL top bits from r31 font line 279 | "mov r29, r31" "\n\t" 280 | "subi r29, hi8(%[font_start])" "\n\t" 281 | "ldi r28, %[sfc_num]" "\n\t" 282 | "mul r28, r29" "\n\t" 283 | "ldi r28, lo8(soft_offset)" "\n\t" 284 | "ldi r29, hi8(soft_offset)" "\n\t" 285 | "add r0, r28" "\n\t" 286 | #if CLGLCD_SOFT_UPPER == 1 287 | "ldi r28, %[sfc_mask]\n\t" 288 | "or r0, r28" "\n\t" 289 | #endif 290 | "adc r29, r1" "\n\t" 291 | // Keep r0 as now it contains bitmask offset to soft font data 292 | 293 | // Preload first byte in r28 294 | SHIFT_OUT_BYTE_SCR_LOAD() 295 | SHIFT_OUT_BYTE_BRANCH_3F() 296 | SHIFT_OUT_BYTE_FIXED_LOAD() 297 | "rjmp 4f" "\n\t" 298 | "3:" 299 | SHIFT_OUT_BYTE_SOFT_OFFSET() 300 | SHIFT_OUT_BYTE_SOFT_LOAD() 301 | "4:" 302 | #endif // Fixed/Soft 303 | 304 | // Start timer 305 | "eor r30, r30" "\n\t" 306 | "sts %[cl2_tcnt], r30\n\t" 307 | "ldi r30, %[cl2_tccr_val]" "\n\t" 308 | "sts %[cl2_tccr], r30" "\n\t" 309 | CL2_TIMER_SYNC() 310 | 311 | // Shift out 39 (4x9+3) bytes 312 | SHIFT_OUT_9X_BYTE() 313 | SHIFT_OUT_9X_BYTE() 314 | SHIFT_OUT_9X_BYTE() 315 | SHIFT_OUT_9X_BYTE() 316 | SHIFT_OUT_3X_BYTE() 317 | 318 | // And for the last byte: 319 | // - Do not load next screen byte 320 | // - Stop timer ASAP, 321 | 322 | #if CLGLCD_SOFT_CHARS < 1 // Fixed font 323 | 324 | "out %[data_port], r24" "\n\t" 325 | "swap r24" "\n\t" 326 | "eor r30, r30" "\n\t" 327 | "nop" "\n\t" 328 | "out %[data_port], r24" "\n\t" 329 | // Stop the timer just as CL2 goes down (STS takes 2 cycles) 330 | "sts %[cl2_tccr], r30" "\n\t" 331 | 332 | #else // Fixed+Soft 333 | 334 | "out %[data_port], r28" "\n\t" 335 | "swap r28" "\n\t" 336 | "nop" "\n\t" 337 | "nop" "\n\t" 338 | //"ldi r24, %[cl1_bv]" "\n\t" 339 | "nop" "\n\t" 340 | "nop" "\n\t" 341 | // Output last nibble 342 | "out %[data_port], r28" "\n\t" 343 | // Stop the timer just as CL2 goes down (STS takes 2 cycles) 344 | "nop" "\n\t" 345 | "eor r1, r1" "\n\t" 346 | "sts %[cl2_tccr], r1" "\n\t" 347 | 348 | #endif // Fixed/Soft 349 | 350 | "subi r31, 0xFF" "\n\t" 351 | "cpi r31, hi8(font_end)" "\n\t" 352 | "brcs 5f" "\n\t" 353 | // We have sent one full screen line (CLGLCD_FONT_SIZE) 354 | // Store screen position (in X register), for next interrupt 355 | "sts %[scr_pos], r26" "\n\t" 356 | "sts %[scr_pos]+1, r27\n\t" 357 | // Rreset the font line 358 | "ldi r31, hi8(%[font_start])" "\n\t" 359 | "5:" 360 | // Store current font line for next interrupt 361 | "sts %[font_line], r31\n\t" 362 | 363 | #if CLGLCD_SOFT_CHARS > 0 364 | "pop r29" "\n\t" 365 | "pop r28" "\n\t" 366 | "pop r1" "\n\t" 367 | "pop r0" "\n\t" 368 | #endif 369 | "pop r31" "\n\t" 370 | "pop r30" "\n\t" 371 | "pop r27" "\n\t" 372 | "pop r26" "\n\t" 373 | "pop r24" "\n\t" 374 | "out 0x3f, r24" "\n\t" 375 | "pop r24" "\n\t" 376 | "reti" "\n\t" 377 | 378 | :: 379 | [font_start] "" (fixed_font), 380 | [font_size] "" (256 * CLGLCD_FONT_LINES), 381 | [screen] "" (screen), 382 | [screen_size] "" (40 * CLGLCD_Y_LINES), 383 | [scr_pos] "" (screen_pos), 384 | [font_line] "" (font_line), 385 | #if CLGLCD_SOFT_CHARS > 0 386 | [soft_font] "" (soft_font), 387 | [sfc_num] "" (CLGLCD_SOFT_CHARS), 388 | [sfc_mask] "" (CLGLCD_SOFT_CHARS-1), 389 | [sfc_upper] "" (256-CLGLCD_SOFT_CHARS), 390 | #endif 391 | [cl2_tcnt] "" (CL2_TIMER_CNT), 392 | [cl2_tccr_val] "" (CL2_TIMER_VAL), 393 | [cl2_tccr] "" (&CL2_TIMER_CR), 394 | [cl1_set_m] "" (CL1_SET_M), 395 | [cl1_clear_m] "" (CL1_CLEAR_M), 396 | [cl1_tccr] "" (&CL1_TIMER_CR), 397 | [data_port] "I" (_SFR_IO_ADDR(PORT(CLGLCD_DATA))), 398 | [flm_port] "I" (_SFR_IO_ADDR(PORT(CLGLCD_FLM))), 399 | [flm_bit] "" (BIT(CLGLCD_FLM)), 400 | [alt_m_port] "" (_SFR_IO_ADDR(PORT(CLGLCD_ALT_M))), 401 | [alt_m_portin] "" (_SFR_IO_ADDR(PIN(CLGLCD_ALT_M))), 402 | [alt_m_bit] "" (BIT(CLGLCD_ALT_M)) 403 | ); 404 | } 405 | 406 | // 407 | // CLGLCD API 408 | // 409 | 410 | void CLGLCD_init() { 411 | CLEAR(CLGLCD_DISPOFF); 412 | OUTPUT_PIN(CLGLCD_DISPOFF); 413 | #if defined(CLGLCD_BCKL_CTRL) 414 | CLEAR(CLGLCD_BCKL_CTRL); 415 | OUTPUT_PIN(CLGLCD_BCKL_CTRL); 416 | #endif 417 | #if defined(CLGLCD_VEE_CTRL) 418 | CLEAR(CLGLCD_VEE_CTRL); 419 | OUTPUT_PIN(CLGLCD_VEE_CTRL); 420 | #endif 421 | 422 | DDR(CLGLCD_DATA) = 0xF0; 423 | OUTPUT_PIN(CLGLCD_ALT_M); 424 | OUTPUT_PIN(CLGLCD_CL2); 425 | OUTPUT_PIN(CLGLCD_CL1); 426 | OUTPUT_PIN(CLGLCD_FLM); 427 | 428 | #if CLGLCD_SOFT_CHARS > 0 429 | for(uint8_t y=0; y> 8); 444 | 445 | // Shift out the row selector bit (FLM) from common drivers, 446 | // so we don't end up with two active bits 447 | CLEAR(CLGLCD_CL1); 448 | CLEAR(CLGLCD_FLM); 449 | for (uint8_t y=0; y<240; y++) { 450 | TOGGLE(CLGLCD_CL1); 451 | _NOP(); 452 | TOGGLE(CLGLCD_CL1); 453 | } 454 | 455 | // Setup the CL1 timer 456 | #if (TM_BASE(CLGLCD_CL1_OCnX) == 1) && (TM_BASE(CLGLCD_ALT_M_OCnX) == 1) 457 | TCCR1A = 0; 458 | TCCR1B = 0; 459 | TCNT1 = 0; 460 | ICR1 = CLGLCD_CL1_TICKS / 8 - 1; 461 | TM_OCR(CLGLCD_ALT_M_OCnX) = CLGLCD_CL1_TICKS / 8 - 1; 462 | TM_OCR(CLGLCD_CL1_OCnX) = CLGLCD_CL1_TICKS / 8 - 2; 463 | TCCR1A = CL1_CLEAR_M; 464 | TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11); // WGM13:0=15, prescaller 8 465 | TIFR1 |= (1 << TOV1); 466 | TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt 467 | #elif (TM_BASE(CLGLCD_CL1_OCnX) == 4 ) && (TM_BASE(CLGLCD_ALT_M_OCnX) == 4) 468 | TIMSK4 = 0; 469 | TCCR4A = 0; 470 | TCCR4B = 0; 471 | TCCR4C = 0; 472 | TCCR4D = 0; 473 | TCCR4E = 0; 474 | TC4H = 0; 475 | TCNT4 = 0; 476 | //TC4H = highByte(CLGLCD_CL1_TICKS / 8 - 1); 477 | OCR4C = lowByte(CLGLCD_CL1_TICKS / 8 - 1); // TOP 478 | TM_OCR(CLGLCD_ALT_M_OCnX) = lowByte(CLGLCD_CL1_TICKS / 8); 479 | TM_OCR(CLGLCD_CL1_OCnX) = lowByte(CLGLCD_CL1_TICKS / 8 - 2); 480 | TCCR4A = (1< 30 | 31 | #ifndef CLGLCD_CONFIG 32 | #include "clglcd_config.h" 33 | #endif 34 | #ifndef CLGLCD_FONT_LINES 35 | #include "clglcd_font.h" 36 | #endif 37 | 38 | // Screen SRAM buffer: screen[y][x] 39 | extern uint8_t volatile screen[CLGLCD_Y_LINES][40]; 40 | 41 | // SRAM font buffer: soft_font[character_line][character_offset] 42 | #if CLGLCD_SOFT_CHARS > 0 43 | extern uint8_t volatile soft_font[CLGLCD_FONT_LINES][CLGLCD_SOFT_CHARS] __attribute__((aligned(CLGLCD_SOFT_CHARS))); 44 | #endif 45 | 46 | // Setup LCD output pins and clear screen 47 | void CLGLCD_init(); 48 | 49 | // Turn on the LCD and start driving interrupt 50 | void CLGLCD_on(); 51 | 52 | // Turn off the LCD and stop driving interrupt 53 | void CLGLCD_off(); 54 | 55 | // Clear the screen, fill the screen buffer with zeroes 56 | void CLGLCD_clear_screen(); 57 | 58 | // Check if FLM signal is up, usefull when disabling interrputs 59 | bool CLGLCD_FLM_is_up(); 60 | 61 | 62 | #endif // CLGLCD_H 63 | -------------------------------------------------------------------------------- /examples/clglcd_soft/clglcd_config.h: -------------------------------------------------------------------------------- 1 | // 2 | // Hardware configuration 3 | // 4 | 5 | #ifndef CLGLCD_CONFIG 6 | #define CLGLCD_CONFIG 7 | 8 | // 9 | // F-51543NFU <> Arduino 10 | // 11 | // 1 V LED- => (GND trough backlight MOSFET) 12 | // 2 V LED+ <= (Power, +5V) 13 | // 3 DISPOFF <= (Needs pull-down to GND) 14 | // 4 D3 <= (x,7) 15 | // 5 D2 <= (x,6) 16 | // 6 D1 <= (x,5) 17 | // 7 D0 <= (x,4) 18 | // 8 VEE => (Power, -24V) 19 | // 9 VSS == (GND) 20 | // 10 VDD <= (Power, +5V) 21 | // 11 V0 => (Power, -16.8V from trimmer) 22 | // 12 M <= (PWM, same timer as CL1) 23 | // 13 CL2 <= (PWM) 24 | // 14 CL1 <= (PWM, same timer as M) 25 | // 15 FLM <= (any) 26 | // 27 | 28 | #if defined(ARDUINO_AVR_UNO) 29 | 30 | #define CLGLCD_BCKL_CTRL B,5 31 | #define CLGLCD_DISPOFF B,4 32 | #define CLGLCD_DATA D,4-7 33 | #define CLGLCD_VEE_CTRL B,3 34 | #define CLGLCD_ALT_M B,1 35 | #define CLGLCD_ALT_M_OCnX 1,A 36 | #define CLGLCD_CL2 D,3 37 | #define CLGLCD_CL2_OCnX 2,B 38 | #define CLGLCD_CL1 B,2 39 | #define CLGLCD_CL1_OCnX 1,B 40 | #define CLGLCD_FLM B,0 41 | 42 | #elif defined(ARDUINO_AVR_LEONARDO) 43 | 44 | #define CLGLCD_BCKL_CTRL D,6 45 | #define CLGLCD_DISPOFF D,3 46 | #define CLGLCD_DATA B,4-7 47 | #define CLGLCD_VEE_CTRL E,6 48 | #define CLGLCD_ALT_M D,7 49 | #define CLGLCD_ALT_M_OCnX 4,D 50 | #define CLGLCD_CL2 C,6 51 | #define CLGLCD_CL2_OCnX 3,A 52 | #define CLGLCD_CL1 C,7 53 | #define CLGLCD_CL1_OCnX 4,A 54 | #define CLGLCD_FLM D,4 55 | 56 | #elif defined(ARDUINO_AVR_PROMICRO) 57 | 58 | #define CLGLCD_BCKL_CTRL D,2 59 | #define CLGLCD_DISPOFF D,3 60 | #define CLGLCD_DATA F,4-7 61 | #define CLGLCD_VEE_CTRL E,6 62 | #define CLGLCD_ALT_M B,5 63 | #define CLGLCD_ALT_M_OCnX 1,A 64 | #define CLGLCD_CL2 C,6 65 | #define CLGLCD_CL2_OCnX 3,A 66 | #define CLGLCD_CL1 B,6 67 | #define CLGLCD_CL1_OCnX 1,B 68 | #define CLGLCD_FLM B,2 69 | 70 | #else 71 | #error "No config for this board" 72 | #endif // Board config 73 | 74 | // Define the number of soft (runtime modifiable) characters 75 | // NOTE: Defining soft characters will eat SRAM and 76 | // slow down main code significantly 77 | // 78 | // Muse be power of 2. Supported values 8, 16, 32, 64 79 | #define CLGLCD_SOFT_CHARS 64 80 | 81 | // Define to use upper codes (255-CLGLCD_SOFT_CHARS..255) 82 | // otherwise it it will use (0..CLGLCD_SOFT_CHARS) 83 | #define CLGLCD_SOFT_UPPER 1 84 | 85 | #endif //CLGLCD_CONFIG 86 | -------------------------------------------------------------------------------- /examples/clglcd_soft/clglcd_soft.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "clglcd.h" 4 | 5 | // Oranized for 64 8x16 'soft font' character in 16x4 screen buffer matrix 6 | const unsigned char logo[CLGLCD_FONT_LINES][CLGLCD_SOFT_CHARS] __attribute__((progmem)) = { 7 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 8 | 0x00, 0x7f, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xfe, 0x00, 9 | 0x07, 0xff, 0x80, 0x3f, 0xff, 0xe0, 0x00, 0xff, 0xff, 0x00, 0x07, 0xff, 0xfc, 0x01, 0xff, 0xe0, 10 | 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 11 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 12 | 0x00, 0x7f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0x00, 0x03, 0xff, 0xfe, 0x00, 13 | 0x07, 0xff, 0x80, 0x3f, 0xff, 0xe0, 0x01, 0xff, 0xff, 0x80, 0x07, 0xff, 0xfc, 0x01, 0xff, 0xe0, 14 | 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 16 | 0x00, 0xff, 0xff, 0x00, 0x00, 0x1f, 0xff, 0xf8, 0x1f, 0xff, 0xfc, 0x00, 0x00, 0xff, 0xff, 0x00, 17 | 0x07, 0xff, 0x80, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x80, 0x00, 0x3f, 0x80, 0x01, 0xff, 0xe0, 18 | 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 20 | 0x01, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0xfc, 0x3f, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0x80, 21 | 0x07, 0xff, 0x80, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, 0x00, 0x3f, 0x80, 0x01, 0xff, 0xe0, 22 | 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 23 | 0x00, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x00, 0x00, 24 | 0x01, 0xff, 0xf8, 0x00, 0x00, 0x03, 0xff, 0xfe, 0x7f, 0xff, 0xc0, 0x00, 0x00, 0x1f, 0xff, 0x80, 25 | 0x07, 0xff, 0x80, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0x80, 0x01, 0xff, 0xe0, 26 | 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 27 | 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0x80, 0x00, 0x00, 28 | 0x01, 0xff, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x1f, 0xff, 0x80, 29 | 0x07, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0x80, 0x03, 0xff, 0xe0, 30 | 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 31 | 0x00, 0x00, 0x07, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xe0, 0x00, 0x00, 32 | 0x03, 0xff, 0xf0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0x80, 0x0f, 0xff, 0xc0, 33 | 0x07, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x00, 0x3f, 0x80, 0x03, 0xff, 0xe0, 34 | 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 35 | 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 36 | 0x03, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0x80, 0x07, 0xff, 0xc0, 37 | 0x03, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0x80, 0x07, 0xff, 0xc0, 38 | 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 39 | 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 40 | 0x03, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0x80, 0x07, 0xff, 0xc0, 41 | 0x03, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xff, 0xc0, 42 | 0x00, 0x00, 0x1f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x00, 0x00, 43 | 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 44 | 0x07, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x00, 0x3f, 0x80, 0x03, 0xff, 0xe0, 45 | 0x03, 0xff, 0xf0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xc0, 46 | 0x00, 0x00, 0x07, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xe0, 0x00, 0x00, 47 | 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 48 | 0x07, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0x80, 0x03, 0xff, 0xe0, 49 | 0x01, 0xff, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x1f, 0xff, 0x80, 50 | 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0x80, 0x00, 0x00, 51 | 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 52 | 0x07, 0xff, 0x80, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0x80, 0x01, 0xff, 0xe0, 53 | 0x01, 0xff, 0xfc, 0x00, 0x00, 0x07, 0xff, 0xfe, 0x7f, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0x80, 54 | 0x00, 0x00, 0x00, 0x1f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x00, 0x00, 55 | 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 56 | 0x07, 0xff, 0x80, 0x3f, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xfc, 0x01, 0xff, 0xe0, 57 | 0x00, 0xff, 0xfe, 0x00, 0x00, 0x0f, 0xff, 0xfc, 0x3f, 0xff, 0xf0, 0x00, 0x00, 0x7f, 0xff, 0x00, 58 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 59 | 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 60 | 0x07, 0xff, 0x80, 0x3f, 0xff, 0xe0, 0x01, 0xff, 0xff, 0x80, 0x07, 0xff, 0xfc, 0x01, 0xff, 0xe0, 61 | 0x00, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xf8, 0x1f, 0xff, 0xfc, 0x00, 0x00, 0xff, 0xff, 0x00, 62 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 63 | 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 64 | 0x07, 0xff, 0x80, 0x3f, 0xff, 0xe0, 0x00, 0xff, 0xff, 0x00, 0x07, 0xff, 0xfc, 0x01, 0xff, 0xe0, 65 | 0x00, 0x7f, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0x00, 0x03, 0xff, 0xfe, 0x00, 66 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 67 | 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 68 | 0x07, 0xff, 0x80, 0x3f, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xfc, 0x01, 0xff, 0xe0, 69 | 0x00, 0x7f, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xe0, 0x1f, 0xff, 0xfe, 0x00, 70 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 71 | }; 72 | 73 | void setup() { 74 | CLGLCD_init(); 75 | delay(2000); // Safety 76 | CLGLCD_on(); 77 | CLGLCD_clear_screen(); 78 | for(uint8_t y=0; y> 8); 103 | x += 15; 104 | // Why does GCC optimize away loops counting backwards ? 105 | for(uint8_t i=0; i<16; i++) { 106 | shifted = (soft_font[y][x] << 1); 107 | soft_font[y][x--] = (uint8_t)(shifted | carry); 108 | carry = (shifted >> 8); 109 | } 110 | } 111 | } 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /examples/clglcd_text/clglcd.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Character generator for Controllerless Graphics LCD Display 3 | * 4 | * Copyright (c) 2019 Ivan Kostoski 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 all 14 | * 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 THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include "clglcd.h" 27 | 28 | #define BIT_(p,b) (b) 29 | #define BIT(cfg) BIT_(cfg) 30 | #define PORT_(p,b) (PORT ## p) 31 | #define PORT(cfg) PORT_(cfg) 32 | #define PIN_(p,b) (PIN ## p) 33 | #define PIN(cfg) PIN_(cfg) 34 | #define DDR_(p,b) (DDR ## p) 35 | #define DDR(cfg) DDR_(cfg) 36 | 37 | #define SET(cfg) PORT_(cfg) |= _BV(BIT_(cfg)) 38 | #define CLEAR(cfg) PORT_(cfg) &= ~_BV(BIT_(cfg)) 39 | #define TOGGLE(cfg) PIN_(cfg) = _BV(BIT_(cfg)) 40 | 41 | #define TM_BASE_(n,x) (n) 42 | #define TM_BASE(cfg) TM_BASE_(cfg) 43 | #define TM_LINE_A 1 44 | #define TM_LINE_B 2 45 | #define TM_LINE_C 3 46 | #define TM_LINE_D 4 47 | #define TM_LINE_(n,x) (TM_LINE_ ## x) 48 | #define TM_LINE(cfg) TM_LINE_(cfg) 49 | #define TM_CR_(n,x) (TCCR ## n ## x) 50 | #define TM_CR(cfg) TM_CR_(cfg) 51 | #define TM_OCR_(n,x) (OCR ## n ## x) 52 | #define TM_OCR(cfg) TM_OCR_(cfg) 53 | 54 | #define TM_FPWM_MODE2_(n,x) (1< 0 130 | uint8_t volatile soft_font[CLGLCD_FONT_LINES][CLGLCD_SOFT_CHARS] __attribute__((aligned(CLGLCD_SOFT_CHARS))); 131 | #endif 132 | 133 | // 134 | // Character generator ISR 135 | // 136 | 137 | #if CLGLCD_SOFT_CHARS < 1 // Fixed 138 | 139 | // Outputs 2 nibbles in 8 Fclk ticks 140 | #define SHIFT_OUT_BYTE() \ 141 | "out %[data_port], r24" "\n\t" \ 142 | "ld r30, X+" "\n\t" \ 143 | "swap r24" "\n\t" \ 144 | "out %[data_port], r24" "\n\t" \ 145 | "lpm r24, Z\n\t" 146 | 147 | #else // Fixed + Soft 148 | 149 | #define SHIFT_OUT_BYTE_SCR_LOAD() \ 150 | "ld r30, X+" "\n\t" \ 151 | 152 | #if CLGLCD_SOFT_UPPER == 1 153 | #define SHIFT_OUT_BYTE_BRANCH_3F() \ 154 | "cpi r30, %[sfc_upper]" "\n\t" \ 155 | "brcc 3f" "\n\t" 156 | #else 157 | #define SHIFT_OUT_BYTE_BRANCH_3F() \ 158 | "cpi r30, %[sfc_num]" "\n\t" \ 159 | "brcs 3f" "\n\t" 160 | #endif 161 | 162 | #define SHIFT_OUT_BYTE_FIXED_LOAD() \ 163 | "lpm r28, Z" "\n\t" \ 164 | 165 | #if CLGLCD_SOFT_UPPER == 1 166 | #define SHIFT_OUT_BYTE_SOFT_OFFSET() \ 167 | "mov r28, r30" "\n\t" \ 168 | "and r28, r0" "\n\t" 169 | #else 170 | #define SHIFT_OUT_BYTE_SOFT_OFFSET() \ 171 | "mov r28, r30" "\n\t" \ 172 | "or r28, r0" "\n\t" 173 | #endif 174 | 175 | #define SHIFT_OUT_BYTE_SOFT_LOAD() \ 176 | "ld r28, Y" "\n\t" 177 | 178 | // Outputs 2 nibbles in 12 Fclk ticks 179 | #define SHIFT_OUT_BYTE() \ 180 | "out %[data_port], r28" "\n\t" \ 181 | SHIFT_OUT_BYTE_SCR_LOAD() \ 182 | "swap r28" "\n\t" \ 183 | SHIFT_OUT_BYTE_BRANCH_3F() \ 184 | "out %[data_port], r28" "\n\t" \ 185 | SHIFT_OUT_BYTE_FIXED_LOAD() \ 186 | "rjmp 4f" "\n\t" \ 187 | "3:" \ 188 | /* We are a tick late with this nibble */ \ 189 | "out %[data_port], r28" "\n\t" \ 190 | SHIFT_OUT_BYTE_SOFT_OFFSET() \ 191 | SHIFT_OUT_BYTE_SOFT_LOAD() \ 192 | "4:" 193 | 194 | #endif 195 | 196 | // Common shifting macros 197 | #define SHIFT_OUT_3X_BYTE() \ 198 | SHIFT_OUT_BYTE() \ 199 | SHIFT_OUT_BYTE() \ 200 | SHIFT_OUT_BYTE() 201 | 202 | #define SHIFT_OUT_9X_BYTE() \ 203 | SHIFT_OUT_3X_BYTE() \ 204 | SHIFT_OUT_3X_BYTE() \ 205 | SHIFT_OUT_3X_BYTE() 206 | 207 | // Set vector based on config 208 | #if TM_BASE(CLGLCD_CL1_OCnX) == 1 209 | ISR(TIMER1_OVF_vect, ISR_NAKED) { 210 | #elif TM_BASE(CLGLCD_CL1_OCnX) == 2 211 | ISR(TIMER2_OVF_vect, ISR_NAKED) { 212 | #elif TM_BASE(CLGLCD_CL1_OCnX) == 3 213 | ISR(TIMER3_OVF_vect, ISR_NAKED) { 214 | #elif TM_BASE(CLGLCD_CL1_OCnX) == 4 215 | ISR(TIMER4_OVF_vect, ISR_NAKED) { 216 | #else 217 | #error "CL1 timer not implemented" 218 | #endif 219 | __asm__ ( 220 | ".equ font_end, %[font_start]+%[font_size]" "\n\t" 221 | ".equ screen_start, %[screen]" "\n\t" 222 | ".equ screen_end, %[screen]+%[screen_size]" "\n\t" 223 | #if CLGLCD_SOFT_CHARS > 0 224 | ".equ soft_offset, %[soft_font]" "\n\t" 225 | #endif 226 | 227 | "push r24" "\n\t" 228 | "in r24, 0x3f" "\n\t" 229 | "push r24" "\n\t" 230 | "push r26" "\n\t" 231 | "push r27" "\n\t" 232 | "push r30" "\n\t" 233 | "push r31\n\t" 234 | #if CLGLCD_SOFT_CHARS > 0 235 | "push r0" "\n\t" 236 | "push r1" "\n\t" 237 | "push r28" "\n\t" 238 | "push r29" "\n\t" 239 | #endif 240 | 241 | // Clear FLM 242 | "cbi %[flm_port], %[flm_bit]" "\n\t" 243 | 244 | "1:" 245 | // Pickup where we left off 246 | "lds r31, %[font_line]" "\n\t" 247 | "lds r26, %[scr_pos]" "\n\t" 248 | "lds r27, %[scr_pos]+1" "\n\t" 249 | 250 | // Check if we need to go to top of screen 251 | "ldi r30, hi8(screen_end)" "\n\t" 252 | "cpi r26, lo8(screen_end)" "\n\t" 253 | "cpc r27, r30" "\n\t" 254 | "brcs 2f" "\n\t" 255 | // We have sent all screen lines, ... 256 | // ... reset screen pointer, ... 257 | "ldi r26, lo8(screen_start)" "\n\t" 258 | "ldi r27, hi8(screen_start)" "\n\t" 259 | "sts %[scr_pos], r26" "\n\t" 260 | "sts %[scr_pos]+1, r27\n\t" 261 | // ... reset font line, ... 262 | "ldi r31, hi8(%[font_start])" "\n\t" 263 | // ... set FLM line UP, ... 264 | "sbi %[flm_port], %[flm_bit]" "\n\t" 265 | // ... and set ALT_M_OCnX to be flipped on 266 | // next CL1 timer match. 267 | "ldi r24, %[cl1_set_m]" "\n\t" 268 | "sbis %[alt_m_portin], %[alt_m_bit]" "\n\t" 269 | "ldi r24, %[cl1_clear_m]" "\n\t" 270 | "sts %[cl1_tccr], r24" "\n\t" 271 | 272 | "2:" 273 | #if CLGLCD_SOFT_CHARS < 1 // Fixed 274 | // Preload first byte in r24 275 | "ld r30, X+" "\n\t" 276 | "lpm r24, Z" "\n\t" 277 | #else // Soft + Fixed 278 | // Calculate YH and YL top bits from r31 font line 279 | "mov r29, r31" "\n\t" 280 | "subi r29, hi8(%[font_start])" "\n\t" 281 | "ldi r28, %[sfc_num]" "\n\t" 282 | "mul r28, r29" "\n\t" 283 | "ldi r28, lo8(soft_offset)" "\n\t" 284 | "ldi r29, hi8(soft_offset)" "\n\t" 285 | "add r0, r28" "\n\t" 286 | #if CLGLCD_SOFT_UPPER == 1 287 | "ldi r28, %[sfc_mask]\n\t" 288 | "or r0, r28" "\n\t" 289 | #endif 290 | "adc r29, r1" "\n\t" 291 | // Keep r0 as now it contains bitmask offset to soft font data 292 | 293 | // Preload first byte in r28 294 | SHIFT_OUT_BYTE_SCR_LOAD() 295 | SHIFT_OUT_BYTE_BRANCH_3F() 296 | SHIFT_OUT_BYTE_FIXED_LOAD() 297 | "rjmp 4f" "\n\t" 298 | "3:" 299 | SHIFT_OUT_BYTE_SOFT_OFFSET() 300 | SHIFT_OUT_BYTE_SOFT_LOAD() 301 | "4:" 302 | #endif // Fixed/Soft 303 | 304 | // Start timer 305 | "eor r30, r30" "\n\t" 306 | "sts %[cl2_tcnt], r30\n\t" 307 | "ldi r30, %[cl2_tccr_val]" "\n\t" 308 | "sts %[cl2_tccr], r30" "\n\t" 309 | CL2_TIMER_SYNC() 310 | 311 | // Shift out 39 (4x9+3) bytes 312 | SHIFT_OUT_9X_BYTE() 313 | SHIFT_OUT_9X_BYTE() 314 | SHIFT_OUT_9X_BYTE() 315 | SHIFT_OUT_9X_BYTE() 316 | SHIFT_OUT_3X_BYTE() 317 | 318 | // And for the last byte: 319 | // - Do not load next screen byte 320 | // - Stop timer ASAP, 321 | 322 | #if CLGLCD_SOFT_CHARS < 1 // Fixed font 323 | 324 | "out %[data_port], r24" "\n\t" 325 | "swap r24" "\n\t" 326 | "eor r30, r30" "\n\t" 327 | "nop" "\n\t" 328 | "out %[data_port], r24" "\n\t" 329 | // Stop the timer just as CL2 goes down (STS takes 2 cycles) 330 | "sts %[cl2_tccr], r30" "\n\t" 331 | 332 | #else // Fixed+Soft 333 | 334 | "out %[data_port], r28" "\n\t" 335 | "swap r28" "\n\t" 336 | "nop" "\n\t" 337 | "nop" "\n\t" 338 | //"ldi r24, %[cl1_bv]" "\n\t" 339 | "nop" "\n\t" 340 | "nop" "\n\t" 341 | // Output last nibble 342 | "out %[data_port], r28" "\n\t" 343 | // Stop the timer just as CL2 goes down (STS takes 2 cycles) 344 | "nop" "\n\t" 345 | "eor r1, r1" "\n\t" 346 | "sts %[cl2_tccr], r1" "\n\t" 347 | 348 | #endif // Fixed/Soft 349 | 350 | "subi r31, 0xFF" "\n\t" 351 | "cpi r31, hi8(font_end)" "\n\t" 352 | "brcs 5f" "\n\t" 353 | // We have sent one full screen line (CLGLCD_FONT_SIZE) 354 | // Store screen position (in X register), for next interrupt 355 | "sts %[scr_pos], r26" "\n\t" 356 | "sts %[scr_pos]+1, r27\n\t" 357 | // Rreset the font line 358 | "ldi r31, hi8(%[font_start])" "\n\t" 359 | "5:" 360 | // Store current font line for next interrupt 361 | "sts %[font_line], r31\n\t" 362 | 363 | #if CLGLCD_SOFT_CHARS > 0 364 | "pop r29" "\n\t" 365 | "pop r28" "\n\t" 366 | "pop r1" "\n\t" 367 | "pop r0" "\n\t" 368 | #endif 369 | "pop r31" "\n\t" 370 | "pop r30" "\n\t" 371 | "pop r27" "\n\t" 372 | "pop r26" "\n\t" 373 | "pop r24" "\n\t" 374 | "out 0x3f, r24" "\n\t" 375 | "pop r24" "\n\t" 376 | "reti" "\n\t" 377 | 378 | :: 379 | [font_start] "" (fixed_font), 380 | [font_size] "" (256 * CLGLCD_FONT_LINES), 381 | [screen] "" (screen), 382 | [screen_size] "" (40 * CLGLCD_Y_LINES), 383 | [scr_pos] "" (screen_pos), 384 | [font_line] "" (font_line), 385 | #if CLGLCD_SOFT_CHARS > 0 386 | [soft_font] "" (soft_font), 387 | [sfc_num] "" (CLGLCD_SOFT_CHARS), 388 | [sfc_mask] "" (CLGLCD_SOFT_CHARS-1), 389 | [sfc_upper] "" (256-CLGLCD_SOFT_CHARS), 390 | #endif 391 | [cl2_tcnt] "" (CL2_TIMER_CNT), 392 | [cl2_tccr_val] "" (CL2_TIMER_VAL), 393 | [cl2_tccr] "" (&CL2_TIMER_CR), 394 | [cl1_set_m] "" (CL1_SET_M), 395 | [cl1_clear_m] "" (CL1_CLEAR_M), 396 | [cl1_tccr] "" (&CL1_TIMER_CR), 397 | [data_port] "I" (_SFR_IO_ADDR(PORT(CLGLCD_DATA))), 398 | [flm_port] "I" (_SFR_IO_ADDR(PORT(CLGLCD_FLM))), 399 | [flm_bit] "" (BIT(CLGLCD_FLM)), 400 | [alt_m_port] "" (_SFR_IO_ADDR(PORT(CLGLCD_ALT_M))), 401 | [alt_m_portin] "" (_SFR_IO_ADDR(PIN(CLGLCD_ALT_M))), 402 | [alt_m_bit] "" (BIT(CLGLCD_ALT_M)) 403 | ); 404 | } 405 | 406 | // 407 | // CLGLCD API 408 | // 409 | 410 | void CLGLCD_init() { 411 | CLEAR(CLGLCD_DISPOFF); 412 | OUTPUT_PIN(CLGLCD_DISPOFF); 413 | #if defined(CLGLCD_BCKL_CTRL) 414 | CLEAR(CLGLCD_BCKL_CTRL); 415 | OUTPUT_PIN(CLGLCD_BCKL_CTRL); 416 | #endif 417 | #if defined(CLGLCD_VEE_CTRL) 418 | CLEAR(CLGLCD_VEE_CTRL); 419 | OUTPUT_PIN(CLGLCD_VEE_CTRL); 420 | #endif 421 | 422 | DDR(CLGLCD_DATA) = 0xF0; 423 | OUTPUT_PIN(CLGLCD_ALT_M); 424 | OUTPUT_PIN(CLGLCD_CL2); 425 | OUTPUT_PIN(CLGLCD_CL1); 426 | OUTPUT_PIN(CLGLCD_FLM); 427 | 428 | #if CLGLCD_SOFT_CHARS > 0 429 | for(uint8_t y=0; y> 8); 444 | 445 | // Shift out the row selector bit (FLM) from common drivers, 446 | // so we don't end up with two active bits 447 | CLEAR(CLGLCD_CL1); 448 | CLEAR(CLGLCD_FLM); 449 | for (uint8_t y=0; y<240; y++) { 450 | TOGGLE(CLGLCD_CL1); 451 | _NOP(); 452 | TOGGLE(CLGLCD_CL1); 453 | } 454 | 455 | // Setup the CL1 timer 456 | #if (TM_BASE(CLGLCD_CL1_OCnX) == 1) && (TM_BASE(CLGLCD_ALT_M_OCnX) == 1) 457 | TCCR1A = 0; 458 | TCCR1B = 0; 459 | TCNT1 = 0; 460 | ICR1 = CLGLCD_CL1_TICKS / 8 - 1; 461 | TM_OCR(CLGLCD_ALT_M_OCnX) = CLGLCD_CL1_TICKS / 8 - 1; 462 | TM_OCR(CLGLCD_CL1_OCnX) = CLGLCD_CL1_TICKS / 8 - 2; 463 | TCCR1A = CL1_CLEAR_M; 464 | TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11); // WGM13:0=15, prescaller 8 465 | TIFR1 |= (1 << TOV1); 466 | TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt 467 | #elif (TM_BASE(CLGLCD_CL1_OCnX) == 4 ) && (TM_BASE(CLGLCD_ALT_M_OCnX) == 4) 468 | TIMSK4 = 0; 469 | TCCR4A = 0; 470 | TCCR4B = 0; 471 | TCCR4C = 0; 472 | TCCR4D = 0; 473 | TCCR4E = 0; 474 | TC4H = 0; 475 | TCNT4 = 0; 476 | //TC4H = highByte(CLGLCD_CL1_TICKS / 8 - 1); 477 | OCR4C = lowByte(CLGLCD_CL1_TICKS / 8 - 1); // TOP 478 | TM_OCR(CLGLCD_ALT_M_OCnX) = lowByte(CLGLCD_CL1_TICKS / 8); 479 | TM_OCR(CLGLCD_CL1_OCnX) = lowByte(CLGLCD_CL1_TICKS / 8 - 2); 480 | TCCR4A = (1< 30 | 31 | #ifndef CLGLCD_CONFIG 32 | #include "clglcd_config.h" 33 | #endif 34 | #ifndef CLGLCD_FONT_LINES 35 | #include "clglcd_font.h" 36 | #endif 37 | 38 | // Screen SRAM buffer: screen[y][x] 39 | extern uint8_t volatile screen[CLGLCD_Y_LINES][40]; 40 | 41 | // SRAM font buffer: soft_font[character_line][character_offset] 42 | #if CLGLCD_SOFT_CHARS > 0 43 | extern uint8_t volatile soft_font[CLGLCD_FONT_LINES][CLGLCD_SOFT_CHARS] __attribute__((aligned(CLGLCD_SOFT_CHARS))); 44 | #endif 45 | 46 | // Setup LCD output pins and clear screen 47 | void CLGLCD_init(); 48 | 49 | // Turn on the LCD and start driving interrupt 50 | void CLGLCD_on(); 51 | 52 | // Turn off the LCD and stop driving interrupt 53 | void CLGLCD_off(); 54 | 55 | // Clear the screen, fill the screen buffer with zeroes 56 | void CLGLCD_clear_screen(); 57 | 58 | // Check if FLM signal is up, usefull when disabling interrputs 59 | bool CLGLCD_FLM_is_up(); 60 | 61 | 62 | #endif // CLGLCD_H 63 | -------------------------------------------------------------------------------- /examples/clglcd_text/clglcd_config.h: -------------------------------------------------------------------------------- 1 | // 2 | // Hardware configuration 3 | // 4 | 5 | #ifndef CLGLCD_CONFIG 6 | #define CLGLCD_CONFIG 7 | 8 | // 9 | // F-51543NFU <> Arduino 10 | // 11 | // 1 V LED- => (GND trough backlight MOSFET) 12 | // 2 V LED+ <= (Power, +5V) 13 | // 3 DISPOFF <= (Needs pull-down to GND) 14 | // 4 D3 <= (x,7) 15 | // 5 D2 <= (x,6) 16 | // 6 D1 <= (x,5) 17 | // 7 D0 <= (x,4) 18 | // 8 VEE => (Power, -24V) 19 | // 9 VSS == (GND) 20 | // 10 VDD <= (Power, +5V) 21 | // 11 V0 => (Power, -16.8V from trimmer) 22 | // 12 M <= (PWM, same timer as CL1) 23 | // 13 CL2 <= (PWM) 24 | // 14 CL1 <= (PWM, same timer as M) 25 | // 15 FLM <= (any) 26 | // 27 | 28 | #if defined(ARDUINO_AVR_UNO) 29 | 30 | #define CLGLCD_BCKL_CTRL B,5 31 | #define CLGLCD_DISPOFF B,4 32 | #define CLGLCD_DATA D,4-7 33 | #define CLGLCD_VEE_CTRL B,3 34 | #define CLGLCD_ALT_M B,1 35 | #define CLGLCD_ALT_M_OCnX 1,A 36 | #define CLGLCD_CL2 D,3 37 | #define CLGLCD_CL2_OCnX 2,B 38 | #define CLGLCD_CL1 B,2 39 | #define CLGLCD_CL1_OCnX 1,B 40 | #define CLGLCD_FLM B,0 41 | 42 | #elif defined(ARDUINO_AVR_LEONARDO) 43 | 44 | #define CLGLCD_BCKL_CTRL D,6 45 | #define CLGLCD_DISPOFF D,3 46 | #define CLGLCD_DATA B,4-7 47 | #define CLGLCD_VEE_CTRL E,6 48 | #define CLGLCD_ALT_M D,7 49 | #define CLGLCD_ALT_M_OCnX 4,D 50 | #define CLGLCD_CL2 C,6 51 | #define CLGLCD_CL2_OCnX 3,A 52 | #define CLGLCD_CL1 C,7 53 | #define CLGLCD_CL1_OCnX 4,A 54 | #define CLGLCD_FLM D,4 55 | 56 | #elif defined(ARDUINO_AVR_PROMICRO) 57 | 58 | #define CLGLCD_BCKL_CTRL D,2 59 | #define CLGLCD_DISPOFF D,3 60 | #define CLGLCD_DATA F,4-7 61 | #define CLGLCD_VEE_CTRL E,6 62 | #define CLGLCD_ALT_M B,5 63 | #define CLGLCD_ALT_M_OCnX 1,A 64 | #define CLGLCD_CL2 C,6 65 | #define CLGLCD_CL2_OCnX 3,A 66 | #define CLGLCD_CL1 B,6 67 | #define CLGLCD_CL1_OCnX 1,B 68 | #define CLGLCD_FLM B,2 69 | 70 | #else 71 | #error "No config for this board" 72 | #endif // Board config 73 | 74 | // Define the number of soft (runtime modifiable) characters 75 | // NOTE: Defining soft characters will eat SRAM and 76 | // slow down main code significantly 77 | // 78 | // Muse be power of 2. Supported values 8, 16, 32, 64 79 | //#define CLGLCD_SOFT_CHARS 32 80 | 81 | // Define to use upper codes (255-CLGLCD_SOFT_CHARS..255) 82 | // otherwise it it will use (0..CLGLCD_SOFT_CHARS) 83 | //#define CLGLCD_SOFT_UPPER 1 84 | 85 | #endif //CLGLCD_CONFIG 86 | -------------------------------------------------------------------------------- /examples/clglcd_text/clglcd_text.ino: -------------------------------------------------------------------------------- 1 | 2 | #include "clglcd.h" 3 | 4 | void setup() { 5 | CLGLCD_init(); 6 | 7 | delay(2000); // Safety 8 | Serial.begin(9600); 9 | 10 | CLGLCD_on(); 11 | CLGLCD_clear_screen(); 12 | } 13 | 14 | 15 | void loop() { 16 | 17 | strcpy_P((char *)&screen[0][2], PSTR("320x240 Controllerless Graphics LCD")); 18 | strcpy_P((char *)&screen[1][11], PSTR("driven by Arduino")); 19 | strcpy_P((char *)&screen[14][12], PSTR("Fixed font test")); 20 | 21 | screen[2][0] = 0xC9; // "╔" 22 | screen[2][39] = 0xBB; // "╗" 23 | screen[13][0] = 0xC8; // "╚" 24 | screen[13][39] = 0xBC; // "╝" 25 | for (uint8_t x=1; x<39; x++) { 26 | screen[2][x] = 0xCD; // "═" 27 | screen[13][x] = 0xCD; // "═" 28 | } 29 | for (uint8_t y=3; y<13; y++) { 30 | screen[y][0] = 0xBA; // "║" 31 | screen[y][39] = 0xBA; // "║" 32 | } 33 | 34 | uint8_t c = 0; 35 | uint8_t y = 3; 36 | while (true) { 37 | for(uint8_t x=1; x<39; x++) { 38 | screen[y][x] = c++; 39 | delay(100); 40 | } 41 | y++; 42 | if (y > 12) { 43 | memcpy((char *)&screen[3], (char *)&screen[4], 10 * 40); 44 | y = 12; 45 | screen[y][0] = 0xBA; // "║" 46 | screen[y][39] = 0xBA; // "║" 47 | memset((void*)&screen[y][1], 0x20, 38); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /misc/gen_font_header.py: -------------------------------------------------------------------------------- 1 | # 2 | # Python script to generate clglcd_font.h 3 | # Needs PIL/Pillow Python library 4 | # 5 | from PIL import Image, ImageDraw, ImageFont 6 | 7 | ### Font to convert ### 8 | 9 | FONT_FILE = "\\path\\to\\your_font.ttf" 10 | FONT_ENCODING = "cp437" 11 | FONT_SIZE = 16 12 | FONT_PT_SIZE = 12 13 | FONT_Y_OFFSET = 0 14 | 15 | ####################### 16 | 17 | if (240 % FONT_SIZE) != 0: 18 | raise Exception("Currenty only font sizes that fit as whole number in 240 lines are support") 19 | screen_lines = 240 / FONT_SIZE 20 | 21 | img = Image.new('1', (2048, FONT_SIZE), 0) 22 | font = ImageFont.truetype(FONT_FILE, FONT_PT_SIZE) 23 | draw = ImageDraw.Draw(img) 24 | for c in range(0, 256): 25 | str = bytes([c]).decode(FONT_ENCODING) 26 | draw.text((c*8, FONT_Y_OFFSET), str, font=font, fill=1) 27 | 28 | # Draw/Fix/Replace/ your custom charactes here 29 | 30 | # Save PNG image so you can inspect generated font 31 | img.save('clglcd_font.png') 32 | 33 | fp = open("clglcd_font.h", "w") 34 | fp.write("//\n") 35 | fp.write("// Font for 'Controllerless GLCD'\n") 36 | fp.write("// Generated from '"+FONT_FILE+"'\n") 37 | fp.write("//\n\n") 38 | fp.write("#define CLGLCD_FONT_LINES %d\n" % FONT_SIZE) 39 | fp.write("#define CLGLCD_Y_LINES %d\n\n" % screen_lines) 40 | fp.write("// Layout is 8 bits of horizontal pixels of all 256 chracters\n") 41 | fp.write("// (256 bytes), mutiplied by number of vertial lines for the\n") 42 | fp.write("// characters in the font.\n\n") 43 | fp.write("const unsigned char fixed_font[CLGLCD_FONT_LINES * 256] __attribute__((progmem,aligned(256))) = {\n") 44 | 45 | ib = img.tobytes() 46 | for y in range(0, 16*FONT_SIZE): 47 | l = list(ib[y*16:(y+1)*16]) 48 | fp.write(" 0x"+", 0x".join(["{:02x}".format(x) for x in l])+",\n") 49 | fp.write(");\n") 50 | -------------------------------------------------------------------------------- /src/clglcd.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Character generator for Controllerless Graphics LCD Display 3 | * 4 | * Copyright (c) 2019 Ivan Kostoski 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 all 14 | * 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 THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include "clglcd.h" 27 | 28 | #define BIT_(p,b) (b) 29 | #define BIT(cfg) BIT_(cfg) 30 | #define PORT_(p,b) (PORT ## p) 31 | #define PORT(cfg) PORT_(cfg) 32 | #define PIN_(p,b) (PIN ## p) 33 | #define PIN(cfg) PIN_(cfg) 34 | #define DDR_(p,b) (DDR ## p) 35 | #define DDR(cfg) DDR_(cfg) 36 | 37 | #define SET(cfg) PORT_(cfg) |= _BV(BIT_(cfg)) 38 | #define CLEAR(cfg) PORT_(cfg) &= ~_BV(BIT_(cfg)) 39 | #define TOGGLE(cfg) PIN_(cfg) = _BV(BIT_(cfg)) 40 | 41 | #define TM_BASE_(n,x) (n) 42 | #define TM_BASE(cfg) TM_BASE_(cfg) 43 | #define TM_LINE_A 1 44 | #define TM_LINE_B 2 45 | #define TM_LINE_C 3 46 | #define TM_LINE_D 4 47 | #define TM_LINE_(n,x) (TM_LINE_ ## x) 48 | #define TM_LINE(cfg) TM_LINE_(cfg) 49 | #define TM_CR_(n,x) (TCCR ## n ## x) 50 | #define TM_CR(cfg) TM_CR_(cfg) 51 | #define TM_OCR_(n,x) (OCR ## n ## x) 52 | #define TM_OCR(cfg) TM_OCR_(cfg) 53 | 54 | #define TM_FPWM_MODE2_(n,x) (1< 0 130 | uint8_t volatile soft_font[CLGLCD_FONT_LINES][CLGLCD_SOFT_CHARS] __attribute__((aligned(CLGLCD_SOFT_CHARS))); 131 | #endif 132 | 133 | // 134 | // Character generator ISR 135 | // 136 | 137 | #if CLGLCD_SOFT_CHARS < 1 // Fixed 138 | 139 | // Outputs 2 nibbles in 8 Fclk ticks 140 | #define SHIFT_OUT_BYTE() \ 141 | "out %[data_port], r24" "\n\t" \ 142 | "ld r30, X+" "\n\t" \ 143 | "swap r24" "\n\t" \ 144 | "out %[data_port], r24" "\n\t" \ 145 | "lpm r24, Z\n\t" 146 | 147 | #else // Fixed + Soft 148 | 149 | #define SHIFT_OUT_BYTE_SCR_LOAD() \ 150 | "ld r30, X+" "\n\t" \ 151 | 152 | #if CLGLCD_SOFT_UPPER == 1 153 | #define SHIFT_OUT_BYTE_BRANCH_3F() \ 154 | "cpi r30, %[sfc_upper]" "\n\t" \ 155 | "brcc 3f" "\n\t" 156 | #else 157 | #define SHIFT_OUT_BYTE_BRANCH_3F() \ 158 | "cpi r30, %[sfc_num]" "\n\t" \ 159 | "brcs 3f" "\n\t" 160 | #endif 161 | 162 | #define SHIFT_OUT_BYTE_FIXED_LOAD() \ 163 | "lpm r28, Z" "\n\t" \ 164 | 165 | #if CLGLCD_SOFT_UPPER == 1 166 | #define SHIFT_OUT_BYTE_SOFT_OFFSET() \ 167 | "mov r28, r30" "\n\t" \ 168 | "and r28, r0" "\n\t" 169 | #else 170 | #define SHIFT_OUT_BYTE_SOFT_OFFSET() \ 171 | "mov r28, r30" "\n\t" \ 172 | "or r28, r0" "\n\t" 173 | #endif 174 | 175 | #define SHIFT_OUT_BYTE_SOFT_LOAD() \ 176 | "ld r28, Y" "\n\t" 177 | 178 | // Outputs 2 nibbles in 12 Fclk ticks 179 | #define SHIFT_OUT_BYTE() \ 180 | "out %[data_port], r28" "\n\t" \ 181 | SHIFT_OUT_BYTE_SCR_LOAD() \ 182 | "swap r28" "\n\t" \ 183 | SHIFT_OUT_BYTE_BRANCH_3F() \ 184 | "out %[data_port], r28" "\n\t" \ 185 | SHIFT_OUT_BYTE_FIXED_LOAD() \ 186 | "rjmp 4f" "\n\t" \ 187 | "3:" \ 188 | /* We are a tick late with this nibble */ \ 189 | "out %[data_port], r28" "\n\t" \ 190 | SHIFT_OUT_BYTE_SOFT_OFFSET() \ 191 | SHIFT_OUT_BYTE_SOFT_LOAD() \ 192 | "4:" 193 | 194 | #endif 195 | 196 | // Common shifting macros 197 | #define SHIFT_OUT_3X_BYTE() \ 198 | SHIFT_OUT_BYTE() \ 199 | SHIFT_OUT_BYTE() \ 200 | SHIFT_OUT_BYTE() 201 | 202 | #define SHIFT_OUT_9X_BYTE() \ 203 | SHIFT_OUT_3X_BYTE() \ 204 | SHIFT_OUT_3X_BYTE() \ 205 | SHIFT_OUT_3X_BYTE() 206 | 207 | // Set vector based on config 208 | #if TM_BASE(CLGLCD_CL1_OCnX) == 1 209 | ISR(TIMER1_OVF_vect, ISR_NAKED) { 210 | #elif TM_BASE(CLGLCD_CL1_OCnX) == 2 211 | ISR(TIMER2_OVF_vect, ISR_NAKED) { 212 | #elif TM_BASE(CLGLCD_CL1_OCnX) == 3 213 | ISR(TIMER3_OVF_vect, ISR_NAKED) { 214 | #elif TM_BASE(CLGLCD_CL1_OCnX) == 4 215 | ISR(TIMER4_OVF_vect, ISR_NAKED) { 216 | #else 217 | #error "CL1 timer not implemented" 218 | #endif 219 | __asm__ ( 220 | ".equ font_end, %[font_start]+%[font_size]" "\n\t" 221 | ".equ screen_start, %[screen]" "\n\t" 222 | ".equ screen_end, %[screen]+%[screen_size]" "\n\t" 223 | #if CLGLCD_SOFT_CHARS > 0 224 | ".equ soft_offset, %[soft_font]" "\n\t" 225 | #endif 226 | 227 | "push r24" "\n\t" 228 | "in r24, 0x3f" "\n\t" 229 | "push r24" "\n\t" 230 | "push r26" "\n\t" 231 | "push r27" "\n\t" 232 | "push r30" "\n\t" 233 | "push r31\n\t" 234 | #if CLGLCD_SOFT_CHARS > 0 235 | "push r0" "\n\t" 236 | "push r1" "\n\t" 237 | "push r28" "\n\t" 238 | "push r29" "\n\t" 239 | #endif 240 | 241 | // Clear FLM 242 | "cbi %[flm_port], %[flm_bit]" "\n\t" 243 | 244 | "1:" 245 | // Pickup where we left off 246 | "lds r31, %[font_line]" "\n\t" 247 | "lds r26, %[scr_pos]" "\n\t" 248 | "lds r27, %[scr_pos]+1" "\n\t" 249 | 250 | // Check if we need to go to top of screen 251 | "ldi r30, hi8(screen_end)" "\n\t" 252 | "cpi r26, lo8(screen_end)" "\n\t" 253 | "cpc r27, r30" "\n\t" 254 | "brcs 2f" "\n\t" 255 | // We have sent all screen lines, ... 256 | // ... reset screen pointer, ... 257 | "ldi r26, lo8(screen_start)" "\n\t" 258 | "ldi r27, hi8(screen_start)" "\n\t" 259 | "sts %[scr_pos], r26" "\n\t" 260 | "sts %[scr_pos]+1, r27\n\t" 261 | // ... reset font line, ... 262 | "ldi r31, hi8(%[font_start])" "\n\t" 263 | // ... set FLM line UP, ... 264 | "sbi %[flm_port], %[flm_bit]" "\n\t" 265 | // ... and set ALT_M_OCnX to be flipped on 266 | // next CL1 timer match. 267 | "ldi r24, %[cl1_set_m]" "\n\t" 268 | "sbis %[alt_m_portin], %[alt_m_bit]" "\n\t" 269 | "ldi r24, %[cl1_clear_m]" "\n\t" 270 | "sts %[cl1_tccr], r24" "\n\t" 271 | 272 | "2:" 273 | #if CLGLCD_SOFT_CHARS < 1 // Fixed 274 | // Preload first byte in r24 275 | "ld r30, X+" "\n\t" 276 | "lpm r24, Z" "\n\t" 277 | #else // Soft + Fixed 278 | // Calculate YH and YL top bits from r31 font line 279 | "mov r29, r31" "\n\t" 280 | "subi r29, hi8(%[font_start])" "\n\t" 281 | "ldi r28, %[sfc_num]" "\n\t" 282 | "mul r28, r29" "\n\t" 283 | "ldi r28, lo8(soft_offset)" "\n\t" 284 | "ldi r29, hi8(soft_offset)" "\n\t" 285 | "add r0, r28" "\n\t" 286 | #if CLGLCD_SOFT_UPPER == 1 287 | "ldi r28, %[sfc_mask]\n\t" 288 | "or r0, r28" "\n\t" 289 | #endif 290 | "adc r29, r1" "\n\t" 291 | // Keep r0 as now it contains bitmask offset to soft font data 292 | 293 | // Preload first byte in r28 294 | SHIFT_OUT_BYTE_SCR_LOAD() 295 | SHIFT_OUT_BYTE_BRANCH_3F() 296 | SHIFT_OUT_BYTE_FIXED_LOAD() 297 | "rjmp 4f" "\n\t" 298 | "3:" 299 | SHIFT_OUT_BYTE_SOFT_OFFSET() 300 | SHIFT_OUT_BYTE_SOFT_LOAD() 301 | "4:" 302 | #endif // Fixed/Soft 303 | 304 | // Start timer 305 | "eor r30, r30" "\n\t" 306 | "sts %[cl2_tcnt], r30\n\t" 307 | "ldi r30, %[cl2_tccr_val]" "\n\t" 308 | "sts %[cl2_tccr], r30" "\n\t" 309 | CL2_TIMER_SYNC() 310 | 311 | // Shift out 39 (4x9+3) bytes 312 | SHIFT_OUT_9X_BYTE() 313 | SHIFT_OUT_9X_BYTE() 314 | SHIFT_OUT_9X_BYTE() 315 | SHIFT_OUT_9X_BYTE() 316 | SHIFT_OUT_3X_BYTE() 317 | 318 | // And for the last byte: 319 | // - Do not load next screen byte 320 | // - Stop timer ASAP, 321 | 322 | #if CLGLCD_SOFT_CHARS < 1 // Fixed font 323 | 324 | "out %[data_port], r24" "\n\t" 325 | "swap r24" "\n\t" 326 | "eor r30, r30" "\n\t" 327 | "nop" "\n\t" 328 | "out %[data_port], r24" "\n\t" 329 | // Stop the timer just as CL2 goes down (STS takes 2 cycles) 330 | "sts %[cl2_tccr], r30" "\n\t" 331 | 332 | #else // Fixed+Soft 333 | 334 | "out %[data_port], r28" "\n\t" 335 | "swap r28" "\n\t" 336 | "nop" "\n\t" 337 | "nop" "\n\t" 338 | //"ldi r24, %[cl1_bv]" "\n\t" 339 | "nop" "\n\t" 340 | "nop" "\n\t" 341 | // Output last nibble 342 | "out %[data_port], r28" "\n\t" 343 | // Stop the timer just as CL2 goes down (STS takes 2 cycles) 344 | "nop" "\n\t" 345 | "eor r1, r1" "\n\t" 346 | "sts %[cl2_tccr], r1" "\n\t" 347 | 348 | #endif // Fixed/Soft 349 | 350 | "subi r31, 0xFF" "\n\t" 351 | "cpi r31, hi8(font_end)" "\n\t" 352 | "brcs 5f" "\n\t" 353 | // We have sent one full screen line (CLGLCD_FONT_SIZE) 354 | // Store screen position (in X register), for next interrupt 355 | "sts %[scr_pos], r26" "\n\t" 356 | "sts %[scr_pos]+1, r27\n\t" 357 | // Rreset the font line 358 | "ldi r31, hi8(%[font_start])" "\n\t" 359 | "5:" 360 | // Store current font line for next interrupt 361 | "sts %[font_line], r31\n\t" 362 | 363 | #if CLGLCD_SOFT_CHARS > 0 364 | "pop r29" "\n\t" 365 | "pop r28" "\n\t" 366 | "pop r1" "\n\t" 367 | "pop r0" "\n\t" 368 | #endif 369 | "pop r31" "\n\t" 370 | "pop r30" "\n\t" 371 | "pop r27" "\n\t" 372 | "pop r26" "\n\t" 373 | "pop r24" "\n\t" 374 | "out 0x3f, r24" "\n\t" 375 | "pop r24" "\n\t" 376 | "reti" "\n\t" 377 | 378 | :: 379 | [font_start] "" (fixed_font), 380 | [font_size] "" (256 * CLGLCD_FONT_LINES), 381 | [screen] "" (screen), 382 | [screen_size] "" (40 * CLGLCD_Y_LINES), 383 | [scr_pos] "" (screen_pos), 384 | [font_line] "" (font_line), 385 | #if CLGLCD_SOFT_CHARS > 0 386 | [soft_font] "" (soft_font), 387 | [sfc_num] "" (CLGLCD_SOFT_CHARS), 388 | [sfc_mask] "" (CLGLCD_SOFT_CHARS-1), 389 | [sfc_upper] "" (256-CLGLCD_SOFT_CHARS), 390 | #endif 391 | [cl2_tcnt] "" (CL2_TIMER_CNT), 392 | [cl2_tccr_val] "" (CL2_TIMER_VAL), 393 | [cl2_tccr] "" (&CL2_TIMER_CR), 394 | [cl1_set_m] "" (CL1_SET_M), 395 | [cl1_clear_m] "" (CL1_CLEAR_M), 396 | [cl1_tccr] "" (&CL1_TIMER_CR), 397 | [data_port] "I" (_SFR_IO_ADDR(PORT(CLGLCD_DATA))), 398 | [flm_port] "I" (_SFR_IO_ADDR(PORT(CLGLCD_FLM))), 399 | [flm_bit] "" (BIT(CLGLCD_FLM)), 400 | [alt_m_port] "" (_SFR_IO_ADDR(PORT(CLGLCD_ALT_M))), 401 | [alt_m_portin] "" (_SFR_IO_ADDR(PIN(CLGLCD_ALT_M))), 402 | [alt_m_bit] "" (BIT(CLGLCD_ALT_M)) 403 | ); 404 | } 405 | 406 | // 407 | // CLGLCD API 408 | // 409 | 410 | void CLGLCD_init() { 411 | CLEAR(CLGLCD_DISPOFF); 412 | OUTPUT_PIN(CLGLCD_DISPOFF); 413 | #if defined(CLGLCD_BCKL_CTRL) 414 | CLEAR(CLGLCD_BCKL_CTRL); 415 | OUTPUT_PIN(CLGLCD_BCKL_CTRL); 416 | #endif 417 | #if defined(CLGLCD_VEE_CTRL) 418 | CLEAR(CLGLCD_VEE_CTRL); 419 | OUTPUT_PIN(CLGLCD_VEE_CTRL); 420 | #endif 421 | 422 | DDR(CLGLCD_DATA) = 0xF0; 423 | OUTPUT_PIN(CLGLCD_ALT_M); 424 | OUTPUT_PIN(CLGLCD_CL2); 425 | OUTPUT_PIN(CLGLCD_CL1); 426 | OUTPUT_PIN(CLGLCD_FLM); 427 | 428 | #if CLGLCD_SOFT_CHARS > 0 429 | for(uint8_t y=0; y> 8); 444 | 445 | // Shift out the row selector bit (FLM) from common drivers, 446 | // so we don't end up with two active bits 447 | CLEAR(CLGLCD_CL1); 448 | CLEAR(CLGLCD_FLM); 449 | for (uint8_t y=0; y<240; y++) { 450 | TOGGLE(CLGLCD_CL1); 451 | _NOP(); 452 | TOGGLE(CLGLCD_CL1); 453 | } 454 | 455 | // Setup the CL1 timer 456 | #if (TM_BASE(CLGLCD_CL1_OCnX) == 1) && (TM_BASE(CLGLCD_ALT_M_OCnX) == 1) 457 | TCCR1A = 0; 458 | TCCR1B = 0; 459 | TCNT1 = 0; 460 | ICR1 = CLGLCD_CL1_TICKS / 8 - 1; 461 | TM_OCR(CLGLCD_ALT_M_OCnX) = CLGLCD_CL1_TICKS / 8 - 1; 462 | TM_OCR(CLGLCD_CL1_OCnX) = CLGLCD_CL1_TICKS / 8 - 2; 463 | TCCR1A = CL1_CLEAR_M; 464 | TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11); // WGM13:0=15, prescaller 8 465 | TIFR1 |= (1 << TOV1); 466 | TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt 467 | #elif (TM_BASE(CLGLCD_CL1_OCnX) == 4 ) && (TM_BASE(CLGLCD_ALT_M_OCnX) == 4) 468 | TIMSK4 = 0; 469 | TCCR4A = 0; 470 | TCCR4B = 0; 471 | TCCR4C = 0; 472 | TCCR4D = 0; 473 | TCCR4E = 0; 474 | TC4H = 0; 475 | TCNT4 = 0; 476 | //TC4H = highByte(CLGLCD_CL1_TICKS / 8 - 1); 477 | OCR4C = lowByte(CLGLCD_CL1_TICKS / 8 - 1); // TOP 478 | TM_OCR(CLGLCD_ALT_M_OCnX) = lowByte(CLGLCD_CL1_TICKS / 8); 479 | TM_OCR(CLGLCD_CL1_OCnX) = lowByte(CLGLCD_CL1_TICKS / 8 - 2); 480 | TCCR4A = (1< 30 | 31 | #ifndef CLGLCD_CONFIG 32 | #include "clglcd_config.h" 33 | #endif 34 | #ifndef CLGLCD_FONT_LINES 35 | #include "clglcd_font.h" 36 | #endif 37 | 38 | // Screen SRAM buffer: screen[y][x] 39 | extern uint8_t volatile screen[CLGLCD_Y_LINES][40]; 40 | 41 | // SRAM font buffer: soft_font[character_line][character_offset] 42 | #if CLGLCD_SOFT_CHARS > 0 43 | extern uint8_t volatile soft_font[CLGLCD_FONT_LINES][CLGLCD_SOFT_CHARS] __attribute__((aligned(CLGLCD_SOFT_CHARS))); 44 | #endif 45 | 46 | // Setup LCD output pins and clear screen 47 | void CLGLCD_init(); 48 | 49 | // Turn on the LCD and start driving interrupt 50 | void CLGLCD_on(); 51 | 52 | // Turn off the LCD and stop driving interrupt 53 | void CLGLCD_off(); 54 | 55 | // Clear the screen, fill the screen buffer with zeroes 56 | void CLGLCD_clear_screen(); 57 | 58 | // Check if FLM signal is up, usefull when disabling interrputs 59 | bool CLGLCD_FLM_is_up(); 60 | 61 | 62 | #endif // CLGLCD_H 63 | -------------------------------------------------------------------------------- /src/clglcd_config.h: -------------------------------------------------------------------------------- 1 | // 2 | // Hardware configuration 3 | // 4 | 5 | #ifndef CLGLCD_CONFIG 6 | #define CLGLCD_CONFIG 7 | 8 | // 9 | // F-51543NFU <> Arduino 10 | // 11 | // 1 V LED- => (GND trough backlight MOSFET) 12 | // 2 V LED+ <= (Power, +5V) 13 | // 3 DISPOFF <= (Needs pull-down to GND) 14 | // 4 D3 <= (x,7) 15 | // 5 D2 <= (x,6) 16 | // 6 D1 <= (x,5) 17 | // 7 D0 <= (x,4) 18 | // 8 VEE => (Power, -24V) 19 | // 9 VSS == (GND) 20 | // 10 VDD <= (Power, +5V) 21 | // 11 V0 => (Power, -16.8V from trimmer) 22 | // 12 M <= (PWM, same timer as CL1) 23 | // 13 CL2 <= (PWM) 24 | // 14 CL1 <= (PWM, same timer as M) 25 | // 15 FLM <= (any) 26 | // 27 | 28 | #if defined(ARDUINO_AVR_UNO) 29 | 30 | #define CLGLCD_BCKL_CTRL B,5 31 | #define CLGLCD_DISPOFF B,4 32 | #define CLGLCD_DATA D,4-7 33 | #define CLGLCD_VEE_CTRL B,3 34 | #define CLGLCD_ALT_M B,1 35 | #define CLGLCD_ALT_M_OCnX 1,A 36 | #define CLGLCD_CL2 D,3 37 | #define CLGLCD_CL2_OCnX 2,B 38 | #define CLGLCD_CL1 B,2 39 | #define CLGLCD_CL1_OCnX 1,B 40 | #define CLGLCD_FLM B,0 41 | 42 | #elif defined(ARDUINO_AVR_LEONARDO) 43 | 44 | #define CLGLCD_BCKL_CTRL D,6 45 | #define CLGLCD_DISPOFF D,3 46 | #define CLGLCD_DATA B,4-7 47 | #define CLGLCD_VEE_CTRL E,6 48 | #define CLGLCD_ALT_M D,7 49 | #define CLGLCD_ALT_M_OCnX 4,D 50 | #define CLGLCD_CL2 C,6 51 | #define CLGLCD_CL2_OCnX 3,A 52 | #define CLGLCD_CL1 C,7 53 | #define CLGLCD_CL1_OCnX 4,A 54 | #define CLGLCD_FLM D,4 55 | 56 | #elif defined(ARDUINO_AVR_PROMICRO) 57 | 58 | #define CLGLCD_BCKL_CTRL D,2 59 | #define CLGLCD_DISPOFF D,3 60 | #define CLGLCD_DATA F,4-7 61 | #define CLGLCD_VEE_CTRL E,6 62 | #define CLGLCD_ALT_M B,5 63 | #define CLGLCD_ALT_M_OCnX 1,A 64 | #define CLGLCD_CL2 C,6 65 | #define CLGLCD_CL2_OCnX 3,A 66 | #define CLGLCD_CL1 B,6 67 | #define CLGLCD_CL1_OCnX 1,B 68 | #define CLGLCD_FLM B,2 69 | 70 | #else 71 | #error "No config for this board" 72 | #endif // Board config 73 | 74 | // Define the number of soft (runtime modifiable) characters 75 | // NOTE: Defining soft characters will eat SRAM and 76 | // slow down main code significantly 77 | // 78 | // Muse be power of 2. Supported values 8, 16, 32, 64 79 | //#define CLGLCD_SOFT_CHARS 32 80 | 81 | // Define to use upper codes (255-CLGLCD_SOFT_CHARS..255) 82 | // otherwise it it will use (0..CLGLCD_SOFT_CHARS) 83 | //#define CLGLCD_SOFT_UPPER 1 84 | 85 | #endif //CLGLCD_CONFIG 86 | -------------------------------------------------------------------------------- /timings/big-picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostoski/arduino-clglcd/5f8554aef9935b67f16f78a8b55424dd1f017175/timings/big-picture.png -------------------------------------------------------------------------------- /timings/cl2-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostoski/arduino-clglcd/5f8554aef9935b67f16f78a8b55424dd1f017175/timings/cl2-data.png -------------------------------------------------------------------------------- /timings/flm_latch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostoski/arduino-clglcd/5f8554aef9935b67f16f78a8b55424dd1f017175/timings/flm_latch.png -------------------------------------------------------------------------------- /usb_bridge/README.md: -------------------------------------------------------------------------------- 1 | # Arduino USB Bridge to Controllerless Graphics LCD 2 | 3 | For Arduino MCUs that lack the memory to drive controllerless display in graphics mode, one way around it is if something else is sending the display data, in realtime. We need 9600 bytes x 70 fps = 672KBps ~= 5.4Mbps. 4 | 5 | ## USB controller on ATmega32u4 6 | 7 | On 32u4 there is one peripheral capable of receiving needed amount of data, the USB controller, which has its own independent dual-ported RAM presented to main MCU as FIFOs. 8 | 9 | At 70Hz, we need about 672 bytes per USB microframe (1ms) to keep up with displays demand. 10 | 11 | Isochronous endpoint theoretically would be ideal for this as in full-speed mode it can send one transfer (up to 1023 bytes) per microframe. Unfortunately, according to 32u4 datasheet, only endpoint 1 can be up to 256 bytes. The rest are limited to 64-bytes. 12 | 13 | This also eliminates 'Interrupt' endpoints, which are limited to single transfer per microframe, and with that any HID implementation, which is limited to single interrupt OUT endpoint. 14 | 15 | Fortunately the 'Bulk' endpoints, which can receive multiple packets per USB microframe, can be configured in dual-bank mode (default in Arduino USB Core) which enables us to process received data while the next packet is handled by the USB peripheral. 16 | 17 | Again, ideally we would just shift-out the data from the USB FIFO directly to the LCD. However, Windows is not an RTOS and there can be delays between transfers, so we need to buffer some amount of data in memory to smooth out delays. 18 | 19 | The Arduino code in this directory does exactly that: Reads data from USB endpoint and stores it in SRAM ring-buffer. When the buffer is filled, it starts to shift-out the data to the LCD, while the buffer is being constantly refilled if there is data available in the USB FIFO. It also handles the LCD control lines, as explained elsewhere in this repo. If it runs out of data in the buffer, it cleans up and waits for next start of frame (USB packet beginning with zero) to fill the buffer, without latched FLM bit. After short, period with no data, it gives up and shutdowns the display. 20 | 21 | On the host side, there is a C++ process which reads 'named' shared memory frame buffer (320x240 bytes), applies 'temporal dithering' to the grayscale values, packs this data in 160 64-byte packets and sends the data via WinUSB as single transfer. To keep WinUSB busy, I submit few (i.e. 4) such transfers is 'overlapped' mode, which are then sent to the Arduino by WinUSB sequentially. This adds latency, but pixel response time of the display is slow anyway. The display clients should acquire the named mutex before accessing shared memory. You can compile the code with MinGW, or use the Code::Blocks project. 22 | 23 | And the last part is the demo, which is a Python script, loads or draws grayscale PIL image, puts the image bytes (img.tobytes()) into shared memory buffer and switches the active frame (also via shared memory). 24 | 25 | YouTube video of the demo is here: https://youtu.be/mMqvBnYOjEQ 26 | 27 | ## Notes 28 | 29 | As we need Bulk endpoint, Arduino tries to present itself as WCID compliant, Vendor Class USB device (see https://github.com/pbatard/libwdi/wiki/WCID-Devices) to the host, my humble implementation of WICD descriptors. Hopefully this will enable automatic installation of winusb.sys driver under Windows. I wasn't able to test this part much, depends if you played with Zadig before with the same device VID/PID. The required part is to get the 'DeviceInterfaceGUID' value linked to the device, so the host process can find it. 30 | 31 | For various limitations of WinUSB, screen data is packaged in 64 byte USB packets of which first byte is packet number, than 60 bytes of data payload (I am wasting 3 bytes/packet). Each LCD screen line need 40 bytes of data, so in two USB packets I am providing data for 3 screen lines. Or inside the code, I count this in 20 byte 'buffers' and I increase the counter by 3 when I receive USB packet and decrease it by 2 when I shift-out a line. 32 | 33 | Touchpanel is read via endpoint 0 with vendor control transfers. Due to WCID, I already had the hooks in place , so I used those. Seems to work. The proper way would be to add one more Interrupt IN endpoint... 34 | 35 | The data transfer is paced by the Arduino. When both endpoint banks are full, the USB controller NAKs the packets. 36 | 37 | The host 'driver' is actually simple console application. You can probably build a service around it (or reimplement it as one) but be careful if you run it under different user (i.e. LocalService), you may need to change the name of the shared memory from 'Local\' to 'Global\' (which requires elevated privileges). 38 | 39 | The Arduino ISR takes about 15µs to shift-out from the ring buffer and further 25µs to move the data from USB FIFO to the ring buffer (if packet is available). With overhead, this leaves very little time for any "user" code. Probably just enough to read the touchpanel and handle other USB tasks. 40 | 41 | On ProMicro, display plus power control plus touchpanel leave very few pins available. Besides USB, you can probably have only one other communication interface (i.e. HW Serial or I2C or SPI). If you need one of those, remap the configuration to free the pins. 42 | 43 | -------------------------------------------------------------------------------- /usb_bridge/clglcd_bridge/clglcd_config.h: -------------------------------------------------------------------------------- 1 | // 2 | // Hardware configuration 3 | // 4 | 5 | #ifndef CLGLCD_CONFIG 6 | #define CLGLCD_CONFIG 7 | 8 | // 9 | // F-51543NFU <> Arduino 10 | // 11 | // 1 V LED- => (GND trough backlight MOSFET) 12 | // 2 V LED+ <= (Power, +5V) 13 | // 3 DISPOFF <= (Needs pull-down to GND) 14 | // 4 D3 <= (x,7) 15 | // 5 D2 <= (x,6) 16 | // 6 D1 <= (x,5) 17 | // 7 D0 <= (x,4) 18 | // 8 VEE => (Power, -24V) 19 | // 9 VSS == (GND) 20 | // 10 VDD <= (Power, +5V) 21 | // 11 V0 => (Power, -16.8V from trimmer) 22 | // 12 M <= (PWM, same timer as CL1) 23 | // 13 CL2 <= (PWM) 24 | // 14 CL1 <= (PWM, same timer as M) 25 | // 15 FLM <= (any) 26 | 27 | #if defined(ARDUINO_AVR_LEONARDO) 28 | 29 | #define CLGLCD_BCKL_CTRL D,0 30 | #define CLGLCD_DISPOFF D,6 31 | #define CLGLCD_DATA B,4-7 32 | #define CLGLCD_VEE_CTRL E,6 33 | #define CLGLCD_ALT_M D,7 34 | #define CLGLCD_ALT_M_OCnX 4,D 35 | #define CLGLCD_CL2 C,6 36 | #define CLGLCD_CL2_OCnX 3,A 37 | #define CLGLCD_CL1 C,7 38 | #define CLGLCD_CL1_OCnX 4,A 39 | #define CLGLCD_FLM D,4 40 | 41 | #define CLGLCD_TP_XL_AN A0 42 | #define CLGLCD_TP_XL F,7 43 | #define CLGLCD_TP_YU F,6 44 | #define CLGLCD_TP_XR F,5 45 | #define CLGLCD_TP_YL_AN A3 46 | #define CLGLCD_TP_YL F,4 47 | 48 | #elif defined(ARDUINO_AVR_PROMICRO) 49 | 50 | #define CLGLCD_BCKL_CTRL D,3 51 | #define CLGLCD_DISPOFF D,2 52 | #define CLGLCD_DATA F,4-7 53 | #define CLGLCD_VEE_CTRL D,0 54 | #define CLGLCD_ALT_M B,5 55 | #define CLGLCD_ALT_M_OCnX 1,A 56 | #define CLGLCD_CL2 C,6 57 | #define CLGLCD_CL2_OCnX 3,A 58 | #define CLGLCD_CL1 B,6 59 | #define CLGLCD_CL1_OCnX 1,B 60 | #define CLGLCD_FLM D,1 61 | 62 | #define CLGLCD_TP_XL_AN A8 63 | #define CLGLCD_TP_XL B,4 64 | #define CLGLCD_TP_YU E,6 65 | #define CLGLCD_TP_XR D,7 66 | #define CLGLCD_TP_YL_AN A6 67 | #define CLGLCD_TP_YL D,4 68 | 69 | #else 70 | #error "No config for this board" 71 | #endif // Board config 72 | 73 | #define CLGLCD_CL1_TICKS 952 74 | 75 | // 76 | // Touchpanel calibration 77 | // 78 | 79 | #define CLGLCD_TP_CAL_XF 378 80 | #define CLGLCD_TP_CAL_XM 76 81 | #define CLGLCD_TP_CAL_YF 308 82 | #define CLGLCD_TP_CAL_YM 107 83 | #define CLGLCD_TP_CAL_Z1_MIN 5 84 | #define CLGLCD_TP_CAL_Z1_MAX 70 85 | #define CLGLCD_TP_CAL_Z2_MAX 124 86 | #define CLGLCD_TP_CAL_Z2_MIN 60 87 | 88 | // 89 | // Poweroff display after ~3sec of no data 90 | // 91 | //#define CLGLCD_DISPOFF_CUT 50420 92 | #define CLGLCD_DISPOFF_CUT 480 93 | 94 | // 95 | // Ammount of data to buffer, each bank is 20 bytes 96 | // 97 | #define CLGLCD_BUFFER_BANKS 48 98 | 99 | #endif //CLGLCD_CONFIG_H 100 | -------------------------------------------------------------------------------- /usb_bridge/clglcd_bridge/clglcd_macros.h: -------------------------------------------------------------------------------- 1 | #define BIT_(p,b) (b) 2 | #define BIT(cfg) BIT_(cfg) 3 | #define PORT_(p,b) (PORT ## p) 4 | #define PORT(cfg) PORT_(cfg) 5 | #define PIN_(p,b) (PIN ## p) 6 | #define PIN(cfg) PIN_(cfg) 7 | #define DDR_(p,b) (DDR ## p) 8 | #define DDR(cfg) DDR_(cfg) 9 | 10 | // Explicitly use atomic SBI/CBI to avoid disabling interrupts 11 | // in main loop. Works only on IO addressable registers 12 | #define SET_(p_, b_) asm volatile ("sbi %[p], %[b]\n\t" :: [p] "I" _SFR_IO_ADDR(p_), [b] "I" (b_)) 13 | #define SET(cfg) SET_(PORT_(cfg), BIT_(cfg)) 14 | #define CLEAR_(p_,b_) asm volatile ("cbi %[p], %[b]\n\t" :: [p] "I" _SFR_IO_ADDR(p_), [b] "I" (b_)) 15 | #define CLEAR(cfg) CLEAR_(PORT_(cfg), BIT_(cfg)) 16 | #define TOGGLE(cfg) PIN_(cfg) = _BV(BIT_(cfg)) 17 | #define OUTPUT_PIN(cfg) SET_(DDR_(cfg), BIT_(cfg)) 18 | #define INPUT_PIN(cfg) CLEAR_(DDR_(cfg), BIT_(cfg)) 19 | 20 | // Timers 21 | 22 | #define TM_LINE_A 1 23 | #define TM_LINE_B 2 24 | #define TM_LINE_C 3 25 | #define TM_LINE_D 4 26 | #define TM_LINE_(n,x) (TM_LINE_ ## x) 27 | #define TM_LINE(cfg) TM_LINE_(cfg) 28 | #define TM_BASE_(n,x) (n) 29 | #define TM_BASE(cfg) TM_BASE_(cfg) 30 | #define TM_CR_(n,x,l) (TCCR ## n ## l) 31 | #define TM_CR(cfg,l) TM_CR_(cfg,l) 32 | #define TM_OCR_(n,x) (OCR ## n ## x) 33 | #define TM_OCR(cfg) TM_OCR_(cfg) 34 | #define TM_CNT_(n,x) (TCNT ## n) 35 | #define TM_CNT(cfg) TM_CNT_(cfg) 36 | #define TM_CNT_L_(n,x) (TCNT ## n ## L) 37 | #define TM_CNT_L(cfg) TM_CNT_L_(cfg) 38 | #define TM_ICR_(n,x) (ICR ## n) 39 | #define TM_ICR(cfg) TM_ICR_(cfg) 40 | #define TM_IMSK_(n,x) (TIMSK ## n) 41 | #define TM_IMSK(cfg) TM_IMSK_(cfg) 42 | 43 | #define TM_PSC1_(n,x) (1 << CS ## n ## 0) 44 | #define TM_PSC1(cfg) TM_PSC1_(cfg) 45 | #define TM_WGM_(n,x,b) (1< 2 | 3 | 4 | 5 | 63 | 64 | -------------------------------------------------------------------------------- /usb_bridge/clglcd_usb_host/Makefile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------# 2 | # This makefile was generated by 'cbp2make' tool rev.147 # 3 | #------------------------------------------------------------------------------# 4 | 5 | 6 | WORKDIR = %cd% 7 | 8 | CC = gcc.exe 9 | CXX = g++.exe 10 | AR = ar.exe 11 | LD = g++.exe 12 | WINDRES = windres.exe 13 | 14 | INC = 15 | CFLAGS = -march=native -Winline -Wfatal-errors -Wall -m64 -g -fexceptions -mwindows -mconsole 16 | RESINC = 17 | LIBDIR = 18 | LIB = -lsetupapi -lwinusb 19 | LDFLAGS = -O2 -static-libstdc++ -static-libgcc -m64 20 | 21 | INC_DEBUG = $(INC) 22 | CFLAGS_DEBUG = $(CFLAGS) -g -DSTDOUT_DEBUG 23 | RESINC_DEBUG = $(RESINC) 24 | RCFLAGS_DEBUG = $(RCFLAGS) 25 | LIBDIR_DEBUG = $(LIBDIR) 26 | LIB_DEBUG = $(LIB) 27 | LDFLAGS_DEBUG = $(LDFLAGS) 28 | OBJDIR_DEBUG = obj\\Debug 29 | DEP_DEBUG = 30 | OUT_DEBUG = bin\\Debug\\clglcd_usb_host.exe 31 | 32 | INC_RELEASE = $(INC) 33 | CFLAGS_RELEASE = $(CFLAGS) -O2 34 | RESINC_RELEASE = $(RESINC) 35 | RCFLAGS_RELEASE = $(RCFLAGS) 36 | LIBDIR_RELEASE = $(LIBDIR) 37 | LIB_RELEASE = $(LIB) 38 | LDFLAGS_RELEASE = $(LDFLAGS) -s 39 | OBJDIR_RELEASE = obj\\Release 40 | DEP_RELEASE = 41 | OUT_RELEASE = bin\\Release\\clglcd_usb_host.exe 42 | 43 | OBJ_DEBUG = $(OBJDIR_DEBUG)\\clglcd_ipc.o $(OBJDIR_DEBUG)\\clglcd_winusb.o $(OBJDIR_DEBUG)\\main.o 44 | 45 | OBJ_RELEASE = $(OBJDIR_RELEASE)\\clglcd_ipc.o $(OBJDIR_RELEASE)\\clglcd_winusb.o $(OBJDIR_RELEASE)\\main.o 46 | 47 | all: release 48 | 49 | clean: clean_debug clean_release 50 | 51 | before_debug: 52 | cmd /c if not exist bin\\Debug md bin\\Debug 53 | cmd /c if not exist $(OBJDIR_DEBUG) md $(OBJDIR_DEBUG) 54 | 55 | after_debug: 56 | 57 | debug: before_debug out_debug after_debug 58 | 59 | out_debug: before_debug $(OBJ_DEBUG) $(DEP_DEBUG) 60 | $(LD) $(LIBDIR_DEBUG) -o $(OUT_DEBUG) $(OBJ_DEBUG) $(LDFLAGS_DEBUG) $(LIB_DEBUG) 61 | 62 | $(OBJDIR_DEBUG)\\clglcd_ipc.o: clglcd_ipc.cpp 63 | $(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c clglcd_ipc.cpp -o $(OBJDIR_DEBUG)\\clglcd_ipc.o 64 | 65 | $(OBJDIR_DEBUG)\\clglcd_winusb.o: clglcd_winusb.cpp 66 | $(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c clglcd_winusb.cpp -o $(OBJDIR_DEBUG)\\clglcd_winusb.o 67 | 68 | $(OBJDIR_DEBUG)\\main.o: main.cpp 69 | $(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c main.cpp -o $(OBJDIR_DEBUG)\\main.o 70 | 71 | clean_debug: 72 | cmd /c del /f $(OBJ_DEBUG) $(OUT_DEBUG) 73 | cmd /c rd bin\\Debug 74 | cmd /c rd $(OBJDIR_DEBUG) 75 | 76 | before_release: 77 | cmd /c if not exist bin\\Release md bin\\Release 78 | cmd /c if not exist $(OBJDIR_RELEASE) md $(OBJDIR_RELEASE) 79 | 80 | after_release: 81 | 82 | release: before_release out_release after_release 83 | 84 | out_release: before_release $(OBJ_RELEASE) $(DEP_RELEASE) 85 | $(LD) $(LIBDIR_RELEASE) -o $(OUT_RELEASE) $(OBJ_RELEASE) $(LDFLAGS_RELEASE) $(LIB_RELEASE) 86 | 87 | $(OBJDIR_RELEASE)\\clglcd_ipc.o: clglcd_ipc.cpp 88 | $(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c clglcd_ipc.cpp -o $(OBJDIR_RELEASE)\\clglcd_ipc.o 89 | 90 | $(OBJDIR_RELEASE)\\clglcd_winusb.o: clglcd_winusb.cpp 91 | $(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c clglcd_winusb.cpp -o $(OBJDIR_RELEASE)\\clglcd_winusb.o 92 | 93 | $(OBJDIR_RELEASE)\\main.o: main.cpp 94 | $(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c main.cpp -o $(OBJDIR_RELEASE)\\main.o 95 | 96 | clean_release: 97 | cmd /c del /f $(OBJ_RELEASE) $(OUT_RELEASE) 98 | cmd /c rd bin\\Release 99 | cmd /c rd $(OBJDIR_RELEASE) 100 | 101 | .PHONY: before_debug after_debug clean_debug before_release after_release clean_release 102 | 103 | -------------------------------------------------------------------------------- /usb_bridge/clglcd_usb_host/clglcd_exc.h: -------------------------------------------------------------------------------- 1 | // CLGLCD Custom Exception 2 | 3 | #ifndef CLGLCD_EXC_H 4 | #define CLGLCD_EXC_H 5 | 6 | #include 7 | #include 8 | 9 | // Major error, will terminate program 10 | class CLGLCD_error: public std::runtime_error { 11 | public: 12 | using std::runtime_error::runtime_error; 13 | }; 14 | 15 | // If not device is present or stopped responding 16 | class CLGLCD_device_error: public CLGLCD_error { 17 | public: 18 | using CLGLCD_error::CLGLCD_error; 19 | }; 20 | 21 | // Nothing serious, sometimes transfers do fail 22 | class CLGLCD_transfer_error: public CLGLCD_device_error { 23 | public: 24 | using CLGLCD_device_error::CLGLCD_device_error; 25 | }; 26 | 27 | #endif // CLGLCD_EXC_H 28 | -------------------------------------------------------------------------------- /usb_bridge/clglcd_usb_host/clglcd_ipc.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // CLGLCD IPC interface 3 | // 4 | // Wrapper classes for Windows IPC 5 | // 6 | 7 | #include "clglcd_exc.h" 8 | #include "clglcd_ipc.h" 9 | 10 | #include "versionhelpers.h" 11 | #include "sddl.h" 12 | 13 | //https://stackoverflow.com/questions/20580054/how-to-make-a-synchronization-mutex-with-access-by-every-process 14 | // "D:(A;;GA;;;WD)(A;;GA;;;AN)S:(ML;;NW;;;S-1-16-0) 15 | // "D:(A;;GA;;;WD)(A;;GA;;;AN)S:(ML;;NW;;;ME) -> for medium integrity 16 | // "D:(A;;GA;;;WD)(A;;GA;;;AN)S:(ML;;NW;;;HI) -> for high integrity 17 | 18 | PSECURITY_DESCRIPTOR MakeAllowAllSecurityDescriptor(void) { 19 | PSECURITY_DESCRIPTOR pSecDesc; 20 | if(IsWindowsVistaOrGreater()) { 21 | if(!ConvertStringSecurityDescriptorToSecurityDescriptor("D:(A;;GA;;;WD)(A;;GA;;;AN)S:(ML;;NW;;;ME)", SDDL_REVISION_1, &pSecDesc, NULL)) 22 | throw CLGLCD_error("Error creating security descriptor"); 23 | } else { 24 | if(!ConvertStringSecurityDescriptorToSecurityDescriptor("D:(A;;GA;;;WD)(A;;GA;;;AN)", SDDL_REVISION_1, &pSecDesc, NULL)) 25 | throw CLGLCD_error("Error creating security descriptor"); 26 | } 27 | return pSecDesc; 28 | } 29 | 30 | 31 | CLGLCD_ipc::CLGLCD_ipc() { 32 | PSECURITY_DESCRIPTOR pSecDesc = MakeAllowAllSecurityDescriptor(); 33 | SECURITY_ATTRIBUTES SecAttr; 34 | SecAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 35 | SecAttr.lpSecurityDescriptor = pSecDesc; 36 | SecAttr.bInheritHandle = FALSE; 37 | 38 | mutex = CreateMutex(&SecAttr, TRUE, CLGLCD_MUTEX_NAME); 39 | if (mutex == NULL) { 40 | throw CLGLCD_error("Error " + std::to_string(GetLastError()) + " creating global mutex"); 41 | } 42 | 43 | //char shm_name[] = CLGLCD_SHM_NAME "\0"; 44 | file_map = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(CLGLCD_SHM), CLGLCD_SHM_NAME); 45 | if (file_map == NULL) { 46 | throw CLGLCD_error("Cannot allocate LCD shared memory"); 47 | } 48 | shm = (CLGLCD_SHM*) MapViewOfFile(file_map, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CLGLCD_SHM)); 49 | if (shm == NULL) { 50 | CloseHandle(file_map); 51 | file_map = NULL; 52 | throw CLGLCD_error("Error while memory mapping frame buffer"); 53 | } 54 | memset(shm, 0, sizeof(CLGLCD_SHM)); 55 | 56 | LocalFree(pSecDesc); 57 | ReleaseMutex(mutex); 58 | } 59 | 60 | CLGLCD_ipc::~CLGLCD_ipc() { 61 | if (file_map) { 62 | if (shm) UnmapViewOfFile(shm); 63 | CloseHandle(file_map); 64 | } 65 | if (mutex) { 66 | CloseHandle(mutex); 67 | } 68 | }; 69 | 70 | bool CLGLCD_ipc::client_is_connected() { 71 | DWORD res = WaitForSingleObject(mutex, 0); 72 | if (res == WAIT_TIMEOUT) return true; 73 | if (res == WAIT_OBJECT_0 || res == WAIT_ABANDONED) { 74 | ReleaseMutex(mutex); 75 | return false; 76 | } 77 | if (res == WAIT_FAILED) { 78 | throw new CLGLCD_error("Error " + std::to_string(GetLastError()) + " while checking client mutex"); 79 | } 80 | throw new CLGLCD_error("Unknown result " + std::to_string(res) + " while checking client mutex"); 81 | } 82 | -------------------------------------------------------------------------------- /usb_bridge/clglcd_usb_host/clglcd_ipc.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLGLCD IPC interface 3 | // 4 | // Wrapper classes for Windows IPC 5 | // 6 | 7 | 8 | #ifndef CLGLCD_IPC_H 9 | #define CLGLCD_IPC_H 10 | 11 | #define CLGLCD_MUTEX_NAME "Global\\CLGLCD_MUTEX" 12 | #define CLGLCD_SHM_NAME "Local\\CLGLCD_SHM" 13 | 14 | #include "Windows.h" 15 | 16 | // Share memory structure 17 | typedef struct _CLGLCD_SHM { 18 | // Written by usb_host 19 | char version[8]; 20 | uint32_t usb_host_ts; 21 | uint8_t reserved1[52]; 22 | 23 | // Touchscreen block, read from device 24 | uint32_t touch_ts; 25 | int32_t touch_x; 26 | int32_t touch_y; 27 | int32_t touch_preasure; 28 | // Raw touchscreen data 29 | int32_t raw_touch_x; 30 | int32_t raw_touch_y; 31 | int32_t raw_touch_z1; 32 | int32_t raw_touch_z2; 33 | int32_t raw_touch_cnt; 34 | uint8_t reserved2[28]; 35 | 36 | // Reserved 37 | uint8_t reserved3[64]; 38 | 39 | // Frame buffer selector - written by client(s) 40 | uint32_t active_ts; 41 | int32_t active_buffer; 42 | uint8_t reserved4[56]; 43 | 44 | // Frame buffers - written by client(s) 45 | uint8_t frame_buffer[2][240 * 320]; 46 | 47 | } CLGLCD_SHM; 48 | 49 | 50 | class CLGLCD_ipc { 51 | public: 52 | CLGLCD_SHM* shm; 53 | bool client_is_connected(); 54 | CLGLCD_ipc(); 55 | ~CLGLCD_ipc(); 56 | protected: 57 | HANDLE mutex; 58 | HANDLE file_map; 59 | HANDLE shm_handle; 60 | }; 61 | 62 | #endif // CLGLCD_IPC_H 63 | -------------------------------------------------------------------------------- /usb_bridge/clglcd_usb_host/clglcd_winusb.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // CLGLCD WinUSB interface 3 | // 4 | // Wrapper classes for WinUSB 5 | // 6 | 7 | #include 8 | #include 9 | 10 | #include "clglcd_exc.h" 11 | #include "clglcd_winusb.h" 12 | 13 | BOOL CLGLCD_Device::get_device_path(const GUID interface_GUID) { 14 | BOOL result = FALSE; 15 | HDEVINFO device_info; 16 | SP_DEVICE_INTERFACE_DATA interface_data; 17 | PSP_DEVICE_INTERFACE_DETAIL_DATA detail_data = NULL; 18 | ULONG length = 0; 19 | ULONG required_length = 0; 20 | //HRESULT hr; 21 | 22 | GUID GUID_local = interface_GUID; 23 | device_info = SetupDiGetClassDevs(&GUID_local, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); 24 | interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); 25 | result = SetupDiEnumDeviceInterfaces(device_info, NULL, &GUID_local, 0, &interface_data); 26 | 27 | SetupDiGetDeviceInterfaceDetail(device_info, &interface_data, NULL, 0, &required_length, NULL); 28 | detail_data = (PSP_DEVICE_INTERFACE_DETAIL_DATA) LocalAlloc(LMEM_FIXED, required_length); 29 | if (detail_data == NULL) { 30 | SetupDiDestroyDeviceInfoList(device_info); 31 | return false; 32 | } 33 | 34 | detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); 35 | length = required_length; 36 | result = SetupDiGetDeviceInterfaceDetail(device_info, &interface_data, detail_data, length, &required_length, NULL); 37 | if (result == FALSE) { 38 | LocalFree(detail_data); 39 | SetupDiDestroyDeviceInfoList(device_info); 40 | return false; 41 | } 42 | 43 | strncpy(path, detail_data->DevicePath, sizeof(path)-1); 44 | 45 | SetupDiDestroyDeviceInfoList(device_info); 46 | LocalFree(detail_data); 47 | return result; 48 | } 49 | 50 | 51 | CLGLCD_Device::CLGLCD_Device(const GUID device_guid, ULONG timeout) { 52 | setup_handle = NULL; 53 | usb_handle = NULL; 54 | BOOL result = FALSE; 55 | 56 | memset(path, 0, sizeof(path)); 57 | if (!get_device_path(device_guid)) 58 | throw CLGLCD_device_error("Can't find connected CLGLCD USB device."); 59 | 60 | setup_handle = CreateFile( 61 | path, 62 | GENERIC_WRITE | GENERIC_READ, 63 | FILE_SHARE_WRITE | FILE_SHARE_READ, 64 | NULL, 65 | OPEN_EXISTING, 66 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 67 | NULL 68 | ); 69 | if (setup_handle == INVALID_HANDLE_VALUE) { 70 | DWORD err = GetLastError(); 71 | if (err == ERROR_ACCESS_DENIED) throw CLGLCD_error("Access denied to USB device. Is device accessed by other process ?"); 72 | throw CLGLCD_error("Error " + to_string(err) + "initializing device setup handle."); 73 | } 74 | 75 | if (!WinUsb_Initialize(setup_handle, &usb_handle)) 76 | throw CLGLCD_error("Error initializing WinUSB handle."); 77 | 78 | int ep_count = 0; 79 | result = WinUsb_QueryInterfaceSettings(usb_handle, 0, &if_descriptor); 80 | if (result) { 81 | for(UCHAR i = 0; i < if_descriptor.bNumEndpoints; i++) { 82 | result = WinUsb_QueryPipe(usb_handle, 0, i, &pipe_info); 83 | if (result) { 84 | if ((pipe_info.PipeType == UsbdPipeTypeBulk) && USB_ENDPOINT_DIRECTION_OUT(pipe_info.PipeId)) { 85 | ep = pipe_info.PipeId; 86 | ep_count++; 87 | } 88 | } 89 | } 90 | } 91 | if (ep_count != 1) throw CLGLCD_device_error("Unrecognized CLGLCD output pipe configuration"); 92 | if_num = if_descriptor.bInterfaceNumber; 93 | 94 | // Set pipe policy 95 | ULONG timeout_local = timeout; 96 | WinUsb_ResetPipe(usb_handle, ep); 97 | WinUsb_SetPipePolicy(usb_handle, ep, PIPE_TRANSFER_TIMEOUT, sizeof(timeout_local), &timeout_local); 98 | 99 | version = get_version(); 100 | 101 | if (version.compare(0, 8, CLGLCD_MIN_VERSION) < 0) { 102 | throw CLGLCD_error("Unsupported CLGLCD firmware version"); 103 | } 104 | calibration = get_calibration_report(); 105 | }; 106 | 107 | void CLGLCD_Device::cancel_IO() { 108 | if (!CancelIo(setup_handle)) { 109 | DWORD err = GetLastError(); 110 | throw CLGLCD_device_error("Error " + to_string(err) + " canceling I/O on device"); 111 | } 112 | } 113 | 114 | void CLGLCD_Device::reset_endpoint(int endpoint) { 115 | if (!WinUsb_ResetPipe(usb_handle, endpoint)) { 116 | DWORD err = GetLastError(); 117 | throw CLGLCD_device_error("Error " + to_string(err) + " reseting endpoint " + to_string(endpoint)); 118 | } 119 | } 120 | 121 | string CLGLCD_Device::get_version() { 122 | CHAR buf[64]; 123 | memset(&buf, 0, sizeof(buf)); 124 | WINUSB_SETUP_PACKET version_setup; 125 | memset(&version_setup, 0, sizeof(WINUSB_SETUP_PACKET)); 126 | version_setup.Length = sizeof(buf)-1; 127 | version_setup.RequestType = 0xC0; 128 | version_setup.Index = if_num; 129 | version_setup.Request = CLGLCD_VERSION_REPORT; 130 | DWORD len = 0; 131 | if (!WinUsb_ControlTransfer(usb_handle, version_setup, (PUCHAR)buf, sizeof(buf)-1, &len, NULL)) { 132 | throw CLGLCD_device_error("WinUSB error " + to_string(GetLastError()) + " reading CLGLCD version"); 133 | } 134 | return string(buf); 135 | } 136 | 137 | TP_CAL_REPORT* CLGLCD_Device::get_calibration_report() { 138 | TP_CAL_REPORT* calibration_data = NULL; 139 | calibration_data = (TP_CAL_REPORT*) LocalAlloc(LMEM_FIXED, sizeof(TP_CAL_REPORT)); 140 | memset(calibration_data, 0, sizeof(TP_CAL_REPORT)); 141 | WINUSB_SETUP_PACKET cal_setup; 142 | memset(&cal_setup, 0, sizeof(WINUSB_SETUP_PACKET)); 143 | cal_setup.Length = sizeof(TP_CAL_REPORT)-1; 144 | cal_setup.RequestType = 0xC0; 145 | cal_setup.Index = if_num; 146 | cal_setup.Request = CLGLCD_TP_CAL_REPORT; 147 | DWORD recv_len = 0; 148 | if (!WinUsb_ControlTransfer(usb_handle, cal_setup, (PUCHAR)calibration_data, sizeof(TP_CAL_REPORT), &recv_len, NULL)) { 149 | throw CLGLCD_device_error("WinUSB error " + to_string(GetLastError()) + " reading CLGLCD touchpanel calbiration report"); 150 | } 151 | if (recv_len < sizeof(TP_CALIBRATION)) { 152 | throw CLGLCD_device_error("Invalid CLGLCD touchpanel calibration report size" + to_string(recv_len)); 153 | } 154 | return calibration_data; 155 | } 156 | 157 | 158 | CLGLCD_Transfer::CLGLCD_Transfer(CLGLCD_Device* device, size_t buffer_size) { 159 | dev = device; 160 | buffer = (uint8_t*) HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY, buffer_size); 161 | size = buffer_size; 162 | memset(&overlapped, 0, sizeof(OVERLAPPED)); 163 | overlapped.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL); 164 | completed = true; 165 | } 166 | 167 | CLGLCD_Transfer::~CLGLCD_Transfer() { 168 | if (buffer) { 169 | HeapFree(GetProcessHeap(), 0, buffer); 170 | buffer = NULL; 171 | } 172 | if (overlapped.hEvent) { 173 | CloseHandle(overlapped.hEvent); 174 | } 175 | } 176 | 177 | CLGLCD_Transfer* CLGLCD_Transfer::send_async() { 178 | if (completed) { 179 | throw CLGLCD_error("Re-use of completed data transfer, please reset it first"); 180 | } 181 | if (!WinUsb_WritePipe(dev->usb_handle, dev->ep, buffer, size, NULL, &overlapped)) { 182 | DWORD err = GetLastError(); 183 | if (err != ERROR_IO_PENDING) { 184 | throw CLGLCD_transfer_error("WinUSB error " + to_string(err) + " sending data transfer"); 185 | } 186 | } 187 | return this; 188 | } 189 | 190 | 191 | CLGLCD_Transfer* CLGLCD_Transfer::wait(DWORD timeout) { 192 | if (!completed) { 193 | DWORD ovl_res = WaitForSingleObject(overlapped.hEvent, timeout); 194 | if (ovl_res != WAIT_OBJECT_0) { 195 | throw CLGLCD_transfer_error("Overlapped I/O wait object error " + to_string(ovl_res)); 196 | } 197 | } 198 | return this; 199 | } 200 | 201 | CLGLCD_Transfer* CLGLCD_Transfer::reset() { 202 | completed = false; 203 | ResetEvent(overlapped.hEvent); 204 | return this; 205 | } 206 | 207 | size_t CLGLCD_Transfer::transferred_bytes() { 208 | if (completed) { 209 | throw CLGLCD_error("Re-use of completed control transfer, please reset it first"); 210 | } 211 | DWORD transferred; 212 | if (!WinUsb_GetOverlappedResult(dev->usb_handle, &overlapped, &transferred, false)) { 213 | DWORD err = GetLastError(); 214 | if (err != ERROR_IO_INCOMPLETE) { 215 | throw CLGLCD_transfer_error("WinUSB error " + to_string(err) + " reading input report"); 216 | } 217 | return 0; 218 | } 219 | completed = true; 220 | return transferred; 221 | } 222 | 223 | 224 | CLGLCD_Control_Transfer::CLGLCD_Control_Transfer(CLGLCD_Device* device, size_t buffer_size) : CLGLCD_Transfer(device, buffer_size) { 225 | memset(&setup, 0, sizeof(WINUSB_SETUP_PACKET)); 226 | } 227 | 228 | CLGLCD_Transfer* CLGLCD_Control_Transfer::send_async() { 229 | if (completed) { 230 | throw CLGLCD_error("Re-use of completed transfer, please reset it first"); 231 | } 232 | if (!WinUsb_ControlTransfer(dev->usb_handle, setup, buffer, size, NULL, &overlapped)) { 233 | DWORD err = GetLastError(); 234 | if (err != ERROR_IO_PENDING) { 235 | throw CLGLCD_transfer_error("WinUSB error " + to_string(err) + " sending control transfer"); 236 | } 237 | } 238 | return this; 239 | } 240 | 241 | -------------------------------------------------------------------------------- /usb_bridge/clglcd_usb_host/clglcd_winusb.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLGLCD WinUSB interface 3 | // 4 | // Wrapper classes for WinUSB 5 | // 6 | 7 | #ifndef CLGLCD_WINUSB_H 8 | #define CLGLCD_WINUSB_H 9 | 10 | #include 11 | #include 12 | 13 | // DeviceClassGUID for the controller 14 | DEFINE_GUID(GUID_CLASS_CLGLCD, 0x1DC9A650L, 0x260B, 0x4128, 0x9F, 0x0E, 0x43, 0x4C, 0x47, 0x4C, 0x43, 0x44); 15 | 16 | // Version 17 | #define CLGLCD_MIN_VERSION "CLGLCD10" 18 | 19 | // Reports 20 | #define CLGLCD_VERSION_REPORT 0 21 | #define CLGLCD_TP_CAL_REPORT 1 22 | #define CLGLCD_TOUCH_REPORT 2 23 | 24 | #define TRANSFER_SIZE 160 * 64 // 160 packet * (60 bytes + overhead) 25 | #define MAX_DEVPATH_LENGTH 256 26 | 27 | using namespace std; 28 | 29 | #pragma pack(1) 30 | typedef struct _TP_STATE { 31 | int8_t z1; 32 | int8_t z2; 33 | int16_t x; 34 | int16_t y; 35 | uint16_t cnt; 36 | } TP_STATE; 37 | #pragma pack() 38 | 39 | #pragma pack(1) 40 | typedef struct _TP_REPORT { 41 | TP_STATE state; 42 | uint8_t reserved[54]; 43 | } TP_REPORT; 44 | #pragma pack() 45 | 46 | #pragma pack(1) 47 | typedef struct _TP_CALIBRATION { 48 | int16_t x_min; 49 | int16_t x_fact; 50 | int16_t y_min; 51 | int16_t y_fact; 52 | int16_t z1_min; 53 | int16_t z2_max; 54 | } TP_CALIBRATION; 55 | #pragma pack() 56 | 57 | #pragma pack(1) 58 | typedef struct _TP_CAL_REPORT { 59 | TP_CALIBRATION data; 60 | uint8_t reserved[50]; 61 | } TP_CAL_REPORT; 62 | #pragma pack() 63 | 64 | // class has pointer data members but does not override 65 | class CLGLCD_Device { 66 | public: 67 | HANDLE usb_handle; 68 | CHAR path[MAX_DEVPATH_LENGTH]; 69 | BYTE if_num; 70 | BYTE ep; 71 | string version; 72 | TP_CAL_REPORT* calibration; 73 | CLGLCD_Device(const GUID InterfaceGuid, ULONG timeout); 74 | // TODO variant with serial number 75 | ~CLGLCD_Device(); 76 | void cancel_IO(); 77 | void reset_endpoint(int endpoint); 78 | protected: 79 | HANDLE setup_handle; 80 | USB_INTERFACE_DESCRIPTOR if_descriptor; 81 | WINUSB_PIPE_INFORMATION pipe_info; 82 | BOOL get_device_path(const GUID interface_GUID); 83 | string get_version(); 84 | TP_CAL_REPORT* get_calibration_report(); 85 | }; 86 | 87 | //class has virtual functions and accessible non-virtual destructor 88 | 89 | class CLGLCD_Transfer { 90 | public: 91 | WINUSB_SETUP_PACKET setup; 92 | uint8_t *buffer; 93 | size_t size; 94 | size_t result_size; 95 | OVERLAPPED overlapped; 96 | bool completed; 97 | CLGLCD_Transfer(CLGLCD_Device* device, size_t size); 98 | virtual ~CLGLCD_Transfer(); 99 | virtual CLGLCD_Transfer* send_async(); 100 | virtual CLGLCD_Transfer* wait(DWORD timeout); 101 | virtual CLGLCD_Transfer* reset(); 102 | virtual size_t transferred_bytes(); 103 | protected: 104 | CLGLCD_Device* dev; 105 | }; 106 | 107 | class CLGLCD_Control_Transfer: public CLGLCD_Transfer { 108 | public: 109 | WINUSB_SETUP_PACKET setup; 110 | CLGLCD_Control_Transfer(CLGLCD_Device* device, size_t buffer_size);// : CLGLCD_Transfer(device, buffer_size); 111 | virtual CLGLCD_Transfer* send_async() override; 112 | }; 113 | 114 | 115 | #endif // CLGLCD_WINUSB_H 116 | -------------------------------------------------------------------------------- /usb_bridge/clglcd_usb_host/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "clglcd_exc.h" 6 | #include "clglcd_winusb.h" 7 | #include "clglcd_ipc.h" 8 | 9 | #ifdef STDOUT_DEBUG 10 | #include 11 | #endif 12 | 13 | using namespace std; 14 | 15 | // Overlapping buffers 16 | #define OVERLAPING_TRANSFERS 4 // Number of queued transfers 17 | #define TRANSFER_WAIT 100 // miliseconds 18 | 19 | // Values used for temporal dithering of LCD pixels 20 | // Or in Arduino terminology: PWM for the pixels 21 | static const uint16_t FRC_TABLE[16] = { 22 | 0b0000000000000000, // 0 23 | 0b0000000000000001, // 1 24 | 0b0000000100000001, // 2 25 | 0b0000010000100001, // 3 26 | 0b0001000100010001, // 4 27 | 0b0010010010010001, // 5 28 | 0b0101001001010010, // 6 29 | 0b0100101010010101, // 7 30 | 0b0101010101010101, // 8 31 | 0b0101011010101011, // 9 32 | 0b0101101101011011, // 10 33 | 0b0111011011011011, // 11 34 | 0b0111011101110111, // 12 35 | 0b0111111101111111, // 13 36 | 0b1111111011111111, // 14 37 | 0b1111111111111111, // 15 38 | }; 39 | 40 | // The whole functions ignores the fact that we are probably 41 | // running on 64bit CPU... This is just prototype so let 42 | // the compiler make the best of it. 43 | void render_frc_buffer(uint8_t *frame_data, uint8_t *usb_buffer, uint32_t rnd_seed, uint8_t frc_step) { 44 | uint8_t *frame_pos = frame_data; 45 | uint8_t *buf_pos = usb_buffer; 46 | uint32_t rnd32 = rnd_seed; 47 | 48 | for(uint8_t pckt_no=0; pckt_no<160; pckt_no++) { 49 | *buf_pos++ = pckt_no; 50 | // Dither and pack 480 frame bytes into 60 USB buffer bytes 51 | uint8_t d, p, b, z; 52 | for(uint8_t x=0; x<60; x++) { 53 | z = 0; 54 | for(uint8_t i=0; i<8; i++) { 55 | // xorshift32 56 | rnd32 ^= rnd32 << 13; 57 | rnd32 ^= rnd32 >> 17; 58 | rnd32 ^= rnd32 << 5; 59 | d = *frame_pos++; 60 | p = (d >> 4) & 0x0f; 61 | b = (FRC_TABLE[p] >> ((frc_step + rnd32) & 0x0f)) & 0x01; 62 | z = (z << 1) | b; 63 | } 64 | *buf_pos++ = z; 65 | } 66 | buf_pos += 3; 67 | } 68 | }; 69 | 70 | // Convert raw touch-panel readings into screen coordinates 71 | // based on calibration data reported by device 72 | void calculate_touch_position(TP_STATE* touch, TP_CALIBRATION c9n, CLGLCD_SHM *lcd) { 73 | // copy raw data, if client wants to do its math 74 | lcd->raw_touch_x = touch->x; 75 | lcd->raw_touch_y = touch->y; 76 | lcd->raw_touch_z1 = touch->z1; 77 | lcd->raw_touch_z2 = touch->z2; 78 | lcd->raw_touch_cnt = touch->cnt; 79 | 80 | // Arduino signals invalid reading or reading in progress with z1 < 0 81 | // If reading is valid, process touch data with calibration 82 | if (touch->z1 >= 0) { 83 | int32_t x = max(0, min(319, (int)round((touch->x - c9n.x_min) * c9n.x_fact / 1024))); 84 | int32_t y = max(0, min(239, (int)round((touch->y - c9n.y_min) * c9n.y_fact / 1024))); 85 | int32_t z = max(0, min(100, (int)round(128 + (touch->z1 + x/8) - (touch->z2 + y/8)))); 86 | if ((touch->z1 >= c9n.z1_min) && (touch->z2 <= c9n.z2_max)) { 87 | lcd->touch_x = x; 88 | lcd->touch_y = y; 89 | lcd->touch_preasure = z; 90 | } else { 91 | lcd->touch_x = -1; 92 | lcd->touch_y = -1; 93 | lcd->touch_preasure = 0; 94 | } 95 | lcd->touch_ts = touch->cnt; 96 | } 97 | } 98 | 99 | 100 | int main() { 101 | // Below lines are optional, raise process priority on Windows 102 | DWORD proc_pid = GetCurrentProcessId(); 103 | HANDLE proc_handle = OpenProcess(PROCESS_ALL_ACCESS, true, proc_pid); 104 | SetPriorityClass(proc_handle, HIGH_PRIORITY_CLASS); 105 | 106 | // Represents CLGLCD bridge Arduino 107 | CLGLCD_Device* dev = new CLGLCD_Device(GUID_CLASS_CLGLCD, 100); 108 | #ifdef STDOUT_DEBUG 109 | cout << dev->version << " @ " << dev->path << endl; 110 | #endif 111 | 112 | // Interprocess mutex and shared memory API 113 | CLGLCD_ipc* ipc = new CLGLCD_ipc(); 114 | CLGLCD_SHM *lcd = ipc->shm; 115 | strncpy(&lcd->version[0], dev->version.c_str(), 8); 116 | lcd->active_ts = 0; 117 | lcd->active_buffer = -1; 118 | 119 | try { 120 | 121 | // Setup frame transfers 122 | CLGLCD_Transfer* transfer[OVERLAPING_TRANSFERS]; 123 | for (int i=0; ireset(); 133 | tp_read->setup.Length = sizeof(TP_REPORT); 134 | tp_read->setup.RequestType = 0xC0; 135 | tp_read->setup.Index = dev->if_num; 136 | tp_read->setup.Request = CLGLCD_TOUCH_REPORT; 137 | tp_read->send_async(); 138 | 139 | while (true) { // Main loop 140 | 141 | //time_t ts = time(NULL); 142 | //time_t delta_ts = ts - lcd->active_ts; 143 | uint32_t ts = (uint32_t) time(NULL); 144 | uint32_t delta_ts = ts - lcd->active_ts; 145 | 146 | lcd->usb_host_ts = ts; 147 | 148 | if (active) { 149 | // Check which if any frame buffer is still active and if 150 | // client timestamp is recent (i.e. 60 seconds) 151 | if ((delta_ts > 60) || (lcd->active_buffer < 0) || (lcd->active_buffer > 1)) { 152 | // Wait out for any outstanding transfers 153 | for(int i=0; iwait(100); } catch (CLGLCD_transfer_error &e) {} 155 | } 156 | active = false; 157 | frc_step = 0; 158 | #ifdef STDOUT_DEBUG 159 | cout << "Display OFF, delta: " << delta_ts << endl; 160 | #endif // STDOUT_DEBUG 161 | Sleep(1); 162 | continue; 163 | } 164 | 165 | try { 166 | 167 | frame_buffer = lcd->frame_buffer[lcd->active_buffer]; 168 | 169 | // Wait for queued transfer to finish 170 | transfer[transfer_index]->wait(TRANSFER_WAIT)->reset(); 171 | 172 | // Prepare new buffer with FRC 173 | // TODO: Generate real random when frc_step is 0 174 | uint32_t rnd_seed = 0x1235678; 175 | render_frc_buffer(frame_buffer, transfer[transfer_index]->buffer, rnd_seed, frc_step); 176 | 177 | // And send it again... 178 | transfer[transfer_index]->send_async(); 179 | 180 | if (++transfer_index >= OVERLAPING_TRANSFERS) transfer_index = 0; 181 | frc_step = (frc_step + 1) & 0x0f; 182 | 183 | } catch (CLGLCD_transfer_error &e) { 184 | dev->cancel_IO(); 185 | #ifdef STDOUT_DEBUG 186 | cout << '.'; 187 | #endif 188 | for (int i = 0; i < OVERLAPING_TRANSFERS; i++) 189 | transfer[i]->wait(10); 190 | dev->reset_endpoint(dev->ep); 191 | } 192 | 193 | 194 | } else { // not active 195 | // Check if client activated the display 196 | 197 | if ((lcd->active_buffer > 0) && (lcd->active_buffer < 2) && (delta_ts < 60)) { 198 | #ifdef STDOUT_DEBUG 199 | cout << "Display ON, frame buffer " << lcd->active_buffer << ", delta:" << delta_ts << endl; 200 | #endif // STDOUT_DEBUG 201 | dev->reset_endpoint(dev->ep); 202 | active = true; 203 | continue; 204 | } 205 | 206 | tp_read->wait(TRANSFER_WAIT); 207 | Sleep(10); 208 | } 209 | 210 | // Check touch-panel read result 211 | int read_bytes; 212 | try { 213 | read_bytes = tp_read->transferred_bytes(); 214 | } catch (CLGLCD_transfer_error &e) { 215 | // If we cant read touch-panel, likely device disconnected (or reset) 216 | // Check for ERROR_GEN_FAILURE (31) 217 | throw CLGLCD_device_error(e.what()); 218 | } 219 | 220 | 221 | if (read_bytes == sizeof(TP_STATE)) { 222 | TP_STATE* touch = (TP_STATE*) tp_read->buffer; 223 | calculate_touch_position(touch, dev->calibration->data, lcd); 224 | 225 | // Restart control transfer 226 | tp_read->reset(); 227 | tp_read->send_async(); 228 | } 229 | 230 | 231 | } // end while (true) 232 | 233 | } catch (CLGLCD_error &e) { 234 | #ifdef STDOUT_DEBUG 235 | cerr << e.what() << endl; 236 | #endif 237 | lcd->usb_host_ts = 0; 238 | return 1; 239 | } 240 | 241 | return 0; 242 | } 243 | 244 | 245 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /usb_bridge/demo/SampleVideo_640x360_10mb.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostoski/arduino-clglcd/5f8554aef9935b67f16f78a8b55424dd1f017175/usb_bridge/demo/SampleVideo_640x360_10mb.mp4 -------------------------------------------------------------------------------- /usb_bridge/demo/clglcd.py: -------------------------------------------------------------------------------- 1 | # 2 | # Python classes for IPC communication 3 | # with clglcd_usb_host process 4 | # 5 | 6 | import mmap, time, ctypes 7 | from ctypes import wintypes, Structure, sizeof, c_char, c_byte, c_uint32, c_int32 8 | 9 | SUPPORTED_VERSION = b'CLGLCD10' 10 | 11 | # 12 | # Named objects from CLGLCD USB host process 13 | # 14 | CLGLCD_MUTEX_NAME = "Global\\CLGLCD_MUTEX" 15 | CLGLCD_SHM_NAME = "Local\\CLGLCD_SHM" 16 | 17 | # Adopted from: 18 | # http://code.activestate.com/recipes/577794-win32-named-mutex-class-for-system-wide-mutex/ 19 | # To use OpenMutex instead CreateMutex 20 | 21 | MUTEX_ALL_ACCESS = 0x1F0001 22 | MUTEX_MODIFY_STATE = 0x000001 23 | SYNCHRONIZE = 0x100000 24 | 25 | ERROR_FILE_NOT_FOUND = 0x02 26 | 27 | """Named mutex handling (for Win32).""" 28 | # Create ctypes wrapper for Win32 functions we need, with correct argument/return types 29 | _CreateMutex = ctypes.windll.kernel32.CreateMutexA 30 | _CreateMutex.argtypes = [wintypes.LPCVOID, wintypes.BOOL, wintypes.LPCSTR] 31 | _CreateMutex.restype = wintypes.HANDLE 32 | 33 | #ctypes.windll.kernel32.OpenMutexW.argtypes = [ctypes.wintypes.DWORD, ctypes.wintypes.BOOL, ctypes.wintypes.LPCWSTR] 34 | _OpenMutex = ctypes.windll.kernel32.OpenMutexW 35 | _OpenMutex.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.LPCWSTR] 36 | _OpenMutex.restype = wintypes.HANDLE 37 | 38 | _WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject 39 | _WaitForSingleObject.argtypes = [wintypes.HANDLE, wintypes.DWORD] 40 | _WaitForSingleObject.restype = wintypes.DWORD 41 | 42 | _ReleaseMutex = ctypes.windll.kernel32.ReleaseMutex 43 | _ReleaseMutex.argtypes = [wintypes.HANDLE] 44 | _ReleaseMutex.restype = wintypes.BOOL 45 | 46 | _CloseHandle = ctypes.windll.kernel32.CloseHandle 47 | _CloseHandle.argtypes = [wintypes.HANDLE] 48 | _CloseHandle.restype = wintypes.BOOL 49 | 50 | class NamedMutex(object): 51 | """A named, system-wide mutex that can be acquired and released.""" 52 | 53 | def __init__(self, name, access=SYNCHRONIZE, acquired=False): 54 | """Create named mutex with given name, also acquiring mutex if acquired is True. 55 | Mutex names are case sensitive, and a filename (with backslashes in it) is not a 56 | valid mutex name. Raises WindowsError on error. 57 | 58 | """ 59 | self.name = name 60 | self.acquired = acquired 61 | self.handle = None 62 | ret = _OpenMutex(access, False, name) 63 | if not ret: 64 | # TODO: Friendly message for ERROR_FILE_NOT_FOUND 65 | err = ctypes.GetLastError() 66 | if (err == ERROR_FILE_NOT_FOUND): 67 | raise Exception("Unable to open mutex. CLGLCD USB host process is not running ?") 68 | raise ctypes.WinError() 69 | self.handle = ret 70 | if acquired: 71 | self.acquire() 72 | 73 | def acquire(self, timeout=None): 74 | """Acquire ownership of the mutex, returning True if acquired. If a timeout 75 | is specified, it will wait a maximum of timeout seconds to acquire the mutex, 76 | returning True if acquired, False on timeout. Raises WindowsError on error. 77 | 78 | """ 79 | if timeout is None: 80 | # Wait forever (INFINITE) 81 | timeout = 0xFFFFFFFF 82 | else: 83 | timeout = int(round(timeout * 1000)) 84 | ret = _WaitForSingleObject(self.handle, timeout) 85 | if ret in (0, 0x80): 86 | # Note that this doesn't distinguish between normally acquired (0) and 87 | # acquired due to another owning process terminating without releasing (0x80) 88 | self.acquired = True 89 | return True 90 | elif ret == 0x102: 91 | # Timeout 92 | self.acquired = False 93 | return False 94 | else: 95 | # Waiting failed 96 | raise ctypes.WinError() 97 | 98 | def release(self): 99 | """Relase an acquired mutex. Raises WindowsError on error.""" 100 | ret = _ReleaseMutex(self.handle) 101 | if not ret: 102 | raise ctypes.WinError() 103 | self.acquired = False 104 | 105 | def close(self): 106 | """Close the mutex and release the handle.""" 107 | if self.handle is None: 108 | # Already closed 109 | return 110 | ret = _CloseHandle(self.handle) 111 | if not ret: 112 | raise ctypes.WinError() 113 | self.handle = None 114 | 115 | __del__ = close 116 | 117 | def __repr__(self): 118 | """Return the Python representation of this mutex.""" 119 | return '{0}({1!r}, acquired={2})'.format( 120 | self.__class__.__name__, self.name, self.acquired) 121 | 122 | __str__ = __repr__ 123 | 124 | # Make it a context manager so it can be used with the "with" statement 125 | def __enter__(self): 126 | self.acquire() 127 | return self 128 | 129 | def __exit__(self, exc_type, exc_val, exc_tb): 130 | self.release() 131 | 132 | 133 | class CLGLCD_SHM(Structure): 134 | """ Shared memory structure for communication 135 | with clglcd_usb_host process""" 136 | _fields_ = [ 137 | # USB Host ID 138 | ("version", c_char * 8), 139 | ("usb_host_ts", c_uint32), 140 | ("reserved1", c_byte * 52), 141 | # Touchpanel 142 | ("touch_ts", c_uint32), 143 | ("touch_x", c_int32), 144 | ("touch_y", c_int32), 145 | ("touch_p", c_uint32), 146 | ("raw_touch_x", c_int32), 147 | ("raw_touch_y", c_int32), 148 | ("raw_touch_z1", c_int32), 149 | ("raw_touch_z2", c_int32), 150 | ("raw_touch_cnt", c_int32), 151 | ("reserved2", c_byte * 28), 152 | # Reserved 153 | ("reserved3", c_byte * 64), 154 | # Frame buffer control 155 | ("active_ts", c_uint32), 156 | ("active_frame", c_int32), 157 | ("reserved4", c_byte * 56), 158 | # Frame buffers 159 | ("frame", ((c_byte * (240 * 320)) * 2)) 160 | ] 161 | 162 | class IPC: 163 | 164 | def __init__(self, open = True): 165 | self.mutex = None 166 | self.shm_map = None 167 | self.shm = None 168 | self.version = None 169 | self.current_frame = -1 170 | if (open): self.open() 171 | 172 | def open(self): 173 | self.mutex = NamedMutex(CLGLCD_MUTEX_NAME) 174 | if not self.mutex.acquire(1): 175 | raise ctypes.WinError("Unable to acquire CLGLCD mutex. Is device used by other process?") 176 | self.shm_map = mmap.mmap(0, sizeof(CLGLCD_SHM), CLGLCD_SHM_NAME) 177 | if not self.shm_map: 178 | raise ctypes.WinError() 179 | self.shm = CLGLCD_SHM.from_buffer(self.shm_map) 180 | 181 | self.version = self.shm.version 182 | if self.version < SUPPORTED_VERSION: 183 | raise RuntimeError("Unsupported CLGLCD version. %s" % seld.shm.version) 184 | if (int(time.time()) - self.shm.usb_host_ts) > 1: 185 | raise RuntimeError("CLGLCD SHM Timestamp is stale (%d)" % self.shm.usb_host_ts) 186 | 187 | def _check_host_ts(self): 188 | if (int(time.time()) - self.shm.usb_host_ts) > 1: 189 | raise RuntimeError("CLGLCD SHM Timestamp got stale (%d)" % self.shm.usb_host_ts) 190 | 191 | def show(self, image_bytes): 192 | """ Displays bitmap on screen. Typically you 193 | can use 320x240 Grayscale ('L') PIL image 194 | and its .tobytes() method """ 195 | self._check_host_ts() 196 | if self.current_frame < 0: self.current_frame = 0 197 | self.current_frame ^= 1 198 | self.shm.frame[self.current_frame][:] = image_bytes 199 | self.shm.active_frame = self.current_frame 200 | self.shm.active_ts = int(time.time()) 201 | 202 | def wait_for_release(self, timeout = 0): 203 | """ Wait for release of touchpanel or timeout. 204 | Timeout is in (fratcion) of secconds. Default zero 205 | means wait sometime in year 2038. """ 206 | target = 2 ** 31 if timeout == 0 else time.time() + timeout 207 | while (self.shm.touch_p > 1) and (time.time() < target): 208 | time.sleep(0.01) 209 | self._check_host_ts() 210 | return (self.shm.touch_p < 1) 211 | 212 | def wait_for_touch(self, timeout = 0, release_timeout = 2): 213 | """ Wait for touch on the touchpanel or timeout. 214 | Timeout is in (fratcion) of secconds. Default zero 215 | means wait sometime in year 2038. See wait_for_release 216 | for release timeout. """ 217 | touched = False 218 | target = time.time() + timeout if timeout > 0 else 2 ** 31 219 | while (self.shm.touch_p < 1) and (time.time() < target): 220 | time.sleep(0.01) 221 | self._check_host_ts() 222 | touched = (self.shm.touch_p > 1) 223 | self.wait_for_release(release_timeout) 224 | return touched 225 | 226 | def touch_point(self): 227 | """ Return tuple of current X, Y, and Pressure values 228 | from the touchpanel """ 229 | return (self.shm.touch_x, self.shm.touch_y, self.shm.touch_p) 230 | 231 | def raw_touch(self): 232 | """ Return tuple of current X, Y, and Pressure values 233 | from the touchpanel """ 234 | return (self.shm.raw_touch_x, self.shm.raw_touch_y, 235 | self.shm.raw_touch_z1, self.shm.raw_touch_z2, self.shm.raw_touch_cnt) 236 | 237 | 238 | def is_touched(self, release_timeout = 2): 239 | """ Return true if there is touch on the touchpanel 240 | See wait_for_release for release timeout. """ 241 | if not self.shm.touch_p > 0: return False 242 | self.wait_for_release(release_timeout) 243 | return True 244 | 245 | def sleep(self, timeout): 246 | """ Sleep will keeping USB host notified we are still alive """ 247 | t = time.time() 248 | self.shm.active_ts = t 249 | target = t + timeout 250 | while (t < timeout): 251 | time.sleep(min(1, timeout - t)) 252 | t = time.time() 253 | self.shm.active_ts = t 254 | self._check_host_ts() 255 | 256 | def close(self): 257 | if self.shm: 258 | self.shm.active_frame = -1 259 | self.shm.active_ts = -1 260 | self.shm = None 261 | if self.shm_map: 262 | self.shm_map.close() 263 | if self.mutex: 264 | if self.mutex.acquired: self.mutex.release() 265 | self.mutex.close() 266 | self.mutex = None 267 | 268 | __del__ = close 269 | 270 | def __repr__(self): 271 | return '{0}({1!r}, device_version={2})'.format( 272 | self.__class__.__name__, self.name, self.version) 273 | 274 | __str__ = __repr__ 275 | 276 | # Context manager 277 | def __enter__(self): 278 | return self 279 | 280 | def __exit__(self, exc_type, exc_val, exc_tb): 281 | self.close() 282 | 283 | # EOF -------------------------------------------------------------------------------- /usb_bridge/demo/lcd_shm_demo.py: -------------------------------------------------------------------------------- 1 | # 2 | # Python demo for CLGLCD. 3 | # 4 | # Talks to clglcd_usb_host 'driver' via shared memory IPC 5 | # 6 | # Requires Pillow, and can make use of WMI and CV2 libraries. 7 | # 8 | # Copyright (c) 2019 Ivan Kostoski 9 | # 10 | 11 | import time 12 | import clglcd 13 | 14 | try: 15 | from PIL import Image, ImageFont, ImageDraw 16 | except ImportError: 17 | raise "PIL (or Pillow) library is required for demo" 18 | 19 | lcd = clglcd.IPC() 20 | 21 | try: 22 | 23 | # Standby 24 | # To show PNG image on the LCD, all you need are the below two lines 25 | standby = Image.open('standby320.png').convert('L') 26 | lcd.show(standby.tobytes()) 27 | 28 | print("Touch to start") 29 | if not lcd.wait_for_touch(300): 30 | raise SystemExit(-1) 31 | 32 | # Intro 33 | text = ( 34 | "This is STN monochrome 320x240\n'Controllerless' Graphics LCD module\n" 35 | "driven by Arduino Leonardo\n(ATmega32u4 MCU) USB 'bridge'.\n\n" 36 | "Let's see what we can do with it..." 37 | ) 38 | font = ImageFont.truetype('arial.ttf', 18) 39 | intro = Image.new('L', (320, 240), 0) 40 | draw = ImageDraw.Draw(intro) 41 | (tsx, tsy) = draw.textsize(text, font) 42 | draw.text((160-tsx/2, 120-tsy/2), text, fill=255, font=font, align='center') 43 | lcd.show(intro.tobytes()) 44 | lcd.wait_for_touch(30) 45 | 46 | # Touchpanel test 47 | font = ImageFont.truetype('arial.ttf', 13) 48 | touch_base = Image.new('L', (320, 240), 0) 49 | draw = ImageDraw.Draw(touch_base) 50 | draw.text((0, 0), "Touchpanel test. Touch the 'X' to stop.", font=font, fill=255) 51 | draw.rectangle((300, 0, 319, 19), outline=255) 52 | draw.text((306, 3), "X", font=font, fill=255) 53 | while True: 54 | touch = touch_base.copy() 55 | x, y, p = lcd.touch_point() 56 | if p > 0: 57 | if (x > 300 and y < 20): break 58 | draw = ImageDraw.Draw(touch) 59 | text = "X:"+str(x)+" Y:"+str(y)+" P:"+str(p) 60 | (tsx, tsy) = draw.textsize(text, font) 61 | draw.text((160-tsx/2, 239-tsy), text, font=font, fill=255) 62 | # Raw touchpanel readings 63 | #rx, ry, rz1, rz2, rcnt = lcd.raw_touch() 64 | #text = "rX:"+str(rx)+" rY:"+str(ry)+" rZ1:"+str(rz1)+" rZ2:"+str(rz2) 65 | #(tsx, tsy) = draw.textsize(text, font) 66 | #draw.text((160-tsx/2, 225-tsy), text, font=font, fill=255) 67 | draw.ellipse((x-p/2, y-p/2, x+p/2, y+p/2), outline=127, width=2) 68 | draw.ellipse((x-p/4, y-p/4, x+p/4, y+p/4), outline=192, width=2) 69 | draw.ellipse((x-1, y-1, x+1, y+1), fill=255, width=1) 70 | lcd.show(touch.tobytes()) 71 | time.sleep(0.1) 72 | 73 | 74 | # Display OpenHardwareMonitor sensors 75 | cpu_load = None 76 | try: 77 | import wmi, subprocess 78 | kill_load = time.time() + 7 79 | 80 | try: 81 | cpu_load = subprocess.Popen(["cpuburn", "-u=1", "-n=7"], shell=False, stdout=subprocess.PIPE) 82 | except FileNotFoundError: 83 | pass 84 | 85 | ohmwmi = wmi.WMI(namespace="root\\OpenHardwareMonitor") 86 | ohm_sensors = { 87 | "cpu_load": "/intelcpu/0/load/0", 88 | "cpu_temp": "/lpc/it8628e/temperature/2", 89 | "gpu_load": "/nvidiagpu/0/load/0", 90 | "gpu_temp": "/nvidiagpu/0/temperature/0" 91 | } 92 | wql = ("SELECT Identifier, Value FROM Sensor WHERE " + 93 | "(Identifier='" + "' OR Identifier='".join(ohm_sensors.values())+"')") 94 | id_map = {v: k for k, v in ohm_sensors.items()} 95 | values = {} 96 | 97 | title_font = ImageFont.truetype('calibri.ttf', 18) 98 | label_font = ImageFont.truetype('arial.ttf', 20) 99 | value_font = ImageFont.truetype('impact.ttf', 60) 100 | hw_base = Image.new('L', (320, 240), 0) 101 | draw = ImageDraw.Draw(hw_base) 102 | (tsx, tsy) = draw.textsize("OpenHardwareMonitor sensors", title_font) 103 | draw.text((160-tsx/2, 2), "OpenHardwareMonitor sensors", font=title_font, fill=255) 104 | (tsx, tsy) = draw.textsize("CPU", label_font) 105 | draw.text((80-tsx/2, 30), "CPU", font=label_font, fill=255) 106 | draw.text((240-tsx/2, 30), "GPU", font=label_font, fill=255) 107 | draw.rectangle((18, 123, 142, 142), outline=255) 108 | draw.rectangle((178, 123, 302, 142), outline=255) 109 | 110 | t_x = range(20, 300) 111 | cpu_y = [220] * 280 112 | gpu_y = [220] * 280 113 | 114 | t = time.time() 115 | time_target = t + 20 116 | while t < time_target: 117 | for sensor in ohmwmi.query(wql): 118 | values[id_map[sensor.Identifier]] = int(sensor.Value) 119 | 120 | cpu_y.pop(0) 121 | cpu_y.append(int(220 - 0.7*values["cpu_load"])) 122 | gpu_y.pop(0) 123 | gpu_y.append(int(220 - 0.7*values["gpu_load"])) 124 | 125 | hw_img = hw_base.copy() 126 | draw = ImageDraw.Draw(hw_img) 127 | (tsx, tsy) = draw.textsize((str(values['cpu_temp']) + "°C"), value_font) 128 | draw.text((80-tsx/2, 50), (str(values['cpu_temp']) + "°C"), font=value_font, fill=255) 129 | (tsx, tsy) = draw.textsize((str(values['gpu_temp']) + "°C"), value_font) 130 | draw.text((240-tsx/2, 50), (str(values['gpu_temp']) + "°C"), font=value_font, fill=255) 131 | draw.rectangle((20, 125, 20 + 1.2*values["cpu_load"], 140), fill=192) 132 | draw.rectangle((180, 125, 180 + 1.2*values["gpu_load"], 140), fill=192) 133 | draw.line(list(zip(t_x, gpu_y)), fill=192, width=3) 134 | draw.line(list(zip(t_x, cpu_y)), fill=255, width=2) 135 | 136 | lcd.show(hw_img.tobytes()) 137 | t = time.time() 138 | if cpu_load and ((t > kill_load) or lcd.is_touched(release_timeout=-1)): 139 | cpu_load.terminate() 140 | cpu_load = None 141 | time.sleep(0.5) 142 | if lcd.is_touched(): break 143 | 144 | except ImportError: 145 | pass 146 | finally: 147 | if cpu_load: 148 | cpu_load.terminate() 149 | cpu_load.wait() 150 | 151 | # Grayscale bars 152 | grayscale = Image.new('L', (320, 240), 0) 153 | font = ImageFont.truetype('arial.ttf', 13) 154 | draw = ImageDraw.Draw(grayscale) 155 | for c in range(0, 16): 156 | draw.text((0, c*15+1), str(c), font=font, fill=(c<<4)) 157 | draw.rectangle((20, c*15, 300, c*15+15), fill=(c<<4)) 158 | text = "4-bit Temporal Dithering (FRC)" 159 | font = ImageFont.truetype('arial.ttf', 16) 160 | (tsx, tsy) = draw.textsize(text, font) 161 | draw.text((160-tsx/2, 1), text, font=font, fill=255) 162 | lcd.show(grayscale.tobytes()) 163 | lcd.wait_for_touch(10) 164 | 165 | # Lena 166 | lena = Image.open('lena320x240.png').convert('L') 167 | lcd.show(lena.tobytes()) 168 | lcd.wait_for_touch(10) 169 | 170 | # Big buck bunny @25fps, needs CV2 171 | try: 172 | import cv2 173 | vidcap = cv2.VideoCapture('SampleVideo_640x360_10mb.mp4') 174 | t = time.time() 175 | time_target = t + 30 176 | success, image = vidcap.read() 177 | while (success and (t < time_target)): 178 | thumb = cv2.resize(image, (320, 240), cv2.INTER_AREA) 179 | grayscale = cv2.cvtColor(thumb, cv2.COLOR_RGB2GRAY) 180 | lcd.show(grayscale.tobytes()) 181 | success, image = vidcap.read() 182 | delta = time.time() - t 183 | if delta < 0.04: time.sleep(0.04 - delta) 184 | if lcd.is_touched(): break 185 | t = time.time() 186 | except ImportError: 187 | pass 188 | 189 | # Explanation 190 | text = ( 191 | "The content is rendered with Python\n" 192 | "on 'Big' PC and streamed to the Arduino \n" 193 | "via USB at ~5.7Mbps for 70Hz refresh rate.\n" 194 | "By using 'temporal dithering' (FRC) of the\n" 195 | "LCD pixels we get 16 grayscale levels\n" 196 | "The Arduino is providing the control and \n" 197 | "timing of the LCD module and acting\n" 198 | "as a USB to LCD brigde\n\n" 199 | 200 | "The protoboard provides LCD drive voltage\n" 201 | "(-24V) and control for the LED backlight." 202 | ) 203 | font = ImageFont.truetype('arial.ttf', 15) 204 | intro = Image.new('L', (320, 240), 0) 205 | draw = ImageDraw.Draw(intro) 206 | (tsx, tsy) = draw.textsize(text, font) 207 | draw.text((160-tsx/2, 120-tsy/2), text, fill=255, font=font, align='center') 208 | lcd.show(intro.tobytes()) 209 | lcd.wait_for_touch(20) 210 | 211 | # TY 212 | ty = Image.new('L', (320, 240), 0) 213 | font = ImageFont.truetype('arial.ttf', 50) 214 | draw = ImageDraw.Draw(ty) 215 | text = "Thank You!" 216 | (tsx, tsy) = draw.textsize(text, font) 217 | draw.text((160-tsx/2, 120-tsy/2), text, font=font, fill=255) 218 | lcd.show(ty.tobytes()) 219 | lcd.wait_for_touch(5) 220 | 221 | # Shutdown the display 222 | finally: 223 | lcd.close() 224 | 225 | -------------------------------------------------------------------------------- /usb_bridge/demo/lena320x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostoski/arduino-clglcd/5f8554aef9935b67f16f78a8b55424dd1f017175/usb_bridge/demo/lena320x240.png -------------------------------------------------------------------------------- /usb_bridge/demo/standby320.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikostoski/arduino-clglcd/5f8554aef9935b67f16f78a8b55424dd1f017175/usb_bridge/demo/standby320.png --------------------------------------------------------------------------------