Ring buffer based key interrupt reading

1. Goal

Through previous studies, I learned the basic GPIO input and output and serial port. Next, I used a small demo to learn to introduce external interrupts. That is to say, the key status is read by setting an external interrupt on the key. When the key is pressed, the LED light turns on, and when the key is released, the LED turns off.

2. Interruption

2.1 What is an interrupt

Interruption means that when some unexpected situations occur during the operation of the computer and require host intervention, the machine can automatically stop the running program and transfer to the program that handles the new situation. After the processing is completed, it returns to the original suspended program to continue running.

2.2 Interrupted Process

1. Set the interrupt source and allow the specified interrupt to occur;

2. Set the interrupt controller, such as blocking specified interrupts, such as setting the priority level of allowed interrupts;

3. Set the CPU interrupt main switch and enable interrupts; execute general programs;

4. When an interrupt is generated, the hardware sends the interrupt signal to the interrupt controller, and the interrupt controller sends it to the CPU to let the CPU know what interrupt has occurred;

5. Every time the CPU finishes executing an instruction, it will check whether an abnormal interrupt has occurred; if it finds that an abnormal interrupt has occurred, it will handle the abnormal interrupt;

2.3ARM’s handling of interrupts

1. Save the scene: Save the current execution address, variables, etc. of the general program to the stack;

2. Handle exceptions: Jump to the interrupt service function stored in the exception vector table through the exception interrupt signal to start executing the interrupt;

3. Restore the scene: After executing the interrupt handler, read the previously saved data from the stack, jump to the original execution address and start executing the general program;

2.4 Interrupt priority

1. The lower the priority value, the higher the priority level of execution;

2. Priority is divided into: preemption priority and sub-priority; interrupts with low preemption priority can interrupt the interrupt being executed with high preemption priority;

3. Sub-priority can only determine the execution order of multiple interrupts with the same preemption priority generated at the same time;

4. When two interrupts with the same priority value (whether preemption or sub-priority) occur at the same time, the order of execution is determined based on the position in the exception vector table, and the earlier interrupt is executed first;

3.Analysis

First of all, in this demo, what needs to be involved are LEDs and buttons. The buttons involve external interrupts. The external interrupts have multiple modes. Because the purpose to be achieved is to change the state when pressing and releasing, double edge triggering is used. , that is, both rising and falling edges trigger. Although in this demo, the microcontroller only handles this one thing, we have to imagine that when the processor of the microcontroller is running, external interrupts are triggered by double edges. The external interrupts have corresponding callback functions. What we need to do in this demo is to trigger Interrupt, read the status of the button, and then control the LED to turn on and off. When the button is pressed, the LED turns on. When the button is released, the LED turns off.

4. Create project

4.1 Configure the third configuration
4.2 Configuring LEDs and buttons

Set PA0-PA3 to external interrupt mode, then double edge trigger, set PB12-PB15 to output mode, the default output is high level, that is, the default LED is initially off.

4.3 Configuring NVIC

Enable external interrupts.

5. Code

5.1 Reconstruct the key code

To introduce external interrupts, we need to rewrite the Read function, modify the normal key Read function into the GPIODrvIrqRead function, and write a global variable state as the flag bit.

#define K1 \
{ \
    .name = "K1", \
    .port = GPIOA,\
    .pin = GPIO_PIN_0, \
    .Init = GPIODrvInit, \
    .Write = NULL, \
    .Read = GPIODrvIrqRead, \
    .next = NULL \
}

static GPIO_PinState state = 1;

static int GPIODrvIrqRead(struct GPIODev *ptdev)
{
    if(NULL == ptdev) return -EINVAL;
    return state;
 
}
5.2 Callback function

