/*
  wrapper for OSD code (https://github.com/night-ghost/minimosd-extra) to run in the HAL as independent process

    (c) night_ghost@ykoctpa.ru 2017

*/

#include <AP_HAL/AP_HAL.h>

#ifdef BOARD_OSD_CS_PIN

#include <utility>

#include "osd_core/compat.h"



using namespace F4Light;

#include <AP_Common/AP_Common.h>
#include <stdio.h>

#include <hal.h>
#include "ring_buffer.h"

#include <inttypes.h>
#include <AP_HAL/AP_HAL.h>
#include <AP_HAL/utility/print_vprintf.h>
#include <AP_HAL_F4Light/AP_HAL_F4Light.h>
#include <AP_HAL_F4Light/SPIDevice.h>

#define SLAVE_BUILD

#include "osd_core/Defs.h"

#include "osd.h"


#include "osd_eeprom.h"
#include "osd_core/eeprom.h"
#include "osd_core/version.h"
#include <sd/SD.h>


namespace OSDns {

#include "osd_core/GCS_MAVLink.h"

#include "osd_core/OSD_Max7456.h"
OSD osd; //OSD object

#include "osd_core/prototypes.h"
#include "osd_core/Vars.h"



#include "osd_core/Config_Func.h"
#include "osd_core/Config.h"
#include "osd_core/Func.h"
#include "osd_core/protocols.h"
#include "osd_core/misc.h"

#include "osd_core/Params.h"
#include "osd_core/Panels.h"


// TODO: чтение конфига и еепром с карты памяти, чтобы закинуть .mcm и .osd и все       
static const char * const words[] = {
    "Air Speed",        // 1
    "Altitude",         // 2
    "Auto Mode",        // 3
    "Auto Screen Switch", // 4 
    "batt_a_k",         // 5
    "BattB",            // 6
    "batt_b_k",         // 7
    "Battery",          // 8
    "Battery A",        // 9
    "Battery B",        // 10
    "Battery Percent",  // 11
    "Battery Warning Level", // 12
    "Call Sign",        // 13
    "Chanel Rotation Switching", // 14
    "Channel Raw",      // 15
    "Channel Scale",    // 16
    "Channel state",    // 17
    "Channel Value",    // 18

    "Configuration",    // 19
    "Current",          // 20
    "curr_k",           // 21
    "Efficiency",       // 22
    "fBattA",           // 23
    "fBattB",           // 24      
    "fCurr",            // 25
    "fILS",             // 26
    "flgHUD",           // 27
    "flgOnce",          // 28
    "flgTrack",         // 29
    "Flight Data",      // 30
    "Flight Mode",      // 31
    "fRussianHUD",      // 32
    "GPS Coord",        // 33
    "GPS HDOP",         // 34
    "Heading",          // 35
    "Heading Rose",     // 36
    "Home Altitude",    // 37
    "Home Direction",   // 38
    "Home Distance",    // 39
    "Horizon",          // 40
    "HOS",              // 41
    "Message",          // 42
    "Model Type",       // 43
    "NSCREENS",         // 44
    "OSD Brightness",   // 45
    "Overspeed",        // 46

   "Panel",             // 47
    "Pitch",            // 48
    "pitch_k",          // 49
    "pitch_kn",         // 50
    "PWMDST",           // 51
    "PWMSRC",           // 52
    "Radar Scale",      // 53
    "Real heading",     // 54
    "Roll",             // 55
    "roll_k",           // 56
    "roll_kn",          // 57
    "RSSI",             // 58
    "RSSI Enable Raw",  // 59
    "RSSI High",        // 60
    "rssi_k",           // 61
    "RSSI Low",         // 62
    "RSSI Warning Level", // 63
    "SAdd1",            // 64
    "SAdd2",            // 65
    "SAdd3",            // 66
    "SAdd4",            // 67
    "Sensor 1",         // 68
    "Sensor 2",         // 69
    "Sensor 3",         // 70
    "Sensor 4",         // 71
    "SFactor1",         // 72
    "SFactor2",         // 73
    "SFactor3",         // 74
    "SFactor4",         // 75
    "SFormat1",         // 76
    "SFormat2",         // 77
    "SFormat3",         // 78
    "SFormat4",         // 79
    "Stall",            // 80
    "Temperature",      // 81
    "Throttle",         // 82
    "Time",             // 83
    "Toggle Channel",   // 84
    "Trip Distance",    // 85
    "Tune",             // 86
    "txtTime0",         // 87
    "txtTime1",         // 88
    "txtTime2",         // 89
    "txtTime3",         // 90
    "Units",            // 91
    "Velocity",         // 92
    "Vertical Speed",   // 93
    "Video Mode",       // 94
    "Visible Sats",     // 95
    "VOS",              // 96
    "Warnings",         // 97
    "Wind Speed",       // 98
    "WP Direction",     // 99
    "WP Distance",      // 100
    "#",                // 101
    "Power",            // 102
    "Date",             // 103
    "Time of day",      // 104
    "Motors",           // 105
    "Vibrations",       // 106
    "Variometer",       // 107
    "GPS Coord Lat",    // 108
    "GPS Coord Lon",    // 109
};
    
typedef struct OSD_PAN {
    uint8_t dst;
    uint8_t size;
    char fmt;
    uint8_t pan_n;
} OSD_pan;

#define m1 ((uint8_t)-1)

static const OSD_pan pan_tbl[]={
    { 0, 0, 0, 0, },
    { 0, 0, 0, ID_of(airSpeed), },     //    "Air Speed",        // 1
    { 0, 0, 0, ID_of(alt), },          //    "Altitude",         // 2
    { 9,    m1, 0, 0, },               //        "Auto Mode",        // 3 - bit in flags
    { 7,    m1, 0, 0, },               //        "Auto Screen Switch", // 4 
    { offsetof(Settings,evBattA_koef),    sizeof(sets.evBattA_koef),   'f', 0, }, //        "batt_a_k",         
    { offsetof(Settings,battBv),          sizeof(sets.battBv),         'b', 0, },  //        "BattB",            
    { offsetof(Settings,evBattB_koef),    sizeof(sets.evBattB_koef),   'f', 0, }, //        "batt_b_k",         
    { offsetof(Settings,battv),           sizeof(sets.battv),          'b', 0, },  //        "Battery",          
    { 0, 0, 0, ID_of(batt_A), },       //        "Battery A",        // 9
    { 0, 0, 0, ID_of(batt_B), },       //        "Battery B",        // 10
    { 0, 0, 0, ID_of(batteryPercent), },//       "Battery Percent",  // 11
    { offsetof(Settings,batt_warn_level), sizeof(sets.batt_warn_level),'b', 0, },  //        "Battery Warning Level", // 12
    { offsetof(Settings,OSD_CALL_SIGN),   sizeof(sets.OSD_CALL_SIGN),  'c', 0, },  //        "Call Sign",        // 13
    { offsetof(Settings,switch_mode),     sizeof(sets.switch_mode),    'b', 0, },  //        "Chanel Rotation Switching", // 14
    { 0, 0, 0, ID_of(ch), },          //        "Channel Raw",      // 15
    { 0, 0, 0, ID_of(Scale), },       //        "Channel Scale",    // 16
    { 0, 0, 0, ID_of(State), },       //        "Channel state",    // 17
    { 0, 0, 0, ID_of(CValue), },      //        "Channel Value",    // 18

    { 0, 0, 0, 0, },                  //         "Configuration",    // 19
    { 0, 0, 0, ID_of(curr_A), },      //        "Current",          // 20
    { offsetof(Settings,eCurrent_koef),   sizeof(sets.eCurrent_koef),  'f', 0, },      //        "curr_k",           // 21
    { 0, 0, 0, ID_of(eff), },         //        "Efficiency",       // 22
    { 4, m1,   0, 0, },               //        "fBattA",           // 23
    { 5, m1,   0, 0, },               //        "fBattB",           // 24      
    { 6, m1,   0, 0, },               //        "fCurr",            // 25
    { 0, 0,    0, 0, },               // 26
    { 0, 0,    0, 0, },               // 27
    { 0, m1,   0, 0, },               //        "flgOnce",          // 28
    { 0, 0,    0, 0, },         //29
    { 0, 0, 0, ID_of(Fdata), },       //        "Flight Data",      // 30
    { 0, 0, 0, ID_of(FMod), },        //        "Flight Mode",      // 31
    { 0, 0, 0, 0, },                  //        "fRussianHUD",      // 32
    { 0, 0, 0, ID_of(GPS), },         //        "GPS Coord",        // 33
    { 0, 0, 0, ID_of(Hdop), },        //        "GPS HDOP",         // 34
    { 0, 0, 0, ID_of(heading), },     //        "Heading",          // 35
    { 0, 0, 0, ID_of(rose), },        //        "Heading Rose",     // 36
    { 0, 0, 0, ID_of(homeAlt), },     //        "Home Altitude",    // 37
    { 0, 0, 0, ID_of(homeDir), },     //        "Home Direction",   // 38
    { 0, 0, 0, ID_of(homeDist), },    //        "Home Distance",    // 39
    { 0, 0, 0, ID_of(horizon), },     //        "Horizon",          // 40
    { offsetof(Settings,horiz_offs),      sizeof(sets.horiz_offs),    'b', 0, },  //        "HOS",              // 41
    { 0, 0, 0, ID_of(message), },         //        "Message",          // 42
    { offsetof(Settings,model_type),      sizeof(sets.model_type),    'b', 0, },  //        "Model Type",       // 43
    { offsetof(Settings,n_screens),       sizeof(sets.n_screens),     'b', 0, },  //        "NSCREENS",         // 44
    { offsetof(Settings,OSD_BRIGHTNESS),  sizeof(sets.OSD_BRIGHTNESS),'b', 0, },  //        "OSD Brightness",   // 45
    { offsetof(Settings,overspeed),       sizeof(sets.overspeed),     'b', 0, },  //        "Overspeed",        // 46

    { 0, 0, 0, 0, },                      //       "Panel",             // 47
    { 0, 0, 0, ID_of(pitch), },           //        "Pitch",            // 48
    { offsetof(Settings,horiz_kPitch),    sizeof(sets.horiz_kPitch),  'f', 0, }, //        "pitch_k",          // 49
    { offsetof(Settings,horiz_kPitch_a),  sizeof(sets.horiz_kPitch_a),'f', 0, }, //        "pitch_kn",         // 50
    { offsetof(Settings,pwm_dst),         sizeof(sets.pwm_dst),       'b', 0, }, //        "PWMDST",           // 51
    { offsetof(Settings,pwm_src),         sizeof(sets.pwm_src),       'b', 0, }, //        "PWMSRC",           // 52
    { 0, 0, 0, ID_of(RadarScale), },         //        "Radar Scale",      // 53
    { 0, 0, 0, ID_of(COG), },                //        "Real heading",     // 54
    { 0, 0, 0, ID_of(roll), },               //        "Roll",             // 55
    { offsetof(Settings,horiz_kRoll),    sizeof(sets.horiz_kRoll),    'f', 0, }, //        "roll_k",           // 56
    { offsetof(Settings,horiz_kRoll_a),  sizeof(sets.horiz_kRoll_a),  'f', 0, }, //        "roll_kn",          // 57
    { 0, 0, 0, ID_of(RSSI), },               //        "RSSI",             // 58
    { offsetof(Settings,RSSI_raw),       sizeof(sets.RSSI_raw),       'b', 0, }, //        "RSSI Enable Raw",  // 59
    { offsetof(Settings,RSSI_16_high),   sizeof(sets.RSSI_16_high),   'w', 0, }, //        "RSSI High",        // 60
    { offsetof(Settings,eRSSI_koef),     sizeof(sets.eRSSI_koef),     'f', 0, }, //        "rssi_k",           // 61
    { offsetof(Settings,RSSI_16_low),    sizeof(sets.RSSI_16_low),    'w', 0, }, //        "RSSI Low",         // 62
    { offsetof(Settings,rssi_warn_level),sizeof(sets.rssi_warn_level),'b', 0, }, //        "RSSI Warning Level", // 63
    { 0, 0, 0, 0, },         //        "SAdd1",            // 64     // sensors not supported
    { 0, 0, 0, 0, },         //        "SAdd2",            // 65
    { 0, 0, 0, 0, },         //        "SAdd3",            // 66
    { 0, 0, 0, 0, },         //        "SAdd4",            // 67
    { 0, 0, 0, ID_of(sensor1), },         //        "Sensor 1",         // 68
    { 0, 0, 0, ID_of(sensor2), },         //        "Sensor 2",         // 69
    { 0, 0, 0, ID_of(sensor3), },         //        "Sensor 3",         // 70
    { 0, 0, 0, ID_of(sensor4), },         //        "Sensor 4",         // 71
    { 0, 0, 0, 0, },         //        "SFactor1",         // 72
    { 0, 0, 0, 0, },         //        "SFactor2",         // 73
    { 0, 0, 0, 0, },         //        "SFactor3",         // 74
    { 0, 0, 0, 0, },         //        "SFactor4",         // 75
    { 0, 0, 0, 0, },         //        "SFormat1",         // 76
    { 0, 0, 0, 0, },         //        "SFormat2",         // 77
    { 0, 0, 0, 0, },         //        "SFormat3",         // 78
    { 0, 0, 0, 0, },         //        "SFormat4",         // 79
    { offsetof(Settings,stall),         sizeof(sets.stall),           'b', 0, },         //        "Stall",            // 80
    { 0, 0, 0, ID_of(temp), },         //        "Temperature",      // 81
    { 0, 0, 0, ID_of(throttle), },     //        "Throttle",         // 82
    { 0, 0, 0, ID_of(time), },         //        "Time",             // 83
    { offsetof(Settings,ch_toggle),     sizeof(sets.ch_toggle),       'b', 0, },         //        "Toggle Channel",   // 84
    { 0, 0, 0, ID_of(distance), },     //        "Trip Distance",    // 85
    { 0, 0, 0, ID_of(tune), },         //        "Tune",             // 86
    { 0, 0, 0, 0, },         //        "txtTime0",         // 87
    { 0, 0, 0, 0, },         //        "txtTime1",         // 88
    { 0, 0, 0, 0, },                        //        "txtTime2",         // 89
    { 0, 0, 0, 0, },                        //        "txtTime3",         // 90
    { 1, m1, 0, 0, },                       //        "Units",            // 91
    { 0, 0, 0, ID_of(vel), },               //        "Velocity",         // 92
    { 0, 0, 0, ID_of(climb), },             //        "Vertical Speed",   // 93
    { 3, m1, 0, 0, },                       //        "Video Mode",       // 94
    { 0, 0, 0, ID_of(GPS_sats), },          //        "Visible Sats",     // 95
    { offsetof(Settings,vert_offs),     sizeof(sets.vert_offs),       'b', 0, },         //        "VOS",              // 96
    { 0, 0, 0, ID_of(warn), },              //        "Warnings",         // 97
    { 0, 0, 0, ID_of(windSpeed), },         //        "Wind Speed",       // 98
    { 0, 0, 0, ID_of(WP_dir), },            //        "WP Direction",     // 99
    { 0, 0, 0, ID_of(WP_dist), },           //        "WP Distance",      // 100
    { 0, 0, 0, 0, },                        // #
    { 0, 0, 0, ID_of(Power), },             //        "Power",            // 102
    { 0, 0, 0, ID_of(fDate), },             //        "Date",             // 103
    { 0, 0, 0, ID_of(dayTime), },           //        "Time of day",      // 104
    { 0, 0, 0, ID_of(pMotor), },            //        "Motors",           // 105
    { 0, 0, 0, ID_of(fVibe), },             //        "Vibrations",       // 106
    { 0, 0, 0, ID_of(fVario), },            //        "Variometer",       // 107
    { 0, 0, 0, ID_of(coordLat), },          //        "GPS Coord Lat",    // 108
    { 0, 0, 0, ID_of(coordLon), },          //        "GPS Coord Lon",    // 109

};

static ring_buffer osd_rxrb IN_CCM;
static uint8_t osd_rx_buf[OSD_RX_BUF_SIZE] IN_CCM;

static ring_buffer osd_txrb IN_CCM;
static uint8_t osd_tx_buf[OSD_TX_BUF_SIZE] IN_CCM;

AP_HAL::OwnPtr<F4Light::SPIDevice> osd_spi;
AP_HAL::Semaphore                *osd_spi_sem;

//static volatile byte vas_vsync=false;

mavlink_system_t mavlink_system = {12,1};  // sysid, compid


#ifdef OSD_DMA_TRANSFER
 #define DMA_BUFFER_SIZE 510
    static uint8_t  dma_buffer[DMA_BUFFER_SIZE+1]; // in RAM for DMA
    static uint16_t dma_transfer_length IN_CCM;

static uint8_t shadowbuf[sizeof(OSD::osdbuf)] IN_CCM;

#endif

static bool diff_done;


extern void heartBeat();
extern void writePanels();

void On100ms();
void On20ms() {}
void osd_loop();
void vsync_ISR();
void max_do_transfer(const char *buffer, uint16_t len);

static void max7456_cs_off(){
    osd_spi->wait_busy(); // wait for transfer complete
    
    const stm32_pin_info &pp = PIN_MAP[BOARD_OSD_CS_PIN];
    gpio_write_bit(pp.gpio_device, pp.gpio_bit, HIGH);

    delay_ns100(3);
}

static void max7456_cs_on(){
    const stm32_pin_info &pp = PIN_MAP[BOARD_OSD_CS_PIN];
    gpio_write_bit(pp.gpio_device, pp.gpio_bit, LOW);
    delay_ns100(1);
}


static uint32_t sem_count=0;

void max7456_on(){
    
    max7456_cs_on();

    osd_spi->set_speed(AP_HAL::Device::SPEED_HIGH);
}

static void max7456_sem_on(){
    
    if(osd_spi_sem->take(HAL_SEMAPHORE_BLOCK_FOREVER)) {
        max7456_on();
    }
}

void max7456_off(){
    max7456_cs_off();
}

static void max7456_sem_off(){
    max7456_off();
    osd_spi_sem->give(); // give sem on last count
}

void MAX_write(byte addr, byte data){
    max7456_cs_on();
    osd_spi->transfer(addr); // this transfer don't controls CS
    osd_spi->transfer(data);
    max7456_cs_off();
}

byte MAX_read(byte addr){
    max7456_cs_on();
    osd_spi->transfer(addr);      // this transfer don't controls CS
    uint8_t ret = osd_spi->transfer(0xff);
    max7456_cs_off();
    return ret;
}

byte MAX_rw(byte b){
    max7456_cs_on();
    uint8_t ret=osd_spi->transfer(b);
    max7456_cs_off();
    return ret;
}

static uint16_t rdb_ptr IN_CCM;


#ifdef OSD_DMA_TRANSFER
static void prepare_dma_buffer(){
    uint16_t rp;
    uint16_t wp=0;

    uint8_t last_h=0xff;

//    MAX_write(MAX7456_DMM_reg, 0); 
//    MAX_write(MAX7456_VM1_reg, B01000111); 

    memset(dma_buffer,0xff,sizeof(dma_buffer));

    dma_buffer[wp++] = MAX7456_DMM_reg;  dma_buffer[wp++] = 0; 
    dma_buffer[wp++] = MAX7456_VM1_reg;  dma_buffer[wp++] = B01000111; 

    
    // сначала все изменения
    for(rp=0; rp<MAX7456_screen_size ; rp++){
        uint8_t c = OSD::osdbuf[rp];
        if(c != shadowbuf[rp] ){
            if(wp>=DMA_BUFFER_SIZE-6) break;
            uint8_t h = rp>>8;
            if(last_h != h){                
                last_h = h;
                dma_buffer[wp++] = MAX7456_DMAH_reg;  dma_buffer[wp++] = h; 
                if(wp>=DMA_BUFFER_SIZE-6) break;
            }
        
            dma_buffer[wp++] = MAX7456_DMAL_reg;  dma_buffer[wp++] = rp&0xFF; 
            dma_buffer[wp++] = MAX7456_DMDI_reg;  dma_buffer[wp++] = c; 
            shadowbuf[rp] = c;
        }
    }
    
    // а в оставшееся место все остальное по кольцу. таким образом пересылка у нас всегда 500 байт, и на частоте 4.5МГц занимает ~1ms. 
    // длинные пересылки имеют низкий приоритет, и никому не мешают
    while(wp<DMA_BUFFER_SIZE-6){
            uint8_t c = OSD::osdbuf[rdb_ptr];
            uint8_t h = rdb_ptr>>8;
            if(last_h != h){                
                last_h = h;
                dma_buffer[wp++] = MAX7456_DMAH_reg;  dma_buffer[wp++] = h; 
                if(wp>=DMA_BUFFER_SIZE-6) break;
            }
        
            dma_buffer[wp++] = MAX7456_DMAL_reg;  dma_buffer[wp++] = rdb_ptr&0xFF; 
            dma_buffer[wp++] = MAX7456_DMDI_reg;  dma_buffer[wp++] = c; 
            shadowbuf[rdb_ptr] = c;
            rdb_ptr++;
            if(rdb_ptr >= MAX7456_screen_size) rdb_ptr=0; // loop
    }
    
//    dma_buffer[wp++] = MAX7456_VM0_reg;  dma_buffer[wp++] = MAX7456_ENABLE_display | MAX7456_SYNC_autosync | OSD::video_mode; 
        
    dma_transfer_length = wp;
    diff_done = true;
}
#endif


uint32_t get_word(char *buf, char * &ptr){
    uint32_t sel_len=0;
    uint8_t  sel_id=0;
    
    for(uint32_t i=0; i<ARRAY_SIZE(words); i++){
        uint32_t len=strlen(words[i]);
        if(strncmp(buf, words[i],len)==0){
            if(len > sel_len) { // longest match
                sel_len = len;
                sel_id = i+1;
            }
        }
    }
    ptr=buf+sel_len;
    return sel_id;
}

char * get_lex(char * &ptro){
    char *ptr;
    char *buf = ptro;
    while(*buf && (*buf=='\t' || *buf == ' ')) buf++;
    ptr=buf;
    while(*ptr && *ptr!='\t') ptr++;
    if(*ptr==0) {
        ptro=NULL;
    } else {
        *ptr=0;
        ptro=ptr+1;
    }
    return buf;
    
}

static bool get_flag(char *p) {
    if(!p) return false;
    
    if(*p=='T' || *p=='t' || *p=='1') return true;
    return false;
}

//                          x,         y,           vis,          sign,    Altf,        Alt2,        Alt3,          Alt4,     strings
//                         30         15          False            0       1            1             1              1          A||||
static point create_point(char *px,   char *py, char *pVis,  char *pSign, char *pAlt,  char *pAlt2, char *pAlt3, char *pAlt4,  char *ps ){
    point p;
    p.x = strtoul(px, nullptr, 10);
    p.y = strtoul(py, nullptr, 10);
    p = do_on(p,   get_flag(pVis));
    p = do_sign(p, get_flag(pSign));
    if(get_flag(pAlt))  p=do_alt(p);
    if(get_flag(pAlt2)) p=do_alt2(p);
    if(get_flag(pAlt3)) p=do_alt3(p);
    if(get_flag(pAlt4)) p=do_alt4(p);

//    if(ps) collect_strings(ps); not supported
    return p;
}

#define write_point(n,p) eeprom_write_len((byte *)&p,  OffsetBITpanel * (int)panel_num + n * sizeof(Point),  sizeof(Point) );

static void load_config(){
    File fd = SD.open("eeprom.osd", FILE_READ);
    if (fd) {
        printf("\nLoading OSD config\n");
        char buf[80];
//        memset(buf, 0, sizeof(buf));
        uint32_t panel_num=-1;
        bool is_config=false;
        
        while(fd.gets(buf, sizeof(buf)-1) > 0) {
            // we readed one line
            char *ptr;
            char *p[10];

            uint8_t word=get_word(buf,ptr);
            switch(word) {
            case 0: // not found
            case 101: // #
                continue;
                
            case 47: { // panel
                    char *p2 = get_lex(ptr);
                    panel_num=strtoul(p2, nullptr, 10);
                    uint16_t flags=strtoul(ptr, nullptr, 10);
                    write_point(0,flags);
                }break;
                
            case 19: // config
                is_config=true;
                break;
                
            default:
                char **pp = p;
                memset(p,0,sizeof(p));
                do {
                    *pp++ = get_lex(ptr);
                }while(ptr);
                
                                
                if(is_config){
                    if(pan_tbl[word].size == (uint8_t)-1){ // bit flags
                        uint32_t flags = sets.flags.dw;
                        if(get_flag(p[0])) flags |=  (1<<pan_tbl[word].dst);
                        else               flags &= ~(1<<pan_tbl[word].dst);
                        sets.flags.dw=flags;
                    } else {
                        union {
                            float f;
                            uint8_t b;
                            char buf[8];
                            uint16_t w;
                        } val;
                        
                        switch(pan_tbl[word].fmt){
                        case 'f': // float
                            val.f = atof(p[0]);
                            break;
                        case 'b': // byte
                            val.b=(uint8_t)strtoul(p[0], nullptr, 10);
                            break;
                        case 'c': // char
                            strncpy(val.buf, p[0], 8);
                            break;
                        case 'w':
                            val.w=(uint16_t)strtoul(p[0], nullptr, 10);
                            break;                    
                        default:
                            continue; // ignore this line
                        }                    
                        memmove(         ((uint8_t*)(&sets)) + pan_tbl[word].dst, &val, pan_tbl[word].size);
                        eeprom_write_len(((uint8_t*)(&sets)) + pan_tbl[word].dst, EEPROM_offs(sets) + pan_tbl[word].dst,  pan_tbl[word].size);
                    }
                }else{ // panel
                    uint8_t id = pan_tbl[word].pan_n;
                    if(id) {
                //                           30    15    False   0     1     1     1     1   A||||
                        point po=create_point(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8] );
                    
                        write_point(id, po); // save to eeprom
                    }
                }
                
                break;
            }
            
        }
        fd.close();

        // we don't save flags when parsing so lets do it now
        eeprom_write_len( &sets.flags.pad[0],  EEPROM_offs(sets) + ((byte *)&sets.flags.pad - (byte *)&sets),  sizeof(sets.flags.pad));

        readSettings(); // re-read new values back, for settings that not in config.osd get all-1 state
        
        sets.CHK1_VERSION = VER;        // set version - EEPROM OK
        sets.CHK2_VERSION = (VER ^ 0x55);
        eeprom_write_len( &sets.CHK1_VERSION,  EEPROM_offs(sets) + ((byte *)&sets.CHK1_VERSION - (byte *)&sets),  2 );
    }
}

