I've been hard at work on my IoT Christmas Ornaments project, specifically getting the software and firmware for my little lights working nicely.
As a first step toward that goal, I dove into the manual Infineon provides for the XMC1202 board in order to get a feel for how it works. In the end, I decided to follow Peter Oakes lead and produced a library for the board. While Peter's library focuses on using text-named colors, I wanted mine to act more as a simpler overlay for the actual control of the board, so mine is geared more toward being passed numerical values. Someone more ambitious than I may, someday, want to combine both functionalities into a single library. But for now, this works for me.
The library files, as well as a test program, are attached at the bottom of this post.
The Library - rgbshield
The library implements a number of functions which can be used to control the basic functions of the Infineon board.
There are four functions which can be used to set the intensity of each color channel:
setrgb(Red, Green, Blue) - Sets the intensities of all three color channels at once. This is probably the most common method of controlling color. It accepts three integers as inputs to set the values of the red, green, and blue channels respectively. The valid inputs range from 0 to 0xFFF (or 4095 if you prefer decimal).
setred(Red) - Sets the intensity of the red channel only. This accepts one integer as an input to set the red intensity, and the valid inputs again range from 0 to 0xFFF.
setgreen(Green) - Sets the intensity of the green channel only. It is otherwise the same as the setred function.
setblue(Blue) - Sets the intensity of the blue channel only. Again, it works the same as the previous two functions.
There are also three functions which can be used to set the other basic parameters of the board:
setbrightness(Bright) - Sets the maximum allowed brightness level of the board, from 0 to 0xFFF (or 4095 in decimal), with its single integer input. This doesn't simply set a cutoff point, above which any values will produce the same intensity of light, this acts more as a dimmer switch. If, for example, you passed a value of 0x800 to this function (so 2048 in decimal, or 50% brightness) it would cut all intensities in half. A pseudocode example may help illustrate this more clearly:
setrgb(0xFFF,0xFFF,0xFFF); --> Full intensity white light
setrgb(0x800,0x800,0x800); --> Half intensity white light
setbrightness(0x800);
setrgb(0xFFF,0xFFF,0xFFF); --> Half intensity white light
setbrightness(0x800);
setrgb(0x800,0x800,0x800); --> Quarter intensity white light, because the rgb intensity values are multiplied by the brightness level set
setwalktime(Walk) - Sets the time the board takes in transitioning from one color to the next. The single integer value passed here should be multiplied by 0.01024 seconds to determine what the actual walk time will be in seconds.
setfade(Fade) - Sets the time the board takes in going between brightness levels. This also accepts a single integer value as an input. The value set here isn't used linearly, because the board will follow a pseudo-exponential curve when changing brightness levels (in order to mirror the semi-logarithmic way our eyes perceive brightness) to create the appearance of a linear shift in brightness. This makes it difficult to give a quick and easy method of determining the actual time the board will take to transition from zero brightness to full brightness, so if this is a value that's important to your program you're just going to have to play with it until it looks good (and probably use the read function to determine when full brightness is reached rather than attempt to do it by absolute timing).
The library contains six functions which can be used to read the current state of the board's outputs. All six return an unsigned integer containing the current value of the requested parameter and require no arguments to function. There are three used to read rgb intensity values:
readred()
readgreen()
readblue()
As well as three used to read the other board parameters:
readbrightness()
readwalktime()
readfaderate()
I have also included two of the Infineon created functions for use with the more advanced parameters which are mainly set during setup (these parameters will be discussed later):
I2CWRITE2BYTES (int Address, int Command, unsigned int Data) - Allows two bytes to be written to the shield in order to perform whatever command value was passed.
I2CWRITE6BYTES (unsigned int Address, unsigned int Command, unsigned int DataOne, unsigned int DataTwo, unsigned int DataThree) - Allows six bytes to be written to the shield in order to perform whatever command value was passed.
The Library - a test program
To better demonstrate how the library is used, I have created a basic test program, aptly named rgbshield_test:
#include "rgbshield.h" #include <Wire.h> rgbshield RGB = rgbshield(); void setup() { Serial.begin(9600); Wire.begin(); while (RGB.on != 1) // Wait for shield to respond, keep setting the values till it does { RGB.setfade (0x0000); // Immediate fade RGB.setbrightness (0x0000); // 0% brightness level RGB.on = RGB.readbrightness(); // Request for brightness level if (RGB.message == 1 && RGB.on == 0) // If message received and dimming level = 0%, "message" is set in the I2CREAD function { RGB.message = 0; RGB.on = 1; // break out of loop } } RGB.I2CWRITE2BYTES (ADDRESS, OFFTIME_RED, 0x38); // Set off-time of red channel to 0x38 - good base value RGB.I2CWRITE2BYTES (ADDRESS, OFFTIME_GREEN, 0x39); // Set off-time of green channel to 0x39 - good base value RGB.I2CWRITE2BYTES (ADDRESS, OFFTIME_BLUE, 0x38); // Set off-time of blue channel to 0x38 - good base value RGB.I2CWRITE6BYTES (ADDRESS, CURRENT_RGB, 0x80, 0x80, 0x80); // Set current of red channel to 0x80 = 780mA - maximum safe current! RGB.setfade (0x002C); // Fade Rate between brightness levels RGB.setwalktime (250); // walk time between colors = (value)*0.01024 seconds = (here) ~2.5s RGB.setrgb (0x0555, 0x0555, 0x0555); // low level White Light - indicates initialization is finished RGB.setbrightness (0xFFF); //Maximum brightness means intensity levels are used directly } void loop() { //Variables for storing color intensity reads unsigned int rout = 0; unsigned int gout = 0; unsigned int bout = 0; //Set full green RGB.setrgb(0x000,0xFFF,0x000); //Pause until green reaches desired level while(gout != 0xFFF) { gout = RGB.readgreen(); } //Set half green half blue RGB.setrgb(0x000,0x800,0x800); //Pause until blue & green reach desired levels while(gout != 0x800 && bout != 0x800) { gout = RGB.readgreen(); bout = RGB.readblue(); } //Set full blue RGB.setrgb(0x000,0x000,0xFFF); //Pause until blue reaches desired level while(bout != 0xFFF) { bout = RGB.readblue(); } //Set half blue half red RGB.setrgb(0x800,0x000,0x800); //Pause until blue and red reach desired levels while(rout != 0x800 && bout != 0x800) { rout = RGB.readred(); bout = RGB.readblue(); } //Set full red RGB.setrgb(0xFFF,0x000,0x000); //Pause until red reaches desired level while(rout != 0xFFF) { rout = RGB.readred(); } //Set half red half green RGB.setrgb(0x800,0x800,0x000); //Pause until green and red reach desired levels while(gout != 0x800 && rout!=0x800) { gout = RGB.readgreen(); rout = RGB.readred(); } }
Breaking down the program into its most relevant bits, we start with the initialization check for the shield. This code was adapted from Infineon's test program. As you can see, it attempts to set the brightness level to zero (i.e. turns the LEDs off), then reads back the current brightness level from the shield. It continues this process until the Arduino successfully sees the brightness set to zero at which point it knows the shield is fully responsive and ready to work:
while (RGB.on != 1) // Wait for shield to respond, keep setting the values till it does { RGB.setfade (0x0000); // Immediate fade RGB.setbrightness (0x0000); // 0% brightness level RGB.on = RGB.readbrightness(); // Request for brightness level if (RGB.message == 1 && RGB.on == 0) // If message received and dimming level = 0%, "message" is set in the I2CREAD function { RGB.message = 0; RGB.on = 1; // break out of loop } }
Next, we set some of the board's electrical and timing values. A deeper discussion of just what these do follows, but the values given here are good and safe base choices. I chose to leave these functions unimplemented in the library, because they aren't things that should be played with by the uninitiated. They're still easily accessible by using the I2CWRITE commands implemented in the library, but are not readily apparent to a user who hasn't spent some time digging into the workings of the board:
RGB.I2CWRITE2BYTES (ADDRESS, OFFTIME_RED, 0x38); // Set off-time of red channel to 0x38 - good base value RGB.I2CWRITE2BYTES (ADDRESS, OFFTIME_GREEN, 0x39); // Set off-time of green channel to 0x39 - good base value RGB.I2CWRITE2BYTES (ADDRESS, OFFTIME_BLUE, 0x38); // Set off-time of blue channel to 0x38 - good base value RGB.I2CWRITE6BYTES (ADDRESS, CURRENT_RGB, 0x80, 0x80, 0x80); // Set current of red channel to 0x80 = 780mA - maximum safe current!
Finally, we have the main loop and its color changes. This basic pattern is repeated a few times, but essentially what it does is tell the board to set certain rgb intensity values, then continuously reads the actual intensity values until the desired level is reached before moving on to the next color change.
You could get the same behavior as this test program by replacing the while loop with a delay(2500) statement, and while that is simpler conceptually, it introduces additional problems if you decide to change your walktime value to anything other than 250. Say, for example, you changed walktime to 300 because you wanted slower changes. If you used the delay() method, you would then have to modify every delay() statement or else your color changes would run over each other. Instead of getting to full-green before starting the switch to full-red, you'd get about 70% of the way there, then immediately start transitioning from that color to full-red.
That may seem like a small hassle, but consider a less simple program: You use an input of some kind (whether a button, a rotary encoder, or some web-based input) to modify the walktime value to create different patterns. You would then have to use a variable as your delay value, and have that variable change in proportion to your walktime. It's certainly possible, but your simple solution has now become markedly more difficult, and that's still a relatively simple use case.
With this method, the problem works itself out. Walktime changed to 100? Ok! No problem! Oh, now it's 500? Wow, that's slow, but sure, we can do that... no other changes needed. Have I sold you on this method yet? Good!
//Set full green RGB.setrgb(0x000,0xFFF,0x000); //Pause until green reaches desired level while(gout != 0xFFF) { gout = RGB.readgreen(); }
Now... lets see how that program looks, shall we?
What you see here is everything going from a fully powered off state to the initialization check, then the very dim white light indicating the initialization has succeeded, and then on into the program itself for a few loops.
Now that you know how to use my library to control the Infineon shield, let's go into a few of those technical details I glossed over earlier. You can, of course, feel free to skip this section and simply use the commands discussed above. But we're hackers here, we care about more than just making it work, we care about knowing how it works.
Technical Nitty Gritty
The following commands aren't specifically implemented in my library. They're still accessible through use of the I2CWRITE2BYTES function, however, and can be accessed by using I2CWRITE2BYTES(ADDRESS, [command listed below], [value you want set]).
CURRENT_[COLOR]
This is the first of the "electrical and timing values" I mentioned before. Using this command, you can set the maximum current per given color channel (which are obviously red, green, or blue). It's normally something you would set in your setup() section, as shown in the test program, and then leave. There may be situations where you'd want to modify this during board operation... but I can't think of any offhand.
This command accepts a numerical value in the range of 0 to 128. Feeding it any value over 128 will just result in the setting being changed to 128, so you can't accidentally blow out your board with a funny setting. This maximum value also, unsurprisingly, corresponds to the maximum safe current for the board. Nine times out of ten you'll just set this to 0x80 (or 128) and leave it be.
OFFTIME_[COLOR]
This is the second, and more potentially dangerous, of the "electrical and timing values". The control circuit on the Infineon board uses a chopper style of pulse width modulation in order to achieve the required current for a given intensity level. This command sets the off-time of the chopper circuit.
To get a better understanding of what this means, picture a graph of the current flowing through your LEDs - because of the way circuits like this work, it won't actually be a straight line. Instead, it will be a series of peaks and valleys, because the power source is being constantly turned on and off ("chopped") in order to maintain an average correct level of current. By using this command to change the off-time value, we're either making those valleys wider or thinner, and in the process producing more or less ripple in the output current.
Because of propagation delays inherent in this kind of control, as well as delays introduced by components in use on the board, the peak level of current experienced by the LEDs and the driver board will always be slightly higher than the maximum we set with the previous command. That is, the board will sense that it has hit the maximum current value a short time after that value has actually been reached, and in the meantime the current has continued to go up, so by the time the chopper circuit goes into "off" mode we've already exceeded our desired maximum current.
The hardware can take this, briefly, but if we set the off-time to a value that's too short we can exceed that safety margin. And that's how you get catastrophic failure.
The default value here is 0x40 (or 64), and that's probably a good value to stick with. It results in an off-time of 1 microsecond. If you REALLY want to change it, the manual has equations you can use to try to estimate what is (and isn't) going to be a safe value when combined with your maximum current setting. But so far, the ripple with the default values seems low enough that I can't see its effects. Like the peak current setting, this is also a per-color setting, and something you'll usually handle in your setup() function.
Additional Commands
There are additional commands available for the Infineon RGB shield that are unimplemented in my library and which are actually inaccessible without creating additional functions. These are all very advanced commands, however, which allow you to do things like write directly to registers on the board (rather than reading commands stored in given registers and using them to set values), change the address of the board from the default 0x15EUL to something of your choosing (which would only ever be useful if you have multiple boards connected to a single Arduino), or save the current state of all settings on the board so it will produce the same output even without an Arduino or other microcontroller attached.
These are all very useful things in certain circumstances, and are all quite well documented in Infineon's manual for this board, but were things that I felt were best left out of my library. Because I'm releasing my library as public domain, however, you can add these in yourself if it's something you feel is necessary. If you do use my library as the basis for an expanded, improved, or otherwise optimized library of your own I would just ask that you leave mention of me in the comments of your file and link back to my original so the code can be traced back to its source should anyone ever need to.
Now... on to working out the IoT/Eclipse part of this project. We'll get these bright balls online yet!