-
Notifications
You must be signed in to change notification settings - Fork 571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
I2C APIs should take address on Read/Write #214
Comments
Came across this Python library where they use the address in all methods. #!/usr/bin/python
import smbus
bus = smbus.SMBus(1) # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
DEVICE_ADDRESS = 0x15 #7 bit address (will be left shifted to add the read write bit)
DEVICE_REG_MODE1 = 0x00
DEVICE_REG_LEDOUT0 = 0x1d
#Write a single register
bus.write_byte_data(DEVICE_ADDRESS, DEVICE_REG_MODE1, 0x80)
#Write an array of registers
ledout_values = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
bus.write_i2c_block_data(DEVICE_ADDRESS, DEVICE_REG_LEDOUT0, ledout_values) This is a little redundant, but approach would fix this thread's issue. It also allows you to only have to create one object to communicate with multiple devices on the bus. Of course, this is a big difference than our current implementation (since it's 1-to-1) so it seems like there would be an alternative addition to not break things. |
@shaggygi I think for now my recommendation would be to expose the address field in the I2C through property and for now set it before the write. As we add more devices let's comment on this issue every time we have to do it (this should be cumbersome enough that someone will find this issue or create a similar one). Depending on how many times we have to do it we will or not add this API, sounds fair? As for the auto-selecting I2C device, please create seperate issue since it will get lost quickly |
I'm not sure, but I think this could cause issues with the Windows I2cDevice. See code link below where it is initialized using the DeviceAddress. I guess it could execute the code again to verify and store _winI2cDevice again when the DeviceAddress is changed. iot/src/System.Device.Gpio/System/Device/I2c/Drivers/Windows10I2cDevice.Windows.cs Line 22 in 91c0471
This is not an issue today (that I'm aware of), more of a comment if there was thinking to code for I2cController similar to the GpioController is today. I'll keep watch and possible add issue if it comes up. Thx |
To make sure I'm clear- you're suggesting this? namespace System.Device.I2c
{
public abstract class I2cDevice : IDisposable
{
// Existing API ...
// New API
public abstract byte ReadByte(int deviceAddress);
public abstract void Read(int deviceAddress, Span<byte> buffer);
public abstract void WriteByte(int deviceAddress, byte data);
public abstract void Write(int deviceAddress, Span<byte> data);
}
} My initial reaction is that it would be a bad idea as it isn't broadly applicable and, as you point out, likely to confuse people. If I were presented with this problem I'd start exploring from this idea: namespace System.Device
{
// Abstracts treating the device like a storage object- with a goal of easily being able to
// create helpful things like System.IO.Stream wrappers, etc. Device binding would implement,
// handling address switching to hit different banks, etc.
public interface IStorageDevice
{
// Total capacity in bytes
long Capacity { get; }
// "position" would be the byte position from the start of the device's address space (i.e. Stream.Position)
byte ReadByte(int position);
byte Read(Span<byte> buffer, int position);
// etc.
}
}
namespace Iot.Device.AT24Cxx
{
public abstract class AT24Cxx : IStorageDevice
{
public ReadOnlySpan<IStorageDevice> Banks { get; }
// One possible way to provide direct bank access
private struct BankWrapper : IStorageDevice
{
private int _myAddress;
private AT24Cxx _device;
}
}
} Take all of this with a grain of "I just started looking at this". :) |
@JeremyKuhne this would also align with IGpioController since it also has addresses and Read/Write 😄 We could unify a lot of things this way |
No, I wouldn't like this approach since we already have to use address when creating settings and device. It was mainly for ideas and show it is similar to other GPIO libraries. |
I only did a brief look at https://github.com/dotnet/iot/blob/master/src/System.Device.Gpio/System/Device/I2c/Drivers/UnixI2cDevice.Linux.cs. There is value in having
|
IMHO, I still think it should be
I believe this would work for the Unix flavor today (with some of the lower-level code changed). It wouldn't throw an error until the Read/Write occurred as those checks are not used until the Initialize() method is called. I'm not quite sure about the Windows flavor as some of the checks are configured in the constructor. Sorry to sound so passionate about this topic 😄 I'm honestly open to what makes sense. Just want this to be a good story in the end. Now I understand how you Microsofties feel trying to get something somewhat difficult right. Keep the dialog going 👍 |
@joperezr this one might need a milestone assignment as decision could change the API surface. Thoughts? |
After having worked with a few I2C devices over the weekend I thing tying to specific address is better and doing I2CBus as @JeremyKuhne has suggested for that specific use makes sense. I had worked with 1 device which used 2 different addresses so far and in this particular case it was used to communicate with 2 different sensors on the same device so I also treat this device as 2 - for one or two devices which behave differently I think it makes sense to create I2cBus - I2cDevice in that case would implement on top of the bus and they could easily co-exist. IMO next steps:
|
This comment somewhat clicked just now. As mentioned before, I don't particular care for the name I2cDevice or I2cBus and for passing to a binding that is basically the "device". However, this might work for I2cController (and is where we're headed with other pin types - GpioController, AdcController, PwmController, etc.). Therefore, we could pass in the I2cController to the binding as such: var i2cController = new I2cController(); // More details below.
using(var myBinding = new MyBinding(i2cController))
{
// Do stuff.
} Like int deviceAddress = 0x20;
i2cController.Write(deviceAddress, data); This wouldn't be redundant in most cases as this would be hidden behind the surface APIs where the binding would determine which address to call... usually a specific one, but 2 in your case and 8 for the AT24Cxx. I still like the name "Driver" (instead of UnixI2cDevice, etc.) as that is consistent with other pin Driver types, but don't see us changing at this point. But to point out, you could pass it to the controller like the other drivers. new GpioController(PinNumberingScheme.Logical, gpioDriver);
// similar where this has the I2cConnectionSettings which as the busId and deviceAddress.
new I2cController(i2cDriver); Using Meaning, the following would add a new driver/device under the controller, but with a different address. int deviceAddress1 = 0x20;
int deviceAddress2 = deviceAddress1 + 1;
var i2cController = new I2cController(busId, deviceAddress1);
i2cController.Add(deviceAddress2);
// Now write to each device.
i2cController.Write(deviceAddress1, data);
i2cController.Write(deviceAddress2, data); Thoughts? |
@shaggygi I feel like 1 and multiple are different level of abstractions, so my thinking was more like (I think this is what @JeremyKuhne had in mind): int deviceAddress1 = 0x20;
int deviceAddress2 = deviceAddress1 + 1;
var i2cBus = new I2cBus(busId);
i2cBus.Write(deviceAddress1, data);
i2cBus.Write(deviceAddress2, data);
var i2c = new I2cDevice(busId, deviceAddress1);
i2c.Write(data); note even variable naming suggest it should be bus and device - Controller doesn't mean anything in context of software - almost every class controls something |
Your I2cBus example is a little clearer/cleaner than the I2cController example. As for the At24Cxx binding and the page addressing issue, we could have the following behind the scenes: i2cBus.Write(pageAddress1, data);
i2cBus.Write(pageAddress2, moreData); There still needs to be a way we get the implementation to use, though. Example: // would pick platform default.
var i2cBus = new I2cBus(busId);
// or use specific.
var i2cBus = new I2cBus(new UnixI2cDevice(settings));
// or with a GPIO big-banged flavor.
// In this case, we don't need a busId, but pin numbers for SCL/SDA.
var i2cBus = new I2cBus(new GpioI2cDevice(17, 24));
In this case, we shouldn't have any Controller (Gpio, Adc, Pwm) classes then. Naming is hard 🍺. I2cController would be able to work with whatever implements I2cDriver/I2cDevice including software or GPIO bit-banged. Understanding it's my stubbornness, I just can't get passed comparing it to other pin types. gpioController.Write(3, PinValue.Low);
// similar to...
i2cController.Write(3, data); In the end, I suppose either with work. Just trying to make consistent. using(var myBinding = new MyBinding(i2cBus))
// or
using(var myBinding = new MyBinding(i2cController)) Thanks for trying to think this through with me @krwq 😄 |
Personally, I think that having that wrapper (I2cBus) object on top of the different I2c Devices won't provide you much. I could see a binding that has more than 1 i2c address to simply just have two i2cdevice objects in their implementation. So for example, if you are writing a binding for the SenseHat which has 1 I2c address for humidity and 1 other I2c address for the LED matrix, I simply see that SenseHat binding would have two I2cDevice objects, one for the matrix and one for the humidity sensor. @shaggygi as a side note, just as an FYI I will be taking some time off for my parental leave starting on friday, so I might be slower on responding to issues and PRs. @krwq is the man while I'm gone, so feel free to tag him in PRs and discussions while I'm out. |
@joperezr congrats! |
and congrats @krwq as well 👍 |
While trying to migrate PowerShell/IoT to use System.Devices.Gpio library instead of the current Unosquare.Raspberry.IO, I've stumbled across the following: the Unosquare API allows you to specify which register do you want to read/write from/to (this). Regardless of the device, I would like our module to keep exposing the possibility to specify the register. |
Hey @DanielSSilva, you are correct in the fact that I2c devices have registers which are the ones you usually have to read/write to. The thing is that in the I2c protocol, there is no defined way of how to read or write to a register, all you can do is to write something into the i2c bus, or read something in the i2c bus. Most of all I2c devices out there, use this way for reading and writing registers: void ReadRegister(byte someRegister)
{
i2cDevice.Write(someRegister);
return i2cDevice.Read();
}
void WriteRegister(byte someRegister, byte value)
{
i2cDevice.Write(someRegister);
i2cDevice.Write(value);
} So basically when they want to Read a register, they write the register to the bus, and then perform a read which will cause the device itself to return the value of the register that was written first. And when they want to write a register, they just write two different bytes, one with the register address and one with the value to write. All that said, this is the most common convention most devices follow in order to write or read from a register, but there are a few devices that don't follow this convention, and have it's own private convention in order to read or write a register, for example, there are some that require that whenever you are reading a register, you first pass in the register address but with the most significant bit turned on. Given it is very common for most I2c devices to follow this convention, some libraries (like Unosquare.Raspberry.IO) provide a convenience method that is just doing the protocol for you, but we initially didn't add it as it may be misleading for some devices that read/write registers in a different way. That said, it is likely that we will eventually add these convenience methods as well given that most i2c devices follow it anyway. |
Thank you very much @joperezr ! It now makes much more sense. I will try to implement it and come back if I have any questions 😄 |
@DanielSSilva : Maybe have a look at https://github.com/pgrawehr/raspberryio/tree/NewLowLevelImplementation/src/Unosquare.RaspberryIO.LowLevel. I have made a start writing a wrapper for Unosquare to this library. There you should see how to map the different I2C functions to each other. |
We have a lot of examples of ReadRegister for devices in this repo, for example: iot/src/devices/Ak8963/Ak8963I2c.cs Lines 15 to 25 in 2440c17
|
With the I2cBus, and fact that you can quickly create your own read/write register which are always super specific, we can now close this one. Feel free to reopen it if you still find there is something missing. |
I started a binding for the At24Cxx Serial EEPROM and wanted to ping for thoughts before proceeding.
The problem I noticed while reading the datasheets is the I2C addresses vary based on the size of memory. For example, the AT24C01/AT24C02 uses the hardware pins for addressing A0-A2 and the page addresses are in the first byte read/written (as in most cases).
However, as you work your way up to the AT24C16, it begins using the address bits A0-A2 instead to address the pages. Figure 7. Device Address in datasheet link above explains. This is somewhat of a hindrance in the I2cDevice implementation as you must assign the I2C address to the I2cConnectionSettings and then instantiate the I2cDevice. The I2cDevice object is assigned the I2C address for the lifetime of the object.
I'm hesitantly okay with this as you could design the binding where certain devices would have to use multiple I2cDevice objects to access all available memory. Basically, the one device would look like multiple devices on the bus. For example, the AT24C16 would need 8 I2cDevice objects to address all memory using addresses 0x50 - 0x57 where each object could access 16 pages.
The problem I see with this approach, is there will be some users that want to interact with the device/memory as a whole. I've personally seen where an IoT device has a 4K size of configuration data that is loaded into EEPROM. Ideally, I would use a method call that provides starting address and bytes of data to write. In general use, the binding API would need some internal logic to determine the paging (no matter what AT24C device) and would offer a little more appeasing API surface for Reads/Writes. However, I'm not sure of a good solution being forced to instantiate multiple I2cDevice objects to accomplish this.
/cc @joperezr @JeremyKuhne @krwq I'm copying you as you might have some thoughts how to get around this with current implementation or ideas on how to improve the II2cController that is in the works.
Since most of our current bindings pass in an I2cDevice object to instantiate the binding, I'm not sure how we could recreate another I2cDevice object with the new address to use when attempting to access related pages.
Would it be beneficial to add the following methods where it would override the initial address and use one provided? I could see this confusing some users as they'll see this as an option and think they have to use instead of how the current method includes the address.
One other tidbit, I personally don't want the bindings to provide me with "best" I2cDevice for platform (similar to GpioController) as I might want to use my own (e.g. GpioI2cDevice, etc.) in particular cases.
The text was updated successfully, but these errors were encountered: