Merge pull request #10558 from iNavFlight/MrD_Correct-comments-on-OSD-symbols
[inav.git] / src / main / drivers / max7456.c
blob22f8d9cdf4d99078cb36e35c14965f3aeeeeb07f
1 /*
2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
18 #include <limits.h>
19 #include <stdbool.h>
20 #include <stdint.h>
21 #include <stdio.h>
22 #include <string.h>
24 #include "platform.h"
26 #ifdef USE_MAX7456
28 #if defined(MAX7456_USE_BOUNDS_CHECKS)
29 #define BOUNDS_CHECK_FAILED() __asm("BKPT #0")
30 #else
31 #define BOUNDS_CHECK_FAILED() do {} while(0)
32 #endif
34 #include "build/debug.h"
36 #include "common/bitarray.h"
37 #include "common/printf.h"
38 #include "common/utils.h"
40 #include "drivers/bus.h"
41 #include "drivers/dma.h"
42 #include "drivers/io.h"
43 #include "drivers/light_led.h"
44 #include "drivers/nvic.h"
45 #include "drivers/time.h"
47 #include "max7456.h"
49 // VM0 bits
50 #define VIDEO_BUFFER_DISABLE 0x01
51 #define MAX7456_RESET 0x02
52 #define VERTICAL_SYNC_NEXT_VSYNC 0x04
53 #define OSD_ENABLE 0x08
55 #define SYNC_MODE_AUTO 0x00
56 #define SYNC_MODE_INTERNAL 0x30
57 #define SYNC_MODE_EXTERNAL 0x20
59 #define VIDEO_MODE_PAL 0x40
60 #define VIDEO_MODE_NTSC 0x00
61 #define VIDEO_MODE_MASK 0x40
62 #define VIDEO_MODE_IS_PAL(val) (((val) & VIDEO_MODE_MASK) == VIDEO_MODE_PAL)
63 #define VIDEO_MODE_IS_NTSC(val) (((val) & VIDEO_MODE_MASK) == VIDEO_MODE_NTSC)
65 #define VIDEO_SIGNAL_DEBOUNCE_MS 100 // Time to wait for input to stabilize
67 // VM1 bits
69 // duty cycle is on_off
70 #define BLINK_DUTY_CYCLE_50_50 0x00
71 #define BLINK_DUTY_CYCLE_33_66 0x01
72 #define BLINK_DUTY_CYCLE_25_75 0x02
73 #define BLINK_DUTY_CYCLE_75_25 0x03
75 // blinking time
76 #define BLINK_TIME_0 0x00
77 #define BLINK_TIME_1 0x04
78 #define BLINK_TIME_2 0x08
79 #define BLINK_TIME_3 0x0C
81 // background mode brightness (percent)
82 #define BACKGROUND_BRIGHTNESS_0 (0x00 << 4)
83 #define BACKGROUND_BRIGHTNESS_7 (0x01 << 4)
84 #define BACKGROUND_BRIGHTNESS_14 (0x02 << 4)
85 #define BACKGROUND_BRIGHTNESS_21 (0x03 << 4)
86 #define BACKGROUND_BRIGHTNESS_28 (0x04 << 4)
87 #define BACKGROUND_BRIGHTNESS_35 (0x05 << 4)
88 #define BACKGROUND_BRIGHTNESS_42 (0x06 << 4)
89 #define BACKGROUND_BRIGHTNESS_49 (0x07 << 4)
91 #define BACKGROUND_MODE_GRAY 0x80
93 // STAT register bits
95 #define STAT_PAL 0x01
96 #define STAT_NTSC 0x02
97 #define STAT_LOS 0x04
98 #define STAT_NVR_BUSY 0x20
100 #define STAT_IS_PAL(val) ((val) & STAT_PAL)
101 #define STAT_IS_NTSC(val) ((val) & STAT_NTSC)
102 #define STAT_IS_LOS(val) ((val) & STAT_LOS)
104 #define VIN_IS_PAL(val) (!STAT_IS_LOS(val) && STAT_IS_PAL(val))
105 #define VIN_IS_NTSC(val) (!STAT_IS_LOS(val) && STAT_IS_NTSC(val))
107 // Kluege warning!
108 // There are occasions that NTSC is not detected even with !LOS (AB7456 specific?)
109 // When this happens, lower 3 bits of STAT register is read as zero.
110 // To cope with this case, this macro defines !LOS && !PAL as NTSC.
111 // Should be compatible with MAX7456 and non-problematic case.
113 #define VIN_IS_NTSC_alt(val) (!STAT_IS_LOS(val) && !STAT_IS_PAL(val))
115 #define MAX7456_SIGNAL_CHECK_INTERVAL_MS 1000 // msec
117 // DMM special bits
118 #define DMM_8BIT_MODE (1 << 6)
119 #define DMM_BLINK (1 << 4)
120 #define DMM_INVERT_PIXEL_COLOR (1 << 3)
121 #define DMM_CLEAR_DISPLAY (1 << 2)
122 #define DMM_CLEAR_DISPLAY_VERT (DMM_CLEAR_DISPLAY | 1 << 1)
123 #define DMM_AUTOINCREMENT (1 << 0)
125 #define DMM_IS_8BIT_MODE(val) (val & DMM_8BIT_MODE)
126 #define DMM_CHAR_MODE_MASK (MAX7456_MODE_INVERT | MAX7456_MODE_BLINK | MAX7456_MODE_SOLID_BG)
128 #define DMAH_8_BIT_DMDI_IS_CHAR_ATTR (1 << 1)
130 // Special address for terminating incremental write
131 #define END_STRING 0xff
133 #define MAX7456ADD_READ 0x80
134 #define MAX7456ADD_VM0 0x00 //0b0011100// 00 // 00 ,0011100
135 #define MAX7456ADD_VM1 0x01
136 #define MAX7456ADD_HOS 0x02
137 #define MAX7456ADD_VOS 0x03
138 #define MAX7456ADD_DMM 0x04
139 #define MAX7456ADD_DMAH 0x05
140 #define MAX7456ADD_DMAL 0x06
141 #define MAX7456ADD_DMDI 0x07
142 #define MAX7456ADD_CMM 0x08
143 #define MAX7456ADD_CMAH 0x09
144 #define MAX7456ADD_CMAL 0x0a
145 #define MAX7456ADD_CMDI 0x0b
146 #define MAX7456ADD_OSDM 0x0c
147 #define MAX7456ADD_RB0 0x10
148 #define MAX7456ADD_RB1 0x11
149 #define MAX7456ADD_RB2 0x12
150 #define MAX7456ADD_RB3 0x13
151 #define MAX7456ADD_RB4 0x14
152 #define MAX7456ADD_RB5 0x15
153 #define MAX7456ADD_RB6 0x16
154 #define MAX7456ADD_RB7 0x17
155 #define MAX7456ADD_RB8 0x18
156 #define MAX7456ADD_RB9 0x19
157 #define MAX7456ADD_RB10 0x1a
158 #define MAX7456ADD_RB11 0x1b
159 #define MAX7456ADD_RB12 0x1c
160 #define MAX7456ADD_RB13 0x1d
161 #define MAX7456ADD_RB14 0x1e
162 #define MAX7456ADD_RB15 0x1f
163 #define MAX7456ADD_OSDBL 0x6c
164 #define MAX7456ADD_STAT 0xA0
165 #define MAX7456ADD_CMDO 0xC0
167 #define NVM_RAM_SIZE 54
168 #define READ_NVR 0x50
169 #define WRITE_NVR 0xA0
171 // Maximum time to wait for video sync. After this we
172 // default to PAL
173 #define MAX_SYNC_WAIT_MS 1500
175 // Maximum time to wait for a software reset to complete
176 // Under normal conditions, it should take 20us
177 #define MAX_RESET_TIMEOUT_MS 50
179 #define MAKE_CHAR_MODE_U8(c, m) ((((uint16_t)c) << 8) | m)
180 #define MAKE_CHAR_MODE(c, m) (MAKE_CHAR_MODE_U8(c, m) | (c > 255 ? CHAR_MODE_EXT : 0))
181 #define CHAR_BLANK MAKE_CHAR_MODE(0x20, 0)
182 #define CHAR_BYTE(x) (x >> 8)
183 #define MODE_BYTE(x) (x & 0xFF)
184 #define CHAR_IS_BLANK(x) ((CHAR_BYTE(x) == 0x20 || CHAR_BYTE(x) == 0x00) && !CHAR_MODE_IS_EXT(MODE_BYTE(x)))
185 #define CHAR_MODE_EXT (1 << 2)
186 #define CHAR_MODE_IS_EXT(m) ((m) & CHAR_MODE_EXT)
188 // we write everything in osdCharacterGridBuffer and set a dirty bit
189 // in screenIsDirty to update only changed chars. This solution
190 // is faster than redrawing the whole screen on each frame.
191 static BITARRAY_DECLARE(screenIsDirty, MAX7456_BUFFER_CHARS_PAL);
193 //max chars to update in one idle
194 #define MAX_CHARS2UPDATE 10
195 #define BYTES_PER_CHAR2UPDATE (7 * 2) // SPI regs + values for them
197 typedef struct max7456Registers_s {
198 uint8_t vm0;
199 uint8_t dmm;
200 } max7456Registers_t;
202 typedef struct max7456State_s {
203 busDevice_t *dev;
204 videoSystem_e videoSystem;
205 bool isInitialized;
206 bool mutex;
207 max7456Registers_t registers;
208 } max7456State_t;
210 static max7456State_t state;
212 static bool max7456ReadVM0(uint8_t *vm0)
214 return busRead(state.dev, MAX7456ADD_VM0 | MAX7456ADD_READ, vm0);
217 static bool max7456IsBusy(void)
219 uint8_t status;
221 if (busRead(state.dev, MAX7456ADD_STAT, &status)) {
222 return status & STAT_NVR_BUSY;
224 // Read failed or busy
225 return true;
228 // Wait until max7456IsBusy() returns false, toggling a LED on each iteration
229 static void max7456WaitUntilNoBusy(void)
231 while (1) {
232 if (!max7456IsBusy()) {
233 break;
235 #ifdef LED0_TOGGLE
236 LED0_TOGGLE;
237 #else
238 LED1_TOGGLE;
239 #endif
243 // Sets wether the OSD drawing is enabled. Returns true iff
244 // changes were succesfully performed.
245 static bool max7456OSDSetEnabled(bool enabled)
247 if (enabled && !(state.registers.vm0 | OSD_ENABLE)) {
248 state.registers.vm0 |= OSD_ENABLE;
249 } else if (!enabled && (state.registers.vm0 | OSD_ENABLE)) {
250 state.registers.vm0 &= ~OSD_ENABLE;
251 } else {
252 // No changes needed
253 return false;
255 return busWrite(state.dev, MAX7456ADD_VM0, state.registers.vm0);
258 static bool max7456OSDIsEnabled(void)
260 return state.registers.vm0 & OSD_ENABLE;
263 static void max7456Lock(void)
265 while(state.mutex);
267 state.mutex = true;
270 static void max7456Unlock(void)
272 state.mutex = false;
275 static bool max7456TryLock(void)
277 if (!state.mutex) {
278 state.mutex = true;
279 return true;
281 return false;
284 static int max7456PrepareBuffer(uint8_t * buf, size_t bufsize, int bufPtr, uint8_t add, uint8_t data)
286 if ((size_t)bufPtr + 2 > bufsize) {
287 BOUNDS_CHECK_FAILED();
288 // Force a crash ASAP
289 return INT_MAX;
291 buf[bufPtr++] = add;
292 buf[bufPtr++] = data;
293 return bufPtr;
296 uint16_t max7456GetScreenSize(void)
298 // Default to PAL while the display is not yet initialized. This
299 // was the initial behavior and not all callers might be able to
300 // deal with a zero returned from here.
301 // TODO: Inspect all callers, make sure they can handle zero and
302 // change this function to return zero before initialization.
303 if (state.isInitialized && ((state.registers.vm0 & VIDEO_MODE_PAL) == 0)) {
304 return MAX7456_BUFFER_CHARS_NTSC;
306 return MAX7456_BUFFER_CHARS_PAL;
309 uint8_t max7456GetRowsCount(void)
311 if (!state.isInitialized) {
312 // Not initialized yet
313 return 0;
315 if (state.registers.vm0 & VIDEO_MODE_PAL) {
316 return MAX7456_LINES_PAL;
319 return MAX7456_LINES_NTSC;
322 //because MAX7456 need some time to detect video system etc. we need to wait for a while to initialize it at startup
323 //and in case of restart we need to reinitialize chip. Note that we can't touch osdCharacterGridBuffer here, since
324 //it might already have some data by the first time this function is called.
325 static void max7456ReInit(void)
327 uint8_t buf[2 * 2];
328 int bufPtr = 0;
329 uint8_t statVal;
332 // Check if device is available
333 if (state.dev == NULL) {
334 return;
337 uint8_t vm0Mode;
339 switch (state.videoSystem) {
340 case PAL:
341 vm0Mode = VIDEO_MODE_PAL;
342 break;
343 case NTSC:
344 vm0Mode = VIDEO_MODE_NTSC;
345 break;
346 default:
347 busRead(state.dev, MAX7456ADD_STAT, &statVal);
348 if (VIN_IS_PAL(statVal)) {
349 vm0Mode = VIDEO_MODE_PAL;
350 } else if (VIN_IS_NTSC_alt(statVal)) {
351 vm0Mode = VIDEO_MODE_NTSC;
352 } else if ( millis() > MAX_SYNC_WAIT_MS) {
353 // Detection timed out, default to PAL
354 vm0Mode = VIDEO_MODE_PAL;
355 } else {
356 // No signal detected yet, wait for detection timeout
357 return;
361 state.registers.vm0 = vm0Mode | OSD_ENABLE;
363 // Enable OSD drawing and clear the display
364 bufPtr = max7456PrepareBuffer(buf, sizeof(buf), bufPtr, MAX7456ADD_VM0, state.registers.vm0);
365 bufPtr = max7456PrepareBuffer(buf, sizeof(buf), bufPtr, MAX7456ADD_DMM, DMM_CLEAR_DISPLAY);
367 // Transfer data to SPI
368 busTransfer(state.dev, NULL, buf, bufPtr);
370 // force redrawing all screen
371 BITARRAY_SET_ALL(screenIsDirty);
372 if (!state.isInitialized) {
373 max7456RefreshAll();
374 state.isInitialized = true;
378 //here we init only CS and try to init MAX for first time
379 void max7456Init(const videoSystem_e videoSystem)
381 uint8_t buf[(MAX7456_LINES_PAL + 1) * 2];
382 int bufPtr;
383 state.dev = busDeviceInit(BUSTYPE_SPI, DEVHW_MAX7456, 0, OWNER_OSD);
385 if (state.dev == NULL) {
386 return;
389 busSetSpeed(state.dev, BUS_SPEED_STANDARD);
391 // force soft reset on Max7456
392 busWrite(state.dev, MAX7456ADD_VM0, MAX7456_RESET);
394 // DMM defaults to all zeroes on reset
395 state.registers.dmm = 0;
396 state.videoSystem = videoSystem;
398 // Set screen buffer to all blanks
399 for (uint_fast16_t ii = 0; ii < ARRAYLEN(osdCharacterGridBuffer); ii++) {
400 osdCharacterGridBuffer[ii] = CHAR_BLANK;
403 // Wait for software reset to finish
404 timeMs_t timeout = millis() + MAX_RESET_TIMEOUT_MS;
405 while(max7456ReadVM0(&state.registers.vm0) &&
406 (state.registers.vm0 | MAX7456_RESET) &&
407 millis() < timeout);
409 // Set all rows to same charactor black/white level. We
410 // can do this without finding wether the display is PAL
411 // NTSC because all the brightness registers can be written
412 // regardless of the video mode.
413 bufPtr = 0;
414 for (int ii = 0; ii < MAX7456_LINES_PAL; ii++) {
415 bufPtr = max7456PrepareBuffer(buf, sizeof(buf), bufPtr, MAX7456ADD_RB0 + ii, MAX7456_BWBRIGHTNESS);
418 // Set the blink duty cycle
419 bufPtr = max7456PrepareBuffer(buf, sizeof(buf), bufPtr, MAX7456ADD_VM1, BLINK_DUTY_CYCLE_50_50 | BLINK_TIME_3 | BACKGROUND_BRIGHTNESS_28);
420 busTransfer(state.dev, NULL, buf, bufPtr);
423 void max7456ClearScreen(void)
425 for (uint_fast16_t ii = 0; ii < ARRAYLEN(osdCharacterGridBuffer); ii++) {
426 if (osdCharacterGridBuffer[ii] != CHAR_BLANK) {
427 osdCharacterGridBuffer[ii] = CHAR_BLANK;
428 bitArraySet(screenIsDirty, ii);
433 void max7456WriteChar(uint8_t x, uint8_t y, uint16_t c, uint8_t mode)
435 unsigned pos = y * MAX7456_CHARS_PER_LINE + x;
436 uint16_t val = MAKE_CHAR_MODE(c, mode);
437 if (pos < ARRAYLEN(osdCharacterGridBuffer)) {
438 if (osdCharacterGridBuffer[pos] != val) {
439 osdCharacterGridBuffer[pos] = val;
440 bitArraySet(screenIsDirty, pos);
442 } else {
443 BOUNDS_CHECK_FAILED();
447 bool max7456ReadChar(uint8_t x, uint8_t y, uint16_t *c, uint8_t *mode)
449 unsigned pos = y * MAX7456_CHARS_PER_LINE + x;
450 if (pos < ARRAYLEN(osdCharacterGridBuffer)) {
451 uint16_t val = osdCharacterGridBuffer[pos];
452 *c = CHAR_BYTE(val);
453 *mode = MODE_BYTE(val);
454 if (CHAR_MODE_IS_EXT(*mode)) {
455 *c |= 1 << 8;
456 *mode &= ~CHAR_MODE_EXT;
458 return true;
460 BOUNDS_CHECK_FAILED();
461 return false;
464 void max7456Write(uint8_t x, uint8_t y, const char *buff, uint8_t mode)
466 uint8_t i = 0;
467 uint16_t c;
468 unsigned pos = y * MAX7456_CHARS_PER_LINE + x;
469 for (i = 0; *buff; i++, buff++, pos++) {
470 //do not write past screen's end of line
471 if (x + i >= MAX7456_CHARS_PER_LINE) {
472 break;
474 c = MAKE_CHAR_MODE_U8(*buff, mode);
475 if (pos < ARRAYLEN(osdCharacterGridBuffer)) {
476 if (osdCharacterGridBuffer[pos] != c) {
477 osdCharacterGridBuffer[pos] = c;
478 bitArraySet(screenIsDirty, pos);
480 } else {
481 BOUNDS_CHECK_FAILED();
486 // Must be called with the lock held. Returns whether any new characters
487 // were drawn.
488 static bool max7456DrawScreenPartial(void)
490 uint8_t spiBuff[MAX_CHARS2UPDATE * BYTES_PER_CHAR2UPDATE];
491 int bufPtr = 0;
492 size_t pos;
493 uint_fast16_t updatedCharCount;
494 uint8_t charMode;
495 int next;
497 for (pos = 0, updatedCharCount = 0; pos < ARRAYLEN(osdCharacterGridBuffer);) {
498 next = BITARRAY_FIND_FIRST_SET(screenIsDirty, pos);
499 if (next < 0) {
500 // No more dirty chars.
501 break;
503 pos = next;
504 if (pos >= ARRAYLEN(osdCharacterGridBuffer)) {
505 BOUNDS_CHECK_FAILED();
508 // Found one dirty character to send
509 uint8_t ph = pos >> 8;
510 uint8_t pl = pos & 0xff;
512 charMode = MODE_BYTE(osdCharacterGridBuffer[pos]);
513 uint8_t chr = CHAR_BYTE(osdCharacterGridBuffer[pos]);
514 if (CHAR_MODE_IS_EXT(charMode)) {
515 if (!DMM_IS_8BIT_MODE(state.registers.dmm)) {
516 state.registers.dmm |= DMM_8BIT_MODE;
517 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_DMM, state.registers.dmm);
520 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_DMAH, ph | DMAH_8_BIT_DMDI_IS_CHAR_ATTR);
521 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_DMAL, pl);
522 // Attribute bit positions on DMDI are 2 bits up relative to DMM.
523 // DMM uses [5:3] while DMDI uses [7:4] - one bit more for referencing
524 // characters in the [256, 511] range (which is not possible via DMM).
525 // Since we write mostly to DMM, the internal representation uses
526 // the format of the former and we shift it up here.
527 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_DMDI, charMode << 2);
529 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_DMAH, ph);
530 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_DMAL, pl);
531 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_DMDI, chr);
533 } else {
534 if (DMM_IS_8BIT_MODE(state.registers.dmm) || (DMM_CHAR_MODE_MASK & state.registers.dmm) != charMode) {
535 state.registers.dmm &= ~DMM_8BIT_MODE;
536 state.registers.dmm = (state.registers.dmm & ~DMM_CHAR_MODE_MASK) | charMode;
537 // Send the attributes for the character run. They
538 // will be applied to all characters until we change
539 // the DMM register.
540 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_DMM, state.registers.dmm);
543 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_DMAH, ph);
544 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_DMAL, pl);
545 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_DMDI, chr);
548 bitArrayClr(screenIsDirty, pos);
549 if (++updatedCharCount == MAX_CHARS2UPDATE) {
550 break;
552 // Start next search at next bit
553 pos++;
556 if (bufPtr) {
557 busTransfer(state.dev, NULL, spiBuff, bufPtr);
558 return true;
560 return false;
563 // Must be called with the lock held
564 static void max7456StallCheck(void)
566 static uint32_t lastSigCheckMs = 0;
567 static uint32_t videoDetectTimeMs = 0;
569 uint8_t vm0;
570 uint8_t statReg;
572 if (!state.isInitialized || !max7456ReadVM0(&vm0) || vm0 != state.registers.vm0) {
573 max7456ReInit();
574 return;
577 if (state.videoSystem == VIDEO_SYSTEM_AUTO) {
578 timeMs_t nowMs = millis();
579 if ((nowMs - lastSigCheckMs) > MAX7456_SIGNAL_CHECK_INTERVAL_MS) {
581 // Adjust output format based on the current input format.
582 busRead(state.dev, MAX7456ADD_STAT, &statReg);
584 if (statReg & STAT_LOS) {
585 videoDetectTimeMs = 0;
586 } else {
587 if ((VIN_IS_PAL(statReg) && VIDEO_MODE_IS_NTSC(state.registers.vm0))
588 || (VIN_IS_NTSC_alt(statReg) && VIDEO_MODE_IS_PAL(state.registers.vm0))) {
589 if (videoDetectTimeMs) {
590 if (nowMs - videoDetectTimeMs > VIDEO_SIGNAL_DEBOUNCE_MS) {
591 max7456ReInit();
593 } else {
594 // Wait for signal to stabilize
595 videoDetectTimeMs = nowMs;
600 lastSigCheckMs = nowMs;
605 void max7456Update(void)
607 // Check if device is available
608 if (state.dev == NULL) {
609 return;
612 if ((max7456OSDIsEnabled() && max7456TryLock()) || !state.isInitialized) {
613 // (Re)Initialize MAX7456 at startup or stall is detected.
614 max7456StallCheck();
615 if (state.isInitialized) {
616 max7456DrawScreenPartial();
618 max7456Unlock();
622 // this function redraws the whole display at once and
623 // might a long time to complete. It should not the used
624 // when copter is armed.
625 void max7456RefreshAll(void)
627 uint8_t dmm;
629 // Check if device is available
630 if (state.dev == NULL) {
631 return;
634 if (max7456TryLock()) {
636 // Issue an OSD clear command
637 busRead(state.dev, MAX7456ADD_DMM | MAX7456ADD_READ, &dmm);
638 busWrite(state.dev, MAX7456ADD_DMM, state.registers.dmm | DMM_CLEAR_DISPLAY);
640 // Wait for clear to complete (20us)
641 while (1) {
642 busRead(state.dev, MAX7456ADD_DMM | MAX7456ADD_READ, &dmm);
643 if (!(dmm & DMM_CLEAR_DISPLAY)) {
644 state.registers.dmm = dmm;
645 break;
649 // Mark non-blank characters as dirty
650 BITARRAY_CLR_ALL(screenIsDirty);
651 for (unsigned ii = 0; ii < ARRAYLEN(osdCharacterGridBuffer); ii++) {
652 if (!CHAR_IS_BLANK(osdCharacterGridBuffer[ii])) {
653 bitArraySet(screenIsDirty, ii);
657 // Now perform partial writes until there are no dirty ones
658 while(max7456DrawScreenPartial());
660 max7456Unlock();
664 void max7456ReadNvm(uint16_t char_address, osdCharacter_t *chr)
666 // Check if device is available
667 if (state.dev == NULL) {
668 return;
671 max7456Lock();
672 // OSD must be disabled to read or write to NVM
673 bool enabled = max7456OSDSetEnabled(false);
675 busWrite(state.dev, MAX7456ADD_CMAH, char_address);
676 if (char_address > 255) {
677 // AT7456E and AB7456 have 512 characters of NVM.
678 // To read/write to NVM they use CMAL[6] as the
679 // high bits of the character address.
680 uint8_t addr_h = char_address >> 8;
681 busWrite(state.dev, MAX7456ADD_CMAL, addr_h << 6);
683 busWrite(state.dev, MAX7456ADD_CMM, READ_NVR);
685 max7456WaitUntilNoBusy();
687 for (unsigned ii = 0; ii < OSD_CHAR_VISIBLE_BYTES; ii++) {
688 busWrite(state.dev, MAX7456ADD_CMAL, ii);
689 busRead(state.dev, MAX7456ADD_CMDO, &chr->data[ii]);
692 max7456OSDSetEnabled(enabled);
693 max7456Unlock();
696 void max7456WriteNvm(uint16_t char_address, const osdCharacter_t *chr)
698 uint8_t spiBuff[(sizeof(chr->data) * 2 + 2) * 2];
699 int bufPtr = 0;
701 // Check if device is available
702 if (state.dev == NULL) {
703 return;
706 max7456Lock();
707 // OSD must be disabled to read or write to NVM
708 max7456OSDSetEnabled(false);
710 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_CMAH, char_address & 0xFF); // set start address high
712 uint8_t or_val = 0;
713 if (char_address > 255) {
714 // AT7456E and AB7456 have 512 characters of NVM.
715 // To read/write to NVM they use CMAL[6] as the
716 // high bit of the character address.
718 // Instead of issuing an additional write to CMAL when
719 // we're done uploading to shadow RAM, we set the high
720 // bits of CMAL on every write since they have no side
721 // effects while writing from CMDI to RAM and when we
722 // issue the copy command to NVM, CMAL[6] is already
723 // set.
724 uint8_t addr_h = char_address >> 8;
725 or_val = addr_h << 6;
728 for (unsigned x = 0; x < OSD_CHAR_VISIBLE_BYTES; x++) {
729 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_CMAL, x | or_val); //set start address low
730 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_CMDI, chr->data[x]);
733 // transfer 54 bytes from shadow ram to NVM
734 bufPtr = max7456PrepareBuffer(spiBuff, sizeof(spiBuff), bufPtr, MAX7456ADD_CMM, WRITE_NVR);
736 busTransfer(state.dev, NULL, spiBuff, bufPtr);
738 max7456WaitUntilNoBusy();
740 /* XXX: Don't call max7456OSDEnable(), it's intentionally ommited.
741 * If we continue drawing while characters are being uploaded, we
742 * get some corrupted characters from time to time. As a workaround,
743 * we require a reboot after characters have been uploaded to NVM.
746 max7456Unlock();
750 #endif