static void load_font(){
    const char font[]="font.mcm";
    File fd = SD.open(font, FILE_READ);
    if (fd) {
        char buf[80];
        
        if(fd.gets(buf, sizeof(buf)-1)){
            printf("\nLoading OSD font\n");

            OSD::setPanel(5, 5);
            osd_print_S(PSTR("font uploading "));
            OSD::update();// Show it
            
            char patt[]="MAX7456";
            
            if(strncmp(buf,patt,strlen(patt))==0){
                uint8_t character_bitmap[0x40];
                
                uint16_t font_count = 0;
                byte byte_count = 0;
                byte bit_count=0;

                uint8_t chk=0;
                uint8_t c=0;
                uint8_t last_c;

                uint8_t b=0;

                uint8_t cnt=0;
                while(font_count < 256) {
                    last_c = c;
                    if(fd.read(&c,1)<=0) break; // get a char
                                            
                    switch(c){ // parse and decode mcm file
                    case 0x0A: // line feed - skip
                        if(last_c == 0x0d) continue;
                        // lf without cr cause line end

                    case 0x0d: // carridge return, end of line
                        if (bit_count == 8)  {
                            chk ^= b;
                            character_bitmap[byte_count] = b;
                            b = 0;
                            byte_count++;
                        }
                        bit_count = 0;
                        break;

                    case 0x30: // ascii '0'
                    case 0x31: // ascii '1' 
                        b <<= 1;
                        if(c == 0x31)
                            b += 1;
                        bit_count++;

                        break;

                    default:
                        break;
                    }
            
                    // we have one completed character
                    // write the character to NVM 
                    if(byte_count == 64) {
                        osd.write_NVM(font_count, character_bitmap);
                        byte_count = 0;
                        font_count++;
                        printf(".");
                        chk=0;
                    }                    
                }
                printf("done\n");
            }                    
        }

        fd.close();
//* not in debug mode
        SD.remove(font); // once!
//*/
    }

}

