Let's program a proof-of-concept I2C responder device. In this episode of The Factory we've selected a microcontroller for our PiicoDev family and get it working as an I2C receiver to blink some WS2812B leds. We'll take a look at the proof of concept from both ends: I2C Host (Raspberry Pi Pico + MicroPython) to Receiver (ATtiny + Arduino C). We also brush against the parametric search that found the device, and have a bit of a winge about the global silicone shortage.

Transcript

Welcome back to the factory. Today we are talking I2C responder devices. I have some addressable LEDs over here, I've got a MicroPython device over here, and somehow over I2C the MicroPython device is controlling the LEDs.

Let's have a look at how you might program an I2C responder device. We'll also have a brief chat about component sourcing and how I selected this part for the job. Let's get started.

Here we have a string of GlowBit LEDs. These are single wire addressable WS2812B version 5 LEDs and they're being controlled by the Raspberry Pi Pico. The only connection to the Pico here is this I2C connection, but these aren't I2C LEDs, they use some other asynchronous protocol. And that's where this guy comes in.

This device in the middle is a ATtiny development platform and I'm using it to act as a translator between the I2C bus on the Pico and the asynchronous data that is expected by these LEDs. What this means is that if we bake all of this part of the circuit into a single module then we can have PiicoDev compatible addressable LEDs and other addressable devices.

A diagram might help out a little bit here. So here we go. I have on the left side a MicroPython device, in this case a Raspberry Pi Pico, but it doesn't matter. It could be a Micro:Bit, it could be a Raspberry Pi, whatever. This large block, this large blue block on the right represents everything else. So that was the ATtiny development platform and the addressable LEDs.

We have the four familiar PiicoDev connections between the MicroPython device and the module. We have power and ground and a two-wire I2C bus. That's a synchronous bus. The ATtiny on board is serving as the translator, taking messages received over I2C, doing a little bit of logic andThen passing them out as a separate signal to the addressable LEDs. This means that we can send colour and brightness information over I2C and have the LEDs react exactly as they would if they were wired directly to the MicroPython device.

The point here is that there's no wiring required. You just use the standard PiicoDev cables to connect everything together and you're done.

To get this little proof of concept together I started by driving just a single LED over I2C. So all that was required were three bytes for RGB data and then an extra byte just for say the register to address. Remember I2C devices usually have addresses that they expect data to be written to and so in this case I'm just writing it to say register one.

Once we can drive a single colour on that LED we can sweep it up and down a little bit and just make sure that we can drive it interactively let's say and then it's time to start mixing colors.

Just brought in a top camera so you can see this a little bit closer. This is exactly as we just saw in that diagram. We have a MicroPython device connecting only by an I2C bus to some microcontroller and that microcontroller is driving some number of glow bits.

So the Raspberry Pi Pico is just sending out streams of binary data for red green and blue values for each of these LEDs. So we have in the first LED pure red just going as a sine wave then pure green and blue and then this fourth LED I've just added the sum of all those together.

So we have this kind of rippling wave going down these first three LEDs and those are matching the three-phase sine wave that you can see on the plotter in Thonny.

On the MicroPython side of town for this test I'm just generating three sine waves each offset by 60-degrees.So, kind of like a three-phase sine for red, green, and blue. And then I'm just adding a little bit of an offset to each to create that nice three-phase pattern that you see in the plotter.

