1 | //--------------------------------------------------------------------
|
2 | //
|
3 | // OpenDCC
|
4 | // Throttle MFT 1.0
|
5 | //
|
6 | // Copyright (c) 2009 Wolfgang Kufer
|
7 | //
|
8 | // This source file is subject of the GNU general public license 2,
|
9 | // that is available at the world-wide-web at
|
10 | // http://www.gnu.org/licenses/gpl.txt
|
11 | //
|
12 | // This code is initially based on:
|
13 | // http://www.mikrocontroller.net/topic/75589
|
14 | // it runs as driver for Sami Varjo's gLCD lib functions
|
15 | // available from sourceforge.net
|
16 | //
|
17 | // Here we do the access to a graphic lcd display, no special functions
|
18 | // for MFT contained (so this is a general purpose file)
|
19 | //
|
20 | //--------------------------------------------------------------------
|
21 | //
|
22 | // file: lcd_driver_st7565.c
|
23 | // author: Wolfgang Kufer
|
24 | // contact: kufer@gmx.de
|
25 | // webpage: http://www.opendcc.de
|
26 | // history: 2009-03-24 V0.01 kw start
|
27 | // 2009-03-27 V0.02 kw font proportional added
|
28 | // 2009-04-04 V0.03 kw cursor control added
|
29 | // 2009-04-06 V0.04 kw cursor in housekeeping integriert, blinkend
|
30 | // 2009-07-05 V0.05 kw forced reinit of SPI
|
31 | // 2010-07-12 V0.06 kw added LCD_pixelPattern
|
32 | // 2011-07-09 V0.07 kw bugfix in update_display_page von Thomas Finn
|
33 | //
|
34 | //--------------------------------------------------------------------
|
35 | //
|
36 | // purpose: control of a spi driven graphic display
|
37 | // Controller: EA DOGM128, ST7565, 6B1713
|
38 | //
|
39 | // Interface: downstream: we use hardware SPI, all data transfer is done
|
40 | // with SPI_xmit. This must run with no other
|
41 | // SPI access in between - so in a real time os
|
42 | // you must use a mutex here.
|
43 | // Note: cortos is preemptiv - no mutex needed
|
44 | //
|
45 | // upstream: basic graphic print functions
|
46 | //
|
47 | //--------------------------------------------------------------------
|
48 | // Howto:
|
49 | // All printing and drawing goes to internal ram.
|
50 | // These routines keep track of the changed area.
|
51 | // A separate task is used to update the real display
|
52 | // from this memory (see housekeeping)
|
53 | //
|
54 | // Reason:
|
55 | // a) display is write only (when running on SPI)
|
56 | // b) display content will not flicker during repaint
|
57 | //-----------------------------------------------------------------
|
58 | // Hardware: we need SPI (Chip select: SPI_ENABLE_LCD)
|
59 | // we need to control line A0 (SPI_ADR0_LOW;SPI_ADR0_HIGH;)
|
60 | //
|
61 |
|
62 | #include <avr/io.h>
|
63 | #include <avr/interrupt.h>
|
64 | #include <avr/eeprom.h>
|
65 | #include <avr/pgmspace.h> // put var to program memory
|
66 | #include <util/delay.h>
|
67 | #include <stdio.h>
|
68 |
|
69 | #include "config.h"
|
70 | #include "cortos.h"
|
71 | #include "hardware.h" // CS for SPI
|
72 | #include "timer.h"
|
73 |
|
74 | #include "lcd_font5x7.h"
|
75 | #define FONT5x7_START 0x00 // was 0x20
|
76 | #define FONT5x7_END 0x7F
|
77 |
|
78 | #include "lcd_driver_st7565.h"
|
79 |
|
80 |
|
81 | #if (DISPLAY == LCD_EA128x64)
|
82 |
|
83 | //------------------------------------------------------------------------
|
84 | // hardware settings
|
85 | //------------------------------------------------------------------------
|
86 | #define LCD_CONTRAST_DEFAULT 22 // electronic contrast
|
87 | // default, if we don't have a eeprom
|
88 |
|
89 | #define EA_DOGM_SINGLE_SUPPLY 1 // 0: init for dual supply
|
90 | // 1: init for single supply
|
91 |
|
92 | #define EA_DOGM_TOPVIEW 0 // 0: bottom view (6:00)
|
93 | // 1: top view (12:00)
|
94 |
|
95 | #define EA_DOGM_SOFTRESET 1 // 0: hardware pin
|
96 | // 1: we do software reset
|
97 |
|
98 |
|
99 |
|
100 | //------------------------------------------------------------------------
|
101 | //
|
102 | // Global variables used by driver
|
103 | //
|
104 | //------------------------------------------------------------------------
|
105 | //
|
106 | // 0 1 2 ..................... 127
|
107 | // |---------------------------------|
|
108 | // 0|(0,0) | | 127
|
109 | // 128| y |
|
110 | // | | |
|
111 | // | v |
|
112 | // |---- x --->* |
|
113 | // | |
|
114 | // | |
|
115 | // 896| (127,63)|1023
|
116 | // |---------------------------------|
|
117 | //
|
118 | // The display is organized in 8 pages - 8 pixel high, 128 pixel wide.
|
119 | // defines see header file
|
120 | //
|
121 | // RAM data organisation: left to right; top to bottom
|
122 | // (this is more efficient for font copying)
|
123 | //
|
124 | // Pixel(x,y): index = (y/8)*128 + x
|
125 | // = ((y & ~0x07) << 4 + x
|
126 | // bit = y%8
|
127 | // = (y & 0x07)
|
128 | // Set pixel (x,y): video_ram[(y & ~0x07) * (LCD_LINE_LENGTH/LCD_PAGE_HEIGHT) + x] |= (1 << (y & 0x07));
|
129 | // Clear pixel (x,y): video_ram[(y & ~0x07) * (LCD_LINE_LENGTH/LCD_PAGE_HEIGHT) + x] &= ~(1 << (y & 0x07));
|
130 |
|
131 | #define LCD_PAGE_HEIGHT 8 //8 lines per page
|
132 | #define LCD_LINES 64
|
133 | #define LCD_LINE_LENGTH 128
|
134 |
|
135 | static uint8_t video_ram[1024];
|
136 |
|
137 | uint8_t LCD_currentX; // cursor
|
138 | uint8_t LCD_currentY;
|
139 |
|
140 | //-----------------------------------------------------------------------------
|
141 | // Mode
|
142 |
|
143 | unsigned char display_invers = 0; // this is either 0 or 0xFF
|
144 | // 00: print font bitmap as is
|
145 | // ff: print inverted
|
146 | unsigned char display_keep = 0; // this is either 0 or 0xFF
|
147 | // 00: replace video content
|
148 | // ff: keep video content
|
149 |
|
150 | //-----------------------------------------------------------------------------
|
151 | // Cursor
|
152 |
|
153 | uint8_t cursor_is_on_display; // cursor currently on display
|
154 | uint8_t CursorActive; // cursor task could be enabled
|
155 | uint8_t Cxmin, Cxmax; // Cursor is a line from Cxmin to Cxmax
|
156 | uint8_t Cy, CyPage, CyPattern; // Cursor page and pattern
|
157 |
|
158 |
|
159 | //------------------------------------------------------------------------
|
160 | //
|
161 | // raw hardware functions (only used by "disp_..."-functions)
|
162 | //
|
163 | //------------------------------------------------------------------------
|
164 | // register defines
|
165 | #define ST7565R_RESET 0xE2
|
166 | #define ST7565R_ADC_SELECT 0xA0
|
167 | #define ST7565R_COMMON_OUTPUT_MODE 0xC0
|
168 | #define ST7565R_DISPLAY_NORMAL 0xA6
|
169 | #define ST7565R_DISPLAY_INVERS 0xA7
|
170 | #define ST7565R_LCD_BIAS_SET_1_9 0xA2
|
171 | #define ST7565R_LCD_BIAS_SET_1_7 0xA3
|
172 | #define ST7565R_POWER_CONTROL_SET 0x28
|
173 | #define ST7565R_BOOSTER_MASK 0x04
|
174 | #define ST7565R_REGULATOR_MASK 0x02
|
175 | #define ST7565R_FOLLOWER_MASK 0x01
|
176 | #define ST7565R_BOOSTER_RATIO_SET 0xF8
|
177 | #define ST7565R_BOOSTER_STEP_UP_2x3x4x 0x00
|
178 | #define ST7565R_BOOSTER_STEP_UP_5x 0x01
|
179 | #define ST7565R_BOOSTER_STEP_UP_6x 0x03
|
180 | #define ST7565R_V0_VOLTAGE_REG_RATIO 0x20
|
181 | #define ST7565R_DISPLAY_ON_OFF 0xAE
|
182 | #define ST7565R_SLEEP_MODE_SET 0xAC
|
183 | #define ST7565R_ELECTRONIC_VOLUME_MODE_SET 0x81
|
184 | #define ST7565R_ELECTRONIC_VOLUME_REGISTER_SET 0x00
|
185 |
|
186 | #define ST7565R_DISPLAY_START_LINE 0x40
|
187 | #define ST7565R_SET_PAGE_ADDR 0xB0
|
188 | #define ST7565R_SET_COLUMN_NIBBLE_H 0x10
|
189 | #define ST7565R_SET_COLUMN_NIBBLE_L 0x00
|
190 |
|
191 | static void cursor_init();
|
192 |
|
193 | //-----------------------------------------------------------------------------
|
194 | // SPI access
|
195 | //-----------------------------------------------------------------------------
|
196 |
|
197 | static inline void SPI_init(void)
|
198 | {
|
199 | // Enable SPI, Master, set clock rate fck/16
|
200 | // ST7565 has fast SPI (up to 20MHz), no need to slow down SPI
|
201 | // our F_CPU is 10 MHz, so we use fcpu/2; a transfer lasts 1,6us
|
202 |
|
203 | #define SPI_PRESCALER 2
|
204 |
|
205 | #if (SPI_PRESCALER==2)
|
206 | #define SPI_PRESCALER_BITS ((0<<SPR1)|(0<<SPR0))
|
207 | #define SPI2XMUL (1 << SPI2X)
|
208 | #elif (SPI_PRESCALER==4)
|
209 | #define SPI_PRESCALER_BITS ((0<<SPR1)|(0<<SPR0))
|
210 | #define SPI2XMUL (0 << SPI2X)
|
211 | #elif (SPI_PRESCALER==8)
|
212 | #define SPI_PRESCALER_BITS ((0<<SPR1)|(1<<SPR0))
|
213 | #define SPI2XMUL (1 << SPI2X)
|
214 | #elif (SPI_PRESCALER==16)
|
215 | #define SPI_PRESCALER_BITS ((0<<SPR1)|(1<<SPR0))
|
216 | #define SPI2XMUL (0 << SPI2X)
|
217 | #elif (SPI_PRESCALER==32)
|
218 | #define SPI_PRESCALER_BITS ((1<<SPR1)|(0<<SPR0))
|
219 | #define SPI2XMUL (1 << SPI2X)
|
220 | #elif (SPI_PRESCALER==64)
|
221 | #define SPI_PRESCALER_BITS ((1<<SPR1)|(0<<SPR0))
|
222 | #define SPI2XMUL (0 << SPI2X)
|
223 | #elif (SPI_PRESCALER==128)
|
224 | #define SPI_PRESCALER_BITS ((1<<SPR1)|(1<<SPR0))
|
225 | #define SPI2XMUL (0 << SPI2X)
|
226 | #else
|
227 | #error SPI_PRESCALER is not defined or is not a supported value
|
228 | #endif
|
229 |
|
230 | SPCR = (0 << SPIE) // 0 = no interrupt
|
231 | | (1 << SPE) // 1 = enabled
|
232 | | (0 << DORD) // 1 = data order LSB first; 0 = msb first
|
233 | | (1 << MSTR) // 1 = master mode
|
234 | | (0 << CPOL) // 0 = clock low when idle
|
235 | | (0 << CPHA) // 0 = sample a leading edge
|
236 | | SPI_PRESCALER_BITS;
|
237 |
|
238 | SPSR = (SPI2XMUL);
|
239 | }
|
240 |
|
241 | static unsigned char SPI_xmit (unsigned char aData)
|
242 | {
|
243 | // sendchar(aData); // mirror to usb, debug only
|
244 |
|
245 | // here we do write only, so mormally no need to wait after xmit
|
246 | // we wait, since: we must ensure SPI_CS is kept low, otherwise
|
247 | // we would need a mutex.
|
248 |
|
249 | SPI_init(); // only two accesses - but make sure we the correct setup!
|
250 |
|
251 | SPI_ENABLE_LCD; // CS low
|
252 |
|
253 | SPDR = aData;
|
254 | while(!(SPSR & (1<<SPIF)));
|
255 |
|
256 | SPI_DISABLE_LCD;
|
257 | return (SPDR);
|
258 | }
|
259 |
|
260 | void dogm_send_command(unsigned char command)
|
261 | {
|
262 | SPI_ADR0_LOW; // clear A0-Bit -> command
|
263 | SPI_xmit(command);
|
264 | }
|
265 |
|
266 | void dogm_send_display(unsigned char data)
|
267 | {
|
268 | SPI_ADR0_HIGH; // set A0-Bit -> Display data
|
269 | SPI_xmit(data);
|
270 | }
|
271 |
|
272 | void dogm_reset()
|
273 | {
|
274 | #if (EA_DOGM_SOFTRESET == 1)
|
275 | dogm_send_command( ST7565R_RESET );
|
276 | _delay_ms(50);
|
277 | #else
|
278 | RES_PORT &= ~(1<<RES_BIT); // RES\ = LOW (Reset)
|
279 | _delay_ms(50);
|
280 | RES_PORT |= (1<<RES_BIT); // RES\ = HIGH (kein Reset)
|
281 | #endif
|
282 | }
|
283 |
|
284 |
|
285 | void LCD_contrast(uint8_t contrast)
|
286 | {
|
287 | /* "electronic volume" */
|
288 | dogm_send_command( ST7565R_ELECTRONIC_VOLUME_MODE_SET );
|
289 | dogm_send_command( ST7565R_ELECTRONIC_VOLUME_REGISTER_SET | ( contrast & 0x3F ) );
|
290 | }
|
291 |
|
292 | void dogm_sleep( uint8_t sleep )
|
293 | {
|
294 | dogm_send_command( ST7565R_SLEEP_MODE_SET );
|
295 | if (sleep) dogm_send_command(1);
|
296 | else dogm_send_command(0); // alive
|
297 | }
|
298 |
|
299 | void lcd_init(void)
|
300 | {
|
301 | uint8_t contrast;
|
302 |
|
303 | #ifdef eadr_lcd_contrast
|
304 | contrast = eeprom_read_byte((void *)eadr_lcd_contrast);
|
305 | if (contrast == 0 || contrast > 0x3F)
|
306 | // obviously void
|
307 | contrast = LCD_CONTRAST_DEFAULT;
|
308 | #else
|
309 | contrast = LCD_CONTRAST_DEFAULT;
|
310 | #endif
|
311 |
|
312 | SPI_init();
|
313 |
|
314 | //RESET Display
|
315 | dogm_reset();
|
316 |
|
317 | //Display start line
|
318 | dogm_send_command(ST7565R_DISPLAY_START_LINE | 0); // Display start line 0
|
319 |
|
320 | #if (EA_DOGM_TOPVIEW == 1)
|
321 | dogm_send_command( ST7565R_ADC_SELECT | 0); // ADC set normal
|
322 | dogm_send_command( ST7565R_COMMON_OUTPUT_MODE | 0x08 ); // Common output mode revers COM63-COM0
|
323 | #else // //Bottom view, default
|
324 | dogm_send_command( ST7565R_ADC_SELECT | 1 ); // ADC set reverse
|
325 | dogm_send_command( ST7565R_COMMON_OUTPUT_MODE | 0 ); // Common output mode normal COM0-COM63
|
326 | #endif // EA_DOGM_TOPVIEW
|
327 |
|
328 | dogm_send_command(ST7565R_DISPLAY_NORMAL); // Display normal / invers
|
329 |
|
330 | //Hardware options
|
331 | dogm_send_command(ST7565R_LCD_BIAS_SET_1_9); // Set bias 1/9 (Duty 1/65)
|
332 | #if (EA_DOGM_SINGLE_SUPPLY == 1)
|
333 | dogm_send_command(
|
334 | ST7565R_POWER_CONTROL_SET |
|
335 | ST7565R_BOOSTER_MASK | // booster on
|
336 | ST7565R_REGULATOR_MASK | // regulator on
|
337 | ST7565R_FOLLOWER_MASK ); // follower on
|
338 | dogm_send_command( ST7565R_BOOSTER_RATIO_SET ); // internal booster to 4x
|
339 | dogm_send_command( ST7565R_BOOSTER_STEP_UP_2x3x4x );
|
340 | #else // dual supply
|
341 | dogm_send_command(
|
342 | ST7565R_POWER_CONTROL_SET | // booster off, regulator and follower on
|
343 | ST7565R_REGULATOR_MASK | ST7565R_FOLLOWER_MASK );
|
344 | #endif
|
345 |
|
346 | dogm_send_command(ST7565R_V0_VOLTAGE_REG_RATIO | 0x07 ); // V0 resistor-ratio (see table 11, here 6.5 -> 0b111 = 0x07)
|
347 |
|
348 | LCD_contrast(contrast);
|
349 |
|
350 | dogm_sleep(0);
|
351 |
|
352 | dogm_send_command(ST7565R_DISPLAY_ON_OFF | 1); // Display on
|
353 |
|
354 | cursor_init();
|
355 | }
|
356 |
|
357 | void lcd_reset(void)
|
358 | {
|
359 | dogm_reset();
|
360 | }
|
361 |
|
362 |
|
363 | //------------------------------------------------------------------------------
|
364 | //
|
365 | // Housekeeping
|
366 | //
|
367 | //------------------------------------------------------------------------------
|
368 | // These routines try to update the video_ram to the lcd
|
369 | //
|
370 | volatile uint8_t update_blocked;
|
371 |
|
372 |
|
373 |
|
374 | t_update_lcd update_lcd[LCD_LINES / LCD_PAGE_HEIGHT ];
|
375 | /*
|
376 | struct
|
377 | {
|
378 | uint8_t page_modified;
|
379 | } update_lcd[LCD_LINES / LCD_PAGE_HEIGHT ];
|
380 | */
|
381 |
|
382 | void update_display_page(unsigned char page)
|
383 | {
|
384 | dogm_send_command(ST7565R_SET_PAGE_ADDR + page); // Set page address to <page>
|
385 | dogm_send_command(ST7565R_SET_COLUMN_NIBBLE_H + 0); // Set column address to 0 (4 MSBs)
|
386 | #if (EA_DOGM_TOPVIEW == 1)
|
387 | dogm_send_command(ST7565R_SET_COLUMN_NIBBLE_L + 4); // Shift column address to 4 (4 LSBs) while ST7565 is 132x65 Controller
|
388 | #else
|
389 | dogm_send_command(ST7565R_SET_COLUMN_NIBBLE_L + 0); // Set column address to 0 (4 LSBs)
|
390 | #endif
|
391 | uint8_t column;
|
392 |
|
393 | if (cursor_is_on_display && (page == CyPage)) // with cursor
|
394 | {
|
395 | for (column = 0; column < Cxmin; column++)
|
396 | {
|
397 | dogm_send_display(video_ram[page * LCD_LINE_LENGTH + (column)]);
|
398 | }
|
399 | for (column = Cxmin; column < Cxmax; column++)
|
400 | {
|
401 | dogm_send_display(video_ram[page * LCD_LINE_LENGTH + (column)] ^ CyPattern);
|
402 | }
|
403 | for (column = Cxmax; column < LCD_LINE_LENGTH; column++)
|
404 | {
|
405 | dogm_send_display(video_ram[page * LCD_LINE_LENGTH + (column)]);
|
406 | }
|
407 | }
|
408 | else // normal
|
409 | {
|
410 | for (column = 0; column < LCD_LINE_LENGTH; column++)
|
411 | {
|
412 | dogm_send_display(video_ram[page * LCD_LINE_LENGTH + (column)]);
|
413 | }
|
414 | }
|
415 | update_lcd[page].page_modified = 0;
|
416 | }
|
417 |
|
418 | // see cortos.h
|
419 | // This is the TASK_DISPLAY
|
420 | // scan through the pages and if a page is modified: do the update
|
421 | // if all pages are done: set inactive
|
422 | t_cr_task update_display(void)
|
423 | {
|
424 | if (update_blocked) return(-1); // fall asleep
|
425 |
|
426 | uint8_t page;
|
427 | for (page = 0; page < LCD_LINES / LCD_PAGE_HEIGHT; page++)
|
428 | {
|
429 | if (update_lcd[page].page_modified)
|
430 | {
|
431 | update_display_page(page);
|
432 | return(0); // ready again
|
433 | }
|
434 | }
|
435 | return(-1); // all up to date, fall asleep
|
436 | }
|
437 |
|
438 | // call this function at end of your display routines
|
439 | void enable_display_update(void)
|
440 | {
|
441 | update_blocked = 0;
|
442 | #ifdef _CORTOS_H_
|
443 | set_task_ready(TASK_DISPLAY);
|
444 | #else
|
445 | while (update_display() >= 0) {}
|
446 | #endif
|
447 |
|
448 | }
|
449 |
|
450 | // complete update, no conditions; could be used in non cortos environment
|
451 | // warning: this lasts 1,6ms
|
452 | void update_display_complete(void)
|
453 | {
|
454 | uint8_t page;
|
455 | for (page = 0; page < LCD_LINES / LCD_PAGE_HEIGHT; page++)
|
456 | {
|
457 | update_display_page(page);
|
458 | }
|
459 | }
|
460 |
|
461 |
|
462 |
|
463 | //------------------------------------------------------------------------------
|
464 | //
|
465 | // Upstream Interface
|
466 | //
|
467 | //------------------------------------------------------------------------------
|
468 | // every access to video_ram must:
|
469 | // a) block the update process
|
470 | // b) set to corresponding page to modified
|
471 | //
|
472 | // when a display sequence is complete, it must enable the update
|
473 | //
|
474 |
|
475 | static void _blockcomplete(void) // local helper functions
|
476 | {
|
477 | uint8_t page;
|
478 | for (page = 0; page < LCD_LINES / LCD_PAGE_HEIGHT; page++)
|
479 | {
|
480 | update_lcd[page].page_modified = 1;
|
481 | }
|
482 | update_blocked = 1;
|
483 | }
|
484 |
|
485 | void LCD_clr(void) //clear LCD memory
|
486 | {
|
487 | _blockcomplete();
|
488 | uint16_t i;
|
489 | for (i=0; i<sizeof(video_ram); i++)
|
490 | {
|
491 | video_ram[i] = 0;
|
492 | }
|
493 | LCD_currentX = 0;
|
494 | LCD_currentY = 0;
|
495 | cursor_is_on_display = 0;
|
496 | }
|
497 |
|
498 | void lcd_clear(void)
|
499 | {
|
500 | LCD_clr();
|
501 | }
|
502 |
|
503 | void LCD_fill_with_counter(void)
|
504 | {
|
505 | _blockcomplete();
|
506 | uint16_t i;
|
507 | for (i=0; i<sizeof(video_ram); i++)
|
508 | {
|
509 | video_ram[i] = (unsigned char) i;
|
510 | }
|
511 | }
|
512 |
|
513 | //------------------------------------------------------------------------------
|
514 | // Copy a complete screen (no checking)
|
515 | // bitmap_P points to bitmap in flash
|
516 | // The bitmap must be organized like our page.
|
517 |
|
518 | void LCD_fill_with_BMP(uint8_t* bitmap_P)
|
519 | {
|
520 | _blockcomplete();
|
521 | uint16_t i;
|
522 | for (i=0; i<sizeof(video_ram); i++)
|
523 | {
|
524 | video_ram[i] = pgm_read_byte(&bitmap_P[i]);
|
525 | }
|
526 | }
|
527 |
|
528 | //------------------------------------------------------------------------------
|
529 | //Move cursor to position x y (in pixels)
|
530 | void LCD_setCursorXY(uint8_t x, uint8_t y)
|
531 | {
|
532 | LCD_currentX = x;
|
533 | LCD_currentY = y;
|
534 | }
|
535 |
|
536 | void LCD_getCursorXY(uint8_t* x, uint8_t* y)
|
537 | {
|
538 | *x = LCD_currentX;
|
539 | *y = LCD_currentY;
|
540 | }
|
541 |
|
542 | //------------------------------------------------------------------------------
|
543 | //Write a byte to LCD. Single 8 bit segment is send to current cursor position
|
544 | //(page aligned!)
|
545 |
|
546 | void LCD_writeByte(uint8_t data)
|
547 | {
|
548 | update_blocked = 1;
|
549 |
|
550 | uint8_t page;
|
551 | page = LCD_currentY / LCD_PAGE_HEIGHT;
|
552 | update_lcd[page].page_modified = 1;
|
553 |
|
554 | video_ram[page * LCD_LINE_LENGTH + LCD_currentX] = data;
|
555 | LCD_currentX++;
|
556 |
|
557 | }
|
558 |
|
559 | //------------------------------------------------------------------------------
|
560 | // Pixel at x y
|
561 |
|
562 | void LCD_pixelOn(uint8_t x, uint8_t y)
|
563 | {
|
564 | update_blocked = 1;
|
565 | uint8_t page;
|
566 | page = y / LCD_PAGE_HEIGHT;
|
567 | update_lcd[page].page_modified = 1;
|
568 |
|
569 | video_ram[page * LCD_LINE_LENGTH + x] |= (1 << (y & 0x07));
|
570 | }
|
571 |
|
572 | void LCD_pixelOff(uint8_t x, uint8_t y)
|
573 | {
|
574 | update_blocked = 1;
|
575 | uint8_t page;
|
576 | page = y / LCD_PAGE_HEIGHT;
|
577 | update_lcd[page].page_modified = 1;
|
578 |
|
579 | video_ram[page * LCD_LINE_LENGTH + x] &= ~(1 << (y & 0x07));
|
580 | }
|
581 |
|
582 |
|
583 | t_LCD_linePixel LCD_linePixel =
|
584 | {
|
585 | 1, // Mode
|
586 | 1, // OffAct
|
587 | 1, // OffCount
|
588 | 1, // OnAct
|
589 | 1, // OnCount
|
590 | };
|
591 |
|
592 | void LCD_pixelPattern(uint8_t x, uint8_t y)
|
593 | {
|
594 | if (LCD_linePixel.Mode)
|
595 | {
|
596 | LCD_linePixel.OnAct++;
|
597 | if (LCD_linePixel.OnAct >= LCD_linePixel.OnCount)
|
598 | {
|
599 | LCD_linePixel.OffAct = 0;
|
600 | LCD_linePixel.Mode = 0;
|
601 | }
|
602 | LCD_pixelOn(x, y);
|
603 | }
|
604 | else
|
605 | {
|
606 | LCD_linePixel.OffAct++;
|
607 | if (LCD_linePixel.OffAct >= LCD_linePixel.OffCount)
|
608 | {
|
609 | LCD_linePixel.OnAct = 0;
|
610 | LCD_linePixel.Mode = 1;
|
611 | }
|
612 | LCD_pixelOff(x, y);
|
613 | }
|
614 | }
|
615 |
|
616 | //------------------------------------------------------------------------------
|
617 | //Invert pixel value at x y
|
618 | void LCD_invertPixel(uint8_t x, uint8_t y)
|
619 | {
|
620 | update_blocked = 1;
|
621 | uint8_t mask;
|
622 |
|
623 | uint8_t page;
|
624 | page = y / LCD_PAGE_HEIGHT;
|
625 | update_lcd[page].page_modified = 1;
|
626 |
|
627 |
|
628 | mask = video_ram[page * LCD_LINE_LENGTH + x];
|
629 |
|
630 | if (mask & (1<<(y&0b111))) mask &= ~(1<<(y&0b111));
|
631 | else mask |= (1<<(y&0b111));
|
632 |
|
633 | video_ram[page * LCD_LINE_LENGTH + x] = mask;
|
634 | }
|
635 |
|
636 | //------------------------------------------------------------------------------
|
637 | //Turn pixels on in a single page in LCD DDRAM from x1 to x2
|
638 | void LCD_onPage(uint8_t page, uint8_t x1, uint8_t x2)
|
639 | {
|
640 | update_blocked = 1;
|
641 | update_lcd[page].page_modified = 1;
|
642 | uint8_t i;
|
643 |
|
644 | for (i=x1; i<=x2; i++)
|
645 | {
|
646 | video_ram[page * LCD_LINE_LENGTH + i] = 0xff;
|
647 | }
|
648 | }
|
649 |
|
650 | //------------------------------------------------------------------------------
|
651 | //invert pixels on a page from x1 to x2
|
652 | // x1 must be smaller than x2
|
653 | void LCD_invertPage(uint8_t page, uint8_t x1, uint8_t x2)
|
654 | {
|
655 | update_blocked = 1;
|
656 | uint8_t mask,i;
|
657 | update_lcd[page].page_modified = 1;
|
658 | for (i=x1; i<=x2; i++)
|
659 | {
|
660 | mask = video_ram[page * LCD_LINE_LENGTH + i];
|
661 | video_ram[page * LCD_LINE_LENGTH + i] = ~mask;
|
662 | }
|
663 | }
|
664 |
|
665 | //------------------------------------------------------------------------------
|
666 | //clear a single page in LCD DDRAM from x1 to x2
|
667 | void LCD_offPage(uint8_t page, uint8_t x1, uint8_t x2)
|
668 | {
|
669 | update_blocked = 1;
|
670 | uint8_t i;
|
671 | update_lcd[page].page_modified = 1;
|
672 | for (i=x1; i<=x2; i++)
|
673 | {
|
674 | video_ram[page * LCD_LINE_LENGTH + i] = 0x00;
|
675 | }
|
676 | }
|
677 |
|
678 | //------------------------------------------------------------------------------
|
679 | // Copy a 8 bit high bmp to page
|
680 | // bitmap_P points to bitmap in flash, page correct
|
681 | void LCD_fillPage_with_BMP(uint8_t width, PGM_VOID_P bitmap_P)
|
682 | {
|
683 | update_blocked = 1;
|
684 |
|
685 | uint8_t page;
|
686 | uint8_t upper_bound;
|
687 | page = LCD_currentY / LCD_PAGE_HEIGHT;
|
688 | update_lcd[page].page_modified = 1;
|
689 |
|
690 | if ((LCD_currentX + width) > LCD_LINE_LENGTH)
|
691 | upper_bound=LCD_LINE_LENGTH-1;
|
692 | else
|
693 | upper_bound = LCD_currentX + width;
|
694 |
|
695 | for ( ; LCD_currentX < upper_bound; LCD_currentX++)
|
696 | {
|
697 | video_ram[page * LCD_LINE_LENGTH + LCD_currentX] = pgm_read_byte(bitmap_P++);
|
698 | }
|
699 | }
|
700 |
|
701 |
|
702 | //-----------------------------------------------------------------------------
|
703 | // put a graphical bitmap with width and height (given in pages)
|
704 | // at LCD_currentX,LCD_currentY
|
705 | // y must be page-aligned, input bitmap is page aligned
|
706 | //
|
707 | void LCD_drawBMP_aligned(PGM_VOID_P bitmap_P, uint8_t width, uint8_t height)
|
708 | {
|
709 | uint8_t saved_x,j;
|
710 | PGM_P my_bitmap = bitmap_P;
|
711 | //whole pages
|
712 | saved_x = LCD_currentX;
|
713 | for (j=0; j < height; j++)
|
714 | {
|
715 | LCD_fillPage_with_BMP(width, &my_bitmap[j*width]);
|
716 | LCD_currentY += LCD_PAGE_HEIGHT;
|
717 | if(LCD_currentY>=LCD_LINES) LCD_currentY=0;
|
718 | LCD_currentX = saved_x;
|
719 | }
|
720 | }
|
721 |
|
722 |
|
723 | //-----------------------------------------------------------------------------
|
724 | // put a graphical bitmap with width and height (given in first two locations of bitmap_P)
|
725 | // at LCD_currentX,LCD_currentY
|
726 | // input bitmap is page aligned
|
727 | // parameters: display_keep, display_invers
|
728 | //
|
729 | void LCD_drawBMP(PGM_VOID_P bitmap_P)
|
730 | {
|
731 | uint8_t page, pageoffset;
|
732 | uint8_t copy_width;
|
733 | uint8_t pixel_mask, and_mask, and_maskL, and_maskH;
|
734 | uint8_t j;
|
735 |
|
736 | PGM_P z_ptr = bitmap_P;
|
737 |
|
738 | update_blocked = 1;
|
739 |
|
740 | uint8_t width = pgm_read_byte(z_ptr++);
|
741 | int8_t height = pgm_read_byte(z_ptr++);
|
742 |
|
743 | // bound check
|
744 | if ((LCD_currentX + width) > LCD_LINE_LENGTH) copy_width = LCD_LINE_LENGTH-LCD_currentX;
|
745 | else copy_width = width;
|
746 |
|
747 | if (copy_width == 0) return;
|
748 |
|
749 | if ((LCD_currentY + height) > LCD_LINES) height = LCD_LINES-LCD_currentY;
|
750 |
|
751 | page = LCD_currentY / LCD_PAGE_HEIGHT;
|
752 | pageoffset = LCD_currentY & (LCD_PAGE_HEIGHT-1);
|
753 |
|
754 | and_maskL = 1 << pageoffset;
|
755 | and_maskL -= 1; // aus pageoffset 3 wird dadurch 0b00000111
|
756 |
|
757 |
|
758 | while (height > 0)
|
759 | {
|
760 | update_lcd[page].page_modified = 1;
|
761 | // copy one page of char set to video_ram
|
762 | if ((pageoffset + height) < 8)
|
763 | {
|
764 | // mask on both sides required
|
765 |
|
766 | and_maskH = 1 << (pageoffset + height); // z.B. 3 + 4
|
767 | and_maskH -= 1;
|
768 | and_maskH = ~and_maskH; // z.B. 0b10000000
|
769 |
|
770 | pixel_mask = ~(and_maskL | and_maskH); // z.B. 0b01111000
|
771 | and_mask = and_maskL | and_maskH | display_keep;
|
772 |
|
773 | for (j=0; j<copy_width; j++)
|
774 | {
|
775 | uint8_t temp;
|
776 | uint8_t pixels;
|
777 | pixels = pgm_read_byte(z_ptr+j) ^ display_invers;
|
778 | pixels = pixels << pageoffset;
|
779 | pixels &= pixel_mask;
|
780 |
|
781 | temp = video_ram[page * LCD_LINE_LENGTH + LCD_currentX + j];
|
782 | temp &= and_mask;
|
783 | temp ^= pixels;
|
784 | video_ram[page * LCD_LINE_LENGTH + LCD_currentX + j] = temp;
|
785 | }
|
786 |
|
787 |
|
788 | }
|
789 | else if ((pageoffset + height) == 8)
|
790 | {
|
791 | // mask only on lower bound required
|
792 |
|
793 | and_mask = and_maskL | display_keep;
|
794 |
|
795 | for (j=0; j<copy_width; j++)
|
796 | {
|
797 | uint8_t temp;
|
798 | uint8_t pixels;
|
799 | pixels = pgm_read_byte(z_ptr+j) ^ display_invers;
|
800 | pixels = pixels << pageoffset;
|
801 | temp = video_ram[page * LCD_LINE_LENGTH + LCD_currentX + j];
|
802 | temp &= and_mask;
|
803 | temp ^= pixels;
|
804 | video_ram[page * LCD_LINE_LENGTH + LCD_currentX + j] = temp;
|
805 | }
|
806 | }
|
807 | else
|
808 | {
|
809 | // two pages affected
|
810 |
|
811 | and_mask = and_maskL | display_keep;
|
812 |
|
813 | for (j=0; j<copy_width; j++)
|
814 | {
|
815 | uint8_t temp;
|
816 | uint8_t pixels;
|
817 | pixels = pgm_read_byte(z_ptr+j) ^ display_invers;
|
818 | pixels = pixels << pageoffset; //es werden nullen nachgezogen
|
819 | temp = video_ram[page * LCD_LINE_LENGTH + LCD_currentX + j];
|
820 | temp &= and_mask;
|
821 | temp ^= pixels; // einkopieren
|
822 | video_ram[page * LCD_LINE_LENGTH + LCD_currentX + j] = temp;
|
823 | }
|
824 |
|
825 | // now next page (look at upper bound)
|
826 | page++;
|
827 | update_lcd[page].page_modified = 1;
|
828 |
|
829 | if (height < 8) and_maskH = 1 << (pageoffset + height - 8);
|
830 | else and_maskH = 1 << (pageoffset);
|
831 | and_maskH -= 1;
|
832 | pixel_mask = and_maskH;
|
833 | and_mask = ~and_maskH | display_keep;
|
834 |
|
835 | for (j=0; j<copy_width; j++)
|
836 | {
|
837 | uint8_t temp;
|
838 | uint8_t pixels;
|
839 | pixels = pgm_read_byte(z_ptr+j) ^ display_invers;
|
840 | pixels = pixels >> (8-pageoffset);
|
841 | pixels &= pixel_mask;
|
842 | temp = video_ram[page * LCD_LINE_LENGTH + LCD_currentX + j];
|
843 | temp &= and_mask;
|
844 | temp ^= pixels;
|
845 | video_ram[page * LCD_LINE_LENGTH + LCD_currentX + j] = temp;
|
846 | }
|
847 | page--;
|
848 |
|
849 | }
|
850 | z_ptr += width;
|
851 | page++;
|
852 | height -= 8;
|
853 | }
|
854 | LCD_currentX = LCD_currentX + copy_width;
|
855 | }
|
856 |
|
857 |
|
858 | //------------------------------------------------------------------------------
|
859 | // Put a single char to LCD on line at current cursor position
|
860 | // Note that page is used not exact coordinates
|
861 | // Default font is used, no checking of illegal chars
|
862 | void LCD_putchar(uint8_t c)
|
863 | {
|
864 | update_blocked = 1;
|
865 |
|
866 | uint8_t i = 0;
|
867 | for(i=0; i<Font5x7_WIDTH; i++)
|
868 | {
|
869 | if (LCD_currentX >= LCD_LINE_LENGTH)
|
870 | {
|
871 | if (LCD_currentY < LCD_LINES+LCD_PAGE_HEIGHT)
|
872 | {
|
873 | LCD_setCursorXY(0,LCD_currentY+LCD_PAGE_HEIGHT);
|
874 | }
|
875 | else
|
876 | {
|
877 | LCD_setCursorXY(0,0);
|
878 | }
|
879 | }
|
880 | LCD_writeByte(pgm_read_byte(&Font5x7[(c - FONT5x7_START)*Font5x7_WIDTH + i]));
|
881 | }
|
882 | LCD_writeByte(0x00);
|
883 | }
|
884 |
|
885 | //------------------------------------------------------------------------------
|
886 | // Put a single char to LCD on line at current cursor position
|
887 | // Note that page is used not exact coordinates
|
888 | // Proportional font is used, no checking of illegal chars
|
889 | // IgnP (=Ignore Pattern) is a pixel pattern (typ. 0xAA) which determines the end of pixel map
|
890 | //
|
891 |
|
892 | #define Font5x7prop_WIDTH 5
|
893 | #define Font5x7prop_HEIGHT 7
|
894 | void LCD_PF_putchar(uint8_t c)
|
895 | {
|
896 | update_blocked = 1;
|
897 |
|
898 | uint8_t i = 0;
|
899 | uint8_t pixels;
|
900 | for(i=0; i<Font5x7prop_WIDTH; i++)
|
901 | {
|
902 | if (LCD_currentX >= LCD_LINE_LENGTH)
|
903 | {
|
904 | if (LCD_currentY < LCD_LINES+LCD_PAGE_HEIGHT)
|
905 | {
|
906 | LCD_setCursorXY(0,LCD_currentY+LCD_PAGE_HEIGHT);
|
907 | }
|
908 | else
|
909 | {
|
910 | LCD_setCursorXY(0,0);
|
911 | }
|
912 | }
|
913 | pixels = pgm_read_byte(&Font5x7prop[(c - FONT5x7_START)*Font5x7prop_WIDTH + i]);
|
914 | if (pixels != IgnP) LCD_writeByte(pixels);
|
915 | }
|
916 | LCD_writeByte(0x00);
|
917 | }
|
918 |
|
919 | unsigned char LCD_PF_get_sizeX(uint8_t c)
|
920 | {
|
921 | uint8_t i = 0;
|
922 | uint8_t pixels;
|
923 | for(i=0; i<Font5x7prop_WIDTH; i++)
|
924 | {
|
925 | pixels = pgm_read_byte(&Font5x7prop[(c - FONT5x7_START)*Font5x7prop_WIDTH + i]);
|
926 | if (pixels == IgnP) return(i);
|
927 | }
|
928 | return(i);
|
929 | }
|
930 |
|
931 |
|
932 | //---------------------------------------------------------------------------------
|
933 | // Put a string on display - auto for next line is default (from LCD_putchar)
|
934 | // null terminated strings are expected
|
935 | // /n for new line
|
936 | void LCD_puts(uint8_t* pString)
|
937 | {
|
938 | update_blocked = 1;
|
939 |
|
940 | uint8_t i=0;
|
941 | while (pString[i] != '\0')
|
942 | {
|
943 | if (pString[i]=='\n')
|
944 | {
|
945 | LCD_currentX=0;
|
946 | LCD_currentY+=LCD_PAGE_HEIGHT;
|
947 | }
|
948 | else
|
949 | {
|
950 | LCD_putchar(pString[i]);
|
951 | }
|
952 | i++;
|
953 | }
|
954 | }
|
955 |
|
956 | //-----------------------------------------------------------------------------
|
957 | //Put a string on selected page (line) starting at x
|
958 | //
|
959 | void LCD_putsp(uint8_t* pString, uint8_t page, uint8_t x)
|
960 | {
|
961 | LCD_currentX = x;
|
962 | LCD_currentY = page * LCD_PAGE_HEIGHT;
|
963 | LCD_puts(pString);
|
964 | }
|
965 |
|
966 |
|
967 |
|
968 |
|
969 |
|
970 | //------------------------------------------------------------------------------
|
971 | // printf - fixed font 4x6, only for numbers
|
972 |
|
973 |
|
974 | #include "bmp/number_6px_glcd.h"
|
975 |
|
976 | void LCD_number_6px(char* data)
|
977 | {
|
978 | char val;
|
979 | uint8_t i = 0;
|
980 | while( (val = *data++ ) )
|
981 | {
|
982 | if (val == ' ')
|
983 | {
|
984 | for(i=0; i < NUMBER_6PX_GLCD_WIDTH; i++) LCD_writeByte(0x00);
|
985 | }
|
986 | else if ( val >= '0' && val <= '9' )
|
987 | {
|
988 | for(i=0; i < NUMBER_6PX_GLCD_WIDTH; i++)
|
989 | LCD_writeByte(pgm_read_byte(&number_6px_glcd_bmp[(val-'0')*NUMBER_6PX_GLCD_WIDTH + i]));
|
990 | }
|
991 | LCD_writeByte(0x00);
|
992 | }
|
993 | }
|
994 |
|
995 |
|
996 | //-----------------------------------------------------------------------------
|
997 | // Cursor
|
998 |
|
999 |
|
1000 | void cursor_init(void)
|
1001 | {
|
1002 | cursor_is_on_display = 0;
|
1003 | CursorActive = 0;
|
1004 | }
|
1005 |
|
1006 | void cursor_toggle(void)
|
1007 | {
|
1008 | cursor_is_on_display ^= 1;
|
1009 | }
|
1010 |
|
1011 | void cursor_off(void)
|
1012 | {
|
1013 | if (cursor_is_on_display)
|
1014 | {
|
1015 | cursor_is_on_display = 0;
|
1016 | update_lcd[CyPage].page_modified = 1;
|
1017 | }
|
1018 | // CursorActive = 0;
|
1019 | set_task_blocked(TASK_CURSOR);
|
1020 | }
|
1021 |
|
1022 | void cursor_on_at(uint8_t x, uint8_t y, uint8_t size)
|
1023 | {
|
1024 | if (cursor_is_on_display)
|
1025 | {
|
1026 | update_lcd[CyPage].page_modified = 1; // force update at old location
|
1027 | }
|
1028 | Cxmin = 6 * x;
|
1029 | Cxmax = 6 * (x + size);
|
1030 | CyPage = y;
|
1031 | CyPattern = 0x80;
|
1032 |
|
1033 | cursor_is_on_display = 1;
|
1034 | update_lcd[CyPage].page_modified = 1; // update at new location
|
1035 |
|
1036 | // CursorActive = 1;
|
1037 | set_task_ready(TASK_CURSOR);
|
1038 | }
|
1039 |
|
1040 |
|
1041 |
|
1042 | #define CURSOR_PERIOD 600000L
|
1043 |
|
1044 | t_cr_task cursor(void)
|
1045 | {
|
1046 | if (cursor_is_on_display)
|
1047 | {
|
1048 | cursor_is_on_display = 0;
|
1049 | update_lcd[CyPage].page_modified = 1;
|
1050 | }
|
1051 | else
|
1052 | {
|
1053 | cursor_is_on_display = 1;
|
1054 | update_lcd[CyPage].page_modified = 1;
|
1055 | }
|
1056 | enable_display_update();
|
1057 | return(CURSOR_PERIOD / SYSTICK_PERIOD);
|
1058 | }
|
1059 |
|
1060 |
|
1061 | #endif // (DISPLAY == LCD_EA128x64)
|