Cuatro arquitecturas de aplicaciones integradas

 

Después de tantear y experimentar en el trabajo, se concluye que existen cuatro arquitecturas de aplicación general de microcomputadoras de un solo chip: 

1. Ejecución simple de front-end y back-end del programa. Este tipo de escritura es el método que la mayoría de la gente usa. No necesita pensar en la estructura específica del programa. Puede escribir la aplicación directamente a través de la ejecución secuencia.

2. Máquina de estado

3. Método de sondeo por intervalo de tiempo, este método es un método entre la ejecución secuencial y el sistema operativo. 

4. Sistema operativo, este método debe ser el más alto nivel de programación de aplicaciones. 

 

 

 

Tabla de contenido

1. Método de ejecución secuencial

En segundo lugar, la máquina de estado

1. Los cuatro elementos de una máquina de estados

El primer tipo: máquina de estado de la estructura de la caja del interruptor

La segunda máquina de estado: estructura de la declaración ifelse máquina de estado

El tercer tipo de máquina de estado: máquina de estado de activación de mensajes

Tres, método de sondeo por intervalo de tiempo

Cuarto, el sistema operativo


Hablemos de los pros y contras y el alcance de la adaptación de estos cuatro métodos respectivamente.

1. Método de ejecución secuencial

       Este método, también llamado método de ejecución frontal y posterior, generalmente ejecuta el programa principal en la función principal e ingresa a la función de interrupción después de que ocurre la interrupción. Esta aplicación es relativamente simple y es un buen método cuando los requisitos de paralelismo y tiempo real no son demasiado altos El diseño del programa es simple y el pensamiento claro. Pero cuando la aplicación es más complicada, si no hay un diagrama de flujo completo, puede ser difícil para otros comprender el estado de ejecución del programa y, a medida que aumenta la función del programa, comienza el cerebro del ingeniero que escribe la aplicación. confundirse. Eso no favorece la actualización y el mantenimiento, y tampoco favorece la optimización del código. Escribí algunas aplicaciones más complicadas. Usé este método al principio. Aunque puedo darme cuenta de la función al final, mi pensamiento ha estado en un estado de confusión. Como resultado, el programa no ha podido satisfacerse. 

        La mayoría de la gente utilizará este método, y la educación que recibimos básicamente utiliza este método. Para aquellos de nosotros que básicamente nunca hemos aprendido la estructura de datos y la arquitectura de programas, sin duda es difícil hacer una gran mejora en el diseño de programas de aplicación, lo que también dificulta que las aplicaciones escritas por diferentes ingenieros se beneficien y aprendan unas de otras. 

       Te sugiero que si eres un internauta al que le gusta usar este método, si escribes una aplicación más compleja, primero debes despejar tu mente y diseñar un diagrama de flujo completo antes de escribir el programa, de lo contrario las consecuencias serán muy graves. Por supuesto, si la lógica del producto en sí es relativamente simple, este método también se puede utilizar para lograr mejores funciones.

      Aquí hay un modelo de programa para ejecución secuencial, que es conveniente para comparar con los siguientes cuatro métodos: 

Código 

La función principal se procesa en el programa principal, que se denomina primer plano.

/**************************************************************************************
* 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;
        }
    }
} 

Interrumpir la ejecución de la función, llamada fondo

/****************************************************************************
* @file void USART1_IRQHandler(void)
* @brief 串口1中断服务函数
* @explain
* @parameter 无
* @return 无
****************************************************************************/
void USART1_IRQHandler(void)
{
    u8 tem=0;
    if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET)
    {
        tem = USART1->TDR;//先读SR,然后读DR才能清除
        tem = USART1->RDR;
        USART_ClearITPendingBit(USART1, USART_IT_IDLE);
    }

}

En segundo lugar, la máquina de estado

       Hablando de programación de microcontroladores, tenemos que hablar de máquinas de estado. Como marco principal de la programación de software, las máquinas de estado se han utilizado en varios lenguajes, incluido el lenguaje C. En un programa claro y eficiente, debe haber una máquina de estado. Emerge. La máquina de estado de la aplicación flexible no solo hace que el programa sea más eficiente, sino que también tiene una buena legibilidad y escalabilidad. El estado está en todas partes, hay un estado en el estado, mientras domines este tipo de pensamiento y lo conviertas en un hábito en tu programación, creo que te beneficiarás mucho.

