This Week's Assignment

The requirements for this week were very broad: add a sensor to a board you designed and measure it.

Although Neil was keen for us to make a whole new set of boards, I haven't had a chance to actually use my PSoC 4 Breakout Board yet so I'll be using it. I'll be experimenting with two inputs:

  • Capacitive buttons, using the inbuilt CapSense functionality of the PSoC 4
  • Temperature, using the I2C based TMP275 temperature sensor from Texas Instruments

Here's a video of the final program running on the board.

What's It Going To Do?

My idea was pretty simple. In one state, I wanted to have a rainbow pattern running on the LEDs and be able to change the speed of the pattern using the capacitive buttons. In another state, I wanted to visualise the current temperature on the LEDS. The maximum and minimum temperatures would be continuously updated and the LEDs would shift from blue to red based on the current temperature relative to the historic range.

To start with, I wrote mini proof-of-concept programs for each of the two states. I then integrated them using a mini state machine.

Capacitive Buttons

During the design of the breakout board, I read through the two Cypress application notes:

There's quite a bit of overlap between the two. A large chunk of both documents deals with PCB layout and advanced component settings. The PSoC Creator example projects give a better starting point for the software design. There are no examples for CapSense buttons but the CapSense_CSD_P4_Design example project gives the basic bare bones of how to use the CapSense component. I started a new project with the aim of turning on an LED while one of the buttons was pressed.

It worked. First time. Flawlessly.

I was pretty surprised. I had thought I was going to do all kinds of manual tuning to get the buttons to work, but the SmartSense auto tuning seemed to take care of it all.

One big downside to the capacitive buttons is that you essentially have to poll them. I think they would work great in an RTOS based system but it's OK using them in an OS free program as long as you take care about the update rates. I did notice that button presses while in the temperature sensing state were a bit unresponsive, which was almost certainly due to the 250ms delay in the function.

I made a little function to handle updating the button state, just to keep the code more readable:

void CapSense_Update(){
    CapSense_UpdateEnabledBaselines();
    CapSense_ScanEnabledWidgets();
    while(CapSense_IsBusy());
}

In my design, the CapSense_Update() function gets called before each state gets handled. Using some handy macros, I can then check the state of each capacitive button and react accordingly.

TMP275 Temperature Sensor

I want to have a temperature sensor inside the word clock so that if things start getting a bit too hot I can reduce the brightness. Lower brightness means less current which means less heat, hopefully resulting in a longer life for the LEDs.

The TMP275 is a digital sensor. Instead of reading an analogue voltage and then translating the voltage level to a temperature (as would be required for the sensors that are in the Fab inventory), the chip performs the conversion and stores the result in memory. The memory of the chip, which controls parameters such as conversion accuracy and the last temperature measurement, can then be accessed on an I2C bus. You can have multiple slaves on a single bus, all controlled by one master. In this case the temperature sensor is the slave and the PSoC is the master. The I2C bus means that you can communicate to multiple different devices using only 2 pins (plus ground).

I went with the TMP275 simply because I had some free samples from TI. ±0.5°C with 12 bits of resolution is way more accurate than I actually need! The TMP75B would be cheaper and work just fine. I'm pretty sure they're code and pin compatible (but don't quote me on that!) so if I ever need to make a lot of boards I should be able to simply swap in the cheaper part.

My code for handling the temperature sensor is all pretty self explanatory. One nifty trick I'll talk about is using a union to store the temperature read from the sensor. A union basically allows you to access the same space in memory while treating it as different variable types. Here's my union definition:

typedef union TMP275_temperature{
    uint8_t byte[2];
    int16_t word;
}TMP275_TEMPERATURE;

This basically makes a new variable type called TMP275_TMPERATURE. Any variable declared as a TMP275_TMPERATURE can be accessed in two ways:

  1. As an array of two unsigned 8-bit bytes
  2. As a single signed 16-bit integer.

There are a few reasons to do this. The first is that the I2C memory reads are done as 8-bit bytes at a time and the TMP275 returns either 9 or 12 bits of data i.e. two I2C reads are needed. The second is that the top 8 bits of the data returned by the TMP275 are whole degrees, and the remaining bits are fractions of a degree.

The benefit is easier to see with an example. Instead of doing something like this:

tempUnits = I2C_read(TemperatureUnits);
tempFraction = I2C_read(TemperatureFraction);
temperature = (tempUnits << 8) | tempFraction;

we can instead do something like this:

currentTemperature.byte[1] = I2C_read(TemperatureUnits);
currentTmperature.byte[0] = I2C_read(TemperatureFraction);

Note that this is pseudocode, not actual code!

First off, we've saved having to make some temporary variables and then do some bit shifts and masks to end up with our 16-bit value. Second, we now have the benefit of accessing the temperature as just the whole part, just the fractional part or as a full 16-bit value.

wholeDegrees = currentTemperature.byte[1];
fractionsOfADegree = currentTemperature.byte[0];
maxTemperature = currentTemperature.word;

I think that's pretty nifty. One word of caution: make sure you know the endianness of the processor you want to compile unions for. Otherwise you can get yourself into a pretty horrible mess! The PSoC 4 is little-endian, so the least significant byte is stored in the smallest address.

State Machines in C

I've never actually written a state machine in C before - I've only ever implemented one on an FPGA in Verilog/VHDL. I based my implementation on this great article by Joonas Pihlajamaa about implementing state machines using function pointers. In my case the state machine is a bit trivial (it only has two states after all!) but writing it in this way makes it quite easy to expand and add more states later.

To give you an idea of the flexibility, here is all of the code in my main():

int main(){
    stateFunction currentState = State_Rainbow;

    Init();  
    while(1){
        CapSense_Update();
        currentState = (stateFunction)(*currentState)();
    }

    return 0;
}

The basic idea is that you have a function for each of the states you want. In my case, these are State_Rainbow() and State_Temperature(). Each of these functions performs the actions associated with that state e.g. the State_Rainbow() function updates the rainbow pattern on the LEDs. When a state is finished, it returns a pointer to the next state.

Ideally I would have liked to abstract out the state transitions to be a separate function. That would make complex state machines easier to debug, adapt and maintain. However, that was definitely going above and beyond what was necessary for this week!

Get The Files

No GitHub this week. You can get a zip of PSoC Creator project here.



Comments

comments powered by Disqus