Jon Freeman / Mbed 2 deprecated Alternator2020_06

Dependencies:   mbed BufferedSerial Servo2 PCT2075 I2CEeprom FastPWM

i2c_bit_banged.cpp

Committer:
JonFreeman
Date:
2019-06-28
Revision:
0:77803b3ee157
Child:
1:450090bdb6f4

File content as of revision 0:77803b3ee157:

#include "mbed.h"
#include "Alternator.h"
extern  Serial  pc;
DigitalInOut    SDA (D4);       //  Horrible bodge to get i2c working using bit banging.
DigitalInOut    SCL (D5);       //  DigitalInOut do not work as you might expect. Fine if used only as OpenDrain opuputs though!
DigitalIn       SDA_IN  (A4);   //  That means paralleling up with two other pins as inputs
DigitalIn       SCL_IN  (A5);   //  This works but is a pain. Inbuilt I2C should have worked but never does on small boards with 32 pin cpu.

const int _24LC_rd = 0xa1;  //  set bit 0 for read, clear bit 0 for write
const int _24LC_wr = 0xa0;  //  set bit 0 for read, clear bit 0 for write
const int ACK     = 0;  //  but acknowledge is 0, NAK is 1


/*struct  optpar  {
    int min, max, def;  //  min, max, default
    const char * t;     //  description
}   ;*/
struct  optpar option_list2[] = {
    {0, 100, 10, "max pwm% @ Eng RPM 0, 0 to 100"},
    {0, 100, 10, "max pwm% @ Eng RPM 1000, 0 to 100"},
    {0, 100, 20, "max pwm% @ Eng RPM 2000, 0 to 100"},
    {0, 100, 30, "max pwm% @ Eng RPM 3000, 0 to 100"},
    {0, 100, 40, "max pwm% @ Eng RPM 4000, 0 to 100"},
    {0, 100, 50, "max pwm% @ Eng RPM 5000, 0 to 100"},
    {0, 100, 50, "max pwm% @ Eng RPM 6000, 0 to 100"},
    {0, 100, 50, "max pwm% @ Eng RPM 7000, 0 to 100"},
    {0, 100, 50, "max pwm% @ Eng RPM 8000, 0 to 100"},
    {0, 100, 50, "Set Overall PWM Scale Factor percent"},
    {0, 100, 0, "Future 2"},
    {0, 100, 0, "Future 3"},
    {0, 100, 0, "Future 4"},
    {0, 100, 0, "Future 5"},
}   ;

const   int    numof_eeprom_options2    = sizeof(option_list2) / sizeof (struct optpar);

bool    wr_24LC64  (int start_addr, char * source, int length)   ;  //  think this works
bool    rd_24LC64  (int start_addr, char * source, int length)   ;  //  think this works



eeprom_settings mode  ;

eeprom_settings::eeprom_settings    ()  {}

bool    eeprom_settings::set_defaults   ()  {
    for (int i = 0; i < numof_eeprom_options2; i++)
        settings[i] = option_list2[i].def;       //  Load defaults and 'Save Settings'
    return  save    ();
}

char    eeprom_settings::rd  (uint32_t i)  {           //  Read one setup char value from private buffer 'settings'
    if  (i > 31)    {
        pc.printf   ("ERROR Attempt to read setting %d\r\n", i);
        return  0;
    }
    return  settings[i];
}

bool    eeprom_settings::wr  (char c, uint32_t i)  {           //  Read one setup char value from private buffer 'settings'
    if  (i > 31)
        return  false;
    settings[i] = c;
    return  true;
}

int     eeprom_settings::get_pwm (int rpm)   {
    int p = rpm * lut_size;
    p /= 8000;  //  8000 is upper RPM limit
    if  (p < 0) p = 0;                    //  point to first
    if  (p >= lut_size) p = lut_size - 1; //  point to last
//    pc.printf   ("In get_pwm, rpm = %d, lut entry = %d, pwm = %d\r\n", rpm, p, max_pwm_lut[p]);
    return  max_pwm_lut[p];
}

void    eeprom_settings::build_lut   ()  {
    int ptr = 0;
    int range, i;
    int base = mode.rd(RPM0) * PWM_PERIOD_US;
    double  acc, incr;
    base /= 100;    //  got pwm_pulsewidth of 0 RPM
    acc = (double) base;
    pc.printf   ("pwm_period_us ar 0 RPM = %d\r\n", base);
    for (i = 0; i < 8; i++) {
        range = mode.rd(i+1) - mode.rd(i);  //  range now change in percent between two 'n'000 RPMs
        range *= mode.rd(PWM_SCALE);        //  range now 10000 times factor due to percentage twice
        range *= PWM_PERIOD_US;
        incr = (double)range;
        incr /= 10000.0;
        incr /= lut_seg_size;
        for(int j = 0; j < lut_seg_size; j++)   {
            max_pwm_lut[ptr++] = (int)acc;
            acc += incr;
        }
    }
    max_pwm_lut[ptr] = (int)acc;
    pc.printf   ("At end of build_lut ptr=%d\r\n", ptr);
    range = 0;
//    while   (range < ptr)   {
//        for (i = 0; i < 10; i++)    {
//            pc.printf   ("%d\t", max_pwm_lut[range++]);
//        }
//        pc.printf   ("\r\n");
//    }
    pc.printf   ("lut_size = %d\r\n", lut_size);
}