1. Los cuatro elementos de una máquina de estados

       La máquina de estado se puede resumir en 4 elementos, a saber, estado actual, condición, acción y segundo estado. Este tipo de inducción se basa principalmente en la consideración de la causalidad interna de la máquina de estados. El "estado actual" y la "condición" son la causa, y la "acción" y el "segundo estado" son el resultado. Los detalles son los siguientes:

      ① Estado actual: se refiere al estado actual.

      ②Condiciones: también conocidas como "eventos". Cuando se cumple una condición, se activará una acción o se realizará una transición de estado.

      ③Acción: la acción que se ejecuta después de que se cumple la condición. Una vez que se ejecuta la acción, se puede migrar a un nuevo estado o puede permanecer en el estado original. La acción no es necesaria. Cuando se cumple la condición, también puede pasar directamente al nuevo estado sin realizar ninguna acción.

     ④ El segundo estado: el nuevo estado al que se moverá después de que se cumplan las condiciones. El "segundo estado" es relativo al "estado actual". Una vez que se activa el "segundo estado", se transforma en un nuevo "estado presente".

     Si generalizamos y unificamos aún más el "estado actual" y el "segundo estado", mientras ignoramos la "acción" (procesamiento de degradación), solo quedan dos elementos más críticos, a saber: el estado y las condiciones de transición.

     Hay muchas formas de expresar una máquina de estados, podemos expresar una máquina de estados en forma de texto, gráficos o tablas.

      Para dar un ejemplo simple: en términos de procesamiento de teclas, la propia pulsación de tecla también puede verse como una máquina de estado. Una pequeña acción de pulsación de tecla incluye: estados de liberación, agitación, cierre, agitación y relanzamiento. Cuando abrimos nuestras mentes e introducimos la máquina de estados en el programa como una idea, encontraremos un atajo eficaz para hacer frente a las preguntas. A veces puede ser más eficaz pensar en lo que debería hacer el programa con el pensamiento de una máquina de estados que con el pensamiento de controlar el proceso. De esta forma, la máquina de estados tiene una función más práctica. No hay mucha tontería, la práctica es el único criterio para probar la verdad.

Algunas personas pueden pensar que las máquinas de estado complican el problema. De hecho, las personas que han realizado el diseño de software ya están utilizando máquinas de estado de forma invisible. Aquí hay un resumen de varias máquinas de estado.

El primer tipo: máquina de estado de la estructura de la caja del interruptor

int main(void)
{
    bsp_Init();
    g_MainStatus = 0;
    while (1)
    {
        switch (g_MainStatus)
        {
            case 0:
                status_0();
                g_MainStatus = 1;
                break;
            case 1:
                status_1();
                g_MainStatus = 2;
                break;
            case 2:
                status_2();
                g_MainStatus = 1;
                break;
        }
    }
}

static void status_0(void)
{
    //执行状态0的初始化
    while (1)
    {
        if(状态满足)
        {//如果条件满足则退出该状态
            break;
        }
    }
}

static void status_1(void)
{
    //执行状态1的初始化
    while (1)
    {
        if(状态满足)
        {//如果条件满足则退出该状态
            break;
        }
    }
}

static void status_2(void)
{
    //执行状态2的初始化
    while (1)
    {
        if(状态满足)
        {//如果条件满足则退出该状态
            break;
        }
    }
}

 

Sin embargo, este método no puede predefinir eficazmente todos los estados, ni puede definir razonablemente el proceso de conmutación entre estos estados. No existe una definición razonable de "estado" en sí. Es casi una forma orientada al proceso. Este método es bastante simple y el más fácil para que la gente lo acepte. Las deficiencias son que no hay una función de definición y asignación de "estado", lo que genera confusión en el estado, código duplicado en el procesamiento del estado e incluso inconsistencia. Según el concepto de OO, la descripción del estado es inherentemente Debería ser una entidad.

 

La segunda máquina de estado: estructura de la declaración ifelse máquina de estado

Este tipo de máquina de estado es relativamente flexible, pero para algunos proyectos grandes, el diseño del software del sistema será relativamente complicado.

Las dos máquinas de estado anteriores son con las que todo el mundo tiene más contacto, y también se utilizan con frecuencia, por lo que no hablaré de ellas aquí. Lo siguiente se centra en la tercera máquina de estados.

El tercer tipo de máquina de estado: máquina de estado de activación de mensajes