In the external interrupt function, all external interrupts share a callback function, but the input GPIO_Pin is used to distinguish which external interrupt it is and what kind of interrupt processing is performed.

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    switch(GPIO_Pin)
    {
        case GPIO_PIN_0: // PA0--K1
        {
            state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
            break;
        }
        case GPIO_PIN_1: // PA1--K2
        {
            break;
        }
        case GPIO_PIN_2: // PA2--K3
        {
            break;
        }
        case GPIO_PIN_3: // PA3--K4
        {
            break;
        }
        default:break;
    }
}
5.3 Test function
#include "devices.h"
#include "errno.h"
#include "mytype.h"

void app_key_test(void)
{
    IODevicesRegister();
    
    GPIODevice *pK1 = IODeviceFind("K1");
    if(NULLDEV == pK1) return;
    GPIODevice *pD1 = IODeviceFind("D1");
    if(NULLDEV == pD1) return;
    
    if(pK1->Init(pK1) != ESUCCESS) return;
    if(pD1->Init(pD1) != ESUCCESS) return;
    while(1)
    {
        int status = pK1->Read(pK1);
        pD1->Write(pD1, status);
        
    }
}
5.4 main function
#include "main.h"
#include "usart.h"
#include "gpio.h"

void SystemClock_Config(void);

extern void app_key_test(void);

int main(void)
{
  
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();

  app_key_test();

  while (1)
  {
  }
}
5.5 Logical Analysis

First initialize the HAL library, initialize the system clock, initialize GPIO, and initialize the serial port (can be ignored). Then call app_key_test(). In the app_key_test() function, first look for K1 and D1 devices, that is, button 1 and LED1, then initialize D1 and K1, then poll to read the status of K1, and write a global variable state as The flag bit, the default is 1, that is, the LED is off. When the button is pressed, the status of the PA0 port is 0, and the LED is on. When the button is released, the status of the PA0 port is 1, and the LED is off.

6. Ring buffer

6.1 What is a ring buffer

The ring buffer is a first-in-first-out (FIFO) closed-loop storage space. The popular understanding is that a “circular” area is planned in the memory, and the “circle” is divided into N (the size of the Ring Buffer) equal parts.

6.2 ring_buffer.c
#include "errno.h"
#include "libs.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

static int RingBufferWrite(struct RingBuffer *ptbuf, const unsigned char *src, unsigned int length);
static int RingBufferRead(struct RingBuffer *ptbuf, unsigned char *dst, unsigned int length);
static int RingBufferClear(struct RingBuffer *ptbuf);
static int RingBufferFree(struct RingBuffer *ptbuf);

/*
    Function name: RingBufferNew
    Function: Initialize a specified ring buffer
    Input parameters: length-> indicates the memory size allocated by the buffer, in bytes
    Output parameters: none
    Return value: NULL-> indicates error; ptbuf-> indicates successful acquisition of a buffer
*/
struct RingBuffer *RingBufferNew(unsigned int length)
{
    struct RingBuffer *ptbuf;
    if(0 == length) return NULL;
    
