Session 19: Flashx Driver

Document created by Gabriela Godinez Employee on Jun 30, 2016
Version 1Show Document
  • View in full screen mode

This video presentation is the ninteenth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to Flashx Driver.

This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner.

Session 19 Course LineLab Outline
  • Overview of the main types of flash memory
  • NAND flash vs NOR flash
  • Erasing and writing to flash
  • Initializing the Flashx Driver
  • How the Flashx Driver accesses flash
  • Structure of the Flashx Driver
  • Code walkthrough of the Flashx Driver
  • Creating a new System State Structure
  • Using a Mutex to protect access to the flash
  • Creating a flash reading function
  • Creating a flash writing function
  • Creating a function to get the next record in flash
  • Updating the contents of the System State Structure
  • Creating a new shell command to print the entries in flash

 

First, watch the video for Session 19: Flashx Driver.

Then, follow through with the interactive lab assignment below.

 

SESSION 19: LAB ASSIGNMENT

INTRODUCTION

Well, we've saved the longest lab for the end, but it will be worth the slog in order to have a completed application and to gain some experience writing and reading from flash. It is quite common to use the flash for logging system status information given how important this information is to you as a developer, but also to your customers who you may enable access to retrieve operational metrics, or perhaps to adjust operating parameters.

OBJECTIVE

The objective of this lab is to complete the application that we've been working on since session 1, and to gain experience with writing and reading to flash.

ASSIGNMENT

ENABLING THE FLASHX DRIVER

    1. Before getting into the code, we should verify that the flashx driver has been enabled. Open user_config.h for your BSP and ensure that the define BSPCFG_ENABLE_FLASHX has been set to 1. If it was not previously set, re-compile the BSP project.

CREATING THE ALARM STRUCTURE AND DEFINES

    1. Since we will be recording a 'set of data' for each alarm it will need to follow a defined structure. This structure is the first thing that we're going to create. In main.h add a new structure called SYSTEM_STATE_STRUCT. The contents of the structure will be the same as the HEALTH_RECORD structure except we do not need the QUEUE_ELEMENT_STRUCT or the sequence NUM. What we do need are the elements for the TIME , TEMP, Input Voltage (MV), and the accelerometer data (X, Y, Z).
    2. The items replicated by the two structures can be replaced in the HEALTH_RECORD_STRUCT with the SYSTEM_STATE_STRUCT with a variable called 'STATE', like this:
      SYSTEM_STATE_STRUCT          STATE 
    3. Add a uint32_t field called ALARM to the SYSTEM_STATE_STRUCT since we will be using this structure to record alarms.
    4. Flash has a minimum write size (8 bytes) and it is more efficient to be writing data to flash if the data is a multiple of 8 bytes. The TIME_STRUCT is an 8 byte structure and so we have a total of 3 sets of 8 bytes with two bytes left over. So to make it a multiple of 8 bytes we need to add an array of uint8_t called 'reserved[]' that is 6 bytes long.
      cheat.png
      Cheat 19-1.png
    5. Since the purpose of writing to flash is to log alarms it would help to have some defines for the different alarm types that will be generated. At the top of main.h, create a define for over temperature, motion detection, and a voltage alarm like this:
      #define   ALARM_OVER_TEMP      1 
      #define   ALARM _MOTION        2
      #define   ALARM_VOLTAGE        4

ADDING A MUTEX TO PROTECT THE FLASH

    1. Multiple tasks will need access to the flash and for the same reasons that we used a Mutex to protect access to the queue of health records we'll use a Mutex to control access to the flash. In main.c, declare a 'flash_mutex' in the same way that the 'log_mutex' was declared. This will need to be an extern in main.h.
    2. In the initialization section of the Health Task initialize the flash_mutex using the _mutex_init() function.
    3. To prevent a priority inversion situation it would make sense to update the parameters of flash_mutex to enable priority protection. Since we have a variable of type MUTEX_ATTR_STRUCT already with priority protection enabled (done in the previous lab) we can re-use that instead of creating a new one. Make sure that the initialization of flash_mutex done in step 8 is located after the code that sets up the Mutex attributes, and update this function to use our set of attributes.
      cheat.png
      Cheat 19-2.png