Hay muchas implementaciones de este tipo de máquina de estados, con varias formas, pero los cuatro elementos de la máquina de estados y el estado actual, las condiciones, las acciones y los subestados son inseparables.

A continuación se describe una máquina de estado de un tipo de desencadenante de mensaje.

Mecanismo de máquina de estado controlado por mensajes

Principio: una vez que se activa un mensaje, la función de servicio del sistema busca inmediatamente el mensaje y el par de funciones de procesamiento de mensajes en el estado, y ejecuta la función de procesamiento de mensajes cuando se encuentra

paso:

1. Agregar mensaje y mapeo de mensajes

…
BEGIN_MESSAGE_ MAP(Name,Count) :状态机名,消息数
ADD_NEW_MSG_ITEM (Msg,OnMsg) :消息与消息处理函数
END_MESSAGE_MAP:结束
…

2. Regístrese aquí

BEGIN_Register_Task:头
...
ADD_Register_Task(Name,Count):状态机名,消息数
...
END_Register_Task:尾

1. Divida el estado de la balanza electrónica y complete la función de procesamiento de mensajes OnMsg después de completar los pasos anteriores

Void OnMsg(void)
{
    …
}

Nota: Lo anterior se hace con macros, las macros específicas se definen de la siguiente manera:


#defineBEGIN_MESSAGE_MAP(Name,Count) constMSG_NODE_TYP MSG_node_Array_##Name[(Count)]={
#define ADD_NEW_MSG_ITEM(Msg,OnMsg) {Msg,OnMsg},
#define END_MESSAGE_MAP };
#define BEGIN_Register_Task const MSG_MAP TaskMap[TotalTask]={
#define ADD_Register_Task(Name,Count) {(MSG_NODE_TYP*)MSG_node_Array_##Name,Count},
#define END_Register_Task };

Del código anterior, podemos ver:

1. Agregar el mensaje y el mapeo de mensajes es en realidad una matriz de mensajes y pares de funciones de procesamiento de mensajes para formar una tabla.

2. La máquina de estado de registro en realidad define la entrada de todos los mensajes a la matriz como una matriz para formar una tabla.

¿Cómo se ejecuta el mensaje?

Distribuir mensajes

void Default_DisposeMessage(unsigned char *pMsg)
{
    unsigned chari;
    unsigned charcount=TaskMap[g_Status].cItemCount;//定位到状态表
    for(i=0;i<count;i++)
    {
        if(*pMsg==TaskMap[g_Status].pMsgItems.msg)//看能否匹配消息
        {
            TaskMap[g_Status].pMsgItems.pMsgFunc();//找到就执行消息处理函数
            return;
        }
    }
}

void DispatchMessage(unsigned char*pMsg)
{
    if(*pMsg)
    {
        Default_DisposeMessage(pMsg);
    }
}

Función principal: centro de procesamiento de mensajes

void Message_Dispose_Central(void)
{
    BYTE Msg;
    while(GetMessage(&Msg)) //获取消息
    {
        TranslateMessage(&Msg); //解释消息
        DispatchMessage(&Msg); //分发消息
    }
}

Tres, método de sondeo por intervalo de tiempo

El método de sondeo por intervalo de tiempo se menciona en muchos libros, y aparece con el sistema operativo en muchos casos, es decir, este método se utiliza en el sistema operativo en muchos casos. Sin embargo, el método de sondeo por intervalo de tiempo del que vamos a hablar aquí no se cuelga del sistema operativo, sino que utiliza este método en programas de front-end y back-end. También es el método que se explicará e introducirá en detalle en este artículo. En el caso del método de encuesta por franjas de tiempo, aunque muchos libros los han presentado, la mayoría de ellos no son sistemáticos, solo mencionan conceptos. A continuación, presentaré este modo en detalle y me referiré al método de un programa de arquitectura de sondeo de intervalo de tiempo establecido por el código de otras personas.Creo que será una referencia para los principiantes. 

Aquí primero presentamos la función de multiplexación del temporizador. Utilice 1 temporizador, que puede ser cualquier temporizador. Aquí no se dan instrucciones especiales. Suponiendo que hay 3 tareas a continuación, deberíamos hacer lo siguiente: 

1. Inicialice el temporizador, suponga aquí que la interrupción del temporizador es de 1 ms (por supuesto, puede cambiarlo a 10 ms, esto es lo mismo que el sistema operativo, la eficiencia de la interrupción es demasiado frecuente, la interrupción es demasiado larga y el rendimiento en tiempo real es deficiente). 

