Skip to main content

STM32 Pushbutton Example: Part 2. Event Queue

The Event Queue is a simple ring buffer, whose element is a byte array of fixed size. You can use it any purpose as long as it fits. When it is used as an event queue, the meaning of each byte is supposed to be defined by the user. In this example, we define the first byte as the event id followed by event data as below:
Source file
   73 /** Pushbutton input event
   74  *
   75  * Event Data: (PBTN_ID)(EVT_TYPE)
   76  * 
   77  *  * PBTN_ID: id of the pushbutton that generated the event
   78  *  * EVT_TYPE: type of the event such as single click, double click, 
   79  */
   80 #define EVT_PBTN_INPUT          0x10        ///< event code for pushbutton input
   81 #define PBTN_SCLK               0x01        ///< single click
   82 #define PBTN_LCLK               0x02        ///< long click
   83 #define PBTN_DCLK               0x03        ///< double click
   84 #define PBTN_TCLK               0x04        ///< triple click
Posting an event is done by calling Evt_EnQueue() with the event id and corresponding data is packed into an byte array of size EVT_QWIDTH. So for example, the PushButton task post an long click event when it detects a click that lasts certain amount of time:
Source file
  156         // button pressed and long timeout passed
  157         else if((pp.duration[i] > PUSHBTN_TO_LONG) && 
  158                 (((pp.new_state >> i) & 0x01) == 0x01))
  159         {
  160             // long click event
  161             event[0] = EVT_PBTN_INPUT;
  162             event[1] = (uint8_t)(i+1);
  163             event[2] = PBTN_LCLK;
  164 
  165             // post the event
  166             Evt_EnQueue(event);
  167 
  168             // clear log
  169             PushButton_ClearLog(i);
  170 
  171             // raise flag: this will prevent false detect after long click
  172             flag = true;
  173         }
The event is generated by the PushButton_Routine() automatically. Thus you need to handle the event in your main loop() by calling Evt_DeQueue():
Source file
  113     while (1)
  114     {
  115         /* USER CODE END WHILE */
  116 
  117         /* USER CODE BEGIN 3 */
  118 
  119         // check event queue
  120         if(Evt_DeQueue(event))
  121         {
  122             switch(event[0])
  123             {
  124                 // pushbutton event
  125                 case EVT_PBTN_INPUT:
  126 
  127                     if(event[2] == PBTN_SCLK)
  128                     {
  129                         UartPrintf("Button %d: single click.\r\n", event[1]);
  130                     }
  131                     else if(event[2] == PBTN_LCLK)
  132                     {
  133                         UartPrintf("Button %d: long click.\r\n", event[1]);
  134                     }
  135                     else if(event[2] == PBTN_DCLK)
  136                     {
  137                         UartPrintf("Button %d: double click.\r\n", event[1]);
  138                     }
  139                     else if(event[2] == PBTN_TCLK)
  140                     {
  141                         UartPrintf("Button %d: triple click.\r\n", event[1]);
  142                     }
  143                     break;

Consideration about the Race Condition

If you share data between main loop and an interrupt handler, you have the risk of race condition being occur regardless of the data type. If you can't secure the exclusive access to the data via synchronization mechanism such as semaphore, then you have to at least consider disabling the interrupt during main loop access.

In the current design, generation of event (Evt_EnQueue()) were to be done only in the timer routine, which is based on the SysTick interrupt. While consumption of the event (Evt_DeQueue()) is done in the main loop. This simplifies the job of preventing the race condition by calling HAL-SuspendTick() / HAL_ResumeTick() inside of the Evt_DeQueue(). More sophisticated synchronization method is required in different situations.
Source file
   61 bool Evt_DeQueue(uint8_t *event)
   62 {
   63     uint8_t i;
   64     bool flag = false;
   65 
   66     // suspend systick
   67     HAL_SuspendTick();
   68 
   69     // queue is not empty
   70     if(evt_queue.tail != evt_queue.head)
   71     {
   72         // copy event bytes into the buffer
   73         for(i = 0; i < EVT_QWIDTH; i++)
   74         {
   75             event[i] = evt_queue.buff[evt_queue.tail][i];
   76         }
   77         // move to the next position
   78         evt_queue.tail = ADVANCE_QPTR(evt_queue.tail);
   79         // set flag
   80         flag = true;
   81     }
   82 
   83     // resume tick
   84     HAL_ResumeTick();
   85 
   86     // return with the flag
   87     return flag;
   88 }

 <<source code>>

Comments

Popular posts from this blog

A Simple STM32 Example Project

Most of the embedded projects share certain initial steps. You need to confirm the clock settings before doing anything, then you want to have debug connection via a UART channel since it is cheap  but still it can provide useful information for debugging. Let us start with CubeMX. You select a device/board of your choice, set up the pinouts for one GPIO output and one UART port. Configure the clock if necessary then create a project. Clock Checking using SysTick The sanity of the clock setting can be done by checking the SysTick interval. All Cortex-M series core have SysTick timer by default, which should fire at 1msec interval while the MCU is active. In the STM32Cube, the SysTick is initialized by HAL_Init() call, which in turn calls SysTick_Config() in CMSIS. Once the SysTick is initialized, it generates 1 msec interrupt and this interrupt is handled by SysTick_Handler() according to the Cube framework. Source file 1 /** 2 * @brief This function handles

STM32 USB MSC Device with FLASH memory

USB Mass Storage Class framework implements bulk-only-transfer (BOT) with SCSI protocol. USB packets from the host eventually converted SCSI transport commands by the middleware, in which data is exchanged (read / write) in the unit of logical block, typically 512 bytes. This SCSI commands works well with SD card system where a dedicated controller does the job of managing the actual memory elements. If you want to use a FLASH chip as a memory unit instead, you need to handle read / write operation directly. Fortunately, most of flash memory support 4KB block erase. This makes the 4096 bytes as a natural choice for the size of the logical block in the file usbd_storage_if.c. In this case, 8Mbit Flash memory was used. During initial enumeration, this information is registered to the host. The middleware maintains one logical block size of buffer and handles USB transaction where each payload is only 64 bytes. It then calls SCSI requests to store / retrieve data to / from physical

STM32 USB MSD with SD Card

Build a low level driver for SD card, then the glue logic for FatFs and USB MSD is pretty much the same as Flash memory case posed before. In case of SD card, sector size is 512 in most of the cases. Thus the memory requirement is much relaxed. You can even allocate a file buffer that is bigger than the sector size. FatFs site has a  dedicated page for MMC/SDC, on which you can find fairly detailed explanation about how to interface MMC/SDC via SPI bus. Implementation should be straightforward until you encounter with cheap SD cards that do not behave very well. In such cases, you either have to protect your code with redundancy or just stick with quality devices. If you choose SanDisk or Kingston brand, you will be safe. ADATA on the other hand, frequently generates timeout error at first try. Most of the SD card sockets have a pin to detect the presence of the card. This pin is usually connected to GND pin or some other pin. You can use this to generate interrupt whenever a ca