CREATING A FLASH READING FUNCTION

    1. In order to read from flash we'll need a flash_read_log() function. It should receive a passed in file handle of type 'MQX_FILE *', the entry number to read, and a pointer to the SYSTEM_STATE_STRUCT of where the record should be written to. Since there can be an error in accessing flash it should return an int32_t result so the calling code knows if the read was successful or not.
    2. Use the fseek() function to locate the requested record. You need to pass in the file handle and the size of each record. You also need to tell it to start from the beginning of the block of flash using the IO_SEEK_SET define:
      fseek(flash, flash_entry*sizeof(*system_state), IO_SEEK_SET); 
    3. Use the read() function to read the record that we have just seeked to. You will need to pass the file handle, the type of structure to read (type cast to a char pointer), and the number of records to read.
      read(flash, (char *) system_state, sizeof(*system_state)); 
    4. Since we are done with accessing the flash now the Mutex should be unlocked.
    5. Next we should check the number of bytes read, which is given as the return of the read() function. If the number of bytes read is not equal to the size of our system_state structure a -1 should be returned to indicate an error.
      cheat.png
      Cheat 19-3.png
    6. Our function needs some addition code though because in some cases it is helpful to know if data was actually read, or if a blank record was read (ie all fields were 0xff). To do this we need a blank record to compare the read record to.
    7. Declare a variable of type SYSTEM_STATE_RECORD and then use the memset() function to set all fields to 0xff.
      memset((char *)&erased_record, 0xff, sizeof(*system_state) ) 
    8. Use the memcpy() function to compare the read record with the blank one. If a blank record was read (memcpy returns 0) then the flash_read_log() function should also return a 0.
      Memcmp( (char )system_state, (char *)&erased_record, sizeof(*system_state)) 
    9. If a blank record was not read, then the flash_read_log() function should return a 1 to indicate that it was successful.
      cheat.png
      Cheat 19-4.png
    10. This function will be used later by the UI Task so its function prototype should be added to main.h as an extern.

CREATING A FLASH WRITING FUNCTION

    1. We will need to create a flash_write_log() function that will write a single record to flash at a specified entry point. The parameters passed into this function can be identical to the parameters passed into the flash_read_log() function. It will also return an int32_t to indicate success or an error.
    2. Similar to what was done in the reading function, if the flash entry point was >= 0, the code should lock the Mutex and then use fseek() to get to the specified record.
    3. Instead of using the read() function however we will use the write() function which uses the same parameters.
    4. After the write the Mutex should be unlocked.
    5. One other difference over the read() function though is that we want to update the time stored in the system_state structure before the write to flash is done. Use the _time_get() function to update the TIME field of the system state structure.
    6. If the write() function returned a number of written parameters that is not equal to the size of a SYSTEM_STATE_STRUCT then a -1 should be returned to indicate an error. Otherwise a 1 should be returned to indicate success.
      cheat.png
      Cheat 19-5.png

CREATING A NEXT ENTRY FUNCTION

    1. Our application will require a function for finding the next entry in flash. Create a function called flash_next_entry() that receives a file handle of type 'MQX_FILE *' and returns the usual int32_t for error status.
    2. You will need to declare a variable of type SYSTEM_STATE_STRUCT and two int32_t variables, one for the result returned from a function call and one to retain the record number of where we are at. The record number should be initialized to 0.
    3. Create a short while loop that will increment the record number we are checking while a call to flash_read_log() returns a 1 (ie a non-blank record was read).
    4. If a blank record was found then this record number should be returned. If it wasn't (ie no blank records were found in the block of flash) then a -1 should be returned to indicate an error.
      cheat.png
      Cheat 19-6.png

UPDATING THE HEALTH TASK

    1. The flash is organized into 4 equal sized banks and our code and data will be stored in these banks. The breakdown of where the code and data go is defined in the linker file which is getting beyond the scope of what was intended to be covered in this session, so for now we'll locate our data in the 3rd bank because we know our application (including MQX) starts at the base of bank 0 and it's fairly small, so it won't be interfering with bank 3.
      whiteboard.png
      Whiteboard 19-1.jpg

    2. Much of the writing to flash will be done by the Health Task since it knows when an alarm has been generated. Open the Health Task and declare a file handle called 'flash' of type 'MQX_FILE *'.
    3. In the initialization section of the Health Task use the fopen() function to open a file handle to bank3 of the flash.
    4. When writing to flash you need to know where in the accessible area you want to write to. This requires some mechanism to keep track of where the last record was written to. For this we'll need an int32_t in the Health Task for our flash entry point.
    5. As well, we will be writing to flash data that is organized as records of type SYSTEM_STATE_STRUCT so you should declare a variable of this type called 'system_state', and to ensure that we start with a clean structure initialize it in the declaration to NULL, ie: set it = {0} .
    6. Currently when a message of type ACCEL_MESSAGE is received the accelerometer data is stored into a health record. We're going to reorganize things here a bit in terms of how the health record is filled in, so update the code for processing messages of type ACCEL_MESSAGE to just update the system_state structure like this:
      case ACCEL_MESSAGE: 
      system_state.X = msg->X;
      system_state.Y = msg->Y;
      system_state.Z = msg->Z;
      break;
    7. Do the same thing for the processing of messages of type ADC_READ_MESSAGE.
      cheat.png
      Cheat 19-7.png
    8. Currently the Health Task does not process messages of type THEFT_MESSAGE so that code needs to be added. When a Theft Message is received, update the ALARM field of the system_state structure using the define we created 'ALARM_MOTION'. The ALARM field may have other alarms so don't over write the current value in the ALARM field, OR this define into it.
    9. When a Theft Alarm Message has been received we want to log this condition so use the flash_write_log() function to write the system_state to flash at the current entry point. Don't forget to increment the entry point so the next log goes after this one.
      cheat.png
      Cheat 19-8.png
    10. The processing of messages of type TEMP_MESSAGE will also need to be updated. The received temperature should be stored in the system_state record and if the received temperature was above the set threshold the ALARM field of the system state record should be ORed with the ALARM_OVER_TEMP define. Then write this alarm to flash after a message has been sent to the Display Task. The TEMP in the health record no longer needs to be set.
      cheat.png
      Cheat 19-9.png
    11. Switch 2 on the tower board is used to reset all of the LEDs on the K70 tower board and it would be a good idea if it also cleared the alarm states. Add a case for processing messages of type SW2_MESSAGE. When a SW2_MESSAGE is received the ALARM field of system_state should be set to 0. The Input Task is already set up to forward SW2 messages to the Health Task.
      cheat.png
      Cheat 19-10.png
    12. In step 3 we modified the HEALTH_RECORD structure by creating a variable called STATE of type SYSTEM_STATE_STRUCT. Consequently a bit of code needs to be updated, in particular the call to the _time_get() function in the processing of messages of type LOG_TICK_MESSAGE:
       _time_get(&health_record->STATE.TIME) 