static bool osd_need_redraw = false;
static void * task_handle;

// slowly write all buffer
static void write_buff_to_MAX(bool all){

    max7456_on();
    MAX_write(MAX7456_DMM_reg, 0); 

    // clear internal memory
    uint8_t old_h=0xff;
    for(uint16_t len = MAX7456_screen_size, cnt=0;len--; cnt++){
        uint8_t c= OSD::osdbuf[cnt];
        if(all || c!=0x20) {
            max7456_cs_on();
            uint8_t h = cnt>>8;
            if(old_h!=h){
                MAX_write(MAX7456_DMAH_reg, h);
                old_h = h;
            }
            MAX_write(MAX7456_DMAL_reg, cnt&0xFF);
            MAX_write(MAX7456_DMDI_reg, c);
            max7456_cs_off();
        }
#ifdef OSD_DMA_TRANSFER
        shadowbuf[cnt] = c;
#endif
    }
    max7456_off();
}



void osd_begin(AP_HAL::OwnPtr<F4Light::SPIDevice> spi){

    osd_spi = std::move(spi);
    
    osd_spi_sem = osd_spi->get_semaphore(); // bus semaphore
    {
        const stm32_pin_info &pp = PIN_MAP[BOARD_OSD_CS_PIN];
        gpio_set_mode(pp.gpio_device, pp.gpio_bit, GPIO_OUTPUT_PP);
        gpio_set_speed(pp.gpio_device, pp.gpio_bit, GPIO_speed_100MHz); 
        gpio_write_bit(pp.gpio_device, pp.gpio_bit, HIGH);
    }

#ifdef BOARD_OSD_RESET_PIN
    {
        const stm32_pin_info &pp = PIN_MAP[BOARD_OSD_RESET_PIN];
        gpio_set_mode(pp.gpio_device, pp.gpio_bit, GPIO_OUTPUT_PP);
        gpio_set_speed(pp.gpio_device, pp.gpio_bit, GPIO_speed_25MHz); 
        gpio_write_bit(pp.gpio_device, pp.gpio_bit, LOW);
        delayMicroseconds(50);
        gpio_write_bit(pp.gpio_device, pp.gpio_bit, HIGH);
        delayMicroseconds(120);
    }
#endif


    rb_init(&osd_rxrb, OSD_RX_BUF_SIZE, osd_rx_buf);
    rb_init(&osd_txrb, OSD_TX_BUF_SIZE, osd_tx_buf);

    OSD_EEPROM::init();

    // clear memory
    memset(OSD::osdbuf,0x20, sizeof(OSD::osdbuf));
#ifdef OSD_DMA_TRANSFER
    memset(shadowbuf,  0x20, sizeof(shadowbuf));
#endif

/*
    lets try to load settings from SD card
*/
    load_config();

    readSettings();

    doScreenSwitch(); // set vars for startup screen

    if( sets.CHK1_VERSION != VER || sets.CHK2_VERSION != (VER ^ 0x55)) { // wrong version
        lflags.bad_config=1;
        
        // some useful defaults
        sets.OSD_BRIGHTNESS = 2;
        sets.horiz_offs = 0x20;
        sets.vert_offs  = 0x10;
        
    }

    while(millis()<1000) { // delay initialization until video stabilizes
        hal_yield(1000);
    }

    max7456_sem_on();

    write_buff_to_MAX(true);


    for(uint8_t i=0; i<100; i++) {
        osd.init();    // Start display
        
        max7456_on();
        uint8_t vm0 = MAX_read(MAX7456_VM0_reg | MAX7456_reg_read); // check register
        max7456_off();

        uint8_t patt = MAX7456_ENABLE_display | MAX7456_SYNC_autosync | OSD::video_mode;

        if(vm0==patt) break;
    }

    max7456_off();

    load_font();

    max7456_sem_off();

#define REL_1 int(RELEASE_NUM/100)
#define REL_2 int((RELEASE_NUM - REL_1*100 )/10) 
#define REL_3 int((RELEASE_NUM - REL_1*100 - REL_2*10  )) 

    if(sets.FW_VERSION[0]!=(REL_1 + '0') || sets.FW_VERSION[1]!=(REL_2 + '0') || sets.FW_VERSION[2]!=(REL_3 + '0') ){
        sets.FW_VERSION[0]=REL_1 + '0';
        sets.FW_VERSION[1]=REL_2 + '0';
        sets.FW_VERSION[2]=REL_3 + '0';

        eeprom_write_len( sets.FW_VERSION,  EEPROM_offs(sets) + ((uint8_t *)sets.FW_VERSION - (uint8_t *)&sets),  sizeof(sets.FW_VERSION) );
    }
    
    logo();

    
#ifdef BOARD_OSD_VSYNC_PIN
    Revo_hal_handler h = { .vp = vsync_ISR };
    
    GPIO::_attach_interrupt(BOARD_OSD_VSYNC_PIN, h.h, RISING, VSI_INT_PRIORITY);
#endif

    task_handle = Scheduler::start_task(OSDns::osd_loop, SMALL_TASK_STACK); // 
    Scheduler::set_task_priority(task_handle, OSD_LOW_PRIORITY); // less than main task
    Scheduler::set_task_period(task_handle, 10000);              // 100Hz 
}

