├── 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 | 
71 | 
72 | 
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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
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
--------------------------------------------------------------------------------