打印
[开发资料]

单片机开发中常用的三种软件架构

[复制链接]
17|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
benjaminka|  楼主 | 2024-12-18 20:28 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
1.线性架构这是最简单的一种程序设计方法,也就是我们在入门时写的,下面是一个使用C语言编写的线性架构示例:
#include <reg51.h>  // 包含51系列单片机的寄存器定义// 延时函数,用于产生一定的延迟void delay(unsigned int count) {    unsigned int i;    while(count--) {        for(i = 0; i < 120; i++) {}  // 空循环,用于产生延迟    }}void main() {    // 初始设置P1端口为输出模式,用于控制LED    P1 = 0xFF;  // 将P1端口设置为高电平,关闭所有LED    while(1) {  // 无限循环        P1 = 0x00;  // 将P1端口设置为低电平,点亮所有LED        delay(500000);  // 调用延时函数,延迟一段时间        P1 = 0xFF;  // 将P1端口设置为高电平,关闭所有LED        delay(500000);  // 再次调用延时函数,延迟相同的时间    }}

2.模块化架构模块化架构是一种将程序分解为独立模块的设计方法,每个模块执行特定的任务。

这种架构有助于代码的重用、维护和测试。

下面是一个使用C语言编写的模块化架构示例,该程序模拟了一个简单的交通信号灯控制系统。
#include <reg51.h>  // 包含51系列单片机的寄存器定义// 定义信号灯的状态typedef enum {    RED_LIGHT,    YELLOW_LIGHT,    GREEN_LIGHT} TrafficLightState;// 函数声明void initializeTrafficLight(void);void setTrafficLight(TrafficLightState state);void delay(unsigned int milliseconds);// 信号灯控制主函数void main(void) {    initializeTrafficLight();  // 初始化交通信号灯    while(1) {        setTrafficLight(RED_LIGHT);        delay(5000);  // 红灯亮5秒        setTrafficLight(YELLOW_LIGHT);        delay(2000);  // 黄灯亮2秒        setTrafficLight(GREEN_LIGHT);        delay(5000);  // 绿灯亮5秒    }}// 初始化交通信号灯的函数void initializeTrafficLight(void) {    // 这里可以添加初始化代码,比如设置端口方向、默认状态等    // 假设P1端口连接了信号灯,初始状态为熄灭(高电平)    P1 = 0xFF;}// 设置交通信号灯状态的函数void setTrafficLight(TrafficLightState state) {    switch(state) {        case RED_LIGHT:            // 设置红灯亮,其他灯灭            P1 = 0b11100000;  // 假设低电平有效,这里设置P1.0为低电平,其余为高电平            break;        case YELLOW_LIGHT:            // 设置黄灯亮,其他灯灭            P1 = 0b11011000;  // 设置P1.1为低电平,其余为高电平            break;        case GREEN_LIGHT:            // 设置绿灯亮,其他灯灭            P1 = 0b11000111;  // 设置P1.2为低电平,其余为高电平            break;        default:            // 默认为熄灭所有灯            P1 = 0xFF;            break;    }}// 延时函数,参数是毫秒数void delay(unsigned int milliseconds) {    unsigned int delayCount = 0;    while(milliseconds--) {        for(delayCount = 0; delayCount < 120; delayCount++) {            // 空循环,用于产生延时        }    }}

