Essentials of MQX RTOS Application Development Course - Lab Guides Knowledge Base

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Essentials of MQX RTOS Application Development Course - Lab Guides Knowledge Base

Discussions

Sort by:
This video presentation is the thirteenth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to Memory Services. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 13 Course Line Lab Outline Different uses for RAM in an Embedded System Where different types of data are stored Creating memory pools Allocating memory from a memory pool Creating partitions Allocating memory from a partition Light Weight Memory Manager vs Full Featured Memory Manager Adding a queue to be used for logging health records Adding a timer and a timer ISR Adding the Health Record structure Processing messages sent to the Health Task to fill in the Health Record entries and store them in the queue Adding a new UI command to print out the Health Records First, watch the video for Session 13: Memory Services​. Then, follow through with the interactive lab assignment below. SESSION 13: LAB ASSIGNMENT INTRODUCTION In this lab we will add the logging of data to memory that is allocated from the system memory pool. We will continue to flesh out our application in this lab and we will focus on the Health Task. The Health Task receives data from various tasks through Message Passing. When a periodic timer expires the Health Task will place the data it has received onto a queue and the UI Task will print out that data. Queue structures have not been covered so far in the course so you may want to review this in the MQX Reference Manual briefly, but it should be quite quick for you to pick this up. A queue is simply a sequential list of data which you can read from and add to. Note that our implementation does not put a cap on the amount of data that could eventually be allocated, so eventually the system will run out of memory and additional health records will not be logged. For a system that you intend to deploy to the field it would obviously be good practice to not exhaust the available memory and an ideal way to do this is with a partition. This lab can be updated on your own to allocate memory from a partition you define instead of allocating memory directly from the system pool. OBJECTIVE The objective of this lab is to use Memory Services to support the logging of health data for our application. We will also cover the use of queues and will implement a timer. Light Weight Timers were covered in session 8. ASSIGNMENT ADDING A QUEUE The first thing we need to do is to add a Queue Structure and the best place to locate this is in main.c where our other global structures are located. Add the following line, and make sure that it is also declared as an extern in main.h.            QUEUE_STRUCT     log_queue; In the Health Task, add the following init function for our log_queue. The '0' parameter indicates that the queue is unbounded and will grow to any size, assuming the system has the memory.            _queue_init(&log_queue, 0 ); ADDING A TIMER In order to set up a timer we'll need a structure to be declared in the Health Task of type LWTIMER_PERIOD_STRUCT and another one of type LWTIMER_STRUCT. In the initialization section of the Health Task create the periodic queue with the _lwtimer_create_periodic_queue() function that has a period of 1 second and no wait time. Note that for this BSP of MQX the define BSP_ALARM_FREQUENCY is set to 200 ticks and each tick is 5 msec, so it represents 1 second. Then add a timer to the queue using the _lwtimer_add_timer_to_queue() function. You do not need an offset and the timer should call a 'health_timer' ISR function that will be defined later. The parameter to pass is the Health Task Queue ID. Note that the 'my_qid' variable isn't valid until after the _msgq_open() function so these new lines should go after the _msgq_open() call. CREATING THE TIMER FUNCTION At the top of HealthTask.c create the 'health_timer' function that will be called when the timer expires. It will receive the Health Task's queue ID as its only parameter, which will have to be converted into a '_queue_id' type. This function will send a message to the Health Task essentially to let it know that it's time to write a log entry of the current values. As was done elsewhere in the application, declare a message of type 'APPLICATION MESSAGE *', set the target queue id, set the message type to be 'LOG_TICK_MESSAGE' and then send the message. That's all that this function needs to do. This new message type needs to be added to the 'APPLICATION_MESSAGE_TYPE_T' data structure in main.h. HEALTH INFORMATION STRUCTURE The Health Task needs a structure to hold all of its health data so in main.h declare a structure as shown below. Since this is going to be copied to a queue, the first element should be of type QUEUE_ELEMENT_STRUCT which MQX uses to manage queue entries. The structure should also have a number to indicate which entry it is in the queue, the temperature, the voltage, and the accelerometer x, y, and z data. typedef struct {    QUEUE_ELEMENT_STRUCT    QE;      uint32_t               NUM;      uint32_t               TEMP;      uint32_t               MV;      uint16_t               X;      uint16_t               Y;      uint16_t               Z; } HEALTH_RECORD; Declare a pointer of type 'HEALTH_RECORD *' at the top of the Health Task. In the Initialization section of the Health Task (ie before the while(1) loop) uses the _mem_alloc_system_zero() function to allocate memory for an instance of "HEALTH_RECORD" from the system pool that is set to zero. For now you don't need to check if this was successful or not, we'll do that later. Declare a 32 bit variable that will be used to count the number of health records, and after the first one has been created (in step 9 above), set this variable to 1. UPDATE THE PROCESSING OF MESSAGES In the while(1) loop of the Health Task it checks if an incoming message is from the Temp Task, and if there is an over temperature condition a message is sent to the Display Task. The Health Task now needs to be processing several types of messages so it would make sense that a switch statement on the received message type is used. Add a case to the switch to handle messages of type TEMP_MESSAGE that will contain the same functionality that is already in the while(1) loop for messages of type TEMP_MESSAGE. And add another case for messages of type LOG_TICK_MESSAGE which for now won't do anything, we'll fix that later. When a TEMP_MESSAGE is received we need to store the passed in temperature, so in the case that handles TEMP_MESSAGEs update the "TEMP" parameter of the health record to be the passed in temperature in this message. Here is where it would be a good idea to first check if our health record != NULL. Add in a case for ACCEL_MESSAGEs which will record the passed in x, y, and z axis motion values into the health record, assuming it's valid. Add in a case for ADC_MESSAGEs which will record the passed in voltage into the health record, assuming it's valid. We are now ready to add the handling of a LOG_TICK_MESSAGE. Since the health record has been updated when the other messages were processed we only need to save the record number (count) into the health record, increment the record number for next time, and then add this health record to the queue using the _queue_enqueue() function like this: _queue_enqueue(&log_queue, &health_record->QE) The Health Task is done with the health record since it just put it on the queue so the 'health_record' pointer should be set to NULL so the rest of your code doesn't try to use it. A valid health_record pointer is required for the next record of course and it is required now so its fields can be filled in as new messages arrive. Use the _mem_alloc_system_zero() function as was done before to allocate memory for another record. Currently, at the end of the while(1) loop all received messages are being passed on to the Display Task. However, we don't want to pass on messages of type LOG_TICK_MESSAGE so the code needs to be updated such that it only passes on the other types of messages. This could be done with a test for msg->MESSAGE_TYPE != LOG_TICK_MESSAGE condition, but to be more generic and accommodating of future message types that shouldn't be passed on to the Display Task it might be better for the handling of the LOG_TICK_MESSAGEs to free the message using the _msg_free() function and to set the 'msg' pointer to NULL. Then at the end of the while loop check that only non-NULL messages are sent to the Display Task. UPDATING THE UI TASK It is intended that the health records can be printed out using the UI Task. This is a menu driven interface and so we need to add a new command to read the log file. In UiTask.c add a case for the user pressing the letter 'l' (lower case L) for the log file and get the health record from the queue using the queue_dequeue() function. It can be a good idea to us a small function for items like this since there can be multiple places in your code that need to do a similar task. Such a function could look like this: HEALTH_RECORD  *      get_log_record(void) {       return (HEALTH_RECORD * )   _queue_dequeue(&log_queue); } Add print statements for the record number and all other data that is in the health record. Free up the memory of the health record once you're done with it to avoid a memory leak. This will cover the printing of the first record but the intention is for this UI command to print out all health records that are stored on the queue. Use a while loop to fetch records from the log using our new get_log_record() function until this function returns a NULL entry indicating that there are no more records to fetch from the queue. Note that the function returns a pointer to a HEALTH_RECORD, so a variable in the UI_TASK of this type will have to be declared. Compile and run your code. Use the user interface to request the health logs to be printed out and confirm that the data is correct. You can adjust the potentiometer and move the tower board to change the data. You may also want to comment out the print messages from the Health Task and Display Task so these messages don't interfere. Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here​.
View full article
This video presentation is the sixteenth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to the SPI Driver. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 16 Course Line Lab Outline Overview ofthe SPI Bus Using Multiple Slaves Clocking Modes Example of using the SPI bus to connect to a Barometer (MPL115A1) Interrupt version of the SPI Driver DMA version of the SPI Driver Driver initialization Overview of a typical SPI Controller Walkthrough of SPI Driver code Set up the Analog Board Enabling the SPI device Initializing the SPI device Write and ADC Read function Calling the ADC Read function Displaying the read results NOTE: This lab uses the Analog Tower Board P/N TWR-ADCDAC-LTC. This will require a variable voltage input ranging from 0v to +10Vdc to generate a signal that will be measured. First, watch the video for Session 16: SPI Driver​. Then, follow through with the interactive lab assignment below. SESSION 16: LAB ASSIGNMENT INTRODUCTION In this lab the SPI port is used to talk to an external ADC that is on the Analog Tower Board. You will notice some similarities to using the I2C bus, but you will also notice some differences. The Input Task will read the external ADC at the same time that it reads the internal ADC and it will print out the read value in order to verify that the code is operating correctly. OBJECTIVE The objective of this lab is to learn how the SPI driver is set up and used for communicating with a common peripheral. As well, understanding the differences between SPI and I2C, and having experience using both, will hopefully give you some insight as to which one might suit your application best if you are given the option to select one over the other. ASSIGNMENT SETTING UP THE ANALOG BOARD Before getting started there are some jumper settings to check on the Analog Tower Board (P/N TWR-ADCDAC-LTC available from Freescale). Ensure that the jumpers controlling the chip selects, Jumpers 14, 15, and 16, are installed from pin 1 - pin 2. This hard codes the chip selects needed to enable the LTC1859 ADC chip. In general it's easier to use the GPIO lines that go over the back plane connections via the card edge connectors, but the particular GPIO that we'd need on the K70 are already in use. Also ensure that jumper J30 is installed. This will connect the 5v power on the backplane bus to the board's 5v circuit. On terminal strip J27 (screw terminals at the edge of the board) is where the analog input is connected. We will use channel 0 of 8 analog inputs. The input goes to pin 3 (Channel 0) and the 0v reference for your analog input goes to pin 2 (common). The ADC will be set up for a maximum input of 10 vdc. ENABLING SPI We will need to enable SPI 2 in the BSP. Note that, if you are looking on the Analog board's schematic it is SPI 0 that is connected to the ADC chip, but through the backplane card edge connections it lines up with SPI 2 on the K70 tower board. Import the 'bsp_twrk70f120m' project into CodeWarrior if it isn't there already. Open the user_config.h file in the User_Config folder. Look for the define BSPCFG_ENABLE_SPI2 and set it to '1' (or to 'true' if you are using the table version of the user config) to enable this controller. Note that SPI2 is pre-configured to use the DMA version of the SPI driver. Save this file and recompile the BSP. INITIALIZING THE SPI The Input Task is where we read the switch status and the ADC that is on the K70 board, so this is a good place to put the code to read the external ADC. We will need two new functions that should go in InputTask.c, one to initialize the SPI2 device for the LTC1859 ADC that will be used, and one to read the ADC over the SPI2 channel. Starting with the init function, create a function called 'ltc1859_init()'. It will not need any parameters passed to it, and it will return a pointer to an MQX_FILE. Inside the function we will need a uint32_t variable called 'param'. This will be used by a number of ioctl calls to set up some internal parameters associated with the ADC. Also inside the init function you will need a file descriptor, which is a variable of type 'MQX_FILE *'. The first thing our function needs to do is to set the file descriptor (handle) to the SPI2 device by using the fopen() function. We used the fopen() function a fair bit when setting up the I2C in session 11 so you may want to refer to that. There are no parameters to pass, just specify "spi2:" as the device to open. If a non-NULL handle was returned from the fopen() call, a number of ioctl calls are done to set up the SPI channel in the way that the ADC needs it to be configured. The ADC uses mode 0 so the first call will set the SPI device for this mode. Set the 'param' variable to SPI_CLK_POL_PHA_MODE0 (which is a define in the BSP) and use the ioctl() function to configure the SPI2 device (via the file handle) to the param parameter using the command IO_IOCTL_SPI_SET_MODE. Use another ioctl() call in the same way to set the SPI2 device to Master Mode. The command is IO_IOCTL_SPI_SET_TRANSFER_MODE and the parameter to pass is SPI_DEVICE_MASTER_MODE. The final setting is for the baud rate. The default baud rate is 10 Mbps and the ADC device supports data rates up to 20 Mbps, but to show the use of this command we'll use it to set a data rate of 100 Kbps. Use an ioctl() function call with command IO_IOCTL_SPI_SET_BAUD and the parameter set to 100000. The last thing our function should do is to return the file descriptor (handle). The Input Task will need to call this function so declare a file handle of type 'MQX_FILE *' and in the initialization code of the Input Task call our new function (' ltc1859_init()' ) saving the returned file handle. ADC READING FUNCTION Our second function will be used to read the ADC. Call the new function 'ltc1859_read()'. The ADC returns a 16 bit value (which could be signed) so our function should also return an int16_t. It should receive a file handle of type 'MQX_FILE *' as well as a channel number since the ADC has 8 channels. The ADC must be instructed to do an analog to digital conversion so that is the first thing we need to do, but we will have to command it to do two conversions in order to ensure that we are using current data. Since SPI is full duplex, an ioctl() call is made for a read / write transaction and for this we need a read / write struct of type SPI_READ_WRITE_STRUCT. So declare a variable called 'param' of this type. The spi read / write struct has two buffers (one each for transmit and receive) so declare two arrays of uint8_t that are 2 bytes long, one called 'read_buffer[]' and the other called 'write_buffer[]'. If the passed in file handle is NULL the function should return an error condition (ie -1). If the passed in file handle is valid we can write to the ADC but first the write buffer needs to be populated with our command to perform a conversion. There are a number of items to set in the conversion command so set the first byte of the write buffer to: 0x80 | ((channel & 0x7)<<4) | 0x4 This will set ADC for a single input (as opposed to a differential input), set the channel number to the passed in channel, and it will turn on the gain. Set the second byte of the write buffer to 0x00. Set the elements of the read / write structure to be the read buffer, the write buffer, and the size of our two buffers like this: Param.READ_BUFFER = read_buffer; Param.WRITE_BUFFER = write_buffer; Param.BUFFER_LENGTH = sizeof(write_buffer); To initiate the writing of the command (and reading of the previous ADC conversion), use an ioctl() call of command IO_IOCTL_SPI_READ_WRITE and using read / write structure as a param. Next use the fflush() function since it will de-assert the chip select. Since we need to write the conversion command twice, repeat the ioctl() and the fflush() calls, but to ensure the conversion has completed before the command is sent for a second time, put a time delay of 1 msec in between the two conversion commands. Finally we can return with the read in conversion value. This is sitting in the two byte read buffer so this data will have to be combined like this: return ( read_buffer[0] <<8 | read_buffer[1] );           UPDATING THE INPUT TASK We want to read the ADC from the Input Task so declare an int16_t variable to store the returned result in. You may recall that the reading of the internal ADC (ie the potentiometer on the K70) by the Input Task is triggered by an event bit being set, and this event bit was set by a timer ISR that was set up in the lab for session 8. This would be a good place to put the code to read the external ADC. At the end of the code that is run when the ADC_EVENT bit is set, add in a call to our ltc1859_read() function and save it in the variable declared in step 27. A printf can be added for now to print out the value read, and for consistency it would be a good idea to send this value to the Health Task. Use the message code used for sending the internal ADC value but change the MESSAGE_TYPE to a new type called EXTERNAL_ADC_READ_MESSAGE. This type will have to be added as a new Application Message Type. Compile and run your code. Ensure that the display shows the ADC value and it changes as you adjust the input voltage. Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here​.
View full article
This video presentation is the twentieth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to Task Aware Debugging. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 19 Course Line Lab Outline Getting Started with TAD: the Task Summary window, Stack Usage window, Kernel Data window, and the Check for Errors window How the stack high water mark is determined Working with memory blocks and the Memory Blocks window Task synchronization windows: Message Passing, Semaphores, Mutexes, and Events Logging and the Log Summary window Example of debugging an issue with TAD Debugging a stack overflow Exploring the various TAD windows Creating a custom error code Creating a Stack Overflow condition Creating a new Memory Block Type and using it Observing Message Queues Using the Kernel Log to debug a sequence of events issue First, watch the video for Session 20: Task Aware Debugging​. Then, follow through with the interactive lab assignment below. SESSION 20: LAB ASSIGNMENT INTRODUCTION Task Aware Debugging (TAD) is a very powerful feature for gaining insight into the inner workings of your application. In many cases it is not practical, or too time consuming, to use a conventional method such as print statements to the console in order to efficiently debug your application. As you saw in the video, you can use TAD to obtain information about all of the kernel objects, stacks, interrupts, and the status of your tasks. You will now see in the lab how TAD can be used to debug a sequence of events bug that is in the application. OBJECTIVE The objective of this lab is to gain experience using the Task Aware Debugging (TAD) feature and to learn how it can be used to debug an application. ASSIGNMENT RUNNING THE APPLICATION Open your completed lab 19, or use the provided code with Lab 19 completed. Compile and download the code, and let it run for a few seconds. Click on the 'suspend' icon to pause the execution. Do not kill the application with the Terminate icon (which is a red square). LOOKING AT TASK WINDOWS Click on the MQX pull down menu and select the Task Summary from the MQX pull down menu. The task summary list should appear. Confirm that the tasks listed by name is the correct set of tasks for your project, check that the priorities are correct, note the various states that the tasks are in, and observe if any of the tasks have an error. Note that the Health Task has an error "MSGQ: Queue is not Open." This will be investigated later in the lab. Open the 'Ready Queue' window and confirm that the Active Task is listed at the top of the Ready Queue, and that any other tasks you saw in the Task Summary window with a status of 'Ready' show up below the Active Task in the Ready Queue window. You may only see one application task in this queue (along with MQX's Idle Task), or there ma be other tasks listed, it all depends on what the status was when you paused execution of the application. Open the 'Stack Usage' window and observe if there has been an overflow or if any tasks have come close to overflowing their stack. CREATING A NEW ERROR CODE Stop execution of the application and open main.h. Create a define for a new error code that will be used when the application has run out of memory and cannot log more data. The define should use a name such as 'USER_ERROR_LOG_OVERFLOW', and the value that you give this define should not conflict with existing defines for error codes. This can be achieve by putting your error codes at 0xffff0001 and above. In order to quickly observe that TAD will acknowledge this new error, open Uitask.c and force this error condition using the _task_set_error() function before the while(1) loop. Compile and run the application code for a few seconds. Pause the application and observe that the Ui_task has an 'Unknown error (0xffff0001)' listed in the 'Task Error Code' column of the 'Task Summary' window. To make this more meaningful though, it would be helpful if the error code had a custom name instead of 'Unknown error'. In order to find out what path TAD is using to get the error code information from, open the 'Check for Errors' window and expand the 'Show MQX TAD Diagnostics' listing. Look for the entry called 'Tad Path' to see where the TAD information is being retrieved from. Open this location in Explorer and create a new text file called 'user.tad' in this folder. Open the new file for editing and enter a title at the top that indicates that this file is an extension of the error codes and then enter the new error code with its definition. [strings:TASK_ERROR_CODE] 0xffff0001  = USER: Log Overflow Save the new file, terminate the application (if it is still being paused), compile, and then run the application for a short period. Now observe the 'Task Summary' window to ensure that the new error code shows up for the Ui_task. CREATING A STACK OVERFLOW Open the 'Stack Usage' window and observe the amount of the stack that has been used for the Ui Task under the '% Used' column. Open main.c and in the Task Template List change the amount of stack space allocated for the Ui Task to be just slightly under the amount being used. For example, if 54% of the stack was being used previously (which is 810 bytes of the 1,500 bytes allocated), then set the stack size in the Task Template List to be 800 bytes. Note that if you reduce the stack size too much there will likely be a crash of the application due to corruption of the memory that the stack for the Ui Task is overwriting. Terminate, compile, and briefly run the application. Ensure that an overflow is indicated for the Ui Task on the 'Stack Usage' window. Restore the stack size of the Ui Task to 1,500 and remove the _task_set_error() function from the Ui Task code. CREATING A MEMORY ALLOCATION TYPE CODE Open the 'user.tad' file that was created in step 11 and add an extension to the memory string types listing to include one for Log Entries. Note that memory type values are 16 bit, not 32 bit. To avoid a conflict with currently defined memory types, start with a value of 0xff01 or higher. [strings:MEM_TYPE] 0xff01  =  USER: Log Entry In main.h create a define called 'USER_MEM_LOG_ENTRY' that is assigned the value used in user.tad (ie 0xff01). Open the code for the Health Task and locate the _mem_alloc_system_zero() function call that is used to allocate memory for a new log (when processing messages of type 'LOG_TICK_MESSAGE'). If memory was successfully allocated, set its type to 'USER_MEM_LOG_ENTRY' using the _mem_set_type() function. In the initialization section of the Health Task use the same code for setting the memory type after the memory for first health record is allocated. Compile and briefly run the code. Open up the 'Memory Block' TAD window and at the very bottom you should find at least one memory block of type 'User Log Entry'. There will be one entry for each health record created and a new entry is created each second. If you continue running the application and let it go for a longer period of time you should see a larger list of memory blocks of type 'User Log Entry' in the 'Memory Block' window. Note that memory blocks can be expanded to get some additional information. Click on the '+' icon to the left of the address column for memory blocks of different types, as designated in the 'Type' Column. Part of what is shown is the block type, and if this block type references a TAD component, you can get further information on that component. For example expand a Message Pool Summary or Task Descriptor memory block. Open the 'Check for Errors' window. You should not see a Memory Manager error since the stack overflow issue has been corrected. OBSERVING MESSAGE QUEUES In the Health Task, when a message of type 'LOG_TICK_MESSAGE' is received memory is allocated for a new health record and then frees up the incoming message. Comment out the '_msg_free(msg);' line at the end of the processing of the message type. Compile and then run the code for about 5 seconds. Open the 'Task Summary' window and confirm that none of the tasks have an error condition (other than the Health Task which we'll debug shortly). Open the 'Message Pools' window. Click on the '+' to expand the details for the System Pool. Since the Health Task is not freeing up the message buffers when the tick messages are received, you should notice that the first group of buffers in the pool are 'Owned' and towards the bottom of the pool they are 'Free'. Run the application for a little while longer and then observe the System Pool. You should see that all of the buffers now are 'Owned'. Add the '_msg_free(msg)' function back into the code for the Health Task (it was commented out in step 25). DEBUGGING WITH THE KERNEL LOG In this section we'll use the Kernel Log feature in TAD to debug the scenario that was discussed towards the end of the video. Specifically we saw in the 'Task Summary' window that for the Health_Task, in the 'Task Error Code' column it said that there was a 'MSGQ: Queue is not Open' error. As you would imagine, this error is generated when a task wants to access a message queue but it hasn't been previously opened. However, before you can begin using the kernel log, you need to ensure that it is enabled and that the right information is being logged. Kernel Logging is enabled and set up at the top of the init section of the Health Task and the information to be logged is managed using the _klog_control() function. If you haven't touched this code since we added logging in session 10 there will be a variety of information set up to be logged, and with multiple types of log entries it can be harder to debug a specific issue. Our problem appears to be related to Message Passing, so we will need this information stored in the log and it is generally helpful to include context switch information to be able to track which task is active. In order to see the logs associated with message passing 'KLOG_MESSAGE_FUNCTIONS' will need to be included in the list, and 'KLOG_CONTEXT_ENABLED' will turn on logging for context switching. All of the other log types can be deleted from the _klog_control() function, or at least commented out for now. To ensure that the log won't run out of memory increase the memory allocated to the log to something like 64K bytes. This can be done when the kernel log is created using the _klog_create() function. It should look something like this: _klog_create(64*1024, 0); Compile and run the application for a few seconds, and in the Task Summary window confirm that the 'MSGQ: Queue is not Open' error condition for the Health Task is still present. Your job now is simply to figure out what the problem is that has caused the 'Queue not Open' error to occur. Yes, this is always much easier to say than to do, but a methodical approach of how your system runs and the order that things should be done in is generally a sound practice. The debugging process was started in the video and really you just need to continue along this trail, though perhaps not in so much detail. The whiteboard pop up below gives you a diagram that defines which tasks send messages to each of the message queues. It also lists the Message Passing API calls that are used in our application to help you identify what could be wrong or missing. The remaining steps of this lab will guide you through this thought process, and eventually it gives you the answer, but you are encouraged to at least make an attempt at this. Think about how you'd go about debugging this scenario if it were your own application and you had to solve this before you could progress with your development. Open the 'Kernel Log' window. As mentioned in the video, the Health Task would have started running first as it was the highest priority autostart task in the application. You should see in the log that the first thing done is to create the message pool. After that the Health Task's message queue was open, and then the _msgq_receive() function was called so the Health Task will block waiting for a message to come in. The Accel task starts to run (context switch to Accel Task), it allocates a message buffer, and then it sends its message to the Health Task. Since the Health Task was currently blocked waiting for a message to arrive in its queue it will become ready to run and, since it is the highest priority task, it then immediately become the active task (context switch to Health Task). You should see some context switches; (Accel Task, Temp Task, and Input Task). The Input Task allocates a message buffer, sends the message to the Health Task, and the Health Task immediately becomes active to read its message. So far so good - this all looks normal. By now you are getting a sense for the message passing interaction. Look a bit further down in your log and look for a sequence of logs relating to sending and receiving messages that doesn't make sense. The cheat pop up below will identify the area that is suspect. Look for yourself though before opening the cheat or reading further. In the sequence of logs identified, the Health Task is trying to send a message to a message queue but it cannot reach that queue. Since the message cannot be sent the message buffer is freed up. This is where the error we saw in the Task Summary Window is recorded. So why would you think that the receiving queue is not reachable, and what can be done about it? Answer: See the Cheat above for the reason why this is happening. One way to prevent it from happening is to have the Display Task send a message to the Health Task when it starts up to announce that it is up and running. Also, the Health Task shouldn't send any messages to the Display Task until it is confirmed to be running. The completed source code has this solution implemented so you can reference the relevant areas of the Health Task and the Display Task. Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here​.
View full article
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 How a Semaphore works Examples of using a Semaphore Binary vs Counting Semaphores What happens when a task is waiting for a Sempahore Posting a Semaphore Light Weight Semaphores and overview of the API Full Featured Semaphores and overview of the API Comparison between Semaphores, Events, and Message Passing Note: Temperature Sensor that is part of the TWR-SENSOR-PAK from Freescale is required for this lab. Set up the GPIO needed for the Temperature Sensor Add I2C code to the Temp Task to read the temperature Convert the value read to degrees Celsius Add a semaphore to protect access to the I2C bus Detecting an over temperature condition Displaying an over temperature condition First, watch the video for Session 12: Semaphores​. Then, follow through with the interactive lab assignment below. SESSION 12: LAB ASSIGNMENT INTRODUCTION 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. OBJECTIVE 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. ASSIGNMENT GPIO SETUP 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[0]<<2)&0x3fc) + ((data[1]>>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​.
View full article
This video presentation is the eighteenth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to Priority Inversion. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 18 Course Line Lab Outline What is Priority Inversion Example of a Priority Inversion in an embedded system Using Priority Protection to deal with a Priority Inversion Using Priority Inheritence to deal with a Priority Inversion Use of Semaphores and Mutexes to implement Priority Protection / Priority Inheritence Creating a new function that consumes a specified amount of time Causing the Input Task to run slowly Causing the UI Task to consume extra time Verify the priority inversion Adding Priority Protection to the Mutex Verify that a priority inversion does not occur First, watch the video for Session 18: Priority Inversion​. Then, follow through with the interactive lab assignment below. SESSION 18: LAB ASSIGNMENT INTRODUCTION You'll recall from the video that tasks of 3 different priority levels are required for a priority inversion scenario to occur. All of our tasks are quite short, as they should be, so to create the priority inversion we'll be artificially extending the time that a task takes to execute in order to create the symptoms of an inversion.If you aren't fully familiar with the conditions to create a priority inversion then it is suggested that you first watch the video for this session before continuing with the lab. OBJECTIVE The objective of this lab is to create a priority inversion and to verify that it is happening. Then, the priority protection features will be added to the Mutex being used and the results will be verified. ASSIGNMENT CREATING A FUNCTION TO WASTE A REQUESTED AMOUNT OF TIME Open the Task Template List in main.c and observe the different priority levels used. Since we need 3 tasks at different priority levels the highest priority task, the Health Task, is a good candidate to use, as is the lowest priority task, the UI Task. We're going to use the Input Task as our middle priority task that we'll modify to spend an inordinate amount of time running without blocking. To get the Input Task to run for an extended period of time we'll need a utility function that takes a long time to execute. Since this is throw away code we'll put it in main.c. Create a function called 'waste_time()' that accepts a 32bit number that represents the number of milliseconds the function is to consume. Declare a time start and time end variable of type MQX_TICK_STRUCT, a Boolean to indicate an overflow, and a uint32_t that will store the time delta. The first thing our function needs to do is to get the current time using the _time_get_ticks() function and then save it as our start time. In a DO loop, use the _time_get_ticks() function to save the current time into the end time variable. Then use the _time_diff_milliseonds() function to compare the two times, store an overflow in the overflow variable, and save the result in the time delta variable. The DO loop should continue while the time delta is less than the passed in number of milliseconds to consume. Since the Input Task will need to call the waste_time() function make it an extern. UPDATING TASKS TO CONSUME TIME For the purposes of our lab we'll pick on the ADC conversion code in the Input Task. When the ADC_EVENT bit is set and the internal and external ADCs are being read, add in a call to the new waste_time() function to consume 250 milliseconds. Also, to create the priority inversion we'll need the UI Task to consume a bit of extra time when the health logs are being read and printed. Update the Shell_log() function so that each time it goes through the while loop an extra 100 milliseconds are consumed using the waste_time() function. OBSERVING THE RESULTS Compile and run your code. Use the shell to print out a list of logs. If you do so reasonably soon after the system starts you may not see any issues with the log, though the user interface should appear to run slower. However, if you let the system run for 30 or more seconds before requesting a printout of the logs it should create the priority inversion while the Input Task is printing out the log. Once the log has stopped printing, wait 5 seconds or so and then print the log again. Each printout has a sequence number and the Health Records are logged every second, so before the priority inversion occurs the sequence number of each entry should match the number of seconds in the time stamp for the same entry (assuming that you haven't updated the time), or the time stamp may be one second less since the first entry could have been logged when only a fraction of a second has gone by. But if the priority inversion occurred during the first printing session of the logs, the Health Task will occasionally not be able to log an entry (even though it is the highest priority task) because it cannot get access to the mutex. See the Whiteboard for a diagram. Yes it does require you to create a specific scenario to generate a priority inversion, but this is similar to diagnosing any elusive, intermittent bug. It can be difficult to pin down what sequence of events are required to create a priority inversion, and it can be equally hard to understand what is happening, so better to avoid them in the first place! ADDING PRIORITY INVERSION PROTECTION To prevent this scenario from occurring, Priority Inversion Protection needs to be added and this is done in a Mutex Attribute Structure which is used to configure a Mutex when it is initialized. In the Health Task create a variable of type MUTEX_ATTR_STRUCT and a unit32_t that will store our priority level for the Mutex. Just before the Mutex is initialized in the Health Task, use the _mutatr_init() function to put the default attributes into the attributes structure. Use the _mutatr_set_sched_protocol() function to change the scheduling protocol to Priority Protection using the MUTEX_PRIO_PROTECT define. The priority for the Mutex needs to be set and to prevent a priority inversion from occurring the priority of the UI Task (who is locking the Mutex) needs to become higher than the priority of the Input Task (who is running for a long time and preventing the UI Task from running). In general, the priority of the Mutex should be set to the priority level of the task that you want to run (ie the Health Task), or higher. Use the _task_get_priority() function to find out the priority of the Health Task and save it into our priority level variable. Since this code is running in the Health Task, you are really wanting to get the priority of the calling task. For this you can use MQX_NULL_TASK_ID as the task id in the _task_get_priority() function to mean that you want the priority level of the calling task. Use the _mutatr_set_priority_ceiling() function to set the priority level in the attributes structure to the priority level returned in the previous step. Use the _mutatr_set_wait_protocol() function to set the queuing mechanism that this Mutex will use to MUTEX_PRIORITY_QUEUING. Finally, update the _mutex_init() function to use the attributes we just set in lieu of the default attributes. CONFIRMING THE RESULTS Compile and run your code. After the code has been running for a good while check the logs and confirm that the log entries did not fall behind like they did before. Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here​.
View full article
This video presentation is the seventeenth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to Mutexes. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 17 Course Line Lab Outline Overview of how Mutexes work Examples of how Mutexes are used How the queue of waiting tasks is managed Strict Mutexes Use of Mutexes to deal with priority inversion Atributes of Mutexes Data structures used with Mutexes API for Mutexes Mutexes vs Message Passing, Events, and Semaphores Declaring a Mutex Initializing a Mutex Locking and unlocking the Mutex in the Health Task Locking and unlocking the Mutex in the UI Task First, watch the video for Session 17: Mutexes​. Then, follow through with the interactive lab assignment below. SESSION 17: LAB ASSIGNMENT INTRODUCTION In this lab a mutex will be used to protect the logging that we added in the lab for session 13 on memory services. If you followed through the lab for session 13 you will recall that the Health Task was logging information into Health Records and then copying them onto a queue. When commanded to do so by the user, the UI Task would traverse the queue to read all of the Health Records and print them out. However, a corruption can occur during the time that the UI task is accessing the queue to retrieve and remove the Health Records if, at the same time, the Health Task is trying to add to the queue.The operation of mutexes is quite straight forward as you saw in the video, and given that we don't need to flesh out any more of our application at this stage this is a fairly short lab. OBJECTIVE The objective of this lab is to see the operation of a mutex to protect access to a resource, and to see how simple they are to implement. ASSIGNMENT DECLARING AND INITIALIZING THE MUTEX Since mutexes are an optional component of MQX you'll need to add the mutex.h include file to main.h. We'll need global access to our mutex so declare an external variable 'log_mutex' of type MUTEX_STRUCT in main.c where the other global structures are declared, and make this an extern in main.h. The mutex needs to be initialized which can be done in the Health Task at the same time the queue is initialized (with the _queue_init() function) using the _mutex_init() function. We'll just use the default values so you can use NULL as the mutex attribute struct. UPDATING THE HEALTH TASK In the while(1) loop of the Health Task where the LOG_TICK_MESSAGE message types are processed is where the Health Records are added to the queue using the _queue_enqueue() function. Since the job of the mutex is to prevent simultaneous access to the mutex, just before the record is logged is where we want to lock the mutex. And immediately after the Health Record has been added to the queue is where the mutex should be unlocked since we're done accessing the queue for now. Use the _mutex_lock() and _mutex_unlock() functions for this. UPDATING THE UI TASK The UI Task traverses the list of Health Records in the queue when the user requests that the logs be printed. This is done in the Shell_log() function. Use the _mutex_lock() and the _mutex_unlock() functions to protect the queue for the entire time that the list is being traversed. Compile and run your code. You first may want to comment out any periodic print statements. Use the 'log' function in the shell to see a print out of logged Health Records and ensure that a new record was being made every second. Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here​.
View full article
This video presentation is the fifteenth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to Time Services. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 15 Course Line Lab Outline How MQX uses time How MQX maintains time Time data structures Elapsed time vs absolute time Managing the elapsed time Maintaining the absolute time API of the Time Manager Conversions and calculations using time structures Using the time delay function How to get the current time Add the current time to each log of the health records Print out the logged time Add a shell function to set the current date Add a shell function to set the current time Print the time stamp with each health record Using time to measure an interval First, watch the video for Session 15: Time Services​. Then, follow through with the interactive lab assignment below. SESSION 15: LAB ASSIGNMENT INTRODUCTION This lab has two parts. In the first part we will update the application to fetch the current time and store it in each health record when it is logged. Since the application does not automatically know the absolute current time it will represent the running time until it is updated. Two new shell commands will be added then, one to update the current date and the other to update the current time. The logs will then contain the absolute current time.The second part of the lab demonstrates how to measure a time interval. In this case we'll measure the time it takes to run the ISR associated with switch 1 and print it out. OBJECTIVE The objective of this lab is to 1) learn how time is used and can be updated to represent the absolute time and 2) to learn how the time functions can be used to measure an interval such as the execution of a section of code. ASSIGNMENT ADDING A TIME STAMP TO THE HEALTH LOGS In order to add a time stamp to the Health Logs this data structure will have to be updated. In main.h, add the variable 'TIME' of type TIME_STRUCT to the HEALTH_RECORD data structure. We will need the current time when the health records are added to the queue (using the _queue_enqueue() function ). This is done in the Health Task when a LOG_TICK_MESSAGE has been received. Use the _time_get() function to fetch the current time and to store it in the TIME variable of the current health record. Since the current time is being logged in each record, the UI Task can now print this out by the Shell_log() function so it appears at the beginning of each record beside the record number. Compile and run your code and ensure that the log time is currently being printed out. Note that the time will start at 00:00:00. SETTING THE DATE AND TIME It would be helpful to be able to set the date and time so the health logs have a more meaningful time entry. This is best accomplished by enhancing the shell that we just implemented in the lab for the previous session. Start by adding two new function prototypes for shell functions in UiTask.c: Shell_set_date() and Shell_set_time(). Add two commands, 'date' and 'time' to the list of shell commands that are listed at the top of UiTask.c. Create the Shell_set_date() function. You might want to copy the Shell_log() function and make the appropriate changes instead of starting from scratch. The command for setting the date will be 'date YYYY/MM/DD', so a total of two arguments will be required. The sscanf() function can be used in a similar way to how it was used in the Shell_can() function to get the number of CAN tasks to create, however in this case you will be expecting 3 arguments to be processed, which will each be copied into a uint32_t. Note that updating the date is a bit more involved than you'd think at first. Remember that the absolute time is the Offset Time (time from Jan 1, 1970 to boot up time) + the Running Time. A change to the current date means that the offset needs to be adjusted which is done for you when you use the _time_set() function. So you will have to first get the current time, convert it to the DATE_STRUCT format, update the date, convert it back to the TIME_STRUCT and then set the current time based on this new value. The Whiteboard popup below gives you some additional info. Copy the Shell_set_date() function to create a Shell_set_time() function. The format to change the time will be 'time HH:MM:SS' but otherwise there is very little to change. Remember that you'll still have to get the current time (in TIME_STRUCT which is the seconds and milliseconds from Jan 1, 1970), convert it to the date format (which includes the hours, minutes, seconds), update the time, convert it back and save it. UPDATING THE SHELL_LOG() FUNCTION Since the user can now update the absolute date and time, it makes sense to update the printing of the logged records that is done by the Shell_log() function accordingly. Add a new printf statement in Shell_log() that will print out the absolute date and time based on the TIME saved in the record. The TIME in the record will have to be converted to the DATE_STRUCT with the time_to_date() function first in order to determine the YYYY/MM/DD and HH:MM:SS that the logged time equates to. Compile and run your code to ensure that everything is working. MEASURING AN ISR'S EXECUTION TIME The second part of the lab involves the measurement of time. One of the switch ISRs will be used, and these are located in InputTask.c. Getting the current time at the beginning of the ISR and then again at the end of the ISR code and subtracting the two is not truly representative of the time to execute the ISR because it includes the overhead associated with getting the current time. So first we should measure what this overhead time is and then subtract it when we calculate the ISR execution time later. When determining an overhead it's a good idea to take several measurements and average them to get a more accurate number. Create a for loop in the initialization section of the Input Task that will loop for something like 16 times. Call the _time_get_ticks() function twice in a row in the for loop. Subtracting the two times will give the overhead associated with calling this function once. Use _time_diff_nanoseconds() function to calculate the difference and add it to an 'overhead' variable to calculate a running total of the measured overheads. You will need to declare a Boolean for an 'overflow' variable that the time diff function requires, and you will also need to declare two variables ('isr_start' and 'isr_end'), both of type MQX_TICK_STRUCT, for the _time_get_ticks() function to write to. After the for loop divide the total overhead by the number of times through the for loop to determine the average overhead. In the sw1_isr() function at the top of InputTask.c use the _time_get_ticks() function at the beginning of the ISR to save the current time into 'isr_start' and use the _time_get_ticks() function again at the end of the ISR to save the current time into 'isr_end'. Subtract these two times to get the ISR execution time and save it to a global variable. In the while(1) loop of the Input Task print out the execution time whenever the switch 1 event bit has been set. Remember that the exeution time will be the time measured in the ISR less the average overhead time calculated in step 13. Compile and run your code. Verify that the switch 1 ISR execution time is calculated and printed out. Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here​.
View full article
This video presentation is the ninth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to the LWADC Driver, and will learn to configure and read ADC input. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 9 Course Line Lab Outline Analog signals and uses cases for ADCs How ADCs work Driver walk through Attributes of ADCs Configuring ADCs Reading the ADC Scaling the ADC output Initializing an ADC device Initializing an ADC input Reading an ADC input Scaling an ADC reading Reading an average of the ADC input First, watch the video for Session 9: LWADC Driver​. Then, follow through with the interactive lab assignment below. SESSION 9: LAB ASSIGNMENT INTRODUCTION In our previous lab we used a light weight timer to set an event bit every 100 msec and whenever this event bit was set the Input Task would send a message to the Health Task with the reading from the potentiometer. The problem however was that we hadn't yet covered the ADC and so the potentiometer value sent was 0.Now that you know how to initialize an ADC device and it's input it's now time to update this part of our application to include the actual potentiometer value in the message that is sent by the Input Task. OBJECTIVE The objective of this lab is to learn about the lwadc driver by adding code to read the potentiometer and send the value to the health task.This objective will be accomplished by: Initializing the ADC driver Initializing the ADC input Periodical reading of the ADC Changing the scaling so a voltave between 0 and 12000 millivolts is displayed Using _lwadc_read_average New functions/ structures you will use:      LWADC_STRUCT, LWADC_VALUE, __lwadc_init(), _lwadc_init_input(), _lwadc_read(),_lwadc_set_attribute(),_lwadc_read_average() ASSIGNMENT INITIALIZE THE INPUT We saw in the video that there is a separate structure of type LWADC_INIT_STRUCT for each of the ADC Devices. These are located in init_lwadc.c which is in the 'BSP_Files' folder of the BSP project. It would be a good idea to review this file to get familiar with this structure. Just below where you initialized the switches (with the init_switch() function) in InitTask.c use the lwadc_init() function to initialize ADC 1. ADC 1 will be used because the potentiometer is connected to ADC 1 on the tower K70 board. We'll see this in a moment. ADC 1 is the default ADC controller in this BSP and there is a define for this in the board specific BSP file (twrk70f120m.h) called BSP_DEFAULT_LWADC_MODULE. So you can use this define to point to the ADC1 init structure or more directly use 'lwadc1_init'. Note that the extern for BSP_DEFAULT_LWADC_MODULE is missing in some versions of MQX so you may need to add an 'extern const LWADC_INIT_STRUCT BSP_DEFAULT_LWADC_MODULE' at the top of InputTask.c. This will initialize the ADC device but the next step is to initialize the data structure for the input to this ADC Device. This is done with the lwadc_init_input() function. Don't forget to declare a structure of type LWADC_STRUCT. As with all inputs, you will find in twrk70f120m.h, a define for the ADC input from the potentiometer. It is called BSP_ADC_POTENTIOMETER and it is set it to (ADC1_SOURCE_AD20). This defines the ADC Device that it is connected to and the pin muxing. READING THE INPUT Now we're ready to read the input. You will recall that in the endless loop of our Init Task it will wait indefinitely for either a switch or ADC event bit to be set, and once one of these bits has been set the appropriate action is taken. We added the setting of the ADC event bit in the last lab with the use of a timer. Currently when this bit is set the Input Task will send a message to the Health Task but we don't have any real data to send so our job is to add that data now. Before sending the message the Input Task should use the _lwadc_read() function to get the setting of the potentiometer. You will need a variable of type LWADC_VALUE for the reading from the ADC to be written to. Update the data field in the message to the Health Task to be the read value from the ADC. Since the _lwadc_read() function returns TRUE if the read is successful and FALSE if the read was unsuccessful it would make sense to update your code to only send a message if the read from the ADC was successful. TEST THE ADC Compile and run your code. On the print out you should see the Health Task and Display Task message ADC messages being displayed continuously (officially once every 100 msec because that's the period of our timer that triggers the reading of the ADC). If you change the setting of the potentiometer on the K70 tower card the value printed out should change and the readings should range from 0 (or close to it) to 3300 (or close to it) to represent 3300 millivolts. CHANGING THE SCALING AND READING METHOD As you saw the input wasn't scaled at all, it simply showed you the voltage (in millivolts) that the ADC was reading. But you may want to scale the input to a range that is more meaningful. Instead of having the input range from 0 to 3300 we want to scale this so it ranges from 0 to 12000. This could represent a temperature that goes up to 1,200 degrees (in tenths of a degree) or a voltage range that goes up to 12 volts (measured in millivolts). Use the _lwadc_set_attribute() function to change the scaling. Try this on your own, if you need a hint use the whiteboard. Change the reading of the ADC to return an average value over 8 reads. This requires the _lwadc_read_average() function. TESTING THE SCALING Compile and run your code. Confirm on the output that the range of the potentiometer is now from 0 to 12000. Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here​.
View full article
This video presentation is the eigth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to light weight timers, timer queues, and the timer task. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 8 Course Line Lab Outline One shot versus periodic timers Use cases for timers Working with correlated timers Writing timer ISRs Light weight timers and timer queues Timers and the timer task Creating a LW Timer Setting timer queue period Adding a timer to the queue Creating a timer ISR Accessing events from a timer ISR Triggering tasks from a timer ISR First, watch the video for Session 8: Light Weight Timers​. Then, follow through with the interactive lab assignment below. SESSION 8: LAB ASSIGNMENT INTRODUCTION Though timers can be used in a great variety of ways, in this lab we'll implement a fairly straight forward, but typical, use case for a timer. A light weight timer will be used to periodically inform the Input Task of when to read the ADC and send a message to the Health Task. In keeping with our philosophy of keeping ISRs short, the timer ISR we implement will leverage the event group that was implemented in the lab for the previous session. OBJECTIVE The objective of this lab is to learn about lwtimers by having the input task respond to an event that is periodically generated by a lwtimer.This objective will be accomplished by: In the input task, creating a lw timer period and a lwtimer timer Creating the timer function wheich generates an event Modifying the input task to handle the new event. New functions/structures you will use:      LWTIMER_PERIOD_STRUCT, LWTIMER_STRUCT, _lwtimer_create_periodic_queue, _lwtimer_add_timer_to_queue ASSIGNMENT CREATING THE TIMER LW Timers is an optional component so lwtimer.h must be included for any file that will be accessing this feature. This is most conveniently added to main.h that all files have included. Prior to creating a timer queue and a timer to put in that queue we need two structures, one of type LWTIMER_PERIOD_STRUCT and one of type LWTIMER_STRUCT. These don't need to be global and can be declared at the top of the Input Task. The lwtimer periodic queue needs to be created which can be done with the _lwtimer_create_periodic_queue() function. It would make sense to create this in the Input Task just after the lwevent group is created. The periodic queue should have a period of 10 Hz, however the period needs to be expressed in system ticks so this number will have to be converted. The number of ticks / second for the system is defined in the board specific header file of our BSP, in this case twrK70f120m.h. In there you will see a define for BSP_ALARM_FREQUENCY which is set to 200. For a period of 10 Hz then our timer should be expiring then every 1/10th of a second, or every 20 ticks. To write code that is easier to follow and more transportable it is better to define the period in your function call as 'BSP_ALARM_FREQUENCY/10'. In our case there is no point to have a delay before the timer queue starts so this can be left at 0 in the _lwtimer_create_periodic_queue() function call. Now that the timer queue has been created you can add a timer to that queue using the _lwtimer_add_timer_to_queue() function. Reference the structure of type LWTIMER_STRUCT that you already declared and select an offset of 0 ticks. A timer ISR function to be called when the timer expires is required so you can select a function name now for this ISR (eg: 'adc_timer_isr') and we'll write this ISR shortly. We won't need a parameter to be passed to our ISR so you can set this parameter to 'NULL'. CREATING THE TIMER ISR Add a new ISR to InputTask.c using the name you defined when the timer was created in the previous step. This ISR will be called when the timer expires every 1/10th of a second. Like the switch ISRs the timer ISR will set an event bit to indicate that the timer expired. Add a new define that specifies which bit in the event group will be used for this indication, which of course should be a different bit than the two used for the switches (eg 0x00000004). This define can be placed just below the define for the switches in InputTask.c Like the switch ISRs the timer ISR should use the _lwevent_set() function to set the bit reserved for the ADC Timer and that's all we'll use the timer ISR for. Don't over think it, it's a one line function. UPDATING THE INPUT TASK In the next lab we'll set up the Input Task to read the ADC value when the timer expires. But since the ADC interface hasn't been covered in the course material yet we'll skip that step and simply send a message to the Health Task to indicate that the timer has gone off. The Input Task will need to unblock when timer expires so the _lwevent_wait_ticks() function should be updated accordingly. Since we will be adding a new message type for indicating that the ADC timer has expired our list of available message types will have to be updated. In main.h locate the APPLICATION_MESSAGE_TYPE_T enum and add a new type called ADC_READ_MESSAGE. The handling of the ADC timer expired bit needs to be added to the Input Task. You can copy the message passing code from the switch handling section and paste it in a new section that will handle the ADC timer expiry. Be sure to update the message type to ADC_READ_MESSAGE. You can leave the data field set to 0. UPDATING THE DISPLAY TASK The Health Task passes all messages on to the Display Task and we should update the display task to indicate that the timer is working. For now we'll toggle the state of the blue LED (which is LED[3]). Add the handling of an ADC_READ_MESSAGE in the Display Task. When this message is received, use the _lwgpio_toggle_value() function to change the state of the blue LED. TESTING YOUR CHANGES Compile and run your code. You should see the blue LED changing state rapidly, ie every 1/10th of a second so it will flash on 5 times per second. The Timer Expired message should be seen continuously printing as well. Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here​.
View full article
This video presentation is the seventh installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to various features of light weight events and full featured events. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 7 Course Line Lab Outline Reasons for using synchronization Overview of events Working with light weight events Working with full featured events Ways to wait on an event Various synchronization traits and comparison between events, LW events, message passing, and LW message passing Creating an Event Group Controlling event bits from an ISR Updating a task to wait indefinitely for an event First, watch the video for Session 7: Light Weight Events​. Then, follow through with the interactive lab assignment below. SESSION 7: LAB ASSIGNMENT INTRODUCTION As mentioned in the videos we want to keep interrupt service routines (ISRs) as short as possible. Currently the ISRs associated with switch presses send messages directly to the health task and it would reduce the over head of these ISRs to just post an event to indicate that a switch was pressed and let the Input Task deal with the sending of messages. This lab will walk you through the changes required to use light weight events to make this update and you'll see events in action with both auto clearing bits and non-auto clearing bits. OBJECTIVE The objective of this lab is to learn about lwevents by having the switch ISRs signal the input task using lwevents.This objective will be accomplished by: Creating a lwevent in the input task Setting the lwevent in the switch ISRs Having the input task wait on the lw event and send a message to the health task Experimenting with using autoclear events vs. non- autoclear events New functions/ structures you will use:      LWEVENT_STRUCT, _lwevent_create, _lwevent_wait_ticks, _lwevent_get_signalled, _lwevent_set, _lwevent_clear ASSIGNMENT CREATING THE EVENT GROUP Light weight events (LW Events) is an optional component so prior to being able to use them you need to include lwevent.h. Add this to main.h since all of our files include this header file. The code in the Input Task will need access to the LW Event structure so in InputTask.c add a global variable of type LWEVENT_STRUCT. The LW Event will have to be created so in the initialization section of the Input Task you need to call _lwevent_create(). In this case we do not want the flags to auto clear. Since we are using events to indicate the pressing of the switches we need to specify the specific bit being used for each switch. At the top of InputTask.c create a define that specifies that bit 0 of the event group will indicate the status of switch 1 (ie a define set to 0x0000001) as well as a define that specifies that bit 1 of the event group will indicate the status of switch 2 (ie a define set to 0x00000002). UPDATING THE ISRS To reduce the overhead of the two ISRs they will set the corresponding event bit in lieu of sending a message to the Health Task. The body of the Input Task will do send the messages to the Health Task - we'll get to this part in a moment. For now you can remove the message sending code from the ISRs and replace it with the _lwevent_set() function. The ISR for switch 1 will set bit 0 (using the define we created in the previous step) and the ISR for switch 2 will set bit 1 using the respective define. UPDATING THE INPUT TASK Since the input task is only responding to a press of one of the switches there isn't anything for it to do until this happens. The most efficient way to handle this is to have the Input Task wait indefinitely until one of the event bits of interest have been set. This can be done using the _lwevent_wait_ticks() function. Both event bits will need to be specified with the call waiting on either of the bits to be set, not both, and in order for the call to wait indefinitely specify a timeout of 0. Once a switch has been pressed the Input Task will need to know which switch was pressed in order to know how to respond. It can determine this by using the _lwevent_get_signalled() function. This function returns a 32 bit number that indicates which bit in the event group caused this task to become unblocked. You should save this returned value in order to process it later. In response to a switch press the Input Task will simple send a message to the Health Task that either switch 1 or switch 2 was pressed. The returned value from the _lwevent_get_signalled() function can be tested if bit 0 or bit 1 has been set to determine which switch was pressed and the corresponding message can be sent. The code for sending a message should still be available as we commented it out in the lab for session 6. When a switch is pressed the read value is 0 so we don't need to get the input level on the GPIO pin and we don't need the code to monitor the switches for changes of state, simply send a message with the appropriate message type and a data value of 0. Since the event group was not set up to be auto clearing the bit that unblocked the Input Task will still be set and it should be cleared using the _lwevent_clear() function. TESTING YOUR CHANGES Compile and run your code. Verify that the operation of the system is the same as it was at the end of the lab for session 6, meaning that the green LED(LED [2]) flashes and that is switch 1 is pressed the orange LED (LED[0]) will go on and if switch 2 is pressed the orange LED will go off. You should also see appropriate messages being printed out on the console. Put a break point in the Input Task code on a line just after the _lwevent_wait_ticks() call. Press switch 1 on the tower board and when the break point is hit use task aware debugging to view the lightweight events status window. Bit 0 should be set in the event group. Run the code and press the other switch on the tower card. The task aware debugging window should show only bit 1 is set. Remove your break point and update the creation of the lwevent group so the bits are auto clearing by using the LWEVENT_AUTO_CLEAR flag in the _lwevent_create() function. Then remove the _lwevent_clear() function calls and confirm that the system behaves in the same way that it did before. Put a break point back in the same location that you did previously and confirm that despite a switch being pressed the bits are auto clearing and that neither bit 0 or bit 1 are set in the lightweight events status window. Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here​.
View full article
This video presentation is the sixth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to interrupts, the scheduler, and the ISR table. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 6 Course Line Lab Outline Interrupts and the scheduler Techniques for writing an ISR Hardware Vector Table Nested Interrupts MQX Interrupt ISR Table Installing ISRs Default Interrupt handler Creating an init function Initializing an interrupt Installing and enabling interrupt Creating ISRs Modifying the LED control First, watch the video for Session 6: Interrupts​. Then, follow through with the interactive lab assignment below. SESSION 6: LAB ASSIGNMENT INTRODUCTION Effective interrupt handling is a critical part of most any embedded system. There are a few steps you have to learn in order to set up and use interrupts but taking the time to understand these steps are well worth the benefits. In this lab we'll replace the code that polls the switches to monitor for changes in state with interrupt driven code. The reaction the system has to switch presses will be updated to reflect what we'll need them to do for our application. OBJECTIVE The objective of this lab is to learn how to handle interrupts in MQX by converting the polled switch handling to interrupt driven switch handling.This objective will be accomplished by: Initializing interrupt handling on lwgpio switch inputs Installing an isr for each switch Enabling lwgpio interrupt in GPIO device Enabling lwgpio interrupt in Interrupt controller device Writing ISR for switch interrupt that sends a message to the health task New functions/ structures you will use:      lwgpio_int_init, _int_install_isr, lwgpio_int_enable, _int_install_isr, lwgpio_int_get_vector, lwgpio_int_enable, _bsp_int_init, lwgpio_int_clear_flag ASSIGNMENT REORGANIZING SWITCH INITIALIZATION First we need to do a bit of re-organization of the code to facility the changes we are going to make. Encapsulate the initialization of each switch into a function because there is a lot of common code to initialize the GPIO, set the functionality, and set the attribute. Create a new function called init_switch() and add it to InputTask.c. Your function should have the following parameters: A pointer of type LWGPIO_STRUCT_PTR in order to reference the switch The associated pin that this switch is connected to (type LWGPIO_PIN_ID) The muxing value (type uint32_t) Move the code you had in the initialization section of the Input Task that you used to initialize, set the functionality, and set the attribute of the of the GPIO and put this code into your new function. The parameters used will be the passed in parameters. In the initialization section of the Input Task where you removed the initialize, set the functionality, and set the attributes function calls put in a call to your new init_switch() function using the appropriate parameters that your function is expecting. You will need to call init_switch() twice, once for each switch. Compile and run your application to confirm that it operates as it did before. Nothing has functionally changed, but it's a good opportunity to check everything before we move on. Ensure that the green LED is flashing and that pressing sw 1 turns on the orange LED and that pressing sw 2 turns on the yellow LED. SETTING UP THE INTERRUPTS Now we need to set up the GPIO to be interrupt driven. This will require access to the ISR for the interrupt from each switch. This means our init_switch() function will need a fourth parameter which is a pointer to our ISR, which will be of type INT_ISR_FPTR. In the init_switch() function, after the set up code we have there already we need to initialize the interrupt using the lwgpio_int_init(). The input from the switches is active low so the mode we'll use for these inputs is LWGPIO_INT_MODE_FALLING. If you recall, during the set up we defined that the internal pull ups should be used and that a closed switch will connect the input to ground. So having the interrupt triggered when the input is falling syncs up with the switch being pressed. The next step is for init_switch() to install an interrupt service routine (ISR) that will be called when the falling transition is detected on one of the switch inputs. This is done using _int_install_isr() function. As you saw in the video you need the vector number for the vector assigned to that pin. You can get the vector number using the lwgpio_int_get_vector() function. A pointer to the ISR is the parameter we added two steps ago but we also need to pass the ISR a parameter of the GPIO that triggered the interrupt so the ISR can clear the GPIO / interrupt. Next the interrupt needs to be enabled and this needs to happen in two places, in the GPIO device and in the Interrupt Controller. Enabling an ISR in the GPIO device is done using lwgpio_int_enable() function. Enabling the interrupt in the Interrupt Controller is done using the _bsp_int_init() function. This function requires the vector number which can be retrieved in the same way that was used two steps ago when installing the ISR. You also pass with this function the priority level and the priority sub level. The last parameter is the enable flag that you'd set to TRUE. Where init_switch() is called from the Input Task you'll need to add in the last parameter which is your ISR. You can't use the same ISR for both switches of course so you'll need two different ISRs; 'sw1_isr' and 'sw2_isr'. CREATING THE ISR FUNCTION Now we're ready to write our ISR so we need to create the 'sw1_isr' function in InputTask.c. An ISR takes a standard pointer as a parameter but we really need this to be of type LWGPIO_STRUCT_PTR so the first thing to do is to create a variable of type LWGPIO_STRUCT_PTR and set it to the passed parameter type cast to this same type. The first thing an ISR needs to do is to clear the flag associated with this interrupt. Failing to do so will cause you to immediately re-enter the ISR again as soon as you exit it. This is done with the lwgpio_int_clear_flag() function and we need to pass it the pointer to the LWGPIO_STRUCT that we just sorted out in the previous step. The only other thing we want to do with our ISR is to send a message to the Health Task that a switch was pressed. You can copy all of this code from the body of the Input Task. Don't forget to declare a pointer of type APPLICATION_MESSAGE as that will be needed by the code. Since this is the ISR for switch 1 the MESSAGE_TYPE of our message should be SW1_MESSAGE and since we are only getting this interrupt when the switch is pressed the DATA field of our message can be set to 0. Create the sw2_isr function to behave in the same way that sw1_isr does, but for switch 2 of course. Since these functions are almost identical it is probably easiest to just start with a copy of sw1_isr and make the appropriate changes. Since our handling of the switches is now interrupt drive we don't need the code in the Input Task that was polling the switches. You can comment out all of the polling code and message passing code in the while(1) loop of the Input Task and just leave in the time delay. Compile and run your code. Confirm that you see a single message from the Display task each time either of the switches are pressed. CHANGING THE REACTION TO SWITCH PRESSES You may have noticed however that the first time the switches are pressed the corresponding LED goes on, but it never goes off. This is because we were using the release of the switches to turn the LEDs off and the ISRs are only reacting to pressing of the switch and not the release of the switch. This could be fixed by implementing additional ISRs that are activated by a low to high transition of the switch inputs, however the purpose of the switches isn't to control the LEDs so it would be better to update the functionality of the switches and the LEDs. Our intention is to use switch 2 to clear an alarm status and since the orange, yellow, and blue LEDs (LEDs 0, 1, and 3 respectively) are used to indicate alarm situations these LEDs should all be turned off when switch 2 is pressed. Switch 1 will be used to simulate a security input that indicates an alarm event and so the blue LED will be used to represent that status. Since the Display Task is to handle all print outs and LEDs we'll update the code here. When a switch press message is received from switch 2 (message type SW2_MESSAGE) then the orange, red, and blue LEDs should be set to high (LWGPIO_VALUE_HIGH) using the lwgpio_set_value() function to turn the LED off. To control the activation of the security input update the response in the Display Task to a message of type SW1_MESSAGE should be to turn on the orange LED (LED 0) to low (LWGPIO_VALUE_LOW). Compile and flash your code to the board. Each press of switch 1 should turn on the orange LED and if the orange LED is on a press of switch 2 will turn it off. Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here​.
View full article
This video presentation is the fifth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to MQX driver architecture, how drivers are initialized and used, and walk through two very popular drivers - serial, and light-weight GPIO. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 5 Course Line Lab Outline Driver Architecture Block vs. Byte modes POSIX drivers Low level drivers Polled vs. Interrupt modes Driver Initialization I/O Subsystem (POSIX) Walkthrough of the Serial Driver Polled vs. Interrupt Walkthrough of the Light-weight GPIO Driver Lab Assignment Initializing GPIO Setting the functionality for the inputs Monitor the inputs Updating the user interface Configuring the outputs Controlling the outputs First, watch the video for Session 5: Introduction to Drivers​. Then, follow through with the interactive lab assignment below. SESSION 5: LAB ASSIGNMENT INTRODUCTION The lightweight GPIO (LWGPIO) driver is used for monitoring inputs and controlling inputs. Though the concept is quite straight forward there are a number of structures and functions you need to use with this driver. In part this is to keep your code easily portable should you change processors and in part because of the complexity of modern processors such as Kinetis. OBJECTIVE The objective of this lab is to implement the switch and LED functionality.This objective will be accomplished by: Adding code to the Input Task to poll inputs from switches On a change of state for one of the switches, sending a message to the health task who passes it on to the Display Task Adding code to the Display task to set the orange LED to reflect the state of sw1 and the yellow LED to reflect the state of sw2 Toggling the state of the green LED every second New functions/ structures you will use:      LWGPIO_STRUCT, LWGPIO_PIN_ID, lwgpio_init, lwgpio_set_functionality, lwgpio_set_attribute, lwgpio_get_value, lwgpio_set_value, lwgpio_toggle_value ASSIGNMENT INITIALIZING THE INPUTS We will be focusing on messages related to GPIO for the time being so to avoid confusing our results with extra messages we'll disable the messages from the Accel Task and the Temp Task. You can either comment out the sending of messages from these two tasks or after they print out a message to say that they've started permanently block the task. The job of the Input task is to monitor the inputs but so far the inputs have been simulated by periodically sending a place holder message. This can now be replaced by only sending a message when the state of either switch 1 or switch 2 on the K70 tower card changes state. If you recall, in the lab for another session we defined all of the message types in main.h in an enum. We added Input_Message as the message type from the Input Task but now that we'll have two types of messages, one for switch 1 (sw1) and one for switch 2 (sw2). Delete Input_Message from the list and add SW1_Message and SW2_Message. In the Input Task we'll need to declare a few variables of different structure types associated with LWGIO. At the top of the function where the variable declaration is done add variables sw1 and sw2 both of type LWGPIO_STRUCT. We also need to know the value of sw1 and sw2 so declare variables sw1_value and sw2_value of type LWGPIO_VALUE. Finally, to retain the previous value of each switch we'll need two more variables of type LWGPIO_VALUE called sw1_last_value and sw2_last_value. In order to use an I/O on the processor it has to first be initialized to define how you want to use it. In the initialization section of the Input_Task use the _lwgpio_init() function to initialize the input pin connected to sw1 and again to initialize the input pin associated with sw2. The ID for each pin is defined in the BSP for the card you are using, in this case twrk70f120m.h which can be found in the BSP_Files folder of the BSP project. Since these we are using these pins as inputs we can use an initialized value of LWGPIO_VALUE_NOCHANGE. We also have to set the functionality for each pin and _lwgpio_init() doesn't set the actual pin mux register, meaning that we haven't defined that the associated pins on the processor should actually be used as IO. Use the _lwgpio_set_functionality () function to define the associated pins as inputs. The defines for the different functionality options for the IO pins are defined in the same BSP file (towrk70f120m.h), ie BSP_SW1_MUX_GPIO is used to set pin D0 the internal signal for sw1 and BSP_SW1_MUX_GPIO associates pin E26 with the internal signal for sw2. Since the circuits on the tower board for the switches don't have pull up resistors you need to enable the internal pull up resistors using the lwgpio_set_attribute () function. MONITORING THE SWITCHES First thing to do in the endless loop of the Input Task is to read the value of the switches using the _lwgpio_get_value() function and store the result in sw1_value and sw2_value. If the read value is not the same as the previous value, it must have changed so this is when we send a message to the Health Task. Copy the code we had before for sending a message. Update the message type (either SW1_MESSAGE or SW2_MESSAGE) and instead of sending data of 0 like we did before we can send the switches value (sw1_value or sw2_value). We are detecting switch state changes by comparing the current value to the last value so now that we've detected a change the current value should be copied to the last value. If you haven't thought about this already we could get a false change of state detection on power up if sw1_last_value and sw2_last_value are not initialized. Since the read value will be a 1 when the switch is not pressed it would be a good idea to pre-initialize the last value to a 1 (LWGPIO_VALUE_HIGH). Change the time delay in the endless loop to 100 ms to make sure we catch every press of a switch. UPDATING THE DISPLAY Since we are now sending actual data in our messages it would be good to have the Display Task print out the switch state when an update is received. To do this update the printf in the Display Task to show this parameter as well which is being passed in the data field of our message. Compile and run the code. INITIALIZE THE LEDS As was done in the Input Task we will need to initialize the GPIOs that control the LEDs, but of course they will now be configured as outputs. In the Display Task declare a variable of type LWGPIO_STRUCT for each LED. Since we have 4 LEDs that we want to control it may be more convenient to use an array like this "LWGPIO_STRUCT leds[4];" We also need to keep track of the IDs (eg BSP_LED1) for each LED which are defined in the twrk70f120m.h file. If you using an array for the LWGPIO structures it would make sense to use an array for these as well. In the initialization section of Display Task we need to initialize the IO pins associated with the LEDs in a similar way to how it was done for the switches. However we now want to set the direction as being an output (LWGPIO_DIR_OUTPUT) and the initial value should be high with a value of high (LWGPIO_VALUE_HIGH) because we want the output of these pins to reflect the value of the corresponding switches and those are defaulted to high (remember the pull ups?). As was also done with the IO for the switches we need to set the functionality for the corresponding pins so they are configured as IO. You'll need the define from the BSP header file again (eg: BSP_LED1_MUX_GPIO) Since there are 4 LEDs to initialize you could consider using a for loop for the initialization to keep things cleaner. Compile and run the code. Our last requirement is to toggle the state of the green LED (LED2) every second. This can be accomplished with a 1000ms timeout to the msgq_receive()function in the Display Task. This function will return NULL when the timeout expires, otherwise it returns a pointer to the received message. Toggling an output can be done by keeping track of the current setting and using the lwgpio_set_value() function or more simply by using the lwgpio_toggle_value() function. Compile and run the code to verify that the switch processing still works and that the green LED is toggling. Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here​.
View full article
This video presentation is the fourth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will learn about synchronization techniques for managing data, task flow, and resources. You will be introduced to the various synchronization options available with MQX RTOS including events, semaphores, mutexes, and message passing. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 4 Course Line Lab Outline Synchronization Explanation Data Flow Control Flow Mutual Exclusion Synchronization Options Events Semaphores Mutexes Message Passing Message Passing Types of Message Pools Message Pool Creation Sending and Receiving Light-weight Message Passing Creating the message types Creating a message structure Adding message queues and a pool of buffers Sending messages Reading and displaying messages First, watch the video for Session 4: Synchronization and Message Passing​. Then, follow through with the interactive lab assignment below. SESSION 4: LAB ASSIGNMENT INTRODUCTION In this lab we will implement our first type of synchronization - Message Passing. We will also see the data flow of our application begin to take shape as 3 of our tasks (Accel, Temp, and Input Tasks) will be using Message Passing in order to send data to the Health Task. OBJECTIVE The objective of this lab is to understand message passing and implement the message passing as outlined in (diagram of message passing between tasks).This objective will be accomplished by: Adding code to Health Task to send a message to Display task with health status Setting up Message Passing from Temp Task to Health Task Setting up Message Passing from Accel Task to Health Task and Theft Task Setting up Message Passing from Health Task to Display Task Setting up Message Passing from Input Task to Health Task Setting up bi-directional Message Passing between Health Task and CAN Task Ensuring that when each task starts, it sends a "Message from Task x" message to the tasks it talks to. When the Health task receives a message from a task, it should forward it to the Display task who will print it. New functions/ structures you will use:       _msgq_get_id, _msg_alloc_system, _msgq_send, _msgq_open, _msgq_receive, _msg_free, MESSAGE_HEADER_STRUCT ASSIGNMENT HOUSEKEEPING Since we will be using message passing you need to include message.h in main.h. Message Passing does not stipulate a structure for the messages and as far as it's concerned it's just sending a packet of bytes, so you need to define a structure for your messages. In our message structure the receiver will need to know who the message was sent from so we should have an entry for the 'message type'. An example of a message type is a report of the current temperature or a report of the current accelerometer data. The message type could simply be a 32 bit entry with a unique number used for each message type and each type is defined using #define statements. However it's a bit less error prone to use an enum structure to ensure that each message type gets a unique number. You'll see this technique used throughout the labs. Create a define for the following message types: TEMP_MESSAGE, ACCEL_MESSAGE, INPUT_MESSAGE, CAN_MESSAGE, TIMER_MESSAGE, ISR_MESSAGE, HEALTH_MESSAGE, UI_MESSAGE, DISPLAY_MESSAGE. Using an enum. Create an instance of this enum called 'APPLICATION_MESSAGE_TYPE_T' Message structures always start with a MESSAGE HEADER STRUCT and typically you follow that by the message type. The last thing we need in our message structure is a data field and for now we'll just use a single 32 bit entry for our data. Create a structure to define the format of our messages and create an instance of this structure called APPLICATION_MESSAGE. Each task that will receive a message will need a queue and each message queue needs a unique ID. Create the queue ID numbers for the Temp, Accel, Input, CAN, Health, and UI tasks (ie use defines INPUT_QUEUE, CAN_QUEUE, DISPLAY_QUEUE, and THEFT_QUEUE). Again an enum should be used Somewhere in your system you need to create the system pool of message buffers and this needs to be done before anyone tries to use one of the buffers. So it makes sense that this goes in the initialization code of the highest priority task. Use the msgpool_create_system() function to do this. Use a message size of "sizeof (*msg)" where msg is a pointer to the message structure APPLICATION_MESSAGE. A size of 10 buffers should be adequate and there should be no reason for the size of the pool to grow. INITIATING MESSAGES (ACCEL TASK, TEMP TASK, INPUT TASK) Each task that sends a message to Health Task will need to get the ID of the queue for the Health Task so it can send messages to it. This can be done using the msgq_get_id() function. The body of theses sending tasks then will be to do the following: Wait for a time delay to space out it's messages allocate a message using msg_alloc_system(), populate the target queue ID of the receiver (ie the Health Task), populate the message type (eg ACCEL_MESSAGE), set the message data (can be set to 0 for now since we don't have any real data to send. This will be updated later. Send the message using msgq_send() Repeats the above process The time delay each task waits between sending a message will vary based on the desired frequency for each type of information. The accelerometer data is important to get on a fairly frequent basis so perhaps a time delay of 500ms would be best here. The temperature won't change very quickly so a delay of 5,000ms would work best here and the voltage reading being done by the Input Task can be set to 2,000ms. PROCESSING MESSAGE (HEALTH TASK) Since the Health Task will be receiving messages it needs to open the message queue which can be done with msgq_open () function. And since it will be sending messages to the Display Task it will need to know the queue id of the Display Task's queue which it can get with the msgq_get_id() function. The body of the Health Task then uses msgq_receive() to fetch a message from it's queue (which is a blocking function), prints out a message to say that it received a message with the message number, passes the message on to the Display Task using msgq_send(), and then waits for the next message to come in. DISPLAYING THE MESSAGES (DISPLAY TASK) As was done with the Health Task, the Display Task will need to open its message queue before it can read messages and this is done with the msgq_open() function. The body of the Display Task will block on msgq_receive(), once a message is received it will print a message to say that it received a message with the message number, frees up the message so it can be returned to the system pool using msg_free(), and then waits for the next message. VERIFY RESULTS Compile and run your application. We should be receiving all 3 types of messages at both the Health Task and the UI Task, but now each at the same frequency because we used different time delays. However, you will only be getting a small number of messages at the Health Task and then the printouts will stop. Why do you think this is? Pause the application and look at the MQX Task Summary window in TAD for a clue. 14. Fix the problem and re-run your application. Verify that both the Health Task and Display Task print out the messages of all 3 message types (Accel, Temp, and Input). Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here​.
View full article
This video presentation is the third installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to the MQX task scheduler. You will learn how tasks are scheduled and how to select a scheduling policy and set task priority levels for your system. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 3 Course Line Lab Outline OScheduling Policies Priority-based Time-slice (Round Robin) How the Scheduler Works Selecting Priority Levels for Tasks Task Template List Using Tasks in your System Implementing priority based scheduling Understanding the scheduler: Round Robin vs Priority based scheduling Adding commands to the user interface SESSION 3: LAB ASSIGNMENTFirst, watch the video for Session 3: Task Management and the Scheduler​. Then, follow through with the interactive lab assignment below. INTRODUCTION In the lab for session 1 we were using round robin scheduling and we saw how tasks can share the CPU time efficiently. In this lab we'll switch to priority based scheduling and learn about how to share CPU time efficiently using this scheduling policy. We then get to finally start adding some more code to our application and the first task we'll start with is the User Interface task. Here we'll add in an interface over the serial port where the user specifies how many instances of the CAN Task there should be in the system. The interface will be enhanced to allow the user to destroy, abort, and restart the CAN Tasks giving you experience with the task creation API. OBJECTIVE The objectives of this lab are to: Understand the task template list Create tasks dynamically using _task_create Use and understand the differences between _task_destroy, _task_abort and _task_restart These objectives will be accomplished by modifying the UI task to accept the following commands: Create command - create the specified number of CAN tasks. Each CAN task should be started with a parameter that uniquely identifies the instance of the CAN task. Destroy command - destroys all CAN tasks. Restart command - Restarts all CAN tasks. The CAN task should be modified to: Install an exit handler Print an initial message when it starts Print a periodic message (different than the initial message) once a second New functions/ structures you will use:      _time_delay, _task_create, _task_destroy, _task_abort, _task_restart, _task_set_exit_handler ASSIGNMENT IMPLEMENT PRIORITY BASED SCHEDULING Currently all tasks are set to priority level 9 which invoked Round Robin Scheduling and now we'd now like to switch over to priority based scheduling. Re-organize the priorities of the tasks to have high priority tasks, medium priority tasks, and low priority tasks. When assigning tasks to relative priorities keep in mind that user information tasks are typically low priority because a user won't notice or care if the update to a display is delayed by a fraction of a second. On the other hand, more time critical tasks need to be high priority to ensure that they are as responsive as possible. Think about the priority level you'd assign for each task and then look at our list of suggested priorities. Update the priority levels in the Task Template List to reflect your selected priorities. Remember that a lower number is actually a higher priority level and that it will unnecessarily use more memory if you leave gaps in your priority levels. Assign the highest priority tasks to level 9, medium to level 10, and low priority tasks to level 11. Run your code and observe the output on the terminal. Pause the execution and observe the Ready Queue in TAD. UNDERSTANDING THE SCHEDULER With Round Robin Scheduling we saw that a schedule yield from the active task allowed the next task in the ready queue to run. That doesn't work though when you have tasks at different priorities and the only way a higher priority task will yield the CPU to a lower priority task is to enter a blocked state. So our use of a time delay before wasn't such a bad idea after all. Change the sched_yield call in the Health Task to a one second time delay and see what happens. You may have to put a break point on the time_delay in the Health Task in order to catch its printout. We now have a means for the high and medium priority tasks to run so we need to extend this to allow the low priority tasks to run as well. Since we currently don't have a means of unblocking from any of the blocking calls other than a time delay we'll have to stick with that for now, but these will all eventually be replaced. Update all tasks now to use a one second time delay in lieu of the schedule yield. Now see what results you get. UPDATING THE USER INTERFACE Now we finally get to add some meat to one of our tasks. The next step is to upgrade the UI task to accept from the user a command to either create CAN Task (by entering "c"), destroying all CAN Tasks (by entering "d"), or by getting help on the available commands (by entering "h"). If the user wants to create CAN Tasks they will enter the number of task to be created (up to a max of 10). The task_create() function will be used to create the CAN Tasks. Here are some additional requirements: Task Creation: When you create a CAN task pass to it an ID number. For example, the first CAN task will get ID number 0, the next one will get ID number 1, and so on. In order to confirm the results update the CAN task to print a message to say that it has been created and once a second it should print a message identifying which CAN task it is. In order to avoid the periodic printouts from the other tasks interfering with this interface you can comment out their printf statements or move them to be above the while (1) loop. Finally, since the UI Task will be creating the CAN tasks, they should not be auto generated at MQX start up so you need to reflect this in the Task Template List. Task Destroy: To verify the operation of this command use a printf to output a message that identifies which CAN Task is being destroyed. Help: Print out a series of messages to identify the available commands. For example "c - create CAN Tasks", "d - destroy CAN Tasks". To try out your changes create a number of CAN Tasks using your new interface and then pause the program. Check that the requested number of CAN Tasks exist in the task summary window in TAD. Delete all CAN Tasks using your interface and ensure that they are removed from the Task Summary window. Now add an additional command to your UI to allow the user to abort the CAN Tasks instead of destroying them by entering in an 'a'. The abort function allows the task being terminated to run an exit handler before termination so we'll add an exit handler to the CAN Task. The exit handler is a separate function outside of the can_task() function and it should be put in CanTask.c. For now all that the exit handler will do is print out a message that the task is shutting down. In order to access the exit handler the CAN Task will need to install the exit handler in its initialization code using the task-set-exit-handler() function. Run your code and verify that when the abort command is entered all CAN Tasks will print out the shutting down message but they don't print this message when the destroy message is used. Finally, add one more command to your UI to restart the CAN Tasks by entering an 'r'. In order to see when a task has started add a printf in the initialization section of the CAN Task code that indicates that the task has just started with the ID parameter passed to the CAN Task. Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here​.
View full article
This video presentation is the first installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to multi-tasking, blocking calls, and switching. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 2 Course Line Lab Topic Overview of a Multi-Tasking Environment Super Loop Programming Limitations Task Coding Structure Task States What is a blocking call? Task Content Switching Updating the structure of each task Cooperative sharing of the CPU time Working with time delays Working with schedule yield First, watch the video for Session 2: Designing for a Multi-Tasking Environment​. Then, follow through with the interactive lab assignment below. SESSION 2: LAB ASSIGNMENT INTRODUCTION Now that we have our application outlined it's time to organize the code into a structure that is appropriate for a multi-tasking environment. For one thing we'll need the application to run indefinitely instead of doing a small task and terminating. As well, having multiple tasks running in parallel requires a degree of cooperation and synchronization between the tasks. This is a topic that will be visited at several points in the course but for starters we'll look at a couple of options and see the impact they have on performance. OBJECTIVE The objective of this lab is to understand the interaction of multiple tasks running indefinitely.This objective will be accomplished by: Converting each task into the proper format Exploring ways for tasks to share the CPU ASSIGNMENT UPDATING THE STRUCTURE OF EACH TASK You can either continue on with where you left off after the session 1 assignment or replace you files with the provided solution for lab 1. If you decide to use the provided solution it is suggested that you compile and run this code first to make sure that it is operational on your machine. Since in a real system tasks typically run indefinitely the code for each task should be modified so they print their unique message continuously. Once these changes have been made compile and run the application. Observe the print out. Is this what you expected? Why do you think this happened? COOPERATIVE SHARING OF THE CPU TIME Change the Health Task such that it does a 1 second time delay after it's printf. Compile and run the application. What impact did you observe? Put a breakpoint on the printf in the Display Tasks and pull up the Task List in Task Aware Debugging (TAD). Does this explain the results? Since the use of a time delay allowed the next task to run, putting a 1 second time delay after the print statement in all tasks should allow for better cooperation. Try this and re-run the application. Did you get all print messages? If so, what is the downside to this method ensuring shared use of the CPU? Try replacing all of the time delays with the schedule yield function (_sched_yield() ). Run your code and compare the results.           Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here​.
View full article
This video presentation is the first installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to the MQX architecture, learn how MQX is initialized, and understand how to schedule and prioritize tasks. This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner. Session 1 Course Line Lab Topic Creating tasks Setting priorities Scheduling Synchronization concepts Introduction to drivers Creating the environment Creating source code Understanding results First, watch the video for Session 1: MQX Architecture and Initialization at the Freescale Website. Then, follow through with the interactive lab assignment below. SESSION 1: LAB ASSIGNMENT INTRODUCTION This lab walks you through the basics of starting a new project from scratch and then creating all of the tasks that we'll need for our application. For now the tasks will just print out a unique message but getting the environment set up and the shell of our application in place is an important first step. OBJECTIVE The objective of this lab is to download and run your first MQX application on target hardware. This objective will be accomplished by: Using the CodeWarrior New project wizard to create an MQX project Adding the stubs for all of the required tasks Modifying the task template to autostart all of the tasks New functions/ structures you will use:       Printf, _task_block, TASK_TEMPLATE_LIST ASSIGNMENT CREATING THE ENVIRONMENT Create a new MQX project. When asked about which libraries will be used you should add the shell library to your project. The code will run from flash, be sure to use "int flash debug PnE Multilink". Import the BSP, PSP, and Shell for the tower K70 board and re-build these libraries. We now need to add .c files for the source code for each task. To do this we need a separate file for each task so in the source code folder you'll have HealthTask.c, DisplayTask.c, etc. At the end you'll have the new 8 files plus the main.c and main.h files created when the project was created. In CodeWarrior a new file should be added by right clicking on the source folder and selecting new > source. This will create a new file with a minimal amount of code to get you started including a function that prints a message to the default output stream and then aborts. Update the name of each function to be the function associated with this file (eg: Temp_task). If the tools you're using don't fill in a minimal amount of code when you create a new file create a simple 1 line function that prints out a message. /* * TempTask.c * Created on: Feb 6, 2014 * Author: Embedded Access Inc */ #include "main.h" /*TASK*---------------- * Task name : Temp_task * Comments : * This task prints "Hello World" *END*----------------*/ void Temp_task(uint_32_t initial_data) { printf("\n Temp: Hello World\n") } We'll need a function prototype for each of our new functions which can be added in main.h /* Example function prototype */ extern void Healt_task(uint32_t); Since we'll be using the printf () function you should include the stdint.h header file at the top of main.h. For a few reasons we will need a unique ID number for each task but of course just using a number is confusing and error prone so it's more practical to use a define with each set to a unique number. You can use a series of #define statements to do this but of course you can inadvertently assign two defines to the same number. This case can be avoided by using an enum structure and you'll see that throughout the course. Find the '#define MAIN_TASK 1' in the autogenerated code and replace it with an enum defining all 8 tasks HEALTH_TASK, DISPLAY_TASK, etc). To create the tasks you need to update the Task Template List found in main.c. The Template List will be covered in a later session of the course but essentially it is a table identifying all of the tasks that you'll have in your application and it provides MQX with some information about each task. The project generated source includes a starting task template list with a single task called "main_task." You don't need the main_task but you can use this entry as a guide for entering in the entries you need. Be sure to leave the entry of zeros in the template list to demark the end of the list. Create all 8 tasks that we'll have in our application: Health Task, Display Task, Accel Task, Temp Task, Input Task, CAN Task, UI Task, and Theft Task. All tasks should be set to priority 9, have a stack size of 1500, and be auto starting. /* EXAMPLE TASK TEMPLATE LIST ENTRY*/ {HEALT_TASK,  Healt_task,  1500,  9,  "Health_task",  MQX_AUTO_START_TASK}, Below the Template List in main.c you'll see the autogenerated code for a main_task. We don't have a task called main in our system so this can be removed. For each of our new tasks we need update the code a bit. Change the printf to 'printf("\n Function: Hello World \n");'. In your code, 'Function' will be the actual name of function in this file (e.g. Health, Display, Temp, etc). Replace the mqx_exit with the _task_block() function. Be sure to update any comments to reflect the name of this function. Also, each function should include the main.h header as this is where we'll have some defines and the function prototypes. Run the application to confirm that each task's message is printed out onto the console. Your print out souhld look like this Review the order of the print statements and explain why they came out in the order that they did. Explanation of the Result The tasks will be created and run in the order that they are listed in the Task Template List (TTL). So, the order of the print outs you see should match the order of the tasks listed in the TTL. Later in the course we'll look at what happens when tasks are at different priority levels, but for now, since all tasks are at the same priority level, they will start and run in the order that they are created in. Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here.
View full article