Once we have those RGB values, we just create a bytes buffer which has the first three bytes as just red (that's for the first LED), the second LED is pure green, the third LED is pure blue, and the last LED is some mixture of the three waves.

Next, we just push out onto the I2C bus to the address of our responder device (I've just hard programmed that to some constant). We're writing to register one, which is just some location in memory (for now, I'm choosing location number one). Then, we punch out this buffer of what looks to be 12 bytes for four LEDs (four LEDs, three bytes each), so that's 12 bytes, plus one more for the location to start writing them.

The rest of this code is just handling generating those sine waves. We increment some variable to move to the next step in the sine wave.

On the Arduino side of town, we have a receive event. This is called any time an I2C transaction is received by the device. It takes in an argument for how many bytes were received. In Arduino, you get a buffer of about 32 bytes, which is more than enough for what we need to do here.

We set some global variable for how many bytes we received. Then, while there are bytes remaining in the buffer, we just read them into our own persistent buffer that gets used by the program. We execute wire read into our own buffer, increment the index, and we're done.

The last thing to do is set some data ready flag so that in our main loop (not inside the interrupt handler), we can just check.If data is ready and we check that the first byte in the message is equal to one (which is the register we want to write the data to), we then check if we have received the correct register and if there are a total of 13 bytes (for four LEDs and one register byte). If all these conditions are met, we proceed to write each buffer value into a spot for each LED.

Since we get to set the protocol, I will assume that each byte represents the values for red, green, and blue for each LED. So, the first byte is used for the red position of the first LED, the next byte for the green position, and the third byte for the blue position. We then wrap around to the fourth byte, fifth byte, sixth byte, and so on.

I could have used loops for this, but it was simpler to accomplish with some flat code. This block of code essentially extracts the byte information from our long buffer and assigns each byte to the appropriate position for the red, green, and blue information of each LED. It's essentially deserializing the string of bytes.

Of course, there is still more work to be done to complete this, but it serves as a great proof of concept and will give us something to design with. Currently, this is a hard-coded example, so the next steps would be to wrap the Python code into a more general class. The goal is to create a class that looks and feels similar to a NeoPixel library, where you can simply call setPixelColor and specify the LED number and the RGB values.Values and also to implement other nice functions like setting the global brightness so that you can control the global brightness of every LED regardless of what flat data is written to it and also maybe some nice like colour generation functions like a colour wheel or a few pre-programmed patterns.

For the I2C responder device firmware, this of course is a pretty hard programmed example but it's well on the way to being generalized. Of course, the firmware will be for a specific module with a known number of LEDs but yeah it's definitely on its way.

Eagle-eyed viewers might have picked up that I'm using an AtTiny explained 416 development board here. The only gotcha is the 416 doesn't have enough memory to handle the wire library, that's the I2C interfacing library, and the addressable LED library. I've actually transplanted onto this development board an AtTiny 816 which has double the flash.

But how did I get to this part? Last week I said I'd spend some time in the parametric search and on the microchip website I found this parametric search page. I have no idea how to get here through the website, I was only able to get to it through a google search but there's the URL if you need it.

I'm going with 8-bit AVRs because they're cheap and AVRs might have an Arduino core, super important because we want our device to have Arduino libraries for as many peripherals as possible. We need a little bit of flash, so I select 8 and up program memory size in kilobytes. An EEPROM would be great, so anything that isn't zero, and a fair few pins.

I know that a QFN20 package would be pretty desirable, it's a good size for a micro, it's a good size for a PiicoDev module, so I'll pick just a couple of these options and that actually narrows.flash, which may not be huge, but it should be sufficient for simple devices. In this proof of concept, we are using a fairly large library for the glow bits, which is taking up 50% of the program memory space and 43% of the dynamic memory. However, we still have 287 bytes available, and I believe that should be enough. These devices are specific I2C devices with well-defined flight envelopes, so the remaining resources should be more than adequate for our needs.

Currently, we are only on parameter number two, and we have already covered the ADC requirement. While a DAC would be nice to have, it may be challenging to fit it into such a small device.

The options that remain on the list are mostly filled with the attiny zero one and two series. The difference between the zero and one series is not significant, and I eventually chose the 806 for this project. In fact, what I showed you today was actually running on an 806. The choice was also influenced by the global silicon shortage, as microcontrollers are not exempt from the shortage. The 806 and 816 are still available, unlike many other options. Despite the availability factor, the 806 is a great device for the task at hand.

Let's take a closer look at the 816. It has eight kilobytes of flash, which should be sufficient for our needs. The proof of concept we are working on is not light on resources, as it is using a fairly large library for the glow bits. This library is taking up 50% of the program memory space and 43% of the dynamic memory. However, we still have 287 bytes available, which I believe should be enough. Considering the specific requirements of these I2C devices, I don't foresee a need for much more than what we are currently using. Flash, 20 meg clock is heaps. We're probably only gonna run that at the eight MHz internal. At most 512 RAM, fine. It's got some EEPROM, great. Maybe you want to program in some calibration constants, maybe you just want a user programmable address so you could have many many of the same device on a bus, and you would do that by writing address values into the EEPROM. Meets that requirement for being rich with peripheries, so we've got UART, yes. SPI, yes. And of course, hardware I2C. So that's already pretty easy to serve as a bridge between these three interfaces.

20 pin count device which makes it nice and small to fit on a PiicoDev module, and importantly we have a 12-channel ADC. That is, that's a lot of ADC for such a small device. It is only 10 bits, you know, 12 bits would be nice but 10 bits more than acceptable for these custom devices. If you want to read say a potentiometer, that'll do just fine.

Just an aside, I mentioned the parts shortage. This is the kind of thing we're working with here. We have lead times that go into November 22, November 2022, October 22, 22. Oh, there's a few parts around but they're not in the right package, so you know, things we've managed to secure enough for now.

Let's say thanks for joining me in this deep dive into my little working prototype here. I hope you enjoyed looking at how you might make your own I2C responder and having a bit of a fireside chat about parametric search. Love a parametric search. As always, if you have any suggestions, if you have any questions, do open a thread on the Core Electronics forums. We'd love to see you there. And until next time, thanks for watching.

Comments


Loading...
Feedback

Please continue if you would like to leave feedback for any of these topics:

  • Website features/issues
  • Content errors/improvements
  • Missing products/categories
  • Product assignments to categories
  • Search results relevance

For all other inquiries (orders status, stock levels, etc), please contact our support team for quick assistance.

Note: click continue and a draft email will be opened to edit. If you don't have an email client on your device, then send a message via the chat icon on the bottom left of our website.

Makers love reviews as much as you do, please follow this link to review the products you have purchased.