/* * This file is part of Cleanflight and Betaflight. * * Cleanflight and Betaflight are free software. You can redistribute * this software and/or modify this software under the terms of the * GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) * any later version. * * Cleanflight and Betaflight are distributed in the hope that they * will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software. * * If not, see . */ #include #include #include #include "platform.h" #ifdef USE_MAX7456 #include "build/debug.h" #include "pg/max7456.h" #include "pg/vcd.h" #include "drivers/bus_spi.h" #include "drivers/dma.h" #include "drivers/io.h" #include "drivers/light_led.h" #include "drivers/max7456.h" #include "drivers/nvic.h" #include "drivers/osd.h" #include "drivers/osd_symbols.h" #include "drivers/time.h" // 10 MHz max SPI frequency #define MAX7456_MAX_SPI_CLK_HZ 10000000 #define MAX7456_INIT_MAX_SPI_CLK_HZ 5000000 // DEBUG_MAX7456_SIGNAL #define DEBUG_MAX7456_SIGNAL_MODEREG 0 #define DEBUG_MAX7456_SIGNAL_SENSE 1 #define DEBUG_MAX7456_SIGNAL_REINIT 2 #define DEBUG_MAX7456_SIGNAL_ROWS 3 // DEBUG_MAX7456_SPICLOCK #define DEBUG_MAX7456_SPICLOCK_OVERCLOCK 0 #define DEBUG_MAX7456_SPICLOCK_DEVTYPE 1 #define DEBUG_MAX7456_SPICLOCK_DIVISOR 2 // VM0 bits #define VIDEO_BUFFER_DISABLE 0x01 #define MAX7456_RESET 0x02 #define VERTICAL_SYNC_NEXT_VSYNC 0x04 #define OSD_ENABLE 0x08 #define SYNC_MODE_AUTO 0x00 #define SYNC_MODE_INTERNAL 0x30 #define SYNC_MODE_EXTERNAL 0x20 #define VIDEO_MODE_PAL 0x40 #define VIDEO_MODE_NTSC 0x00 #define VIDEO_MODE_MASK 0x40 #define VIDEO_MODE_IS_PAL(val) (((val) & VIDEO_MODE_MASK) == VIDEO_MODE_PAL) #define VIDEO_MODE_IS_NTSC(val) (((val) & VIDEO_MODE_MASK) == VIDEO_MODE_NTSC) #define VIDEO_SIGNAL_DEBOUNCE_MS 100 // Time to wait for input to stabilize // VM1 bits // duty cycle is on_off #define BLINK_DUTY_CYCLE_50_50 0x00 #define BLINK_DUTY_CYCLE_33_66 0x01 #define BLINK_DUTY_CYCLE_25_75 0x02 #define BLINK_DUTY_CYCLE_75_25 0x03 // blinking time #define BLINK_TIME_0 0x00 #define BLINK_TIME_1 0x04 #define BLINK_TIME_2 0x08 #define BLINK_TIME_3 0x0C // background mode brightness (percent) #define BACKGROUND_BRIGHTNESS_0 0x00 #define BACKGROUND_BRIGHTNESS_7 0x01 #define BACKGROUND_BRIGHTNESS_14 0x02 #define BACKGROUND_BRIGHTNESS_21 0x03 #define BACKGROUND_BRIGHTNESS_28 0x04 #define BACKGROUND_BRIGHTNESS_35 0x05 #define BACKGROUND_BRIGHTNESS_42 0x06 #define BACKGROUND_BRIGHTNESS_49 0x07 #define BACKGROUND_MODE_GRAY 0x80 // STAT register bits #define STAT_PAL 0x01 #define STAT_NTSC 0x02 #define STAT_LOS 0x04 #define STAT_NVR_BUSY 0x20 #define STAT_IS_PAL(val) ((val) & STAT_PAL) #define STAT_IS_NTSC(val) ((val) & STAT_NTSC) #define STAT_IS_LOS(val) ((val) & STAT_LOS) #define VIN_IS_PAL(val) (!STAT_IS_LOS(val) && STAT_IS_PAL(val)) #define VIN_IS_NTSC(val) (!STAT_IS_LOS(val) && STAT_IS_NTSC(val)) // Kluege warning! // There are occasions that NTSC is not detected even with !LOS (AB7456 specific?) // When this happens, lower 3 bits of STAT register is read as zero. // To cope with this case, this macro defines !LOS && !PAL as NTSC. // Should be compatible with MAX7456 and non-problematic case. #define VIN_IS_NTSC_alt(val) (!STAT_IS_LOS(val) && !STAT_IS_PAL(val)) #define MAX7456_SIGNAL_CHECK_INTERVAL_MS 1000 // msec #define MAX7456_STALL_CHECK_INTERVAL_MS 1000 // msec // DMM special bits #define CLEAR_DISPLAY 0x04 #define CLEAR_DISPLAY_VERT 0x06 #define INVERT_PIXEL_COLOR 0x08 // Special address for terminating incremental write #define END_STRING 0xff #define MAX7456ADD_READ 0x80 #define MAX7456ADD_VM0 0x00 //0b0011100// 00 // 00 ,0011100 #define MAX7456ADD_VM1 0x01 #define MAX7456ADD_HOS 0x02 #define MAX7456ADD_VOS 0x03 #define MAX7456ADD_DMM 0x04 #define MAX7456ADD_DMAH 0x05 #define MAX7456ADD_DMAL 0x06 #define MAX7456ADD_DMDI 0x07 #define MAX7456ADD_CMM 0x08 #define MAX7456ADD_CMAH 0x09 #define MAX7456ADD_CMAL 0x0a #define MAX7456ADD_CMDI 0x0b #define MAX7456ADD_OSDM 0x0c #define MAX7456ADD_RB0 0x10 #define MAX7456ADD_RB1 0x11 #define MAX7456ADD_RB2 0x12 #define MAX7456ADD_RB3 0x13 #define MAX7456ADD_RB4 0x14 #define MAX7456ADD_RB5 0x15 #define MAX7456ADD_RB6 0x16 #define MAX7456ADD_RB7 0x17 #define MAX7456ADD_RB8 0x18 #define MAX7456ADD_RB9 0x19 #define MAX7456ADD_RB10 0x1a #define MAX7456ADD_RB11 0x1b #define MAX7456ADD_RB12 0x1c #define MAX7456ADD_RB13 0x1d #define MAX7456ADD_RB14 0x1e #define MAX7456ADD_RB15 0x1f #define MAX7456ADD_OSDBL 0x6c #define MAX7456ADD_STAT 0xA0 #define NVM_RAM_SIZE 54 #define WRITE_NVR 0xA0 // Device type #define MAX7456_DEVICE_TYPE_MAX 0 #define MAX7456_DEVICE_TYPE_AT 1 #define CHARS_PER_LINE 30 // XXX Should be related to VIDEO_BUFFER_CHARS_*? #define MAX7456_SUPPORTED_LAYER_COUNT (DISPLAYPORT_LAYER_BACKGROUND + 1) typedef struct max7456Layer_s { uint8_t buffer[VIDEO_BUFFER_CHARS_PAL]; } max7456Layer_t; static DMA_DATA_ZERO_INIT max7456Layer_t displayLayers[MAX7456_SUPPORTED_LAYER_COUNT]; static displayPortLayer_e activeLayer = DISPLAYPORT_LAYER_FOREGROUND; extDevice_t max7456Device; extDevice_t *dev = &max7456Device; static bool max7456DeviceDetected = false; static uint16_t max7456SpiClock; uint16_t maxScreenSize = VIDEO_BUFFER_CHARS_PAL; // We write everything to the active layer and then compare // it with shadowBuffer to update only changed chars. // This solution is faster then redrawing entire screen. static uint8_t shadowBuffer[VIDEO_BUFFER_CHARS_PAL]; //Max chars to update in one idle #define MAX_CHARS2UPDATE 100 static uint8_t spiBuff[MAX_CHARS2UPDATE*6]; static uint8_t videoSignalCfg; static uint8_t videoSignalReg = OSD_ENABLE; // OSD_ENABLE required to trigger first ReInit static uint8_t displayMemoryModeReg = 0; static uint8_t hosRegValue; // HOS (Horizontal offset register) value static uint8_t vosRegValue; // VOS (Vertical offset register) value static bool fontIsLoading = false; static uint8_t max7456DeviceType; static displayPortBackground_e deviceBackgroundType = DISPLAY_BACKGROUND_TRANSPARENT; // previous states initialized outside the valid range to force update on first call #define INVALID_PREVIOUS_REGISTER_STATE 255 static uint8_t previousBlackWhiteRegister = INVALID_PREVIOUS_REGISTER_STATE; static uint8_t previousInvertRegister = INVALID_PREVIOUS_REGISTER_STATE; static void max7456DrawScreenSlow(void); static uint8_t *getLayerBuffer(displayPortLayer_e layer) { return displayLayers[layer].buffer; } static uint8_t *getActiveLayerBuffer(void) { return getLayerBuffer(activeLayer); } static void max7456SetRegisterVM1(void) { uint8_t backgroundGray = BACKGROUND_BRIGHTNESS_28; // this is the device default background gray level uint8_t vm1Register = BLINK_TIME_1 | BLINK_DUTY_CYCLE_75_25; // device defaults if (deviceBackgroundType != DISPLAY_BACKGROUND_TRANSPARENT) { vm1Register |= BACKGROUND_MODE_GRAY; switch (deviceBackgroundType) { case DISPLAY_BACKGROUND_BLACK: backgroundGray = BACKGROUND_BRIGHTNESS_0; break; case DISPLAY_BACKGROUND_LTGRAY: backgroundGray = BACKGROUND_BRIGHTNESS_49; break; case DISPLAY_BACKGROUND_GRAY: default: backgroundGray = BACKGROUND_BRIGHTNESS_28; break; } } vm1Register |= (backgroundGray << 4); spiWriteReg(dev, MAX7456ADD_VM1, vm1Register); } uint8_t max7456GetRowsCount(void) { return (videoSignalReg & VIDEO_MODE_PAL) ? VIDEO_LINES_PAL : VIDEO_LINES_NTSC; } // When clearing the shadow buffer we fill with 0 so that the characters will // be flagged as changed when compared to the 0x20 used in the layer buffers. static void max7456ClearShadowBuffer(void) { memset(shadowBuffer, 0, maxScreenSize); } // Buffer is filled with the whitespace character (0x20) static void max7456ClearLayer(displayPortLayer_e layer) { memset(getLayerBuffer(layer), 0x20, VIDEO_BUFFER_CHARS_PAL); } void max7456ReInit(void) { uint8_t srdata = 0; static bool firstInit = true; switch (videoSignalCfg) { case VIDEO_SYSTEM_PAL: videoSignalReg = VIDEO_MODE_PAL | OSD_ENABLE; break; case VIDEO_SYSTEM_NTSC: videoSignalReg = VIDEO_MODE_NTSC | OSD_ENABLE; break; case VIDEO_SYSTEM_AUTO: srdata = spiReadRegMsk(dev, MAX7456ADD_STAT); if (VIN_IS_NTSC(srdata)) { videoSignalReg = VIDEO_MODE_NTSC | OSD_ENABLE; } else if (VIN_IS_PAL(srdata)) { videoSignalReg = VIDEO_MODE_PAL | OSD_ENABLE; } else { // No valid input signal, fallback to default (XXX NTSC for now) videoSignalReg = VIDEO_MODE_NTSC | OSD_ENABLE; } break; } if (videoSignalReg & VIDEO_MODE_PAL) { //PAL maxScreenSize = VIDEO_BUFFER_CHARS_PAL; } else { // NTSC maxScreenSize = VIDEO_BUFFER_CHARS_NTSC; } // Set all rows to same charactor black/white level previousBlackWhiteRegister = INVALID_PREVIOUS_REGISTER_STATE; max7456Brightness(0, 2); // Re-enable MAX7456 (last function call disables it) // Make sure the Max7456 is enabled spiWriteReg(dev, MAX7456ADD_VM0, videoSignalReg); spiWriteReg(dev, MAX7456ADD_HOS, hosRegValue); spiWriteReg(dev, MAX7456ADD_VOS, vosRegValue); max7456SetRegisterVM1(); // Clear shadow to force redraw all screen in non-dma mode. max7456ClearShadowBuffer(); if (firstInit) { max7456DrawScreenSlow(); firstInit = false; } } void max7456PreInit(const max7456Config_t *max7456Config) { spiPreinitRegister(max7456Config->csTag, max7456Config->preInitOPU ? IOCFG_OUT_PP : IOCFG_IPU, 1); } // Here we init only CS and try to init MAX for first time. // Also detect device type (MAX v.s. AT) max7456InitStatus_e max7456Init(const max7456Config_t *max7456Config, const vcdProfile_t *pVcdProfile, bool cpuOverclock) { max7456DeviceDetected = false; deviceBackgroundType = DISPLAY_BACKGROUND_TRANSPARENT; // initialize all layers for (unsigned i = 0; i < MAX7456_SUPPORTED_LAYER_COUNT; i++) { max7456ClearLayer(i); } max7456HardwareReset(); if (!max7456Config->csTag || !spiSetBusInstance(dev, max7456Config->spiDevice, OWNER_OSD_CS)) { return MAX7456_INIT_NOT_CONFIGURED; } dev->busType_u.spi.csnPin = IOGetByTag(max7456Config->csTag); if (!IOIsFreeOrPreinit(dev->busType_u.spi.csnPin)) { return MAX7456_INIT_NOT_CONFIGURED; } IOInit(dev->busType_u.spi.csnPin, OWNER_OSD_CS, 0); IOConfigGPIO(dev->busType_u.spi.csnPin, SPI_IO_CS_CFG); IOHi(dev->busType_u.spi.csnPin); // Detect MAX7456 existence and device type. Do this at half the speed for safety. // Detect MAX7456 and compatible device by reading OSDM (OSD Insertion MUX) register. // This register is not modified in this driver, therefore ensured to remain at its default value (0x1B). spiSetClkDivisor(dev, spiCalculateDivider(MAX7456_INIT_MAX_SPI_CLK_HZ)); // Write 0xff to conclude any current SPI transaction the MAX7456 is expecting spiWrite(dev, END_STRING); uint8_t osdm = spiReadRegMsk(dev, MAX7456ADD_OSDM); if (osdm != 0x1B) { IOConfigGPIO(dev->busType_u.spi.csnPin, IOCFG_IPU); return MAX7456_INIT_NOT_FOUND; } // At this point, we can claim the ownership of the CS pin max7456DeviceDetected = true; IOInit(dev->busType_u.spi.csnPin, OWNER_OSD_CS, 0); // Detect device type by writing and reading CA[8] bit at CMAL[6]. // This is a bit for accessing second half of character glyph storage, supported only by AT variant. spiWriteReg(dev, MAX7456ADD_CMAL, (1 << 6)); // CA[8] bit if (spiReadRegMsk(dev, MAX7456ADD_CMAL) & (1 << 6)) { max7456DeviceType = MAX7456_DEVICE_TYPE_AT; } else { max7456DeviceType = MAX7456_DEVICE_TYPE_MAX; } #if defined(USE_OVERCLOCK) // Determine SPI clock divisor based on config and the device type. switch (max7456Config->clockConfig) { case MAX7456_CLOCK_CONFIG_HALF: max7456SpiClock = spiCalculateDivider(MAX7456_MAX_SPI_CLK_HZ / 2); break; case MAX7456_CLOCK_CONFIG_OC: max7456SpiClock = (cpuOverclock && (max7456DeviceType == MAX7456_DEVICE_TYPE_MAX)) ? spiCalculateDivider(MAX7456_MAX_SPI_CLK_HZ / 2) : spiCalculateDivider(MAX7456_MAX_SPI_CLK_HZ); break; case MAX7456_CLOCK_CONFIG_FULL: max7456SpiClock = spiCalculateDivider(MAX7456_MAX_SPI_CLK_HZ); break; } DEBUG_SET(DEBUG_MAX7456_SPICLOCK, DEBUG_MAX7456_SPICLOCK_OVERCLOCK, cpuOverclock); DEBUG_SET(DEBUG_MAX7456_SPICLOCK, DEBUG_MAX7456_SPICLOCK_DEVTYPE, max7456DeviceType); DEBUG_SET(DEBUG_MAX7456_SPICLOCK, DEBUG_MAX7456_SPICLOCK_DIVISOR, max7456SpiClock); #else UNUSED(max7456Config); UNUSED(cpuOverclock); #endif spiSetClkDivisor(dev, max7456SpiClock); // force soft reset on Max7456 spiWriteReg(dev, MAX7456ADD_VM0, MAX7456_RESET); // Wait for 100us before polling for completion of reset delayMicroseconds(100); // Wait for reset to complete while ((spiReadRegMsk(dev, MAX7456ADD_VM0) & MAX7456_RESET) != 0x00); // Setup values to write to registers videoSignalCfg = pVcdProfile->video_system; hosRegValue = 32 - pVcdProfile->h_offset; vosRegValue = 16 - pVcdProfile->v_offset; // Real init will be made later when driver detect idle. return MAX7456_INIT_OK; } /** * Sets inversion of black and white pixels. */ void max7456Invert(bool invert) { if (invert) { displayMemoryModeReg |= INVERT_PIXEL_COLOR; } else { displayMemoryModeReg &= ~INVERT_PIXEL_COLOR; } if (displayMemoryModeReg != previousInvertRegister) { // clear the shadow buffer so all characters will be // redrawn with the proper invert state max7456ClearShadowBuffer(); previousInvertRegister = displayMemoryModeReg; spiWriteReg(dev, MAX7456ADD_DMM, displayMemoryModeReg); } } /** * Sets the brighness of black and white pixels. * * @param black Black brightness (0-3, 0 is darkest) * @param white White brightness (0-3, 0 is darkest) */ void max7456Brightness(uint8_t black, uint8_t white) { const uint8_t reg = (black << 2) | (3 - white); if (reg != previousBlackWhiteRegister) { previousBlackWhiteRegister = reg; for (int i = MAX7456ADD_RB0; i <= MAX7456ADD_RB15; i++) { spiWriteReg(dev, i, reg); } } } void max7456ClearScreen(void) { max7456ClearLayer(activeLayer); } void max7456WriteChar(uint8_t x, uint8_t y, uint8_t c) { uint8_t *buffer = getActiveLayerBuffer(); if (x < CHARS_PER_LINE && y < VIDEO_LINES_PAL) { buffer[y * CHARS_PER_LINE + x] = c; } } void max7456Write(uint8_t x, uint8_t y, const char *buff) { if (y < VIDEO_LINES_PAL) { uint8_t *buffer = getActiveLayerBuffer(); for (int i = 0; buff[i] && x + i < CHARS_PER_LINE; i++) { buffer[y * CHARS_PER_LINE + x + i] = buff[i]; } } } bool max7456LayerSupported(displayPortLayer_e layer) { if (layer == DISPLAYPORT_LAYER_FOREGROUND || layer == DISPLAYPORT_LAYER_BACKGROUND) { return true; } else { return false; } } bool max7456LayerSelect(displayPortLayer_e layer) { if (max7456LayerSupported(layer)) { activeLayer = layer; return true; } else { return false; } } bool max7456LayerCopy(displayPortLayer_e destLayer, displayPortLayer_e sourceLayer) { if ((sourceLayer != destLayer) && max7456LayerSupported(sourceLayer) && max7456LayerSupported(destLayer)) { memcpy(getLayerBuffer(destLayer), getLayerBuffer(sourceLayer), VIDEO_BUFFER_CHARS_PAL); return true; } else { return false; } } bool max7456DmaInProgress(void) { return spiIsBusy(dev); } bool max7456BuffersSynced(void) { for (int i = 0; i < maxScreenSize; i++) { if (displayLayers[DISPLAYPORT_LAYER_FOREGROUND].buffer[i] != shadowBuffer[i]) { return false; } } return true; } void max7456ReInitIfRequired(bool forceStallCheck) { static timeMs_t lastSigCheckMs = 0; static timeMs_t videoDetectTimeMs = 0; static uint16_t reInitCount = 0; static timeMs_t lastStallCheckMs = MAX7456_STALL_CHECK_INTERVAL_MS / 2; // offset so that it doesn't coincide with the signal check const timeMs_t nowMs = millis(); bool stalled = false; if (forceStallCheck || (lastStallCheckMs + MAX7456_STALL_CHECK_INTERVAL_MS < nowMs)) { lastStallCheckMs = nowMs; // Write 0xff to conclude any current SPI transaction the MAX7456 is expecting spiWrite(dev, END_STRING); stalled = (spiReadRegMsk(dev, MAX7456ADD_VM0) != videoSignalReg); } if (stalled) { max7456ReInit(); } else if ((videoSignalCfg == VIDEO_SYSTEM_AUTO) && ((nowMs - lastSigCheckMs) > MAX7456_SIGNAL_CHECK_INTERVAL_MS)) { // Write 0xff to conclude any current SPI transaction the MAX7456 is expecting spiWrite(dev, END_STRING); // Adjust output format based on the current input format. const uint8_t videoSense = spiReadRegMsk(dev, MAX7456ADD_STAT); DEBUG_SET(DEBUG_MAX7456_SIGNAL, DEBUG_MAX7456_SIGNAL_MODEREG, videoSignalReg & VIDEO_MODE_MASK); DEBUG_SET(DEBUG_MAX7456_SIGNAL, DEBUG_MAX7456_SIGNAL_SENSE, videoSense & 0x7); DEBUG_SET(DEBUG_MAX7456_SIGNAL, DEBUG_MAX7456_SIGNAL_ROWS, max7456GetRowsCount()); if (videoSense & STAT_LOS) { videoDetectTimeMs = 0; } else { if ((VIN_IS_PAL(videoSense) && VIDEO_MODE_IS_NTSC(videoSignalReg)) || (VIN_IS_NTSC_alt(videoSense) && VIDEO_MODE_IS_PAL(videoSignalReg))) { if (videoDetectTimeMs) { if (millis() - videoDetectTimeMs > VIDEO_SIGNAL_DEBOUNCE_MS) { max7456ReInit(); DEBUG_SET(DEBUG_MAX7456_SIGNAL, DEBUG_MAX7456_SIGNAL_REINIT, ++reInitCount); } } else { // Wait for signal to stabilize videoDetectTimeMs = millis(); } } } lastSigCheckMs = nowMs; } //------------ end of (re)init------------------------------------- } void max7456DrawScreen(void) { static uint16_t pos = 0; // This routine doesn't block so need to use static data static busSegment_t segments[] = { {NULL, NULL, 0, true, NULL}, {NULL, NULL, 0, true, NULL}, }; if (!fontIsLoading) { // (Re)Initialize MAX7456 at startup or stall is detected. max7456ReInitIfRequired(false); uint8_t *buffer = getActiveLayerBuffer(); int buff_len = 0; for (int k = 0; k < MAX_CHARS2UPDATE; k++) { if (buffer[pos] != shadowBuffer[pos]) { spiBuff[buff_len++] = MAX7456ADD_DMAH; spiBuff[buff_len++] = pos >> 8; spiBuff[buff_len++] = MAX7456ADD_DMAL; spiBuff[buff_len++] = pos & 0xff; spiBuff[buff_len++] = MAX7456ADD_DMDI; spiBuff[buff_len++] = buffer[pos]; shadowBuffer[pos] = buffer[pos]; } if (++pos >= maxScreenSize) { pos = 0; break; } } if (buff_len) { segments[0].txData = spiBuff; segments[0].len = buff_len; // Ensure any prior DMA has completed spiWaitClaim(dev); spiSequence(dev, &segments[0]); // Non-blocking, so transfer still in progress } } } static void max7456DrawScreenSlow(void) { bool escapeCharFound = false; uint8_t *buffer = getActiveLayerBuffer(); // Enable auto-increment mode and update every character in the active buffer. uint8_t dma_regs[3]; dma_regs[0] = displayMemoryModeReg | 1; dma_regs[1] = 0; dma_regs[2] = 0; spiWriteRegBuf(dev, MAX7456ADD_DMM, dma_regs, sizeof (dma_regs)); // The "escape" character 0xFF must be skipped as it causes the MAX7456 to exit auto-increment mode. for (int xx = 0; xx < maxScreenSize; xx++) { if (buffer[xx] == END_STRING) { escapeCharFound = true; spiWriteReg(dev, MAX7456ADD_DMDI, ' '); // replace the 0xFF character with a blank in the first pass to avoid terminating auto-increment } else { spiWriteReg(dev, MAX7456ADD_DMDI, buffer[xx]); } shadowBuffer[xx] = buffer[xx]; } spiWriteReg(dev, MAX7456ADD_DMDI, END_STRING); // Turn off auto increment spiWriteReg(dev, MAX7456ADD_DMM, displayMemoryModeReg); // If we found any of the "escape" character 0xFF, then make a second pass // to update them with direct addressing if (escapeCharFound) { dma_regs[2] = END_STRING; for (int xx = 0; xx < maxScreenSize; xx++) { if (buffer[xx] == END_STRING) { dma_regs[0] = xx >> 8; dma_regs[1] = xx & 0xFF; spiWriteRegBuf(dev, MAX7456ADD_DMAH, dma_regs, sizeof (dma_regs)); } } } } // should not be used when armed void max7456RefreshAll(void) { max7456ReInitIfRequired(true); max7456DrawScreenSlow(); } bool max7456WriteNvm(uint8_t char_address, const uint8_t *font_data) { if (!max7456DeviceDetected) { return false; } // Block pending completion of any prior SPI access spiWait(dev); // disable display fontIsLoading = true; spiWriteReg(dev, MAX7456ADD_VM0, 0); spiWriteReg(dev, MAX7456ADD_CMAH, char_address); // set start address high for (int x = 0; x < 54; x++) { spiWriteReg(dev, MAX7456ADD_CMAL, x); //set start address low spiWriteReg(dev, MAX7456ADD_CMDI, font_data[x]); #ifdef LED0_TOGGLE LED0_TOGGLE; #else LED1_TOGGLE; #endif } // Transfer 54 bytes from shadow ram to NVM spiWriteReg(dev, MAX7456ADD_CMM, WRITE_NVR); // Wait until bit 5 in the status register returns to 0 (12ms) while ((spiReadRegMsk(dev, MAX7456ADD_STAT) & STAT_NVR_BUSY) != 0x00); return true; } #ifdef MAX7456_NRST_PIN static IO_t max7456ResetPin = IO_NONE; #endif void max7456HardwareReset(void) { #ifdef MAX7456_NRST_PIN #define IO_RESET_CFG IO_CONFIG(GPIO_Mode_OUT, GPIO_Speed_2MHz, GPIO_OType_PP, GPIO_PuPd_DOWN) max7456ResetPin = IOGetByTag(IO_TAG(MAX7456_NRST_PIN)); IOInit(max7456ResetPin, OWNER_OSD, 0); IOConfigGPIO(max7456ResetPin, IO_RESET_CFG); // RESET 50ms long pulse, followed by 100us pause IOLo(max7456ResetPin); delay(50); IOHi(max7456ResetPin); delayMicroseconds(100); #else // Allow device 50ms to powerup delay(50); #endif } bool max7456IsDeviceDetected(void) { return max7456DeviceDetected; } void max7456SetBackgroundType(displayPortBackground_e backgroundType) { deviceBackgroundType = backgroundType; max7456SetRegisterVM1(); } #endif // USE_MAX7456