    ptbuf = (struct RingBuffer*)malloc(sizeof(struct RingBuffer));
    if(NULL == ptbuf) return NULL;
    if(NULL != ptbuf->info.pHead)
    {
        free(ptbuf->info.pHead);
    }
    ptbuf->info.pHead = (unsigned char*)malloc(length);
    if(NULL == ptbuf->info.pHead)
    {
        xprintf("Error. Malloc %d bytes failed.\r\\
", length);
        return NULL;
    }
    ptbuf->info.pValid = ptbuf->info.pValidEnd = ptbuf->info.pHead;
    ptbuf->info.pEnd = ptbuf->info.pHead + length;
    ptbuf->info.nValidLength = 0;
    ptbuf->info.nBufferLength = length;
    
    ptbuf->Write = RingBufferWrite;
    ptbuf->Read = RingBufferRead;
    ptbuf->Clear = RingBufferClear;
    ptbuf->Free = RingBufferFree;
    
    return ptbuf;
}

static int RingBufferFree(struct RingBuffer *ptbuf)
{
    if(ptbuf == NULL) return -EINVAL;
    if(ptbuf->info.pHead==NULL) return -EINVAL;
    
    free((unsigned char*)ptbuf->info.pHead);
    
    ptbuf->info.pHead = NULL;
    ptbuf->info.pValid = NULL;
    ptbuf->info.pValidEnd = NULL;
    ptbuf->info.pEnd = NULL;
    ptbuf->info.nValidLength = 0;
    
    free((struct RingBuffer *)ptbuf);
    return ESUCCESS;
}

static int RingBufferWrite(struct RingBuffer *ptbuf, const unsigned char *src, unsigned int length)
{
    unsigned int len1 = 0, len2 = 0;
    unsigned int move_len = 0;
    
    if(length > ptbuf->info.nBufferLength)
    {
        return -EINVAL;
    }
    if(ptbuf->info.pHead==NULL)
    {
        return -EINVAL;
    }
    
    // copy buffer to pValidEnd
    if( (ptbuf->info.pValidEnd + length) > ptbuf->info.pEnd ) // exceeds the Buffer range and needs to be divided into two sections
    {
        len1 = (unsigned)(ptbuf->info.pEnd - ptbuf->info.pValidEnd);
        len2 = length - len1;
        
        memcpy((unsigned char*)ptbuf->info.pValidEnd, src, len1);
        memcpy((unsigned char*)ptbuf->info.pHead, src + len1, len2);
        
        ptbuf->info.pValidEnd = ptbuf->info.pHead + len2; // Update the end address of the valid data area
    }
    else
    {
        memcpy((unsigned char*)ptbuf->info.pValidEnd, src, length);
        ptbuf->info.pValidEnd = ptbuf->info.pValidEnd + length;
    }
    
    // Recalculate the starting position of the used area
    if( (ptbuf->info.nValidLength + length) > ptbuf->info.nBufferLength ) // The data to be written exceeds the total length of the buffer and is divided into two parts.
    {
        move_len = ptbuf->info.nValidLength + length - ptbuf->info.nBufferLength;
        if( (ptbuf->info.pValid + move_len) > ptbuf->info.pEnd )
        {
            len1 = (unsigned)(ptbuf->info.pEnd - ptbuf->info.pValid);
            len2 = move_len - len1;
            
            ptbuf->info.pValid = ptbuf->info.pHead + len2;
        }
        else
        {
            ptbuf->info.pValid = ptbuf->info.pValid + move_len;
        }
        
        ptbuf->info.nValidLength = ptbuf->info.nBufferLength;
    }
    else
    {
        ptbuf->info.nValidLength = ptbuf->info.nValidLength + length;
    }
    
    return (int)length;
}

static int RingBufferRead(struct RingBuffer *ptbuf, unsigned char *dst, unsigned int length)
{
    unsigned int len1 = 0, len2 = 0;
    if(ptbuf->info.pHead==NULL) return -EINVAL;
    if(ptbuf->info.nValidLength==0) return -ENOMEM;
    
    if(length > ptbuf->info.nValidLength)
    {
        length = ptbuf->info.nValidLength;
    }
    
    if( (ptbuf->info.pValid + length) > ptbuf->info.pEnd )
    {
        len1 = (unsigned int)(ptbuf->info.pEnd - ptbuf->info.pValid);
        len2 = length - len1;
        
        memcpy(dst, (unsigned char*)ptbuf->info.pValid, len1);
        memcpy(dst + len1, (unsigned char*)ptbuf->info.pHead, len2);
        
        ptbuf->info.pValid = ptbuf->info.pHead + len2;
    }
    else
    {
        memcpy(dst, (unsigned char*)ptbuf->info.pValid, length);
        ptbuf->info.pValid = ptbuf->info.pValid + length;
    }
    
    ptbuf->info.nValidLength -= length;
    
    return (int)length;
}

static int RingBufferClear(struct RingBuffer *ptbuf)
{
    if(ptbuf == NULL) return -EINVAL;
    if(ptbuf->info.pHead==NULL) return -EINVAL;
    if(ptbuf->info.pHead != NULL)
    {
        memset(ptbuf->info.pHead, 0, ptbuf->info.nBufferLength);
    }
    
    ptbuf->info.pValid = ptbuf->info.pValidEnd = ptbuf->info.pHead;
    ptbuf->info.nValidLength = 0;
    return ESUCCESS;
}
6.2 ring_buffer.h
#ifndef __RING_BUFFER_H
#define __RING_BUFFER_H

typedef struct RingBuffInfo{
    unsigned char *pHead;
    unsigned char *pEnd;
    unsigned char *pValid;
    unsigned char *pValidEnd;
    unsigned int nBufferLength;
    unsigned int nValidLength;
}RingBuffInfo;

typedef struct RingBuffer{
    RingBuffInfo info;
    int (*Write)(struct RingBuffer *ptbuf, const unsigned char *src, unsigned int length);
    int (*Read)(struct RingBuffer *ptbuf, unsigned char *dst, unsigned int length);
    int (*Clear)(struct RingBuffer *ptbuf);
    int (*Free)(struct RingBuffer *ptbuf);
    struct RingBuffer *next;
}RingBuffer;

struct RingBuffer *RingBufferNew(unsigned int length);

#endif /* __DRV_BUFFER_H */

7. Code after using ring buffer

7.1 Refactoring code

Add value to GPIODev to represent the current status of GPIO.

typedef struct GPIODev{
    char *name;
    void *port;
    unsigned int pin;
    unsigned char value;
    int (*Init)(struct GPIODev *ptdev);
    int (*Write)(struct GPIODev *ptdev, unsigned char status);
    int (*Read)(struct GPIODev *ptdev);
    struct GPIODev *next;
}GPIODevice;
static RingBuffer *gK1Buffer = NULL;

static int GPIODrvInit(struct GPIODev *ptdev)
{
    if(NULL == ptdev) return -EINVAL;
    
    if(strstr(ptdev->name, "K1"))
    {
        gK1Buffer = RingBufferNew(sizeof(unsigned char) * 10);
        if(NULL == gK1Buffer) return -EIO;
    }
    
    return ESUCCESS;
}
static int GPIODrvIrqRead(struct GPIODev *ptdev)
{
    if(NULL == ptdev) return -EINVAL;

    if(strstr(ptdev->name, "K1"))
    {
        if(NULL != gK1Buffer)
        {
            int ret = gK1Buffer->Read(gK1Buffer, (unsigned char*) & amp;ptdev->value, 1);
            if(ret != 1) return -EIO;
        }
    }
    
    return ESUCCESS;
}
7.2 Callback function
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    switch(GPIO_Pin)
    {
        case GPIO_PIN_0: // PA0--K1
        {
            GPIO_PinState state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
            if(NULL != gK1Buffer)
                gK1Buffer->Write(gK1Buffer, (unsigned char*) & amp;state, 1);
            break;
        }
        case GPIO_PIN_1: // PA1--K2
        {
           
        }
        case GPIO_PIN_2: // PA2--K3
        {
            
        }
        case GPIO_PIN_3: // PA3--K4
        {
           
        }
        default:break;
    }
}
7.3 Test function
#include "devices.h"
#include "errno.h"
#include "mytype.h"

void app_key_test(void)
{
    IODevicesRegister();
    
    GPIODevice *pK1 = IODeviceFind("K1");
    if(NULLDEV == pK1) return;
    GPIODevice *pD1 = IODeviceFind("D1");
    if(NULLDEV == pD1) return;
    
    if(pK1->Init(pK1) != ESUCCESS) return;
    if(pD1->Init(pD1) != ESUCCESS) return;
    while(1)
    {
        int status = pK1->Read(pK1);
        if(ESUCCESS == status)
            pD1->Write(pD1, pK1->value);
    }
}