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/>.
28 #if defined(MAX7456_USE_BOUNDS_CHECKS)
29 #define BOUNDS_CHECK_FAILED() __asm("BKPT #0")
31 #define BOUNDS_CHECK_FAILED() do {} while(0)
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"
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
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
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
96 #define STAT_NTSC 0x02
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))
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
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
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
{
200 } max7456Registers_t
;
202 typedef struct max7456State_s
{
204 videoSystem_e videoSystem
;
207 max7456Registers_t registers
;
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)
221 if (busRead(state
.dev
, MAX7456ADD_STAT
, &status
)) {
222 return status
& STAT_NVR_BUSY
;
224 // Read failed or busy
228 // Wait until max7456IsBusy() returns false, toggling a LED on each iteration
229 static void max7456WaitUntilNoBusy(void)
232 if (!max7456IsBusy()) {
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
;
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)
270 static void max7456Unlock(void)
275 static bool max7456TryLock(void)
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
292 buf
[bufPtr
++] = data
;
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
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)
332 // Check if device is available
333 if (state
.dev
== NULL
) {
339 switch (state
.videoSystem
) {
341 vm0Mode
= VIDEO_MODE_PAL
;
344 vm0Mode
= VIDEO_MODE_NTSC
;
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
;
356 // No signal detected yet, wait for detection timeout
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
) {
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];
383 state
.dev
= busDeviceInit(BUSTYPE_SPI
, DEVHW_MAX7456
, 0, OWNER_OSD
);
385 if (state
.dev
== NULL
) {
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
) &&
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.
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
);
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
];
453 *mode
= MODE_BYTE(val
);
454 if (CHAR_MODE_IS_EXT(*mode
)) {
456 *mode
&= ~CHAR_MODE_EXT
;
460 BOUNDS_CHECK_FAILED();
464 void max7456Write(uint8_t x
, uint8_t y
, const char *buff
, uint8_t mode
)
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
) {
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
);
481 BOUNDS_CHECK_FAILED();
486 // Must be called with the lock held. Returns whether any new characters
488 static bool max7456DrawScreenPartial(void)
490 uint8_t spiBuff
[MAX_CHARS2UPDATE
* BYTES_PER_CHAR2UPDATE
];
493 uint_fast16_t updatedCharCount
;
497 for (pos
= 0, updatedCharCount
= 0; pos
< ARRAYLEN(osdCharacterGridBuffer
);) {
498 next
= BITARRAY_FIND_FIRST_SET(screenIsDirty
, pos
);
500 // No more dirty chars.
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
);
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
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
) {
552 // Start next search at next bit
557 busTransfer(state
.dev
, NULL
, spiBuff
, bufPtr
);
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;
572 if (!state
.isInitialized
|| !max7456ReadVM0(&vm0
) || vm0
!= state
.registers
.vm0
) {
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;
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
) {
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
) {
612 if ((max7456OSDIsEnabled() && max7456TryLock()) || !state
.isInitialized
) {
613 // (Re)Initialize MAX7456 at startup or stall is detected.
615 if (state
.isInitialized
) {
616 max7456DrawScreenPartial();
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)
629 // Check if device is available
630 if (state
.dev
== NULL
) {
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)
642 busRead(state
.dev
, MAX7456ADD_DMM
| MAX7456ADD_READ
, &dmm
);
643 if (!(dmm
& DMM_CLEAR_DISPLAY
)) {
644 state
.registers
.dmm
= dmm
;
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());
664 void max7456ReadNvm(uint16_t char_address
, osdCharacter_t
*chr
)
666 // Check if device is available
667 if (state
.dev
== NULL
) {
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
);
696 void max7456WriteNvm(uint16_t char_address
, const osdCharacter_t
*chr
)
698 uint8_t spiBuff
[(sizeof(chr
->data
) * 2 + 2) * 2];
701 // Check if device is available
702 if (state
.dev
== NULL
) {
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
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
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.