当前位置:网站首页>100ask seven day IOT training camp learning notes - bare metal program framework design

100ask seven day IOT training camp learning notes - bare metal program framework design

2022-06-26 01:15:00 _ kukukukiki__

1. Preface

Signed up 100ask Organized a seven day IOT smart home training camp , Every day in the morning 2 An hour to talk about the basics , Afternoon 2 Hours of advanced lecture . After two days' study, I really feel more fulfilled . In the next few days, I will focus on the key points of each class 、 Record the difficulties and your understanding .

2. theory

2.1 Throw questions

In this lesson, Mr. Wei started the course with the program framework design as the starting point , Take embedded bare metal development as an example , Many beginners usually work in business layer code or even main Function , Directly call the interface used to describe or hardware ( Such as HAL library ):

void main(void)
{
    
    GPIO_PinState key;
    while (1)
    {
    
        key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
        if (key == GPIO_PIN_RESET)
            HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_RESET);
        else
            HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_SET);
    }
}

This will cause serious coupling between business layer code and board level code , Extend the following software functions 、 Hardware upgrade and code reuse will cause inconvenience , At the same time, it will also create obstacles for business layer developers who do not understand hardware .

To solve this problem , We layered the program structure , Separate business logic from hardware driver code :

// main.c
void main(void)
{
    
    int key;
    while (1)
    {
    
        key = read_key();
        if (key == UP)
            led_on();
        else
            led_off();
    }
}

// key.c
int read_key(void)
{
    
    GPIO_PinState key;
    key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
    if (key == GPIO_PIN_RESET)
        return 0;
    else
        return 1;
}

// led.c
void led_on(void)
{
    
    HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_RESET);
}

void led_off(void)
{
    
    HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_SET);
}

This solves the problem of coupling business logic code with hardware driver code , But there are still two unsolved problems :

One 、 Software compatibility problems caused by hardware version iteration

Two 、 Functional scalability issues

2.2 Introduce function pointer

Here we have to solve the first problem , Usually there are 3 Methods :

  1. Macro switch

    #define HARDWARE_VER 1
    
    // key.c
    //  Return value : 0 Indicates being pressed , 1 Indicates that it is loosened 
    int read_key(void)
    {
          
        GPIO_PinState key;
    #if (HARDWARE_VER == 1)
        key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
    #else
        key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_7);
    #endif
        if (key == GPIO_PIN_RESET)
            return 0;
        else
            return 1;            
    }
    

    Macro switch if more than one , It will be a disaster to maintain .

  2. stay EEPROM Save the hardware version number in , Call the hardware version difference interface according to the version number

    // key.c
    //  Return value : 0 Indicates being pressed , 1 Indicates that it is loosened 
    int read_key(void)
    {
          
        GPIO_PinState key;
        int ver = read_hardware_ver();
        
    	if (ver == 1)
    	    key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
    	else (ver == 2)
    	    key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_7);
    
        if (key == GPIO_PIN_RESET)
            return 0;
        else
            return 1;            
    }
    
    

    Similar to macro switches , More than one , It's hard to maintain .

  3. A function pointer

    // key.c
    int (*read_key)(void);
    
    //  Return value : 0 Indicates being pressed , 1 Indicates that it is loosened 
    int read_key_ver1(void)
    {
          
        GPIO_PinState key;
        key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
        if (key == GPIO_PIN_RESET)
            return 0;
        else
            return 1;            
    }
    
    //  Return value : 0 Indicates being pressed , 1 Indicates that it is loosened 
    int read_key_ver2(void)
    {
          
        GPIO_PinState key;
        key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
        if (key == GPIO_PIN_RESET)
            return 0;
        else
            return 1;            
    }
    
    void key_init()
    {
          
        int ver = read_hardware_ver();
        if (ver == 1)
            read_key = read_key_ver1;
        else
            read_key = read_key_ver2;
    }
    
    // main.c
    void main(void)
    {
          
        int key;
        
        key_init();
        
        while (1)
        {
          
            key = read_key();
            if (key == UP)
                led_on();
            else
                led_off();
        }
    }
    

I think this method should belong to the 2 An upgraded version of the method , It is also necessary to write the hardware version number to EEPROM in , Make judgments in the software , But the difference lies in the introduction of function pointers , You only need to judge once according to the version number during power on initialization , Assign the interface corresponding to the version to the pointer , There is no need to make a lot of judgment calls in subsequent code .