// all task is in one thread so no sync required

void osd_loop() {
    if(osd_need_redraw){ // если была отложенная передача
        osd_need_redraw=false;
        
        OSD::update();           
        Scheduler::set_task_priority(task_handle, OSD_LOW_PRIORITY); // restore priority to low
    }

    uint32_t pt=millis();

    seconds = pt / 1000;

    osd_dequeue(); // we MUST parse input even in case  of bad config because it is the only way to communicate

    if(pt < BOOTTIME || lflags.bad_config){ // startup delay for fonts or EEPROM error
            logo();
            return;
    }

#if defined(MAV_REQUEST) && (defined(USE_MAVLINK) || defined(USE_MAVLINKPX4))
    if(apm_mav_system && !lflags.mav_request_done){ // we got HEARTBEAT packet and still don't send requests
        for(uint8_t n = 3; n >0; n--){
            request_mavlink_rates();        //Three times to certify it will be readed
            delay_150();
        }
        lflags.mav_request_done=1;
    }
#endif

    if(lflags.got_data){ // if new data comes

        pan_toggle(); // check for screen toggle

        if(!lflags.need_redraw) {
            lflags.need_redraw=1;
            vsync_wait=1; // will wait for interrupt
        }

        lflags.got_data=0; // data parsed
    }
    
    if( lflags.need_redraw) {                 
        lflags.need_redraw=0; // screen drawn

        setHomeVars();   // calculate and set Distance from home and Direction to home

        setFdataVars();  // statistics and min/max 

        writePanels();   // writing enabled panels (check OSD_Panels Tab)

#ifdef OSD_DMA_TRANSFER
        prepare_dma_buffer(); // prepare diff with addresses
#endif

        update_screen = 1; // data comes, wee need to redraw screen
    }

    if(pt > timer_20ms){
        timer_20ms+=20;
        On20ms();
        
        if(update_screen && vsync_wait && (millis() - vsync_time)>50){ // interrupts stopped - more than 50 ms passed from the last one
            vsync_wait=0; // хватит ждать
            Scheduler::set_task_priority(task_handle, OSD_HIGH_PRIORITY); // equal to main 
            OSD::update(); // update compulsorily (and then update every 20ms)
            update_screen = 0;
            Scheduler::set_task_priority(task_handle, OSD_LOW_PRIORITY);
        }
    }

    if(pt > timer_100ms){
        timer_100ms+= 100;
        On100ms(); 

        lflags.flag_01s = !lflags.flag_01s;

        if(lflags.flag_01s) {

            if(skip_inc) {
                skip_inc++;

                if(skip_inc >=3){
                    count02s++;
                    skip_inc=0; // we go again
                }

            } else
                count02s++;
        }
//      count01s++;
    }

    if(pt > timer_500ms){
        timer_500ms+= 500;
        lflags.got_data=1; // every half second forcibly
        update_screen = 1; 

        lflags.flag_05s = 1;

        count05s++;

        lflags.blinker = !lflags.blinker;
        if(lflags.blinker) {
    //        seconds++;
            lflags.one_sec_timer_switch = 1; // for warnings

            if(lflags.got_date) day_seconds++; // if we has GPS time - let it ticks

            if( vas_vsync && vsync_count < 5) { // at a frame rate they should be 25 or 50
                                                    // but there are boards where this pin is not connected. China...
                max7456_err_count++;
                if(max7456_err_count>3) { // 3 seconds bad sync
#ifdef DEBUG   
                    printf(PSTR("restart MAX! vsync_count=%d\n"),vsync_count);
#endif
                    osd.reset();    // restart MAX7456
                }
            } else  max7456_err_count=0;

            vsync_count=0;

            heartBeat();
        }
    }
}