bool    eeprom_settings::load    ()  {               //  Get 'settings' buffer from EEPROM
    bool    rv  ;
    rv =    rd_24LC64   (eeprom_page * 32, settings, 32);   //  Can now build lookup table
    build_lut   ();
    return  rv;
}

bool    eeprom_settings::save    ()  {               //  Write 'settings' buffer to EEPROM
    return  wr_24LC64   (eeprom_page * 32, settings, 32);
}



/**
*   bool    i2c_init(void) {
*
*   Init function. Needs to be called once in the beginning.
*   Returns false if SDA or SCL are low, which probably means 
*   a I2C bus lockup or that the lines are not pulled up.
*/
bool    i2c_init(void) {
    SDA.output();
    SCL.output();
    SDA.mode(OpenDrain);
    SCL.mode(OpenDrain);    //  Device may pull clock lo to indicate to master
    SDA = 0;
    SCL = 0;
    wait_us   (1);
    SDA = 1;
    wait_us (1);
    SCL = 1;
    wait_us (1);
    if  (SCL_IN == 0 || SDA_IN == 0)    return  false;
  return true;
}

/**
*   During data transfer, the data line must remain 
*   stable whenever the clock line is high. Changes in 
*   the data line while the clock line is high will be 
*   interpreted as a Start or Stop condition
*
*   A high-to-low transition of the SDA line while the clock
*   (SCL)   is   high   determines   a   Start   condition.   All
*   commands must be preceded by a Start condition.
*/
int i2c_start  ()  {   // Should be Both hi, start takes SDA low
    int    rv = 0;
    if  (SDA_IN == 0 )  {
        rv |= 1;    //  Fault - SDA was lo on entry
        SDA = 1;
        wait_us (1);
    }
    if  (SCL == 0 )  {
        rv |= 2;    //  Fault - SCL was lo on entry
        SCL = 1;
        wait_us (1);
    }
    SDA = 0;                    //  Take SDA lo
    wait_us (1);
    SCL = 0;
    wait_us (1);
    return  rv;     //  Returns 0 on success, 1 with SDA fault, 2 with SCL fault, 3 with SDA and SCL fault
}

/**
*   During data transfer, the data line must remain 
*   stable whenever the clock line is high. Changes in 
*   the data line while the clock line is high will be 
*   interpreted as a Start or Stop condition
*
*   A low-to-high transition of the SDA line while the clock
*   (SCL)   is   high   determines   a   Stop   condition.   All
*   operations must be ended with a Stop condition.
*/
int i2c_stop  ()  {   //  Should be SDA=0, SCL=1, start takes SDA hi
    int    rv = 0;
    SDA = 0;    //  Pull SDA to 0
    wait_us (1);
    if  (SCL_IN != 0)   {
        pc.printf   ("SCL 1 on entry to stop\r\n");
        SCL = 0;    //  pull SCL to 0 if not there already
        wait_us (1);
    }
    SCL = 1;
    wait_us (1);
    if  (SCL_IN == 0)
        pc.printf   ("SCL stuck lo in stop\r\n");
    SDA = 1;
    wait_us (1);
    if  (SDA_IN == 0)
        pc.printf   ("SDA stuck lo in stop\r\n");
    return  rv;     //  Returns 0 on success, 1 with SDA fault, 2 with SCL fault, 3 with SDA and SCL fault
}

void    jclk    (int bit)   {
    SCL = bit;
    wait_us (1);
}

void    jclkout ()  {
    wait_us (1);
    SCL = 1;
    wait_us (1);
    SCL = 0;
    wait_us (1);
}

int i2c_write (int    d)  {
    int ackbit = 0;
    if  (SCL_IN != 0)   {
        pc.printf   ("SCL hi on entry to write\r\n");
        jclk    (0);
    }
    for (int i = 0x80; i != 0; i >>= 1) {        //  bit out msb first
        if  ((d & i) == 0)  SDA = 0;
        else                SDA = 1;
        jclkout ();     //  SCL ____---____
    }
    SDA = 1;    //  Release data to allow remote device to pull lo for ACK or not
    jclk    (1);    //  SCL = 1
    ackbit = SDA_IN;   //  read in ack bit
    jclk    (0);    //  SCL = 0
//    pc.printf   ("wr 0x%x %s\r\n", d, ackbit == 0 ? "ACK" : "nak");
    return  ackbit; //      0 for acknowledged ACK, 1 for NAK
}