2. Defina un valor: 

Código

#define TASK_NUM   (3)                //  这里定义的任务数为3,表示有三个任务会使用此定时器时。
uint16 TaskCount[TASK_NUM] ;         //  这里为三个任务定义三个变量来存放定时值
uint8  TaskMark[TASK_NUM];         //  同样对应三个标志位,为0表示时间没到,为1表示定时时间到。 

3. Agregue la función de servicio de interrupción del temporizador: 

Código 

/**************************************************************************************
* 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; 
            }
        }
    }
}

Explicación del código: función de servicio de interrupción de temporización, juzgue uno por uno en la interrupción, si el valor de temporización es 0, significa que el temporizador no se utiliza o que el temporizador ha completado la temporización y no necesita ser procesado. De lo contrario, el temporizador se reduce en uno, y cuando se sabe que es cero, el valor del bit indicador correspondiente es 1, lo que indica que ha llegado el valor de temporización de esta tarea. 

4. En nuestra aplicación, agregue el siguiente código al lugar donde se necesita la sincronización de la aplicación. El siguiente es un ejemplo de la tarea 1: 

Código

TaskCount[0] = 20;       // 延时20ms
TaskMark[0]  = 0x00;     // 启动此任务的定时器 到此我们只需要在任务中判断
TaskMark[0] 是否为0x01即可。

Las otras tareas se añaden de la misma forma, hasta ahora se ha realizado el problema de reutilización de un temporizador. Puedes probarlo con amigos que lo necesiten y el efecto es bueno. . . . . . . . . .

A través de la multiplexación de un temporizador anterior, podemos ver que mientras esperamos la llegada de un temporizador, podemos realizar un ciclo para determinar el bit de bandera, y también podemos ejecutar otras funciones. 

Bandera de juicio de bucle: Entonces podemos pensar en ello, si el bit de la bandera de juicio de bucle, ¿es el mismo que el programa de ejecución secuencial descrito anteriormente? Un bucle grande, pero este retardo es más preciso que el bucle for ordinario, y se puede lograr un retardo preciso. 

Ejecutar otras funciones: Entonces, si ejecutamos otras funciones cuando una función está retrasada y hacemos un uso completo del tiempo de la CPU, ¿es algo similar al sistema operativo? Pero la gestión de tareas y el cambio del sistema operativo es muy complicado. A continuación, utilizaremos este método para crear nuevas aplicaciones. 

La arquitectura del método de sondeo por intervalo de tiempo: 

1. Diseñe una estructura: 

Código

// 任务结构
typedef struct _TASK_COMPONENTS
{
    uint8 Run;                 // 程序运行标记:0-不运行,1运行
    uint8 Timer;              // 计时器
    uint8 ItvTime;              // 任务运行间隔时间
    void (*TaskHook)(void);    // 要运行的任务函数
} TASK_COMPONENTS;   // 任务定义 这个结构体的设计非常重要,一个用4个参数,注释说的非常详细,这里不在描述。 

2. Una vez marcada la tarea, esta función es equivalente a la función de servicio de interrupción. Esta función debe llamarse en la función de servicio de interrupción del temporizador. Esta función es independiente aquí y se puede trasplantar y comprender. 

Código

/**************************************************************************************
* 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;           // 任务可以运行
            }
        }
   }
}

 Todos comparen cuidadosamente la siguiente función, ¿es la misma que la función multiplexada regularmente anterior? 

3. Procesamiento de tareas: 

Código

/**************************************************************************************
* 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
        }
    }   
}

Esta función es para determinar cuándo ejecutar qué tarea y para realizar operaciones de gestión de tareas, el usuario solo necesita llamar a esta función en la función main () y no necesita llamar y procesar funciones de tarea por separado.

Llegados a este punto, se ha construido la arquitectura de una aplicación de sondeo por franjas de tiempo, ¿veamos si es muy simple? Esta arquitectura solo necesita dos funciones, una estructura y una variable enumerada se creará a continuación para su aplicación.

Hablemos de cómo usarlo, supongamos que tenemos tres tareas: visualización del reloj, escaneo de teclas y visualización del estado de funcionamiento.

1. Defina una variable de estructura del tipo definido anteriormente:

Código 

/**************************************************************************************

* Variable definition 

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

 Al definir las variables, hemos inicializado los valores, la inicialización de estos valores es muy importante y tiene algo que ver con la prioridad del tiempo de ejecución específico, esto debe ser dominado por usted mismo. 

①Probablemente significa que tenemos tres tareas y ejecutamos la siguiente visualización del reloj dentro de 1. Debido a que nuestro reloj tiene una unidad mínima de 1s, es suficiente mostrarlo una vez después del segundo cambio. 

② Debido a que el botón temblará cuando se presione el botón, y sabemos que el temblor del botón general es de unos 20ms, entonces generalmente extendemos 20ms en la función que se ejecuta secuencialmente, y aquí escaneamos cada 20ms, lo cual es muy bueno, es decir, se logra el propósito de antirrebote y no se perderá la entrada clave. 

③Para poder mostrar otras indicaciones e interfaz de trabajo después de presionar el botón, diseñamos para mostrarlo cada 30 ms. Si siente que la respuesta es lenta, puede reducir estos valores. El siguiente nombre es el nombre de la función correspondiente, debe escribir este nombre de función y estas tres tareas en el programa de aplicación. 

2. Lista de tareas: 

Código

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

Mire bien, el propósito de definir esta lista de tareas aquí es en realidad el valor del parámetro TASKS_MAX, otros valores no tienen un significado específico, solo para aclarar la relación entre las tareas superficiales.

3. Escriba la función de la tarea: 

Código

/**************************************************************************************
* 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)
{ 

} // 这里添加其他任务。。。。。。。。。 

Ahora puede escribir tareas según sus necesidades. 

4. Función principal: 

Código

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

En este punto, nuestra arquitectura de la aplicación de sondeo de intervalo de tiempo está completa, solo necesita agregar su propia función de tarea donde se lo solicitemos. ¿No es simple ?, ¿Se siente como un sistema operativo? ¿Probarlo sin precaución para ver si las tareas no interfieren entre sí? ¿Qué tal correr en paralelo? Por supuesto, es importante tener en cuenta que cuando se transfieren datos entre tareas, es necesario utilizar variables globales. Además, también es necesario prestar atención a la división de tareas y al tiempo de ejecución de las tareas. Al escribir tareas, Intente completar las tareas lo antes posible. . . . . . .

Cuarto, el sistema operativo

El sistema operativo en sí es algo relativamente complicado, y las habilidades de gestión y ejecución de tareas no requieren que lo comprendamos. Pero consumirá una cierta cantidad de recursos y no es fácil de aplicar si los recursos son escasos. Ahora hay sistemas operativos gratuitos como freeRTOS, sistemas operativos domésticos RT-Thread, HuaweiLiteOS, etc., y sus sistemas tienen sus propias ventajas, así que no los repetiré aquí. A continuación, se toma freeRTOS como ejemplo para presentarlo.

/*
*********************************************************************************************************
* 函 数 名: main
* 功能说明: 标准c程序入口。
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
int main(void)
{
/* 硬件初始化 */
bsp_Init();
/* 创建任务 */
AppTaskCreate();
/* 启动调度,开始执行任务 */
vTaskStartScheduler();
}

