You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于PIC18 16位MCU的裸机嵌入式系统多任务架构咨询

裸机PIC18系统架构实现方案(基于定时器的任务调度)

针对你的需求,我们可以用硬件定时器+时间片轮询任务的架构来实现,完全不需要操作系统,靠全局时间基准和任务列表来满足时序约束。核心思路是用定时器产生固定周期的中断(比如1ms)维护系统时间戳,主循环轮询各个任务的触发条件,满足就执行对应逻辑。

核心架构设计

  1. 系统时间基准:用PIC18的硬件定时器(比如Timer0/Timer1)配置1ms中断,在中断服务函数中仅递增全局毫秒计数器,不做其他复杂操作,保证中断响应速度。
  2. 任务管理:定义任务结构体,包含任务的执行周期、上次执行时间戳、任务函数指针。主循环中轮询所有任务,当当前时间与上次执行时间的差值达到任务周期时,执行任务。
  3. 模块化拆分:将消抖、CAN发送、定时输出等功能拆分为独立任务,各自维护状态,降低耦合。

具体实现步骤与代码示例

1. 全局变量与结构体定义

#include <pic18.h>
#include <stdint.h>

// 全局系统毫秒计数器(16位,最大覆盖65.5秒,溢出后无符号减法可自动处理)
volatile uint16_t sys_tick_ms = 0;

// 任务结构体:描述每个任务的调度参数
typedef struct {
    uint16_t last_run_tick;  // 上次执行的时间戳
    uint16_t interval_ms;    // 任务执行周期(毫秒)
    void (*execute)(void);   // 任务执行函数指针
} ScheduledTask;

// 数字输入消抖结构体:维护每个输入的消抖状态
typedef struct {
    uint8_t raw_pin;               // 原始引脚电平
    uint8_t stable_state;          // 消抖后的稳定状态
    uint8_t debounce_counter;      // 消抖计数
    const uint8_t threshold;       // 消抖阈值(比如5ms=5次连续采样)
} DebouncedInput;

// 定时输出结构体:维护每个输出的定时状态
typedef struct {
    uint8_t* output_pin;           // 输出引脚指针
    uint16_t expire_tick;          // 到期时间戳
    uint8_t is_active;             // 是否处于定时激活状态
} TimedOutput;

2. 定时器中断配置

以Timer0为例(可根据PIC18型号调整定时器),配置1ms中断周期,仅更新时间戳:

void __interrupt() isr(void) {
    // 处理Timer0中断
    if (INTCONbits.TMR0IF) {
        // 重置定时器计数(需根据你的系统时钟和预分频计算准确值)
        TMR0 = 0x80;  // 示例值,实际需根据Fosc计算
        sys_tick_ms++;
        INTCONbits.TMR0IF = 0;  // 清除中断标志位
    }

    // 处理CAN接收中断(仅标记新消息,解析放在主循环)
    if (PIR3bits.RXB0IF) {
        can_new_msg_flag = 1;  // 全局标志位,标记有新CAN消息
        PIR3bits.RXB0IF = 0;
    }
}

// 初始化定时器与中断
void init_timer_and_interrupts(void) {
    // 配置Timer0:预分频、时钟源等
    OPTION_REGbits.T0CS = 0;  // 内部时钟Fosc/4
    OPTION_REGbits.PSA = 0;   // 预分频分配给Timer0
    OPTION_REGbits.PS = 0b111; // 预分频值1:256
    TMR0 = 0x80;              // 初始计数,保证1ms中断

    // 开启中断
    INTCONbits.TMR0IE = 1;    // 允许Timer0中断
    INTCONbits.GIE = 1;       // 开启全局中断
    INTCONbits.PEIE = 1;      // 允许外设中断(用于CAN)
}

3. 任务定义与初始化

// 任务函数声明
void task_debounce_inputs(void);
void task_10ms_can_transmit(void);
void task_check_timed_outputs(void);

// 任务列表:按执行周期排序,优化轮询效率
ScheduledTask tasks[] = {
    {0, 1, task_debounce_inputs},       // 1ms一次消抖检测
    {0, 1, task_check_timed_outputs},   // 1ms一次检查定时输出
    {0, 10, task_10ms_can_transmit}     // 10ms一次CAN发送
};
const uint8_t task_count = sizeof(tasks) / sizeof(ScheduledTask);

// 初始化2路数字输入消抖实例
DebouncedInput dig_inputs[2] = {
    {PORTBbits.RB0, 0, 0, 5},  // 输入1:RB0,消抖阈值5ms
    {PORTBbits.RB1, 0, 0, 5}   // 输入2:RB1,消抖阈值5ms
};

