This video presentation is the twelfth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to Semaphores.
This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner.
|Session 12 Course Line||Lab Outline|
Note: Temperature Sensor that is part of the TWR-SENSOR-PAK from Freescale is required for this lab.
First, watch the video for Session 12: Semaphores.
Then, follow through with the interactive lab assignment below.
SESSION 12: LAB ASSIGNMENT
Semaphores are frequently used in embedded systems and you'll see in the lab that adding them to a project is quite straight forward. However, there are some scenarios to watch out for to ensure that you don't inadvertently lock up the semaphore. In this lab the Temp Task will be enhanced to use the I2C bus to communicate with the temperature sensor and retrieve the current ambient temperature. This will be used to detect an over temperature condition and the appropriate response will be initiated. The temp sensor module is part of the sensors kit for tower boards (P/N TWR-SENSOR_PAK from Freescale). This does not come with the K70 Tower Kit and needs to be purchased separately.There are a couple of inputs to the temperature sensor, the reset line and shut down line, that need to be taken care of. We'll control these using GPIO lines and will set them up at start up such that the temperature sensor is not shutdown or reset. There is no reason to change these states later so we just have to do the initial set up.
The objective of this lab is to learn about Semaphores by using one to protect access to the I2C bus. You will also use a couple of GPIOs, and you'll leverage the I2C code that you added in the lab for session 11 as this will be needed by the Temp Task.
- We'll start with setting up the Temperature Task. The first thing to do is to set up the two GPIO lines that will drive the reset and the shut down inputs to the temperature sensor. Use the lwgpio_init() function to initialize the shutdown pin (which is connected to GPIO Port B, pin 4). This should be configured as an output with an initial value of 'high'. Do the same to initialize the reset line (which is connected to GPIO Port B, pin 8). This should also be an output with an initial value of 'high'. You may want to review the lab for session 5 that covered GPIO to remind yourself of how to do this.
- You will also need to set the functionality for these two pins using the lwgpio_set_functionality() function. Note that you'll need a LWGPIO_STRUCT for each to set the functionality.
USING I2C TO READ THE TEMPERATURE
- In the lab for session 11 when access to the I2C interface was first added only the Accel Task was accessing the bus and therefore the handle for this was declared in this task. Now that more than one task will be using the I2C bus this declaration needs to be an extern. Remove this declaration from the Accel Task and add it to main.h as an extern like this:
extern MQX_FILE * i2c;
- This handle still needs to be declared however so in main.c place the 'MQX_FILE * i2c' declaration above the task template list.
- The I2C channel needs to be opened before any task tries to use it. Currently the Accel Task uses it and shortly the Temp Task will also access the I2C bus. So remove the fopen for the i2c from the Accel Task and move it to the Health Task when the other start up housekeeping is being done such as creating the message pool.
- The temperature sensor needs to be instructed to do a conversion, but before the Temp Task can send this message there is some set up to do. Inside the while(1) loop of the Temp Task you need to communicate with the temperature sensor to:
- put the I2C bus in Master Mode
- set the destination address
- write a 2 byte message to the temperature sensor (containing 0x12, 0x0 to write a zero to register 0x12 to initiate the conversion)
- flush the bus
- then assert a Stop condition
Fortunately we just finished doing all of this in the Accel Task in the lab for the previous session. Copy this section of code from the Accel Task and put it at the top of the while(1) loop of the Temp Task. The destination address of the temperature sensor is 0x60. Remember to declare the variable used for ioctl parameters, the data array, tracking the bytes read, and the result returned from the io_read() function.
- Keep the _time_delay() function already in this code but shorten the delay to 50 msec which will be enough time for a convertion of the temperature. This is all that is needed to allow the temp sensor to complete a conversion.
- Once the temperature conversion has been done you can now read the temperature value. Again we can borrow this code from Accel Task. You need to:
- Set the destination address to 0x60
- Write 0x02 to the I2C bus to indicate that you wish to read two bytes from the sensor.
- Flush the output buffer with an ioctl() call using the IO_IOCTL_I2C_FLUSH_OUTPUT command
- If the ACK bit was received after the previous write (ie the parameter returned from the ioctl call was not set) then use a Do loop to read the two bytes back from the bus.
- Use an ioctl call to issue a stop using the IO_IOCTL_I2C_STOP.
- Make all necessary declarations. The data array of uint8_t can be 4 bytes in size since we are reading shorter messages back from the temperature sensor.
- The data read back from the I2C now needs to be converted into a temperature. Declare a 32bit uint to hold the data read and another 32bit uint to hold the value in degrees Celsius.
- Combine the two bytes of data read from the temperature sensor into a 32 bit number using the following shifts and masks, and convert that into degrees Celsius as shown below. 'Tadc' is the temperature from the ADC and 'Temp' is the temperature converted to Celsius. This information is available in the documentation of the temp sensor if you'd like to reference it.
Tadc = ((data<<2)&0x3fc) + ((data>>6)&0x03);
Temp = (Tadc*100 - 60575)/-535;
- We are now ready to send the read temperature to the Health Task. Update the current message so the temperature in degrees C is sent as the data of this message.
- Finally, as a housekeeper matter, it would be a good practice to copy the code at the top of the Accel task that checks if the i2c handle is valid or not and place it at the top of the Temp Task. This should prevent the Temp Task from running if the i2c handle isn't valid. Also, be sure that the _task_block() call that was originally in the Temp Task after it's printf statement has been removed.
ADDING A SEMAPHORE
- We now are ready to get to the main part of our lab which is to use a semaphore to protect the access to the I2C bus. Declare a light weight semaphore of type LWSEM_STRUCT in main.c right after the declaration of the I2C file handle. Add this as an extern in main.h to keep the compiler happy.
- The semaphore needs to be created with the _lwsem_create() function and for the reasons that the I2C channel was opened in the Health Task, the semaphore should be created in the same area. Initialize the semaphore with a count of 1 to restrict access to a single task (ie a binary semaphore).
- The semaphore can now be used to safeguard against simultaneous access of the bus. Use the _lwsem_wait() function and the _lwsem_post() functions in the areas of the code that access the bus. The semaphore wait should be done before the first ioctl call over the bus and the post should be used after the stop condition has been asserted. Note that there are two places in the Accel Task where the I2C bus is accessed, first when the accelerometer is turned on, and second in the while(1) loop when the data is being read.
DETECTING AN OVER TEMPERATURE CONDITION
- The code is now set up such that the temperature read is sent to the Health Task but currently all the Health Task does in response is to print out the fact that it received a message. To make use of the temp data received the Health Task needs to be updated so it detects if there is an over temperature condition and react to it when that condition occurs. As well, in the event of an over temp condition, the Health Task should send a message to the Display Task to notify it of the situation. To facilitate this add a Boolean to the APPLICATION_MESSAGE data structure in main.h called 'OVER_TEMP'.
- In the while(1) loop of the Health Task add a check of the incoming messages to see if they are of type 'TEMP_MESSAGE' and check if the temperature received in this message is greater than a threshold temperature of say 32 C. Add the code to send a message to the Display Task in an over temperature situation. Note that a new message of type APPLICATION_MESSAGE will need to be declared at the top of the Health Task. When creating the new message the Target Queue ID ('->Header.TARGET_QID') and the message type ('->MESSAGE_TYPE') will have to be set. The message data ('->data') can be the received temperature and of course our new parameter to the APPLICATION_MESSAGE called OVER_TEMP should be set to true.
INDICATING AN OVER TEMPERATURE CONDITION
- The last thing to add is the visual indication of an over temp condition by turning on an LED. This is similar to what was done in the previous lab when motion was detected by the Theft Task. In the Display Task add a new case to the switch statement that processes incoming messages to handle a HEALTH_MESSAGE. If the OVER_TEMP bool in the message is set, LED 2 should be turned on.
- Note that the msgq_receive() function at the top of the while (1) loop was set up with a 1 second timeout. If a message was received it was processed, if it was not received before the time out expired LED 2 was toggled to show general health of the system. This will interfere with our use of LED 2 to indicate an over temperature situation so this line should be commented out or removed.
- You may want to add LED 2 to the list of LEDs that are set to 1 (off) when a SW2 message is received by the Display Task.
- You can now compile and run the application. To create an over temperature situation you can 'carefully' use a heat gun to heat up the temperature sensor a bit. Alternatively you can change the threshold in the code to something below what the ambient temperature is. Your system will always be in an over temperature condition this way but at least you can verify that your code is functional.
Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here.