pasko-zh / brzo_i2c Goto Github PK
View Code? Open in Web Editor NEWBrzo I2C is a fast I2C Implementation written in Assembly for the esp8266
License: GNU General Public License v3.0
Brzo I2C is a fast I2C Implementation written in Assembly for the esp8266
License: GNU General Public License v3.0
It would be great if this library would be supported as a drop in replacement for the Wire
library. There could be a thin wrapper around this lib to make integration into existing code simpler.
Hey,
ESP8266 Arduino Core project plans to use current version of GCC as default some time soon, but something had changed in compilation process since 4.8.2 and brzo can no longer build.
esp8266/Arduino#6294
https://github.com/earlephilhower/esp-quick-toolchain/releases
I commented on the GCC issue directly in hope it was something noticed during the migration of the Core sources, but it was suggested I should post here. Building using PIatformIO.
xtensa-lx106-elf-gcc -o ".pio/build/d1_mini/lib8be/Brzo I2C_ID335/brzo_i2c.c.o" -c -std=gnu99 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -fno-exceptions -Wall -DPLATFORMIO=40305 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -DARDUINO=10805 -DARDUINO_BOARD=\"PLATFORMIO_D1_MINI\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH "-I/home/max/.platformio/lib/Brzo I2C_ID335/src" -I.pio/build/d1_mini/core -I/home/max/.platformio/packages/framework-arduinoespressif8266@src-31d658a59f41540201fc3726a1394910/tools/sdk/include -I/home/max/.platformio/packages/framework-arduinoespressif8266@src-31d658a59f41540201fc3726a1394910/tools/sdk/libc/xtensa-lx106-elf/include -I/home/max/.platformio/packages/framework-arduinoespressif8266@src-31d658a59f41540201fc3726a1394910/cores/esp8266 -I/home/max/.platformio/packages/framework-arduinoespressif8266@src-31d658a59f41540201fc3726a1394910/tools/sdk/lwip2/include -I/home/max/.platformio/packages/framework-arduinoespressif8266@src-31d658a59f41540201fc3726a1394910/variants/d1_mini "/home/max/.platformio/lib/Brzo I2C_ID335/src/brzo_i2c.c"
/home/max/.platformio/lib/Brzo I2C_ID335/src/brzo_i2c.c: In function 'brzo_i2c_write':
/home/max/.platformio/lib/Brzo I2C_ID335/src/brzo_i2c.c:72:2: error: cannot find a register in class 'RL_REGS' while reloading 'asm'
72 | asm volatile (
| ^~~
/home/max/.platformio/lib/Brzo I2C_ID335/src/brzo_i2c.c:72:2: error: 'asm' operand has impossible constraints
*** [.pio/build/d1_mini/lib8be/Brzo I2C_ID335/brzo_i2c.c.o] Error 1
I also noticed #9 (comment), but I have no idea what exactly is missing from flags / defines. ICACHE_RAM_ATTR seems to be there, optimization flags do not change anything.
In brzo_i2c.c lines 706-710, while preparing for a STOP, SDA is brought LOW a few instructions before SCL is brought LOW (i.e., an unintended START condition). Two of my three I2C slaves didn't mind, but the third one (BPS120 pressure sensor) did. Took me 4 days to find the problem :)
One thing that makes library hard to use for EEPROM write operations.
Write operation to EEPROM is made by two steps: first, send address; second, send data to write. Address and Data must sent as single one array. The tricky part here is that if I will use two separate calls to brzo_i2c_write I will have additional "Setup write" before address and data - and this is error. Address and Data must be sent one after another, so imagine that I have next function prototype to write data to EEPROM:
bool at24c_write(uint16_t addr, uint8_t* data, uint8_t len)
I can't write body like that because in this case I will have extra "Setup write" between address and data transmission:
uint8_t data_addr[2];
data_addr[0] = (uint8_t)(((unsigned)addr) >> 8);
data_addr[1] = (uint8_t)(((unsigned)addr) & 0xff);
brzo_i2c_write(data_addr, 2, true);
brzo_i2c_write(data, len, false);
To overrun this I need to malloc temporary array with size of data + 2, copy first two bytes of address into it, then copy data and after that send it in one brzo_i2c_write call. I guess this looks like redundant memory allocation:
uint8 *d = os_malloc(len + 2);
d[0] = (uint8)(((unsigned)addr) >> 8);
d[1] = (uint8)(((unsigned)addr) & 0xff);
os_memcpy(d+2, data, len);
brzo_i2c_write(d, len+2, false);
os_free(d);
Here is some ideas:
brzo_i2c_write
function to control send or not to send device address.The work on adding a "clock_stretch" and making different devices work at different speeds on the bus gave me lots of ideas; but namely it grounded something I'd been thinking about for a while now; that it should be possible to encode all the configuration required for an I2C Request in a single numeric identifier that can be "reused" and "passed around".
An application would feed in the details into a function and get back a single 32-bit or 64-bit identfier that represented that "request" as a "request_id". Then one mechanism to trigger a request is just to ask for it by request_id. These ids (with certain "fill in the blank" bits masked out) could be sent over a network or other communication lines to have other devices execute them at your request and send back the results. [This is like what slave I2C devices in some chips do, or what an I2C aggregator/hub is doing.]
So I started wondering about using an I2C bus library that acted like a "virtual i2c hub".
I'd register requests, set up precanned fetches/puts, and then interact with it much the same I would an actual device. I'd call a couple "update" functions at the start of "loop()" and a "commitChanges()" at the end; and the library would get the state/values from the preconfigured devices, I'd manipulate/read them inside loop(), and the library would push back the changes.
We could call commitChanges(), or update(), as often as required.
For example:
"Device Id" + "Item Id" == 64-bit "Request Id"
void setup() {
I2CBus i2c_bus1(sda, scl);
uint64_t req_id = i2c_get_request_id(
device_address, bus_speed, clock_stretch
, register_address, data_length, is_read, start_repeat, ...
);
i2c_bus1.execRequest(req_id, &data[6]);
i2c_bus1.setSlaveRequest(slave_id, req_id, (uint8_t*)&the_device_struct); // Populate data into the_device_struct
}
void loop() {
i2c_bus1.updateSlaves();
if (the_device_struct.data_available) {
Serial.println("Got Value: " + String(the_device_struct.important_value));
}
the_device_struct.config_setting = get_new_setting();
i2c_bus1.commitChanges();
}
I am failing to get this library working with a BME280 Environment Sensor.
I moved from the Arduino Wire library to brzo_i2c since the AMS iAQ-Core air quality sensor did not work correctly with the builtin Wire lib.
I took the code from Adafruit and changed the parts handling I2C communication.
Original Code:
void Adafruit_BME280::write8(byte reg, byte value) {
if (_cs == -1) {
Wire.beginTransmission((uint8_t)_i2caddr);
Wire.write((uint8_t)reg);
Wire.write((uint8_t)value);
Wire.endTransmission();
} else {
if (_sck == -1)
SPI.beginTransaction(SPISettings(500000, MSBFIRST, SPI_MODE0));
digitalWrite(_cs, LOW);
spixfer(reg & ~0x80); // write, bit 7 low
spixfer(value);
digitalWrite(_cs, HIGH);
if (_sck == -1)
SPI.endTransaction(); // release the SPI bus
}
}
uint8_t Adafruit_BME280::read8(byte reg) {
uint8_t value;
if (_cs == -1) {
Wire.beginTransmission((uint8_t)_i2caddr);
Wire.write((uint8_t)reg);
Wire.endTransmission();
Wire.requestFrom((uint8_t)_i2caddr, (byte)1);
value = Wire.read();
} else {
if (_sck == -1)
SPI.beginTransaction(SPISettings(500000, MSBFIRST, SPI_MODE0));
digitalWrite(_cs, LOW);
spixfer(reg | 0x80); // read, bit 7 high
value = spixfer(0);
digitalWrite(_cs, HIGH);
if (_sck == -1)
SPI.endTransaction(); // release the SPI bus
}
return value;
}
uint16_t Adafruit_BME280::read16(byte reg)
{
uint16_t value;
if (_cs == -1) {
Wire.beginTransmission((uint8_t)_i2caddr);
Wire.write((uint8_t)reg);
Wire.endTransmission();
Wire.requestFrom((uint8_t)_i2caddr, (byte)2);
value = (Wire.read() << 8) | Wire.read();
} else {
if (_sck == -1)
SPI.beginTransaction(SPISettings(500000, MSBFIRST, SPI_MODE0));
digitalWrite(_cs, LOW);
spixfer(reg | 0x80); // read, bit 7 high
value = (spixfer(0) << 8) | spixfer(0);
digitalWrite(_cs, HIGH);
if (_sck == -1)
SPI.endTransaction(); // release the SPI bus
}
return value;
}
uint32_t Adafruit_BME280::read24(byte reg)
{
uint32_t value;
if (_cs == -1) {
Wire.beginTransmission((uint8_t)_i2caddr);
Wire.write((uint8_t)reg);
Wire.endTransmission();
Wire.requestFrom((uint8_t)_i2caddr, (byte)3);
value = Wire.read();
value <<= 8;
value |= Wire.read();
value <<= 8;
value |= Wire.read();
} else {
if (_sck == -1)
SPI.beginTransaction(SPISettings(500000, MSBFIRST, SPI_MODE0));
digitalWrite(_cs, LOW);
spixfer(reg | 0x80); // read, bit 7 high
value = spixfer(0);
value <<= 8;
value |= spixfer(0);
value <<= 8;
value |= spixfer(0);
digitalWrite(_cs, HIGH);
if (_sck == -1)
SPI.endTransaction(); // release the SPI bus
}
return value;
}
My adapted version: (except leaving out the SPI stuff and Wire.begin()
the rest of the code has not changed)
#define I2C_FREQ 100
brzo_i2c_setup(4,5,10000);
i2c_buffer = (uint8_t*)os_malloc(10);
//...
void BME280::write8(byte reg, byte value) {
i2c_buffer[0] = reg;
i2c_buffer[1] = value;
brzo_i2c_start_transaction((uint8_t)_i2caddr, I2C_FREQ);
brzo_i2c_write(i2c_buffer, 1, false);
brzo_i2c_write(&i2c_buffer[1], 1, false);
uint8_t error = brzo_i2c_end_transaction();
Serial.print("write8 W: ");Serial.print(reg, HEX);Serial.print(", R: ");Serial.print(value, HEX);
Serial.print(" (");Serial.print(value, BIN);Serial.print("), i2c: "); Serial.println(error);
}
uint8_t BME280::read8(byte reg) {
i2c_buffer[0] = reg;
brzo_i2c_start_transaction((uint8_t)_i2caddr, I2C_FREQ);
brzo_i2c_write(i2c_buffer, 1, false);
brzo_i2c_read(&i2c_buffer[1], 1, false);
uint8_t error = brzo_i2c_end_transaction();
Serial.print("read8 W: ");Serial.print(reg, HEX);Serial.print(", R: ");Serial.print(i2c_buffer[1], HEX);
Serial.print(" (");Serial.print(i2c_buffer[1], BIN);Serial.print("), i2c: "); Serial.println(error);
return i2c_buffer[1];
}
uint16_t BME280::read16(byte reg)
{
uint16_t value;
i2c_buffer[0] = reg;
brzo_i2c_start_transaction((uint8_t)_i2caddr, I2C_FREQ);
brzo_i2c_write(i2c_buffer, 1, false);
brzo_i2c_read(&i2c_buffer[1], 2, false);
uint8_t error = brzo_i2c_end_transaction();
value = i2c_buffer[1]<<8 | i2c_buffer[2];
Serial.print("read16 W: ");Serial.print(reg, HEX);Serial.print(", R: ");Serial.print(value, HEX);Serial.print(", i2c: "); Serial.println(error);
return value;
}
uint32_t BME280::read24(byte reg)
{
uint32_t value = 0;
i2c_buffer[0]=reg;
brzo_i2c_start_transaction((uint8_t)_i2caddr, I2C_FREQ);
brzo_i2c_write(i2c_buffer, 1, false);
brzo_i2c_read(&i2c_buffer[1], 3, false);
uint8_t error = brzo_i2c_end_transaction();
value = ((uint32_t)i2c_buffer[1])<<16 | ((uint16_t)i2c_buffer[2])<<8 | i2c_buffer[3];
Serial.print("read24 W: ");Serial.print(reg, HEX);Serial.print(", R: ");Serial.print(value, HEX);Serial.print(", i2c: "); Serial.println(error);
return value;
}
I get the following output when requesting the registers holding measured data:
write8 W: F4, R: 51 (1010001), i2c: 0 //(1) bme280.takeForcedMeasurement();
read8 W: F3, R: 0 (0), i2c: 0
read24 W: FA, R: 800000, i2c: 0 //bme280.readTemperature();
read24 W: FA, R: 800000, i2c: 0 //bme280.readPressure();
read24 W: F7, R: 800000, i2c: 0
read24 W: FA, R: 800000, i2c: 0 //bme280.readHumidity();
read16 W: FD, R: 8000, i2c: 0
Temp: nan *C Pressure: nan hPa Humidity: nan % // Calculated
For me it seems like reading works, but the BME280 does not measure and hence does not fill its registers with correct values. According to the datasheet 800000
is the default value. Writing (1) should force measurement and after this the registers should be filled with data. I guess there is something wrong in the write8(...)
method.
If I change back to wire (by copy-pasting the methods shown above), the BME280 works fine (so there is no hardware fault)...
Maybe an example in the Wiki comparing Arduinos Wire-lib and brzo_i2c would be helpful :)
Although Wiki warns that MEMW is required between GPIO register writes, it is occasionally missing. So, e.g., in brzo_i2c.c lines 332-333 the actual order of SDA LOW and SCL HIGH may depend on how the CPU manages the cache. Also in some cases MEMW is missing between a register write and a timing loop, e.g. line 309. If the CPU delays writing to memory, the SCL pulse would end up shorter by the amount of this delay, no?
I am failing to get your lib to work with the HTU21 Temperature/Humidity sensor. I am not quite shure if this is a problem with the lib or with my code. I always get a return code of 2 when finishing the transaction.
#include "brzo_i2c.h"
unsigned char buf[10];
void setup() {
Serial.begin(115200);
Serial.println("Beginning application");
brzo_i2c_setup(4,5,20000);
brzo_i2c_start_transaction(0x80, 100);
buf[0] = 0xE7;
brzo_i2c_write(&buf[0], 1, true);
brzo_i2c_read(&buf[1], 1, false);
unsigned char returnCode = brzo_i2c_end_transaction();
Serial.println(returnCode, HEX);
Serial.println(buf[1], HEX);
}
Hi, Grüezi
have you thought about a i2c slave code?
KR Mark
I got an issue that make I2C bus unusable after receiving code 8 from brzo_i2c_end_transaction. The SCL/SDA line remains low and makes not possible to use bus until reinit.
How to reproduce:
brzo_i2c_setup(3000)
Hello,
I need to read and write to AT24C32 i2c eeprom. To ensure data was written by chip I need to use "acknowledge polling".
From datasheet:
ACKNOWLEDGE POLLING: Once the internally-timed write cycle has started and the
EEPROM inputs are disabled, acknowledge polling can be initiated. This involves sending
a start condition followed by the device address word. The read/write bit is
representative of the operation desired. Only if the internal write cycle has completed
will the EEPROM respond with a zero, allowing the read or write sequence to continue.
How can I do ACK polling with you library?
Hello,
I have a question. ESP8266 is using 3.3v on the GPIO pins, right?
I am assuming most I2C devices are 5v.
Are you assuming a logic level converter is in use or is there no issue between the 3.3 and 5v.
OR am I just wrong and all I2C is 3.3v?
Unfortunately, I forgot the second question. grrr
Hi
At first, thank very much for your job!
I'd propose little improvement for your library - it is add possibility to instantiate multiple instances of I2C bus object. It allows use more then one I2C bus with different IO lines simultaneously.
My proposition is to use pointer to structure wich descripts I2C bus instance. This structure (casted to void pointer for hide internal bus data from end user) used as handle for manipulate single instanse of I2C bus. Each instance of I2C bus should be binded to different couple GPIO lines.
Also I used semaphore as latch to lock I2C bus to avoid interleaving requests within one transaction to different devices.
Unfortunately I can't offer clean diff of changes with your code because I made a lot changes when adapted your code to use with esp8266_rtos_sdk and formated code style.
You could see changed library at my test project:
https://github.com/sacculus/aircontrol.v2.x.x/include/brzo_i2c.h
https://github.com/sacculus/aircontrol.v2.x.x/user/brzo_i2c.c
Hello,
Today I get compile error. Can you please confirm if this lib works ok for Heltec Wifi Kit 32.
Meanwhile, I will test sample code.
Thank you!
..........................
WARNING: library Brzo_I2C claims to run on (esp8266) architecture(s) and may be incompatible with your current board which runs on (esp32) architecture(s).
..........................
libraries\Brzo_I2C\brzo_i2c.c.o:(.iram1.literal+0x2c): undefined reference to `system_get_cpu_freq'
libraries\Brzo_I2C\brzo_i2c.c.o:(.literal.brzo_i2c_setup+0x14): undefined reference to `system_get_cpu_freq'
libraries\Brzo_I2C\brzo_i2c.c.o: In function `brzo_i2c_start_transaction':
C:\Users\IlikeB00bs\Documents\Arduino\libraries\Brzo_I2C/brzo_i2c.c:989: undefined reference to `system_get_cpu_freq'
libraries\Brzo_I2C\brzo_i2c.c.o: In function `brzo_i2c_setup':
C:\Users\IlikeB00bs\Documents\Arduino\libraries\Brzo_I2C/brzo_i2c.c:1044: undefined reference to `system_get_cpu_freq'
collect2.exe: error: ld returned 1 exit status
exit status 1
Error compiling for board Heltec_WIFI_Kit_32.
In #13 it came up about keeping the library clean by avoiding a whole host of wrapper functions that more complex libraries provide.
While I agree with keeping it clean and minimal; I disagree those extra commands clutter a usable and useful I2C library. Useful work with I2C devices requires flipping bits without altering the rest of the register, changing endianness on Word read/writes, and most of those other provided functions.
In fact, when you look at the implementation of those extra commands, they don't actually do any bus work. They keep using the same couple of read/write functions to do the actual Wire work. So you could take any of those libraries, replace a few functions with the brzo functions and get the full functionality of the library without adding any functions to brzo at all.
And that's what I recommend as a target for brzo. Being a useful bit banging on the bus part for Wire and libraries like Wire. The Wire library (and others) already support using different underlying bit banging methods, so making brzo compatible with them in that sense (to be used as the bit banging method underlying the library) is where I think brzo (and other platform/MCU specific libraries) belong.
So how does this relate to optionally providing register addresses separate from the data?
(And functions like writeByte that don't take a buffer address but instead just take a direct value..)
Unlike bit flipping and byte ordering, sending the register address is something that happens on the wire and is required in many, if not most, command circumstances.
The way most code deals with the register and device addresses keeps those items in separate variables, or even in #define statements which makes is extremely cumbersome to always put at address 0 of the data buffer.
Having #define statements with values from the product sheets, or hand crafted for the various modes of the device can save a lot of code space and pointer dereference requirements by hardcoding those immediate values straight into the function calls.
The compiler optimizer might even find ways to rearrange that code to make it even more efficient. When everything is always wrapped behind a pointer dereference, there is fewer opportunities for those optimizations. The Tensilica assembly can even use smaller commands in some cases when it knows it's dealing with immediate values.
And those arguments are secondary to the primary argument which is how programmers actually work with I2C devices. They have data buffers where the data to send to the device and read from the device get stored, then they have #define and const uint8_t and other mechanisms to store the device and register addresses that those data buffers belong too.
So it's just plain inconvenient to rearrange my data buffers to force a #define value into data_buffer[0] when I could have more easily placed it as its own value in the function call. To the brzo function it just means sending a byte from a separate location before sending the data at the pointer location. It's exactly the same idea as the device Id. It's not the "data" it's about the "where/what" to send/read the data to/from.
I've already done the work for this with the write function. I'll post my changes in the next comment and you can tell me if you think this is keeping with the spirit of a clean/lean i2c bit banging machine. :)
Thanks for a very nice library!
I've noticed that it's sometimes necessary to use speeds < 100KHz to talk to a device, which the library currently doesn't support.
As an example, at 100KHz, it's almost impossible (very error-prone) to talk to an ATMega running at 8MHz or even an ATTiny, such as the reasonably popular BlinkM LEDs.
I can work around this by setting iteration_scl_halfcycle
accordingly to achieve speeds like 50KHz or even slower, but the library currently doesn't support this out of the box.
Doing a pull request for this would be trivial, but I'm unsure about how to approach it without breaking the current API, e.g. right now, any speeds <100KHz would get treated as 100KHz. Maybe adding a new function like start_transaction_slow
is the safest way without breaking backwards compatibility, which just supports speeds (roughly) between 10-100KHz?
brzo_i2c_write/read(uint8_t *data, uint8_t no_of_bytes, boolean repeated_start)
expects a pointer to a buffer *data
. It then always sends/reads the no_of_bytes
starting from the beginning of the buffer, i.e. data[0]
... data[no_of_bytes - 1]
.
Instead, brzo_i2c_write/read
should allow that any pointer to an elemen (of array of byte) is passed to it and that this then taken as the base address from which no_of_bytes
is sent/read. E.g., brzo_i2c_write(&buffer[4], 5, true)
would send 5 bytes starting from buffer[4]
I've been playing with making these "Request Ids" to represent precanned I2C Item fetches.
Two of the items in the description list for buses and devices are the busSpeed and the clockStretch.
Currently a 16-bit and 32-bit value. Together they require 48-bits; busSpeed is represented in kHz, (1KHz to 65.5Mhz) and the other is clock stretch represented in nanoseconds (0 nanoseconds to ~4.3 seconds). I wanted to make them smaller (and increase their value range) if I could.
So I came up with a different way to encode the same ideas. Instead of representing frequency as "the number of pulses in a second" I imagined describing frequency as "the time between pulse changes" (a much smaller number).
To make a long story short, I ended up hacking together the concept of a 24-bit "TimeWindow" structure using a badly hacked version of floating point numbers to "play with it".
A "Time Window" is a "pulse start" and a "stretch time".
As an example, 400kHz is a pulse time of 2.5 ms, e.g. 0.250 x 10^-5 seconds {-, 5, 250}
0.001 x 10^-9 seconds == 1 THz {-, 9, 1}
0.600 x 10^2 seconds == 0.0167 Hz (a clock pulse of ~1 minute) {+, 2, 600}
And that's how this structure encodes a pulse_frequency, a 10-bit "significant digits", a 4-bit exponent, and a 1-bit exponent sign. "Significant digits" are the three digits after the decimal point.
The value range then is 0.001 to 1.024; and the exponent is between -15 and +15; using 1 second as a base.
For the clock stretch, I split the byte 50/50; 4-bits for "Significance" and 4-bits for "Exponent". This gives a significance range of 0.1 to 1.6 * 10 +/- 15. Using 0.1 microseconds as a base.
Combined the two values take up 24-bits.
Here's the structure in code:
/*
* This time_window32_t represents two 16-bit floats;
* one for "time between pulses" (1 == 1 second); the other for "stretch before timeout" (1 == 0.1 microseconds)
* together they make a repeating clock frequency and a "clock stretch" to deal with device inconsistencies
*/
union time_window32_t {
uint32_t bytes;
struct {
uint16_t pulse_exp_sign:1; // 0 or 1
uint16_t pulse_exp:4; // 0 through 15
uint16_t pulse_sig:10; // 0 through 1023 (exp0 == 1s) (0.001x10^-15) (0.001 femtosecond) to [1.023 x 10^15] (32 million years)
uint16_t stretch_exp_sign:1; // 0 or 1
uint8_t stretch_exp:4; // 0 through 15
uint8_t stretch_sig:4; // 1 through 16 (exp0 == 0.1us (or 100ns); range is [1 x 10^-23] (time for light to travel 1 femtometer) to [1.6 x 10^8 (~5 years))
uint8_t reserved; // reserved; the request length ultimately ends up in this spot in a txn_request_id
} settings;
};
This method is an extremely inefficient (aka really horrendous) way to represent floating point numbers. The main point right now was introducing the concept and gauging feedback. I like that both these values are encoded into a single 24-bit value.
I could probably learn from the techniques that a "half-precision" (16-bit) IEEE floating point value uses.
Another change would be "clock stretch" being a divisor of "pulse frequency"; so rather than having a prefixed value like it does now, it's always some fraction of a "pulse time"... This should use the available values better to get more reasonable granularity. For instance, 6 hours + 1 femtosecond (i.e. exactly 6 hours) seems like a very unlikely time window to me. There's likely a better way to use the clock stretch bits to either make pulse frequency better or express more reasonable values within a pulse range.
I still expect users to express "frequency" in terms of "kHz" and "clock_stretch" in terms of nanoseconds. I just see the library transforming these two separate values into a single 24-bit "time window" identifier.
I have another use case for this "time window" to provide data synchronization frequencies. For instance, "these values must be refreshed from the device at least every 5 minutes +/- 15 seconds" or "these values must be synchronized every 500 nanoseconds +/- 10 nanoseconds". Once set, the library ensures that those frequencies for data synchronization are maintained.
Currently, brzo i2c uses a uint16_t
datatype for holding the number of iterations iteration_scl_clock_stretch
for the timeout of SCL clock stretching. The maximum timeout is therefore around 5 msec (milli seconds).
There are i2c slaves, like for instance the HTU21, which stretch the clock up to 50 msec (milli seconds) or even more!
Thus, iteration_scl_clock_stretch
should be of datatype uint32_t
instead.
Hi. With wire library the sensor is found.
I haven't find a way to contact the author of this program, so I fill a request:
I am using an ESP8266 (but it would be an Arduino I'd think it would be the same) to read temperature from BME280 (I2C Master), but I'd like to add I2C Slave capabilities on the same ESP to communicate with another device (Slave is a strong constraint). I read the source code of this library, and I don't see any reason why it could be feasible:
You'll probably ask yourself why my ESP & BME280 aren't slave with different addresses ?
Well there are several reasons that makes me want continue on this solution:
Why don't I use Wifi ? Because it does, but my wife doesn't want Wifi for children 😀
I would highly appreciate any suggestion, or if you think it is a valid use case like me, and think it is feasible quite easily, I can provide a PR (if you are interested of course).
Thank you in advance,
Cheers.
A huge thank you for this library. It's easily the best I2C driver one can have on ESP8266, in terms of speed and usability.
However, I recently had some problems using this library, sampling an IMU via I2C at 1 kHz and doing continuous WebSocket communication and PWM realized by the HW timer simultaneously. When the interrupts were disabled during I2C transactions, there were frequent stalls / resets, probably caused by some clashes due to missed interrupts. I then commented out the lines where the interrupts were disabled, and stalls became less frequent. (There are still some, but they aren't probably related to brzo_i2c
.)
Therefore it would be great to have a configuration option in brzo_i2c.h
where the user could opt out of disabling the interrupts. This should be a simple addition, and the default behaviour should be left unchanged.
I would be able to supply the patch as a pull request if desired.
Hi,
I'm trying to re-implement TI ADS1015 ADC interface using brzo_i2c library, since Adafruit's implementation using Wire.h is not fast enough to read data at the rate ADS1015 cat produce.
On the same hardware (esp-12F, 4.7k pull-up resistors, ADS1015 test board),
the following code in Wire.h works:
#include <Arduino.h>
#include <Wire.h>
#define SCL_PIN 5
#define SDA_PIN 4
#define ADS1X15_ADDRESS (0x48)
#define I2C_SPEED 100
void setup() {
Serial.begin(115200);
Wire.begin(SDA_PIN,SCL_PIN);
Wire.setClock(I2C_SPEED * 1000L);
}
void loop() {
uint16_t result = 0;
uint16_t cfg_value = 50627;
uint8_t b0, b1;
// Configure to start ADC
Wire.beginTransmission(ADS1X15_ADDRESS);
Wire.write(1); // Config register
Wire.write((uint8_t)(cfg_value >> 8)); // 2 bytes of config
Wire.write((uint8_t)(cfg_value & 0xFF));
Wire.endTransmission();
delay(2); // Some sleep for ADC conversion to be done
// Read data
Wire.beginTransmission(ADS1X15_ADDRESS);
Wire.write(0); // Data register
Wire.endTransmission();
// now, actually read
Wire.requestFrom(ADS1X15_ADDRESS,2);
b0 = Wire.read();
b1 = Wire.read();
result = ((b0 << 8) | b1);
Serial.printf("Received: %u\n",result);
// Sleep for a while
delay(1000);
}
And the following code in brzo_i2c does not:
#include <Arduino.h>
#include <brzo_i2c.h>
#define SCL_PIN 5
#define SDA_PIN 4
#define ADS1X15_ADDRESS (0x48)
#define I2C_SPEED 100
void setup() {
Serial.begin(115200);
brzo_i2c_setup(SDA_PIN, SCL_PIN,20000);
}
void loop() {
uint16_t cfg_value = 50627;
uint8_t rc;
uint8_t buffer[2];
// Configure to start ADC
brzo_i2c_start_transaction(ADS1X15_ADDRESS, I2C_SPEED);
buffer[0] = 1;
brzo_i2c_write(buffer,1, true);
buffer[0] = (uint8_t)(cfg_value >> 8);
buffer[1] = (uint8_t)(cfg_value & 0xFF);
brzo_i2c_write(buffer,2,false);
rc = brzo_i2c_end_transaction();
if (rc > 0) {
Serial.printf("Write failed with rc=%u\n",rc);
}
delay(2); // Some sleep for ADC conversion to be done
// Read data
brzo_i2c_start_transaction(ADS1X15_ADDRESS, I2C_SPEED);
buffer[0] = 0;
brzo_i2c_write(buffer,1, true);
brzo_i2c_read(buffer,2,false);
rc=brzo_i2c_end_transaction();
if (rc > 0) {
Serial.printf("Read failed with rc=%u\n",rc);
}
result=((buffer[0]<<8)|buffer[1]);
Serial.printf("Received: %u\n",result);
delay(1000);
}
Wire-based code correctly returns different ADC values, depending on what the signal is.
brzo-i2c based code executes correctly (rc is always 0), but data coming from the device is always the same.
Hardware is OK, clock frequency of 100kHz is low enough (Wire-based example runs good at up to 600kHz).
Changing repeated_start from true to false has no effect.
Is there anything else that can be done to make this setup work?
Thanks.
I am implementing i2c communication with MAX11615 and i am not getting desired result. Below is a snapshot to give more clarity on the slave. Datasheet
My code looks like
uint8_t SDA_PIN = 4;
uint8_t SCL_PIN = 5;
uint8_t MAX11615_ADR = 0x33;
uint8_t buffer[8];
uint8_t error = 0;
float pH = 0.0;
float EC = 0.0;
uint8_t ICACHE_RAM_ATTR get_pH_EC(float *p, float *e) {
uint32_t a_code = 0;
uint32_t b_code = 0;
uint32_t c_code = 0;
uint32_t d_code = 0;
uint8_t bcode = 0;
brzo_i2c_start_transaction(MAX11615_ADR, 400);
buffer[0] = 0xD2;
buffer[1] = 0x09;
brzo_i2c_write(buffer, 2, false); //writes 2 bytes to slave address, repeated start is false
brzo_i2c_read(buffer, 8, false); // read 8 bytes to slave address, repeated start is false
bcode = brzo_i2c_end_transaction();
delay(50);
a_code = ((buffer[0] << 4) | buffer[1]);
b_code = ((buffer[2] << 4) | buffer[3]);
c_code = ((buffer[4] << 4) | buffer[5]);
d_code = ((buffer[6] << 4) | buffer[7]);
*p = a_code;
*e = b_code;
Serial.print(" | a = "); Serial.print(a_code);
Serial.print(" | b = "); Serial.print(b_code);
Serial.print(" | c = "); Serial.print(c_code);
Serial.print(" | d = "); Serial.print(d_code);
return bcode;
}
void setup() {
delay(1000);
Serial.begin(115200);
brzo_i2c_setup(SDA_PIN, SCL_PIN, 2000);
}
void loop() {
Serial.println();
Serial.println("Waiting 5 seconds...");
delay(5000);
error = get_pH_EC(&pH, &EC);
if (error == 0) {
//Print ph and ec
}
else {
Serial.print("Brzo error : ");
Serial.println(error);
}
}
What is funny is the values are same for a_code, b_code, c_code, d_code. The setup and configuration byte is proper and checked with manufacturer. Is it possible that the write is not happening correctly and hence giving value from only one channel ?
I am not very versed with writing assembly so not sure how to debug this.
First: Thanks a lot for your great work! :)
Currently I'm trying to generate some sound with the ESP8266 and a MCP4725 (DAC with a I2C inteface).
Since I would like to write a lot of data in one rush, i discovered that you made the following variable definitions in the write (and read) function:
void ICACHE_RAM_ATTR brzo_i2c_write(uint8_t _data, _uint8_t no_of_bytes*, boolean repeated_start)
My Question:
Is there a good reason you limited the number of bytes (no_of_bytes) to 256?
Could you increase that to at least an uint32_t?
I tested that localy with some random values and it seems to work fine so far. But you may be a better judge.
Keep your good work up :)
It seems (correct me if I'm wrong) that BRZO uses the following clock stretch logic:
The consequence is that if the slave stretches clock by an amount less than T, BRZO will not notice, will bring SCL LOW, and the SCL pulse could end up being very short. But I'm puzzled because BRZO seems to be deliberately coded in this way. Why? Am I missing something?
/arduino/libraries/Brzo_I2C/brzo_i2c.c: In function 'brzo_i2c_write':
/arduino/libraries/Brzo_I2C/brzo_i2c.c:72:2: error: cannot find a register in class 'RL_REGS' while reloading 'asm'
72 | asm volatile (
| ^~~
/arduino/libraries/Brzo_I2C/brzo_i2c.c:72:2: error: 'asm' operand has impossible constraints
exit status 1
Fehler beim Kompilieren für das Board Generic ESP8266 Module.
Can this be easy fixed? Thanks.
I'm using latest brzo version...
The README says "If you are developing with PlatformIO then simply use the library manager to install brzo_i2c". That brzo_i2c link takes you to a generic page that is not helpful. The actual name of the package, as would be used in a "lib_deps" field in platformio.ini, is "pasko-zh/Brzo I2C". So I think it would be clearer to say:
pasko-zh/Brzo I2C
to lib_deps in platformio.ini你好,很抱歉打扰你,希望你在百忙之中帮我分析下困扰我已久的问题:
在使用您的库之后,存储和读取操作的返回结果都是0,读取是有结果的,但是存储好像并没有执行,只是偶尔会执行一次。我是用esp8266和24c16通讯
Hello, I'm sorry to disturb you. I hope you can help me analyze the problems that have been bothering me for a long time in your busy schedule:
After using your library, the return results of the storage and reading operations are both 0, and the reading has results, but the storage does not seem to execute, only occasionally. I use esp8266 to communicate with 24C16
I'm using brzo_i2c in my hobby project.
I have ESP8266 as master and 3 slaves on i2c line (2 displays and 1 sensor).
Esp8266 communicates with all slaves every 500ms. All works fine when all 3 salves are connected.
But, if I disconnect 1 of the slaves, all 3 stop working.
So, in this case communication with absent slave fails, i.e. it just does not respond to its address, but all other slaves which are connected stop responding as well.
Any idea why could this happen?
I'm planning to have several instances of my device in different configurations, for example with one display only, or without sensor, so, I would like to have same software flashed to ESP8266 and detect if i2c slave device is present or not in runtime.
Hello pasko,
i have found your nice i²c library and wanted to try it out for my
(INA219). I tried to modify a simple library and came up with my forked version.
In short: It does not work, but i think i am not completely wrong. Could you have a look at it and give me a hint, how to make this work (at all).
There are basically only to functions that i have changed, and i think the problem is this one:
int16_t INA219::readRegister16(uint8_t reg)
{
int16_t value;
Wire.beginTransmission(inaAddress);
#if ARDUINO >= 100
Wire.write(reg);
#else
Wire.send(reg);
#endif
Wire.endTransmission();
delay(1);
Wire.beginTransmission(inaAddress);
Wire.requestFrom(inaAddress, 2);
while(!Wire.available()) {};
#if ARDUINO >= 100
uint8_t vha = Wire.read();
uint8_t vla = Wire.read();
#else
uint8_t vha = Wire.receive();
uint8_t vla = Wire.receive();
#endif;
Wire.endTransmission();
value = vha << 8 | vla;
return value;
}
changed to:
int16_t INA219_brzo::readRegister16(uint8_t reg)
{
int16_t value;
brzo_i2c_start_transaction(ADDR, SCL_frequency_KHz);
brzo_i2c_write(®, 1, true); // 1 byte, repeat
brzo_i2c_end_transaction();
brzo_i2c_start_transaction(ADDR, SCL_frequency_KHz);
brzo_i2c_read(&buffer[0], 2, false);
// uint8_t vha = brzo_i2c_end_transaction();
// uint8_t vla = brzo_i2c_end_transaction();
uint8_t vha = buffer[0];
uint8_t vla = buffer[1];
brzo_i2c_end_transaction();
value = vha << 8 | vla;
return value;
}
kind regards
Jürgen
P.S.: I really appreciate your work and your incredibly intimedating ASM code!
Disclaimer: I have hardly any experience with C coding and iam very new to all this MCU stuff.
I'm not familar with assembler, please check either those variables are used or can be safely removed:
'a_bit_index' is used uninitialized in this function [-Wuninitialized] brzo_i2c.c line 768
'a_bit_index' may be used uninitialized in this function [-Wmaybe-uninitialized] brzo_i2c.c line 73
'a_bit_index' may be used uninitialized in this function [-Wmaybe-uninitialized] brzo_i2c.c line 389
'a_in_value' is used uninitialized in this function [-Wuninitialized] brzo_i2c.c line 768
'a_in_value' may be used uninitialized in this function [-Wmaybe-uninitialized] brzo_i2c.c line 73
'a_in_value' may be used uninitialized in this function [-Wmaybe-uninitialized] brzo_i2c.c line 389
'a_set' is used uninitialized in this function [-Wuninitialized] brzo_i2c.c line 768
'a_set' is used uninitialized in this function [-Wuninitialized] brzo_i2c.c line 1082
'a_set' may be used uninitialized in this function [-Wmaybe-uninitialized] brzo_i2c.c line 73
'a_set' may be used uninitialized in this function [-Wmaybe-uninitialized] brzo_i2c.c line 389
'a_temp1' is used uninitialized in this function [-Wuninitialized] brzo_i2c.c line 768
'a_temp1' is used uninitialized in this function [-Wuninitialized] brzo_i2c.c line 1082
'a_temp1' may be used uninitialized in this function [-Wmaybe-uninitialized] brzo_i2c.c line 73
'a_temp1' may be used uninitialized in this function [-Wmaybe-uninitialized] brzo_i2c.c line 389
Hi - nice library! Making use of it in my non-RTOS esp SDK application the linker is not able to find the round() function in any of the system libraries I'm already including. Where are you expecting to pick this up from? I'm already having problems with available RAM so I'm keen not to include any extra library functions unnecessarily!
When using gpio_init(); the library cannot be used anymore.
I will receive upon send error code = 1.
Hey, thanks for your great work!
I've installed the library, but can't get the cock stretching feature to work.
Is it really supported?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.