Using the QN908x FreeRTOS BLE Abstraction Layer Stack to create Tasks and Queues.

Document created by Fabian Ramirez Employee on Jan 30, 2020Last modified by Fabian Ramirez Employee on Feb 7, 2020
Version 7Show Document
  • View in full screen mode


Introduction

 

In this document, we are focusing on the usage of two functions of FreeRTOS: Creation of tasks and the use of queues as a way to trigger a generic functionality in our application. NXP’s SDK abstraction layer for the QN908x is intended to give the customer a simpler way of FreeRTOS usage and control functionalities.

Hardware Requirements:

  • QN9080DK

Software Requirements:

  • MCUXpresso IDE v11.1.0 or newer
  • QN908XCDK SDK with BLE stack and examples (It can be downloaded from the SDK Builder)

This document is based on the Wireless UART example from the SDK.

Setting up the required variables and definitions

  1. Import the FreeRTOS version of the wireless_uart example included in the QN9080’s SDK.

 

Note: Be sure to select UART as your debug console.

   2. In order to change the default role of the QN9080 board it is required to change the definition of the mGapRole variable, from gGapCentral_c to gGapPeripheral_c in PROJECT_FILE_NAME > source > wireless_uart.c > BleApp_Config().

//wireless_uart.c
//static void BleApp_Config(void) {
/* By default, always start node as GAP central */
//mGapRole = gGapCentral_c;
mGapRole = gGapPeripheral_c;

(void)Serial_Print(gAppSerMgrIf, "\n\rWireless UART starting as GAP Peripheral, press the role switch to change it.\n\r", gAllowToBlock_d);

mAdvState.advOn = TRUE;
mScanningOn = FALSE;
//...}

   3. It is important to change the macro value of: osNumberOfMessageQs in PROJECT_FILE_NAME > framework > OSAbstracion > Interface > fsl_os_abstraction_config.h to a value > 0. 

//fsl_os_abstraction_config.h
#ifndef osNumberOfMutexes
#define osNumberOfMutexes 5
#endif
#ifndef osNumberOfMessageQs
#define osNumberOfMessageQs 1
#endif
#ifndef osNumberOfMessages
#define osNumberOfMessages 10
#endif

//...

Then, to start the task implementation it is needed to edit the file: PROJECT_FILE_NAME > framework > OSAbstracion > Source > fsl_os_abstraction_free_rtos.c.
       First, we create the prototype of the main task function, then define the task using the “OSA_TASK_DEFINE” definition. The syntax should be the following:

This function is part of the thread Management, it helps the user define the creation of a new thread, controlling the priority and stack requirements. The following parameters are used in the function definition.

name: Should be the name of the function used as our task

priority: The scheduler will grant a minor or major priority to our function with this parameter.
instances: This parameter give us the control to limit the number of instances in order to manage the memory in the most optimal way.
stackSz : This parameter has a default value, which is defined by the abstraction layer as: gMainThreadStackSize_c as 1024 bytes.
useFloat : This parameter is part of the thread definition structure, is a Boolean, It defines if the thread will use floating point.

 

//fsl_os_abstraction_free_rtos.c
/*! *********************************************************************************
*************************************************************************************
* Private functions
*************************************************************************************
********************************************************************************** */


OSA_TASK_DEFINE(startup_task, gMainThreadPriority_c, 1, gMainThreadStackSize_c, 0) ;

void vfnTaskPWM(void* param); //New Task Definition
OSA_TASK_DEFINE(vfnTaskPWM, 1, 1, 500, FALSE );
osaTaskId_t gAppTestTask1Id = 0; // TestTask1 Id
osaTaskId_t pwmTask_queue = 0;

//get defines from wireless_uart
int main (void)
{
/* Initialize MCU clock */
hardware_init();
OSA_TaskCreate(OSA_TASK(startup_task), NULL);
//Task and Queue creation.
gAppTestTask1Id = OSA_TaskCreate(OSA_TASK(vfnTaskPWM), NULL); //Task Creation
pwmTask_queue = OSA_MsgQCreate(1);
//Scheduler starting
vTaskStartScheduler();

return 0;
}
//..

 

4. The function: OSA_TaskCreate returns a taskID it is required to create a osaTaskId_t variable as the following: osaTaskId_t gAppTestTask1Id = 0; and also another osaTaskId_t variable for the FreeRTOS queue we will be using :

osaTaskId_t pwmTask_queue = 0.

5. Inside the main function we will create our task in the same fashion the startup_task is created:

gAppTestTask1Id = OSA_TaskCreate(OSA_TASK(vfnTaskPWM), NULL);

6. We also need to create the queue using the same type of variable and using the OSA_MsgQCreate() function to create a new Queue:

pwmTask_queue = OSA_MsgQCreate(1);

7. After this statements the scheduler should start: vTaskStartScheduler();

8. In the wireless_uart.c file it is required to add pwmTask_queue, created for the queue as an extern variable.

