Summary
This article expands the functionality of the Tilt Database by adding functions to get the status of Tilts from other threads in the system. It demonstrates the FreeRTOS queue’s to provide a generic mechanism for communication between threads.
This series is broken up into the following 12 articles with a few additional possible articles.
Tilt Hydrometer (Part 1) Overview & Out-of-Box
Tilt Hydrometer (Part 2) Architecture
Tilt Hydrometer (Part 3) Advertising Scanner
Tilt Hydrometer (Part 4) Advertising Packet Error?
Tilt Hydrometer (Part 5) Tilt Simulator & Multi Advertising iBeacons
Tilt Hydrometer (Part 6) Tilt Simulator an Upgrade
Tilt Hydrometer (Part 7) Advertising Database
Tilt Hydrometer (Part 8) Read the Database
Tilt Hydrometer (Part 9) An LCD Display
Tilt Hydrometer (Part 10) The Display State Machine
Tilt Hydrometer (Part 11) Draw the Display Screens
Tilt Hydrometer (Part 12) CapSense
Tilt Hydrometer: LittleFS & SPI Flash (Part ?)
Tilt Hydrometer: WiFi Introducer (Part ?)
Tilt Hydrometer: WebServer (Part ?)
Tilt Hydrometer: Amazon MQTT (Part ?)
Tilt Hydrometer: Printed Circuit Board (Part ?)
You can get the source code from git@github.com:iotexpert/Tilt2.git This repository has tags for each of the articles which can be accessed with "git checkout part12" You can find the Tilt Simulator at git@github.com:iotexpert/TiltSimulator.git.
Story
At this point the Tilt Data Manager has all of the knowledge of the status of Tilts. But, we have other threads which need access to that data. How are they going to get it in a thread safe way? Moreover, how am I going to provide access to this data without hardcoding specific information about the threads into the Tilt Data Manager? In this article I add functionality to the Tilt Data Manager Thread to provide Tilt status. Then I will test the functionality by adding test code into the ntshell. The green boxes are the functional blocks I will touch in this update.
Update the Public Interface to tiltDataManager.h
As I thought about what the other threads in the system would need I decided the best thing to do would be to write their public APIs. Here it is:
- What Color is the “Tilt” i.e. “Pink”
- How many Tilts are in the system (so that you can loop through them)
- What Tilts are currently active – a bitmask
- How many events have been seen for each Tilt
- Please give me a copy of the current data.
/////////////// Generally callable threadsafe - non blocking char *tdm_colorString(tdm_tiltHandle_t handle); // Return a char * to the color string for the tilt handle int tdm_getNumTilt(); // Returns the number of possible tilts (probably always 8) uint32_t tdm_getActiveTiltMask(); // Return a bitmask of the active handles uint32_t tdm_getNumDataSeen(tdm_tiltHandle_t handle); // Return number of data points seen tdm_tiltData_t *tdm_getTiltData(tdm_tiltHandle_t handle);
Create the Thread Unsafe Functions
Obviously you need to do things thread safely. However, I decided to have a few functions which couldn’t hurt anything, but are unsafe. (Sorry Butch… comment below if you don’t agree and Ill fix it). To make this a bit safer, I set the data as volatile to make the compiler at least write it back into memory when things are done.
typedef struct { char *colorName; uint8_t uuid[TILT_IBEACON_HEADER_LEN]; volatile tdm_tiltData_t *data; volatile int numDataPoints; volatile int numDataSeen; } tilt_t;
The functions are very straight forward.
char *tdm_colorString(tdm_tiltHandle_t handle) { return tiltDB[handle].colorName; } int tdm_getNumTilt() { return NUM_TILT; } uint32_t tdm_getActiveTiltMask() { uint32_t mask=0; for(int i=0;i<NUM_TILT;i++) { if(tiltDB[i].data) mask |= 1<<i; } return mask; } uint32_t tdm_getNumDataSeen(tdm_tiltHandle_t handle) { return tiltDB[handle].numDataSeen; }
Thread Safe Communication
Now, a much better answer for thread safety. If you have multiple tasks that need access to the same data it is very tempting to just communicate between the threads with global variables (see above). You are just asking for trouble because one task might be partially through updating data, when it is interrupted by the RTOS scheduler. Then another threads starts reading/writing from global variables that were incompletely updated by the suspended thread. BAD!
So, what is a person to do?
What I typically do is use an RTOS queue to communicate between the two tasks. In the picture below you can see that the ntshell will send a message to the Tilt Data Manager that it wants to know the data from a Tilt (using the handle) and it wants the response sent back to the “response queue”. When the Tilt Data Manager gets this command, it will build a response and push it back into the “response queue”. With this scheme the Tilt Data Manager does not know which thread that it is talking to, just the handle of the queue.
Create the Thread Safe Function
Inside of my code, the first step in creating the thread safe communication is to add a command to the Tilt Commands, TDM_CMD_GET_DATAPOINT
typedef enum { TDM_CMD_ADD_DATAPOINT, TDM_CMD_PROCESS_DATA, TDM_CMD_GET_DATAPOINT, } tdm_cmd_t;
The response to all questions about Tilts will be in the form of a “tdm_tiltData_t” structure. Specifically one that has been malloc’d on the heap. To support this I will create a function that:
- Mallocs a new structure
- Copys the data
- Fix the averaging
- Returns a pointer to the new data
// This function returns a malloc'd copy of the front of the most recent datapoint ... this function should only be used // internally because it is not thread safe. static tdm_tiltData_t *tdm_getDataPointCopy(tdm_tiltHandle_t handle) { tdm_tiltData_t *dp; dp = malloc(sizeof(tdm_tiltData_t)); memcpy(dp,(tdm_tiltData_t *)tiltDB[handle].data,sizeof(tdm_tiltData_t)); dp->gravity = dp->gravity / tiltDB[handle].numDataPoints; dp->temperature = dp->temperature / tiltDB[handle].numDataPoints; return dp; }
Then I update the command queue to deal with the get data message.
case TDM_CMD_GET_DATAPOINT: dp = tdm_getDataPointCopy(msg.tilt); xQueueSend(msg.msg,&dp,10); break;
Then I create the public function for other threads to call the send the request for the data command. This function is called in the context of the calling thread NOT the tiltDataManager. It will create a temporary queue for the transaction, send the command, then sit and wait for the response.
tdm_tiltData_t *tdm_getTiltData(tdm_tiltHandle_t handle) { QueueHandle_t respqueue; tdm_tiltData_t *rsp; if(handle<0 || handle>=NUM_TILT || tiltDB[handle].data == 0 ) return 0; respqueue = xQueueCreate(1,sizeof(tdm_tiltData_t *)); if(respqueue == 0) return 0; tdm_cmdMsg_t msg; msg.msg = respqueue; msg.tilt = handle; msg.cmd = TDM_CMD_GET_DATAPOINT; if(xQueueSend(tdm_cmdQueue,&msg,0) != pdTRUE) { printf("failed to send to dmQueue\n"); } xQueueReceive(respqueue,(void *)&rsp,portMAX_DELAY); vQueueDelete(respqueue); return rsp; }
Test with the ntshell
To test things whole thing out I add a new command to ntshell. This command just try all of the public APIs
static int usrcmd_testdm(int argc, char **argv) { tdm_tiltData_t *msg; printf("NumTilt = %d\n",tdm_getNumTilt()); printf("Active = %X\n",(unsigned int)tdm_getActiveTiltMask()); for(int i =0;i<tdm_getNumTilt();i++) { printf("Color = %s\t#Seen=%d\t", tdm_getColorString(i), (int)tdm_getNumDataSeen(i)); if(tdm_getActiveTiltMask() & 1<<i) { msg = tdm_getTiltData(i); if(msg) { printf("G=%f\tT=%d", msg->gravity, (int)msg->temperature); free(msg); } } printf("\n"); } return 0; }
Here it is running. Notice that I typed the command “testdm” And that my test setup has heard two Tilts (remember I have a test broadcaster)
In the next article Ill add the TFT Display.
No comment yet, add your voice below!