3.层次化架构层次化架构是一种将系统分解为多个层次的设计方法,每个层次负责不同的功能。
着以下是一个使用C语言编写的层次化架构示例,模拟了一个具有不同权限级别的嵌入式系统。
#include <reg51.h>  // 包含51系列单片机的寄存器定义// 定义不同的操作级别typedef enum {    LEVEL_USER,    LEVEL_ADMIN,    LEVEL_SUPERUSER} OperationLevel;// 函数声明void systemInit(void);void performOperation(OperationLevel level);void displayMessage(char* message);// 系统初始化后的主循环void main(void) {    systemInit();  // 系统初始化    // 模拟用户操作    performOperation(LEVEL_USER);    // 模拟管理员操作    performOperation(LEVEL_ADMIN);    // 模拟超级用户操作    performOperation(LEVEL_SUPERUSER);    while(1) {        // 主循环可以是空闲循环或者处理其他低优先级任务    }}// 系统初始化函数void systemInit(void) {    // 初始化系统资源,如设置端口、中断等    // 这里省略具体的初始化代码}// 执行不同级别操作的函数void performOperation(OperationLevel level) {    switch(level) {        case LEVEL_USER:          //用户操作具体代码            break;        case LEVEL_ADMIN:          //管理员操作具体代码            break;        case LEVEL_SUPERUSER:           //超级用户操作具体代码            break;    }}// 显示消息的函数void displayMessage(char* message) {    // 这里省略了实际的显示代码,因为单片机通常没有直接的屏幕输出    // 消息可以通过LED闪烁、串口输出或其他方式展示    // 假设通过P1端口的LED展示,每个字符对应一个LED闪烁模式    // 实际应用中,需要根据硬件设计来实现消息的显示}

4.事件驱动架构事件驱动架构是一种编程范式,其中程序的执行流程由事件(如用户输入、传感器变化、定时器到期等)触发。

在单片机开发中,事件驱动架构通常用于响应外部硬件中断或软件中断。

以下是一个使用C语言编写的事件驱动架构示例,模拟了一个基于按键输入的LED控制。
#include <reg51.h>  // 包含51系列单片机的寄存器定义// 定义按键和LED的状态#define KEY_PORT P3  // 假设按键连接在P3端口#define LED_PORT P2  // 假设LED连接在P2端口// 函数声明void delay(unsigned int milliseconds);bit checkKeyPress(void);  // 返回按键是否被按下的状态(1表示按下,0表示未按下)// 定时器初始化函数void timer0Init(void) {    TMOD = 0x01;  // 设置定时器模式寄存器,使用模式1(16位定时器)    TH0 = 0xFC;   // 设置定时器初值,用于产生定时中断    TL0 = 0x18;    ET0 = 1;      // 开启定时器0中断    EA = 1;       // 开启总中断    TR0 = 1;      // 启动定时器}// 定时器中断服务程序void timer0_ISR() interrupt 1 {    // 定时器溢出后自动重新加载初值,无需手动重置    // 这里可以放置定时器溢出后需要执行的代码}// 按键中断服务程序bit keyPress_ISR(void) interrupt 2 using 1 {    if(KEY_PORT != 0xFF) // 检测是否有按键按下        {          LED_PORT = ~LED_PORT;  // 如果有按键按下,切换LED状态        delay(20);  // 去抖动延时        while(KEY_PORT != 0xFF);  // 等待按键释放        return 1;  // 返回按键已按下    }    return 0;  // 如果没有按键按下,返回0}// 延时函数,参数是毫秒数void delay(unsigned int milliseconds) {    unsigned int i, j;    for(i = 0; i < milliseconds; i++)        for(j = 0; j < 1200; j++);  // 空循环,用于产生延时}// 主函数void main(void) {    timer0Init();  // 初始化定时器    LED_PORT = 0xFF;  // 初始LED熄灭(假设低电平点亮LED)    while(1)     {        if(checkKeyPress())        {  // 检查是否有按键按下事件            // 如果有按键按下,这里可以添加额外的处理代码        }    }}// 检查按键是否被按下的函数bit checkKeyPress(void) {    bit keyState = 0;    // 模拟按键中断触发,实际应用中需要连接硬件中断    if(1) // 假设按键中断触发    {        keyState = keyPress_ISR();  // 调用按键中断服务程序    }    return keyState;  // 返回按键状态}

事实上,真正的事件型驱动架构,是非常复杂的,我职业生涯的巅峰之作,就是用的事件型驱动架构。




