4303 views|0 replies

291

Posts

0

Resources
The OP
 

The microcontroller application architecture summarized by experts is recommended for collection [Copy link]

After groping and experimenting at work, it is concluded that there are three types of architectures for microcontroller applications:
1. Simple front-end and back-end sequential execution programs. This type of writing is the method used by most people. There is no need to think about the specific architecture of the program. You can directly write the application program through the execution sequence.

2. Time slice polling method. This method is a method between sequential execution and operating system.

3. Operating system. This method should be the highest level of application writing.

Let's talk about the advantages and disadvantages and scope of application of these three methods.
1. Sequential execution method
This method is a good method when the application is relatively simple and the real-time and parallel requirements are not too high. The program design is simple and the ideas are relatively clear. However, when the application is more complex, if there is no complete flowchart, it is probably difficult for others to understand the running status of the program. Moreover, as the program functions increase, the brains of the engineers who write the application programs also begin to be confused. That is, it is not conducive to upgrade maintenance, nor is it conducive to code optimization. I wrote a few more complex applications. At the beginning, I used this method. Although I was able to realize the functions in the end, my thinking was always in a state of confusion. As a result, the program has never satisfied me.

Most people will use this method, and the education we receive is basically using this method. For those of us MCU engineers who have not learned data structures and program architecture, it is undoubtedly difficult to make a big improvement in application design. It also makes it difficult for applications written by different engineers to benefit and learn from each other.

I suggest that if you like to use this method, if you write a more complex application, you must first clear your mind and design a complete flowchart before writing the program, otherwise the consequences will be serious. Of course, if the program itself is very simple, this method is still a very necessary choice.

The following is a sequential execution program model for comparison with the following two methods:

Code

/**************************************************************************************
* FunctionName : main()
* Description  : 主函数
* EntryParameter : None
* ReturnValue  : None
**************************************************************************************/

int main(void)
{
  uint8 keyValue;

  InitSys();      // 初始化

  while (1)
  {
  TaskDisplayClock();
  keyValue = TaskKeySan();
  switch (keyValue)
   {
    case x: TaskDispStatus(); break;
    ...
    default: break;
  }
  }
}


2. Time Slice Polling Method
The time slice polling method is mentioned in many books, and it often appears with the operating system, that is, it is often used in the operating system. However, the time slice polling method we are talking about here is not hung under the operating system, but used in the foreground and background programs. It is also the method that this post will explain and introduce in detail.

Although there are many books that introduce the time slice polling method, most of them are not systematic, but just mention the concept. Below I will introduce this mode in detail, and refer to other people's code to establish a method of time slice polling architecture program, I think it will be a certain reference for beginners.

Here we first introduce the multiplexing function of the timer.

Use 1 timer, which can be any timer, no special explanation is given here. Assuming that there are 3 tasks below, we should do the following work:

1. Initialize the timer. Here, it is assumed that the timer interrupt is 1ms (of course, you can change it to 10ms. This is the same as the operating system. If the interrupt is too frequent, the efficiency will be low, and if the interrupt is too long, the real-time performance will be poor).

2. Define a value:
code

#define TASK_NUM (3)      //这里定义的任务数为3,表示有三个任务会使用此定时器定时。

uint16 TaskCount[TASK_NUM] ;   //这里为三个任务定义三个变量来存放定时值
uint8TaskMark[TASK_NUM];     //同样对应三个标志位,为0表示时间没到,为1表示定时时间到。



3. Add the following code to the timer interrupt service function:

/**************************************************************************************
* FunctionName : TimerInterrupt()
* Description : 定时中断服务函数
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
void TimerInterrupt(void)
{
  uint8 i;

  for (i=0; i<TASKS_NUM; i++)
  {
  if (TaskCount[i])
  {
    TaskCount[i]--;
    if (TaskCount[i] == 0)
    {
      TaskMark[i] = 0x01;
    }
  }
 }
}


Code explanation: Timer interrupt service function, judge one by one in the interrupt, if the timer value is 0, it means that this timer is not used or this timer has completed the timing, no need to process. Otherwise, the timer is reduced by one, and when it reaches zero, the corresponding flag value is 1, indicating that the timer value of this task has expired.

4. In our application, add the following code where the application timing is required. Let's take Task 1 as an example:

Code

TaskCount[0] = 20;   // 延时20ms
TaskMark[0]= 0x00; // 启动此任务的定时器


At this point, we only need to determine whether TaskMark[0] is 0x01 in the task. Add other tasks in the same way, and the reuse problem of a timer has been solved. Friends who need it can try it, the effect is good. . . . . . . . . . . . . . .

Through the reuse of a timer above, we can see that while waiting for a timer to arrive, we can loop to determine the flag bit and execute other functions at the same time.

Loop to determine the flag bit:
Then we can think about it, if the loop determines the flag bit, is it the same as the sequential execution program introduced above? A large loop, but this delay is more accurate than the ordinary for loop, and accurate delay can be achieved.

Execute other functions:
Then if we execute other functions when a function is delayed, making full use of CPU time, is it similar to the operating system? However, the task management and switching of the operating system is very complicated. Next, we will use this method to build a new application.

The architecture of the time slice polling method:

1. Design a structure:
Code

// 任务结构
typedef struct _TASK_COMPONENTS
{
  uint8 Run;     // 程序运行标记:0-不运行,1运行
  uint8 Timer;    // 计时器
  uint8 ItvTime;    // 任务运行间隔时间
  void (*TaskHook)(void);  // 要运行的任务函数
} TASK_COMPONENTS;   // 任务定义


The design of this structure is very important. It uses 4 parameters. The comments are very detailed and will not be described here.

2. The task running flag is displayed. This function is equivalent to the interrupt service function. This function needs to be called in the interrupt service function of the timer. It is separated here for easy transplantation and understanding.
Code

/**************************************************************************************
* FunctionName : TaskRemarks()
* Description  : 任务标志处理
* EntryParameter : None
* ReturnValue  : None
**************************************************************************************/
void TaskRemarks(void)
{
  uint8 i;
  for (i=0; i<TASKS_MAX; i++)    // 逐个任务时间处理
  {
   if (TaskComps[i].Timer)    // 时间不为0
  {
    TaskComps[i].Timer--;   // 减去一个节拍
    if (TaskComps[i].Timer == 0)   // 时间减完了
    {
     TaskComps[i].Timer = TaskComps[i].ItvTime;   // 恢复计时器值,从新下一次
     TaskComps[i].Run = 1;   // 任务可以运行
    }
  }
 }
}


Please carefully compare this function with the timed multiplexing function above. Is it the same?

3. Task processing:
code

/**************************************************************************************
* FunctionName : TaskProcess()
* Description  : 任务处理
* EntryParameter : None
* ReturnValue  : None
**************************************************************************************/
void TaskProcess(void)
{
  uint8 i;
  for (i=0; i<TASKS_MAX; i++)   // 逐个任务时间处理
  {
   if (TaskComps[i].Run)   // 时间不为0
  {
     TaskComps[i].TaskHook();   // 运行任务
     TaskComps[i].Run = 0;    // 标志清0
  }
  } 
}


This function is used to determine when to execute a task and implement task management operations. The user only needs to call this function in the main() function, and does not need to call and process task functions separately.

At this point, the architecture of a time-slice polling application has been built. Do you think it is very simple? This architecture only requires two functions and a structure. For the sake of application, an enumeration variable will be established below.

Let's talk about how to apply it. Suppose we have three tasks: clock display, key scanning, and working status display.

1. Define a structure variable as defined above:

Code

/**************************************************************************************
* Variable definition         
**************************************************************************************/
static TASK_COMPONENTS TaskComps[] =
{
  {0, 60, 60, TaskDisplayClock},    // 显示时钟
  {0, 20, 20, TaskKeySan},     // 按键扫描
  {0, 30, 30, TaskDispStatus},    // 显示工作状态
 // 这里添加你的任务。。。。
};


When defining variables, we have initialized the values. The initialization of these values is very important and is related to the specific execution time priority, etc. This needs to be mastered by yourself.

① It roughly means that we have three tasks, and the following clock display is executed every 1s. Because the minimum unit of our clock is 1s, it is enough to display it once after the second changes.

② Since the key will jitter when it is pressed, and we know that the jitter of a general key is about 20ms, then we generally extend 20ms in the sequential execution function, and here we scan once every 20ms, which is very good. It achieves the purpose of de-jittering and will not miss the key input.

