单片机高效并发编程:基于命名协程的轻量级多任务方案

news/2025/11/24 17:30:11/文章来源:https://www.cnblogs.com/zxc-cppnb/p/19264446

引言

在嵌入式开发中,如何在资源受限的单片机上实现高效并发一直是个挑战。传统RTOS虽然功能强大,但内存开销和复杂性较高。
本文介绍一种基于协程的轻量级并发方案,实现起来非常简单,通过创新的宏设计实现了代码段命名,大幅提升了代码的可读性和可维护性。
这个编程思路的灵感是lua语言带给我的,如果你学过lua你会发现我就是再精简的模拟lua语言的协程。

协程的基本原理

协程是一种用户态的轻量级线程,其切换由程序控制而不涉及内核态切换,因此开销极小。我们的实现基于 Duff's Device 技术,
通过 switch-case 语句和标号计算实现函数的多重入口。

协程框架核心设计

enum CoroutineStatus{COROUTINE_READY = 0,       // 就绪状态COROUTINE_RUNNING = 1,     // 运行状态COROUTINE_SUSPENDED = 2,   // 挂起状态COROUTINE_FINISHED = 3     // 完成状态
};struct Coroutine{void *args, *res;                   //协程参数和返回值size_t pc;                          //模拟程序计数器enum CoroutineStatus status;        // 当前状态void (*func)(struct Coroutine *);   // 协程函数
};typedef struct Coroutine Coroutine;
typedef void (*CoroutineFuncType)(Coroutine *);

核心管理函数

// 初始化协程
void coroutineInit(Coroutine *cor, void *args, void *res, CoroutineFuncType func){cor->args = args;cor->res = res;cor->func = func;cor->pc = 1;cor->status = COROUTINE_READY;
}// 运行协程单步
int runCoroutine(Coroutine *cor) {if (cor->status == COROUTINE_READY || cor->status == COROUTINE_SUSPENDED){cor->status = COROUTINE_RUNNING;cor->func(cor);return 0;}return -1;
}// 轮询调度器
void schedule(Coroutine *cor, size_t len){int active;do{active = 0;for (int i = 0; i < len; ++i){if (cor[i].status != COROUTINE_FINISHED){runCoroutine(&cor[i]);active += 1;}}}while (active);
}

协程控制宏

// 跳转到指定位置并挂起
#define COROUTINE_YIELD_SET_PC(cor, val)        \do{                                           \(cor)->pc = val;                            \(cor)->status = COROUTINE_SUSPENDED;        \return ;                                    \}while(0)// 跳转到相对位置并挂起
#define COROUTINE_YIELD_NEXT(cor, delt)         \do{                                           \(cor)->pc += delt;                          \(cor)->status = COROUTINE_SUSPENDED;        \return ;                                    \}while(0)// 结束协程
#define COROUTINE_END(cor)                      \do{                                           \(cor)->status = COROUTINE_FINISHED;         \return ;                                    \}while (0)// 生成标签枚举
#define MAKE_LABEL(n1, p1, n2, p2, n3, p3, n4, p4,      \n5, p5, n6, p6, n7, p7, n8, p8,      \n9, p9, n10, p10, n11, p11,          \n12, p12, n13, p13, n14, p14,        \n15, p15, n16, p16, ...)             \enum{                                                 \n1 = 1, n2, n3, n4, n5, n6, n7, n8, n9, n10,        \n11, n12, n13, n14, n15, n16                        \}// 生成case语句切片,模仿goto
// 由于标准c语言语法中goto后面只能加常量标签所以这里用switch-case模拟goto语句
#define MAKE_LABEL_SLICE(n1, p1, n2, p2, n3, p3, n4, p4,        \n5, p5, n6, p6, n7, p7, n8, p8,        \n9, p9, n10, p10, n11, p11,            \n12, p12, n13, p13, n14, p14,          \n15, p15, n16, p16, ...)               \case n1: p1; case n2: p2; case n3: p3; case n4: p4;           \case n5: p5; case n6: p6; case n7: p7; case n8: p8;           \case n9: p9; case n10: p10; case n11: p11; case n12: p12;     \case n13: p13; case n14: p14; case n15: p15; case n16: p16;   \default: break;// 协程主体定义宏
#define COROUTINE_PROGN(cor, ...)                               \MAKE_LABEL(__VA_ARGS__, N16, , N15, , N14, ,N13,              \, N12, , N11, , N10, , N9, , N8, , N7,             \, N6, , N5, , N4, , N3, , N2, , N1,);              \switch ((cor)->pc){                                           \MAKE_LABEL_SLICE(__VA_ARGS__, N16, , N15, , N14, ,N13,      \, N12, , N11, , N10, , N9, , N8, , N7,     \, N6, , N5, , N4, , N3, , N2, , N1,);      \}