5.状态机架构在单片机开发中,状态机常用于处理复杂的逻辑和事件序列,如用户界面管理、协议解析等。

以下是一个使用C语言编写的有限状态机(FSM)的示例,模拟了一个简单的自动售货机的状态转换。
#include <reg51.h>  // 包含51系列单片机的寄存器定义// 定义自动售货机的状态typedef enum {    IDLE,    COIN_INSERTED,    PRODUCT_SELECTED,    DISPENSE,    CHANGE_RETURNED} VendingMachineState;// 定义事件typedef enum {    COIN_EVENT,    PRODUCT_EVENT,    DISPENSE_EVENT,    REFUND_EVENT} VendingMachineEvent;// 函数声明void processEvent(VendingMachineEvent event);void dispenseProduct(void);void returnChange(void);// 当前状态VendingMachineState currentState = IDLE;// 主函数void main(void){    // 初始化代码(如果有)    // ...    while(1)    {        // 假设事件由外部触发,这里使用一个模拟事件        VendingMachineEvent currentEvent = COIN_EVENT; // 模拟投入硬币事件        processEvent(currentEvent);  // 处理当前事件    }}// 处理事件的函数void processEvent(VendingMachineEvent event){    switch(currentState)    {        case IDLE:            if(event == COIN_EVENT)            {                // 如果在空闲状态且检测到硬币投入事件,则转换到硬币投入状态                currentState = COIN_INSERTED;            }            break;        case COIN_INSERTED:            if(event == PRODUCT_EVENT)            {                // 如果在硬币投入状态且用户选择商品,则请求出货                currentState = PRODUCT_SELECTED;            }            break;        case PRODUCT_SELECTED:            if(event == DISPENSE_EVENT)            {                dispenseProduct();  // 出货商品                currentState = DISPENSE;            }            break;        case DISPENSE:            if(event == REFUND_EVENT)            {                returnChange();  // 返回找零                currentState = CHANGE_RETURNED;            }            break;        case CHANGE_RETURNED:            // 等待下一个循环,返回到IDLE状态            currentState = IDLE;            break;        default:            // 如果状态非法,重置为IDLE状态            currentState = IDLE;            break;    }}// 出货商品的函数void dispenseProduct(void){    // 这里添加出货逻辑,例如激活电机推出商品    // 假设P1端口连接了出货电机    P1 = 0x00;  // 激活电机    // ... 出货逻辑    P1 = 0xFF;  // 关闭电机}// 返回找零的函数void returnChange(void){    // 这里添加找零逻辑,例如激活机械臂放置零钱    // 假设P2端口连接了找零机械臂    P2 = 0x00;  // 激活机械臂    // ... 找零逻辑    P2 = 0xFF;  // 关闭机械臂}

6.面向对象架构STM32的库,就是一种面向对象的架构。

不过在单片机由于资源限制,OOP并不像在高级语言中那样常见,但是一些基本概念如封装和抽象仍然可以被应用。

虽然C语言本身并不直接支持面向对象编程,但可以通过结构体和函数指针模拟一些面向对象的特性。

