工程框架搭建(续)

🧰 二、工程框架搭建(续)

------ 系统初始化 + 超简单任务调度器

💡 想让单片机"听话"?先让它正确启动 ,再给它一个智能管家

本章将带你:

修复并理解 system_init() 的真实作用

从零实现一个超轻量级任务调度器 (像手机闹钟一样提醒你做事!)

学会用结构体组织复杂数据(告别一堆零散变量!)

小白也能写出专业级嵌入式代码!


🔌 一、系统初始化函数 ------ 别被"假操作"骗了!

你看到的这段代码:

c 复制代码
void system_init(void) {
    GPIOC->ODR = 0x00ff;        // 清除高8位?
    GPIOC->ODR = ~(0x00 << 8);  // 设高8位为高?
    GPIOD->BSRR = 0x01 << 2;    // 置位 PD2
    GPIOD->BRR = 0x01 << 2;     // 复位 PD2
}

⚠️ 其实存在严重问题!

❌ 问题分析:

问题 正确做法
GPIOC->ODR = 0x00ff 直接写 ODR 会覆盖整个寄存器!低8位也被强制设为 0xFF,可能误关其他引脚 应使用 BSRR/BRR先配置模式再操作
~(0x00 << 8) 0x00 << 8 还是 0,~0 = 0xFFFFFFFF → 全部拉高!不是只设高8位 想设高8位为1:应写 0xFF00
PD2 置位后立刻复位 速度太快,肉眼/示波器都看不到变化,毫无意义 若用于复位外设,需加延时;否则可删除

✅ 正确的系统初始化应该做什么?

真正的 system_init() 应包含:

  1. 时钟使能(RCC)
  2. GPIO 模式配置(输入/输出/复用)
  3. 外设初始化(UART、ADC 等)
🌟 示例:正确初始化 PC8~PC15 为推挽输出
c 复制代码
#include "stm32f1xx.h"

void system_init(void) {
    // 1. 使能 GPIOC 和 GPIOD 时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN | RCC_APB2ENR_IOPDEN;

    // 2. 配置 PC8~PC15 为推挽输出,50MHz
    GPIOC->CRH = 0x33333333;  // 高8位:每4位控制1个引脚,0b0011 = 推挽输出 50MHz

    // 3. 初始状态:全部拉低
    GPIOC->ODR &= ~0xFF00;    // 清除高8位(PC8~PC15)

    // 4. (可选)PD2 作为普通输出
    GPIOD->CRL &= ~(0xF << (2*4));   // 清除 PD2 配置
    GPIOD->CRL |=  (0x3 << (2*4));   // 推挽输出 50MHz
    GPIOD->ODR &= ~(1 << 2);         // 初始拉低
}

关键原则

  • 先配模式,再设电平
  • 操作寄存器前,先开时钟!
  • 避免直接写 ODR,除非你确定所有引脚状态

⏰ 二、任务调度器 ------ 给单片机请个"智能管家"

🎯 什么是任务调度器?

想象你是个学生,每天要:

  • 7:00 吃早餐
  • 10:00 做作业
  • 16:00 锻炼
  • 22:00 睡觉

但你不想自己记时间------于是请了个管家,他每秒看一次表,到点就提醒你!

在嵌入式系统中,任务调度器 = 这个管家


🧱 核心组成:结构体 + 数组 + 循环

1️⃣ 定义任务结构体("任务档案")
c 复制代码
// scheduler.h
#ifndef __SCHEDULER_H
#define __SCHEDULER_H

#include <stdint.h>

// 一个任务 = 函数 + 周期 + 上次执行时间
typedef struct {
    void (*task_func)(void);   // 函数指针:要做的事
    uint32_t rate_ms;          // 执行周期(毫秒)
    uint32_t last_run;         // 上次执行的时间戳
} task_t;

void scheduler_init(void);
void scheduler_run(void);

#endif

💡 结构体就像"汽车档案":把品牌、型号、年份打包在一起,管理更方便!


2️⃣ 创建任务列表("管家的任务表")
c 复制代码
// scheduler.c
#include "scheduler.h"
#include "main.h"  // 包含 LED、按键等函数声明

// 声明具体任务函数
void led_toggle_task(void);
void key_scan_task(void);

// 任务数组:定义所有要做的事
static task_t tasks[] = {
    {led_toggle_task,   500, 0},   // 每500ms翻转LED
    {key_scan_task,     10, 0},    // 每10ms扫描按键
    // {motor_control,  100, 0},   // 可扩展更多任务
};

// 自动计算任务数量
static const uint8_t task_count = sizeof(tasks) / sizeof(tasks[0]);

3️⃣ 实现任务函数("具体要做的事")
c 复制代码
// 用户自定义任务
void led_toggle_task(void) {
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);  // 翻转板载LED
}