UPDATING THE UI TASK

    1. Similar to what was done in the previous step, the UI Task has a few references to structure fields that need to be updated. In the Shell_log() function the data stored in the Health Record are printed out and the reference to these needs to be updated. EG, change '->TIME' to '->STATE.TIME'.

HOUSE KEEPING

    1. Now that we have implemented a logging feature there is a functional change required to our code. The Theft Task currently sends a message to the Display Task when motion is detected. This should be routed to the Health Task instead since this is where the logging will be done, and the Health Task forwards all received messages to the Display Task anyway, so a Theft status can still be displayed. Modify the Theft Task so it sends its message to the Health Task instead of the Display Task. You will have to update the _msgq_get_id() function call and the setting of the target queue id for the message.
    2. A call to waste_time() was put in the processing of ADC event bits in the Input Task. This was only used to create priority inversions and is no longer required so it should be removed.
    3. The same should be done for the waste_time() call in the Shell_log() function.

CREATING A NEW SHELL COMMAND

    1. The last main feature to add is the ability to print out the records that were logged to flash. Create a new Shell Command called 'flash' and create a corresponding function called 'Shell_flash()' that is called when the user types 'flash' as a command.
    2. When creating the shell_flash() function it is quickest to start with a copy of the Shell_log() function and make the appropriate changes to it so that is what is assumed in the next few steps, or you can type in a new function looking at the others for reference if you prefer. You will need to declare an instance of SYSTEM_STATE_STRUCT, an int32_t for keeping track of the record number we are at (initialized to 0), and a file handle to the flash of type 'MQX_FILE *'.
    3. If the correct number of parameters are received (ie argc == 1) then use the fopen() function to initialize the file handle in the same way that this was done in the Health Task.
    4. If the file handle is NULL, print out an error message, otherwise locking of the mutex, printing of the fields of the structure, and the unlocking of the mutex should be done. Be sure to change the mutex referenced from 'log_mutex' to 'flash_mutex'.
    5. The while loop that controls the printing shouldn't be reading Health Records however so this should be changed to be calling the flash_read_log() function and continue while this call returns a 1 (meaning a non-blank record was read).
    6. Remove the printf statement used to print out the record number since the System State Structure does not have a field for this.
    7. The current time (from _time_to_date() ) should update the system_state.TIME field and not the health_record->TIME field.
    8. In the printf statements, update the reference to the logged data - for example, change 'health_record->X' to 'system_state.X', etc.
    9. Add a line to print out the value of the ALARM field which is a hexadecimal value.
    10. Make sure that the record number index is incremented each time through the while loop.
      cheat.png
      Cheat 19-11.png
    11. Finally, the function prototype for the Shell_flash() function should be used at the top of UiTask.c to declare it as a static function.

UPDATING THE SHELL_LOG() FUNCTION

    1. In the Shell_log() function there are references to HEALTH_RECORD structure elements that need to be updated. Update the health_record pointers to fields TIME, X, Y, Z, TEMP, and MV to show these are part of the STATE structure. For example, change 'health_record->X' to 'health_record->STATE.X'.

VERIFYING THE RESULTS

  1. Compile and run your code. If you type 'flash' to call our new shell command there should be no subsequent print out. However, if you first generate an alarm condition, such as a motion alarm, and then use the flash command you should get a print out of one or more records that were logged to flash.
    results.png
    Results 19-1.jpg

Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here.

Attachments

    Outcomes