下面是一个简化的示例,展示如何在C语言中模拟面向对象的编程风格,以51单片机为背景,创建一个简单的LED类。
#include <reg51.h>// 定义一个LED类typedef struct {    unsigned char state;  // LED的状态    unsigned char pin;    // LED连接的引脚    void (*turnOn)(struct LED*);  // 点亮LED的方法    void (*turnOff)(struct LED*); // 熄灭LED的方法} LED;// LED类的构造函数void LED_Init(LED* led, unsigned char pin) {    led->state = 0;  // 默认状态为熄灭    led->pin = pin;   // 设置LED连接的引脚}// 点亮LED的方法void LED_TurnOn(LED* led) {    // 根据引脚状态点亮LED    if(led->pin < 8) {        P0 |= (1 << led->pin);  // 假设P0.0到P0.7连接了8个LED    } else {        P1 &= ~(1 << (led->pin - 8));  // 假设P1.0到P1.7连接了另外8个LED    }    led->state = 1;  // 更新状态为点亮}// 熄灭LED的方法void LED_TurnOff(LED* led) {    // 根据引脚状态熄灭LED    if(led->pin < 8) {        P0 &= ~(1 << led->pin);  // 熄灭P0上的LED    } else {        P1 |= (1 << (led->pin - 8));  // 熄灭P1上的LED    }    led->state = 0;  // 更新状态为熄灭}// 主函数void main(void) {    LED myLed;  // 创建一个LED对象    LED_Init(&myLed, 3);  // 初始化LED对象,连接在P0.3    // 给LED对象绑定方法    myLed.turnOn = LED_TurnOn;    myLed.turnOff = LED_TurnOff;    // 使用面向对象的风格控制LED    while(1) {        myLed.turnOn(&myLed);  // 点亮LED        // 延时        myLed.turnOff(&myLed); // 熄灭LED        // 延时    }}这段代码定义了一个结构体LED,模拟面向对象中的“类。

这个示例仅用于展示如何在C语言中模拟面向对象的风格,并没有使用真正的面向对象编程语言的特性,如继承和多态,不过对于单片机的应用,足以。


7.基于任务的架构这种我最喜欢用,结构,逻辑清晰,每个任务都能灵活调度。

基于任务的架构是将程序分解为独立的任务,每个任务执行特定的工作。

在单片机开发中,如果没有使用实时操作系统,我们可以通过编写一个简单的轮询调度器来模拟基于任务的架构。

以下是一个使用C语言编写的基于任务的架构的示例,该程序在51单片机上实现。

为了简化,我们将使用一个简单的轮询调度器来在两个任务之间切换:一个是按键扫描任务,另一个是LED闪烁任务。
#include <reg51.h>// 假设P1.0是LED输出sbit LED = P1^0;// 全局变量,用于记录系统Tickunsigned int systemTick = 0;// 任务函数声明void taskLEDBlink(void);void taskKeyScan(void);// 定时器0中断服务程序,用于产生Tickvoid timer0_ISR() interrupt 1 using 1 {    // 定时器溢出后自动重新加载初值,无需手动重置    systemTick++;  // 更新系统Tick计数器}// 任务调度器,主函数中调用,负责任务轮询void taskScheduler(void) {    // 检查系统Tick,决定是否执行任务    // 例如,如果我们需要每1000个Tick执行一次LED闪烁任务    if (systemTick % 1000 == 0)     {       taskLEDBlink();    }    // 如果有按键任务,可以类似地检查Tick并执行    if (systemTick % 10 == 0)     {       taskKeyScan();    }}// LED闪烁任务void taskLEDBlink(void) {    static bit ledState = 0;  // 用于记录LED的当前状态    ledState = !ledState;  // 切换LED状态    LED = ledState;         // 更新LED硬件状态}// 按键扫描任务(示例中省略具体实现)void taskKeyScan(void) {    // 按键扫描逻辑}// 主函数void main(void) {    // 初始化LED状态    LED = 0;    // 定时器0初始化设置    TMOD &= 0xF0;  // 设置定时器模式寄存器,使用模式1(16位定时器/计数器)    TH0 = 0x4C;     // 设置定时器初值,产生定时中断(定时周期取决于系统时钟频率)    TL0 = 0x00;    ET0 = 1;        // 允许定时器0中断    EA = 1;         // 允许中断    TR0 = 1;        // 启动定时器0    while(1)     {        taskScheduler();  // 调用任务调度器    }}
这里只是举个简单的例子,这个代码示例,比较适合51和stm8这种资源非常少的单片机。


8.代理架构这个大家或许比较少听到过,但在稍微复杂的项目中,是非常常用的。

在代理架构中,每个代理(Agent)都是一个独立的实体,它封装了特定的决策逻辑和数据,并与其他代理进行交互。