float avgRSSI(uint16_t d){
    static uint8_t ind = -1;
    static uint16_t RSSI_rawArray[8];

    RSSI_rawArray[(++ind)%8] = d;
    d=0;
    for (uint8_t i=8;i!=0;)
        d += RSSI_rawArray[--i];

    return d/8.0;
}

void On100ms() {

        byte ch = sets.RSSI_raw / 2;

        uint16_t d;


//DBG_PRINTF("\n RSSI ch=%d ", ch);

        switch(ch) {

        case 1: // analog RSSI from pin
        case 2: // digital RSSI from pin

        case 0:
            d=osd_rssi; // mavlink
            goto case_4;

        case 3: // 3dr modem rssi
            d=telem_rssi;
            goto case_4;

        case 4:
        default:
            d = chan_raw[7]; // ch 8

//DBG_PRINTF("ch8_rssi=%d\n", d );

case_4:
            rssi_in = avgRSSI(d);

// RSSI source is not pin so we can read it for sensor
#if defined(USE_SENSORS)
            if(SENSOR4_ON) {
                if(fPulseSensor[3])
                    d=pulseIn(RssiPin,HIGH,10000);
                else
                    d=analogRead(RssiPin);

                sensorData[3] = (sensorData[3]*7 + d) /8;
            }
#endif
            break;
        }
}