/*
*********************************************************************************************************
* 函 数 名: AppTaskCreate
* 功能说明: 创建应用任务
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/

static void AppTaskCreate (void)
{
    xTaskCreate(Adc_Task, /* 任务函数 */
            "Adc_Task", /* 任务名 */
            ADC_STK_SIZE, /* 任务栈大小,单位word,也就是4字节 */
            NULL, /* 任务参数 */
            ADC_TASK_PRIO, /* 任务优先级*/
            &Adc_Task_Handler ); /* 任务句柄 */

    xTaskCreate(Display_Task, /* 任务函数 */
            "Display_Task", /* 任务名 */
            DISPLAY_STK_SIZE, /* 任务栈大小,单位word,也就是4字节 */
            NULL, /* 任务参数 */
            DISPLAY_TASK_PRIO, /* 任务优先级*/
            &Display_Task_Handler ); /* 任务句柄 */

}

No es difícil ver que el método de sondeo por intervalo de tiempo tiene una ventaja relativamente grande, es decir, tiene las ventajas del método de ejecución secuencial y las ventajas del sistema operativo. La estructura es clara, simple y muy fácil de entender.

 

 

Supongo que te gusta

Origin blog.csdn.net/zhuimeng_ruili/article/details/112852685
Recomendado
Clasificación