//wireless_uart.c
static uint8_t gAppSerMgrIf;
static uint16_t mAppUartBufferSize = mAppUartBufferSize_c;
static volatile bool_t mAppUartNewLine = FALSE;
static volatile bool_t mAppDapaPending = FALSE;

extern osaMsgQId_t pwmTask_queue;


/************************************************************************************
*************************************************************************************
* Private functions prototypes
*************************************************************************************
************************************************************************************/



/* Gatt and Att callbacks */
static void BleApp_AdvertisingCallback(gapAdvertisingEvent_t *pAdvertisingEvent);

Tasks and Queues implementation in our application code

In this example we implemented the use of OSA_MsgQPut() to place the message received from the central peripheral in a queue by using:

//wireles_uart.c
static void BleApp_ReceivedUartStream(deviceId_t peerDeviceId, uint8_t *pStream, uint16_t streamLength)
{
static deviceId_t previousDeviceId = gInvalidDeviceId_c;

char additionalInfoBuff[10] = { '\r', '\n', '[', '0', '0', '-', 'M', ']', ':', ' '};


uint8_t *pBuffer = NULL;
uint32_t messageHeaderSize = 0;

if (mAppUartNewLine || (previousDeviceId != peerDeviceId))
{
streamLength += (uint32_t)sizeof(additionalInfoBuff);
}

/* Allocate buffer for asynchronous write */
pBuffer = MEM_BufferAlloc(streamLength);

if (pBuffer != NULL)
{
/* if this is a message from a previous device, print device ID */
if (mAppUartNewLine || (previousDeviceId != peerDeviceId))
{
messageHeaderSize = sizeof(additionalInfoBuff);

if (mAppUartNewLine)
{
mAppUartNewLine = FALSE;
}

additionalInfoBuff[3] = '0' + (peerDeviceId / 10U);
additionalInfoBuff[4] = '0' + (peerDeviceId % 10U);

if (gGapCentral_c != maPeerInformation[peerDeviceId].gapRole)
{
additionalInfoBuff[6] = 'S';
}

FLib_MemCpy(pBuffer, additionalInfoBuff, sizeof(additionalInfoBuff));
}

FLib_MemCpy(pBuffer + messageHeaderSize, pStream, (uint32_t)streamLength - messageHeaderSize);
/**** Adding a message into a Queue ****/
OSA_MsgQPut(pwmTask_queue, (void*)&pBuffer);
/**************************************/
(void)Serial_AsyncWrite(gAppSerMgrIf, pBuffer, streamLength, Uart_TxCallBack, pBuffer);
}

/* update the previous device ID */
previousDeviceId = peerDeviceId;
}

This function points to the external queue variable created before the main() function of the project. Also points to the buffer pointer of the message received in BleApp_ReceivedUartStream(), These parameters can be accessed by using OSA_MsgQGet() function.

osaStatus_t OSA_MsgQGet(osaMsgQId_t msgQId, void *pMessage, uint32_t millisec)

This function gets the message from the head of the message queue, the parameters of this function are the following:

msgQId : Is the identifier of the queue function.
pMessage : Is a pointer to the same message sent fromOSA_MsgQPut();
millisec : Is the number of milliseconds to wait for a message.

 

Inside the task created: vfnTaskPWM() there is an infinite while loop checking if a message is in the queue, this is accomplished by using the os_abstraction function:

The rest of the task implementation is in a different C file. The external queue variable is defined in this file in addition to the task function prototype:

/*pwmApplication.c*/

/*Global Variables*/
static uint8_t gAppSerMgrIf;
extern osaMsgQId_t pwmTask_queue;
/*Function Prototypes*/

void signalConfigValues(int red, int green, int blue);
void getValuesRGB(uint8_t* msgString);
int getInt(char *stringInteger, int size);
/****Task Definition****/
void vfnTaskPWM(void* param);


/*
* Task to configure the PWM pins and start the timer
* */


void vfnTaskPWM(void* param)
{
uint8_t* msgString;
char msgString1[20] = { '\0', '\0', '\0', '\0','\0', '\0', '\0', '\0','\0', '\0', '\0', '\0','\0', '\0', '\0', '\0','\0', '\0', '\0', '\0'};

while(1)
{
/*
* If there is a new message on queue the value will return a 0 according to the OSA_MsgQGet function
* to compare the incoming message
* */

while((OSA_MsgQGet(pwmTask_queue,(void*)&msgString,10)) == 0){
//shift-up the pointer value to get the payload message
if(*msgString == '\r'){
while(*msgString != 32){
msgString++;
}
msgString++;
}
FLib_MemCpy(msgString1,msgString,20);
if(FLib_MemCmp(msgString1, "OFF", 4)){
(void)Serial_Print(gAppSerMgrIf,"\n\r OFF: ", gAllowToBlock_d);
//configure the PWM new function signalConfigValues(0,0,0) and StartTimers(CTMRA,CTMRB)
signalConfigValues(0,0,0);
}
else{
//a function to convert values from a string to integers
getValuesRGB(msgString);
}
}
}
}

Attachments

    Outcomes