d2816b2f32
Some Debugging variables were enabled at ERROR level instead of DEBUG. Specifically `tAfter` and `tBefore`
1771 lines
67 KiB
C
1771 lines
67 KiB
C
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// 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.
|
||
|
||
#include "esp32-hal-i2c.h"
|
||
#include "esp32-hal.h"
|
||
#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"
|
||
#include "soc/i2c_struct.h"
|
||
#include "soc/dport_reg.h"
|
||
#include "esp_attr.h"
|
||
#include "esp32-hal-cpu.h" // cpu clock change support 31DEC2018
|
||
//#define I2C_DEV(i) (volatile i2c_dev_t *)((i)?DR_REG_I2C1_EXT_BASE:DR_REG_I2C_EXT_BASE)
|
||
//#define I2C_DEV(i) ((i2c_dev_t *)(REG_I2C_BASE(i)))
|
||
#define I2C_SCL_IDX(p) ((p==0)?I2CEXT0_SCL_OUT_IDX:((p==1)?I2CEXT1_SCL_OUT_IDX:0))
|
||
#define I2C_SDA_IDX(p) ((p==0)?I2CEXT0_SDA_OUT_IDX:((p==1)?I2CEXT1_SDA_OUT_IDX:0))
|
||
|
||
#define DR_REG_I2C_EXT_BASE_FIXED 0x60013000
|
||
#define DR_REG_I2C1_EXT_BASE_FIXED 0x60027000
|
||
|
||
/* Stickbreaker ISR mode debug support
|
||
|
||
ENABLE_I2C_DEBUG_BUFFER
|
||
Enable debug interrupt history buffer, fifoTx history buffer.
|
||
Setting this define will result in 2570 bytes of RAM being used whenever CORE_DEBUG_LEVEL
|
||
is higher than WARNING. Unless you are debugging a problem in the I2C subsystem,
|
||
I would recommend you leave it commented out.
|
||
*/
|
||
|
||
//#define ENABLE_I2C_DEBUG_BUFFER
|
||
|
||
#if (ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO) && (defined ENABLE_I2C_DEBUG_BUFFER)
|
||
#define INTBUFFMAX 64
|
||
#define FIFOMAX 512
|
||
static uint32_t intBuff[INTBUFFMAX][3][2];
|
||
static uint32_t intPos[2]= {0,0};
|
||
static uint16_t fifoBuffer[FIFOMAX];
|
||
static uint16_t fifoPos = 0;
|
||
#endif
|
||
|
||
// 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;
|
||
|
||
typedef union {
|
||
struct {
|
||
uint32_t rx_fifo_start_addr: 5; /*This is the offset address of the last receiving data as described in nonfifo_rx_thres_register.*/
|
||
uint32_t rx_fifo_end_addr: 5; /*This is the offset address of the first receiving data as described in nonfifo_rx_thres_register.*/
|
||
uint32_t tx_fifo_start_addr: 5; /*This is the offset address of the first sending data as described in nonfifo_tx_thres register.*/
|
||
uint32_t tx_fifo_end_addr: 5; /*This is the offset address of the last sending data as described in nonfifo_tx_thres register.*/
|
||
uint32_t reserved20: 12;
|
||
};
|
||
uint32_t val;
|
||
} I2C_FIFO_ST_t;
|
||
|
||
// end from tools/sdk/include/soc/soc/i2c_struct.h
|
||
|
||
// 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; // data pointer for read/write buffer
|
||
uint16_t length; // size of data buffer
|
||
uint16_t position; // current position for next char in buffer (<length)
|
||
uint16_t cmdBytesNeeded; // used to control number of I2C_COMMAND_t blocks added to queue
|
||
I2C_DATA_CTRL_t ctrl;
|
||
EventGroupHandle_t queueEvent; // optional user supplied for Async feedback EventBits
|
||
} I2C_DATA_QUEUE_t;
|
||
|
||
struct i2c_struct_t {
|
||
i2c_dev_t * dev;
|
||
#if !CONFIG_DISABLE_HAL_LOCKS
|
||
xSemaphoreHandle lock;
|
||
#endif
|
||
uint8_t num;
|
||
int8_t sda;
|
||
int8_t scl;
|
||
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; // number of dq entries in queue.
|
||
uint16_t queuePos; // current queue that still has or needs data (out/in)
|
||
int16_t errorByteCnt; // byte pos where error happened, -1 devId, 0..(length-1) data
|
||
uint16_t errorQueue; // errorByteCnt is in this queue,(for error locus)
|
||
uint32_t exitCode;
|
||
uint32_t debugFlags;
|
||
};
|
||
|
||
enum {
|
||
I2C_CMD_RSTART,
|
||
I2C_CMD_WRITE,
|
||
I2C_CMD_READ,
|
||
I2C_CMD_STOP,
|
||
I2C_CMD_END
|
||
};
|
||
|
||
#if CONFIG_DISABLE_HAL_LOCKS
|
||
#define I2C_MUTEX_LOCK()
|
||
#define I2C_MUTEX_UNLOCK()
|
||
|
||
static i2c_t _i2c_bus_array[2] = {
|
||
{(volatile i2c_dev_t *)(DR_REG_I2C_EXT_BASE_FIXED), 0, -1, -1,I2C_NONE,I2C_NONE,I2C_ERROR_OK,NULL,NULL,NULL,0,0,0,0,0},
|
||
{(volatile i2c_dev_t *)(DR_REG_I2C1_EXT_BASE_FIXED), 1, -1, -1,I2C_NONE,I2C_NONE,I2C_ERROR_OK,NULL,NULL,NULL,0,0,0,0,0}
|
||
};
|
||
#else
|
||
#define I2C_MUTEX_LOCK() do {} while (xSemaphoreTakeRecursive(i2c->lock, portMAX_DELAY) != pdPASS)
|
||
#define I2C_MUTEX_UNLOCK() xSemaphoreGiveRecursive(i2c->lock)
|
||
|
||
static i2c_t _i2c_bus_array[2] = {
|
||
{(volatile i2c_dev_t *)(DR_REG_I2C_EXT_BASE_FIXED), NULL, 0, -1, -1, I2C_NONE,I2C_NONE,I2C_ERROR_OK,NULL,NULL,NULL,0,0,0,0,0,0},
|
||
{(volatile i2c_dev_t *)(DR_REG_I2C1_EXT_BASE_FIXED), NULL, 1, -1, -1,I2C_NONE,I2C_NONE,I2C_ERROR_OK,NULL,NULL,NULL,0,0,0,0,0,0}
|
||
};
|
||
#endif
|
||
|
||
/*
|
||
* index - command index (0 to 15)
|
||
* op_code - is the command
|
||
* byte_num - This register is to store the amounts of data that is read and written. byte_num in RSTART, STOP, END is null.
|
||
* ack_val - Each data byte is terminated by an ACK bit used to set the bit level.
|
||
* ack_exp - This bit is to set an expected ACK value for the transmitter.
|
||
* ack_check - This bit is to decide whether the transmitter checks ACK bit. 1 means yes and 0 means no.
|
||
* */
|
||
|
||
|
||
/* Stickbreaker ISR mode debug support
|
||
*/
|
||
static void IRAM_ATTR i2cDumpCmdQueue(i2c_t *i2c)
|
||
{
|
||
#if (ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR)&&(defined ENABLE_I2C_DEBUG_BUFFER)
|
||
static const char * const cmdName[] ={"RSTART","WRITE","READ","STOP","END"};
|
||
uint8_t i=0;
|
||
while(i<16) {
|
||
I2C_COMMAND_t c;
|
||
c.val=i2c->dev->command[i].val;
|
||
log_e("[%2d]\t%c\t%s\tval[%d]\texp[%d]\ten[%d]\tbytes[%d]",i,(c.done?'Y':'N'),
|
||
cmdName[c.op_code],
|
||
c.ack_val,
|
||
c.ack_exp,
|
||
c.ack_en,
|
||
c.byte_num);
|
||
i++;
|
||
}
|
||
#endif
|
||
}
|
||
|
||
/* Stickbreaker ISR mode debug support
|
||
*/
|
||
#if (ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO)
|
||
static void i2cDumpDqData(i2c_t * i2c)
|
||
{
|
||
#if defined (ENABLE_I2C_DEBUG_BUFFER)
|
||
uint16_t a=0;
|
||
char buff[140];
|
||
I2C_DATA_QUEUE_t *tdq;
|
||
int digits=0,lenDigits=0;
|
||
a = i2c->queueCount;
|
||
while(a>0) {
|
||
digits++;
|
||
a /= 10;
|
||
}
|
||
while(a<i2c->queueCount) { // find maximum number of len decimal digits for formatting
|
||
if (i2c->dq[a].length > lenDigits ) lenDigits = i2c->dq[a].length;
|
||
a++;
|
||
}
|
||
a=0;
|
||
while(lenDigits>0){
|
||
a++;
|
||
lenDigits /= 10;
|
||
}
|
||
lenDigits = a;
|
||
a = 0;
|
||
while(a<i2c->queueCount) {
|
||
tdq=&i2c->dq[a];
|
||
char buf1[10],buf2[10];
|
||
sprintf(buf1,"%0*d",lenDigits,tdq->length);
|
||
sprintf(buf2,"%0*d",lenDigits,tdq->position);
|
||
log_i("[%0*d] %sbit %x %c %s buf@=%p, len=%s, pos=%s, ctrl=%d%d%d%d%d",digits,a,
|
||
(tdq->ctrl.addr>0x100)?"10":"7",
|
||
(tdq->ctrl.addr>0x100)?(((tdq->ctrl.addr&0x600)>>1)|(tdq->ctrl.addr&0xff)):(tdq->ctrl.addr>>1),
|
||
(tdq->ctrl.mode)?'R':'W',
|
||
(tdq->ctrl.stop)?"STOP":"",
|
||
tdq->data,
|
||
buf1,buf2,
|
||
tdq->ctrl.startCmdSent,tdq->ctrl.addrCmdSent,tdq->ctrl.dataCmdSent,(tdq->ctrl.stop)?tdq->ctrl.stopCmdSent:0,tdq->ctrl.addrSent
|
||
);
|
||
uint16_t offset = 0;
|
||
while(offset<tdq->length) {
|
||
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_i("%s",buff);
|
||
}
|
||
a++;
|
||
}
|
||
#else
|
||
log_i("Debug Buffer not Enabled");
|
||
#endif
|
||
}
|
||
#endif
|
||
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO
|
||
static void i2cDumpI2c(i2c_t * i2c)
|
||
{
|
||
log_e("i2c=%p",i2c);
|
||
log_i("dev=%p date=%p",i2c->dev,i2c->dev->date);
|
||
#if !CONFIG_DISABLE_HAL_LOCKS
|
||
log_i("lock=%p",i2c->lock);
|
||
#endif
|
||
log_i("num=%d",i2c->num);
|
||
log_i("mode=%d",i2c->mode);
|
||
log_i("stage=%d",i2c->stage);
|
||
log_i("error=%d",i2c->error);
|
||
log_i("event=%p bits=%x",i2c->i2c_event,(i2c->i2c_event)?xEventGroupGetBits(i2c->i2c_event):0);
|
||
log_i("intr_handle=%p",i2c->intr_handle);
|
||
log_i("dq=%p",i2c->dq);
|
||
log_i("queueCount=%d",i2c->queueCount);
|
||
log_i("queuePos=%d",i2c->queuePos);
|
||
log_i("errorByteCnt=%d",i2c->errorByteCnt);
|
||
log_i("errorQueue=%d",i2c->errorQueue);
|
||
log_i("debugFlags=0x%08X",i2c->debugFlags);
|
||
if(i2c->dq) {
|
||
i2cDumpDqData(i2c);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
#if (ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO)
|
||
static void i2cDumpInts(uint8_t num)
|
||
{
|
||
#if defined (ENABLE_I2C_DEBUG_BUFFER)
|
||
uint32_t b;
|
||
log_i("%u row\tcount\tINTR\tTX\tRX\tTick ",num);
|
||
for(uint32_t a=1; a<=INTBUFFMAX; a++) {
|
||
b=(a+intPos[num])%INTBUFFMAX;
|
||
if(intBuff[b][0][num]!=0) {
|
||
log_i("[%02d]\t0x%04x\t0x%04x\t0x%04x\t0x%04x\t0x%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_i("Debug Buffer not Enabled");
|
||
#endif
|
||
}
|
||
#endif
|
||
|
||
#if (ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO)&&(defined ENABLE_I2C_DEBUG_BUFFER)
|
||
static void IRAM_ATTR i2cDumpStatus(i2c_t * i2c){
|
||
typedef union {
|
||
struct {
|
||
uint32_t ack_rec: 1; /*This register stores the value of ACK bit.*/
|
||
uint32_t slave_rw: 1; /*when in slave mode 1:master read slave 0: master write slave.*/
|
||
uint32_t time_out: 1; /*when I2C takes more than time_out_reg clocks to receive a data then this register changes to high level.*/
|
||
uint32_t arb_lost: 1; /*when I2C lost control of SDA line this register changes to high level.*/
|
||
uint32_t bus_busy: 1; /*1:I2C bus is busy transferring data. 0:I2C bus is in idle state.*/
|
||
uint32_t slave_addressed: 1; /*when configured as i2c slave and the address send by master is equal to slave's address then this bit will be high level.*/
|
||
uint32_t byte_trans: 1; /*This register changes to high level when one byte is transferred.*/
|
||
uint32_t reserved7: 1;
|
||
uint32_t rx_fifo_cnt: 6; /*This register represent the amount of data need to send.*/
|
||
uint32_t reserved14: 4;
|
||
uint32_t tx_fifo_cnt: 6; /*This register stores the amount of received data in ram.*/
|
||
uint32_t scl_main_state_last: 3; /*This register stores the value of state machine for i2c module. 3'h0: SCL_MAIN_IDLE 3'h1: SCL_ADDRESS_SHIFT 3'h2: SCL_ACK_ADDRESS 3'h3: SCL_RX_DATA 3'h4 SCL_TX_DATA 3'h5:SCL_SEND_ACK 3'h6:SCL_WAIT_ACK*/
|
||
uint32_t reserved27: 1;
|
||
uint32_t scl_state_last: 3; /*This register stores the value of state machine to produce SCL. 3'h0: SCL_IDLE 3'h1:SCL_START 3'h2:SCL_LOW_EDGE 3'h3: SCL_LOW 3'h4:SCL_HIGH_EDGE 3'h5:SCL_HIGH 3'h6:SCL_STOP*/
|
||
uint32_t reserved31: 1;
|
||
};
|
||
uint32_t val;
|
||
} status_reg;
|
||
|
||
status_reg sr;
|
||
sr.val= i2c->dev->status_reg.val;
|
||
|
||
log_i("ack(%d) sl_rw(%d) to(%d) arb(%d) busy(%d) sl(%d) trans(%d) rx(%d) tx(%d) sclMain(%d) scl(%d)",sr.ack_rec,sr.slave_rw,sr.time_out,sr.arb_lost,sr.bus_busy,sr.slave_addressed,sr.byte_trans, sr.rx_fifo_cnt, sr.tx_fifo_cnt,sr.scl_main_state_last, sr.scl_state_last);
|
||
}
|
||
#endif
|
||
|
||
#if (ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO)&&(defined ENABLE_I2C_DEBUG_BUFFER)
|
||
static void i2cDumpFifo(i2c_t * i2c){
|
||
char buf[64];
|
||
uint16_t k = 0;
|
||
uint16_t i = fifoPos+1;
|
||
i %=FIFOMAX;
|
||
while((fifoBuffer[i]==0)&&(i!=fifoPos)){
|
||
i++;
|
||
i %=FIFOMAX;
|
||
}
|
||
if(i != fifoPos){// actual data
|
||
do{
|
||
if(fifoBuffer[i] & 0x8000){ // address byte
|
||
if(fifoBuffer[i] & 0x100) { // read
|
||
if(fifoBuffer[i] & 0x1) { // valid read dev id
|
||
k+= sprintf(&buf[k],"READ 0x%02X",(fifoBuffer[i]&0xff)>>1);
|
||
} else { // invalid read dev id
|
||
k+= sprintf(&buf[k],"Bad READ 0x%02X",(fifoBuffer[i]&0xff));
|
||
}
|
||
} else { // write
|
||
if(fifoBuffer[i] & 0x1) { // bad write dev id
|
||
k+= sprintf(&buf[k],"bad WRITE 0x%02X",(fifoBuffer[i]&0xff));
|
||
} else { // good Write
|
||
k+= sprintf(&buf[k],"WRITE 0x%02X",(fifoBuffer[i]&0xff)>>1);
|
||
}
|
||
}
|
||
} else k += sprintf(&buf[k],"% 4X ",fifoBuffer[i]);
|
||
|
||
i++;
|
||
i %= FIFOMAX;
|
||
bool outBuffer=false;
|
||
if( fifoBuffer[i] & 0x8000){
|
||
outBuffer=true;
|
||
k=0;
|
||
}
|
||
if((outBuffer)||(k>50)||(i==fifoPos)) log_i("%s",buf);
|
||
outBuffer = false;
|
||
if(k>50) {
|
||
k=sprintf(buf,"-> ");
|
||
}
|
||
}while( i!= fifoPos);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
static void IRAM_ATTR i2cTriggerDumps(i2c_t * i2c, uint8_t trigger, const char locus[]){
|
||
#if (ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO)&&(defined ENABLE_I2C_DEBUG_BUFFER)
|
||
if( trigger ){
|
||
log_i("%s",locus);
|
||
if(trigger & 1) i2cDumpI2c(i2c);
|
||
if(trigger & 2) i2cDumpInts(i2c->num);
|
||
if(trigger & 4) i2cDumpCmdQueue(i2c);
|
||
if(trigger & 8) i2cDumpStatus(i2c);
|
||
if(trigger & 16) i2cDumpFifo(i2c);
|
||
}
|
||
#endif
|
||
}
|
||
// end of debug support routines
|
||
|
||
/* Start of CPU Clock change Support
|
||
*/
|
||
|
||
static void i2cApbChangeCallback(void * arg, apb_change_ev_t ev_type, uint32_t old_apb, uint32_t new_apb){
|
||
i2c_t* i2c = (i2c_t*) arg; // recover data
|
||
if(i2c == NULL) { // point to peripheral control block does not exits
|
||
return;
|
||
}
|
||
uint32_t oldFreq=0;
|
||
switch(ev_type){
|
||
case APB_BEFORE_CHANGE :
|
||
if(new_apb < 3000000) {// too slow
|
||
log_e("apb speed %d too slow",new_apb);
|
||
break;
|
||
}
|
||
I2C_MUTEX_LOCK(); // lock will spin until current transaction is completed
|
||
break;
|
||
case APB_AFTER_CHANGE :
|
||
oldFreq = (i2c->dev->scl_low_period.period+i2c->dev->scl_high_period.period); //read old apbCycles
|
||
if(oldFreq>0) { // was configured with value
|
||
oldFreq = old_apb / oldFreq;
|
||
i2cSetFrequency(i2c,oldFreq);
|
||
}
|
||
I2C_MUTEX_UNLOCK();
|
||
break;
|
||
default :
|
||
log_e("unk ev %u",ev_type);
|
||
I2C_MUTEX_UNLOCK();
|
||
}
|
||
return;
|
||
}
|
||
/* End of CPU Clock change Support
|
||
*/
|
||
static void IRAM_ATTR 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_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;
|
||
}
|
||
|
||
|
||
static void IRAM_ATTR fillCmdQueue(i2c_t * i2c, bool INTS)
|
||
{
|
||
/* this function is called on initial i2cProcQueue() or when a I2C_END_DETECT_INT occurs
|
||
*/
|
||
uint16_t cmdIdx = 0;
|
||
uint16_t qp = i2c->queuePos; // all queues before queuePos have been completely processed,
|
||
// so look start by checking the 'current queue' so see if it needs commands added to
|
||
// hardware peripheral command list. walk through each queue entry until all queues have been
|
||
// checked
|
||
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) {// marks that all required cmds[] have been added to peripheral
|
||
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?
|
||
// (cmdIdx<14) because a START op cannot directly precede an END op, else a time out cascade occurs
|
||
i2cSetCmd(i2c, cmdIdx++, I2C_CMD_RSTART, 0, false, false, false);
|
||
tdq->ctrl.startCmdSent=1;
|
||
}
|
||
|
||
//CMD WRITE ADDRESS
|
||
if((!done)&&(tdq->ctrl.startCmdSent)) { // have to leave room for continue(END), 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 by 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(END) if necessary.
|
||
As a corollary, 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, if END is moved to [14], [14] and [15] can no longer be used 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
|
||
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 remainder 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.stopCmdSent){
|
||
if(tdq->ctrl.stop) { //send a stop
|
||
i2cSetCmd(i2c, (cmdIdx)++,I2C_CMD_STOP,0,false,false,false);
|
||
done = cmdIdx > 14;
|
||
}
|
||
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;
|
||
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))) {
|
||
/* add a dummy read/write cmd[] with num_bytes set to zero,just a place holder in the cmd[]list
|
||
*/
|
||
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");
|
||
i2cDumpCmdQueue(i2c);
|
||
done = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
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
|
||
i2cSetCmd(i2c, (cmdIdx)++,I2C_CMD_END, 0,false,false,false);
|
||
i2c->dev->int_ena.end_detect=1;
|
||
i2c->dev->int_clr.end_detect=1;
|
||
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];
|
||
} else {
|
||
done = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
}// while(!done)
|
||
if(INTS) { // don't want to prematurely enable fifo ints until ISR is ready to handle them.
|
||
if(ena_rx) {
|
||
i2c->dev->int_ena.rx_fifo_full = 1;
|
||
}
|
||
if(ena_tx) {
|
||
i2c->dev->int_ena.tx_fifo_empty = 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void IRAM_ATTR fillTxFifo(i2c_t * i2c)
|
||
{
|
||
/*
|
||
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;
|
||
bool rxQueueEncountered = false;
|
||
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
|
||
#if (ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO) && (defined ENABLE_I2C_DEBUG_BUFFER)
|
||
uint16_t a = 0x8000 | tdq->ctrl.addr | (tdq->ctrl.mode<<8);
|
||
fifoBuffer[fifoPos++] = a;
|
||
fifoPos %= FIFOMAX;
|
||
#endif
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// add write data to fifo
|
||
if(tdq->ctrl.mode==0) { // write
|
||
if(tdq->ctrl.addrSent == tdq->ctrl.addrReq) { //address has been sent, is Write Mode!
|
||
uint32_t moveCnt = 32 - i2c->dev->status_reg.tx_fifo_cnt; // how much room in txFifo?
|
||
while(( moveCnt>0)&&(tdq->position < tdq->length)) {
|
||
#if (ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO) && (defined ENABLE_I2C_DEBUG_BUFFER)
|
||
fifoBuffer[fifoPos++] = tdq->data[tdq->position];
|
||
fifoPos %= FIFOMAX;
|
||
#endif
|
||
i2c->dev->fifo_data.val = tdq->data[tdq->position++];
|
||
cnt++;
|
||
moveCnt--;
|
||
|
||
}
|
||
}
|
||
} else { // read Queue Encountered, can't update QueuePos past this point, emptyRxfifo will do it
|
||
if( ! rxQueueEncountered ) {
|
||
rxQueueEncountered = true;
|
||
if(a > i2c->queuePos){
|
||
i2c->queuePos = a;
|
||
}
|
||
}
|
||
}
|
||
#if (ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO) && (defined ENABLE_I2C_DEBUG_BUFFER)
|
||
|
||
// 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
|
||
full=!(i2c->dev->status_reg.tx_fifo_cnt<31);
|
||
if(!full) {
|
||
a++; // check next buffer for address tx, or write data
|
||
}
|
||
}
|
||
|
||
if(a >= i2c->queueCount ) { // disable TX IRQ, all tx Data has been queued
|
||
i2c->dev->int_ena.tx_fifo_empty= 0;
|
||
}
|
||
|
||
i2c->dev->int_clr.tx_fifo_empty=1;
|
||
}
|
||
|
||
|
||
static void IRAM_ATTR emptyRxFifo(i2c_t * i2c)
|
||
{
|
||
uint32_t d, cnt=0, moveCnt;
|
||
|
||
moveCnt = i2c->dev->status_reg.rx_fifo_cnt;//no need to check the reg until this many are read
|
||
|
||
while((moveCnt > 0)&&(i2c->queuePos < i2c->queueCount)){ // data to move
|
||
|
||
I2C_DATA_QUEUE_t *tdq =&i2c->dq[i2c->queuePos]; //short cut
|
||
|
||
while((tdq->position >= tdq->length)&&( i2c->queuePos < i2c->queueCount)){ // find were to store
|
||
i2c->queuePos++;
|
||
tdq = &i2c->dq[i2c->queuePos];
|
||
}
|
||
|
||
if(i2c->queuePos >= i2c->queueCount){ // bad stuff, rx data but no place to put it!
|
||
log_e("no Storage location for %d",moveCnt);
|
||
// discard
|
||
while(moveCnt>0){
|
||
d = i2c->dev->fifo_data.val;
|
||
moveCnt--;
|
||
cnt++;
|
||
}
|
||
break;
|
||
}
|
||
|
||
if(tdq->ctrl.mode == 1){ // read command
|
||
if(moveCnt > (tdq->length - tdq->position)) { //make sure they go in this dq
|
||
// part of these reads go into the next dq
|
||
moveCnt = (tdq->length - tdq->position);
|
||
}
|
||
} else {// error
|
||
log_e("RxEmpty(%d) call on TxBuffer? dq=%d",moveCnt,i2c->queuePos);
|
||
// discard
|
||
while(moveCnt>0){
|
||
d = i2c->dev->fifo_data.val;
|
||
moveCnt--;
|
||
cnt++;
|
||
}
|
||
break;
|
||
}
|
||
|
||
while(moveCnt > 0) { // store data
|
||
d = i2c->dev->fifo_data.val;
|
||
moveCnt--;
|
||
cnt++;
|
||
tdq->data[tdq->position++] = (d&0xFF);
|
||
}
|
||
|
||
moveCnt = i2c->dev->status_reg.rx_fifo_cnt; //any more out there?
|
||
}
|
||
|
||
#if (ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO)&& (defined ENABLE_I2C_DEBUG_BUFFER)
|
||
// 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
|
||
}
|
||
|
||
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->queuePos < i2c->queueCount) && (i2c->dq[i2c->queuePos].ctrl.mode == 1)) {
|
||
emptyRxFifo(i2c); // grab last few characters
|
||
}
|
||
i2c->dev->int_ena.val = 0; // shut down interrupts
|
||
i2c->dev->int_clr.val = 0x1FFF;
|
||
i2c->stage = I2C_DONE;
|
||
i2c->exitCode = exitCode; //true eventcode
|
||
|
||
portBASE_TYPE HPTaskAwoken = pdFALSE,xResult;
|
||
// try to notify Dispatch we are done,
|
||
// else the 50ms time out will recover the APP, just a little 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_update_error_byte_cnt(i2c_t * i2c)
|
||
{
|
||
/* i2c_update_error_byte_cnt 07/18/2018
|
||
Only called after an error has occurred, so, most of the time this function is never used.
|
||
This function obliterates the need to interrupt monitor each byte transferred, at high bitrates
|
||
the byte interrupts were overwhelming the OS. Spurious Interrupts were being generated.
|
||
it updates errorByteCnt, errorQueue.
|
||
*/
|
||
uint16_t a=0; // start at top of DQ, count how many bytes added to tx fifo, and received from rx_fifo.
|
||
int16_t bc = 0;
|
||
I2C_DATA_QUEUE_t *tdq;
|
||
i2c->errorByteCnt = 0;
|
||
while( a < i2c->queueCount){ // add up all bytes loaded into fifo's
|
||
tdq = &i2c->dq[a];
|
||
i2c->errorByteCnt += tdq->ctrl.addrSent;
|
||
i2c->errorByteCnt += tdq->position;
|
||
a++;
|
||
}
|
||
// now errorByteCnt contains total bytes moved into and out of FIFO's
|
||
// but, there may still be bytes waiting in Fifo's
|
||
i2c->errorByteCnt -= i2c->dev->status_reg.tx_fifo_cnt; // waiting to go out;
|
||
i2c->errorByteCnt += i2c->dev->status_reg.rx_fifo_cnt; // already received
|
||
// now walk thru DQ again, find which byte is 'current'
|
||
bc = i2c->errorByteCnt;
|
||
i2c->errorQueue = 0;
|
||
while( i2c->errorQueue < i2c->queueCount ){
|
||
tdq = &i2c->dq[i2c->errorQueue];
|
||
if(bc>0){ // not found yet
|
||
if( tdq->ctrl.addrSent >= bc){ // in address
|
||
bc = -1; // in address
|
||
break;
|
||
} else {
|
||
bc -= tdq->ctrl.addrSent;
|
||
if( tdq->length > bc) { // data nak
|
||
break;
|
||
} else { // count down
|
||
bc -= tdq->length;
|
||
}
|
||
}
|
||
} else break;
|
||
|
||
i2c->errorQueue++;
|
||
}
|
||
|
||
i2c->errorByteCnt = bc;
|
||
}
|
||
|
||
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&0x7FF;
|
||
|
||
if(p_i2c->stage==I2C_DONE) { //get Out, can't service, not configured
|
||
p_i2c->dev->int_ena.val = 0;
|
||
p_i2c->dev->int_clr.val = 0x1FFF;
|
||
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
|
||
uint32_t raw = p_i2c->dev->int_raw.val;
|
||
log_w("eject raw=%p, int=%p",raw,activeInt);
|
||
#endif
|
||
return;
|
||
}
|
||
while (activeInt != 0) { // Ordering of 'if(activeInt)' statements is important, don't change
|
||
|
||
#if (ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO) && (defined ENABLE_I2C_DEBUG_BUFFER)
|
||
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) {
|
||
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_ACK_ERR_INT_ST_M) {//fatal error, abort i2c service
|
||
if (p_i2c->mode == I2C_MASTER) {
|
||
i2c_update_error_byte_cnt(p_i2c); // calc which byte caused ack Error, check if address or data
|
||
if(p_i2c->errorByteCnt < 0 ) { // address
|
||
i2cIsrExit(p_i2c,EVENT_ERROR_NAK,true);
|
||
} else {
|
||
i2cIsrExit(p_i2c,EVENT_ERROR_DATA_NAK,true); //data
|
||
}
|
||
}
|
||
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;
|
||
// since a timeout occurred, capture the rxFifo data
|
||
emptyRxFifo(p_i2c);
|
||
p_i2c->dev->int_clr.rx_fifo_full=1;
|
||
p_i2c->dev->int_ena.rx_fifo_full=1; //why?
|
||
|
||
}
|
||
|
||
if (activeInt & I2C_TRANS_COMPLETE_INT_ST_M) {
|
||
p_i2c->dev->int_clr.trans_complete = 1;
|
||
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
|
||
// 01AUG2018 if another interrupt happened, the OS will schedule another interrupt
|
||
// this is the source of spurious interrupts
|
||
}
|
||
}
|
||
|
||
/* 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, bool dataOnly, EventGroupHandle_t event)
|
||
{
|
||
// need to grab a MUTEX for exclusive Queue,
|
||
// what about if ISR is running?
|
||
|
||
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;
|
||
if( dataOnly) {
|
||
/* special case to add a queue data only element.
|
||
START and devAddr will not be sent, this dq element can have a STOP.
|
||
allows multiple buffers to be used for one transaction.
|
||
sequence: normal transaction(sendStop==false), [dataonly(sendStop==false)],dataOnly(sendStop==true)
|
||
*** Currently only works with WRITE, final byte NAK an READ will cause a fail between dq buffer elements. (in progress 30JUL2018)
|
||
*/
|
||
dqx.ctrl.startCmdSent = 1; // mark as already sent
|
||
dqx.ctrl.addrCmdSent = 1;
|
||
} else {
|
||
dqx.ctrl.addrReq = ((i2cDeviceAddr&0xFC00)==0x7800)?2:1; // 10bit or 7bit address
|
||
}
|
||
dqx.ctrl.addr = i2cDeviceAddr;
|
||
dqx.ctrl.mode = mode;
|
||
dqx.ctrl.stop= sendStop;
|
||
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 i2cAddQueueWrite(i2c_t * i2c, uint16_t i2cDeviceAddr, uint8_t *dataPtr, uint16_t dataLen,bool sendStop,EventGroupHandle_t event)
|
||
{
|
||
return i2cAddQueue(i2c,0,i2cDeviceAddr,dataPtr,dataLen,sendStop,false,event);
|
||
}
|
||
|
||
i2c_err_t i2cAddQueueRead(i2c_t * i2c, uint16_t i2cDeviceAddr, uint8_t *dataPtr, uint16_t dataLen,bool sendStop,EventGroupHandle_t event)
|
||
{
|
||
//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 against.
|
||
// this is the Industry Standard specification.
|
||
|
||
if((i2cDeviceAddr &0xFC00)==0x7800) { // ten bit read
|
||
i2c_err_t err = i2cAddQueue(i2c,0,i2cDeviceAddr,NULL,0,false,false,event);
|
||
if(err==I2C_ERROR_OK) {
|
||
return i2cAddQueue(i2c,1,(i2cDeviceAddr>>8),dataPtr,dataLen,sendStop,false,event);
|
||
} else {
|
||
return err;
|
||
}
|
||
}
|
||
return i2cAddQueue(i2c,1,i2cDeviceAddr,dataPtr,dataLen,sendStop,false,event);
|
||
}
|
||
|
||
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);
|
||
if(readCount){ //total reads accomplished in all queue elements
|
||
*readCount = 0;
|
||
}
|
||
if(i2c == NULL) {
|
||
return I2C_ERROR_DEV;
|
||
}
|
||
if(i2c->debugFlags & 0xff000000) i2cTriggerDumps(i2c,(i2c->debugFlags>>24),"before ProcQueue");
|
||
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()-timeOutTick<timeOutMillis())){
|
||
delay(2); // allow task switch
|
||
}
|
||
}
|
||
if(i2c->dev->status_reg.bus_busy){ // still busy, so die
|
||
*/
|
||
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) && (defined ENABLE_I2C_DEBUG_BUFFER)
|
||
for(uint16_t i=0; i<INTBUFFMAX; i++) {
|
||
intBuff[i][0][i2c->num] = 0;
|
||
intBuff[i][1][i2c->num] = 0;
|
||
intBuff[i][2][i2c->num] = 0;
|
||
}
|
||
intPos[i2c->num] = 0;
|
||
fifoPos = 0;
|
||
memset(fifoBuffer,0,FIFOMAX);
|
||
#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->dev->int_clr.val = 0x1FFF; // kill them All!
|
||
|
||
i2c->dev->ctr.ms_mode = 1; // master!
|
||
i2c->queuePos=0;
|
||
i2c->errorByteCnt=0;
|
||
i2c->errorQueue = 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->length + tdq->ctrl.addrReq; // 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_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
|
||
f.nonfifo_tx_thres = 31;
|
||
// 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?
|
||
// let i2cSetFrequency() set thrhds
|
||
// f.rx_fifo_full_thrhd = 30; // 30 bytes before INT is issued
|
||
// f.tx_fifo_empty_thrhd = 0;
|
||
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->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
|
||
|
||
|
||
uint32_t interruptsEnabled =
|
||
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_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()
|
||
|
||
i2c->dev->int_ena.val = interruptsEnabled;
|
||
|
||
if(!i2c->intr_handle) { // create ISR for either peripheral
|
||
// log_i("create ISR %d",i2c->num);
|
||
uint32_t ret = 0;
|
||
uint32_t flags = ESP_INTR_FLAG_IRAM | //< ISR can be called if cache is disabled
|
||
ESP_INTR_FLAG_LOWMED | //< Low and medium prio interrupts. These can be handled in C.
|
||
ESP_INTR_FLAG_SHARED; //< Reduce resource requirements, Share interrupts
|
||
|
||
if(i2c->num) {
|
||
ret = esp_intr_alloc_intrstatus(ETS_I2C_EXT1_INTR_SOURCE, flags, (uint32_t)&i2c->dev->int_status.val, interruptsEnabled, &i2c_isr_handler_default,i2c, &i2c->intr_handle);
|
||
} else {
|
||
ret = esp_intr_alloc_intrstatus(ETS_I2C_EXT0_INTR_SOURCE, flags, (uint32_t)&i2c->dev->int_status.val, interruptsEnabled, &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;
|
||
}
|
||
if( !addApbChangeCallback( i2c, i2cApbChangeCallback)) {
|
||
log_e("install apb Callback failed");
|
||
I2C_MUTEX_UNLOCK();
|
||
return I2C_ERROR_DEV;
|
||
}
|
||
|
||
}
|
||
//hang until it completes.
|
||
|
||
// how many ticks should it take to transfer totalBytes through the I2C hardware,
|
||
// add user supplied timeOutMillis to Calculated Value
|
||
|
||
portTickType ticksTimeOut = ((totalBytes*10*1000)/(i2cGetFrequency(i2c))+timeOutMillis)/portTICK_PERIOD_MS;
|
||
|
||
i2c->dev->ctr.trans_start=1; // go for it
|
||
|
||
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG
|
||
portTickType tBefore=xTaskGetTickCount();
|
||
#endif
|
||
|
||
// wait for ISR to complete the transfer, or until timeOut in case of bus fault, hardware problem
|
||
|
||
uint32_t eBits = xEventGroupWaitBits(i2c->i2c_event,EVENT_DONE,pdFALSE,pdTRUE,ticksTimeOut);
|
||
|
||
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG
|
||
portTickType tAfter=xTaskGetTickCount();
|
||
#endif
|
||
|
||
|
||
// 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_ERROR)||(!(eBits & EVENT_DONE))){ // need accurate errorByteCnt for debug
|
||
i2c_update_error_byte_cnt(i2c);
|
||
}
|
||
|
||
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);
|
||
#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;
|
||
i2c_update_error_byte_cnt(i2c);
|
||
if((i2c->errorByteCnt == 0)&&(i2c->errorQueue==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_DEBUG
|
||
log_d(" 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_DEBUG
|
||
log_d(" 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
|
||
07/22/2018
|
||
Need to use the queueEvent value to identify transaction blocks, if an error occurs,
|
||
all subsequent queue items with the same queueEvent value will receive the EVENT_ERROR_PREV.
|
||
But, ProcQue should re-queue queue items that have a different queueEvent value(different transaction)
|
||
This change will support multi-thread i2c usage. Use the queueEvent as the transaction event
|
||
identifier.
|
||
*/
|
||
uint32_t b = 0;
|
||
|
||
while(b < i2c->queueCount) {
|
||
if(i2c->dq[b].ctrl.mode==1 && readCount) {
|
||
*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++;
|
||
}
|
||
if(i2c->debugFlags & 0x00ff0000) i2cTriggerDumps(i2c,(i2c->debugFlags>>16),"after ProcQueue");
|
||
|
||
I2C_MUTEX_UNLOCK();
|
||
return reason;
|
||
}
|
||
|
||
static void i2cReleaseISR(i2c_t * i2c)
|
||
{
|
||
if(i2c->intr_handle) {
|
||
esp_intr_free(i2c->intr_handle);
|
||
i2c->intr_handle=NULL;
|
||
if (!removeApbChangeCallback( i2c, i2cApbChangeCallback)) {
|
||
log_e("unable to release apbCallback");
|
||
}
|
||
}
|
||
}
|
||
|
||
static bool i2cCheckLineState(int8_t sda, int8_t scl){
|
||
if(sda < 0 || scl < 0){
|
||
return false;//return false since there is nothing to do
|
||
}
|
||
// if the bus is not 'clear' try the cycling SCL until SDA goes High or 9 cycles
|
||
digitalWrite(sda, HIGH);
|
||
digitalWrite(scl, HIGH);
|
||
pinMode(sda, PULLUP|OPEN_DRAIN|INPUT);
|
||
pinMode(scl, PULLUP|OPEN_DRAIN|OUTPUT);
|
||
|
||
if(!digitalRead(sda) || !digitalRead(scl)) { // bus in busy state
|
||
log_w("invalid state sda(%d)=%d, scl(%d)=%d", sda, digitalRead(sda), scl, digitalRead(scl));
|
||
digitalWrite(scl, HIGH);
|
||
for(uint8_t a=0; a<9; a++) {
|
||
delayMicroseconds(5);
|
||
digitalWrite(scl, LOW);
|
||
delayMicroseconds(5);
|
||
digitalWrite(scl, HIGH);
|
||
if(digitalRead(sda)){ // bus recovered
|
||
log_d("Recovered after %d Cycles",a+1);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if(!digitalRead(sda) || !digitalRead(scl)) { // bus in busy state
|
||
log_e("Bus Invalid State, TwoWire() Can't init sda=%d, scl=%d",digitalRead(sda),digitalRead(scl));
|
||
return false; // bus is busy
|
||
}
|
||
return true;
|
||
}
|
||
|
||
i2c_err_t i2cAttachSCL(i2c_t * i2c, int8_t scl)
|
||
{
|
||
if(i2c == NULL) {
|
||
return I2C_ERROR_DEV;
|
||
}
|
||
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;
|
||
}
|
||
|
||
i2c_err_t i2cDetachSCL(i2c_t * i2c, int8_t scl)
|
||
{
|
||
if(i2c == NULL) {
|
||
return I2C_ERROR_DEV;
|
||
}
|
||
pinMatrixOutDetach(scl, false, false);
|
||
pinMatrixInDetach(I2C_SCL_IDX(i2c->num), false, false);
|
||
pinMode(scl, INPUT | PULLUP);
|
||
return I2C_ERROR_OK;
|
||
}
|
||
|
||
i2c_err_t i2cAttachSDA(i2c_t * i2c, int8_t sda)
|
||
{
|
||
if(i2c == NULL) {
|
||
return I2C_ERROR_DEV;
|
||
}
|
||
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;
|
||
}
|
||
|
||
i2c_err_t i2cDetachSDA(i2c_t * i2c, int8_t sda)
|
||
{
|
||
if(i2c == NULL) {
|
||
return I2C_ERROR_DEV;
|
||
}
|
||
pinMatrixOutDetach(sda, false, false);
|
||
pinMatrixInDetach(I2C_SDA_IDX(i2c->num), false, false);
|
||
pinMode(sda, INPUT | PULLUP);
|
||
return I2C_ERROR_OK;
|
||
}
|
||
|
||
/*
|
||
* PUBLIC API
|
||
* */
|
||
// 24Nov17 only supports Master Mode
|
||
i2c_t * i2cInit(uint8_t i2c_num, int8_t sda, int8_t scl, uint32_t frequency) {
|
||
#ifdef ENABLE_I2C_DEBUG_BUFFER
|
||
log_v("num=%d sda=%d scl=%d freq=%d",i2c_num, sda, scl, frequency);
|
||
#endif
|
||
if(i2c_num > 1) {
|
||
return NULL;
|
||
}
|
||
|
||
i2c_t * i2c = &_i2c_bus_array[i2c_num];
|
||
|
||
// pins should be detached, else glitch
|
||
if(i2c->sda >= 0){
|
||
i2cDetachSDA(i2c, i2c->sda);
|
||
}
|
||
if(i2c->scl >= 0){
|
||
i2cDetachSCL(i2c, i2c->scl);
|
||
}
|
||
i2c->sda = sda;
|
||
i2c->scl = scl;
|
||
|
||
#if !CONFIG_DISABLE_HAL_LOCKS
|
||
if(i2c->lock == NULL) {
|
||
i2c->lock = xSemaphoreCreateRecursiveMutex();
|
||
if(i2c->lock == NULL) {
|
||
return NULL;
|
||
}
|
||
}
|
||
#endif
|
||
I2C_MUTEX_LOCK();
|
||
|
||
i2cReleaseISR(i2c); // ISR exists, release it before disabling hardware
|
||
|
||
if(frequency == 0) {// don't change existing frequency
|
||
frequency = i2cGetFrequency(i2c);
|
||
if(frequency == 0) {
|
||
frequency = 100000L; // default to 100khz
|
||
}
|
||
}
|
||
|
||
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);// 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->dev->ctr.val = 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 = 400000;//clocks max=1048575
|
||
//disable apb nonfifo access
|
||
i2c->dev->fifo_conf.nonfifo_en = 0;
|
||
|
||
i2c->dev->slave_addr.val = 0;
|
||
I2C_MUTEX_UNLOCK();
|
||
|
||
i2cSetFrequency(i2c, frequency); // reconfigure
|
||
|
||
if(!i2cCheckLineState(i2c->sda, i2c->scl)){
|
||
return NULL;
|
||
}
|
||
|
||
if(i2c->sda >= 0){
|
||
i2cAttachSDA(i2c, i2c->sda);
|
||
}
|
||
if(i2c->scl >= 0){
|
||
i2cAttachSCL(i2c, i2c->scl);
|
||
}
|
||
return i2c;
|
||
}
|
||
|
||
void i2cRelease(i2c_t *i2c) // release all resources, power down peripheral
|
||
{
|
||
I2C_MUTEX_LOCK();
|
||
|
||
if(i2c->sda >= 0){
|
||
i2cDetachSDA(i2c, i2c->sda);
|
||
}
|
||
if(i2c->scl >= 0){
|
||
i2cDetachSCL(i2c, i2c->scl);
|
||
}
|
||
|
||
i2cReleaseISR(i2c);
|
||
|
||
if(i2c->i2c_event) {
|
||
vEventGroupDelete(i2c->i2c_event);
|
||
i2c->i2c_event = NULL;
|
||
}
|
||
|
||
i2cFlush(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();
|
||
}
|
||
|
||
i2c_err_t i2cFlush(i2c_t * i2c)
|
||
{
|
||
if(i2c==NULL) {
|
||
return I2C_ERROR_DEV;
|
||
}
|
||
i2cTriggerDumps(i2c,i2c->debugFlags & 0xff, "FLUSH");
|
||
|
||
// 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 i2cWrite(i2c_t * i2c, uint16_t address, uint8_t* buff, uint16_t size, bool sendStop, uint16_t timeOutMillis){
|
||
if((i2c==NULL)||((size>0)&&(buff==NULL))) { // need to have location to store requested data
|
||
return I2C_ERROR_DEV;
|
||
}
|
||
i2c_err_t 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
|
||
last_error = i2cProcQueue(i2c, NULL, timeOutMillis);
|
||
if(last_error == I2C_ERROR_BUSY) { // try to clear the bus
|
||
if(i2cInit(i2c->num, i2c->sda, i2c->scl, 0)) {
|
||
last_error = i2cProcQueue(i2c, NULL, timeOutMillis);
|
||
}
|
||
}
|
||
i2cFlush(i2c);
|
||
} else { // stop not received, so wait for I2C stop,
|
||
last_error = I2C_ERROR_CONTINUE;
|
||
}
|
||
}
|
||
return last_error;
|
||
}
|
||
|
||
i2c_err_t i2cRead(i2c_t * i2c, uint16_t address, uint8_t* buff, uint16_t size, bool sendStop, uint16_t timeOutMillis, uint32_t *readCount){
|
||
if((size == 0)||(i2c == NULL)||(buff==NULL)){ // hardware will hang if no data requested on READ
|
||
return I2C_ERROR_DEV;
|
||
}
|
||
i2c_err_t 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
|
||
last_error = i2cProcQueue(i2c, readCount, timeOutMillis);
|
||
if(last_error == I2C_ERROR_BUSY) { // try to clear the bus
|
||
if(i2cInit(i2c->num, i2c->sda, i2c->scl, 0)) {
|
||
last_error = i2cProcQueue(i2c, readCount, timeOutMillis);
|
||
}
|
||
}
|
||
i2cFlush(i2c);
|
||
} else { // stop not received, so wait for I2C stop,
|
||
last_error = I2C_ERROR_CONTINUE;
|
||
}
|
||
}
|
||
return last_error;
|
||
}
|
||
|
||
#define MIN_I2C_CLKS 100 // minimum ratio between cpu and i2c Bus clocks
|
||
#define INTERRUPT_CYCLE_OVERHEAD 16000 // number of cpu clocks necessary to respond to interrupt
|
||
i2c_err_t i2cSetFrequency(i2c_t * i2c, uint32_t clk_speed)
|
||
{
|
||
if(i2c == NULL) {
|
||
return I2C_ERROR_DEV;
|
||
}
|
||
uint32_t apb = getApbFrequency();
|
||
uint32_t period = (apb/clk_speed) / 2;
|
||
|
||
if((apb/8192 > clk_speed)||(apb/MIN_I2C_CLKS < clk_speed)){ //out of bounds
|
||
log_d("i2c freq(%d) out of bounds.vs APB Clock(%d), min=%d, max=%d",clk_speed,apb,(apb/8192),(apb/MIN_I2C_CLKS));
|
||
}
|
||
if(period < (MIN_I2C_CLKS/2) ){
|
||
period = (MIN_I2C_CLKS/2);
|
||
clk_speed = apb/(period*2);
|
||
log_d("APB Freq too slow, Reducing i2c Freq to %d Hz",clk_speed);
|
||
} else if ( period> 4095) {
|
||
period = 4095;
|
||
clk_speed = apb/(period*2);
|
||
log_d("APB Freq too fast, Increasing i2c Freq to %d Hz",clk_speed);
|
||
}
|
||
#ifdef ENABLE_I2C_DEBUG_BUFFER
|
||
log_v("freq=%dHz",clk_speed);
|
||
#endif
|
||
uint32_t halfPeriod = period/2;
|
||
uint32_t quarterPeriod = period/4;
|
||
|
||
I2C_MUTEX_LOCK();
|
||
|
||
I2C_FIFO_CONF_t f;
|
||
|
||
f.val = i2c->dev->fifo_conf.val;
|
||
/* Adjust Fifo thresholds based on differential between cpu frequency and bus clock.
|
||
The fifo_delta is calculated such that at least INTERRUPT_CYCLE_OVERHEAD cpu clocks are
|
||
available when a Fifo interrupt is triggered. This allows enough room in the Fifo so that
|
||
interrupt latency does not cause a Fifo overflow/underflow event.
|
||
*/
|
||
#ifdef ENABLE_I2C_DEBUG_BUFFER
|
||
log_v("cpu Freq=%dMhz, i2c Freq=%dHz",getCpuFrequencyMhz(),clk_speed);
|
||
#endif
|
||
uint32_t fifo_delta = (INTERRUPT_CYCLE_OVERHEAD/((getCpuFrequencyMhz()*1000000 / clk_speed)*10))+1;
|
||
if (fifo_delta > 24) fifo_delta=24;
|
||
f.rx_fifo_full_thrhd = 32 - fifo_delta;
|
||
f.tx_fifo_empty_thrhd = fifo_delta;
|
||
i2c->dev->fifo_conf.val = f.val; // set thresholds
|
||
#ifdef ENABLE_I2C_DEBUG_BUFFER
|
||
log_v("Fifo delta=%d",fifo_delta);
|
||
#endif
|
||
//the clock num during SCL is low level
|
||
i2c->dev->scl_low_period.period = period;
|
||
//the clock num during SCL is high level
|
||
i2c->dev->scl_high_period.period = period;
|
||
|
||
//the clock num between the negedge of SDA and negedge of SCL for start mark
|
||
i2c->dev->scl_start_hold.time = halfPeriod;
|
||
//the clock num between the posedge of SCL and the negedge of SDA for restart mark
|
||
i2c->dev->scl_rstart_setup.time = halfPeriod;
|
||
|
||
//the clock num after the STOP bit's posedge
|
||
i2c->dev->scl_stop_hold.time = halfPeriod;
|
||
//the clock num between the posedge of SCL and the posedge of SDA
|
||
i2c->dev->scl_stop_setup.time = halfPeriod;
|
||
|
||
//the clock num I2C used to hold the data after the negedge of SCL.
|
||
i2c->dev->sda_hold.time = quarterPeriod;
|
||
//the clock num I2C used to sample data on SDA after the posedge of SCL
|
||
i2c->dev->sda_sample.time = quarterPeriod;
|
||
I2C_MUTEX_UNLOCK();
|
||
return I2C_ERROR_OK;
|
||
}
|
||
|
||
uint32_t i2cGetFrequency(i2c_t * i2c)
|
||
{
|
||
if(i2c == NULL) {
|
||
return 0;
|
||
}
|
||
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 = getApbFrequency() / old_count;
|
||
} else {
|
||
result = 0;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
|
||
uint32_t i2cDebug(i2c_t * i2c, uint32_t setBits, uint32_t resetBits){
|
||
if(i2c != NULL) {
|
||
i2c->debugFlags = ((i2c->debugFlags | setBits) & ~resetBits);
|
||
return i2c->debugFlags;
|
||
}
|
||
return 0;
|
||
|
||
}
|
||
|
||
uint32_t i2cGetStatus(i2c_t * i2c){
|
||
if(i2c != NULL){
|
||
return i2c->dev->status_reg.val;
|
||
}
|
||
else return 0;
|
||
}
|
||
|
||
|
||
/* todo
|
||
22JUL18
|
||
need to add multi-thread capability, use dq.queueEvent as the group marker. When multiple threads
|
||
transactions are present in the same queue, and an error occurs, abort all succeeding unserviced transactions
|
||
with the same dq.queueEvent value. Succeeding unserviced transactions with different dq.queueEvent values
|
||
can be re-queued and processed independently.
|
||
30JUL18 complete data only queue elements, this will allow transfers to use multiple data blocks,
|
||
*/
|
||
|