void vsync_ISR(){
    vas_vsync=true;
    vsync_wait=0;       // note its presence

    vsync_count++; // count VSYNC interrupts
    vsync_time=millis(); // and note a time

    if(update_screen) { // there is data for screen
        osd_need_redraw=true;
        Scheduler::set_task_priority(task_handle, OSD_HIGH_PRIORITY); // higher than all drivers so it will be scheduled just after semaphore release
        Scheduler::task_resume(task_handle); // task should be finished at this time so resume it
        update_screen = 0;
    }
}



// is there any chars in ring buffer?
int16_t osd_available(){
    return rb_full_count(&osd_rxrb);
}

void osd_queue(uint8_t c) {    // push bytes from OSD to FC around in the ring buffer
    uint8_t cnt=10;
    while(rb_is_full(&osd_rxrb)) {
        hal_yield(0);
        if(--cnt == 0) return; // destination don't listen
    }
    rb_push_insert(&osd_rxrb, c);
}


int16_t osd_getc(){ // get char from ring buffer
    return rb_remove(&osd_rxrb);
}

uint32_t osd_txspace() {
    return osd_txrb.size - rb_full_count(&osd_txrb);
}


void osd_putc(uint8_t c){ 
    uint8_t cnt=10;
    while(rb_is_full(&osd_txrb)) {
        Scheduler::set_task_priority(task_handle, OSD_HIGH_PRIORITY); // to run in time of yield()
        hal_yield(0);
        if(--cnt == 0) break; // destination don't listen
    }
    rb_push_insert(&osd_txrb, c);
    Scheduler::set_task_priority(task_handle, OSD_LOW_PRIORITY); // restore priority to low
}

