The embedded environment is generally low configuration, partial to hardware, the bottom layer, tight resources, the code is mainly C language and assembly, and the code application logic is simple. But with the advent of the AIOT era, the situation has changed. The performance resources of the chip are gradually improved, and the business logic is gradually becoming more and more complex. Compared with the efficiency of the code, the requirements of code reuse and portability are higher and higher, so as to obtain shorter project cycle and higher maintainability. The following is a common software framework for embedded devices in the AIOT era.
Design pattern: high-level language, high-end, architecture, etc. In the AIOT era, what kind of spark can design patterns and embedded make? The design pattern can be described as: for a kind of similar problems, after the continuous attempts of predecessors, the recognized and effective solutions to such problems are summarized.
Embedded is mainly developed in C language and process oriented, and design patterns are common in high-level language (object-oriented). At present, most books describing design patterns on the market use JAVA language. Can C language realize design patterns? Design pattern has nothing to do with language. It is a method to solve problems. It can be implemented in JAVA and C language. Similarly, JAVA programmers will encounter problems that need to be handled with patterns, and C programmers may also encounter them. Therefore, it is necessary to learn design patterns.
Pattern trap: design pattern is an effective solution to some specific problems. Not all problems can match the corresponding design pattern. Therefore, we can't blindly pursue design patterns. Sometimes simple and direct treatment is more effective. For some problems, there is no appropriate mode, and some design principles can be met as much as possible, such as the opening and closing principle (open to expansion and closed to modification)
1. Observer mode: define a one to many dependency between objects. When the state of an object changes, all dependent objects will be notified automatically.
The subject object provides a unified registration interface and registration functions. The observer itself instantiates the observer_intf interface, and then use the registration function to add it to the corresponding topic list. When the topic state changes, all objects in the list will be notified in turn.
struct observer_ops { void*(handle)(uint8_t evt); }; struct observer_intf { struct observer_intf* next; const char* name; void* condition; const struct observer_ops *ops; } int observer_register(struct topical* top , struct observer_intf* observer);
When the subject status changes, all observers will be notified. The observer can also set conditions to choose whether to receive the notification
struct observer_intf observer_list; void XXXX_topical_evt(uint8_t evt) { struct observer_intf* cur_observer = observer_list.next; uint8_t* condition = NULL; while(cur_observer != NULL) { condition = (uint8_t*)cur_observer->condition; if(NULL == condition || (condition && *condition)) { if(cur_observer->ops->handle){ cur_observer->ops->handle(evt); } } cur_observer = cur_observer->next; } }
Example: embedded bare metal low power framework
Device power consumption distribution
The line loss, power circuit and other software cannot be controlled, so it is not discussed. On board peripherals, such as sensors, may enter the low power consumption mode through a command configuration, or the hardware supports controlling the power supply of peripherals to control the power consumption. The on-chip and external devices and the peripherals inside the chip control the power consumption by unloading the relevant drivers and turning off the clock configuration working mode.
Device wake-up mode
Active wake-up
When a timing event of the system arrives, the system is actively awakened to deal with the event
Passive arousal
The system is asleep and awakened by external events. For example, the serial port receives a packet of data, the sensor detects the change and notifies the chip through the pin
Conditions under which the system allows sleep
There is no data being sent or received by the peripheral
Cache no data to process
Application layer state is idle (no events need to be handled)
Implementation of PM framework based on observer mode
Interfaces provided by PM components
struct pm { struct pm* next; const char* name; void(*init)(void); void(*deinit(void); void* condition; }; static struct pm pm_list; static uint8_t pm_num; static uint8_t pm_status; int pm_register(const struct pm* pm , const char* name) { struct pm* cur_pm = &pm_list; while(cur_pm->next) { cur_pm = cur_pm->next; } cur_pm->next = pm; pm->next = NULL; pm->name = name; pm_num++; } void pm_loop(void) { uint32_t pm_condition = 0; struct pm* cur_pm = pm_list.next; static uint8_t cnt; /*check all condition*/ while(cur_pm) { if(cur_pm->condition){ pm_condition |= *((uint32_t*)(cur_pm->condition)); } cur_pm = cur_pm->next; } if(pm_condition == 0) { cnt++; if(cnt>=5) { pm_status = READY_SLEEP; } } else { cnt = 0; } if( pm_status == READY_SLEEP) { cur_pm = pm_list.next; while(cur_pm) { if(cur_pm->deinit){ cur_pm->deinit(); } cur_pm = cur_pm->next; } pm_status = SLEEP; ENTER_SLEEP_MODE(); } /*sleep--->wakeup*/ if(pm_status == SLEEP) { pm_status = NORMAL; cur_pm = pm_list.next; while(cur_pm) { if(cur_pm->init){ cur_pm->init(); } cur_pm = cur_pm->next; } } }
struct uart_dev { ... struct pm pm; uint32_t pm_condition; }; struct uart_dev uart1; void hal_uart1_init(void); void hal_uart1_deinit(void); void uart_init(void) { uart1.pm.init = hal_uart1_init; uart1.pm.deinit = hal_uart1_deinit; uart1.pm.condition = &uart1.pm_condition; pm_register(&uart1.pm , "uart1"); }
conclusion
PM power management can form a separate module. When the power consumption of peripherals increases, just implement the interface and register
By defining the segment export operation, the registration logic of the application layer or peripherals can be more simplified
It is convenient to debug and print out the modules currently meeting the sleep conditions of the system
Through the division of condition fields, it should be possible to realize partial sleep of the system
Responsibility chain model
scene
In real life, it is a common scene that an event (task) needs to be processed by multiple objects. For example, in the reimbursement process, the company's employees should first sort out the reimbursement form and check the reimbursement amount. If there is any error, continue to check and sort until there is no error. Submit the reimbursement form to the finance department for verification. After checking, judge the amount and quantity. If it is less than a certain amount, the finance department can directly approve it. If the amount exceeds the range, the reimbursement form will be circulated to the general manager, The whole task is not over until it is approved. There are many similar scenarios. For example, a WIFI module is configured. In order to connect the module to WIFI correctly through AT instructions, the configuration instructions need to be sent in a certain order, such as setting the setting mode, setting the WIFI name and password to be connected. Each configuration instruction is sent, the module will return the configuration result, and the sender needs to judge the correctness of the result, Then select whether to send the next instruction or retransmit.
To sum up, a series of tasks need to be processed in strict accordance with the sequential logic of the timeline, as shown in the figure below.
When there is a system, such logic can be easily realized by blocking delay, as follows:
void process_task(void) { task1_process(); msleep(1000); task2_process(); mq_recv(¶m , 1000); task3_process(); while(mq_recv(¶m , 1000) != OK) { if(retry) { task3_process(); --try; } } }
In the case of bare metal, in order to ensure the real-time performance of the system, blocking delay cannot be used. Generally, timed events and state machine are used to realize:
/*Reply callback of task*/
void task_ans_cb(void* param)
{
if(task==task2)
{
task_state = task3;
process_task();
}
}
Compared with the system implementation, the implementation of bare metal is more complex. In order to avoid blocking, the logic of sequential delay can only be realized through status and timer. It can be seen that the implementation process is quite decentralized, and the processing of a single task is scattered into three functions. The consequences are inconvenient modification and transplantation. In practical applications, there are quite a lot of similar logic. If implemented according to the above method, it will lead to strong coupling of applications.
realization
It can be found that the above scenario has the following characteristics:
Tasks are executed in sequence. The next task can be executed only after the current task has been executed (with conclusion, success or failure)
The execution result of the previous task will affect the execution of the next task
Tasks have some characteristics, such as timeout, delay time, and number of retries
From the above information, we can abstract such a model: tasks are nodes, and each task node has its attributes, such as timeout, delay, Retry, parameters, processing methods and execution results. When it is necessary to execute a series of tasks in sequence, string the task nodes into a chain in sequence and start the chain operation, then run from the first node of the task chain, and the operation results can be OK, BUSY and ERROR. If ok, it indicates that the node has been processed and deleted from the task chain. ERROR indicates that there is a running ERROR. The task chain will stop running and carry out ERROR callback. A user can decide whether to continue running. BUSY indicates that the task chain is waiting for response or waiting for delay. When all the nodes in the whole task chain have been executed, a successful callback is performed.
void process_task(void) { switch(task_state) { case task1: task1_process(); set_timeout(1000);break; case task2: task1_process(); set_timeout(1000);break; case task3: task1_process(); set_timeout(1000)break; default:break; } } /*Timer timeout callback*/ void timeout_cb(void) { if(task_state == task1) { task_state = task2; process_task(); } else //task2 and task3 { if(retry) { retry--; process_task(); } } } /*Reply callback of task*/ void task_ans_cb(void* param) { if(task==task2) { task_state = task3; process_task(); } }
/*shadow node api type for req_chain src*/ typedef struct shadow_resp_chain_node { uint16_t timeout; uint16_t duration; uint8_t init_retry; uint8_t param_type; uint16_t retry; /*used in mpool*/ struct shadow_resp_chain_node* mp_prev; struct shadow_resp_chain_node* mp_next; /*used resp_chain*/ struct shadow_resp_chain_node* next; node_resp_handle_fp handle; void* param; }shadow_resp_chain_node_t;
Necessity of using memory pool: in fact, AT the same time, the number of responsibility chains and the number of nodes in a single chain are relatively limited, but there are quite a lot of types. For example, a module that supports AT instructions may support dozens of AT instructions, but only 3-5 instructions may be used when performing a configuration operation. If all nodes are statically defined, it will consume a lot of memory resources. Therefore, dynamic allocation is necessary.
Initialize the node memory pool, and all nodes in the memory pool will be added to free_ list. When applying for a node, the first idle node will be taken out and added to the used node_ List and access to the responsibility chain. When a node in the responsibility chain is completed, it will be automatically recycled (deleted from the responsibility chain, deleted from the used_list, and then added to the free_list)
Definition of responsibility chain data structure
typedef struct resp_chain { bool enable; //enble == true Start of responsibility chain bool is_ans; //Response received, and void* param Together form a response signal uint8_t state; const char* name; void* param; TimerEvent_t timer; bool timer_is_running; shadow_resp_chain_node_t node; //Node chain void(*resp_done)(void* result); //Execution result callback }resp_chain_t;
Responsibility chain initialization
void resp_chain_init(resp_chain_t* chain , const char* name , void(*callback)(void* result)) { RESP_ASSERT(chain); /*only init one time*/ resp_chain_mpool_init(); chain->enable = false; chain->is_ans = false; chain->resp_done = callback; chain->name = name; chain->state = RESP_STATUS_IDLE; chain->node.next = NULL; chain->param = NULL; TimerInit(&chain->timer,NULL); }
Add node of responsibility chain
int resp_chain_node_add(resp_chain_t* chain , node_resp_handle_fp handle , void* param) { RESP_ASSERT(chain); BoardDisableIrq(); shadow_resp_chain_node_t* node = chain_node_malloc(); if(node == NULL) { BoardEnableIrq(); RESP_LOG("node malloc error ,no free node"); return -2; } /*Initialize the node and join the responsibility chain*/ shadow_resp_chain_node_t* l = &chain->node; while(l->next != NULL) { l = l->next; } l->next = node; node->next = NULL; node->handle = handle; node->param = param; node->timeout = RESP_CHIAN_NODE_DEFAULT_TIMEOUT; node->duration = RESP_CHIAN_NODE_DEFAULT_DURATION; node->init_retry = RESP_CHIAN_NODE_DEFAULT_RETRY; node->retry = (node->init_retry == 0)? 0 :(node->init_retry-1); BoardEnableIrq(); return 0; }
Start of responsibility chain
void resp_chain_start(resp_chain_t* chain) { RESP_ASSERT(chain); chain->enable = true; }
Response of responsibility chain
void resp_chain_set_ans(resp_chain_t* chain , void* param) { RESP_ASSERT(chain); if(chain->enable) { chain->is_ans = true; if(param != NULL) chain->param = param; else { chain->param = "NO PARAM"; } } }
Operation of responsibility chain
int resp_chain_run(resp_chain_t* chain) { RESP_ASSERT(chain); if(chain->enable) { shadow_resp_chain_node_t* cur_node = chain->node.next; /*maybe ans occur in handle,so cannot change state direct when ans comming*/ if(chain->is_ans) { chain->is_ans = false; chain->state = RESP_STATUS_ANS; } switch(chain->state) { case RESP_STATUS_IDLE: { if(cur_node) { uint16_t retry = cur_node->init_retry; if(cur_node->handle) { cur_node->param_type = RESP_PARAM_INPUT; chain->state = cur_node->handle((resp_chain_node_t*)cur_node ,cur_node->param); } else { RESP_LOG("node handle is null ,goto next node"); chain->state = RESP_STATUS_OK; } if(retry != cur_node->init_retry) { cur_node->retry = cur_node->init_retry>0?(cur_node- >init_retry-1):0; } } else { if(chain->resp_done) { chain->resp_done((void*)RESP_RESULT_OK); } chain->enable = 0; chain->state = RESP_STATUS_IDLE; TimerStop(&chain->timer); chain->timer_is_running = false; } break; } case RESP_STATUS_DELAY: { if(chain->timer_is_running == false) { chain->timer_is_running = true; TimerSetValueStart(&chain->timer , cur_node->duration); } if(TimerGetFlag(&chain->timer) == true) { chain->state = RESP_STATUS_OK; chain->timer_is_running = false; } break; } case RESP_STATUS_BUSY: { /*waiting for ans or timeout*/ if(chain->timer_is_running == false) { chain->timer_is_running = true; TimerSetValueStart(&chain->timer , cur_node->timeout); } if(TimerGetFlag(&chain->timer) == true) { chain->state = RESP_STATUS_TIMEOUT; chain->timer_is_running = false; } break; } case RESP_STATUS_ANS: { /*already got the ans,put the param back to the request handle*/ TimerStop(&chain->timer); chain->timer_is_running = false; if(cur_node->handle) { cur_node->param_type = RESP_PARAM_ANS; chain->state = cur_node->handle((resp_chain_node_t*)cur_node , chain->param); } else { RESP_LOG("node handle is null ,goto next node"); chain->state = RESP_STATUS_OK; } break; } case RESP_STATUS_TIMEOUT: { if(cur_node->retry) { cur_node->retry--; /*retry to request until cnt is 0*/ chain->state = RESP_STATUS_IDLE; } else { chain->state = RESP_STATUS_ERROR; } break; } case RESP_STATUS_ERROR: { if(chain->resp_done) { chain->resp_done((void*)RESP_RESULT_ERROR); } chain->enable = 0; chain->state = RESP_STATUS_IDLE; TimerStop(&chain->timer); chain->timer_is_running = false; cur_node->retry = cur_node->init_retry>0?(cur_node->init_retry-1):0; chain_node_free_all(chain); break; } case RESP_STATUS_OK: { /*get the next node*/ cur_node->retry = cur_node->init_retry>0?(cur_node->init_retry-1):0; chain_node_free(cur_node); chain->node.next = chain->node.next->next; chain->state = RESP_STATUS_IDLE; break; } default: break; } } return chain->enable; }
test case
Define and initialize the responsibility chain
void chain_test_init(void) { resp_chain_init(&test_req_chain , "test request" , test_req_callback); }
Define the run function and call it in the main loop.
void chain_test_run(void) { resp_chain_run(&test_req_chain); }
The test node adds and starts the trigger function
void chain_test_tigger(void) { resp_chain_node_add(&test_req_chain , node1_req ,NULL); resp_chain_node_add(&test_req_chain , node2_req,NULL); resp_chain_node_add(&test_req_chain , node3_req,NULL); resp_chain_start(&test_req_chain); }
Implement the node request function respectively
/*Execute the next node after a delay of 1s*/ int node1_req(resp_chain_node_t* cfg, void* param) { cfg->duration = 1000; RESP_LOG("node1 send direct request: delay :%d ms" , cfg->duration); return RESP_STATUS_DELAY; } /*Timeout 1S, retransmission times 5 times*/ int node2_req(resp_chain_node_t* cfg , void* param) { static uint8_t cnt; if(param == NULL) { cfg->init_retry = 5; cfg->timeout = 1000; RESP_LOG("node2 send request max retry:%d , waiting for ans..."); return RESP_STATUS_BUSY; } RESP_LOG("node2 get ans: %d",(int)param); return RESP_STATUS_OK; } /*Non asynchronous request*/ int node3_req(resp_chain_node_t* cfg , void* param) { RESP_LOG("node4 send direct request"); return RESP_STATUS_OK; } void ans_callback(void* param) { resp_chain_set_ans(&test_req_chain , param); }
conclusion
The bare metal processing sequential delay task is realized
It greatly simplifies the implementation of the application. Users only need to implement the response processing function and call the interface to add, which can be executed according to the time requirements
If the parameter is empty, it indicates a request; otherwise, it indicates a response. (in some cases, the request may also have parameters, such as the LAP protocol described below. At this time, you need to judge the type of parameters.)