Implement Thread-Safe I2C based on ESP-IDF API (#5683)

* Implement Thread-Safe I2C based on ESP-IDF API

* Update esp32-hal.h

* use proper types for size and timeout

* Allow disabling of the HAL locks

* Limit frequency settings to prevent Interrupt WDT
This commit is contained in:
Me No Dev
2021-10-01 17:34:20 +03:00
committed by GitHub
parent ce85cf03cc
commit f87107dedb
7 changed files with 516 additions and 2423 deletions

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// modified Nov 2017 by Chuck Todd <StickBreaker> to support Interrupt Driven I/O // modified Nov 2017 by Chuck Todd <StickBreaker> to support Interrupt Driven I/O
// modified Nov 2021 by Hristo Gochkov <Me-No-Dev> to support ESP-IDF API
#ifndef _ESP32_HAL_I2C_H_ #ifndef _ESP32_HAL_I2C_H_
#define _ESP32_HAL_I2C_H_ #define _ESP32_HAL_I2C_H_
@ -22,58 +23,16 @@ extern "C" {
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include "freertos/FreeRTOS.h" #include <esp_err.h>
#include "freertos/event_groups.h"
// External Wire.h equivalent error Codes esp_err_t i2cInit(uint8_t i2c_num, int8_t sda, int8_t scl, uint32_t clk_speed);
typedef enum { esp_err_t i2cDeinit(uint8_t i2c_num);
I2C_ERROR_OK=0, esp_err_t i2cSetClock(uint8_t i2c_num, uint32_t frequency);
I2C_ERROR_DEV, esp_err_t i2cGetClock(uint8_t i2c_num, uint32_t * frequency);
I2C_ERROR_ACK, esp_err_t i2cWrite(uint8_t i2c_num, uint16_t address, const uint8_t* buff, size_t size, uint32_t timeOutMillis);
I2C_ERROR_TIMEOUT, esp_err_t i2cRead(uint8_t i2c_num, uint16_t address, uint8_t* buff, size_t size, uint32_t timeOutMillis, size_t *readCount);
I2C_ERROR_BUS, esp_err_t i2cWriteReadNonStop(uint8_t i2c_num, uint16_t address, const uint8_t* wbuff, size_t wsize, uint8_t* rbuff, size_t rsize, uint32_t timeOutMillis, size_t *readCount);
I2C_ERROR_BUSY, bool i2cIsInit(uint8_t i2c_num);
I2C_ERROR_MEMORY,
I2C_ERROR_CONTINUE,
I2C_ERROR_NO_BEGIN
} i2c_err_t;
struct i2c_struct_t;
typedef struct i2c_struct_t i2c_t;
i2c_t * i2cInit(uint8_t i2c_num, int8_t sda, int8_t scl, uint32_t clk_speed);
void i2cRelease(i2c_t *i2c); // free ISR, Free DQ, Power off peripheral clock. Must call i2cInit() to recover
i2c_err_t i2cWrite(i2c_t * i2c, uint16_t address, uint8_t* buff, uint16_t size, bool sendStop, uint16_t timeOutMillis);
i2c_err_t i2cRead(i2c_t * i2c, uint16_t address, uint8_t* buff, uint16_t size, bool sendStop, uint16_t timeOutMillis, uint32_t *readCount);
i2c_err_t i2cFlush(i2c_t *i2c);
i2c_err_t i2cSetFrequency(i2c_t * i2c, uint32_t clk_speed);
uint32_t i2cGetFrequency(i2c_t * i2c);
uint32_t i2cGetStatus(i2c_t * i2c); // Status register of peripheral
//Functions below should be used only if well understood
//Might be deprecated and removed in future
i2c_err_t i2cAttachSCL(i2c_t * i2c, int8_t scl);
i2c_err_t i2cDetachSCL(i2c_t * i2c, int8_t scl);
i2c_err_t i2cAttachSDA(i2c_t * i2c, int8_t sda);
i2c_err_t i2cDetachSDA(i2c_t * i2c, int8_t sda);
//Stickbreakers ISR Support
i2c_err_t i2cProcQueue(i2c_t *i2c, uint32_t *readCount, uint16_t timeOutMillis);
i2c_err_t i2cAddQueueWrite(i2c_t *i2c, uint16_t i2cDeviceAddr, uint8_t *dataPtr, uint16_t dataLen, bool SendStop, EventGroupHandle_t event);
i2c_err_t i2cAddQueueRead(i2c_t *i2c, uint16_t i2cDeviceAddr, uint8_t *dataPtr, uint16_t dataLen, bool SendStop, EventGroupHandle_t event);
//stickbreaker debug support
uint32_t i2cDebug(i2c_t *, uint32_t setBits, uint32_t resetBits);
// Debug actions have 3 currently defined locus
// 0xXX------ : at entry of ProcQueue
// 0x--XX---- : at exit of ProcQueue
// 0x------XX : at entry of Flush
//
// bit 0 causes DumpI2c to execute
// bit 1 causes DumpInts to execute
// bit 2 causes DumpCmdqueue to execute
// bit 3 causes DumpStatus to execute
// bit 4 causes DumpFifo to execute
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -31,6 +31,11 @@
#include "sdkconfig.h" #include "sdkconfig.h"
#include "esp_system.h" #include "esp_system.h"
#include "esp_sleep.h" #include "esp_sleep.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {

View File

@ -1,276 +0,0 @@
# Debugging I2C
With the release of Arduino-ESP32 V1.0.1 the I2C subsystem contains code to exhaustively report communication errors.
* Basic debugging can be enable by setting the *CORE DEBUG LEVEL* at or above *ERROR*. All errors will be directed the the *DEBUG OUTPUT* normally connected to `Serial()`.
* Enhanced debugging can be used to generate specified information at specific positions during the i2c communication sequence. Increase *CORE DEBUG LEVEL* to ***DEBUG***
## Enable Debug Buffer
The Enhanced debug features are enabled by uncommenting the `\\#define ENABLE_I2C_DEBUG_BUFFER` at line 45 of `esp32-hal-i2c.c`.
* When Arduino-Esp32 is installed in Windows with Arduino Boards Manager, `esp32-hal-i2c.c` can be found in:
`C:\Users\{user}\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.1\cores\esp32\`
* When Arduino-Esp32 Development version is installed from GitHub, `esp32-hal-i2c.c` can be found in:
`{arduino Sketch}\hardware\espressif\esp32\cores\esp32\`
```c++
//#define ENABLE_I2C_DEBUG_BUFFER
```
Change it to:
```c++
#define ENABLE_I2C_DEBUG_BUFFER
```
and recompile/upload the resulting code to your ESP32.
Enabling this `#define` will consume an additional 2570 bytes of RAM and include a commensurate amount of code FLASH. If you see the message `"Debug Buffer not Enabled"` in your console log I would suggest you un-comment the line and regenerate the error. Additional information will be supplied on the log console.
## Manually controlled Debugging
Manual logging of the i2c control data buffers can be accomplished by using the debug control function of `Wire()`:
```c++
uint32_t setDebugFlags( uint32_t setBits, uint32_t resetBits);
```
`setBits`, and `resetBits` manually cause output of the control structures to the log console. They are bit fields that enable/disable the reporting of individual control structures during specific phases of the i2c communications sequence. The 32bit values are divided into four 8bit fields. Currently only five bits are defined. ***If an error is detected during normal operations, the relevant control structure will bit added to the log irrespective of the current debug flags.***
* **bit 0** causes DumpI2c to execute
header information about current communications event,
and the dataQueue elements showing the logical i2c transaction commands
* **bit 1** causes DumpInts to execute
Actual sequence of interrupts handled during last communications event, cleared on entry into `ProcQueue()`.
* **bit 2** causes DumpCmdqueue to execute
The last block of commands to the i2c peripheral.
* **bit 3** causes DumpStatus to execute
A descriptive display of the 32bit i2c peripheral status word.
* **bit 4** causes DumpFifo to execute
A buffer listing the sequence of data added to the txFifo of the i2c peripheral.
Of the four division, only three are currently implemented:
* 0xXX - - - - - - : at entry of ProcQueue (`bitFlags << 24`)
* 0x - - XX - - - - : at exit of ProcQueue (`bitFlags << 16`)
* 0x - - - - - - XX : at entry of Flush (`bitFlags`)
For example, to display the sequence of Interrupts processed during the i2c communication transaction, **bit 1** would be set, and, since this information on Interrupt usage would only be valid after the communications have completed, the locus would be *at exit of ProcQueue*. The following code would be necessary.
### code
```c++
uint8_t flag = 1 << 1; // turn on bit 1
uint32_t debugFlag = flag << 16; // correctly position the 8bits of flag as the second byte of setBits.
Wire.setDebugFlags(debugFlag,0);// resetBits=0 says leave all current setBits as is.
Wire.requestFrom(id,byteCount); // read byteCount bytes from slave at id
Wire.setDebugFlags(0,debugFlag); // don't add any new debug, remove debugFlag
```
### output of log console
```
[I][esp32-hal-i2c.c:437] i2cTriggerDumps(): after ProcQueue
[I][esp32-hal-i2c.c:346] i2cDumpInts(): 0 row count INTR TX RX Tick
[I][esp32-hal-i2c.c:350] i2cDumpInts(): [01] 0x0001 0x0002 0x0003 0x0000 0x005baac5
[I][esp32-hal-i2c.c:350] i2cDumpInts(): [02] 0x0001 0x0200 0x0000 0x0000 0x005baac5
[I][esp32-hal-i2c.c:350] i2cDumpInts(): [03] 0x0001 0x0080 0x0000 0x0008 0x005baac6
```
# Debug Log example
### Code
To read eight bytes of data from a DS1307 RTCC
```
uint32_t debugFlag = 0x001F0000;
uint8_t ID = 0x68;
uint8_t block=8;
if(debugFlag >0){
Wire.setDebugFlags(debugFlag,0);
}
Wire.beginTransmission(ID);
Wire.write(lowByte(addr));
if((err=Wire.endTransmission(false))!=0) {
Serial.printf(" EndTransmission=%d(%s)",Wire.lastError(),Wire.getErrorText(Wire.lastError()));
if(err!=2) {
Serial.printf(", resetting\n");
if( !Wire.begin()) Serial.printf(" Reset Failed\n");
if(debugFlag >0) Wire.setDebugFlags(0,debugFlag);
return;
} else {
Serial.printf(", No Device present, aborting\n");
currentCommand= NO_COMMAND;
return;
}
}
err = Wire.requestFrom(ID,block,true);
if(debugFlag >0){
Wire.setDebugFlags(0,debugFlag);
}
```
### output of log console
```
[I][esp32-hal-i2c.c:437] i2cTriggerDumps(): after ProcQueue
[E][esp32-hal-i2c.c:318] i2cDumpI2c(): i2c=0x3ffbdc78
[I][esp32-hal-i2c.c:319] i2cDumpI2c(): dev=0x60013000 date=0x16042000
[I][esp32-hal-i2c.c:321] i2cDumpI2c(): lock=0x3ffb843c
[I][esp32-hal-i2c.c:323] i2cDumpI2c(): num=0
[I][esp32-hal-i2c.c:324] i2cDumpI2c(): mode=1
[I][esp32-hal-i2c.c:325] i2cDumpI2c(): stage=3
[I][esp32-hal-i2c.c:326] i2cDumpI2c(): error=1
[I][esp32-hal-i2c.c:327] i2cDumpI2c(): event=0x3ffb85c4 bits=10
[I][esp32-hal-i2c.c:328] i2cDumpI2c(): intr_handle=0x3ffb85f4
[I][esp32-hal-i2c.c:329] i2cDumpI2c(): dq=0x3ffb858c
[I][esp32-hal-i2c.c:330] i2cDumpI2c(): queueCount=2
[I][esp32-hal-i2c.c:331] i2cDumpI2c(): queuePos=1
[I][esp32-hal-i2c.c:332] i2cDumpI2c(): errorByteCnt=0
[I][esp32-hal-i2c.c:333] i2cDumpI2c(): errorQueue=0
[I][esp32-hal-i2c.c:334] i2cDumpI2c(): debugFlags=0x001F0000
[I][esp32-hal-i2c.c:288] i2cDumpDqData(): [0] 7bit 68 W buf@=0x3ffc04b2, len=1, pos=1, ctrl=11101
[I][esp32-hal-i2c.c:306] i2cDumpDqData(): 0x0000: . 00
[I][esp32-hal-i2c.c:288] i2cDumpDqData(): [1] 7bit 68 R STOP buf@=0x3ffc042c, len=8, pos=8, ctrl=11111
[I][esp32-hal-i2c.c:306] i2cDumpDqData(): 0x0000: 5P...... 35 50 07 06 13 09 18 00
[I][esp32-hal-i2c.c:346] i2cDumpInts(): 0 row count INTR TX RX Tick
[I][esp32-hal-i2c.c:350] i2cDumpInts(): [01] 0x0001 0x0002 0x0003 0x0000 0x000073d5
[I][esp32-hal-i2c.c:350] i2cDumpInts(): [02] 0x0001 0x0200 0x0000 0x0000 0x000073d5
[I][esp32-hal-i2c.c:350] i2cDumpInts(): [03] 0x0001 0x0080 0x0000 0x0008 0x000073d6
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 0] Y RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 1] Y WRITE val[0] exp[0] en[1] bytes[1]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 2] Y WRITE val[0] exp[0] en[1] bytes[1]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 3] Y RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 4] Y WRITE val[0] exp[0] en[1] bytes[1]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 5] Y READ val[0] exp[0] en[0] bytes[7]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 6] Y READ val[1] exp[0] en[0] bytes[1]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 7] Y STOP val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 8] N RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 9] N RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [10] N RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [11] N RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [12] N RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [13] N RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [14] N RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [15] N RSTART val[0] exp[0] en[0] bytes[0]
[I][esp32-hal-i2c.c:385] i2cDumpStatus(): ack(0) sl_rw(0) to(0) arb(0) busy(0) sl(1) trans(0) rx(0) tx(0) sclMain(5) scl(6)
[I][esp32-hal-i2c.c:424] i2cDumpFifo(): WRITE 0x68 0
[I][esp32-hal-i2c.c:424] i2cDumpFifo(): READ 0x68
```
## Explaination of log output
### DumpI2c
```
[I][esp32-hal-i2c.c:437] i2cTriggerDumps(): after ProcQueue
[E][esp32-hal-i2c.c:318] i2cDumpI2c(): i2c=0x3ffbdc78
[I][esp32-hal-i2c.c:319] i2cDumpI2c(): dev=0x60013000 date=0x16042000
[I][esp32-hal-i2c.c:321] i2cDumpI2c(): lock=0x3ffb843c
[I][esp32-hal-i2c.c:323] i2cDumpI2c(): num=0
[I][esp32-hal-i2c.c:324] i2cDumpI2c(): mode=1
[I][esp32-hal-i2c.c:325] i2cDumpI2c(): stage=3
[I][esp32-hal-i2c.c:326] i2cDumpI2c(): error=1
[I][esp32-hal-i2c.c:327] i2cDumpI2c(): event=0x3ffb85c4 bits=10
[I][esp32-hal-i2c.c:328] i2cDumpI2c(): intr_handle=0x3ffb85f4
[I][esp32-hal-i2c.c:329] i2cDumpI2c(): dq=0x3ffb858c
[I][esp32-hal-i2c.c:330] i2cDumpI2c(): queueCount=2
[I][esp32-hal-i2c.c:331] i2cDumpI2c(): queuePos=1
[I][esp32-hal-i2c.c:332] i2cDumpI2c(): errorByteCnt=0
[I][esp32-hal-i2c.c:333] i2cDumpI2c(): errorQueue=0
[I][esp32-hal-i2c.c:334] i2cDumpI2c(): debugFlags=0x001F0000
```
variable | description
---- | ----
**i2c** | *memory address for control block*
**dev** | *memory address for access to i2c peripheral registers*
**date** | *revision date of peripheral silicon 2016, 42 week*
**lock** | *hal lock handle*
**num** | *0,1 which peripheral is being controlled*
**mode** | *configuration of driver 0=none, 1=MASTER, 2=SLAVE, 3=MASTER and SLAVE*
**stage** | *internal STATE of driver 0=not configured, 1=startup, 2=running, 3=done*
**error** | *internal ERROR status 0=not configured, 1=ok, 2=error, 3=address NAK, 4=data NAK, 5=arbitration loss, 6=timeout*
**event** | *handle for interprocess FreeRTOS eventSemaphore for communication between ISR and APP*
**intr_handle** | *FreeRTOS handle for allocated interrupt*
**dq** | *memory address for data queue control block*
**queueCount** | *number of data operations in queue control block*
**queuePos** | *last executed data block*
**errorByteCnt** | *position in current data block when error occured -1=address byte*
**errorQueue** | *queue that was executing when error occurred*
**debugFlags** | *current specified error bits*
### DQ data
```
[I][esp32-hal-i2c.c:288] i2cDumpDqData(): [0] 7bit 68 W buf@=0x3ffc04b2, len=1, pos=1, ctrl=11101
[I][esp32-hal-i2c.c:306] i2cDumpDqData(): 0x0000: . 00
[I][esp32-hal-i2c.c:288] i2cDumpDqData(): [1] 7bit 68 R STOP buf@=0x3ffc042c, len=8, pos=8, ctrl=11111
[I][esp32-hal-i2c.c:306] i2cDumpDqData(): 0x0000: 5P...... 35 50 07 06 13 09 18 00
```
variable | description
--- | ---
**[n]** | *index of data queue element*
**i2c address** | *7bit= 7bit i2c slave address, 10bit= 10bit i2c slave address*
**direction** | *W=Write, R=READ*
**STOP** | *command issued a I2C STOP, else if blank, a RESTART was issued by next dq element.*
**buf@** | *pointer to data buffer*
**len** | *length of data buffer*
**pos** | *last position used in buffer*
**ctrl** | *bit field for data queue control, this bits describe if all necessary commands have been added to peripheral command buffer. in order(START,ADDRESS_Write,DATA,STOP,ADDRESS_value*
**0xnnnn** | *data buffer content, displayable followed by HEX, 32 bytes on a line.*
### DumpInts
```
[I][esp32-hal-i2c.c:346] i2cDumpInts(): 0 row count INTR TX RX Tick
[I][esp32-hal-i2c.c:350] i2cDumpInts(): [01] 0x0001 0x0002 0x0003 0x0000 0x000073d5
[I][esp32-hal-i2c.c:350] i2cDumpInts(): [02] 0x0001 0x0200 0x0000 0x0000 0x000073d5
[I][esp32-hal-i2c.c:350] i2cDumpInts(): [03] 0x0001 0x0080 0x0000 0x0008 0x000073d6
```
variable | description
---- | ----
**row** | *array index*
**count** | *number of consecutive, duplicate interrupts*
**INTR** | *bit value of active interrupt (from ..\esp32\tools\sdk\include\soc\soc\i2c_struct.h)*
**TX** | *number of bytes added to txFifo*
**RX** | *number of bytes read from rxFifo*
**Tick** | *current tick counter from xTaskGetTickCountFromISR()*
### DumpCmdQueue
```
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 0] Y RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 1] Y WRITE val[0] exp[0] en[1] bytes[1]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 2] Y WRITE val[0] exp[0] en[1] bytes[1]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 3] Y RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 4] Y WRITE val[0] exp[0] en[1] bytes[1]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 5] Y READ val[0] exp[0] en[0] bytes[7]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 6] Y READ val[1] exp[0] en[0] bytes[1]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 7] Y STOP val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 8] N RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [ 9] N RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [10] N RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [11] N RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [12] N RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [13] N RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [14] N RSTART val[0] exp[0] en[0] bytes[0]
[E][esp32-hal-i2c.c:243] i2cDumpCmdQueue(): [15] N RSTART val[0] exp[0] en[0] bytes[0]
```
Column | description
---- | ----
**command** | *RSTART= generate i2c START sequence, WRITE= output byte(s), READ= input byte(s), STOP= generate i2c STOP sequence, END= continuation flag for peripheral to pause execution waiting for a refilled command list*
**val** | *value for ACK bit, 0 = LOW, 1= HIGH*
**exp** | *test of ACK bit 0=no, 1=yes*
**en** | *output of val, 0=no, 1=yes*
**bytes** | *number of byte to send(WRITE) or receive(READ) 1..255*
### DumpStatus
```
[I][esp32-hal-i2c.c:385] i2cDumpStatus(): ack(0) sl_rw(0) to(0) arb(0) busy(0) sl(1) trans(0) rx(0) tx(0) sclMain(5) scl(6)
```
variable | description
---- | ----
**ack** | *last value for ACK bit*
**sl_rw** | *mode for SLAVE operation 0=write, 1=read*
**to** | *timeout*
**arb** | *arbitration loss*
**busy** | *bus is inuse by other Master, or SLAVE is holding SCL,SDA*
**sl** | *last address on bus was equal to slave_addr*
**trans** | *a byte has moved though peripheral*
**rx** | *count of bytes in rxFifo*
**tx** | *count of bytes in txFifo*
**sclMain** | *state machine for i2c module. 0: SCL_MAIN_IDLE, 1: SCL_ADDRESS_SHIFT, 2: SCL_ACK_ADDRESS, 3: SCL_RX_DATA, 4: SCL_TX_DATA, 5: SCL_SEND_ACK, 6 :SCL_WAIT_ACK*
**scl** | *SCL clock state. 0: SCL_IDLE, 1: SCL_START, 2: SCL_LOW_EDGE, 3: SCL_LOW, 4: SCL_HIGH_EDGE, 5: SCL_HIGH, 6:SCL_STOP*
### DumpFifo
```
[I][esp32-hal-i2c.c:424] i2cDumpFifo(): WRITE 0x68 0
[I][esp32-hal-i2c.c:424] i2cDumpFifo(): READ 0x68
```
Mode | datavalues
--- | ---
**WRITE** | the following bytes added to the txFifo are in response to a WRITE command
**READ** | the following bytes added to the txFifo are in response to a READ command

View File

@ -11,13 +11,14 @@
####################################### #######################################
begin KEYWORD2 begin KEYWORD2
end KEYWORD2
setClock KEYWORD2 setClock KEYWORD2
setClockStretchLimit KEYWORD2 getClock KEYWORD2
setTimeOut KEYWORD2
getTimeOut KEYWORD2
beginTransmission KEYWORD2 beginTransmission KEYWORD2
endTransmission KEYWORD2 endTransmission KEYWORD2
requestFrom KEYWORD2 requestFrom KEYWORD2
send KEYWORD2
receive KEYWORD2
onReceive KEYWORD2 onReceive KEYWORD2
onRequest KEYWORD2 onRequest KEYWORD2
@ -26,6 +27,7 @@ onRequest KEYWORD2
####################################### #######################################
Wire KEYWORD2 Wire KEYWORD2
TwoWire KEYWORD2
####################################### #######################################
# Constants (LITERAL1) # Constants (LITERAL1)

View File

@ -20,6 +20,7 @@
Modified December 2014 by Ivan Grokhotkov (ivan@esp8266.com) - esp8266 support Modified December 2014 by Ivan Grokhotkov (ivan@esp8266.com) - esp8266 support
Modified April 2015 by Hrsto Gochkov (ficeto@ficeto.com) - alternative 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 Modified Nov 2017 by Chuck Todd (ctodd@cableone.net) - ESP32 ISR Support
Modified Nov 2021 by Hristo Gochkov <Me-No-Dev> to support ESP-IDF API
*/ */
extern "C" { extern "C" {
@ -36,41 +37,79 @@ TwoWire::TwoWire(uint8_t bus_num)
:num(bus_num & 1) :num(bus_num & 1)
,sda(-1) ,sda(-1)
,scl(-1) ,scl(-1)
,i2c(NULL)
,rxIndex(0) ,rxIndex(0)
,rxLength(0) ,rxLength(0)
,rxQueued(0)
,txIndex(0)
,txLength(0) ,txLength(0)
,txAddress(0) ,txAddress(0)
,txQueued(0)
,transmitting(0)
,last_error(I2C_ERROR_OK)
,_timeOutMillis(50) ,_timeOutMillis(50)
,nonStop(false)
#if !CONFIG_DISABLE_HAL_LOCKS
,nonStopTask(NULL)
,lock(NULL)
#endif
{} {}
TwoWire::~TwoWire() TwoWire::~TwoWire()
{ {
flush(); end();
if(i2c) { #if !CONFIG_DISABLE_HAL_LOCKS
i2cRelease(i2c); if(lock != NULL){
i2c=NULL; vSemaphoreDelete(lock);
} }
#endif
} }
bool TwoWire::setPins(int sdaPin, int sclPin) bool TwoWire::setPins(int sdaPin, int sclPin)
{ {
if(i2c) { #if !CONFIG_DISABLE_HAL_LOCKS
log_e("can not set pins if begin was already called"); if(lock == NULL){
lock = xSemaphoreCreateMutex();
if(lock == NULL){
log_e("xSemaphoreCreateMutex failed");
return false; return false;
} }
}
//acquire lock
if(xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){
log_e("could not acquire lock");
return false;
}
#endif
if(!i2cIsInit(num)){
sda = sdaPin; sda = sdaPin;
scl = sclPin; scl = sclPin;
return true; } else {
log_e("bus already initialized. change pins only when not.");
}
#if !CONFIG_DISABLE_HAL_LOCKS
//release lock
xSemaphoreGive(lock);
#endif
return !i2cIsInit(num);
} }
bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency) bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency)
{ {
bool started = false;
esp_err_t err = ESP_OK;
#if !CONFIG_DISABLE_HAL_LOCKS
if(lock == NULL){
lock = xSemaphoreCreateMutex();
if(lock == NULL){
log_e("xSemaphoreCreateMutex failed");
return false;
}
}
//acquire lock
if(xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){
log_e("could not acquire lock");
return false;
}
#endif
if(i2cIsInit(num)){
started = true;
goto end;
}
if(sdaPin < 0) { // default param passed if(sdaPin < 0) { // default param passed
if(num == 0) { if(num == 0) {
if(sda==-1) { if(sda==-1) {
@ -81,7 +120,7 @@ bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency)
} else { } else {
if(sda==-1) { if(sda==-1) {
log_e("no Default SDA Pin for Second Peripheral"); log_e("no Default SDA Pin for Second Peripheral");
return false; //no Default pin for Second Peripheral goto end; //no Default pin for Second Peripheral
} else { } else {
sdaPin = sda; // reuse prior pin sdaPin = sda; // reuse prior pin
} }
@ -98,7 +137,7 @@ bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency)
} else { } else {
if(scl == -1) { if(scl == -1) {
log_e("no Default SCL Pin for Second Peripheral"); log_e("no Default SCL Pin for Second Peripheral");
return false; //no Default pin for Second Peripheral goto end; //no Default pin for Second Peripheral
} else { } else {
sclPin = scl; // reuse prior pin sclPin = scl; // reuse prior pin
} }
@ -107,14 +146,74 @@ bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency)
sda = sdaPin; sda = sdaPin;
scl = sclPin; scl = sclPin;
i2c = i2cInit(num, sda, scl, frequency); err = i2cInit(num, sda, scl, frequency);
if(!i2c) { started = (err == ESP_OK);
return false;
end:
#if !CONFIG_DISABLE_HAL_LOCKS
//release lock
xSemaphoreGive(lock);
#endif
return started;
} }
flush(); bool TwoWire::end()
return true; {
esp_err_t err = ESP_OK;
#if !CONFIG_DISABLE_HAL_LOCKS
if(lock != NULL){
//acquire lock
if(xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){
log_e("could not acquire lock");
return false;
}
#endif
if(i2cIsInit(num)){
err = i2cDeinit(num);
}
#if !CONFIG_DISABLE_HAL_LOCKS
//release lock
xSemaphoreGive(lock);
}
#endif
return (err == ESP_OK);
}
uint32_t TwoWire::getClock()
{
uint32_t frequency = 0;
#if !CONFIG_DISABLE_HAL_LOCKS
//acquire lock
if(lock == NULL || xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){
log_e("could not acquire lock");
} else {
#endif
i2cGetClock(num, &frequency);
#if !CONFIG_DISABLE_HAL_LOCKS
//release lock
xSemaphoreGive(lock);
}
#endif
return frequency;
}
bool TwoWire::setClock(uint32_t frequency)
{
esp_err_t err = ESP_OK;
#if !CONFIG_DISABLE_HAL_LOCKS
//acquire lock
if(lock == NULL || xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){
log_e("could not acquire lock");
return false;
}
#endif
err = i2cSetClock(num, frequency);
#if !CONFIG_DISABLE_HAL_LOCKS
//release lock
xSemaphoreGive(lock);
#endif
return (err == ESP_OK);
} }
void TwoWire::setTimeOut(uint16_t timeOutMillis) void TwoWire::setTimeOut(uint16_t timeOutMillis)
@ -127,119 +226,93 @@ uint16_t TwoWire::getTimeOut()
return _timeOutMillis; return _timeOutMillis;
} }
void TwoWire::setClock(uint32_t frequency) void TwoWire::beginTransmission(uint16_t address)
{ {
#if CONFIG_IDF_TARGET_ESP32S2 #if !CONFIG_DISABLE_HAL_LOCKS
i2c = i2cInit(num, sda, scl, frequency); if(nonStop && nonStopTask == xTaskGetCurrentTaskHandle()){
if(!i2c) { log_e("Unfinished Repeated Start transaction! Expected requestFrom, not beginTransmission! Clearing...");
//release lock
xSemaphoreGive(lock);
}
//acquire lock
if(lock == NULL || xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){
log_e("could not acquire lock");
return; return;
} }
#endif #endif
i2cSetFrequency(i2c, frequency); nonStop = false;
}
size_t TwoWire::getClock()
{
return i2cGetFrequency(i2c);
}
/* stickBreaker Nov 2017 ISR, and bigblock 64k-1
*/
i2c_err_t TwoWire::writeTransmission(uint16_t address, uint8_t *buff, uint16_t size, bool sendStop)
{
last_error = i2cWrite(i2c, address, buff, size, sendStop, _timeOutMillis);
return last_error;
}
i2c_err_t TwoWire::readTransmission(uint16_t address, uint8_t *buff, uint16_t size, bool sendStop, uint32_t *readCount)
{
last_error = i2cRead(i2c, address, buff, size, sendStop, _timeOutMillis, readCount);
return last_error;
}
void TwoWire::beginTransmission(uint16_t address)
{
transmitting = 1;
txAddress = address; txAddress = address;
txIndex = txQueued; // allow multiple beginTransmission(),write(),endTransmission(false) until endTransmission(true)
txLength = txQueued;
last_error = I2C_ERROR_OK;
}
/*stickbreaker isr
*/
uint8_t TwoWire::endTransmission(bool sendStop) // Assumes Wire.beginTransaction(), Wire.write()
{
if(transmitting == 1) {
// txlength is howmany bytes in txbuffer have been use
last_error = writeTransmission(txAddress, &txBuffer[txQueued], txLength - txQueued, sendStop);
if(last_error == I2C_ERROR_CONTINUE){
txQueued = txLength;
} else if( last_error == I2C_ERROR_OK){
rxIndex = 0;
rxLength = rxQueued;
rxQueued = 0;
txQueued = 0; // the SendStop=true will restart all Queueing
}
} else {
last_error = I2C_ERROR_NO_BEGIN;
flush();
}
txIndex = 0;
txLength = 0; txLength = 0;
transmitting = 0;
return (last_error == I2C_ERROR_CONTINUE)?I2C_ERROR_OK:last_error; // Don't return Continue for compatibility.
} }
/* @stickBreaker 11/2017 fix for ReSTART timeout, ISR uint8_t TwoWire::endTransmission(bool sendStop)
*/ {
esp_err_t err = ESP_OK;
if(sendStop){
err = i2cWrite(num, txAddress, txBuffer, txLength, _timeOutMillis);
#if !CONFIG_DISABLE_HAL_LOCKS
//release lock
xSemaphoreGive(lock);
#endif
} else {
//mark as non-stop
nonStop = true;
#if !CONFIG_DISABLE_HAL_LOCKS
nonStopTask = xTaskGetCurrentTaskHandle();
#endif
}
switch(err){
case ESP_OK: return 0;
case ESP_FAIL: return 2;
case ESP_ERR_TIMEOUT: return 5;
default: break;
}
return 4;
}
uint8_t TwoWire::requestFrom(uint16_t address, uint8_t size, bool sendStop) 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 esp_err_t err = ESP_OK;
uint32_t cnt = rxQueued; // currently queued reads, next available position in rxBuffer if(nonStop
if(cnt < (I2C_BUFFER_LENGTH-1) && (size + cnt) <= I2C_BUFFER_LENGTH) { // any room left in rxBuffer #if !CONFIG_DISABLE_HAL_LOCKS
rxQueued += size; && nonStopTask == xTaskGetCurrentTaskHandle()
} else { // no room to receive more! #endif
log_e("rxBuff overflow %d", cnt + size); ){
cnt = 0; if(address != txAddress){
last_error = I2C_ERROR_MEMORY; log_e("Unfinished Repeated Start transaction! Expected address do not match! %u != %u", address, txAddress);
flush(); return 0;
return cnt;
} }
nonStop = false;
last_error = readTransmission(address, &rxBuffer[cnt], size, sendStop, &cnt);
rxIndex = 0; rxIndex = 0;
rxLength = 0;
rxLength = cnt; err = i2cWriteReadNonStop(num, address, txBuffer, txLength, rxBuffer, size, _timeOutMillis, &rxLength);
} else {
if( last_error != I2C_ERROR_CONTINUE){ // not a buffered ReSTART operation #if !CONFIG_DISABLE_HAL_LOCKS
// so this operation actually moved data, queuing is done. //acquire lock
rxQueued = 0; if(lock == NULL || xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){
txQueued = 0; // the SendStop=true will restart all Queueing or error condition log_e("could not acquire lock");
return 0;
} }
#endif
if(last_error != I2C_ERROR_OK){ // ReSTART on read does not return any data rxIndex = 0;
cnt = 0; rxLength = 0;
err = i2cRead(num, address, rxBuffer, size, _timeOutMillis, &rxLength);
} }
#if !CONFIG_DISABLE_HAL_LOCKS
return cnt; //release lock
xSemaphoreGive(lock);
#endif
return rxLength;
} }
size_t TwoWire::write(uint8_t data) size_t TwoWire::write(uint8_t data)
{ {
if(transmitting) {
if(txLength >= I2C_BUFFER_LENGTH) { if(txLength >= I2C_BUFFER_LENGTH) {
last_error = I2C_ERROR_MEMORY;
return 0; return 0;
} }
txBuffer[txIndex] = data; txBuffer[txLength++] = data;
++txIndex;
txLength = txIndex;
return 1; return 1;
} }
last_error = I2C_ERROR_NO_BEGIN; // no begin, not transmitting
return 0;
}
size_t TwoWire::write(const uint8_t *data, size_t quantity) size_t TwoWire::write(const uint8_t *data, size_t quantity)
{ {
@ -262,8 +335,7 @@ int TwoWire::read(void)
{ {
int value = -1; int value = -1;
if(rxIndex < rxLength) { if(rxIndex < rxLength) {
value = rxBuffer[rxIndex]; value = rxBuffer[rxIndex++];
++rxIndex;
} }
return value; return value;
} }
@ -281,11 +353,8 @@ void TwoWire::flush(void)
{ {
rxIndex = 0; rxIndex = 0;
rxLength = 0; rxLength = 0;
txIndex = 0;
txLength = 0; txLength = 0;
rxQueued = 0; //i2cFlush(num); // cleanup
txQueued = 0;
i2cFlush(i2c); // cleanup
} }
uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop) uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop)
@ -333,56 +402,5 @@ uint8_t TwoWire::endTransmission(void)
return endTransmission(true); return endTransmission(true);
} }
/* 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;
}
}
/*stickbreaker Dump i2c Interrupt buffer, i2c isr Debugging
*/
uint32_t TwoWire::setDebugFlags( uint32_t setBits, uint32_t resetBits){
return i2cDebug(i2c,setBits,resetBits);
}
bool TwoWire::busy(void){
return ((i2cGetStatus(i2c) & 16 )==16);
}
TwoWire Wire = TwoWire(0); TwoWire Wire = TwoWire(0);
TwoWire Wire1 = TwoWire(1); TwoWire Wire1 = TwoWire(1);