So the first problem is solved , Let's look at the second question , How to solve the problem of software scalability ?

There is a design principle in the design pattern :OCP, Opening and closing principle , Good design needs to be open to extensions , Turn off for changes . In other words, only new code is added during function expansion , Do not modify the existing code .

Back to the question , If we need to increase the number of buttons , According to our previous thinking, it should be :

// key.c
//  Return value : 0 Indicates being pressed , 1 Indicates that it is loosened 
int read_key1(void)
{
    
    GPIO_PinState key;
    key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
    if (key == GPIO_PIN_RESET)
        return 0;
    else
        return 1;
}

//  Return value : 0 Indicates being pressed , 1 Indicates that it is loosened 
int read_key2(void)
{
    
    GPIO_PinState key;
    key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_7);
    if (key == GPIO_PIN_RESET)
        return 1;
    else
        return 0;
}

or

// key.c
//  Return value : 0 Indicates being pressed , 1 Indicates that it is loosened 
int read_key(int which)
{
    
    GPIO_PinState key;
    switch (which)
    {
    
        case 0:
			key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
            if (key == GPIO_PIN_RESET)
                return 0;
            else
                return 1;
            break;
            
        case 1:
			key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_7);
            if (key == GPIO_PIN_RESET)
                return 1;
            else
                return 0;
            break;
            
    }
}

Both of these methods can alleviate the problem to some extent , But to cure the symptoms is not to cure the root . The former method will increase with the number of keys , More and more confusion at the call , Difficult to maintain . The latter method , As long as there is a button to add, it will modify our read_key function , In violation of the OCP principle .

2.3 Introduce structure

In order to solve problems with new ideas , Here new knowledge points are introduced : Structure .

 Please add a picture description

Let's start by layering the program ,main Functions belong to the application layer or business logic layer ,key_manager It belongs to the middle layer , The bottom layer belongs to the hardware driver layer , The management of keys is realized through the middle layer , At the same time, the business logic layer and the driver layer are decoupled .

2.3.1 key_system

// key_manager.h
typedef struct key {
    
	char *name;
    void (*init)(struct key *k);
    int (*read)(void);
}key, *p_key;

//  Initialization of all keys 
void key_init(void);

//  According to the key name Get the key 
key *get_key(char *name);
// key_manager.c
int key_cnt = 0;
key *keys[32];

void register_key(key *k)
{
    
	keys[key_cnt] = k;
	key_cnt++;
}

void key_init(void)
{
    
	k1_init();
	k2_init();
}

key *get_key(char *name)
{
    
	int i = 0; 
	for (i = 0; i < key_cnt; i++)
		if (strcmp(name, keys[i]->name) == 0)
			return keys[i];

	return 0;
}
// key1.c
//  Return value : 0 Indicates being pressed , 1 Indicates that it is loosened 
static int read_key1(void)
{
    
    GPIO_PinState key_status;
    key_status = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
    if (key_status == GPIO_PIN_RESET)
        return 0;
    else
        return 1;
}

static key k1 = {
    "k1", 0, read_key1};

void k1_init(void)
{
    
	register_key(&k1);
}
// key2.c
//  Return value : 0 Indicates being pressed , 1 Indicates that it is loosened 
static int read_key2(void)
{
    
    GPIO_PinState key_status;
    key_status = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_7);
    if (key_status == GPIO_PIN_RESET)
        return 1;
    else
        return 0;
}

static key k2 = {
    "k2", NULL, read_key2};

void k2_init(void)
{
    
	register_key(&k2);
}
// main.c
void main(void)
{
    
	key *k;
	
	key_init();	

	/*  Use a key  */
	k = get_key("k1");
	if (k == NULL)
		return;

	while (1)
	{
    
		if (k->read(k) == 0)
			led_on();
		else
			led_off();
	}
}

2.3.2 key_system_read_multi_key

There are still some problems with the current code , There is no decoupling between the business layer and the driver layer , stay main There are also specific keys in the function read Function call and state judgment . meanwhile , As the business layer, the middle layer is expected to read the status of all keys at the same time .

Then optimize the implementation of the middle tier :