void key_scan_task(void) {
    // 伪代码:读取按键状态
    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) {
        // 按键按下,做点什么...
    }
}

4️⃣ 调度器核心逻辑("管家查表")
c 复制代码
// 初始化:其实这里不需要做啥(任务已静态定义)
void scheduler_init(void) {
    // 可在此处初始化 last_run = HAL_GetTick(),避免上电瞬间全触发
    for (int i = 0; i < task_count; i++) {
        tasks[i].last_run = HAL_GetTick();
    }
}

// 调度主循环:每调用一次,检查所有任务是否该执行
void scheduler_run(void) {
    uint32_t now = HAL_GetTick();  // 获取当前系统时间(ms)

    for (uint8_t i = 0; i < task_count; i++) {
        // 判断:现在是不是该执行这个任务了?
        if (now - tasks[i].last_run >= tasks[i].rate_ms) {
            tasks[i].last_run = now;      // 更新上次执行时间
            tasks[i].task_func();         // 执行任务!
        }
    }
}

为什么用 now - last_run >= rate_ms

避免 SysTick 溢出(49天后 HAL_GetTick() 会回绕),此写法天然抗溢出!


🧪 在 main() 中使用调度器

c 复制代码
// main.c
int main(void) {
    HAL_Init();           // HAL库初始化(含SysTick)
    system_init();        // 我们的系统初始化
    scheduler_init();     // 调度器初始化

    while (1) {
        scheduler_run();  // 不断检查并执行任务
        // 注意:这里没有 delay!调度器自己管时间
    }
}

优势

  • 多个任务互不干扰
  • 时间精准(依赖 SysTick)
  • 代码模块化、易扩展

🔧 三、关键工具:HAL_GetTick() 是什么?

  • 功能 :返回系统启动后经过的毫秒数(从 0 开始计数)

  • 原理:SysTick 定时器每 1ms 中断一次,内部计数器 +1

  • 用途

    • 任务调度(如本例)
    • 测量时间间隔:start = HAL_GetTick(); ... end = HAL_GetTick(); duration = end - start;
    • 超时判断:if (HAL_GetTick() - start > 1000) { timeout! }
c 复制代码
uint32_t HAL_GetTick(void);  // 返回值范围:0 ~ 4294967295(约49.7天)

⚠️ 注意:49.7天后会自动回绕到0 ,但 now - last_run 的比较依然有效!


🧠 本章口诀总结

🔌 初始化三步走

开时钟 → 配模式 → 设电平!

调度器像管家

任务列成表,时间到了就执行!

🧱 结构体是宝盒

把函数、周期、时间,统统装进一个包!

时间用差值比
now - last >= rate,不怕溢出稳如狗!

🔄 主循环只一行
scheduler_run(); ------ 万事交给它!


🔗 延伸学习


✅ 动手试试!

  1. 修复你的 system_init(),正确配置 LED 引脚
  2. 实现一个调度器,让 LED 每 1 秒闪烁一次
  3. 添加一个按键扫描任务,短按开灯,长按关灯

有了系统初始化 + 任务调度器,

你就拥有了一个可扩展、可维护、专业级 的嵌入式骨架!

下一步:往里面填业务逻辑吧!🚀

相关推荐
缘友一世6 小时前
精粤X99-TI D4 PLUS大板使用多显卡BIOS设置
bug·gpu·硬件·主板·x99
_OP_CHEN10 小时前
【算法基础篇】(四十八)突破 IO 与数值极限:快速读写 +__int128 实战指南
c++·算法·蓝桥杯·算法竞赛·快速读写·高精度算法·acm/icpc
不脱发的程序猿10 小时前
使用Python高效对比多个相似的CAN DBC数据
python·单片机·嵌入式硬件·嵌入式
皮蛋sol周11 小时前
嵌入式学习数据结构(二)双向链表 内核链表
linux·数据结构·学习·嵌入式·arm·双向链表
cui__OaO1 天前
Linux驱动--基于驱动设备分离的按键中断驱动
linux·运维·服务器·嵌入式
Hello_Embed1 天前
RS485 双串口通信 + LCD 实时显示(DMA+IDLE 空闲中断版)
笔记·单片机·学习·操作系统·嵌入式·freertos
Hello_Embed1 天前
RS485 双串口通信 + LCD 实时显示(中断版)
c语言·笔记·单片机·学习·操作系统·嵌入式
_OP_CHEN1 天前
【算法基础篇】(四十七)乘法逆元终极宝典:从模除困境到三种解法全解析
c++·算法·蓝桥杯·数论·算法竞赛·乘法逆元·acm/icpc
No0d1es1 天前
2025年第十六届蓝桥杯青少组省赛 Python编程 初/中级组真题
python·蓝桥杯·第十六届·省事
仰泳的熊猫2 天前
题目1109:Hanoi双塔问题
数据结构·c++·算法·蓝桥杯