这里我只写了16个case,也就是说如果超过16个代码片段就会被抛弃,如果你的程序真的很复杂也可以自己再加,
不过16个应该能满足绝大多数情况了。
另一个问题是此方法可能要求编译器有一定优化能力,因为如果只写了几个片段剩下的case就是空的,
不过本人测试了一下,像clang和gcc对空case的优化特别好,哪怕不开编译优化也不会增加代码体积。
接下来我们写一个最简单的多协程计算数组所有元素的和,示范一下:

struct I32AddStructure{int *array;size_t len, idx;
};typedef struct I32AddStructure I32AddStructure;void add(Coroutine *cor){int *sum = (int *)cor->res;I32AddStructure *add = (I32AddStructure *)cor->args;COROUTINE_PROGN(cor,ADD_ONE_NUM /*给代码段命名,一般情况下可能无用,如果你想COROUTINE_YIELD_SET_PC进行绝对跳转的时候特别有用*/,{if (add->idx == add->len)COROUTINE_END(cor); // 已经到最后一个元素关闭协程*sum += add->array[add->idx++];COROUTINE_YIELD_NEXT(cor, 0); // 挂起,并且下次执行再执行此代码段});
}int main(){Coroutine cor[2];int array[100];I32AddStructure add1, add2;int sum = 0;for (int i = 0; i < 100; ++i){array[i] = i;}add1.array = array;add1.len = 50;add1.idx = 0;add2.array = array + 50;  add2.len = 50;add2.idx = 0;coroutineInit(&cor[0], &add1, &sum, add);coroutineInit(&cor[1], &add2, &sum, add);schedule(cor, 2);printf("%d\n", sum);return 0;
}

当然这并不是再单片机上运行的程序,只是简单的举个例子。核心用法就是使用COROUTINR_PROGN生成需要分割的代码段。
每个代码段用COROUTINE_YIELD_NEXT(相对跳转并挂起)或者COROURINE_YIELD_SET_PC(绝对跳转并挂起)主动的让出cpu
也就是说可以在一些耗时等待其他硬件操作完成时主动调用COROUTINE_YIELD让出cpu,或者在一段时间内完成多个作业也可以使用这个框架。
接下来再来一个复杂的例子(伪代码):

// 传感器参数
typedef struct {uint8_t sensor_pin;float temperature;float humidity;uint32_t sample_count;
} SensorParams;void sensorCoroutine(Coroutine *cor) {SensorParams *params = (SensorParams*)cor->args;static uint32_t last_sample_time = 0;COROUTINE_PROGN(cor,// 命名代码段:初始化传感器INIT_SENSOR,{printf("初始化传感器引脚 %d\n", params->sensor_pin);sensorInit(params->sensor_pin);last_sample_time = getSystemTime();COROUTINE_YIELD_NEXT(cor, 1); //挂起,下次运行时运行下一片段,也就是WAIT_SAMPLE_INTERVAL片段}, // 别忘了这里的逗号// 命名代码段:等待采样间隔WAIT_SAMPLE_INTERVAL,{if (getSystemTime() - last_sample_time < 1000) { // 1秒间隔COROUTINE_YIELD_NEXT(cor, 0); // 保持当前状态}COROUTINE_YIELD_NEXT(cor, 1); //挂起,下次运行时运行下一片段,也就是READ_SENSOR_DATA片段}, // 别忘了这里的逗号// 命名代码段:读取传感器数据READ_SENSOR_DATA,{params->temperature = readTemperature(params->sensor_pin);params->humidity = readHumidity(params->sensor_pin);params->sample_count++;last_sample_time = getSystemTime();printf("第%lu样本: 温度=%.2fC, 湿度=%.2f%%\n",params->sample_count, params->temperature, params->humidity);COROUTINE_YIELD_NEXT(cor, -1); // 回到等待状态// 或者使用COROUTINE_YIELD_SET_PC(cor, WAIT_SAMPLE_INTERVAL);});
}

使用相对跳转还是使用绝对跳转要看情况,使用绝对跳转可以在以后维护添加代码时在一定程度上不受影响,相反的相对跳转会受影响。
相对跳转适合挂起后接着运行下一段代码片段。

技术优势

1. 极低的内存开销

每个协程仅需约20字节内存(还可以接着优化,比如pc和status可以都用uint8_t)
无需为每个任务分配独立堆栈

2. 高效的上下文切换

切换开销仅为几个寄存器操作
无系统调用开销
确定性执行时间

3. 避免复杂的同步机制

协程在明确位置主动让出CPU
无需互斥锁、信号量等同步原语
降低死锁风险

4. 高度可移植性

纯C实现,不依赖特定硬件特性
可在任何支持标准C的平台运行
与RTOS兼容,可作为补充方案

5. 灵活的调度策略

支持轮询、优先级等多种调度方式
可根据系统负载动态调整

关于拓展

值得注意的是我并没有写协程休眠的机制,其实也很好写,需要在Coroutine结构体中增加一个变量,
在CoroutineStatus增加COROUTINE_SLEEPING状态,并添加几个休眠宏函数,再修改runCoroutine函数就可以了:

enum CoroutineStatus{COROUTINE_READY = 0,COROUTINE_RUNNING = 1,COROUTINE_SUSPENDED = 2,COROUTINE_SLEEPING = 3, //增加睡眠状态COROUTINE_FINISHED = 4
};#define GET_TIME() clock(); //在单片机中可以用SysTick获取运行时间int runCoroutine(Coroutine *cor) {if (cor->status == COROUTINE_READY || cor->status == COROUTINE_SUSPENDED){cor->status = COROUTINE_RUNNING;cor->func(cor);return 0;} else if (cor->status == COROUTINE_SLEEPING){//如果是睡眠状态检测是否到达唤醒时间if (GET_TIME() >= cor->sleepEndTime){cor->status = COROUTINE_SUSPENDED;}return 0;}return -1;
}//睡眠并指定下一次唤醒跳转到哪里,绝对跳转
#define COROUTINE_SLEEP_SET_PC(cor, time, pc_val)       \do{                                                   \(cor)->pc = val;                                    \(cor)->sleepEndTime = GET_TIME() + time;            \(cor)->status = COROUTINE_SLEEPING;                 \return ;                                            \}while(0)//睡眠并指定下一次唤醒跳转到哪里,相对跳转
#define COROUTINE_SLEEP_NEXT(cor, time, delt)           \do{                                                   \(cor)->pc += delt;                                  \(cor)->sleepEndTime = GET_TIME() + time;            \(cor)->status = COROUTINE_SLEEPING;                 \return ;                                            \}while(0)

不过可能很多人觉得这个和状态机很像,确实是这样,叫它封装的状态机也可以。最重要的是这种封装方式不仅简化代码,
而且也美观了一些不是吗?

结语

本文提出的基于协程的轻量级并发方案,为资源受限的嵌入式系统提供了一种简洁高效的并发编程范式。通过借鉴Lua语言的协程思想,
并巧妙运用C语言的宏定义和Duff's Device技术,我们成功地在单片机上实现了内存开销极低、切换效率极高的协程框架。
该方案的核心优势在于:
极简设计:每个协程仅需约20字节内存,无需独立堆栈
高效切换:纯用户态切换,无系统调用开销
代码优雅:通过宏定义实现了代码段命名,大幅提升了状态机代码的可读性和可维护性
高度可移植:纯C实现,不依赖特定硬件平台

与传统RTOS相比,本方案在满足大多数嵌入式并发需求的同时,避免了复杂的内存管理和同步机制,降低了系统复杂度和死锁风险。
特别是对于那些对内存和实时性要求极高的应用场景,这种轻量级协程框架展现出了独特的价值。
展望未来,该框架还可以进一步扩展,如增加优先级调度、协程间通信、动态创建销毁等功能。希望这个从Lua语言中汲取灵感的实现方案,能够为嵌入式开发者提供新的思路,在资源受限的环境中依然能够编写出清晰、高效的并发代码。
正如编程语言的设计哲学所示:简洁并不等于简单,优雅的解决方案往往来自于对问题本质的深刻理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/975085.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

动物实验机构权威推荐榜单:五家顶尖服务商助力科研创新

行业规范发展,实验动物质量持续提升 在我国生物医药与生命科学领域飞速发展的背景下,实验动物作为不可或缺的"活试剂",其质量与动物实验的规范性已成为影响科研成果可靠性与产业转化效率的关键环节。 在严…

动物实验机构排行榜发布:这五家公司最受科研机构青睐

据QYResearch最新调研统计,2024年全球动物实验服务市场规模已达约306.5亿元,预计到2031年将增长至近520.6亿元,2025-2031年期间年复合增长率达7.8%。同时,全球研究用动物模型市场同样呈现稳步增长,预计2031年全球…

如何选择动物实验机构?五家优质服务商综合评估

近年来,随着我国生物医药与生命科学领域的快速发展,实验动物质量与动物实验规范性已成为影响科研成果可靠性与产业转化效率的关键因素。在这一背景下,了解如何选择优质的动物实验机构,对于科研项目的顺利推进和实验…

Scrapy与Brotli解压缩漏洞导致拒绝服务攻击

本文详细分析了CVE-2025-6176漏洞,Scrapy框架在使用Brotli压缩时存在拒绝服务攻击风险,攻击者可通过特制数据耗尽客户端内存,影响版本包括Scrapy 2.13.3及以下和Brotli 1.1.0及以下。Scrapy与Brotli解压缩漏洞导致拒…

2025年11月单机游戏推荐:权威榜单与全面选择指南

随着数字娱乐产业的快速发展,单机游戏凭借其沉浸式体验和丰富的内容设计,持续吸引着广大玩家的关注。2025年,随着硬件性能的提升和开发技术的成熟,单机游戏市场呈现出多元化、高品质化的发展趋势。国家相关部门发布…

来挑战!Nano banana Pro 全网最低价APi,0.09/张,稳得可怕!

谷歌Nano Banana Pro(官方名称为Gemini 3 Pro Image)之所以被称为最强Ai绘画模型,是因为这次Pro版本是基于Gemini 3 Pro开发的新一代图像生成与编辑模型,不仅在图像质量上达到了新的高度,更在文字渲染、多图像融合…

计算机视觉:基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的零售柜商品检测识别系统(Python+PySide6界面+训练代码)(源码+文档)✅ - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

ECharts GL | 3D 地图区域高度设置

ECharts GL | 3D 地图区域高度设置Posted on 2025-11-24 17:13 且行且思 阅读(0) 评论(0) 收藏 举报3D 地图除了使用柱状图表现数值高低(参考ECharts GL | 3D 柱状图),还可以直接通过区域高度表达数据大小,常…

信誉卓著的医疗器械第三方公司:安全合规,专业可靠!

在医疗科技日新月异的今天,选择一家信誉良好、安全合规且专业可靠的医疗器械第三方服务公司,对于保障产品质量、加速产品上市进程至关重要。本文将为您详细介绍五家在该领域内表现突出的品牌,其中斯坦德医疗器械作为…

哪家做医疗器械第三方比较好?值得信赖的服务商推荐清单!

在医疗器械行业,选择一家可靠且专业的第三方服务商至关重要。这不仅关乎产品的合规上市,更直接影响到企业的创新效率与市场竞争力。本文将为您推荐五家值得信赖的医疗器械第三方服务商,其中斯坦德医疗器械将作为重点…

杭州可靠的GEO优化实力厂家排行榜单,短视频矩阵/GEO优化服务/GEO优化AI工具排名/GEO服务GEO优化实力厂家选哪家

行业背景分析 随着人工智能技术的快速发展,GEO优化服务已成为企业品牌推广的重要渠道。通过对主流AI平台生成内容进行优化,企业能够以更自然的方式展现品牌形象,获取AI流量红利。杭州作为数字经济先行区,涌现出一批…

值得信赖的医疗器械第三方公司:资质认证 + 专业检测,让医疗产品更可靠!

在医疗健康领域,医疗器械的安全性与有效性直接关系到患者的生命健康。因此,选择一家具备权威资质认证、提供专业检测服务的第三方公司显得尤为重要。今天,我们不仅将深入介绍行业内的佼佼者——斯坦德医疗器械,还会…

医疗器械第三方实验室哪家好?这几家实力口碑双在线!

在医疗器械行业蓬勃发展的今天,第三方检测实验室的角色愈发重要。它们不仅为医疗器械的安全性和有效性提供了科学依据,还助力企业加速产品上市进程,推动行业创新与发展。在众多第三方实验室中,有几家凭借其实力和口…

2025年水库铸铁闸门制造企业权威推荐榜单:渠道铸铁闸门/平板铸铁闸门/弧形铸铁闸门源头厂家精选

在水库安全与水资源配置体系中,铸铁闸门作为关键的控制设备,其质量与性能直接关系到工程的安全运行和效益发挥。 随着国家水利基础设施建设投入的持续加大,水库铸铁闸门市场迎来了新的发展机遇。面对众多的制造企业…

2025年水玻璃精密铸造制造企业权威推荐榜单:不锈钢锻件/管阀铸造/阀门铸造源头厂家精选

在高端制造业转型升级的浪潮中,水玻璃精密铸造以其在成本控制和复杂结构成型方面的优势,在精密铸造领域占据了重要地位。 据行业报告显示,2024年全球水玻璃铸造市场容量达亿元级别,其产品在国防、油气工业、汽车、…

2025医疗器械第三方测试机构推荐:靠谱选择 + 核心资质全解析!

在医疗器械行业蓬勃发展的今天,选择一家专业、可靠的第三方测试机构,对于确保产品合规上市、保障患者安全至关重要。本文将为您推荐几家在行业内享有盛誉的测试机构,其中重点介绍斯坦德医疗器械,同时涵盖其他几家知…

sklearn中的OneHotEncoder

先不管 LightGBM,咱先把这段 FeatureEncoder 当成一个小黑盒,看看它的 fit 到底干了啥。你问“举例讲解”,那我就给你造个最小可复现样例,按行走一遍。1. 先假设有这样一份原始数据 X 比如你的训练集里有这些列(随…

AI 十大论文精讲(七):Switch Routing 如何破解 MoE 的路由、通信与稳定性三大痛点

《Switch Transformers:用简单高效的稀疏化实现万亿参数模型》论文解读 本文深入解析了Google提出的Switch Transformers架构,该论文通过创新的稀疏化设计解决传统MoE模型的路由复杂性和训练不稳定性问题。核心创新在…

怎么都在 AKIOI

[<难度> <省份> <初始人数>-<剩余人数>] <结局> <表现分> <第一个人知识分> <第二个人知识分> ...集训流一直集训,压力大了就放假。 接受所有能加钱的机会。简单模式…

合规为先 安全护航 — 专业医疗器械第三方公司推荐!

在医疗器械行业,合规性与安全性是产品成功的基石。随着医疗技术的不断进步和监管要求的日益严格,选择一家专业、可靠的第三方服务公司成为企业产品上市的关键。今天,我们首先聚焦于一家在医疗器械领域深耕多年的领军…