③ In order to be able to display other prompts and work interfaces after pressing the key, we design it to display every 30ms. If you feel that the response is slow, you can make these values smaller. The name behind is the corresponding function name. You must write this function name and the same three tasks in the application.

2. Task list:
code

// 任务清单
typedef enum _TASK_LIST
{
  TAST_DISP_CLOCK,    // 显示时钟
  TAST_KEY_SAN,     // 按键扫描
  TASK_DISP_WS,     // 工作状态显示
 // 这里添加你的任务。。。。
 TASKS_MAX               // 总的可供分配的定时任务数目
} TASK_LIST;


Take a closer look. The purpose of defining this task list here is actually the value of the parameter TASKS_MAX. The other values have no specific meaning and are only used to clearly show the relationship between the surface tasks.

3. Write a task function:
Code

/**************************************************************************************
* FunctionName : TaskDisplayClock()
* Description  : 显示任务
* EntryParameter : None
* ReturnValue  : None
**************************************************************************************/
void TaskDisplayClock(void)
{

}
/**************************************************************************************
* FunctionName : TaskKeySan()
* Description  : 扫描任务
* EntryParameter : None
* ReturnValue  : None
**************************************************************************************/
void TaskKeySan(void)
{

}
/**************************************************************************************
* FunctionName : TaskDispStatus()
* Description  : 工作状态显示
* EntryParameter : None
* ReturnValue  : None
**************************************************************************************/
void TaskDispStatus(void)
{

}


// Add other tasks here. . . . . . . . .

Now you can write tasks according to your needs.

4. Main function:
code

/**************************************************************************************
* FunctionName : main()
* Description  : 主函数
* EntryParameter : None
* ReturnValue  : None
**************************************************************************************/
int main(void)
{
  InitSys();      // 初始化
  while (1)
  {
  TaskProcess();     // 任务处理
  }
}


At this point, the architecture of our time slice polling application is complete. You only need to add your own task function where we prompt. Isn't it very simple? Does it feel like an operating system? Why not

try it and see if the tasks do not interfere with each other? Can they run in parallel? Of course, it is important to note that when transferring data between tasks, global variables need to be used. In addition, it is also necessary to pay attention to the division of tasks and the execution time of tasks. When writing tasks, try to complete the tasks as soon as possible. ... Code






/**************************************************************************************
* FunctionName : main()
* Description  : 主函数
* EntryParameter : None
* ReturnValue  : None
**************************************************************************************/
int main(void)
{
  OSInit();      // 初始化uCOS-II
  OSTaskCreate((void (*) (void *)) TaskStart,  // 任务指针
      (void *) 0,    // 参数
      (OS_STK *) &TaskStartStk[TASK_START_STK_SIZE - 1], // 堆栈指针
      (INT8U ) TASK_START_PRIO);  // 任务优先级
  OSStart();             // 启动多任务环境
             
  return (0);
}

代 码
/**************************************************************************************
* FunctionName : TaskStart()   
* Description  : 任务创建,只创建任务,不完成其他工作
* EntryParameter : None
* ReturnValue  : None
**************************************************************************************/
void TaskStart(void* p_arg)
{
  OS_CPU_SysTickInit();             // Initialize the SysTick.
#if (OS_TASK_STAT_EN > 0)
  OSStatInit();               // 这东西可以测量CPU使用量
#endif
OSTaskCreate((void (*) (void *)) TaskLed, // 任务1
      (void *) 0,     // 不带参数
      (OS_STK *) &TaskLedStk[TASK_LED_STK_SIZE - 1],// 堆栈指针
      (INT8U ) TASK_LED_PRIO);   // 优先级
// Here the task of creating your
     
  while (1)
  {
  OSTimeDlyHMSM(0, 0, 0, 100);
  }
}


It is not difficult to see that the time slice polling method has a relatively large advantage, that is, it has the advantages of the sequential execution method and the advantages of the operating system. The structure is clear, simple, and very easy to understand.

牛人总结的单片机应用程序架构.docx

18.44 KB, downloads: 16

This post is from MCU
 

Guess Your Favourite
Just looking around
Find a datasheet?

EEWorld Datasheet Technical Support

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号
快速回复 返回顶部 Return list