Avatar for virtualcodewarrior

virtualcodewarrior

Member since Apr 2016 • Last active Oct 2017
  • 1 conversations
  • 12 comments

Most recent activity

  • in ESP8266
    Avatar for virtualcodewarrior

    I gave up on the build-in I2C and used the following code to bit bang the I2C instead in JavaScript (code almost 1 to 1 from I2C article on wikipedia):

    function jsI2C(p_Info){
      function pinHigh(pin){ pin.mode("input"); }
      function pinLow(pin){ pin.mode("opendrain"); }
      
      function I2C_delay(){ wait(1); }
      function read_SCL(){ return p_Info.scl.read(); } // Set SCL as input and return current level of line, 0 or 1
      function read_SDA(){ return p_Info.sda.read(); } // Set SDA as input and return current level of line, 0 or 1
      function set_SCL(){ pinHigh(p_Info.scl); } // Actively drive SCL signal high
      function clear_SCL(){ pinLow(p_Info.scl); } // Actively drive SCL signal low
      function set_SDA(){ pinHigh(p_Info.sda); } // Actively drive SDA signal high
      function clear_SDA(){ pinLow(p_Info.sda); } // Actively drive SDA signal low
      function arbitration_lost(){}
    
      function wait(n){ for (var index = 0; index < n; index++){}  }
      function waitForClockStretch(){ while (!read_SCL()){ wait(1); } }
      
      var started = false; // global data
    
      function i2c_start_cond(){
        if (started) 
        { 
          // if started, do a restart cond
          // set SDA to 1
          set_SDA();
          I2C_delay();
    
          waitForClockStretch();
          
          // Repeated start setup time, minimum 4.7us
          I2C_delay();
        }
    
        if (!read_SDA()) 
        {
          arbitration_lost();
        }
    
        // SCL is high, set SDA from 1 to 0.
        clear_SDA();
        I2C_delay();
        clear_SCL();
        started = true;
      }
    
      function i2c_stop_cond(){
        // set SDA to 0
        set_SCL();
        clear_SDA();
        I2C_delay();
    
        waitForClockStretch();
    
        // Stop bit setup time, minimum 4us
        I2C_delay();
    
        // SCL is high, set SDA from 0 to 1
        set_SDA();
        I2C_delay();
    
        if (!read_SDA()){
          arbitration_lost();
        }
    
        I2C_delay();
        started = false;
    
      }
    
      // Write a bit to I2C bus
      function i2c_write_bit(bit){
        if (bit) 
        {
          set_SDA();
        } 
        else 
        {
          clear_SDA();
        }
    
        // SDA change propagation delay
        I2C_delay();  
    
        // Set SCL high to indicate a new valid SDA value is available
        set_SCL();
    
        // Wait for SDA value to be read by slave, minimum of 4us for standard mode
        I2C_delay();
    
        waitForClockStretch();
    
        // SCL is high, now data is valid
        // If SDA is high, check that nobody else is driving SDA
        if (bit && !read_SDA()){
          arbitration_lost();
        }
    
        // Clear the SCL to low in preparation for next change
        clear_SCL();
      }
    
      // Read a bit from I2C bus
      function i2c_read_bit(){
        var bit;
    
        // Let the slave drive data
        set_SDA();
    
        // Wait for SDA value to be written by slave, minimum of 4us for standard mode
        I2C_delay();
    
        // Set SCL high to indicate a new valid SDA value is available
        set_SCL();
    
        waitForClockStretch();
    
        // Wait for SDA value to be read by slave, minimum of 4us for standard mode
        I2C_delay();
    
        // SCL is high, read out bit
        bit = read_SDA();
    
        // Set SCL low in preparation for next operation
        clear_SCL();
    
        return bit;
    
      }
    
    // Write a byte to I2C bus. Return 0 if ack by the slave.
      function i2c_write_byte(send_start, send_stop, byte){
        var nack = false;
    
        if (send_start){
          i2c_start_cond();
        }
    
        for (var bit = 0; bit < 8; bit++){
          i2c_write_bit((byte & 0x80) !== 0);
          byte <<= 1;
        }
    
        nack = i2c_read_bit();
    
        if (send_stop){
          i2c_stop_cond();
        }
    
        return nack;
    
      }
    
      // Read a byte from I2C bus
      function i2c_read_byte(nack, send_stop){
        var byte = 0;
    
        for (var bit = 0; bit < 8; bit++ ) 
        {
          byte = ( byte << 1 ) | i2c_read_bit();
        }
    
        i2c_write_bit(nack);
    
        if (send_stop){
          i2c_stop_cond();
        }
    
        return byte;
      }
      
      return {
        readRegister: function(address, register, bytes){
          var result = [];
          // write address and write mode
          if (!i2c_write_byte(true, false, (address << 1) | 0)){
            // write the data to write in this case the register and write a stop
            if (!i2c_write_byte(false, true, register)){
              // write the address and read mode
              if (!i2c_write_byte(true, false, (address << 1) | 1)){
                // read the bytes
                while (bytes){
                  bytes--;
                  // if no bytes remaining send nack followed by stop
                  result.push(i2c_read_byte(bytes === 0, bytes === 0));
                }
              }
            }
          }
          return result;
        },
        write: function(address, p_Bytes){
          if (!i2c_write_byte(true, false, (address << 1) | 0)){
            // write the data to write in this case the register and write a stop
            for (var index = 0; index < p_Bytes.length; index++){
              if (i2c_write_byte(false, index == p_Bytes.length - 1, p_Bytes[index])){
                break;
              }
            }
          }
        }
      };
    }
    
    

    This code actually works with the radio module and I managed to tune the FM station and got it to play :-).

    Here is the radio code that uses my bit-banging I2C for anyone who is interested :

    // create the radio interface
    // specify SDA and SCL pins to use, will default to 4 and 5 if not set, neither pin can be 0
    function Radio(p_SDA, p_SCL){
        "use strict";
    
        var i2c = jsI2C({ sda: p_SDA || D4, scl: p_SCL || D5});
    
        var i2c_write_address = 0x10;
        var i2c_read_address =  0x11;
        var out_buffer = [];
    
        // constants : 
        var rda_CHIP_ID =  0x58, // CHIP ID
    
        // Tuning Band //
        rda_87_108MHz =   0b0000000000000000, // us band
        rda_76_91MHz =    0b0000000000000100, // japan band
        rda_76_108MHz =   0b0000000000001000, // world band we use this
        rda_65_76MHz =    0b0000000000001100, // east europe
    
        // Tuning Steps //
        rda_100kHz =      0b0000000000000000,
        rda_200kHz =      0b0000000000000001, // not US band compatible **//
        rda_50kHz =       0b0000000000000010,
        rda_25kHz =       0b0000000000000011, // we use this
        // ** 200kHz spacing works but frequencies are always out by 100kHz from US frequency slots.
        // It's not possible to tune to a US station with 200kHz spacing you must use 100kHz instead.
    
        // De-emphasis //
        rda_50us =        0b0000100000000000, // eu standard
        rda_75us =        0b0000000000000000, // us standard
    
        // REG 0x02
        rda_DHIZ =        0b1000000000000000,
        rda_DMUTE =       0b0100000000000000,
        rda_MONO =        0b0010000000000000,
        rda_BASS =        0b0001000000000000,
        rda_RCLK =        0b0000100000000000,
        rda_RCKL_DIM =    0b0000010000000000,
        rda_SEEKUP =      0b0000001000000000,
        rda_SEEK =        0b0000000100000000,
        rda_SKMODE =      0b0000000010000000, // default is to wrap around
    
        // Timing XTAL = rda_CLK_MODE //
        // we are not changing the defaults so remark
        rda_32_768kHz =   0b0000000000000000,
        rda_12MHz =       0b0000000000010000,
        rda_24MHz =       0b0000000001010000,
        rda_13MHz =       0b0000000000100000,
        rda_26MHz =       0b0000000001100000,
        rda_19_2MHz =     0b0000000000110000,
        rda_38_4MHz =     0b0000000001110000,
        rda_CLK_MODE =    0b0000000001110000, // default is 32.768kHz which is the build in crystal
        rda_RDS_EN =      0b0000000000001000,
        rda_NEW_METHOD =  0b0000000000000100,
        rda_SOFT_RESET =  0b0000000000000010,
        rda_ENABLE =      0b0000000000000001,
        // REG 0x03
        rda_CHAN =        0b1111111111000000,
        rda_DIRECT_MODE = 0b0000000000100000, // only used with test
        rda_TUNE =        0b0000000000010000,
        rda_BAND =        0b0000000000001100,
        rda_SPACE =       0b0000000000000011,
        // REG 0x04
        rda_DE =          0b0000100000000000,
        rda_SOFTMUTE_EN = 0b0000001000000000,
        rda_AFCD =        0b0000000100000000,
        // REG 0x05
        rda_INT_MODE =    0b1000000000000000,
        rda_SEEKTH =      0b0000111100000000,
        rda_VOLUME =      0b0000000000001111,
        // REG 0x06
        rda_OPEN_MODE =   0b0110000000000000,
        // REG 0x07
        rda_BLEND_TH =    0b0111110000000000,
        rda_65_50M_MODE = 0b0000001000000000,
        rda_SEEK_TH_OLD = 0b0000000011111100,
        rda_BLEND_EN =    0b0000000000000010,
        rda_FREQ_MODE =   0b0000000000000001,
        // REG 0x0A
        rda_RDSR =        0b1000000000000000,
        rda_STC =         0b0100000000000000,
        rda_SF =          0b0010000000000000,
        rda_RDSS =        0b0001000000000000,
        rda_BLK_E =       0b0000100000000000,
        rda_ST =          0b0000010000000000,
        rda_READCHAN =    0b0000001111111111,
        // REG 0x0B
        rda_RSSI =        0b1111110000000000,
        rda_FM_TRUE =     0b0000001000000000,
        rda_FM_READY =    0b0000000100000000,
        rda_ABCD_E =      0b0000000000010000,
        rda_BLERA =       0b0000000000001100,
        rda_BLERB =       0b0000000000000011;
    
        // 
        function read_chip(offset) {
            var data = i2c.readRegister(i2c_read_address, offset, 2);
            return ((data[0] << 8) & 0xff00) | (data[1] & 0x00ff);
        }
    
        function write_chip(size) {
            i2c.write(i2c_write_address, out_buffer.slice(0, size));
        }
    
        function tune(p_On){
          var data = ((out_buffer[2]<<8)|out_buffer[3])|rda_T­UNE;
          if (!p_On) { data = data^rda_TUNE; }
          out_buffer[2] = (data >> 8) & 0xff;
          out_buffer[3] = data & 0xff;
        }
        
        function readLoopChip(){
          for (var loop = 2; loop < 8; loop++) {
            var data = read_chip(loop);
            out_buffer[(loop*2)-4] = (data >> 8) & 0xff;
            out_buffer[(loop*2)-3] = data & 0xff;
          }
          tune(false); // disable tuning
        }
    
        function init_chip() {
          var data = read_chip(0);
          var found = 0;
          var result;
    
          if ((data&0xff) == rda_CHIP_ID) {
            found = 1;
          }
          data = (data >> 8)&0xff;
          if (data == rda_CHIP_ID) {
            found = 1;
          }
          if (found === 0) {
          }
          else if ((read_chip(13) == 0x5804) && (read_chip(15) == 0x5804)) {
              result = 0; 
          } // not set up
          else {
              result = 1;
          } // already used
          return result;
        }
    
        function isInitialized(){
          var state = init_chip();
          return state !== 0;
        }
    
        // radio on    
        function on(p_Callback){
          // REG 02
          // normal output, enable mute, stereo, no bass boost, clock = 32.768kHz, RDS enabled, new demod method, power on
          var data = rda_DHIZ|(rda_DMUTE&0)|(rda_MONO&0)|(rda­_BASS&0)|rda_RDS_EN|rda_NEW_METHOD|rda_E­NABLE;
          out_buffer[0] = (data >> 8) & 0xff;
          out_buffer[1] = data & 0xff;
          // REG 03 - no auto tune, 76-108 band, 0.1 spacing
          data = (rda_TUNE&0)|rda_76_108MHz|rda_100kHz;
          out_buffer[2] = (data >> 8) & 0xff;
          out_buffer[3] = data & 0xff;
          // REG 04 - audio 50us, no soft mute, disable AFC
          data = rda_50us|(rda_SOFTMUTE_EN&0)|rda_AFCD;
          out_buffer[4] = (data >> 8) & 0xff;
          out_buffer[5] = data & 0xff;
          // REG 05 - max volume
          data = rda_INT_MODE|0x0480|rda_VOLUME;
          out_buffer[6] = (data >> 8) & 0xff;
          out_buffer[7] = data & 0xff;
          // REG 06 - reserved
          out_buffer[8] = 0;
          out_buffer[9] = 0;
          // REG 07
          var blend_threshold = 0b0011110000000000; // mix L+R with falling signal strength
          data = blend_threshold|rda_65_50M_MODE|0x80|0x4­0|rda_BLEND_EN|(rda_FREQ_MODE&0);
          out_buffer[10] = (data >> 8) & 0xff;
          out_buffer[11] = data &0xff;
          write_chip(12);
    
          setTimeout(p_Callback, 200);
        }
    
        function off(){
          if (isInitialized()){
            var data = (read_chip(2)|rda_ENABLE)^rda_ENABLE;
            out_buffer[0] = (data >> 8) & 0xff;
            out_buffer[1] = data &0xff;
            write_chip(2);
          }
        }
    
        function forceMono(p_On){
          if (isInitialized()){
            var data = (read_chip(2)|rda_MONO);
            if (!p_On) { data = data^rda_MONO; }
            out_buffer[0] = (data >> 8) & 0xff;
            out_buffer[1] = data & 0xff;
            write_chip(2);
          }
        }
    
        function bassBoost(p_On){
          if (isInitialized()){
            var data = (read_chip(2)|rda_BASS);
            if (!p_On) { data = data^rda_BASS; }
            out_buffer[0] = (data >> 8) & 0xff;
            out_buffer[1] = data & 0xff;
            write_chip(2);
          }
        }
    
        function mute(p_On){
          if (isInitialized()){
            var data = (read_chip(2)|rda_DMUTE);
            if (p_On) { data = data^rda_DMUTE; }
            out_buffer[0] = (data >> 8) & 0xff;
            out_buffer[1] = data & 0xff;
            write_chip(2);
          }
        }
    
        function checkSeek(p_Callback){
          var regA = read_chip(0x0A);
    
          if (!!(regA & rda_STC)){
            p_Callback(!(regA & rda_SF));
          }
          else {
            setTimeout(function(){ checkSeek(p_Callback); }, 500);
          }
        }
    
        function seekUp(p_Callback){
          if (isInitialized()){
            var data = (read_chip(2)|rda_SEEKUP|rda_SEEK);
            out_buffer[0] = (data >> 8) & 0xff;
            out_buffer[1] = data & 0xff;
            tune(true);
    
            write_chip(2);
    
            checkSeek(p_Callback);
          }
        }
    
        function seekDown(p_Callback){
          if (isInitialized()){
            var data = (read_chip(2)|rda_SEEKUP|rda_SEEK)^rda_S­EEKUP;
            out_buffer[0] = (data >> 8) & 0xff;
            out_buffer[1] = data & 0xff;
            tune(true);
    
            write_chip(2);
    
            checkSeek(p_Callback);
          }
        }
    
        function deemphasis75(p_On){
          if (isInitialized()){
            readLoopChip();
            var data = (read_chip(4)|rda_DE)^rda_DE;
            data = (p_On) ? (data | rda_75us) : (data | rda_50us);
            out_buffer[4] = (data >> 8) & 0xff;
            out_buffer[5] = data & 0xff;
            write_chip(6);
          }
        }
    
        function volume(p_Val){
          if (isInitialized()){
            readLoopChip();
            var data = (read_chip(5)|rda_VOLUME)^rda_VOLUME;
            if (p_Val > rda_VOLUME) { p_Val = rda_VOLUME; }
            data = data|p_Val;
            out_buffer[6] = (data >> 8) & 0xff;
            out_buffer[7] = data & 0xff;
            write_chip(8);
          }
        }
    
        function setFrequency(p_Mhz){
          if (isInitialized()){
            mute(false);
            var channel = ((((p_Mhz - 76)/0.1) | 0) & 0x3FF) << 6;
            var data = (read_chip(3)|rda_CHAN)^rda_CHAN;
            data = data|channel;
            out_buffer[2] = (data >> 8) & 0xff;
            out_buffer[3] = data & 0xff;
            tune(true);
            write_chip(4);
          }
        }
    
        function getRDS() {
            // check register A
            var data = read_chip(0x0A);
            var rds = [0,0,0,0, false, false];
    
            // block E type
            rds[4] = !!(data & rda_BLK_E);
    
            // check for new RDS data available
            if (rds[4] || data & rda_RDSR) {
              rds[0] = read_chip(0x0C) & 0xffff;
              rds[1] = read_chip(0x0D) & 0xffff;
              rds[2] = read_chip(0x0E) & 0xffff;
              rds[3] = read_chip(0x0F) & 0xffff;
              rds[5] = true;
            }
            return rds;
        }
    
        function getState(){
          var reg2 = read_chip(2);
          var regA = read_chip(0x0A);
          var regB = read_chip(0x0B);
    
          return { 
              isInitialized:  isInitialized(),
              muteOn:         !(reg2 & rda_DMUTE),
              forceMonoOn:    !!(reg2 & rda_MONO),
              bassBoostOn:    !!(reg2 & rda_BASS),
              rdsOn:          !!(reg2 & rda_RDS_EN),
              de:             !read_chip(4) & rda_DE,
              volume:         read_chip(5) & rda_VOLUME,
              stereo:         !!(regA & rda_ST),
              signalStrength: ((regB & rda_RSSI)>>10),
              channel:        ((regA & 0x03ff) + 760) / 10,
              isStation:      !!(regB & rda_FM_TRUE),
              isReady:        !!(regB & rda_FM_READY),
              seekComplete:   !!(regA & rda_STC),
              seekFail:       !!(regA & rda_SF),
              hasRDS:         !!(regA & rda_BLK_E) || !!(regA & rda_RDSR)
          };
        }
    
        return {
            read_chip: read_chip,
            write_chip: write_chip,
            on: on,
            off: off,
            forceMono: forceMono,
            bassBoost: bassBoost,
            mute: mute,
            deemphasis75: deemphasis75,
            volume: volume,
            setFrequency: setFrequency,
            getState: getState,
            getRDS: getRDS,
            seekUp: seekUp,
            seekDown: seekDown
        };
    }
    
    var radio = Radio(D4, D5);
    radio.on(function(){
      radio.setFrequency(104.5);
    });
    
    
  • in ESP8266
    Avatar for virtualcodewarrior

    Here is the mongoose IoT output on the same exact hardware for comparison. just to rule out any hardware or measuring errors.

  • in ESP8266
    Avatar for virtualcodewarrior

    The device is a RRD-102V2.0 module that has a RDA5807M Fm radio chip.

    According to the specs, It should be capable of 400kHz I2C communication speeds.
    Google for RDA5807M will bring up the data sheet.

    I managed to get slightly higher resolution that shows the glitch even better

    The glitch shows up even if not connected to the radio module and rda and scl just connected to the two pullup resistors.

  • in ESP8266
    Avatar for virtualcodewarrior

    The code was indeed running at around 250kHz, according to the data sheet of the radio module it supports up to 400 kHz so it should have worked, but for some reason it doesn't.
    The I2C signals look correct, except for the following artifacts.

    The espruino version has these artifacts on the signal that don't exist on the mongoose version.

    I cannot get higher resolution out of my logic analyzer but
    the peak and dip coincide with the clock signal. In the code it seems to reapply the value of sda at each scl change :

    i2c_master_writeByte(uint8 wrdata)
    {
        uint8 dat;
        sint8 i;
    
        i2c_master_wait(2);
        i2c_master_setDC(m_nLastSDA, 0);
        i2c_master_wait(4);
    
        for (i = 7; i >= 0; i--) {
            dat = wrdata >> i;
            i2c_master_setDC(dat, 0); // << set sda when clock still low
            i2c_master_wait(1);
            i2c_master_setDC(dat, 1); // << set sda to same value but now with clock high
            i2c_master_wait(5);
            i2c_master_setDC(dat, 0); // << set sda to same value but with clock low again
            i2c_master_wait(2);
        }
    }
    

    I wonder if setting sda to 1 actually pulls it to 0 first before reapplying the 1.
    I cannot see any other problems when comparing the signals generated by espruino with the ones generated by mongoose :

    Note that the following images are using a lower sample rate so that is why the rise times look slow and it doesn't show the artifacts.
    Things to note: In this version of espruino I slowed down the I2C speed to match the mongoose speed, it is using the clock stretching version of i2c_master.c. Besides some slight timing differences to better match mongoose this should otherwise be the same as the version of this file in the master espruino branch.


    Espruino signal, notice it doesn't get ack.


    Mongoose signal, notice it does get ack (and does so consistently) .

    It looks like mongoose allows clock stretching also, but that happens after ack so should not be the cause of the difference.

    You can imagine that I am positively stumped why mongoose works consistently correct while Espruino consistently fails.

  • in ESP8266
    Avatar for virtualcodewarrior

    Ok back to the drawing board, I slowed Espruino down to the same speed and even slower, but it made no difference.
    So the exact timing is the next thing to check out.
    For some reason the working version results in 001000100 while the espruino version results in 001000101.
    I am assuming that the slave is the one producing the last 0 in the working version and
    produces a 1 (or does nothing resulting in an automatic 1) in the not working version.

    Hopefully I am learning something here ;-)

  • in ESP8266
    Avatar for virtualcodewarrior

    Looks like espruinos I2C is almost 10x faster than mongoose, maybe it is too fast for this module. Would now be nice if I could set the bitrate lower.

  • in ESP8266
    Avatar for virtualcodewarrior

    Looks like the I2C problem is specific to the radio module.

    A MCP23017 port expander that I connected to the same bus is recognized and works without problem.

    It is not a problem with the esp8266 because flashing different firmware makes it work.
    It should also mean that the circuit is wired up correctly.

    I will check the difference between the two firmwares next using a logic analyzer

  • in ESP8266
    Avatar for virtualcodewarrior
    • FM Radio module RDA5807M
    • On an ESP8266-12F
    • Using I2C1.setup({sda: D4, scl: D5});
    • NOT working :(

    Works fine in Mongoose IoT (formerly smart.js)

    Will try to build the mentioned clock stretching code soon to see if that can make it work.

  • in ESP8266
    Avatar for virtualcodewarrior

    I am still trying to make it work with my FM radio module.

    The wiring should be correct because when I flashed Mongoose IoT (formerly smart.js)
    I could use the following code :

    var i2c = new I2C(4, 5);
    i2c.do(0x11, [I2C.WRITE, 0], [I2C.READ, 2]);
    

    And have it work perfectly.

    On Espruino I used the following (hopefully) equivalent code :

    I2C1.setup({ sda: D4, SCL: D5 });
    I2C1.writeTo(0x11, 0);
    I2C1.readFrom(0x11, 2)
    

    Which results in

    ERROR: No ACK
    ERROR: No ACK
    

    Can this have something to do with the lack of clock stretching ??

Actions