void osd_dequeue() {
    Scheduler::set_task_priority(task_handle, 100); // equal to main to not overflow buffers on packet decode

    while(!rb_is_empty(&osd_txrb)) {
        extern bool mavlink_one_byte(char c);
        char c = rb_remove(&osd_txrb);
    
        if(mavlink_one_byte(c)) lflags.got_data=true;
    }
    Scheduler::set_task_priority(task_handle, OSD_LOW_PRIORITY); // restore priority to low

}


static uint8_t max_err_cnt=0;

void update_max_buffer(const uint8_t *buffer, uint16_t len){
    max7456_sem_on();

    uint16_t cnt=0;
    
    
#if 1
    uint8_t patt = MAX7456_ENABLE_display | MAX7456_SYNC_autosync | OSD::video_mode;
    max7456_cs_on();
    uint8_t vm0 = MAX_read(MAX7456_VM0_reg | MAX7456_reg_read);
        
    if(vm0 != patt) {
        max_err_cnt++;
        if(max_err_cnt<3) {
            OSD::hw_init(); // first try without reset
        } else {
                // 3 errors together - nothing helps :(
#ifdef BOARD_OSD_RESET_PIN
            {
                const stm32_pin_info &pp = PIN_MAP[BOARD_OSD_RESET_PIN];
                gpio_write_bit(pp.gpio_device, pp.gpio_bit, LOW);
                delayMicroseconds(50);
                gpio_write_bit(pp.gpio_device, pp.gpio_bit, HIGH);
                delayMicroseconds(120);
            }
#endif
            OSD::init();
            max_err_cnt=0; 
        }
    } else {
        max_err_cnt=0;
    }

    MAX_write(MAX7456_DMAH_reg, 0);
    MAX_write(MAX7456_DMAL_reg, 0);
    MAX_write(MAX7456_DMM_reg, 1); // set address auto-increment

    max7456_cs_off();
    
    if(osd_spi->send_strobe(buffer, len)!=len) {
    /*/// for debug - mark last written char
        MAX_rw(0x86); // finish transfer
    //*///
        MAX_rw(0xff); // finish transfer
    }
    
#elif 0
    MAX_write(MAX7456_DMAH_reg, 0);
    MAX_write(MAX7456_DMAL_reg, 0);
    MAX_write(MAX7456_DMM_reg, 0); 

// try to send just diffenence - don't clears last chars
    uint8_t last_h=0;
    while(len--){
        if(*buffer != shadowbuf[cnt]){
            uint8_t h = cnt>>8 ;
            if(last_h != h){
                MAX_write(MAX7456_DMAH_reg, h);
                last_h = h;
            }
            MAX_write(MAX7456_DMAL_reg, cnt&0xFF);
            MAX_write(MAX7456_DMDI_reg, *buffer);
            shadowbuf[cnt] = *buffer;
        }
        buffer++;
        cnt++;
    }

#elif 0

// a try to do writes in software strobe mode
    MAX_write(MAX7456_DMAH_reg, 0);
    MAX_write(MAX7456_DMAL_reg, 0);
    MAX_write(MAX7456_DMM_reg, 1); // set address auto-increment
    max7456_cs_off();
    while(len--){
        max7456_cs_on();
        // osd_spi->transfer(*buffer++); MAX7456
        MAX_rw(*buffer++);
        buffer++;
        cnt++;
        osd_spi->wait_busy();
        max7456_cs_off();
    }
    max7456_cs_on();
    MAX_write(MAX7456_DMM_reg, 0); // clear address auto-increment
#else
// just write all to MAX
    MAX_write(MAX7456_DMAH_reg, 0);
    MAX_write(MAX7456_DMAL_reg, 0);
    MAX_write(MAX7456_DMM_reg, 0); 

    uint8_t last_h=0;
    while(len--){
        uint8_t h = cnt>>8 ;
        if(last_h != h){
            MAX_write(MAX7456_DMAH_reg, h);
            last_h = h;
        }
        
        MAX_write(MAX7456_DMAL_reg, cnt&0xFF);
        MAX_write(MAX7456_DMDI_reg, *buffer);
        buffer++;
        cnt++;
    }

#endif    

    max7456_sem_off();
}



} // namespace


#endif