diff --git a/README.md b/README.md index 4c0c046e5a8..d949dd22c69 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,18 @@ - [ESP32Dev Board PINMAP](#esp32dev-board-pinmap) ## Development Status -Most of the framework is implemented. Most noticable is the missing analogWrite. While analogWrite is on it's way, there are a few other options that you can use: -- 16 channels [LEDC](cores/esp32/esp32-hal-ledc.h) which is PWM -- 8 channels [SigmaDelta](cores/esp32/esp32-hal-sigmadelta.h) which uses SigmaDelta modulation -- 2 channels [DAC](cores/esp32/esp32-hal-dac.h) which gives real analog output +- Most of the framework is implemented. +- Differences: + - `Wire()` for deeper explanation [README.md](libraries/Wire/docs/README.md) + - 64k-1 data transfers + - Special handling for sendStop=false +- Missing: + - `analogWrite()` While analogWrite is on it's way, there are a few other options that you can use: + - 16 channels [LEDC](cores/esp32/esp32-hal-ledc.h) which is PWM + - 8 channels [SigmaDelta](cores/esp32/esp32-hal-sigmadelta.h) which uses SigmaDelta modulation + - 2 channels [DAC](cores/esp32/esp32-hal-dac.h) which gives real analog output + - `Wire.onReceive()` + - `Wire.onRequest()` ## Installation Instructions diff --git a/cores/esp32/esp32-hal-i2c.c b/cores/esp32/esp32-hal-i2c.c index dd3d35baafb..09c6caf91a4 100644 --- a/cores/esp32/esp32-hal-i2c.c +++ b/cores/esp32/esp32-hal-i2c.c @@ -17,6 +17,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" +#include "freertos/event_groups.h" #include "rom/ets_sys.h" #include "driver/periph_ctrl.h" #include "soc/i2c_reg.h" @@ -30,15 +31,24 @@ #define DR_REG_I2C_EXT_BASE_FIXED 0x60013000 #define DR_REG_I2C1_EXT_BASE_FIXED 0x60027000 - -#define COMMAND_BUFFER_LENGTH 16 - + struct i2c_struct_t { i2c_dev_t * dev; #if !CONFIG_DISABLE_HAL_LOCKS xSemaphoreHandle lock; #endif uint8_t num; + I2C_MODE_t mode; + I2C_STAGE_t stage; + I2C_ERROR_t error; + EventGroupHandle_t i2c_event; // a way to monitor ISR process + // maybe use it to trigger callback for OnRequest() + intr_handle_t intr_handle; /*!< I2C interrupt handle*/ + I2C_DATA_QUEUE_t * dq; + uint16_t queueCount; + uint16_t queuePos; + uint16_t byteCnt; + uint32_t exitCode; }; enum { @@ -54,25 +64,125 @@ enum { #define I2C_MUTEX_UNLOCK() static i2c_t _i2c_bus_array[2] = { - {(volatile i2c_dev_t *)(DR_REG_I2C_EXT_BASE_FIXED), 0}, - {(volatile i2c_dev_t *)(DR_REG_I2C1_EXT_BASE_FIXED), 1} + {(volatile i2c_dev_t *)(DR_REG_I2C_EXT_BASE_FIXED), 0,I2C_NONE,I2C_NONE,I2C_ERROR_OK,NULL,NULL,NULL,0,0,0,0}, + {(volatile i2c_dev_t *)(DR_REG_I2C1_EXT_BASE_FIXED), 1,I2C_NONE,I2C_NONE,I2C_ERROR_OK,NULL,NULL,NULL,0,0,0,0} }; #else #define I2C_MUTEX_LOCK() do {} while (xSemaphoreTake(i2c->lock, portMAX_DELAY) != pdPASS) #define I2C_MUTEX_UNLOCK() xSemaphoreGive(i2c->lock) static i2c_t _i2c_bus_array[2] = { - {(volatile i2c_dev_t *)(DR_REG_I2C_EXT_BASE_FIXED), NULL, 0}, - {(volatile i2c_dev_t *)(DR_REG_I2C1_EXT_BASE_FIXED), NULL, 1} + {(volatile i2c_dev_t *)(DR_REG_I2C_EXT_BASE_FIXED), NULL, 0,I2C_NONE,I2C_NONE,I2C_ERROR_OK,NULL,NULL,NULL,0,0,0,0}, + {(volatile i2c_dev_t *)(DR_REG_I2C1_EXT_BASE_FIXED), NULL, 1,I2C_NONE,I2C_NONE,I2C_ERROR_OK,NULL,NULL,NULL,0,0,0,0} }; #endif +/* Stickbreaker added for ISR 11/2017 +functional with Silicon date=0x16042000 +*/ +static i2c_err_t i2cAddQueue(i2c_t * i2c,uint8_t mode, uint16_t i2cDeviceAddr, uint8_t *dataPtr, uint16_t dataLen,bool sendStop, EventGroupHandle_t event){ + + if(i2c==NULL) return I2C_ERROR_DEV; + + I2C_DATA_QUEUE_t dqx; + dqx.data = dataPtr; + dqx.length = dataLen; + dqx.position = 0; + dqx.cmdBytesNeeded = dataLen; + dqx.ctrl.val = 0; + dqx.ctrl.addr = i2cDeviceAddr; + dqx.ctrl.mode = mode; + dqx.ctrl.stop= sendStop; + dqx.ctrl.addrReq = ((i2cDeviceAddr&0xFC00)==0x7800)?2:1; // 10bit or 7bit address + dqx.queueLength = dataLen + dqx.ctrl.addrReq; + dqx.queueEvent = event; + +if(event){// an eventGroup exist, so, initialize it + xEventGroupClearBits(event, EVENT_MASK); // all of them + } + +if(i2c->dq!=NULL){ // expand +//log_i("expand"); + I2C_DATA_QUEUE_t* tq =(I2C_DATA_QUEUE_t*)realloc(i2c->dq,sizeof(I2C_DATA_QUEUE_t)*(i2c->queueCount +1)); + if(tq!=NULL){// ok + i2c->dq = tq; + memmove(&i2c->dq[i2c->queueCount++],&dqx,sizeof(I2C_DATA_QUEUE_t)); + } + else { // bad stuff, unable to allocate more memory! + log_e("realloc Failure"); + return I2C_ERROR_MEMORY; + } + } +else { // first Time +//log_i("new"); + i2c->queueCount=0; + i2c->dq =(I2C_DATA_QUEUE_t*)malloc(sizeof(I2C_DATA_QUEUE_t)); + if(i2c->dq!=NULL){ + memmove(&i2c->dq[i2c->queueCount++],&dqx,sizeof(I2C_DATA_QUEUE_t)); + } + else { + log_e("malloc failure"); + return I2C_ERROR_MEMORY; + } + } +return I2C_ERROR_OK; +} + +i2c_err_t i2cFreeQueue(i2c_t * i2c){ +if(i2c==NULL) return I2C_ERROR_DEV; + // need to grab a MUTEX for exclusive Queue, + // what out if ISR is running? +i2c_err_t rc=I2C_ERROR_OK; +if(i2c->dq!=NULL){ +// log_i("free"); +// what about EventHandle? + free(i2c->dq); + i2c->dq = NULL; + } +i2c->queueCount=0; +i2c->queuePos=0; +// release Mutex +return rc; +} + +i2c_err_t i2cAddQueueWrite(i2c_t * i2c, uint16_t i2cDeviceAddr, uint8_t *dataPtr, uint16_t dataLen,bool sendStop,EventGroupHandle_t event){ + // need to grab a MUTEX for exclusive Queue, + // what out if ISR is running? + + return i2cAddQueue(i2c,0,i2cDeviceAddr,dataPtr,dataLen,sendStop,event); +} + +i2c_err_t i2cAddQueueRead(i2c_t * i2c, uint16_t i2cDeviceAddr, uint8_t *dataPtr, uint16_t dataLen,bool sendStop,EventGroupHandle_t event){ +// need to grab a MUTEX for exclusive Queue, +// what out if ISR is running? + + + //10bit read is kind of weird, first you do a 0byte Write with 10bit + // address, then a ReSTART then a 7bit Read using the the upper 7bit + + // readBit. + + // this might cause an internal register pointer problem with 10bit + // devices, But, Don't have any to test agains. + // this is the Industry Standard specification. + + if((i2cDeviceAddr &0xFC00)==0x7800){ // ten bit read + i2c_err_t err = i2cAddQueue(i2c,0,i2cDeviceAddr,NULL,0,false,event); + if(err==I2C_ERROR_OK){ + return i2cAddQueue(i2c,1,(i2cDeviceAddr>>8),dataPtr,dataLen,sendStop,event); + } + else return err; + } + return i2cAddQueue(i2c,1,i2cDeviceAddr,dataPtr,dataLen,sendStop,event); +} +// Stickbreaker + i2c_err_t i2cAttachSCL(i2c_t * i2c, int8_t scl) { if(i2c == NULL){ return I2C_ERROR_DEV; } - pinMode(scl, OUTPUT_OPEN_DRAIN | PULLUP); + digitalWrite(scl, HIGH); + pinMode(scl, OPEN_DRAIN | PULLUP | INPUT | OUTPUT); pinMatrixOutAttach(scl, I2C_SCL_IDX(i2c->num), false, false); pinMatrixInAttach(scl, I2C_SCL_IDX(i2c->num), false); return I2C_ERROR_OK; @@ -85,7 +195,7 @@ i2c_err_t i2cDetachSCL(i2c_t * i2c, int8_t scl) } pinMatrixOutDetach(scl, false, false); pinMatrixInDetach(I2C_SCL_IDX(i2c->num), false, false); - pinMode(scl, INPUT); + pinMode(scl, INPUT | PULLUP); return I2C_ERROR_OK; } @@ -94,7 +204,8 @@ i2c_err_t i2cAttachSDA(i2c_t * i2c, int8_t sda) if(i2c == NULL){ return I2C_ERROR_DEV; } - pinMode(sda, OUTPUT_OPEN_DRAIN | PULLUP); + digitalWrite(sda, HIGH); + pinMode(sda, OPEN_DRAIN | PULLUP | INPUT | OUTPUT ); pinMatrixOutAttach(sda, I2C_SDA_IDX(i2c->num), false, false); pinMatrixInAttach(sda, I2C_SDA_IDX(i2c->num), false); return I2C_ERROR_OK; @@ -107,7 +218,7 @@ i2c_err_t i2cDetachSDA(i2c_t * i2c, int8_t sda) } pinMatrixOutDetach(sda, false, false); pinMatrixInDetach(I2C_SDA_IDX(i2c->num), false, false); - pinMode(sda, INPUT); + pinMode(sda, INPUT | PULLUP); return I2C_ERROR_OK; } @@ -121,260 +232,26 @@ i2c_err_t i2cDetachSDA(i2c_t * i2c, int8_t sda) * */ void i2cSetCmd(i2c_t * i2c, uint8_t index, uint8_t op_code, uint8_t byte_num, bool ack_val, bool ack_exp, bool ack_check) { - i2c->dev->command[index].val = 0; - i2c->dev->command[index].ack_en = ack_check; - i2c->dev->command[index].ack_exp = ack_exp; - i2c->dev->command[index].ack_val = ack_val; - i2c->dev->command[index].byte_num = byte_num; - i2c->dev->command[index].op_code = op_code; -} - -void i2cResetCmd(i2c_t * i2c) { - uint8_t i; - for(i=0;i<16;i++){ - i2c->dev->command[i].val = 0; - } -} - -void i2cResetFiFo(i2c_t * i2c) { - i2c->dev->fifo_conf.tx_fifo_rst = 1; - i2c->dev->fifo_conf.tx_fifo_rst = 0; - i2c->dev->fifo_conf.rx_fifo_rst = 1; - i2c->dev->fifo_conf.rx_fifo_rst = 0; + I2C_COMMAND_t cmd; + cmd.val=0; + cmd.ack_en = ack_check; + cmd.ack_exp = ack_exp; + cmd.ack_val = ack_val; + cmd.byte_num = byte_num; + cmd.op_code = op_code; + i2c->dev->command[index].val = cmd.val; } -i2c_err_t i2cWrite(i2c_t * i2c, uint16_t address, bool addr_10bit, uint8_t * data, uint16_t len, bool sendStop) +void i2cResetFiFo(i2c_t * i2c) { - int i; - uint16_t index = 0; - uint16_t dataLen = len + (addr_10bit?2:1); - address = (address << 1); - - if(i2c == NULL){ - return I2C_ERROR_DEV; - } - - I2C_MUTEX_LOCK(); - - if (i2c->dev->status_reg.bus_busy == 1) - { - log_e( "Busy Timeout! Addr: %x", address >> 1 ); - I2C_MUTEX_UNLOCK(); - return I2C_ERROR_BUSY; - } - - while(dataLen) { - uint8_t willSend = (dataLen > 32)?32:dataLen; - uint8_t dataSend = willSend; - - i2cResetFiFo(i2c); - i2cResetCmd(i2c); - //Clear Interrupts - i2c->dev->int_clr.val = 0xFFFFFFFF; - - //CMD START - i2cSetCmd(i2c, 0, I2C_CMD_RSTART, 0, false, false, false); - - //CMD WRITE(ADDRESS + DATA) - if(!index) { - if(addr_10bit){// address is leftshifted with Read/Write bit set - i2c->dev->fifo_data.data = (((address >> 8) & 0x6) | 0xF0); // send a9:a8 plus 1111 0xxW mask - i2c->dev->fifo_data.data = ((address >> 1) & 0xFF); // send a7:a0, remove W bit (7bit address style) - dataSend -= 2; - } - else { // 7bit address - i2c->dev->fifo_data.data = address & 0xFF; - dataSend--; - } - } - i = 0; - while(idev->fifo_data.val = data[index++]; - while(i2c->dev->status_reg.tx_fifo_cnt < i); - } - i2cSetCmd(i2c, 1, I2C_CMD_WRITE, willSend, false, false, true); - dataLen -= willSend; - - //CMD STOP or CMD END if there is more data - if(dataLen || !sendStop) { - i2cSetCmd(i2c, 2, I2C_CMD_END, 0, false, false, false); - } else if(sendStop) { - i2cSetCmd(i2c, 2, I2C_CMD_STOP, 0, false, false, false); - } - - //START Transmission - i2c->dev->ctr.trans_start = 1; - - //WAIT Transmission - uint32_t startAt = millis(); - while(1) { - //have been looping for too long - if((millis() - startAt)>50){ - log_e("Timeout! Addr: %x", address >> 1); - I2C_MUTEX_UNLOCK(); - return I2C_ERROR_BUS; - } - - //Bus failed (maybe check for this while waiting? - if(i2c->dev->int_raw.arbitration_lost) { - log_e("Bus Fail! Addr: %x", address >> 1); - I2C_MUTEX_UNLOCK(); - return I2C_ERROR_BUS; - } - - //Bus timeout - if(i2c->dev->int_raw.time_out) { - log_e("Bus Timeout! Addr: %x", address >> 1); - I2C_MUTEX_UNLOCK(); - return I2C_ERROR_TIMEOUT; - } - - //Transmission did not finish and ACK_ERR is set - if(i2c->dev->int_raw.ack_err) { - log_w("Ack Error! Addr: %x", address >> 1); - while((i2c->dev->status_reg.bus_busy) && ((millis() - startAt)<50)); - I2C_MUTEX_UNLOCK(); - return I2C_ERROR_ACK; - } - - if((sendStop && i2c->dev->command[2].done) || !i2c->dev->status_reg.bus_busy){ - break; - } - } - - } - I2C_MUTEX_UNLOCK(); - return I2C_ERROR_OK; -} - -uint8_t inc( uint8_t* index ) -{ - uint8_t i = index[ 0 ]; - if (++index[ 0 ] == COMMAND_BUFFER_LENGTH) - { - index[ 0 ] = 0; - } - - return i; -} - -i2c_err_t i2cRead(i2c_t * i2c, uint16_t address, bool addr_10bit, uint8_t * data, uint16_t len, bool sendStop) -{ - address = (address << 1) | 1; - uint8_t addrLen = (addr_10bit?2:1); - uint8_t amountRead[16]; - uint16_t index = 0; - uint8_t cmdIdx = 0, currentCmdIdx = 0, nextCmdCount; - bool stopped = false, isEndNear = false; - uint8_t willRead; - - if(i2c == NULL){ - return I2C_ERROR_DEV; - } - - I2C_MUTEX_LOCK(); - - if (i2c->dev->status_reg.bus_busy == 1) - { - log_w( "Busy Timeout! Addr: %x", address >> 1 ); - I2C_MUTEX_UNLOCK(); - return I2C_ERROR_BUSY; - } - - i2cResetFiFo(i2c); - i2cResetCmd(i2c); - - //CMD START - i2cSetCmd(i2c, cmdIdx++, I2C_CMD_RSTART, 0, false, false, false); - - //CMD WRITE ADDRESS - if (addr_10bit) { // address is left-shifted with Read/Write bit set - i2c->dev->fifo_data.data = (((address >> 8) & 0x6) | 0xF1); // send a9:a8 plus 1111 0xxR mask - i2c->dev->fifo_data.data = ((address >> 1) & 0xFF); // send a7:a0, remove R bit (7bit address style) - } - else { // 7bit address - i2c->dev->fifo_data.data = address & 0xFF; - } - i2cSetCmd(i2c, cmdIdx++, I2C_CMD_WRITE, addrLen, false, false, true); - nextCmdCount = cmdIdx; - - //Clear Interrupts - i2c->dev->int_clr.val = 0x00001FFF; - - //START Transmission - i2c->dev->ctr.trans_start = 1; - while (!stopped) { - //WAIT Transmission - uint32_t startAt = millis(); - while(1) { - //have been looping for too long - if((millis() - startAt)>50) { - log_e("Timeout! Addr: %x, index %d", (address >> 1), index); - I2C_MUTEX_UNLOCK(); - return I2C_ERROR_BUS; - } - - //Bus failed (maybe check for this while waiting? - if(i2c->dev->int_raw.arbitration_lost) { - log_e("Bus Fail! Addr: %x", (address >> 1)); - I2C_MUTEX_UNLOCK(); - return I2C_ERROR_BUS; - } - - //Bus timeout - if(i2c->dev->int_raw.time_out) { - log_e("Bus Timeout! Addr: %x, index %d", (address >> 1), index ); - I2C_MUTEX_UNLOCK(); - return I2C_ERROR_TIMEOUT; - } - - //Transmission did not finish and ACK_ERR is set - if(i2c->dev->int_raw.ack_err) { - log_w("Ack Error! Addr: %x", address >> 1); - while((i2c->dev->status_reg.bus_busy) && ((millis() - startAt)<50)); - I2C_MUTEX_UNLOCK(); - return I2C_ERROR_ACK; - } - - // Save bytes from the buffer as they arrive instead of doing them at the end of the loop since there is no - // pause from an END operation in this approach. - if((!isEndNear) && (nextCmdCount < 2)) { - willRead = ((len>32)?32:len); - if (willRead > 0) { - if (willRead > 1) { - i2cSetCmd(i2c, cmdIdx, I2C_CMD_READ, (amountRead[ inc( &cmdIdx ) ] = willRead -1), false, false, false); - nextCmdCount++; - } - i2cSetCmd(i2c, cmdIdx, I2C_CMD_READ, (amountRead[ inc( &cmdIdx ) ] = 1), (len<=32), false, false); - nextCmdCount++; - len -= willRead; - } else { - i2cSetCmd(i2c, inc( &cmdIdx ), I2C_CMD_STOP, 0, false, false, false); - isEndNear = true; - nextCmdCount++; - } - } - - if(i2c->dev->command[currentCmdIdx].done) { - nextCmdCount--; - if (i2c->dev->command[currentCmdIdx].op_code == I2C_CMD_READ) { - while(amountRead[currentCmdIdx]>0) { - data[index++] = i2c->dev->fifo_data.val & 0xFF; - amountRead[currentCmdIdx]--; - } - i2cResetFiFo(i2c); - } else if (i2c->dev->command[currentCmdIdx].op_code == I2C_CMD_STOP) { - stopped = true; - } - inc( ¤tCmdIdx ); - break; - } - } - } - I2C_MUTEX_UNLOCK(); - - return I2C_ERROR_OK; + I2C_FIFO_CONF_t f; + f.val = i2c->dev->fifo_conf.val; + f.tx_fifo_rst = 1; + f.rx_fifo_rst = 1; + i2c->dev->fifo_conf.val = f.val; + f.tx_fifo_rst = 0; + f.rx_fifo_rst = 0; + i2c->dev->fifo_conf.val = f.val; } i2c_err_t i2cSetFrequency(i2c_t * i2c, uint32_t clk_speed) @@ -416,8 +293,13 @@ uint32_t i2cGetFrequency(i2c_t * i2c) if(i2c == NULL){ return 0; } - - return APB_CLK_FREQ/(i2c->dev->scl_low_period.period+i2c->dev->scl_high_period.period); + uint32_t result = 0; + uint32_t old_count = (i2c->dev->scl_low_period.period+i2c->dev->scl_high_period.period); + if(old_count>0){ + result = APB_CLK_FREQ / old_count; + } + else result = 0; + return result; } /* @@ -425,8 +307,8 @@ uint32_t i2cGetFrequency(i2c_t * i2c) * slave_addr - I2C Address * addr_10bit_en - enable slave 10bit address mode. * */ - -i2c_t * i2cInit(uint8_t i2c_num, uint16_t slave_addr, bool addr_10bit_en) +// 24Nov17 only supports Master Mode +i2c_t * i2cInit(uint8_t i2c_num) //before this is called, pins should be detached, else glitch { if(i2c_num > 1){ return NULL; @@ -442,44 +324,47 @@ i2c_t * i2cInit(uint8_t i2c_num, uint16_t slave_addr, bool addr_10bit_en) } } #endif + I2C_MUTEX_LOCK(); + + i2cReleaseISR(i2c); // ISR exists, release it before disabling hardware + + uint32_t old_clock = i2cGetFrequency(i2c); if(i2c_num == 0) { + DPORT_SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG,DPORT_I2C_EXT0_RST); //reset hardware DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG,DPORT_I2C_EXT0_CLK_EN); - DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG,DPORT_I2C_EXT0_RST); + DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG,DPORT_I2C_EXT0_RST);// release reset } else { + DPORT_SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG,DPORT_I2C_EXT1_RST); //reset Hardware DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG,DPORT_I2C_EXT1_CLK_EN); DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG,DPORT_I2C_EXT1_RST); } - - I2C_MUTEX_LOCK(); i2c->dev->ctr.val = 0; - i2c->dev->ctr.ms_mode = (slave_addr == 0); + i2c->dev->ctr.ms_mode = 1; i2c->dev->ctr.sda_force_out = 1 ; i2c->dev->ctr.scl_force_out = 1 ; i2c->dev->ctr.clk_en = 1; //the max clock number of receiving a data - i2c->dev->timeout.tout = 1048575;//clocks max=1048575 + i2c->dev->timeout.tout = 400000;//clocks max=1048575 //disable apb nonfifo access i2c->dev->fifo_conf.nonfifo_en = 0; i2c->dev->slave_addr.val = 0; - if (slave_addr) { - i2c->dev->slave_addr.addr = slave_addr; - i2c->dev->slave_addr.en_10bit = addr_10bit_en; - } I2C_MUTEX_UNLOCK(); + + if(old_clock) i2cSetFrequency(i2c,old_clock); // reconfigure return i2c; } - +/* unused 03/15/2018 void i2cInitFix(i2c_t * i2c){ if(i2c == NULL){ return; } I2C_MUTEX_LOCK(); + i2c->dev->ctr.trans_start = 0; i2cResetFiFo(i2c); - i2cResetCmd(i2c); i2c->dev->int_clr.val = 0xFFFFFFFF; i2cSetCmd(i2c, 0, I2C_CMD_RSTART, 0, false, false, false); i2c->dev->fifo_data.data = 0; @@ -494,7 +379,8 @@ void i2cInitFix(i2c_t * i2c){ while ((!i2c->dev->command[2].done) && (--count > 0)); I2C_MUTEX_UNLOCK(); } - +/* + unused 03/15/2018 void i2cReset(i2c_t* i2c){ if(i2c == NULL){ return; @@ -506,4 +392,892 @@ void i2cReset(i2c_t* i2c){ periph_module_enable( moduleId ); I2C_MUTEX_UNLOCK(); } +*/ + +/* Stickbreaker ISR mode debug support +*/ +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO +#define INTBUFFMAX 64 +static uint32_t intBuff[INTBUFFMAX][3][2]; +static uint32_t intPos[2]={0,0}; +#endif + +/* Stickbreaker ISR mode debug support +*/ +void IRAM_ATTR dumpCmdQueue(i2c_t *i2c){ +uint8_t i=0; +while(i<16){ + I2C_COMMAND_t c; + c.val=i2c->dev->command[i].val; + log_e("[%2d] %c op[%d] val[%d] exp[%d] en[%d] bytes[%d]",i,(c.done?'Y':'N'), + c.op_code, + c.ack_val, + c.ack_exp, + c.ack_en, + c.byte_num); + i++; + } +} + +/* Stickbreaker ISR mode support +*/ +static void IRAM_ATTR fillCmdQueue(i2c_t * i2c, bool INTS){ +/* this function is call on initial i2cProcQueue() + or when a I2C_END_DETECT_INT occures +*/ + uint16_t cmdIdx = 0; + uint16_t qp = i2c->queuePos; + bool done; + bool needMoreCmds = false; + bool ena_rx=false; // if we add a read op, better enable Rx_Fifo IRQ + bool ena_tx=false; // if we add a Write op, better enable TX_Fifo IRQ + +while(!needMoreCmds&&(qp < i2c->queueCount)){ // check if more possible cmds + if(i2c->dq[qp].ctrl.stopCmdSent) { + qp++; + } + else needMoreCmds=true; + } +//log_e("needMoreCmds=%d",needMoreCmds); +done=(!needMoreCmds)||(cmdIdx>14); + +while(!done){ // fill the command[] until either 0..14 filled or out of cmds and last cmd is STOP +//CMD START + I2C_DATA_QUEUE_t *tdq=&i2c->dq[qp]; // simpler coding + + if((!tdq->ctrl.startCmdSent) && (cmdIdx < 14)){// has this dq element's START command been added? + // <14 testing if ReSTART END is causeing the Timeout + i2cSetCmd(i2c, cmdIdx++, I2C_CMD_RSTART, 0, false, false, false); + tdq->ctrl.startCmdSent=1; + done = (cmdIdx>14); + } + +//CMD WRITE ADDRESS + if((!done)&&(tdq->ctrl.startCmdSent)){// have to leave room for continue, and START must have been sent! + if(!tdq->ctrl.addrCmdSent){ + i2cSetCmd(i2c, cmdIdx++, I2C_CMD_WRITE, tdq->ctrl.addrReq, false, false, true); //load address in cmdlist, validate (low) ack + tdq->ctrl.addrCmdSent=1; + done =(cmdIdx>14); + ena_tx=true; // tx Data necessary + } + } + +/* Can I have another Sir? + ALL CMD queues must be terminated with either END or STOP. + + If END is used, when refilling the cmd[] next time, no entries from END to [15] can be used. + AND the cmd[] must be filled starting at [0] with commands. Either fill all 15 [0]..[14] and leave the + END in [15] or include a STOP in one of the positions [0]..[14]. Any entries after a STOP are IGNORED byte the StateMachine. + The END operation does not complete until ctr->trans_start=1 has been issued. + + So, only refill from [0]..[14], leave [15] for a continuation if necessary. + As a corrilary, once END exists in [15], you do not need to overwrite it for the + next continuation. It is never modified. But, I update it every time because it might + actually be the first time! + + 23NOV17 START cannot proceed END. if START is in[14], END cannot be in [15]. + so, AND if END is moved to [14], [14] and [15] can nolonger be use for anything other than END. + If a START is found in [14] then a prior READ or WRITE must be expanded so that there is no START element in [14]. + + +*/ + if((!done)&&(tdq->ctrl.addrCmdSent)){ //room in command[] for at least One data (read/Write) cmd + uint8_t blkSize=0; // max is 255? does numBytes =0 actually mean 256? haven't tried it. + //log_e("needed=%2d index=%d",*neededRead,cmdIdx); + while(( tdq->cmdBytesNeeded > tdq->ctrl.mode )&&(!done )) { // more bytes needed and room in cmd queue, leave room for END + blkSize = (tdq->cmdBytesNeeded > 255)?255:(tdq->cmdBytesNeeded - tdq->ctrl.mode); // Last read cmd needs different ACK setting, so leave 1 byte remainer on reads + tdq->cmdBytesNeeded -= blkSize; // + if(tdq->ctrl.mode==1){ //read mode + i2cSetCmd(i2c, (cmdIdx)++, I2C_CMD_READ, blkSize,false,false,false); // read cmd, this can't be the last read. + ena_rx=true; // need to enable rxFifo IRQ + } + else {// write + i2cSetCmd(i2c, cmdIdx++, I2C_CMD_WRITE, blkSize, false, false, true); // check for Nak + ena_tx=true; // need to enable txFifo IRQ + } + done = cmdIdx>14; //have to leave room for END + } + + if(!done){ // buffer is not filled completely + if((tdq->ctrl.mode==1)&&(tdq->cmdBytesNeeded==1)){ //special last read byte NAK + i2cSetCmd(i2c, (cmdIdx)++, I2C_CMD_READ, 1,true,false,false); + // send NAK to mark end of read + tdq->cmdBytesNeeded=0; + done = cmdIdx > 14; + ena_rx=true; + } + } + + tdq->ctrl.dataCmdSent=(tdq->cmdBytesNeeded==0); // enough command[] to send all data + + if((!done)&&(tdq->ctrl.dataCmdSent)){ // possibly add stop + if(tdq->ctrl.stop){ //send a stop + i2cSetCmd(i2c, (cmdIdx)++,I2C_CMD_STOP,0,false,false,false); + done = cmdIdx > 14; + tdq->ctrl.stopCmdSent = 1; + } + else {// dummy a stop because this is a restart + tdq->ctrl.stopCmdSent = 1; + } + } + } + + if((cmdIdx==14)&&(!tdq->ctrl.startCmdSent)){ + // START would have preceded END, causes SM TIMEOUT + // need to stretch out a prior WRITE or READ to two Command[] elements + done = false; // reuse it + uint16_t i = 13; // start working back until a READ/WRITE has >1 numBytes + cmdIdx =15; + // log_e("before Stretch"); + // dumpCmdQueue(i2c); + while(!done){ + i2c->dev->command[i+1].val = i2c->dev->command[i].val; // push it down + if (((i2c->dev->command[i].op_code == 1)||(i2c->dev->command[i].op_code==2))){ + /* just try a num_bytes =0; + &&(i2c->dev->command[i].byte_num>1)){ // found the one to expand + i2c->dev->command[i+1].byte_num =1; +// the -= in the following statment caused unintential consequences. +// The op_code field value changed from 2 to 4, so the manual cludge was needed +// i2c->dev->command[i].byte_num -= 1; +/ + uint32_t temp = i2c->dev->command[i].val; + temp = (temp&0xFFFFFF00) | ((temp & 0xFF)-1); + i2c->dev->command[i].val = temp; +*/ + i2c->dev->command[i].byte_num = 0; + done = true; + + } + else { + if(i > 0) { + i--; + } + else { // unable to stretch, fatal + log_e("invalid CMD[] layout Stretch Failed"); + dumpCmdQueue(i2c); + done = true; + } + } + } + // log_e("after Stretch"); + // dumpCmdQueue(i2c); + + } + + + if(cmdIdx==15){ //need continuation, even if STOP is in 14, it will not matter + // cmd buffer is almost full, Add END as a continuation feature + // log_e("END at %d, left=%d",cmdIdx,neededRead); + i2cSetCmd(i2c, (cmdIdx)++,I2C_CMD_END, 0,false,false,false); + i2c->dev->int_ena.end_detect=1; //maybe? + i2c->dev->int_clr.end_detect=1; //maybe? + done = true; + } + + if(!done){ + if(tdq->ctrl.stopCmdSent){ // this queue element has been completely added to command[] buffer + qp++; + if(qp < i2c->queueCount){ + tdq = &i2c->dq[qp]; +// log_e("inc to next queue=%d",qp); + } + else done = true; + } + } + + }// while(!done) +if(INTS){ // don't want to prematurely enable fifo ints until ISR is ready to handle it. + if(ena_rx) i2c->dev->int_ena.rx_fifo_full = 1; + if(ena_tx) i2c->dev->int_ena.tx_fifo_empty = 1; + } +} + +/* Stickbreaker ISR mode debug support +*/ +void i2cDumpDqData(i2c_t * i2c){ +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR +uint16_t a=0; +char buff[140]; +I2C_DATA_QUEUE_t *tdq; +while(aqueueCount){ + tdq=&i2c->dq[a]; + log_e("[%d] %x %c %s buf@=%p, len=%d, pos=%d, eventH=%p bits=%x",a,tdq->ctrl.addr,(tdq->ctrl.mode)?'R':'W',(tdq->ctrl.stop)?"STOP":"",tdq->data,tdq->length,tdq->position,tdq->queueEvent,(tdq->queueEvent)?xEventGroupGetBits(tdq->queueEvent):0); + uint16_t offset = 0; + while(offsetlength){ + memset(buff,' ',140); + buff[139]='\0'; + uint16_t i = 0,j; + j=sprintf(buff,"0x%04x: ",offset); + while((i<32)&&(offset < tdq->length)){ + char ch = tdq->data[offset]; + sprintf((char*)&buff[(i*3)+41],"%02x ",ch); + if((ch<32)||(ch>126)) ch='.'; + j+=sprintf((char*)&buff[j],"%c",ch); + buff[j]=' '; + i++; + offset++; + } + log_e("%s",buff); + } + a++; + } +#else +log_n("Enable Core Debug Level \"Error\""); +#endif +} + +void i2cDumpI2c(i2c_t * i2c){ +log_e("i2c=%p",i2c); +char levelText[8]; +switch(ARDUHAL_LOG_LEVEL){ + case 0 : sprintf(levelText,"NONE"); break; + case 1 : sprintf(levelText,"ERROR"); break; + case 2 : sprintf(levelText,"WARN"); break; + case 3 : sprintf(levelText,"INFO"); break; + case 4 : sprintf(levelText,"DEBUG"); break; + case 5 : sprintf(levelText,"VERBOSE"); break; + default : sprintf(levelText,"uk=%d",ARDUHAL_LOG_LEVEL); +} +log_e("dev=%p date=%p level=%s",i2c->dev,i2c->dev->date,levelText); +#if !CONFIG_DISABLE_HAL_LOCKS +log_e("lock=%p",i2c->lock); +#endif +log_e("num=%d",i2c->num); +log_e("mode=%d",i2c->mode); +log_e("stage=%d",i2c->stage); +log_e("error=%d",i2c->error); +log_e("event=%p bits=%x",i2c->i2c_event,(i2c->i2c_event)?xEventGroupGetBits(i2c->i2c_event):0); +log_e("intr_handle=%p",i2c->intr_handle); +log_e("dq=%p",i2c->dq); +log_e("queueCount=%d",i2c->queueCount); +log_e("queuePos=%d",i2c->queuePos); +log_e("byteCnt=%d",i2c->byteCnt); +if(i2c->dq) i2cDumpDqData(i2c); +} + +/* Stickbreaker ISR mode support +*/ +static void IRAM_ATTR fillTxFifo(i2c_t * i2c){ +/* need to test overlapping RX->TX fifo operations, + Currently, this function attempts to queue all possible tx elements into the Fifo. + What happens when WRITE 10, READ 20, Write 10? + (Write Addr, Write 10),(Write addr, Read 20) (Write addr, Write 10). + I know everything will work up to the End of the Read 20, but I am unsure + what will happen to the third command, will the Read 20 overwrite the previously + queued (write addr, write 10) of the Third command? I need to test! + */ +/*11/15/2017 will assume that I cannot queue tx after a READ until READ completes +11/23/2017 Seems to be a TX fifo problem, the SM sends 0x40 for last rxbyte, I +enable txEmpty, filltx fires, but the SM has already sent a bogus byte out the BUS. + I am going so see if I can overlap Tx/Rx/Tx in the fifo +12/01/2017 The Fifo's are independent, 32 bytes of tx and 32 bytes of Rx. + overlap is not an issue, just keep them full/empty the status_reg.xx_fifo_cnt + tells the truth. And the INT's fire correctly +*/ +uint16_t a=i2c->queuePos; // currently executing dq, +bool full=!(i2c->dev->status_reg.tx_fifo_cnt<31); +uint8_t cnt; +while((a < i2c->queueCount) && !full){ + I2C_DATA_QUEUE_t *tdq = &i2c->dq[a]; + cnt=0; +// add to address to fifo ctrl.addr already has R/W bit positioned correctly + if(tdq->ctrl.addrSent < tdq->ctrl.addrReq){ // need to send address bytes + if(tdq->ctrl.addrReq==2){ //10bit + if(tdq->ctrl.addrSent==0){ //10bit highbyte needs sent + if(!full){ // room in fifo + i2c->dev->fifo_data.val = ((tdq->ctrl.addr>>8)&0xFF); + cnt++; + tdq->ctrl.addrSent=1; //10bit highbyte sent + } + } + full=!(i2c->dev->status_reg.tx_fifo_cnt<31); + + if(tdq->ctrl.addrSent==1){ //10bit Lowbyte needs sent + if(!full){ // room in fifo + i2c->dev->fifo_data.val = tdq->ctrl.addr&0xFF; + cnt++; + tdq->ctrl.addrSent=2; //10bit lowbyte sent + } + } + } + else { // 7bit} + if(tdq->ctrl.addrSent==0){ // 7bit Lowbyte needs sent + if(!full){ // room in fifo + i2c->dev->fifo_data.val = tdq->ctrl.addr&0xFF; + cnt++; + tdq->ctrl.addrSent=1; // 7bit lowbyte sent + } + } + } + } + full=!(i2c->dev->status_reg.tx_fifo_cnt<31); +// add write data to fifo +//21NOV2017 might want to look into using local capacity counter instead of reading status_reg +// a double while loop, like emptyRxFifo() + if(tdq->ctrl.mode==0){ // write + if(tdq->ctrl.addrSent == tdq->ctrl.addrReq){ //address has been sent, is Write Mode! + while((!full)&&(tdq->position < tdq->length)){ + i2c->dev->fifo_data.val = tdq->data[tdq->position++]; + cnt++; + full=!(i2c->dev->status_reg.tx_fifo_cnt<31); + } + } + } + +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO + +// update debug buffer tx counts + cnt += intBuff[intPos[i2c->num]][1][i2c->num]>>16; + intBuff[intPos[i2c->num]][1][i2c->num] = (intBuff[intPos[i2c->num]][1][i2c->num]&0xFFFF)|(cnt<<16); + +#endif + + if(!full) a++; // check next buffer for tx + } + +if(!full || (a >= i2c->queueCount)){// disable IRQ, the next dq will re-enable it + i2c->dev->int_ena.tx_fifo_empty=0; + } + +i2c->dev->int_clr.tx_fifo_empty=1; +} + +/* Stickbreaker ISR mode support +*/ +static void IRAM_ATTR emptyRxFifo(i2c_t * i2c){ + uint32_t d, cnt=0, moveCnt; + I2C_DATA_QUEUE_t *tdq =&i2c->dq[i2c->queuePos]; + +moveCnt = i2c->dev->status_reg.rx_fifo_cnt;//no need to check the reg until this many are read +if(moveCnt > (tdq->length - tdq->position)){ //makesure they go in this dq + // part of these reads go into the next dq + moveCnt = (tdq->length - tdq->position); + } + +if(tdq->ctrl.mode==1) { // read + while(moveCnt > 0){ + while(moveCnt > 0){ + d = i2c->dev->fifo_data.val; + moveCnt--; + cnt++; + tdq->data[tdq->position++] = (d&0xFF); + } + // see if any more chars showed up while empting Fifo. + moveCnt = i2c->dev->status_reg.rx_fifo_cnt; + if(moveCnt > (tdq->length - tdq->position)){ //makesure they go in this dq + // part of these reads go into the next dq + moveCnt = (tdq->length - tdq->position); + } + } +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO +// update Debug rxCount + cnt += (intBuff[intPos[i2c->num]][1][i2c->num])&&0xffFF; + intBuff[intPos[i2c->num]][1][i2c->num] = (intBuff[intPos[i2c->num]][1][i2c->num]&0xFFFF0000)|cnt; +#endif + } +else { + log_e("RxEmpty(%d) call on TxBuffer? dq=%d",moveCnt,i2c->queuePos); +// dumpI2c(i2c); + } +//log_e("emptied %d",*index); +} + +static void IRAM_ATTR i2cIsrExit(i2c_t * i2c,const uint32_t eventCode,bool Fatal){ + +switch(eventCode){ + case EVENT_DONE: + i2c->error = I2C_OK; + break; + case EVENT_ERROR_NAK : + i2c->error =I2C_ADDR_NAK; + break; + case EVENT_ERROR_DATA_NAK : + i2c->error =I2C_DATA_NAK; + break; + case EVENT_ERROR_TIMEOUT : + i2c->error = I2C_TIMEOUT; + break; + case EVENT_ERROR_ARBITRATION: + i2c->error = I2C_ARBITRATION; + break; + default : + i2c->error = I2C_ERROR; + } +uint32_t exitCode = EVENT_DONE | eventCode |(Fatal?EVENT_ERROR:0); + +if(i2c->dq[i2c->queuePos].ctrl.mode == 1) emptyRxFifo(i2c); // grab last few characters + +i2c->dev->int_ena.val = 0; // shutdown interrupts +i2c->dev->int_clr.val = 0x1FFFF; +i2c->stage = I2C_DONE; +i2c->exitCode = exitCode; //true eventcode + +portBASE_TYPE HPTaskAwoken = pdFALSE,xResult; +// try to notify Dispatch we are done, +// else the 50ms timeout will recover the APP, just alittle slower +HPTaskAwoken = pdFALSE; +xResult = xEventGroupSetBitsFromISR(i2c->i2c_event, exitCode, &HPTaskAwoken); +if(xResult == pdPASS){ + if(HPTaskAwoken==pdTRUE) { + portYIELD_FROM_ISR(); +// log_e("Yield to Higher"); + } + } + +} + +static void IRAM_ATTR i2c_isr_handler_default(void* arg){ +i2c_t* p_i2c = (i2c_t*) arg; // recover data +uint32_t activeInt = p_i2c->dev->int_status.val&0x1FFF; + +portBASE_TYPE HPTaskAwoken = pdFALSE; + +if(p_i2c->stage==I2C_DONE){ //get Out + log_e("eject int=%p, ena=%p",activeInt,p_i2c->dev->int_ena.val); + p_i2c->dev->int_ena.val = 0; + p_i2c->dev->int_clr.val = activeInt; //0x1FFF; + + return; + } +while (activeInt != 0) { // Ordering of 'if(activeInt)' statements is important, don't change +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO + if(activeInt==(intBuff[intPos[p_i2c->num]][0][p_i2c->num]&0x1fff)){ + intBuff[intPos[p_i2c->num]][0][p_i2c->num] = (((intBuff[intPos[p_i2c->num]][0][p_i2c->num]>>16)+1)<<16)|activeInt; + } + else{ + intPos[p_i2c->num]++; + intPos[p_i2c->num] %= INTBUFFMAX; + intBuff[intPos[p_i2c->num]][0][p_i2c->num] = (1<<16) | activeInt; + intBuff[intPos[p_i2c->num]][1][p_i2c->num] = 0; + } + + intBuff[intPos[p_i2c->num]][2][p_i2c->num] = xTaskGetTickCountFromISR(); // when IRQ fired + +#endif + + if (activeInt & I2C_TRANS_START_INT_ST_M) { + // p_i2c->byteCnt=0; + if(p_i2c->stage==I2C_STARTUP){ + p_i2c->stage=I2C_RUNNING; + } + + activeInt &=~I2C_TRANS_START_INT_ST_M; + p_i2c->dev->int_ena.trans_start = 1; // already enabled? why Again? + p_i2c->dev->int_clr.trans_start = 1; // so that will trigger after next 'END' + } + + if (activeInt & I2C_TXFIFO_EMPTY_INT_ST) {//should this be before Trans_start? + fillTxFifo(p_i2c); //fillTxFifo will enable/disable/clear interrupt + activeInt&=~I2C_TXFIFO_EMPTY_INT_ST; + } + + if(activeInt & I2C_RXFIFO_FULL_INT_ST){ + emptyRxFifo(p_i2c); + p_i2c->dev->int_clr.rx_fifo_full=1; + p_i2c->dev->int_ena.rx_fifo_full=1; //why? + + activeInt &=~I2C_RXFIFO_FULL_INT_ST; + } + + if(activeInt & I2C_MASTER_TRAN_COMP_INT_ST){ // each byte the master sends/recv + p_i2c->dev->int_clr.master_tran_comp = 1; + + p_i2c->byteCnt++; + + if(p_i2c->byteCnt > p_i2c->dq[p_i2c->queuePos].queueLength){// simulate Trans_start + + p_i2c->byteCnt -= p_i2c->dq[p_i2c->queuePos].queueLength; + + if(p_i2c->dq[p_i2c->queuePos].ctrl.mode==1){ // grab last characters for this dq + emptyRxFifo(p_i2c); + p_i2c->dev->int_clr.rx_fifo_full=1; + p_i2c->dev->int_ena.rx_fifo_full=1; + } + + p_i2c->queuePos++; //inc to next dq + + if(p_i2c->queuePos < p_i2c->queueCount) // load next dq address field + data + p_i2c->dev->int_ena.tx_fifo_empty=1; + + } + activeInt &=~I2C_MASTER_TRAN_COMP_INT_ST; + } + + if (activeInt & I2C_ACK_ERR_INT_ST_M) {//fatal error, abort i2c service + if (p_i2c->mode == I2C_MASTER) { + // log_e("AcK Err byteCnt=%d, queuepos=%d",p_i2c->byteCnt,p_i2c->queuePos); + if(p_i2c->byteCnt==1) i2cIsrExit(p_i2c,EVENT_ERROR_NAK,true); + else if((p_i2c->byteCnt == 2) && (p_i2c->dq[p_i2c->queuePos].ctrl.addrReq == 2)) + i2cIsrExit(p_i2c,EVENT_ERROR_NAK,true); + else i2cIsrExit(p_i2c,EVENT_ERROR_DATA_NAK,true); + } + return; + } + + if (activeInt & I2C_TIME_OUT_INT_ST_M) { + // let Gross timeout occur, Slave may release SCL before the configured timeout expires + // the Statemachine only has a 13.1ms max timout, some Devices >500ms + p_i2c->dev->int_clr.time_out =1; + activeInt &=~I2C_TIME_OUT_INT_ST; + } + + if (activeInt & I2C_TRANS_COMPLETE_INT_ST_M) { + i2cIsrExit(p_i2c,EVENT_DONE,false); + return; // no more work to do +/* + // how does slave mode act? + if (p_i2c->mode == I2C_SLAVE) { // STOP detected + // empty fifo + // dispatch callback +*/ + } + + if (activeInt & I2C_ARBITRATION_LOST_INT_ST_M) { //fatal + i2cIsrExit(p_i2c,EVENT_ERROR_ARBITRATION,true); + return; // no more work to do + } + + if (activeInt & I2C_SLAVE_TRAN_COMP_INT_ST_M) { + p_i2c->dev->int_clr.slave_tran_comp = 1; +// need to complete this ! + } + + if (activeInt & I2C_END_DETECT_INT_ST_M) { + p_i2c->dev->int_ena.end_detect = 0; + p_i2c->dev->int_clr.end_detect = 1; + p_i2c->dev->ctr.trans_start=0; + fillCmdQueue(p_i2c,true); // enable interrupts + p_i2c->dev->ctr.trans_start=1; // go for it + activeInt&=~I2C_END_DETECT_INT_ST_M; + } + + if(activeInt){ // clear unhandled if possible? What about Disabling interrupt? + p_i2c->dev->int_clr.val = activeInt; + log_e("unknown int=%x",activeInt); + // disable unhandled IRQ, + p_i2c->dev->int_ena.val = p_i2c->dev->int_ena.val & (~activeInt); + } + + activeInt = p_i2c->dev->int_status.val; // start all over if another interrupt happened + } +} + +void i2cDumpInts(uint8_t num){ +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO +uint32_t b; +log_e("%u row count INTR TX RX",num); +for(uint32_t a=1;a<=INTBUFFMAX;a++){ + b=(a+intPos[num])%INTBUFFMAX; + if(intBuff[b][0][num]!=0) log_e("[%02d] 0x%04x 0x%04x 0x%04x 0x%04x 0x%08x",b,((intBuff[b][0][num]>>16)&0xFFFF),(intBuff[b][0][num]&0xFFFF),((intBuff[b][1][num]>>16)&0xFFFF),(intBuff[b][1][num]&0xFFFF),intBuff[b][2][num]); + } +#else +log_n("enable Core Debug Level \"Error\""); +#endif +} + +i2c_err_t i2cProcQueue(i2c_t * i2c, uint32_t *readCount, uint16_t timeOutMillis){ +/* do the hard stuff here + install ISR if necessary + setup EventGroup + handle bus busy? +*/ +//log_e("procQueue i2c=%p",&i2c); +*readCount = 0; //total reads accomplished in all queue elements +if(i2c == NULL){ + return I2C_ERROR_DEV; + } +if (i2c->dev->status_reg.bus_busy){ // return error, let TwoWire() handle resetting the hardware. +/* if multi master then this if should be changed to this 03/12/2018 + if(multiMaster){// try to let the bus clear by its self + uint32_t timeOutTick = millis(); + while((i2c->dev->status_reg.bus_busy)&&(millis()-timeOutTickdev->status_reg.bus_busy){ // still busy, so die + log_i("Bus busy, reinit"); + return I2C_ERROR_BUSY; + } +*/ + log_i("Bus busy, reinit"); + return I2C_ERROR_BUSY; + } + +I2C_MUTEX_LOCK(); +/* what about co-existence with SLAVE mode? + Should I check if a slaveMode xfer is in progress and hang + until it completes? + if i2c->stage == I2C_RUNNING or I2C_SLAVE_ACTIVE +*/ +i2c->stage = I2C_DONE; // until ready + +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO +for(uint16_t i=0;inum] = 0; + intBuff[i][1][i2c->num] = 0; + intBuff[i][2][i2c->num] = 0; + } +intPos[i2c->num] = 0; +#endif +// EventGroup is used to signal transmission completion from ISR +// not always reliable. Sometimes, the FreeRTOS scheduler is maxed out and refuses request +// if that happens, this call hangs until the timeout period expires, then it continues. +if(!i2c->i2c_event){ + i2c->i2c_event = xEventGroupCreate(); + } +if(i2c->i2c_event) { + xEventGroupClearBits(i2c->i2c_event, 0xFF); + } +else {// failed to create EventGroup + log_e("eventCreate failed=%p",i2c->i2c_event); + I2C_MUTEX_UNLOCK(); + return I2C_ERROR_MEMORY; + } + +i2c_err_t reason = I2C_ERROR_OK; +i2c->mode = I2C_MASTER; + +i2c->dev->ctr.trans_start=0; // Pause Machine +i2c->dev->timeout.tout = 0xFFFFF; // max 13ms +I2C_FIFO_CONF_t f; +f.val = i2c->dev->fifo_conf.val; +f.rx_fifo_rst = 1; // fifo in reset +f.tx_fifo_rst = 1; // fifo in reset +f.nonfifo_en = 0; // use fifo mode +// need to adjust threshold based on I2C clock rate, at 100k, 30 usually works, +// sometimes the emptyRx() actually moves 31 bytes +// it hasn't overflowed yet, I cannot tell if the new byte is added while +// emptyRX() is executing or before? +f.rx_fifo_full_thrhd = 30; // 30 bytes before INT is issued +f.fifo_addr_cfg_en = 0; // no directed access +i2c->dev->fifo_conf.val = f.val; // post them all + +f.rx_fifo_rst = 0; // release fifo +f.tx_fifo_rst = 0; +i2c->dev->fifo_conf.val = f.val; // post them all + +i2c->dev->int_clr.val = 0xFFFFFFFF; // kill them All! +i2c->dev->ctr.ms_mode = 1; // master! +i2c->queuePos=0; +i2c->byteCnt=0; +uint32_t totalBytes=0; // total number of bytes to be Moved! +// convert address field to required I2C format +while(i2c->queuePos < i2c->queueCount){ // need to push these address modes upstream, to AddQueue + I2C_DATA_QUEUE_t *tdq = &i2c->dq[i2c->queuePos++]; + uint16_t taddr=0; + if(tdq->ctrl.addrReq ==2){ // 10bit address + taddr =((tdq->ctrl.addr >> 7) & 0xFE) + |tdq->ctrl.mode; + taddr = (taddr <<8) || (tdq->ctrl.addr&0xFF); + } + else { // 7bit address + taddr = ((tdq->ctrl.addr<<1)&0xFE) + |tdq->ctrl.mode; + } + tdq->ctrl.addr = taddr; // all fixed with R/W bit + totalBytes += tdq->queueLength; // total number of byte to be moved! + } +i2c->queuePos=0; + +fillCmdQueue(i2c,false); // don't enable Tx/RX irq's +// start adding command[], END irq will keep it full +//Data Fifo will be filled after trans_start is issued + +i2c->exitCode=0; +i2c->stage = I2C_STARTUP; // everything configured, now start the I2C StateMachine, and +// As soon as interrupts are enabled, the ISR will start handling them. +// it should receive a TXFIFO_EMPTY immediately, even before it +// receives the TRANS_START + +i2c->dev->int_ena.val = + I2C_ACK_ERR_INT_ENA | // (BIT(10)) Causes Fatal Error Exit + I2C_TRANS_START_INT_ENA | // (BIT(9)) Triggered by trans_start=1, initial,END + I2C_TIME_OUT_INT_ENA | //(BIT(8)) Trigger by SLAVE SCL stretching, NOT an ERROR + I2C_TRANS_COMPLETE_INT_ENA | // (BIT(7)) triggered by STOP, successful exit + I2C_MASTER_TRAN_COMP_INT_ENA | // (BIT(6)) counts each byte xfer'd, inc's queuePos + I2C_ARBITRATION_LOST_INT_ENA | // (BIT(5)) cause fatal error exit + I2C_SLAVE_TRAN_COMP_INT_ENA | // (BIT(4)) unhandled + I2C_END_DETECT_INT_ENA | // (BIT(3)) refills cmd[] list + I2C_RXFIFO_OVF_INT_ENA | //(BIT(2)) unhandled + I2C_TXFIFO_EMPTY_INT_ENA | // (BIT(1)) triggers fillTxFifo() + I2C_RXFIFO_FULL_INT_ENA; // (BIT(0)) trigger emptyRxFifo() + +if(!i2c->intr_handle){ // create ISR for either peripheral + // log_i("create ISR %d",i2c->num); + uint32_t ret=0xFFFFFFFF; // clear uninitialized var warning + switch(i2c->num){ + case 0: + ret = esp_intr_alloc(ETS_I2C_EXT0_INTR_SOURCE, 0, &i2c_isr_handler_default, i2c, &i2c->intr_handle); + break; + case 1: + ret = esp_intr_alloc(ETS_I2C_EXT1_INTR_SOURCE, 0, &i2c_isr_handler_default, i2c, &i2c->intr_handle); + break; + default :; + } + + if(ret!=ESP_OK){ + log_e("install interrupt handler Failed=%d",ret); + I2C_MUTEX_UNLOCK(); + return I2C_ERROR_MEMORY; + } + } + +//hang until it completes. + +// how many ticks should it take to transfer totalBytes thru the I2C hardware, +// add user supplied timeOutMillis to Calc Value + +portTickType ticksTimeOut = ((totalBytes*10*1000)/(i2cGetFrequency(i2c))+timeOutMillis)/portTICK_PERIOD_MS; +portTickType tBefore=xTaskGetTickCount(); + +//log_e("before startup @tick=%d will wait=%d",xTaskGetTickCount(),ticksTimeOut); + +i2c->dev->ctr.trans_start=1; // go for it + +uint32_t eBits = xEventGroupWaitBits(i2c->i2c_event,EVENT_DONE,pdFALSE,pdTRUE,ticksTimeOut); + +//log_e("after WaitBits=%x @tick=%d",eBits,xTaskGetTickCount()); + +portTickType tAfter=xTaskGetTickCount(); + +uint32_t b; +// if xEventGroupSetBitsFromISR() failed, the ISR could have succeeded but never been +// able to mark the success + +if(i2c->exitCode!=eBits){ // try to recover from O/S failure +// log_e("EventGroup Failed:%p!=%p",eBits,i2c->exitCode); + eBits=i2c->exitCode; + } + +if(!(eBits==EVENT_DONE)&&(eBits&~(EVENT_ERROR_NAK|EVENT_ERROR_DATA_NAK|EVENT_ERROR|EVENT_DONE))){ // not only Done, therefore error, exclude ADDR NAK, DATA_NAK +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO + i2cDumpI2c(i2c); + i2cDumpInts(i2c->num); +#else + log_n("I2C exitCode=0x%x",eBits); +#endif + } + +if(eBits&EVENT_DONE){ // no gross timeout + switch(i2c->error){ + case I2C_OK : + reason = I2C_ERROR_OK; + break; + case I2C_ERROR : + reason = I2C_ERROR_DEV; + break; + case I2C_ADDR_NAK: + reason = I2C_ERROR_ACK; + break; + case I2C_DATA_NAK: + reason = I2C_ERROR_ACK; + break; + case I2C_ARBITRATION: + reason = I2C_ERROR_BUS; + break; + case I2C_TIMEOUT: + reason = I2C_ERROR_TIMEOUT; + break; + default : + reason = I2C_ERROR_DEV; + } + } +else { // GROSS timeout, shutdown ISR , report Timeout + i2c->stage = I2C_DONE; + i2c->dev->int_ena.val =0; + i2c->dev->int_clr.val = 0x1FFF; + if((i2c->queuePos==0)&&(i2c->byteCnt==0)){ // Bus Busy no bytes Moved + reason = I2C_ERROR_BUSY; + eBits = eBits | EVENT_ERROR_BUS_BUSY|EVENT_ERROR|EVENT_DONE; +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR + log_e(" Busy Timeout start=0x%x, end=0x%x, =%d, max=%d error=%d",tBefore,tAfter,(tAfter-tBefore),ticksTimeOut,i2c->error); + i2cDumpI2c(i2c); + i2cDumpInts(i2c->num); +#endif + } + else { // just a timeout, some data made it out or in. + reason = I2C_ERROR_TIMEOUT; + eBits = eBits | EVENT_ERROR_TIMEOUT|EVENT_ERROR|EVENT_DONE; + +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR + log_e(" Gross Timeout Dead start=0x%x, end=0x%x, =%d, max=%d error=%d",tBefore,tAfter,(tAfter-tBefore),ticksTimeOut,i2c->error); + i2cDumpI2c(i2c); + i2cDumpInts(i2c->num); +#endif + } + } + +// offloading all EventGroups to dispatch, EventGroups in ISR is not always successful +// 11/20/2017 +// if error, need to trigger all succeeding dataQueue events with the EVENT_ERROR_PREV + +b = 0; + +while(b < i2c->queueCount){ + if(i2c->dq[b].ctrl.mode==1){ + *readCount += i2c->dq[b].position; // number of data bytes received + } + if(b < i2c->queuePos){ // before any error + if(i2c->dq[b].queueEvent){ // this data queue element has an EventGroup + xEventGroupSetBits(i2c->dq[b].queueEvent,EVENT_DONE); + } + } + else if(b == i2c->queuePos){ // last processed queue + if(i2c->dq[b].queueEvent){ // this data queue element has an EventGroup + xEventGroupSetBits(i2c->dq[b].queueEvent,eBits); + } + } + else{ // never processed queues + if(i2c->dq[b].queueEvent){ // this data queue element has an EventGroup + xEventGroupSetBits(i2c->dq[b].queueEvent,eBits|EVENT_ERROR_PREV); + } + } + b++; + } + +I2C_MUTEX_UNLOCK(); +return reason; +} + +void i2cReleaseISR(i2c_t * i2c){ +if(i2c->intr_handle){ +// log_i("Release ISR %d",i2c->num); + esp_err_t error =esp_intr_free(i2c->intr_handle); + if(error!=ESP_OK) log_e("Error releasing ISR=%d",error); + i2c->intr_handle=NULL; + } +} + +void i2cReleaseAll(i2c_t *i2c){ // release all resources, power down peripheral +// gpio pins must be released BEFORE this function or a Glitch will appear +I2C_MUTEX_LOCK(); + +i2cReleaseISR(i2c); + +if(i2c->i2c_event){ + vEventGroupDelete(i2c->i2c_event); + i2c->i2c_event = NULL; + } + +i2cFreeQueue(i2c); + +// reset the I2C hardware and shut off the clock, power it down. +if(i2c->num == 0) { + DPORT_SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG,DPORT_I2C_EXT0_RST); //reset hardware + DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG,DPORT_I2C_EXT0_CLK_EN); // shutdown hardware +} else { + DPORT_SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG,DPORT_I2C_EXT1_RST); //reset Hardware + DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG,DPORT_I2C_EXT1_CLK_EN); // shutdown Hardware + } + +I2C_MUTEX_UNLOCK(); +} +/* todo + 24Nov17 + Need to think about not usings I2C_MASTER_TRAN_COMP_INT_ST to adjust queuePos. This + INT triggers every byte. The only reason to know which byte is being transfered is + the status_reg.tx_fifo_cnt and a .txQueued to do this in the fillRxFifo(). The + same mechanism could work if an error occured in i2cErrorExit(). +*/ + diff --git a/cores/esp32/esp32-hal-i2c.h b/cores/esp32/esp32-hal-i2c.h index f26c7ab2a60..453473e5844 100644 --- a/cores/esp32/esp32-hal-i2c.h +++ b/cores/esp32/esp32-hal-i2c.h @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +// modified Nov 2017 by Chuck Todd to support Interrupt Driven I/O #ifndef _ESP32_HAL_I2C_H_ #define _ESP32_HAL_I2C_H_ @@ -21,24 +22,138 @@ extern "C" { #include #include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +// start from tools/sdk/include/soc/soc/i2c_struct.h + +typedef union { + struct { + uint32_t byte_num: 8; /*Byte_num represent the number of data need to be send or data need to be received.*/ + uint32_t ack_en: 1; /*ack_check_en ack_exp and ack value are used to control the ack bit.*/ + uint32_t ack_exp: 1; /*ack_check_en ack_exp and ack value are used to control the ack bit.*/ + uint32_t ack_val: 1; /*ack_check_en ack_exp and ack value are used to control the ack bit.*/ + uint32_t op_code: 3; /*op_code is the command 0:RSTART 1:WRITE 2:READ 3:STOP . 4:END.*/ + uint32_t reserved14: 17; + uint32_t done: 1; /*When command0 is done in I2C Master mode this bit changes to high level.*/ + }; + uint32_t val; + } I2C_COMMAND_t; + +typedef union { + struct { + uint32_t rx_fifo_full_thrhd: 5; + uint32_t tx_fifo_empty_thrhd:5; //Config tx_fifo empty threhd value when using apb fifo access * / + uint32_t nonfifo_en: 1; //Set this bit to enble apb nonfifo access. * / + uint32_t fifo_addr_cfg_en: 1; //When this bit is set to 1 then the byte after address represent the offset address of I2C Slave's ram. * / + uint32_t rx_fifo_rst: 1; //Set this bit to reset rx fifo when using apb fifo access. * / + // chuck while this bit is 1, the RX fifo is held in REST, Toggle it * / + uint32_t tx_fifo_rst: 1; //Set this bit to reset tx fifo when using apb fifo access. * / + // chuck while this bit is 1, the TX fifo is held in REST, Toggle it * / + uint32_t nonfifo_rx_thres: 6; //when I2C receives more than nonfifo_rx_thres data it will produce rx_send_full_int_raw interrupt and update the current offset address of the receiving data.* / + uint32_t nonfifo_tx_thres: 6; //when I2C sends more than nonfifo_tx_thres data it will produce tx_send_empty_int_raw interrupt and update the current offset address of the sending data. * / + uint32_t reserved26: 6; + }; + uint32_t val; + } I2C_FIFO_CONF_t; + +// end from tools/sdk/include/soc/soc/i2c_struct.h + +// External Wire.h equivalent error Codes typedef enum { - I2C_ERROR_OK, + I2C_ERROR_OK=0, I2C_ERROR_DEV, I2C_ERROR_ACK, I2C_ERROR_TIMEOUT, I2C_ERROR_BUS, - I2C_ERROR_BUSY + I2C_ERROR_BUSY, + I2C_ERROR_MEMORY, + I2C_ERROR_CONTINUE, + I2C_ERROR_NO_BEGIN } i2c_err_t; +// sync between dispatch(i2cProcQueue) and worker(i2c_isr_handler_default) +typedef enum { + //I2C_NONE=0, + I2C_STARTUP=1, + I2C_RUNNING, + I2C_DONE +} I2C_STAGE_t; + +typedef enum { + I2C_NONE=0, + I2C_MASTER, + I2C_SLAVE, + I2C_MASTERSLAVE +}I2C_MODE_t; + +// internal Error condition +typedef enum { +// I2C_NONE=0, + I2C_OK=1, + I2C_ERROR, + I2C_ADDR_NAK, + I2C_DATA_NAK, + I2C_ARBITRATION, + I2C_TIMEOUT +}I2C_ERROR_t; + +// i2c_event bits for EVENTGROUP bits +// needed to minimize change events, FreeRTOS Daemon overload, so ISR will only set values +// on Exit. Dispatcher will set bits for each dq before/after ISR completion +#define EVENT_ERROR_NAK (BIT(0)) +#define EVENT_ERROR (BIT(1)) +#define EVENT_ERROR_BUS_BUSY (BIT(2)) +#define EVENT_RUNNING (BIT(3)) +#define EVENT_DONE (BIT(4)) +#define EVENT_IN_END (BIT(5)) +#define EVENT_ERROR_PREV (BIT(6)) +#define EVENT_ERROR_TIMEOUT (BIT(7)) +#define EVENT_ERROR_ARBITRATION (BIT(8)) +#define EVENT_ERROR_DATA_NAK (BIT(9)) +#define EVENT_MASK 0x3F + +// control record for each dq entry +typedef union{ + struct { + uint32_t addr: 16; // I2C address, if 10bit must have 0x7800 mask applied, else 8bit + uint32_t mode: 1; // transaction direction 0 write, 1 read + uint32_t stop: 1; // sendStop 0 no, 1 yes + uint32_t startCmdSent: 1; // START cmd has been added to command[] + uint32_t addrCmdSent: 1; // addr WRITE cmd has been added to command[] + uint32_t dataCmdSent: 1; // all necessary DATA(READ/WRITE) cmds added to command[] + uint32_t stopCmdSent: 1; // completed all necessary commands + uint32_t addrReq: 2; // number of addr bytes need to send address + uint32_t addrSent: 2; // number of addr bytes added to FIFO + uint32_t reserved_31: 6; + }; + uint32_t val; + }I2C_DATA_CTRL_t; + +// individual dq element +typedef struct { + uint8_t *data; // datapointer for read/write buffer + uint16_t length; // size of data buffer + uint16_t position; // current position for next char in buffer (= ARDUHAL_LOG_LEVEL_NONE +#define log_n(format, ...) log_printf(ARDUHAL_LOG_FORMAT(E, format), ##__VA_ARGS__) +#else +#define log_n(format, ...) +#endif + #ifdef CONFIG_ARDUHAL_ESP_LOG #include "esp_log.h" diff --git a/docs/stickbreaker/ESP32DE_16.jpg b/docs/stickbreaker/ESP32DE_16.jpg new file mode 100644 index 00000000000..de609647183 Binary files /dev/null and b/docs/stickbreaker/ESP32DE_16.jpg differ diff --git a/docs/stickbreaker/ESP32DE_17.jpg b/docs/stickbreaker/ESP32DE_17.jpg new file mode 100644 index 00000000000..044b5e80f44 Binary files /dev/null and b/docs/stickbreaker/ESP32DE_17.jpg differ diff --git a/docs/stickbreaker/ESP32DE_18.jpg b/docs/stickbreaker/ESP32DE_18.jpg new file mode 100644 index 00000000000..4dbc4622373 Binary files /dev/null and b/docs/stickbreaker/ESP32DE_18.jpg differ diff --git a/docs/stickbreaker/README.md b/docs/stickbreaker/README.md new file mode 100644 index 00000000000..fb2a0a915bf --- /dev/null +++ b/docs/stickbreaker/README.md @@ -0,0 +1,6 @@ +# Stickbreaker Branch specific info. + +content: +* [ESP32DE_17.jpg](https://github.com/stickbreaker/arduino-esp32/blob/master/docs/stickbreaker/ESP32DE_17.jpg): Original Signal glitch on GPIO attach to peripheral +* [ESP32DE_16.jpg](https://github.com/stickbreaker/arduino-esp32/blob/master/docs/stickbreaker/ESP32DE_16.jpg): After changes suggested by @ESP32DE, over 50% reduction in glitch duration +* [ESP32DE_18.jpg](https://github.com/stickbreaker/arduino-esp32/blob/master/docs/stickbreaker/ESP32DE_18.jpg): Complete glitch prevention after additional experimentation by @stickbreaker diff --git a/libraries/Wire/docs/README.md b/libraries/Wire/docs/README.md new file mode 100644 index 00000000000..ec490dad2ab --- /dev/null +++ b/libraries/Wire/docs/README.md @@ -0,0 +1,130 @@ +# i2c communications on ESP32 + + By Official I2C protocol as [UM10204 I2C-Bus Specification and User Manual](https://Fwww.nxp.com/docs/en/user-guide/UM10204.pdf), A valid I2C transaction must be a `START`, at least one Data Byte, and a `STOP`. The Arduino environment is base on the AVR processor series, These Atmel CPU's implemention the I2C protocol allows unlimited pauses between protocol elements. The Espressif ESP32 implement enforces a Strict TimeOut between elements. This has resulted in perceived I2C bus stability issues ([834](https://github.com/espressif/arduino-esp32/issues/834),[811](https://github.com/espressif/arduino-esp32/issues/811),[741](https://github.com/espressif/arduino-esp32/issues/741),[682](https://github.com/espressif/arduino-esp32/issues/682),[659](https://github.com/espressif/arduino-esp32/issues/659) and many more. @lonerzzz created a partial solution [#751](https://github.com/espressif/arduino-esp32/pull/751). + + The I2C protocol arbitrates exclusive ownership of the bus to a single Master Device from a `START` signal to the next `STOP`. A `ReSTART` operation is just a `START` signal inserted into this `START` -> `STOP` flow. Schematically like this: + + `START` (Read or Write) -> `ReSTART` (Read or Write) -> `STOP`. + + The existing Arduino code base is reliant on the AVR's ability to infintely pause a i2c transaction. The standard coding practice of: +```c++ +// set internal address pointer in I2C EEPROM from which to read +Wire.beginTransmission(ID); +Wire.write(highByte(addr)); +Wire.write(lowByte(addr)); +uint8_t err =Wire.endTransmission(false); // don't send a STOP, just Pause I2C operations +if(err==0){ // successfully set internal address pointer + uint8_t count=Wire.requestFrom(addr,len); + if(count==0){ // read failed + Serial.print("Bad Stuff!! Read Failed\n"); + } + else {// successful read + while(Wire.avaiable()){ + Serial.print((char)Wire.read()); + } + Serial.println(); + } + } +``` +May not function correctly with the ESP32, actually **usually** will not function correctly. The current arduino-esp32 platform is built upon the [espressif-idf](https://github.com/espressif/esp-idf) which is built on FreeRTOS, a multi-process/processor operating system. The Arduino sketch is just one task executing on One of the Two processor cores. The TimeOut is triggered when the FreeRTOS Task Scheduler does a task switch between `Wire.endTransmission(false);` and the i2c activites inside `Wire.requestfrom()`. The maximum TimeOut interval is around 12ms. + +This rewrite of `Wire()` is designed to avoid this TimeOut from ever being possible. To avoid the TimeOut this library uses a queuing system that does not allow an i2c transaction to start unless a `STOP` has been issued. But, alas, this creates some incompatibilities with the pre-exisiting Arduino code base. The changes to the standard Arduino `Wire()` coding are minimal, but, they are necessary: +```c++ +// new, maybe Excessive Error Return codes for @stickbreaker:arduino-esp32 +typedef enum { + I2C_ERROR_OK=0, + I2C_ERROR_DEV, + I2C_ERROR_ACK, + I2C_ERROR_TIMEOUT, + I2C_ERROR_BUS, + I2C_ERROR_BUSY, + I2C_ERROR_MEMORY, + I2C_ERROR_CONTINUE, + I2C_ERROR_NO_BEGIN +} i2c_err_t; + +// set internal address pointer in I2C EEPROM from which to read +Wire.beginTransmission(ID); +Wire.write(highByte(addr)); +Wire.write(lowByte(addr)); +uint8_t err =Wire.endTransmission(false); +// don't send a STOP, just Pause I2C operations + +//err will be I2C_ERROR_CONTINUE (7), an indication that the preceding +//transaction has been Queued, NOT EXECUTED + +if(err == 7){ // Prior Operation has been queued +// it will be executed when the next STOP is encountered. +// if sendStop had equaled true, then a successful endTransmission() would have +// returned 0. So, if this if() was if(err==0), the Queue operation would be +// considered an error. This is the primary Difference. + + uint8_t count=Wire.requestFrom(addr,len); + if(Wire.lastError()!=0){ // complete/partial read failure + Serial.printf("Bad Stuff!!\nRead of (%d) bytes read %d bytes\nFailed" + " lastError=%d, text=%s\n", len, count, Wire.lastError(), + Wire.getErrorText(Wire.lastError())); + } + // some of the read may have executed + while(Wire.avaiable()){ + Serial.print((char)Wire.read()); + } + Serial.println(); + } +``` + +Additionally this implementation of `Wire()` includes methods to handle local buffer data blocks. These local buffer can be up to 64k-1 bytes in length. + +### New Methods: +```c++ + i2c_err_t writeTransaction(uint8_t address, uint8_t* buff, size_t size, bool sendStop);// big block handling + size_t requestFrom(uint8_t address, uint8_t* buf, size_t size, bool sendStop); + size_t transact(size_t readLen); // replacement for endTransmission(false),requestFrom(ID,readLen,true); + size_t transact(uint8_t* readBuff, size_t readLen);// bigger Block read + i2c_err_t lastError(); // Expose complete error + char * getErrorText(uint8_t err); // return char pointer for text of err + void dumpI2C(); // diagnostic dump of I2C control structure and buffers + void dumpInts(); // diagnostic dump for the last 64 different i2c Interrupts + void dumpOn(); // Execute dumpI2C() and dumpInts() after every I2C procQueue() + void dumpOff(); // turn off dumpOn() + size_t getClock(); // current i2c Clock rate + void setTimeOut(uint16_t timeOutMillis); // allows users to configure Gross Timeout + uint16_t getTimeOut(); +``` + +`transact()` coding is: +```c++ +// set internal address pointer in I2C EEPROM from which to read +Wire.beginTransmission(ID); +Wire.write(highByte(addr)); +Wire.write(lowByte(addr)); + +uint8_t count=Wire.transact(len); // transact() does both Wire.endTransmission(false); and Wire.requestFrom(ID,len,true); +if(Wire.lastError() != 0){ // complete/partial read failure + Serial.printf("Bad Stuff!! Read Failed lastError=%d\n",Wire.lastError()); + } + // some of the read may have executed +while(Wire.avaiable()){ + Serial.print((char)Wire.read()); + } +Serial.println(); + +``` + +### TIMEOUT's +The ESP32 I2C hardware, what I call the StateMachine(SM) is not documented very well, most of the the corner conditions and errata has been discovered throught trial and error. TimeOuts have been the bain of our existance. + +Most were caused by incorrect coding of ReSTART operations, but, a Valid TimeOut operation was discovered by @lonerzzz. He was using a temperature/humidity sensor that uses SCL clock stretching while it does a sample conversion. The issue we discovered was that the SM does not continue after the timeout. It treats the timeout as a failure. The SM's hardware timeout maxes out at 13.1ms, @lonerzzz sensor a (HTU21D), uses SCL stretching while it takes a measurement. These SCL stretching events can last for over 120ms. The SM will issue a TIMEOUT IRQ every 13.1ms while SCL is held low. After SCL is release the SM immediately terminates the current command queue by issuing a STOP. It does NOT continue with the READ command(a protcol violation). In @lonerzzz's case the sensor acknowledges its I2C ID and the READ command, then it starts the sample conversion while holding SCL Low. After it completes the conversion, the SCL signal is release. When the SM sees SCL go HIGH, it initates an Abort by immediately issuing a STOP signal. So, the Sample was taken but never read. + +The current library signals this occurence by returning I2C_ERROR_OK and a dataLength of 0(zero) back through the `Wire.requestFrom()` call. + +### Beta + +This **BETA** release can be compiled with ESP32 Dev Module as its target. Selecting this board allow Core Debug Level to be selected. Setting the "core debug level" to 'error' will route verbose debug out Serial (uart0) when an i2c error occurs. + +This version V0.2.0 14MAR2018 includes code to handle `BUS_BUSY` conditions. This status is usually indicative of a hardware bus glitch. The most common way for a `BUS_BUSY` condition to be created, is when the i2c peripheral has detected a low going spike on SDA and intrepreted it as another i2c MASTER device acquiring the bus. It will wait FORE EVER for the other 'Master' to complete it's transaction. Since this was a temporary signal glitch, not a secondary Master preforming operations, the only way to clear the `BUS_BUSY` condition is to reset the i2c peripheral. So, when a `BUS_BUSY` conditions is detected, a hardware reset is performed. + +This works great, as long as, there is not ACTUALLY another Master on the bus. If this Library is used in a Multi-Master i2c configuration, it will FAIL with continuous `ARBITRATION` failures, `BUS_BUSY` errors. + +Chuck. + diff --git a/libraries/Wire/examples/eeprom_size/eeprom_size.ino b/libraries/Wire/examples/eeprom_size/eeprom_size.ino new file mode 100644 index 00000000000..6e70dd2c6a8 --- /dev/null +++ b/libraries/Wire/examples/eeprom_size/eeprom_size.ino @@ -0,0 +1,292 @@ +#include +// Connect 4.7k pullups on SDA, SCL +// for ESP32 SDA(pin 21), SCL(pin 22) + +/* This sketch uses the address rollover of the 24LCxx EEPROMS to detect their size. + This detection sequence will not work with small capacity EEPROMs 24LC01 .. 24LC16. + These small EEPROMS use single byte addressing, To access more than 256 bytes, + the lc04, lc08, and lc16 EEPROMs respond as if they were multiple lc02's with + consective I2CDevice addresses. + device I2Caddr Address Range + 24LC01 0x50 0x00 .. 0x7F + 24LC02 0x50 0x00 .. 0xFF + 24LC04 0x50 0x00 .. 0xFF + 0x51 0x00 .. 0xFF + 24LC08 0x50 0x00 .. 0xFF + 0x51 0x00 .. 0xFF + 0x52 0x00 .. 0xFF + 0x53 0x00 .. 0xFF + 24LC16 0x50 0x00 .. 0xFF + 0x51 0x00 .. 0xFF + 0x52 0x00 .. 0xFF + 0x53 0x00 .. 0xFF + 0x54 0x00 .. 0xFF + 0x55 0x00 .. 0xFF + 0x56 0x00 .. 0xFF + 0x57 0x00 .. 0xFF + The 24LC32 with selectable I2C address 0x50..0x57 are 4kByte devices with an internal + address range of 0x0000 .. 0x0FFF. A Write to address 0x1000 will actually + be stored in 0x0000. This allows us to read the value of 0x0000, compare + it to the value read from 0x1000, if they are different, then this IC is + not a 24LC32. + If the Value is the same, then we have to change the byte at 0x1000 and + see if the change is reflected in 0x0000. If 0x0000 changes, then we know + that the chip is a 24LC32. We have to restore the 'changed' value so that + the data in the EEPROM is not compromised. + + This pattern of read, compare, test, restore is used for each possible size. + All that changes is the test Address, 0x1000, 0x2000, 0x4000, 0x8000. + if the 0x8000 test is does not change, then the chip is a 24LC512. +*/ + +/* after a write, the I2C device requires upto 5ms to actually program + the memory cells. During this programming cycle, the IC does not respond + to I2C requests. This feature 'NAK' polling is used to determine when + the program cycle has completed. +*/ +bool i2cReady(uint8_t ID){ +uint32_t timeout=millis(); +bool ready=false; +while((millis()-timeout<10)&&(!ready)){ // try to evoke a response from the device. +// If the it does not respond within 10ms return as a failure. + Wire.beginTransmission(ID); + int err=Wire.endTransmission(); + ready=(err==0); + if(!ready){ + if(err!=2)Serial.printf("{%d}:%s",err,Wire.getErrorText(err)); + } + } +return ready; +} + +void dispBuff(uint8_t *buf, uint16_t len,uint16_t offset){ +char asciibuf[100]; +uint8_t bufPos=0; +uint16_t adr=0; +asciibuf[0] ='\0'; +while(adr127)) ch ='.'; + bufPos+=sprintf(&asciibuf[bufPos],"%c",ch); + adr++; + } + +while(bufPos<32){ + Serial.print(" "); + bufPos++; + } +Serial.printf(" %s\n",asciibuf); +} + +/* detectWritePageSize() attempts to write a 256 byte block to an I2C EEPROM. + This large block will use the side effect of the WritePage to detect it's size. + EEPROM's have to erase a 'page' of data in their memory cell array before they + can change it. To facilitate partial page writes they contain a temporary 'WritePage' + that is used store the contents of the memory cells while their page is erase. + When data is written to the device it is merged into this temporary buffer. If the + amount of data written is longer than the temporary buffer it rolls over to the beginning + of the temporary buffer. In a 24lc32, the standard WritePage is 32 bytes, if 256 + bytes of data is written to the device, only the last 32 bytes are stored. + This side effect allow easy detect of the WritePage size. +*/ + +uint16_t detectWritePageSize(uint16_t i2cID,bool verbose=false){ +if(verbose) Serial.printf("detecting WritePage Size for (0x%02x)\n",i2cID); +uint16_t adr = 0,ps=0; +randomSeed(micros()); +adr = random(256)*256; //address is selected at random for wear leveling purposes. +uint8_t *buf =(uint8_t*)malloc(256); //restore buffer +uint8_t *buf1 =(uint8_t*)malloc(258); // write buffer +i2cReady(i2cID); // device may completing a Write Cycle, wait if necessary +Wire.beginTransmission(i2cID); +Wire.write(highByte(adr)); +Wire.write(lowByte(adr)); +uint16_t count = Wire.transact(buf,256);//save current EEPROM content for Restore +if(Wire.lastError()==0){ // successful read, now we can try the test + for(uint16_t a=0;a<256;a++){ //initialize a detectable pattern in the writeBuffer + buf1[a+2]=a; // leave room for the the address + } + buf1[0] = highByte(adr); + buf1[1] = lowByte(adr); + Wire.writeTransmission(i2cID,buf1,258); + if(Wire.lastError()==0){ // wait for the write to complete + if(i2cReady(i2cID)){ + Wire.beginTransmission(i2cID); + Wire.write(highByte(adr)); + Wire.write(lowByte(adr)); + Wire.transact(&buf1[2],256); + if(Wire.lastError()==0){ + ps = 256-buf1[2]; // if the read succeeded, the byte read from offset 0x0 + // can be used to calculate the WritePage size. On a 24lc32 with a 32byte + // WritePage it will contain 224, therefore 256-224 = 32 byte Writepage. + if(verbose){ + Serial.printf("Origonal data\n",i2cID); + dispBuff(buf,256,adr); + Serial.printf("\n OverWritten with\n"); + dispBuff(&buf1[2],256,adr); + Serial.printf("Calculated Write Page is %d\n",ps); + } + memmove(&buf1[2],buf,256); // copy the savebuffer back into + if(i2cReady(i2cID)){ + Wire.writeTransmission(i2cID,buf1,ps+2); // two address bytes plus WritePage + } + if(Wire.lastError()!=0){ + Serial.printf("unable to Restore prom\n"); + if(i2cReady(i2cID)){ + Wire.beginTransmission(i2cID); + Wire.write(highByte(adr)); + Wire.write(lowByte(adr)); + Wire.transact(&buf1[2],256); + Serial.printf("\n Restored to\n"); + dispBuff(&buf1[2],256,adr); + } + } + } + } + } + } +free(buf1); +free(buf); +return ps; +} + +/* eepromSize() only works on 24LC32 .. 24LC512 eeprom, + the smaller 24LC01 .. 24LC16 use one byte addressings. +*/ +void eepromSize(){ +Serial.println("Discovering eeprom sizes 0x50..0x57"); +uint8_t ID=0x50,i; +uint16_t size; +char buf[256]; +while(ID<0x58){ + i=0; + size = 0x1000; // Start at 4k, 16bit address devices, + i += sprintf_P(&buf[i],PSTR("0x%02X: "),ID); + if(i2cReady(ID)) { // EEPROM answered + uint8_t zeroByte; + Wire.beginTransmission(ID); + Wire.write((uint8_t)0); // set address ptr to 0, two bytes High + Wire.write((uint8_t)0); // set address ptr to 0, two bytes Low + uint8_t err=Wire.endTransmission(); + if(err==0){// worked, device exists at this ID + err=Wire.requestFrom(ID,(uint8_t)1); + if(err==1){// got the value of the byte at address 0 + zeroByte=Wire.read(); + uint8_t saveByte,testByte; + do{ + if(i2cReady(ID)){ + Wire.beginTransmission(ID); + Wire.write(highByte(size)); // set next test address + Wire.write(lowByte(size)); + Wire.endTransmission(); + err=Wire.requestFrom(ID,(uint8_t)1); + if(err==1){ + saveByte=Wire.read(); + if(saveByte == zeroByte) { // have to test it + Wire.beginTransmission(ID); + Wire.write(highByte(size)); // set next test address + Wire.write(lowByte(size)); + Wire.write((uint8_t)~zeroByte); // change it + err=Wire.endTransmission(); + if(err==0){ // changed it + if(!i2cReady(ID)){ + i+=sprintf_P(&buf[i],PSTR(" notReady2.\n")); + Serial.print(buf); + ID++; + break; + } + Wire.beginTransmission(ID); + Wire.write((uint8_t)0); // address 0 byte High + Wire.write((uint8_t)0); // address 0 byte Low + err=Wire.endTransmission(); + if(err==0){ + err=Wire.requestFrom(ID,(uint8_t)1); + if(err==1){ // now compare it + testByte=Wire.read(); + } + else { + testByte=~zeroByte; // error out + } + } + else { + testByte=~zeroByte; + } + } + else { + testByte = ~zeroByte; + } + + //restore byte + if(!i2cReady(ID)){ + i+=sprintf_P(&buf[i],PSTR(" notReady4.\n")); + Serial.print(buf); + ID++; + break; + } + Wire.beginTransmission(ID); + Wire.write(highByte(size)); // set next test address + Wire.write(lowByte(size)); + Wire.write((uint8_t)saveByte); // restore it + Wire.endTransmission(); + } + else testByte = zeroByte; // They were different so the eeprom Is Bigger + } + else testByte=~zeroByte; + } + else testByte=~zeroByte; + if(testByte==zeroByte){ + size = size <<1; + } + }while((testByte==zeroByte)&&(size>0)); + if(size==0) i += sprintf_P(&buf[i],PSTR("64k Bytes")); + else i+=sprintf_P(&buf[i],PSTR("%2uk Bytes"),size/1024); + i+=sprintf_P(&buf[i],PSTR(" WritePage=%3u"),detectWritePageSize(ID,false)); + if(!i2cReady(ID)){ + i+=sprintf_P(&buf[i],PSTR(" notReady3.\n")); + Serial.print(buf); + ID++; + continue; + } + Wire.beginTransmission(ID); + Wire.write((uint8_t)0); // set address ptr to 0, two bytes High + Wire.write((uint8_t)0); // set address ptr to 0, two bytes Low + err=Wire.endTransmission(); + if(err==0){ + err= Wire.requestFrom(ID,(uint8_t)1); + if (err==1) { + testByte = Wire.read(); + if(testByte != zeroByte){ //fix it + Wire.beginTransmission(ID); + Wire.write((uint8_t)0); // set address ptr to 0, two bytes High + Wire.write((uint8_t)0); // set address ptr to 0, two bytes Low + Wire.write(zeroByte); //Restore + err=Wire.endTransmission(); + } + } + } + } + else i+=sprintf_P(&buf[i],PSTR("Read 0 Failure")); + } + else i+=sprintf_P(&buf[i],PSTR("Write Adr 0 Failure")); + + } + else i+=sprintf_P(&buf[i],PSTR("Not Present")); + Serial.println(buf); + ID++; + } +} + +void setup(){ + Serial.begin(115200); + Wire.begin(); + eepromSize(); +} + +void loop(){ +} \ No newline at end of file diff --git a/libraries/Wire/examples/i2c_scan/i2c_scan.ino b/libraries/Wire/examples/i2c_scan/i2c_scan.ino new file mode 100644 index 00000000000..5fede726cc2 --- /dev/null +++ b/libraries/Wire/examples/i2c_scan/i2c_scan.ino @@ -0,0 +1,30 @@ +#include + +void scan(){ +Serial.println("\n Scanning I2C Addresses"); +uint8_t cnt=0; +for(uint8_t i=0;i<0x7F;i++){ + Wire.beginTransmission(i); + uint8_t ec=Wire.endTransmission(true); // if device exists on bus, it will aCK + if(ec==0){ // Device ACK'd + if(i<16)Serial.print('0'); + Serial.print(i,HEX); + cnt++; + } + else Serial.print(".."); // no one answered + Serial.print(' '); + if ((i&0x0f)==0x0f)Serial.println(); + } +Serial.print("Scan Completed, "); +Serial.print(cnt); +Serial.println(" I2C Devices found."); +} + +void setup(){ +Serial.begin(115200); +Wire.begin(); +scan(); +} + +void loop(){ +} \ No newline at end of file diff --git a/libraries/Wire/keywords.txt b/libraries/Wire/keywords.txt index 3344011d406..27cdda3be37 100644 --- a/libraries/Wire/keywords.txt +++ b/libraries/Wire/keywords.txt @@ -12,14 +12,23 @@ begin KEYWORD2 setClock KEYWORD2 -setClockStretchLimit KEYWORD2 beginTransmission KEYWORD2 endTransmission KEYWORD2 requestFrom KEYWORD2 -send KEYWORD2 -receive KEYWORD2 onReceive KEYWORD2 onRequest KEYWORD2 +requestFrom KEYWORD2 +writeTransaction KEYWORD2 +transact KEYWORD2 +lastError KEYWORD2 +getErrorText KEYWORD2 +write KEYWORD2 +read KEYWORD2 +available KEYWORD2 +peek KEYWORD2 +flush KEYWORD2 +reset KEYWORD2 + ####################################### # Instances (KEYWORD2) diff --git a/libraries/Wire/library.properties b/libraries/Wire/library.properties index ef29154c8e7..b85b1296bb6 100644 --- a/libraries/Wire/library.properties +++ b/libraries/Wire/library.properties @@ -1,9 +1,9 @@ name=Wire -version=1.0 -author=Hristo Gochkov -maintainer=Hristo Gochkov -sentence=Allows the communication between devices or sensors connected via Two Wire Interface Bus. For esp8266 boards. -paragraph= +version=2.0 +author=StickBreaker github.com +maintainer=StickBreaker at GitHub.com +sentence=V2.0 Rewritten to increase stability by using ISR methods. Allows the communication between devices or sensors connected via Two Wire Interface Bus. For esp32 boards. +paragraph=The origional V1.0 was written by Hristo Gochkov . This new version uses Interrupts and a better understanding of the hardware. As a side benifit of using interrupts, local buffers can now be used, allowing upto 64k-1 byte transfers. The ESP32's Hardware does not allow a naked ReStart (sendStop=false). All calls that end with sendStop=false; are Queued and only executed when a STOP is encountered (endTransmission(true),requestFrom(id,len,true)). category=Signal Input/Output -url=http://arduino.cc/en/Reference/Wire +url=https://github.com/stickbreaker/arduino-esp32 architectures=esp32 diff --git a/libraries/Wire/src/Wire.cpp b/libraries/Wire/src/Wire.cpp index ba670eaf63a..d504d977c7c 100644 --- a/libraries/Wire/src/Wire.cpp +++ b/libraries/Wire/src/Wire.cpp @@ -19,6 +19,7 @@ Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts Modified December 2014 by Ivan Grokhotkov (ivan@esp8266.com) - esp8266 support Modified April 2015 by Hrsto Gochkov (ficeto@ficeto.com) - alternative esp8266 support + Modified Nov 2017 by Chuck Todd (ctodd@cableone.net) - ESP32 ISR Support */ extern "C" { @@ -38,114 +39,390 @@ TwoWire::TwoWire(uint8_t bus_num) ,i2c(NULL) ,rxIndex(0) ,rxLength(0) + ,rxQueued(0) ,txIndex(0) ,txLength(0) ,txAddress(0) + ,txQueued(0) ,transmitting(0) -{} - + ,last_error(I2C_ERROR_OK) + ,_timeOutMillis(50) + ,_dump(false) + {} + +TwoWire::~TwoWire(){ +flush(); +i2cDetachSCL(i2c,scl); // detach pins before resetting I2C perpherial +i2cDetachSDA(i2c,sda); // else a glitch will appear on the i2c bus +if(i2c){ + i2cReleaseAll(i2c); + i2c=NULL; + } +} + void TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency) { - if(sdaPin < 0) { - if(num == 0) { - sdaPin = SDA; - } else { - return; - } + if(sdaPin < 0) { // default param passed + if(num == 0) { + if(sda==-1) sdaPin = SDA; //use Default Pin + else sdaPin = sda; // reuse prior pin + } + else { + if(sda==-1) { + log_e("no Default SDA Pin for Second Peripheral"); + return; //no Default pin for Second Peripheral + } + else sdaPin = sda; // reuse prior pin + } } - if(sclPin < 0) { - if(num == 0) { - sclPin = SCL; - } else { - return; + if(sclPin < 0) { // default param passed + if(num == 0) { + if(scl==-1) sclPin = SCL; // use Default pin + else sclPin = scl; // reuse prior pin + } + else { + if(scl==-1){ + log_e("no Default SCL Pin for Second Peripheral"); + return; //no Default pin for Second Peripheral } + else sclPin = scl; // reuse prior pin + } } - if(i2c == NULL) { - i2c = i2cInit(num, 0, false); - if(i2c == NULL) { - return; - } - } + if(!initHardware(sdaPin, sclPin, frequency)) return; + flush(); + +} + +void TwoWire::setTimeOut(uint16_t timeOutMillis){ + _timeOutMillis = timeOutMillis; +} + +uint16_t TwoWire::getTimeOut(){ + return _timeOutMillis; +} + +void TwoWire::setClock(uint32_t frequency) +{ i2cSetFrequency(i2c, frequency); +} - if(sda >= 0 && sda != sdaPin) { - i2cDetachSDA(i2c, sda); - } +bool TwoWire::initHardware(int sdaPin, int sclPin, uint32_t frequency){ - if(scl >= 0 && scl != sclPin) { - i2cDetachSCL(i2c, scl); - } + i2cDetachSCL(i2c,scl); // detach pins before resetting I2C perpherial + i2cDetachSDA(i2c,sda); // else a glitch will appear on the i2c bus + i2c = i2cInit(num);// i2cInit() now performs a hardware reset + if(i2c == NULL) { + return false; + } + + if(frequency==0) {// don't change existing frequency + frequency = i2cGetFrequency(i2c); + } + if(frequency==0) frequency = 100000L; // default to 100khz + + i2cSetFrequency(i2c, frequency); sda = sdaPin; scl = sclPin; +// 03/15/2018 What about MultiMaster? How can I be polite and still catch glitches? + +// 03/10/2018 test I2C bus before attach. +// if the bus is not 'clear' try the recommended recovery sequence, START, 9 Clocks, STOP + digitalWrite(sda,HIGH); + digitalWrite(scl,HIGH); + pinMode(sda,PULLUP|OPEN_DRAIN|OUTPUT|INPUT); + pinMode(scl,PULLUP|OPEN_DRAIN|OUTPUT|INPUT); + + if(!digitalRead(sda)||!digitalRead(scl)){ // bus in busy state + log_e("invalid state sda=%d, scl=%d\n",digitalRead(sda),digitalRead(scl)); + digitalWrite(sda,HIGH); + digitalWrite(scl,HIGH); + delayMicroseconds(5); + digitalWrite(sda,LOW); + for(uint8_t a=0; a<9;a++){ + delayMicroseconds(5); + digitalWrite(scl,LOW); + delayMicroseconds(5); + digitalWrite(scl,HIGH); + } + delayMicroseconds(5); + digitalWrite(sda,HIGH); + } i2cAttachSDA(i2c, sda); i2cAttachSCL(i2c, scl); + + if(!digitalRead(sda)||!digitalRead(scl)){ // bus in busy state + log_e("Bus Invalid State, TwoWire() Can't init"); + return false; // bus is busy + } - flush(); + return true; +} - i2cInitFix(i2c); +/*@StickBreaker common handler for processing the queued commands +*/ +i2c_err_t TwoWire::processQueue(uint32_t * readCount){ + last_error=i2cProcQueue(i2c,readCount,_timeOutMillis); + if(last_error==I2C_ERROR_BUSY){ // try to clear the bus + if(initHardware(sda,scl,getClock())){ + last_error=i2cProcQueue(i2c,readCount,_timeOutMillis); + } + } + + rxIndex = 0; + rxLength = rxQueued; + rxQueued = 0; + txQueued = 0; // the SendStop=true will restart all Queueing + if(_dump){ + i2cDumpI2c(i2c); + i2cDumpInts(num); + } + i2cFreeQueue(i2c); + return last_error; +} + +/* @stickBreaker 11/2017 fix for ReSTART timeout, ISR +*/ +uint8_t TwoWire::requestFrom(uint16_t address, uint8_t size, bool sendStop){ +//use internal Wire rxBuffer, multiple requestFrom()'s may be pending, try to share rxBuffer + + uint16_t cnt = rxQueued; // currently queued reads, next available position in rxBuffer + if(cnt<(I2C_BUFFER_LENGTH-1)){ // any room left in rxBuffer + if((size+cnt)>I2C_BUFFER_LENGTH) + size = (I2C_BUFFER_LENGTH-cnt); + rxQueued += size; + } + else { // no room to receive more! + log_e("rxBuff overflow %d",cnt+size); + cnt = 0; + last_error = I2C_ERROR_MEMORY; + flush(); + return cnt; + } + + return requestFrom(address, &rxBuffer[cnt],size,sendStop); + } + +uint16_t TwoWire::requestFrom(uint16_t address, uint8_t * readBuff, uint16_t size, bool sendStop){ + uint32_t cnt=0; + last_error =i2cAddQueueRead(i2c,address,readBuff,size,sendStop,NULL); + if(last_error==I2C_ERROR_OK){ // successfully queued the read + if(sendStop){ //now actually process the queued commands + last_error = processQueue(&cnt); + } + else { // stop not received, so wait for I2C stop, + last_error=I2C_ERROR_CONTINUE; + cnt = 0; + } + } + else {// only possible error is I2C_ERROR_MEMORY + cnt = 0; + } + return cnt; } -void TwoWire::setClock(uint32_t frequency) -{ - i2cSetFrequency(i2c, frequency); +/* stickBreaker Nov 2017 ISR, and bigblock 64k-1 +*/ +i2c_err_t TwoWire::writeTransmission(uint16_t address, uint8_t *buff, uint16_t size, bool sendStop){ +// will destroy any partially created beginTransaction() +log_i("i2c=%p",i2c); +last_error=i2cAddQueueWrite(i2c,address,buff,size,sendStop,NULL); + +if(last_error==I2C_ERROR_OK){ //queued + if(sendStop){ //now actually process the queued commands, including READs + uint32_t dummy; + last_error=processQueue(&dummy); + } + else { // stop not received, so wait for I2C stop, + last_error=I2C_ERROR_CONTINUE; + } + } +txIndex=0; +txLength=0; +transmitting = 0; +return last_error; } -size_t TwoWire::requestFrom(uint8_t address, size_t size, bool sendStop) -{ - if(size > I2C_BUFFER_LENGTH) { - size = I2C_BUFFER_LENGTH; +i2c_err_t TwoWire::readTransmission(uint16_t address, uint8_t *buff, uint16_t size, bool sendStop){ + +last_error=i2cAddQueueRead(i2c,address,buff,size,sendStop,NULL); + +if(last_error==I2C_ERROR_OK){ //queued + if(sendStop){ //now actually process the queued commands, including READs + uint32_t dummy; + last_error=processQueue(&dummy); } - size_t read = (i2cRead(i2c, address, false, rxBuffer, size, sendStop) == 0)?size:0; - rxIndex = 0; - rxLength = read; - return read; + else { // stop not received, so wait for I2C stop, + last_error=I2C_ERROR_CONTINUE; + } + } +return last_error; } -uint8_t TwoWire::endTransmission(uint8_t sendStop) -{ - int8_t ret = i2cWrite(i2c, txAddress, false, txBuffer, txLength, sendStop); - txIndex = 0; - txLength = 0; - transmitting = 0; - return ret; +/*stickbreaker Dump i2c Interrupt buffer, i2c isr Debugging +*/ +void TwoWire::dumpInts(){ + i2cDumpInts(num); +} + +/*stickbreaker i2c isr Debugging +*/ +size_t TwoWire::getClock(){ + return i2cGetFrequency(i2c); +} + +/*stickbreaker simple ReSTART handling using internal Wire data buffers +*/ +uint8_t TwoWire::transact(uint8_t readLen){ // Assumes Wire.beginTransaction(),Wire.write() +// this command replaces Wire.endTransmission(false) and Wire.requestFrom(readLen,true); +if(transmitting){ + last_error = static_cast(endTransmission(false)); + } + +if(last_error==I2C_ERROR_CONTINUE){ // must have queued the Write + uint8_t cnt = requestFrom(txAddress,readLen,true); + return cnt; + } +else { + last_error = I2C_ERROR_NO_BEGIN; + return 0; + } +} + +/*stickbreaker isr ReSTART with external read Buffer +*/ +uint16_t TwoWire::transact(uint8_t * readBuff, uint16_t readLen){ // Assumes Wire.beginTransaction(),Wire.write() +// this command replaces Wire.endTransmission(false) and Wire.requestFrom(readLen,true); +if(transmitting){ + last_error = static_cast(endTransmission(false)); + } + +if(last_error==I2C_ERROR_CONTINUE){ // must have queued the write + size_t cnt = requestFrom(txAddress,readBuff,readLen,true); + return cnt; + } +else { + last_error = I2C_ERROR_NO_BEGIN; + return 0; + } +} + +/*stickbreaker isr +*/ +uint8_t TwoWire::endTransmission(bool sendStop){ // Assumes Wire.beginTransaction(), Wire.write() +// this command replaces Wire.endTransmission(true) + +if(transmitting==1){ +// log_e("txQueued=%d txLength=%d stop %d",txQueued,txLength,sendStop); + last_error =i2cAddQueueWrite(i2c,txAddress,&txBuffer[txQueued],txLength-txQueued,sendStop,NULL); //queue tx element + + if(last_error == I2C_ERROR_OK){ + if(sendStop){ + uint32_t dummy; + last_error = processQueue(&dummy); + } + else { // queued because it had sendStop==false + // txlength is howmany bytes in txbufferhave been use + txQueued = txLength; + last_error = I2C_ERROR_CONTINUE; + } + } + } +else { + last_error= I2C_ERROR_NO_BEGIN; + flush(); + } +txIndex = 0; +txLength =0; +transmitting = 0; +return last_error; +} + +/* stickbreaker Nov2017 better error reporting +*/ +uint8_t TwoWire::lastError(){ + return (uint8_t)last_error; +} + +const char ERRORTEXT[] = + "OK\0" + "DEVICE\0" + "ACK\0" + "TIMEOUT\0" + "BUS\0" + "BUSY\0" + "MEMORY\0" + "CONTINUE\0" + "NO_BEGIN\0" + "\0"; + + +char * TwoWire::getErrorText(uint8_t err){ +uint8_t t = 0; +bool found=false; +char * message=(char*)&ERRORTEXT; + +while((!found)&&(message[0])){ + found = t==err; + if(!found) { + message = message +strlen(message)+1; + t++; + } + } +if(!found) return NULL; +else return message; } uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop) +{ + return requestFrom(static_cast(address), static_cast(quantity), static_cast(sendStop)); +} + +uint8_t TwoWire::requestFrom(uint16_t address, uint8_t quantity, uint8_t sendStop) { return requestFrom(address, static_cast(quantity), static_cast(sendStop)); } uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity) +{ + return requestFrom(static_cast(address), static_cast(quantity), true); +} + +uint8_t TwoWire::requestFrom(uint16_t address, uint8_t quantity) { return requestFrom(address, static_cast(quantity), true); } uint8_t TwoWire::requestFrom(int address, int quantity) { - return requestFrom(static_cast(address), static_cast(quantity), true); + return requestFrom(static_cast(address), static_cast(quantity), true); } uint8_t TwoWire::requestFrom(int address, int quantity, int sendStop) { - return requestFrom(static_cast(address), static_cast(quantity), static_cast(sendStop)); + return static_cast(requestFrom(static_cast(address), static_cast(quantity), static_cast(sendStop))); } -void TwoWire::beginTransmission(uint8_t address) +void TwoWire::beginTransmission(uint16_t address) { transmitting = 1; txAddress = address; - txIndex = 0; - txLength = 0; + txIndex = txQueued; // allow multiple beginTransmission(),write(),endTransmission(false) until endTransmission(true) + txLength = txQueued; } void TwoWire::beginTransmission(int address) { - beginTransmission((uint8_t)address); + beginTransmission(static_cast(address)); +} + +void TwoWire::beginTransmission(uint8_t address) +{ + beginTransmission(static_cast(address)); } uint8_t TwoWire::endTransmission(void) @@ -153,6 +430,11 @@ uint8_t TwoWire::endTransmission(void) return endTransmission(true); } +uint8_t TwoWire::endTransmission(uint8_t sendStop) +{ + return endTransmission(static_cast(sendStop)); +} + size_t TwoWire::write(uint8_t data) { if(transmitting) { @@ -209,13 +491,10 @@ void TwoWire::flush(void) rxLength = 0; txIndex = 0; txLength = 0; + rxQueued = 0; + txQueued = 0; + i2cFreeQueue(i2c); // cleanup } -void TwoWire::reset(void) -{ - i2cReset( i2c ); - i2c = NULL; - begin( sda, scl ); -} TwoWire Wire = TwoWire(0); diff --git a/libraries/Wire/src/Wire.h b/libraries/Wire/src/Wire.h index d9a7a752088..09c2ac6d4bb 100644 --- a/libraries/Wire/src/Wire.h +++ b/libraries/Wire/src/Wire.h @@ -19,6 +19,7 @@ Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts Modified December 2014 by Ivan Grokhotkov (ivan@esp8266.com) - esp8266 support Modified April 2015 by Hrsto Gochkov (ficeto@ficeto.com) - alternative esp8266 support + Modified November 2017 by Chuck Todd to use ISR and increase stability. */ #ifndef TwoWire_h @@ -29,7 +30,10 @@ #include "freertos/queue.h" #include "Stream.h" +#define STICKBREAKER "V0.2.3" #define I2C_BUFFER_LENGTH 128 +typedef void(*user_onRequest)(void); +typedef void(*user_onReceive)(uint8_t*, int); class TwoWire: public Stream { @@ -42,28 +46,68 @@ class TwoWire: public Stream uint8_t rxBuffer[I2C_BUFFER_LENGTH]; uint16_t rxIndex; uint16_t rxLength; + uint16_t rxQueued; //@stickBreaker uint8_t txBuffer[I2C_BUFFER_LENGTH]; uint16_t txIndex; uint16_t txLength; - uint8_t txAddress; + uint16_t txAddress; + uint16_t txQueued; //@stickbreaker uint8_t transmitting; +/* slave Mode, not yet Stickbreaker + static user_onRequest uReq[2]; + static user_onReceive uRcv[2]; + void onRequestService(void); + void onReceiveService(uint8_t*, int); +*/ + i2c_err_t last_error; // @stickBreaker from esp32-hal-i2c.h + i2c_err_t processQueue(uint32_t *readCount); + uint16_t _timeOutMillis; + bool _dump; + bool initHardware(int sdaPin, int sclPin, uint32_t frequency); public: TwoWire(uint8_t bus_num); - void begin(int sda=-1, int scl=-1, uint32_t frequency=100000); - void setClock(uint32_t); + ~TwoWire(); + void begin(int sda=-1, int scl=-1, uint32_t frequency=0); + //defaults bus:0 sda=SDA, scl=SCL, frequency =100khz via variant pins_arduino.h + // bus:1 unspecified, emits Log_E() + void setClock(uint32_t); // change bus clock without initing hardware + void beginTransmission(uint16_t); + uint8_t endTransmission(bool); + uint8_t requestFrom(uint16_t address, uint8_t size, bool sendStop); +//@stickBreaker for big blocks and ISR model + i2c_err_t writeTransmission(uint16_t address, uint8_t* buff, uint16_t size, bool sendStop=true); + i2c_err_t readTransmission(uint16_t address, uint8_t* buff, uint16_t size, bool sendStop=true); + uint16_t requestFrom(uint16_t address, uint8_t* buf, uint16_t size, bool sendStop); + uint8_t transact(uint8_t readLen); + uint16_t transact(uint8_t* readBuff, uint16_t readLen); + uint8_t lastError(); + char * getErrorText(uint8_t err); + void dumpOn(){_dump=true;} + void dumpOff(){_dump=false;} + bool getDump(){return _dump;} + void dumpInts(); + void dumpI2C(){i2cDumpI2c(i2c);} + size_t getClock(); // current bus clock rate in hz + void setTimeOut(uint16_t timeOutMillis); + uint16_t getTimeOut(); +// void beginTransmission(uint8_t); void beginTransmission(int); uint8_t endTransmission(void); uint8_t endTransmission(uint8_t); - size_t requestFrom(uint8_t address, size_t size, bool sendStop); - uint8_t requestFrom(uint8_t, uint8_t); uint8_t requestFrom(uint8_t, uint8_t, uint8_t); + uint8_t requestFrom(uint16_t, uint8_t, uint8_t); uint8_t requestFrom(int, int); uint8_t requestFrom(int, int, int); + uint8_t requestFrom(uint16_t, uint8_t); + + void onReceive( void (*)(int) ); + void onRequest( void (*)(void) ); + size_t write(uint8_t); size_t write(const uint8_t *, size_t); @@ -72,8 +116,6 @@ class TwoWire: public Stream int peek(void); void flush(void); - void reset(void); - inline size_t write(const char * s) { return write((uint8_t*) s, strlen(s)); @@ -98,4 +140,9 @@ class TwoWire: public Stream extern TwoWire Wire; + +/* +V0.2.2 13APR2018 preserve custom SCL,SDA,Frequency when no parameters passed to begin() +V0.2.1 15MAR2018 Hardware reset, Glitch prevention, adding destructor for second i2c testing +*/ #endif