在实际项目中,需要创建多个独立的任务或模块,每个模块负责特定的功能,并通过某种机制(如消息队列、事件触发等)进行通信。

这种方式可以大大提高程序可扩展性和可移植性。

以下是一个LED和按键代理的简化模型。
#include <reg51.h>  // 包含51系列单片机的寄存器定义// 假设P3.5是按键输入,P1.0是LED输出sbit KEY = P3^5;sbit LED = P1^0;typedef struct {    unsigned char pin;    // 代理关联的引脚    void (*action)(void); // 代理的行为函数} Agent;// 按键代理的行为函数声明void keyAction(void);// LED代理的行为函数声明void ledAction(void);// 代理数组,存储所有代理的行为和关联的引脚Agent agents[] = {    {5, keyAction},  // 按键代理,关联P3.5    {0, ledAction}   // LED代理,关联P1.0};// 按键代理的行为函数void keyAction(void) {    if(KEY == 0) // 检测按键是否被按下        {          LED = !LED;   // 如果按键被按下,切换LED状态        while(KEY == 0);  // 等待按键释放    }}// LED代理的行为函数void ledAction(void) {    static unsigned int toggleCounter = 0;    toggleCounter++;    if(toggleCounter == 500)  // 假设每500个时钟周期切换一次LED        {         LED = !LED;               // 切换LED状态        toggleCounter = 0;        // 重置计数器    }}// 主函数void main(void) {    unsigned char agentIndex;    // 主循环    while(1)     {        for(agentIndex = 0; agentIndex < sizeof(agents) / sizeof(agents[0]); agentIndex++)         {            // 调用每个代理的行为函数            (*agents[agentIndex].action)(); // 注意函数指针的调用方式        }    }}

9.组件化架构组件化架构是一种将软件系统分解为独立、可重用组件的方法。

将程序分割成负责特定任务的模块,如LED控制、按键处理、传感器读数等。

每个组件可以独立开发和测试,然后被组合在一起形成完整的系统。

以下是一个简化的组件化架构示例,模拟了一个单片机系统中的LED控制和按键输入处理两个组件。

为了简化,组件间的通信将通过直接函数调用来模拟。
#include <reg51.h>  // 包含51系列单片机的寄存器定义// 定义组件结构体typedef struct {    void (*init)(void);      // 组件初始化函数    void (*task)(void);       // 组件任务函数} Component;// 假设P3.5是按键输入,P1.0是LED输出sbit KEY = P3^5;sbit LED = P1^0;// LED组件void LED_Init(void) {    LED = 0;  // 初始化LED状态为关闭}void LED_Task(void) {    static unsigned int toggleCounter = 0;    toggleCounter++;    if (toggleCounter >= 1000) // 假设每1000个时钟周期切换一次LED    {          LED = !LED;                // 切换LED状态        toggleCounter = 0;         // 重置计数器    }}// 按键组件void KEY_Init(void) {    // 按键初始化代码}void KEY_Task(void) {    if (KEY == 0) // 检测按键是否被按下    {         LED = !LED;  // 如果按键被按下,切换LED状态       while(KEY == 0);  // 等待按键释放    }}// 组件数组,存储系统中所有组件的初始化和任务函数Component components[] = {    {LED_Init, LED_Task},    {KEY_Init, KEY_Task}};// 系统初始化函数,调用所有组件的初始化函数void System_Init(void) {    unsigned char componentIndex;    for (componentIndex = 0; componentIndex < sizeof(components) / sizeof(components[0]); componentIndex++)     {        components[componentIndex].init();    }}// 主循环,调用所有组件的任务函数void main(void) {    System_Init();  // 系统初始化    while(1)     {        unsigned char componentIndex;        for (componentIndex = 0; componentIndex < sizeof(components) / sizeof(components[0]); componentIndex++)        {            components[componentIndex].task();  // 调用组件任务        }    }}


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

48

主题

1334

帖子

0

粉丝