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..9155eb81d2a 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,19 +64,106 @@ 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}, + {(volatile i2c_dev_t *)(DR_REG_I2C1_EXT_BASE_FIXED), 1,I2C_NONE,I2C_NONE,I2C_ERROR_OK,NULL,NULL,NULL,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}, + {(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} }; #endif +/* Stickbreaker added for ISR 11/2017 +*/ +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){ + + 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 + 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 + 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){ + // 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){ +// 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. + 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){ @@ -121,260 +218,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; - } + 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; } -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_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) @@ -425,8 +288,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) { if(i2c_num > 1){ return NULL; @@ -450,24 +313,20 @@ i2c_t * i2cInit(uint8_t i2c_num, uint16_t slave_addr, bool addr_10bit_en) 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(); return i2c; @@ -478,8 +337,8 @@ void i2cInitFix(i2c_t * i2c){ 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; @@ -507,3 +366,814 @@ void i2cReset(i2c_t* i2c){ I2C_MUTEX_UNLOCK(); } +/* Stickbreaker ISR mode debug support +*/ +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR +#define INTBUFFMAX 64 +static uint32_t intBuff[INTBUFFMAX][3]; +static uint32_t intPos=0; +#endif +/* 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); +log_e("dev=%p",i2c->dev); +log_e("lock=%p",i2c->lock); +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 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 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 +*/ +bool readEncountered = false; +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 || readEncountered)){ + 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); + } + } + } +//11/23/2017 overlap tx/rx/tx +// else readEncountered = true; + + if(full) readEncountered =false; //tx possibly needs more + +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR + +// update debug buffer tx counts + cnt += intBuff[intPos][1]>>16; + intBuff[intPos][1] = (intBuff[intPos][1]&0xFFFF)|(cnt<<16); +#endif + + if(!(full||readEncountered)) a++; // check next buffer for tx + } + +if((!full) || readEncountered || (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_ERROR +// update Debug rxCount + cnt += (intBuff[intPos][1])&&0xffFF; + intBuff[intPos][1] = (intBuff[intPos][1]&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){ +//log_e("isr Entry=%p",arg); +i2c_t* p_i2c = (i2c_t*) arg; // recover data +uint32_t activeInt = p_i2c->dev->int_status.val&0x1FFF; +//log_e("int=%x",activeInt); +//dumpI2c(p_i2c); + +portBASE_TYPE HPTaskAwoken = pdFALSE,xResult; + +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; +// i2cDumpI2c(p_i2c); +// i2cDumpInts(); + return; + } +while (activeInt != 0) { // Ordering of 'if(activeInt)' statements is important, don't change +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR + if(activeInt==(intBuff[intPos][0]&0x1fff)){ + intBuff[intPos][0] = (((intBuff[intPos][0]>>16)+1)<<16)|activeInt; + } + else{ + intPos++; + intPos %= INTBUFFMAX; + intBuff[intPos][0]=(1<<16)|activeInt; + intBuff[intPos][1] = 0; + } + + intBuff[intPos][2] = xTaskGetTickCountFromISR(); // when IRQ fired +#endif + uint32_t oldInt =activeInt; + + 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) {//fatal death Happens Here + i2cIsrExit(p_i2c,EVENT_ERROR_TIMEOUT,true); + return; + } + + 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(){ +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR +uint32_t b; +log_e("row count INTR TX RX"); +for(uint32_t a=1;a<=INTBUFFMAX;a++){ + b=(a+intPos)%INTBUFFMAX; + if(intBuff[b][0]!=0) log_e("[%02d] 0x%04x 0x%04x 0x%04x 0x%04x 0x%08x",b,((intBuff[b][0]>>16)&0xFFFF),(intBuff[b][0]&0xFFFF),((intBuff[b][1]>>16)&0xFFFF),(intBuff[b][1]&0xFFFF),intBuff[b][2]); + } +#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? + do I load command[] or just pass that off to the ISR +*/ +//log_e("procQueue i2c=%p",&i2c); +*readCount = 0; //total reads accomplished in all queue elements +if(i2c == NULL){ + return I2C_ERROR_DEV; + } + +I2C_MUTEX_LOCK(); +/* what about co-existance 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_ERROR +memset(intBuff,0,sizeof(intBuff)); +intPos=0; +#endif + +if(!i2c->i2c_event){ + i2c->i2c_event = xEventGroupCreate(); + } +if(i2c->i2c_event) { + uint32_t ret=xEventGroupClearBits(i2c->i2c_event, 0xFF); + +// log_e("after clearBits(%p)=%p",i2c->i2c_event,ret); + } +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){ + 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)) causes Fatal error Exit + 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 I2C_0 only, +// log_e("create ISR"); + uint32_t ret = esp_intr_alloc(ETS_I2C_EXT0_INTR_SOURCE, 0, &i2c_isr_handler_default, i2c, &i2c->intr_handle); + 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 /(i2cGetFrequency(i2c)/(10*1000)))+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 + i2cDumpI2c(i2c); + i2cDumpInts(); + } + +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; + reason = I2C_ERROR_TIMEOUT; + eBits = eBits | EVENT_ERROR_TIMEOUT|EVENT_ERROR|EVENT_DONE; + log_e(" Gross Timeout Dead st=0x%x, ed=0x%x, =%d, max=%d error=%d",tBefore,tAfter,(tAfter-tBefore),ticksTimeOut,i2c->error); + i2cDumpI2c(i2c); + i2cDumpInts(); + } + +// 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; +} + +i2c_err_t i2cReleaseISR(i2c_t * i2c){ +if(i2c->intr_handle){ + esp_err_t error =esp_intr_free(i2c->intr_handle); +// log_e("released ISR=%d",error); + i2c->intr_handle=NULL; + } +if(i2c->i2c_event){ + vEventGroupDelete(i2c->i2c_event); + i2c->i2c_event = NULL; + } +return i2cFreeQueue(i2c); +} + +/* 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 + to decide where to store a READ or if an error occured. It may be possible to use + 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..ce120c02906 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,25 +22,137 @@ 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_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/libraries/Wire/docs/README.md b/libraries/Wire/docs/README.md new file mode 100644 index 00000000000..dd16b82f1c0 --- /dev/null +++ b/libraries/Wire/docs/README.md @@ -0,0 +1,116 @@ +# 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 + err=Wire.requestFrom(addr,len); + if(err==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. + + err=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, err, 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 + void dumpInts(); // diagnostic dump for the last 64 different i2c Interrupts + 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 err=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(); + +``` + +This **APLHA** release should be compiled with ESP32 Dev Module as its target, and +Set the "core debug level" to 'error' + +There is MINIMAL to NO ERROR detection, BUS, BUSY. because I have not encounter any of them! + + +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..8b1ece98476 --- /dev/null +++ b/libraries/Wire/examples/eeprom_size/eeprom_size.ino @@ -0,0 +1,173 @@ +#include +// Connect 4.7k pullups on SDA, SCL +// for ESP32 SDA(pin 21), SCL(pin 22) +// for AtMega328p SDA(Pin A5), SCL(pin A4) +// for Mega2560 SDA(Pin 20), SCL(pin 21) + +/* This sketch uses the address rollover of the 24LCxx EEPROMS to detect their size. + The 24LC32 (4kByte) 0x0000 .. 0x0FFF, a Write to addres 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 compromized. + + 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<100)&&(!ready)){ + 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; +} + +/* 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("%dk Bytes"),size/1024); + 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,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..109d46c5a78 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" { @@ -42,7 +43,12 @@ TwoWire::TwoWire(uint8_t bus_num) ,txLength(0) ,txAddress(0) ,transmitting(0) -{} + ,txQueued(0) + ,rxQueued(0) + ,_timeOutMillis(50) + ,last_error(I2C_ERROR_OK) + ,_dump(false) + {} void TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency) { @@ -63,7 +69,7 @@ void TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency) } if(i2c == NULL) { - i2c = i2cInit(num, 0, false); + i2c = i2cInit(num); if(i2c == NULL) { return; } @@ -71,14 +77,14 @@ void TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency) i2cSetFrequency(i2c, frequency); - if(sda >= 0 && sda != sdaPin) { + if(sda >= 0 && sda != sdaPin ) { i2cDetachSDA(i2c, sda); } - if(scl >= 0 && scl != sclPin) { + if(scl >= 0 && scl != sclPin ) { i2cDetachSCL(i2c, scl); } - + sda = sdaPin; scl = sclPin; @@ -90,62 +96,272 @@ void TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency) i2cInitFix(i2c); } +void TwoWire::setTimeOut(uint16_t timeOutMillis){ + _timeOutMillis = timeOutMillis; +} + +uint16_t TwoWire::getTimeOut(){ + return _timeOutMillis; +} + void TwoWire::setClock(uint32_t frequency) { i2cSetFrequency(i2c, frequency); } +/*@StickBreaker common handler for processing the queued commands +*/ +i2c_err_t TwoWire::processQueue(uint32_t * readCount){ + 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(); + } + 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; +} -size_t TwoWire::requestFrom(uint8_t address, size_t size, bool sendStop) -{ - if(size > I2C_BUFFER_LENGTH) { - size = I2C_BUFFER_LENGTH; +/* 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() + +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); } - 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; + } + } +txIndex=0; +txLength=0; +transmitting = 0; +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; +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); + } + else { // stop not received, so wait for I2C stop, + last_error=I2C_ERROR_CONTINUE; + } + } +return last_error; +} + +/*stickbreaker Dump i2c Interrupt buffer, i2c isr Debugging +*/ +void TwoWire::dumpInts(){ + i2cDumpInts(); +} + +/*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 +369,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,10 +430,14 @@ void TwoWire::flush(void) rxLength = 0; txIndex = 0; txLength = 0; + rxQueued = 0; + txQueued = 0; + i2cFreeQueue(i2c); // cleanup } void TwoWire::reset(void) { + i2cReleaseISR(i2c); // remove ISR from Interrupt chain,Delete EventGroup,Free Heap memory i2cReset( i2c ); i2c = NULL; begin( sda, scl ); diff --git a/libraries/Wire/src/Wire.h b/libraries/Wire/src/Wire.h index d9a7a752088..cfec58fa386 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 @@ -30,6 +31,8 @@ #include "Stream.h" #define I2C_BUFFER_LENGTH 128 +typedef void(*user_onRequest)(void); +typedef void(*user_onReceive)(uint8_t*, int); class TwoWire: public Stream { @@ -42,28 +45,63 @@ 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; public: TwoWire(uint8_t bus_num); void begin(int sda=-1, int scl=-1, uint32_t frequency=100000); void setClock(uint32_t); + 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;} + void dumpInts(); + void dumpI2C(){i2cDumpI2c(i2c);} + size_t getClock(); + 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);