// 初始化2路定时输出实例
TimedOutput timed_outputs[2] = {
    {&PORTBbits.RB2, 0, 0},    // 输出1:RB2
    {&PORTBbits.RB3, 0, 0}     // 输出2:RB3
};

// CAN新消息标志
uint8_t can_new_msg_flag = 0;

4. 主循环与任务调度

void main(void) {
    // 硬件初始化:GPIO、CAN、定时器
    init_gpio();
    init_can();
    init_timer_and_interrupts();

    while(1) {
        // 轮询调度所有任务
        for (uint8_t i = 0; i < task_count; i++) {
            // 无符号减法自动处理sys_tick_ms溢出问题
            if ((uint16_t)(sys_tick_ms - tasks[i].last_run_tick) >= tasks[i].interval_ms) {
                tasks[i].execute();
                tasks[i].last_run_tick = sys_tick_ms;
            }
        }

        // 处理CAN接收消息(主循环解析,避免中断阻塞)
        if (can_new_msg_flag) {
            process_can_received_msg();
            can_new_msg_flag = 0;
        }
    }
}

5. 各任务具体实现

数字输入消抖任务

void task_debounce_inputs(void) {
    for (uint8_t i = 0; i < 2; i++) {
        uint8_t current_raw = dig_inputs[i].raw_pin;
        if (current_raw == dig_inputs[i].stable_state) {
            dig_inputs[i].debounce_counter = 0; // 状态一致,重置计数
        } else {
            dig_inputs[i].debounce_counter++;
            // 连续采样达到阈值,更新稳定状态
            if (dig_inputs[i].debounce_counter >= dig_inputs[i].threshold) {
                dig_inputs[i].stable_state = current_raw;
                dig_inputs[i].debounce_counter = 0;
                // 可选:触发输入状态变化的回调逻辑
            }
        }
    }
}

10ms CAN发送任务

void task_10ms_can_transmit(void) {
    // 采集模拟输入
    uint16_t adc_val1 = read_adc(0);  // 自定义AD读取函数
    uint16_t adc_val2 = read_adc(1);
    // 获取消抖后的数字输入状态
    uint8_t dig_state1 = dig_inputs[0].stable_state;
    uint8_t dig_state2 = dig_inputs[1].stable_state;

    // 打包CAN数据帧
    uint8_t can_data[8] = {
        (uint8_t)(adc_val1 >> 8), (uint8_t)adc_val1,
        (uint8_t)(adc_val2 >> 8), (uint8_t)adc_val2,
        dig_state1, dig_state2, 0x00, 0x00
    };

    // 发送CAN消息(自定义CAN发送函数)
    can_send(0x123, can_data, 8);
}

定时输出检查任务

void task_check_timed_outputs(void) {
    for (uint8_t i = 0; i < 2; i++) {
        if (timed_outputs[i].is_active) {
            // 检查是否到达到期时间
            if ((uint16_t)(sys_tick_ms - timed_outputs[i].expire_tick) >= 0) {
                *timed_outputs[i].output_pin = 0;  // 关闭输出
                timed_outputs[i].is_active = 0;
            }
        }
    }
}

CAN消息处理函数

void process_can_received_msg(void) {
    uint8_t can_data[8];
    uint16_t can_id = can_receive(can_data);  // 自定义CAN接收函数

    // 示例指令格式:ID=0x456,字节0=输出通道,字节1-2=定时时长x秒,字节3=动作(1=开启/0=关闭)
    if (can_id == 0x456) {
        uint8_t channel = can_data[0];
        if (channel < 2) {
            uint16_t x_seconds = (can_data[1] << 8) | can_data[2];
            uint8_t action = can_data[3];

            if (action == 1) {
                *timed_outputs[channel].output_pin = 1;  // 开启输出
                timed_outputs[channel].expire_tick = sys_tick_ms + x_seconds * 1000;
                timed_outputs[channel].is_active = 1;
            } else {
                *timed_outputs[channel].output_pin = 0;  // 强制关闭
                timed_outputs[channel].is_active = 0;
            }
        }
    }
}

关键注意事项

  • 中断优先级:如果使用多个中断(比如CAN接收),要配置PIC18的中断优先级,保证定时器中断优先执行,避免时间基准不准。
  • AD采集优化:如果AD转换耗时较长,可以在10ms任务中先启动AD转换,用AD中断标记转换完成,主循环中读取结果,避免阻塞任务调度。
  • 变量原子性:sys_tick_ms是中断修改的全局变量,主循环中读取时如果是16位,要保证原子操作(PIC18读取16位变量时会自动原子化,无需额外处理)。
  • 任务执行时间:每个任务的执行时间必须远小于任务周期,比如1ms任务的执行时间要控制在几百微秒内,避免影响其他任务的时序。

内容的提问来源于stack exchange,提问作者S_A1980

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 06:50:57