How to create a simple HMI application using FreeRTOS and emWin library
From SomLabs Wiki
How to create a simple HMI application using FreeRTOS and emWin library
This article describes how to create a simple GUI application using emWin graphical library. The example may be used as a base for building graphical HMI panels.
Hardware requirements
Importing FreeRTOS base project
This example is based on the FreeRTOS project described in the article How to create and run the first FreeRTOS application on the VisionSOM-RT. It can be created from scratch or imported directly to the MCUXpresso.
Importing drivers and libraries
First we need to import missing drivers and the emWin library from the SDK. Instead of creating a new project we can use the Manage SDK Components option. We will need the following drivers an components:
Drivers
- clock
- common
- elcdif
- flexio
- flexio_i2c_master
- gpio
- iomux
- lpuart
- semc
Middleware
- emwin
In the confirmation dialog we will see the summary of added and updated (in case of a newer SDK) software components. Then we will get another confirmation for each updated suorce file that will replace the one from the older SDK. We should replace everything instead of the files from the project's original xip directory. We can also restore the xip files later by downloading it again from the NXP server: https://community.nxp.com/servlet/JiveServlet/download/10894-1-430488/QSPI_xip.zip.
To make sure that everything works fine we can compile and run the project. In case of the following error:
../emwin/emwin_support.c:14:10: fatal error: fsl_debug_console.h: No such file or directory
we can simply remove this header inclusion.
Import MEX with hardware configuration
The external SDRAM and the LCD need the proper GPIO and peripheral configuration. Instead of configuring them manually, we can import the ready MEX file that is available to download here: FreeRTOS_demo_MEX.zip. To import it to the project let's right-click on the project name and select the Import option. In the Import dialog we need to select the Import Configuration (*.mex) option and browse to the file location. We also need to make sure that all Functional groups have the Import option set to overwrite.
Now we need to open any configuration tool window and update the code generated by MCUXpresso accepting all changes.
Memory configuration
Now we can add the SDRAM memory region in the Project Properties -> C/C++ Build -> MCU settings. The new RAM should be located at address 0x80000000 with the size of 0x2000000 (for 32MB SDRAM module).
The LCD buffer is by default defined in the non-cacheable memory region called 'NonCacheable'. We need to create it in the Project Properties -> C/C++ Build -> Settings -> Managed Linker Script:
emWin library support
The emWin library is configured in two files: emwin/emwin_support.h and emwin/emwin_support.c. The header file contains the definitions related to the image orientation and the library buffers configuration. The source file defines the library buffer, functions responsible for LCD controller interrupts handling and image frames management. There is also a set of functions that provides the interface to the operating system, like delay and locking the resource. The full source code of both files is listed below.
#ifndef _EMWIN_SUPPORT_H_ #define _EMWIN_SUPPORT_H_ #define DISPLAY_DRIVER GUIDRV_LIN_OXY_32 #define COLOR_CONVERSION GUICC_M8888I #define GUI_SCALE_FACTOR 1 #define GUI_SCALE_FACTOR_X 1 #define GUI_SCALE_FACTOR_Y 1 #define GUI_NORMAL_FONT (&GUI_Font24_ASCII) #define GUI_LARGE_FONT (&GUI_Font32B_ASCII) #define GUI_BUFFERS 2 #define GUI_NUMBYTES 80000U #define LCD_BYTES_PER_PIXEL 4 #define VRAM_SIZE (ELCDIF_1_PANEL_HEIGHT * ELCDIF_1_PANEL_WIDTH * LCD_BYTES_PER_PIXEL) #endif
#include "emwin_support.h" #include "peripherals.h" #include "GUI.h" #include "GUIDRV_Lin.h" #include "FreeRTOS.h" #include "task.h" #include "semphr.h" AT_NONCACHEABLE_SECTION_ALIGN(uint8_t s_gui_memory[GUI_NUMBYTES * LCD_BYTES_PER_PIXEL], ELCDIF_1_RGB_BUFFER_ALIGN); #define GUI_MEMORY_ADDR ((uint32_t)s_gui_memory) #define VRAM_ADDR ((uint32_t)eLCDIF_1_Buffer[0]) static volatile int32_t s_LCDpendingBuffer = -1; static SemaphoreHandle_t mutex; extern void eLCDIF_1_init(void); void ELCDIF_1_LCDIF_IRQHANDLER(void) { uint32_t intStatus; intStatus = ELCDIF_GetInterruptStatus(ELCDIF_1_PERIPHERAL); ELCDIF_ClearInterruptStatus(ELCDIF_1_PERIPHERAL, intStatus); if (intStatus & kELCDIF_CurFrameDone) { GUI_MULTIBUF_Confirm(s_LCDpendingBuffer); s_LCDpendingBuffer = -1; } } void LCD_X_Config(void) { GUI_MULTIBUF_Config(GUI_BUFFERS); GUI_DEVICE_CreateAndLink(DISPLAY_DRIVER, COLOR_CONVERSION, 0, 0); LCD_SetSizeEx(0, ELCDIF_1_PANEL_WIDTH, ELCDIF_1_PANEL_HEIGHT); LCD_SetVSizeEx(0, ELCDIF_1_PANEL_WIDTH, ELCDIF_1_PANEL_HEIGHT); LCD_SetVRAMAddrEx(0, (void *)VRAM_ADDR); } int LCD_X_DisplayDriver(unsigned LayerIndex, unsigned Cmd, void *p) { uint32_t addr; int result = 0; LCD_X_SHOWBUFFER_INFO *pData; switch (Cmd) { case LCD_X_INITCONTROLLER: { eLCDIF_1_init(); break; } case LCD_X_SHOWBUFFER: { pData = (LCD_X_SHOWBUFFER_INFO *)p; addr = VRAM_ADDR + VRAM_SIZE * pData->Index; vTaskDelay(1); s_LCDpendingBuffer = pData->Index; ELCDIF_SetNextBufferAddr(ELCDIF_1_PERIPHERAL, addr); while (s_LCDpendingBuffer >= 0) ; return 0; } default: result = -1; break; } return result; } void GUI_X_Config(void) { GUI_ALLOC_AssignMemory((void *)GUI_MEMORY_ADDR, GUI_NUMBYTES); GUI_SetDefaultFont(GUI_FONT_6X8); } void GUI_X_Init(void) { } void GUI_X_InitOS(void) { mutex = xSemaphoreCreateMutex(); } void GUI_X_Lock(void) { xSemaphoreTake(mutex, portMAX_DELAY); } void GUI_X_Unlock(void) { xSemaphoreGive(mutex); } U32 GUI_X_GetTaskId(void) { return 0; } void GUI_X_ExecIdle(void) { vTaskDelay(1); } GUI_TIMER_TIME GUI_X_GetTime(void) { return xTaskGetTickCount(); } void GUI_X_Delay(int Period) { vTaskDelay(Period); } void *emWin_memcpy(void *pDst, const void *pSrc, long size) { return memcpy(pDst, pSrc, size); }
Handling touchscreen
The touch screen in the display module contains the FT5426 controller that is accessible using the I2C interface. The SCL and SDA pins and the I2C controller are already configured in the imported MEX file, so we only need to add the code responsible for reading the touchscreen state and pass the results to the emWin library for further processing. In the example the controller state is read periodically in the FreeRTOS timer code listed below. If exactly one point is detected, its coordinates are passed to the library using GUI_PID_STATE structure.
void touchTimer(TimerHandle_t xTimer) { uint8_t data[5] = {0}; flexio_i2c_master_transfer_t touchTransfer; touchTransfer.data = data; touchTransfer.dataSize = 5; touchTransfer.flags = 0; touchTransfer.slaveAddress = 0x38; touchTransfer.subaddressSize = 0x01; touchTransfer.subaddress = 0x02; touchTransfer.direction = kFLEXIO_I2C_Read; FLEXIO_I2C_MasterTransferBlocking(&FlexIO_I2C_1_peripheralConfig, &touchTransfer); uint8_t points = data[0]; uint32_t x = ((data[1] & 0x0F) << 8) | (data[2]); uint32_t y = ((data[3] & 0x0F) << 8) | (data[4]); static bool activeTouch = false; GUI_PID_STATE state; state.Layer = 0; state.Pressed = 0; state.x = x; state.y = y; if (points == 1) { activeTouch = true; state.Pressed = 1; GUI_PID_StoreState(&state); } else if (activeTouch) { activeTouch = false; state.Pressed = 0; GUI_PID_StoreState(&state); } }
GUI creation
Our last task is to create a user interface using the components available in the emWin library. in this example there is only a single button and a text label displaying the number of button presses. The GUI is created and handled in a FreeRTOS task. All touch events processed by the library may be handled in a registered callback (eventCallback). The application code is listed below.
#include <stdio.h> #include "peripherals.h" #include "pin_mux.h" #include "clock_config.h" #include "FreeRTOS.h" #include "task.h" #include "timers.h" #include "emwin_support.h" #include "GUI.h" #include "TEXT.h" #include "BUTTON.h" #include "DIALOG.h" TaskHandle_t guiTaskHandle; BUTTON_Handle button; static uint32_t pressCounter = 0; void eventCallback(WM_MESSAGE * pMsg) { if(pMsg->MsgId == WM_NOTIFY_PARENT) { if (pMsg->hWinSrc == button) { int code = pMsg->Data.v; if (code == WM_NOTIFICATION_RELEASED) { pressCounter++; } } return; } WINDOW_Callback(pMsg); } void guiTask(void* param) { GUI_Init(); WM_SetCreateFlags(WM_CF_MEMDEV); GUI_Clear(); WM_HWIN window = WINDOW_CreateEx(0, 0, LCD_GetXSize(), LCD_GetYSize(), WM_HBKWIN, WM_CF_SHOW, 0, GUI_ID_USER, NULL); WINDOW_SetBkColor(window, GUI_BLACK); button = BUTTON_CreateEx(100, 140, 250, 200, window, WM_CF_SHOW, 0, GUI_ID_BUTTON0); BUTTON_SetText(button, "Press me!"); BUTTON_SetFont(button, GUI_LARGE_FONT); char textBuffer[32]; sprintf(textBuffer, "Press counter: %lu", pressCounter); TEXT_Handle text = TEXT_CreateEx(450, 215, 250, 50, window, WM_CF_SHOW, TEXT_CF_RIGHT, GUI_ID_TEXT0, textBuffer); TEXT_SetFont(text, GUI_LARGE_FONT); TEXT_SetTextColor(text, GUI_WHITE); WM_SetCallback(window, eventCallback); GUI_Exec(); while(1) { vTaskDelay(150 / portTICK_PERIOD_MS); sprintf(textBuffer, "Press counter: %lu", pressCounter); TEXT_SetText(text, textBuffer); GUI_Exec(); } } void main(void) { BOARD_InitBootPins(); BOARD_InitBootClocks(); BOARD_InitBootPeripherals(); xTaskCreate(guiTask, "GUI", 1024, NULL, 1, &guiTaskHandle); TimerHandle_t timer = xTimerCreate("TOUCH", 150 / portTICK_PERIOD_MS, pdTRUE, NULL, touchTimer); xTimerStart(timer, 0); vTaskStartScheduler(); }
Ready-to-use project
The project containing all described steps can be downloaded ad imported directly to the MCUXpresso: FreeRTOS_HMI_demo.zip. It was built using the NXP SDK v2.6.1.