// key_manager.h
typedef struct key {
    
	char *name;
    unsigned char id;
    void (*init)(struct key *k);
    int (*read)(void);
}key, *p_key;

#define KEY_UP 0xA
#define KEY_DOWN 0xB

//  Initialization of all keys 
void key_init(void);

//  Read the status of all keys 
int read_key(void);
// key_manager.c
int key_cnt = 0;
key *keys[32];

void register_key(key *k)
{
    
	keys[key_cnt] = k;
	key_cnt++;
}

void key_init(void)
{
    
	k1_init();
	k2_init();
}

int read_key(void)
{
    
    int val;
	for (int i = 0; i < key_cnt; i++)
    {
    
        val = keys[i]->read();
        if (val == -1)
            continue;
        else
            return val;
    }
    return -1;
}
// key1.c
//  Return value : 0 Indicates being pressed , 1 Indicates that it is loosened 
#define KEY1_ID 1
static int read_key1(void)
{
    
    static GPIO_PinState pre_key_status;
    GPIO_PinState key_status;
    key_status = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
    
    if (key_status == pre_key_status)
        return -1;
    pre_key_status = key_status;
    if (key_status == GPIO_PIN_RESET)
        return KEY_DOWN | (KEY1_ID << 8);
    else
        return KEY_UP | (KEY1_ID << 8);
}

static key k1 = {
    "k1", KEY1_ID, NULL, read_key1};

void k1_init(void)
{
    
	register_key(&k1);
}
// key2.c
//  Return value : 0 Indicates being pressed , 1 Indicates that it is loosened 
#define KEY2_ID 2
static int read_key1(void)
{
    
    static GPIO_PinState pre_key_status;
    GPIO_PinState key_status;
    key_status = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_7);
    
    if (key_status == pre_key_status)
        return -1;
    pre_key_status = key_status;
    if (key_status == GPIO_PIN_RESET)
        return KEY_UP | (KEY2_ID << 8);
    else
        return KEY_DOWN | (KEY2_ID << 8);
}

static key k2 = {
    "k2", KEY2_ID, NULL, read_key2};

void k2_init(void)
{
    
	register_key(&k2);
}
// main.c
void main(void)
{
    
	int val;
	
	key_init();	

	while (1)
	{
    
		val = read_key();

		if (val == -1)
		{
    
			/*  There are no buttons  */
		}
		else
		{
    
            key_status = val & 0xFF;
            key_id = (val>>8) & 0xFF:
			switch (key_status)
			{
    
				case KEY_UP: 
                    /* key_id  Release  */
					break;
				case KEY_DOWN: 
                    /* key_id  Press down  */
					break;
                default:
                    break;
            }
		}
	}
}

2.3.3 key_system_read_usr_irq

however , There are still some problems with this code , For example, the key detection now belongs to polling , If you can change key detection to interrupt mode , In the use of RTOS There is no need to poll regularly , You can wait for an interrupt to trigger .

here , You can introduce a fifo, Interrupt events as producers of data , The application layer acts as a consumer of data , Further decoupling .

here fifo The realization is from github Casually found on the .

// key_manager.h
typedef struct key {
    
	char *name;
    unsigned char id;
    void (*init)(struct key *k);
    int (*read)(void);
}key, *p_key;

#define KEY_UP 0xA
#define KEY_DOWN 0xB

//  Initialization of all keys 
void key_init(void);

//  Read key status 
int read_key(void)

//  towards fifo Write a key status 
void put_buffer(int val);

//  from fifo Read a key state 
int read_buffer(void);
// key_manager.c
int key_cnt = 0;
key *keys[32];

//  Define a Fifo Buffer 
static RingBufferPointer fifo;

void put_buffer(int val)
{
    
    ringBufferAdd(fifo, val);
}

int read_buffer()
{
    
    int val = -1;
    if (isRingBufferNotEmpty(fifo))
        val = ringBufferGet(fifo);
    return val;
}

void register_key(key *k)
{
    
	keys[key_cnt] = k;
	key_cnt++;
}

void key_init(void)
{
    
    fifo = getRingBufferInstance(100);
	k1_init();
	k2_init();
}

int read_key(void)
{
    
	return read_buffer();
}
// key0.c
#define KEY0_ID 0