int i2c_read    (int acknak)  {     //  acknak  indicates if the byte is to be acknowledged (0 = acknowledge)
    int result = 0;                 //  SCL should be 1 on entry
    SDA = 1;        //  Master released SDA
    if  (SCL_IN != 0)   pc.printf   ("SCL hi arriving at read\r\n");
    wait_us (2);
    for (int i = 0; i < 8; i++) {
        result <<= 1;
        jclk    (1);
        if  (SDA_IN != 0)  result |= 1;
        jclk    (0);
    }
    if  (acknak != 0 && acknak != 1)
        pc.printf   ("Bad acknak in 12c_read %d\r\n", acknak);
    if  (acknak == 0)   SDA = 0;
    else                SDA = 1;
    jclkout ();    //  clock out the ACK bit __--__
//    pc.printf   ("rd 0x%x %s\r\n", result, acknak == 0 ? "ACK" : "nak");
    return  result;             //  Always ? nah
}

int check_24LC64   ()  {     //  Call from near top of main() to init i2c bus
    int last_found = 0, q, e;      //  Note address bits 3-1 to match addr pins on device
    for (int i = 0; i < 255; i += 2)    {   //  Search for devices at all possible i2c addresses
        e = i2c_start();
        if  (e) pc.putc(',');
        q = i2c_write(i);   //  may return error code 2 when no start issued
        if  (q == ACK)  {
            pc.printf   ("I2C device found at 0x%x\r\n", i);
            last_found = i;
            wait_ms (5);
        }
        i2c_stop();
    }
    return  last_found;
}








bool    ack_poll    ()  {   //  wait short while for any previous memory operation to complete
    const int poll_tries    = 40;
    int poll_count = 0;
    bool    i2cfree = false;
    while   (poll_count++ < poll_tries && !i2cfree)  {
        i2c_start   ();
        if  (i2c_write(_24LC_wr) == ACK)
            i2cfree = true;
        else
            wait_ms   (1);
    }
//    pc.printf   ("ack_poll, count = %d, i2cfree = %s\r\n", poll_count, i2cfree ? "true" : "false");
    return  i2cfree;
}

/**bool    set_24LC64_internal_address (int    start_addr)   {
*
*
*
*/
bool    set_24LC64_internal_address (int    start_addr)   {
    if  (!ack_poll())
    {
        pc.printf   ("Err in set_24LC64_internal_address, no ACK writing device address byte\r\n");
        i2c_stop();
        return  false;
    }
    int err = 0;
    if  (i2c_write(start_addr >> 8)   != ACK) err++;
    if  (i2c_write(start_addr & 0xff) != ACK) err++;
    if  (err)   {
        pc.printf   ("In set_24LC64_internal_address, Believe Device present, failed in writing 2 mem addr bytes %d\r\n", err);
        i2c_stop();
        return  false;
    }
//    pc.printf   ("GOOD set_24LC64_internal_address %d\r\n", start_addr);
    return  true;
}

bool    wr_24LC64  (int start_addr, char * source, int length)   {  //  think this works
    int err = 0;
    if(length < 1 || length > 32)   {
        pc.printf   ("Length out of range %d in wr_24LC64\r\n", length);
        return  false;
    }
    if  (!set_24LC64_internal_address   (start_addr))   {
        pc.printf   ("In wr_24LC64, Believe Device present, failed in writing 2 mem addr bytes %d\r\n", err);
        return  false;
    }
    while(length--) {
        err += i2c_write(*source++);
    }
    i2c_stop();
    if  (err)   {
        pc.printf   ("in wr_24LC64, device thought good, mem addr write worked, failed writing string\r\n");
        return  false;
    }
//    pc.printf   ("In wr_24LC64 No Errors Found!\r\n");
    return  true;
}

bool rd_24LC64  (int start_addr, char * dest, int length)   {
    int acknak = ACK;
    if(length < 1)
        return false;
    if  (!set_24LC64_internal_address   (start_addr))   {
        pc.printf   ("In rd_24LC64, failed to set_ramaddr\r\n");
        return  false;
    }
    i2c_start();
    if  (i2c_write(_24LC_rd) != ACK) {
        pc.printf   ("Errors in rd_24LC64 sending 24LC_rd\r\n");
        return  false;
    }
    while(length--) {
        if(length == 0)
            acknak = 1;
        *dest++ = i2c_read(acknak);
    }
    i2c_stop();
    return  true;
}