View File

@ -20,17 +20,20 @@
Modified December 2014 by Ivan Grokhotkov (ivan@esp8266.com) - esp8266 support Modified December 2014 by Ivan Grokhotkov (ivan@esp8266.com) - esp8266 support
Modified April 2015 by Hrsto Gochkov (ficeto@ficeto.com) - alternative esp8266 support Modified April 2015 by Hrsto Gochkov (ficeto@ficeto.com) - alternative esp8266 support
Modified November 2017 by Chuck Todd <stickbreaker on GitHub> to use ISR and increase stability. Modified November 2017 by Chuck Todd <stickbreaker on GitHub> to use ISR and increase stability.
Modified Nov 2021 by Hristo Gochkov <Me-No-Dev> to support ESP-IDF API
*/ */
#ifndef TwoWire_h #ifndef TwoWire_h
#define TwoWire_h #define TwoWire_h
#include <esp32-hal.h> #include <esp32-hal.h>
#if !CONFIG_DISABLE_HAL_LOCKS
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/queue.h" #include "freertos/task.h"
#include "freertos/semphr.h"
#endif
#include "Stream.h" #include "Stream.h"
#define STICKBREAKER 'V1.1.0'
#ifndef I2C_BUFFER_LENGTH #ifndef I2C_BUFFER_LENGTH
#define I2C_BUFFER_LENGTH 128 #define I2C_BUFFER_LENGTH 128
#endif #endif
@ -43,28 +46,21 @@ protected:
uint8_t num; uint8_t num;
int8_t sda; int8_t sda;
int8_t scl; int8_t scl;
i2c_t * i2c;
uint8_t rxBuffer[I2C_BUFFER_LENGTH]; uint8_t rxBuffer[I2C_BUFFER_LENGTH];
uint16_t rxIndex; size_t rxIndex;
uint16_t rxLength; size_t rxLength;
uint16_t rxQueued; //@stickBreaker
uint8_t txBuffer[I2C_BUFFER_LENGTH]; uint8_t txBuffer[I2C_BUFFER_LENGTH];
uint16_t txIndex; size_t txLength;
uint16_t txLength;
uint16_t txAddress; uint16_t txAddress;
uint16_t txQueued; //@stickbreaker
uint8_t transmitting; uint32_t _timeOutMillis;
/* slave Mode, not yet Stickbreaker bool nonStop;
static user_onRequest uReq[2]; #if !CONFIG_DISABLE_HAL_LOCKS
static user_onReceive uRcv[2]; TaskHandle_t nonStopTask;
void onRequestService(void); SemaphoreHandle_t lock;
void onReceiveService(uint8_t*, int); #endif
*/
i2c_err_t last_error; // @stickBreaker from esp32-hal-i2c.h
uint16_t _timeOutMillis;
public: public:
TwoWire(uint8_t bus_num); TwoWire(uint8_t bus_num);
@ -74,20 +70,13 @@ public:
bool setPins(int sda, int scl); bool setPins(int sda, int scl);
bool begin(int sda=-1, int scl=-1, uint32_t frequency=0); // returns true, if successful init of i2c bus bool begin(int sda=-1, int scl=-1, uint32_t frequency=0); // returns true, if successful init of i2c bus
// calling will attemp to recover hung bus bool end();
void setClock(uint32_t frequency); // change bus clock without initing hardware
size_t getClock(); // current bus clock rate in hz
void setTimeOut(uint16_t timeOutMillis); // default timeout of i2c transactions is 50ms void setTimeOut(uint16_t timeOutMillis); // default timeout of i2c transactions is 50ms
uint16_t getTimeOut(); uint16_t getTimeOut();
uint8_t lastError(); bool setClock(uint32_t);
char * getErrorText(uint8_t err); uint32_t getClock();
//@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, uint32_t *readCount=NULL);
void beginTransmission(uint16_t address); void beginTransmission(uint16_t address);
void beginTransmission(uint8_t address); void beginTransmission(uint8_t address);
@ -134,22 +123,9 @@ public:
void onReceive( void (*)(int) ); void onReceive( void (*)(int) );
void onRequest( void (*)(void) ); void onRequest( void (*)(void) );
uint32_t setDebugFlags( uint32_t setBits, uint32_t resetBits);
bool busy();
}; };
extern TwoWire Wire; extern TwoWire Wire;
extern TwoWire Wire1; extern TwoWire Wire1;
/*
V1.1.0 08JAN2019 Support CPU Clock frequency changes
V1.0.2 30NOV2018 stop returning I2C_ERROR_CONTINUE on ReSTART operations, regain compatibility with Arduino libs
V1.0.1 02AUG2018 First Fix after release, Correct ReSTART handling, change Debug control, change begin()
to a function, this allow reporting if bus cannot be initialized, Wire.begin() can be used to recover
a hung bus busy condition.
V0.2.2 13APR2018 preserve custom SCL,SDA,Frequency when no parameters passed to begin()
V0.2.1 15MAR2018 Hardware reset, Glitch prevention, adding destructor for second i2c testing
*/
#endif #endif