static key k0 = {
    "k0", KEY0_ID, NULL, NULL};

void k0_init(void)
{
    
	register_key(&k0);
}

void key0_irq(void)
{
    
    int val;
    GPIO_PinState key_status;
    key_status = HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin);
    
    if (key_status == GPIO_PIN_RESET)
        val = KEY_DOWN | (KEY0_ID << 8);
    else
        val = KEY_UP | (KEY0_ID << 8);
    put_buffer(val);
}

// key1.c
#define KEY1_ID 1

static key k1 = {
    "k1", KEY1_ID, NULL, read_key1};

void k1_init(void)
{
    
	register_key(&k1);
}

void key1_irq(void)
{
    
    int val;
    GPIO_PinState key_status;
    key_status = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
    
    if (key_status == GPIO_PIN_RESET)
        val = KEY_DOWN | (KEY1_ID << 8);
    else
        val = KEY_UP | (KEY1_ID << 8);
    put_buffer(val);
}
// key2.c
#define KEY2_ID 2

static key k2 = {
    "k2", KEY2_ID, NULL, read_key2};

void k2_init(void)
{
    
	register_key(&k2);
}

void key2_irq(void)
{
    
    int val;
    GPIO_PinState key_status;
    key_status = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_7);
    
    if (key_status == GPIO_PIN_RESET)
        val = KEY_DOWN | (KEY2_ID << 8);
    else
        val = KEY_UP | (KEY2_ID << 8);
    put_buffer(val);
}
// stm32f7xx_it.c
void EXTI2_IRQHandler(void)
{
    
  HAL_GPIO_EXTI_IRQHandler(KEY1_Pin);
}

void EXTI3_IRQHandler(void)
{
    
  HAL_GPIO_EXTI_IRQHandler(KEY0_Pin);
}

void EXTI15_10_IRQHandler(void)
{
    
  HAL_GPIO_EXTI_IRQHandler(KEY2_Pin);
}
// main.c
void main(void)
{
    
    int val;
    char key_status;
    char key_id;
	
	key_init();	

    //  If it is rtos The polling method is not applicable 
	while (1)
	{
    
        val = read_key();
        if (val != -1)
        {
    
            key_status = val & 0xFF;
            key_id = (val >> 8) & 0xFF;
            switch (key_status)
            {
    
                case KEY_DOWN:
                    if (key_id == 0)
                    {
    
                        HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
                    }
                    else if (key_id == 1)
                    {
    
                        HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
                    }
                    else if (key_id == 2)
                    {
                    
                        HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);
                        HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
                    }
                    break;
                case KEY_UP:
                    break;
            }
        }
	}
}

void HAL_GPIO_EXTI_Callback(uint16_t pin)
{
    
    HAL_Delay(50);
    switch (pin)
    {
    
        case KEY0_Pin:
            key0_irq();
            break;
        case KEY1_Pin:
            key1_irq();
            break;
        case KEY2_Pin:
            key2_irq();
            break;
        default:
            break;
    }
}

3. Experimental process

According to the idea above , Practice on the development board .

3.1 establish cubemx engineering

 Please add a picture description

After checking the schematic diagram of the development board, we know , The resources are distributed as follows :

  • The development board has user buttons 3 individual , Namely KEY0(PH3)、KEY1(PH2)、KEY2(PC13)
  • The development board has LED2 individual , Namely LED0(PB1)、LED1(PB0)

 Please add a picture description

For this 5 individual GPIO To configure , And then generate the code .

3.2 Project structure

 Please add a picture description

3.3 Experimental results

Press down KEY0,LED0 Light up , Press down KEY1,LED1 Light up , Press down KEY2,LED0、LED1 It goes out at the same time . The experiment is finished .

4. summary

Actually, according to Mr. Wei's course , take led A subsystem has also been established , Manage through the middle tier led, To reduce code coupling , Enhance code scalability 、 reusability . Because of ideas and key The subsystems are basically the same , There is no further implementation .

Through this lesson, I learned the idea of hierarchical design of software structure , Consider future functional extensions from the beginning of the design 、 The robustness of the code . By sacrificing a little bit of operational efficiency , To improve the maintainability of the whole project .

原网站

版权声明
本文为[_ kukukukiki__]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202180600427679.html