当前位置:首页 > 工业控制 > 电路设计项目集锦
[导读]如果我们用Arduino核心在ESP8266上写任何代码,很可能已经遇到了这个瓶颈。你写的每一行代码都运行在loop()函数内部,而loop()是协作式的——它从上到下依次执行,只有在完成所有操作后,SDK才会有机会进行后台处理,然后再调用你。只要你的代码足够快且高效,这没有问题。但一旦某段代码决定卡住——比如阻塞式传感器读取、大量数值运算、或等待某个条件的while循环——其他所有操作就会被暂停。你正在闪烁的LED会冻结,串口输出会停滞,定时器也会漂移。通常建议是“不要阻塞,使用millis()和状态机”,这种方法确实有效。但当达到某个程度后,你不得不在每个程序中手动编写自己的小型调度器,这让人感到疲惫不堪。

如果我们用Arduino核心在ESP8266上写任何代码,很可能已经遇到了这个瓶颈。你写的每一行代码都运行在loop()函数内部,而loop()是协作式的——它从上到下依次执行,只有在完成所有操作后,SDK才会有机会进行后台处理,然后再调用你。只要你的代码足够快且高效,这没有问题。但一旦某段代码决定卡住——比如阻塞式传感器读取、大量数值运算、或等待某个条件的while循环——其他所有操作就会被暂停。你正在闪烁的LED会冻结,串口输出会停滞,定时器也会漂移。通常建议是“不要阻塞,使用millis()和状态机”,这种方法确实有效。但当达到某个程度后,你不得不在每个程序中手动编写自己的小型调度器,这让人感到疲惫不堪。

我真正想要的,是真正的实时操作系统(RTOS)所提供的功能:多个独立的任务,无论是否协作,都能公平地获得CPU资源。我希望在普通的非操作系统SDK上实现这一点,而不是将整个项目切换到FreeRTOS。结果发现这其实非常可行,而有趣的是,一旦掌握了技巧,实际上所需的变动部件却少得可怜。

本项目中看到的调度器、硬件定时器驱动程序以及上下文切换功能,都是从零开始编写的——它们基于PDI框架实现,而该框架中的抢占式调度是其内置子系统之一。将这样一个庞大的框架引入项目只为查看某一项特性,实在过于繁琐。因此,我所做的只是提取其中最关键的这一部分,剥离围绕它的框架辅助代码,将其转化为一个可一次性阅读的小型独立示例程序。这并非框架的重写,而是展示其核心功能的范例。

“先发制人”在这里实际上意味着什么

协作式调度是指任务会保留CPU,直到它主动将其释放。抢占式调度则是指无论任务是否愿意,都有可能被剥夺CPU的使用权。在这款ESP8266上,“某事物”就是硬件定时器。

简单来说,就是:设置一个硬件定时器(FRC1,也称为 timer1),使其大约每毫秒触发一次。每次触发时,它的中断处理程序会静默地记录下当时正在运行的代码的整个CPU状态——包括所有寄存器、程序计数器、状态寄存器等等——然后加载另一个任务的保存状态,并让处理器从该状态继续执行。对于该任务而言,似乎什么都没发生过,它完全不知道自己曾被“冻结”了一段时间,之后又恢复了运行。在多个任务之间,每秒重复一千次这样的操作,对观察LED和串口日志的人来说,它们看起来都同时在运行。这正是整个幻觉所在,而这也正是真正实时操作系统在底层所采用的机制。

ESP8266 的难点在于,Arduino/非操作系统环境并没有提供一个干净的“这里保存了上下文,请替换它”的 API。你需要深入到 Xtensa 中断触发时 SDK 推送的异常帧中,手动重新编写该帧,以便中断返回后能跳转到下一个任务,而不是你之前所在的那个任务。这就是反向工程的汇编部分,也正是使整个系统成为可能的关键所在。

关于示例

因此,该草图在常规的 loop() 旁边运行三个任务:

•Blink — 每250毫秒切换一次板载LED,然后进入睡眠状态。

•Counter — 每秒打印一次心跳行以及空闲堆。

•Hog — 意意地做着粗鲁的事:它在一个紧缩的循环中,整整持续两秒进行浮点数运算,从不休眠,从不让出,也从不给任何人让路。

在普通的协作式调度中,如果有一个进程(hog)运行,那就会造成灾难。在那两秒内,LED灯会一直保持不亮,串行输出也会停止,因为直到hog决定终止之前,其他任务都无法运行。但在这里,这并不重要。LED灯仍以稳定的4赫兹频率闪烁,计数器也持续打印,即使hog处于忙碌状态,因为定时中断不断介入,暂停hog,让其他任务得以占用CPU。当hog执行繁忙时观察LED——那持续不断的闪烁就是证明:抢占式调度是真实存在的,而不仅仅是伪装成协作式调度的假象。

调度器根据优先级来决定下一个执行的任务,但附加了一个老化规则:一个任务如果持续被忽略,其有效优先级会缓慢上升,最终赢得执行机会。这能防止繁忙的高优先级任务永远压制低优先级任务。

此示例中的文件

只有少数几个文件,了解每个文件的用途会有所帮助:

•XtensaContext.c — 上下文切换。这是一段精心编写的 Xtensa LX106 汇编代码,用于保存和恢复所有寄存器。巧妙的实现方式是通过定时器中断内部调用该函数:它不会像正常返回那样退出,而是直接修改被中断的异常帧,使得当中断返回时,CPU 会以一个完全不同的任务状态被唤醒。

•XtensaTimer.cpp — 心跳。初始化 FRC1/timer1 的硬件定时器及其裸露的中断服务例程(ISR)。该 ISR 会抓取异常帧并将其传递给调度程序。它还通过从下次重新加载时间中减去中断期间所花费的时间,确保自身的计时准确,从而防止时钟节拍缓慢漂移。

•PreemptiveScheduler.cpp — 脑部。它维护一个就绪队列和一个睡眠队列(两者均为固定大小的数组,特意设计以避免在中断期间发生堆内存分配,这会带来麻烦)。每次定时器滴答时,它会唤醒所有时间到的睡眠任务,将被中断的任务重新放回队列,根据优先级和老化机制选择下一个任务并切换执行。任务也可以通过 sleep(ms) 或 yield() 自动让出CPU。

•PreemptiveMutex.cpp — 保持共享资源的正常运行。由于任务可能在任何地方被中断,两个同时向串口打印的任务会愉快地将彼此的输出截成碎片。互斥锁解决了这个问题:当锁被获取时,请求任务会被暂停(实际上不会一直旋转,而是被挂起并脱离CPU),并加入队列;持有锁的任务在完成之后会将锁传递给下一位任务。这与任何实时操作系统中的互斥锁原理相同。该演示程序将每次串口打印都包裹在其中,因此输出内容仍然可读。

•ESP8266PreemptiveScheduler.ino — 该演示程序本身。注册三个任务,使用互斥锁保护 Serial,并退出后台运行。

串行监视器(115200 波特率)

类似以下内容将循环显示在串行监视器上:

……在整个打印过程中,板载LED一直正常闪烁——包括在猪只进行2秒的数字计算时。如果你关闭了互斥锁,就会看到线条交错,这能很好地说明互斥锁所解决的问题。

带元件的布线

无需额外布线,它直接使用了内置的LED和USB串口即可。只需将所有文件放入一个文件夹,打开已安装ESP8266核心的Arduino IDE,选择你的开发板,进行刷写,然后在115200波特率下打开串口监视器。每个任务应预留一定的栈空间(打印和浮点运算时,建议保留1–1.5 KB)。需要说明的是:该程序依赖于一些低级且特定于开发板的行为,因此应将其视为教育或实验用途,而非直接用于量产产品。此外,在定时器路径运行期间请避免对SPI闪存进行频繁操作,因为闪存操作会暂时关闭ISR所依赖的缓存。

本文编译自hackster.io

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
换一批
延伸阅读

你是否好奇过微控制器如何同时处理多个任务?本项目展示了STM32F407ZGT6 RT-Spark开发板上定时器中断和任务调度的工作原理。两个定时器以不同的频率闪烁LED,而按钮中断则在按下时立即响应——所有这些功能都通...

关键字: 微控制器 开发板 LED STM32CubeMX

该项目是一个简单的人体反应计时器,使用RT-Spark开发板和STM32F407ZGT6微控制器构建而成。它用于测量当RGB LED亮起时,人能多快做出反应。

关键字: 微控制器 开发板 LED STM32F407ZGT6

我制作了一个人类反应计时器,用来精确测量一个人对灯光亮起的反应速度。电路板会点亮内置的RGB LED灯,我的任务是尽可能快地按下板上的按钮(SW1)。

关键字: LED 电路板 示波器

引言:微控制器广泛应用于嵌入式系统中,用于控制输入和输出设备。GPIO引脚使微控制器能够与所使用的外部硬件进行通信,例如开关按钮、LED灯和显示屏(LCD)。

关键字: 微控制器 嵌入式系统 LED

这是我对硬件与电子世界的入门介绍。Arduino UNO上的GPIO引脚(D0 - D13)用于控制连接到它们的元件,这里就是LED灯。通过编写代码,可以产生电信号来控制LED灯的开关。

关键字: LED 面包板 GPIO引脚 Arduino UNO

本项目详细介绍了采用离散模拟和功率电子元件构建的自动应急备用LED照明系统。该电路将环境光感应与自动双电源切换(交流电源 + 3.7V 18650锂离子电池备用)无缝结合。

关键字: LED 功率电子 二极管 LM7805

我有一台M5Stack AtomS3R,放在一个盒子里。它是一个24×24×27毫米的立方体,前面玻璃后面是ESP32-S3芯片,128×128像素的显示屏,整个正面都是一块按钮。内置BMI270六轴IMU和8MB的PS...

关键字: LED 显示器 ESP32-S3

空气质量问题不仅限于户外,室内空间也可能积聚挥发性有机化合物(VOCs)、二氧化碳(CO₂)和污染物,影响健康与工作效率。本项目将XIAO RP2040和ENS160传感器改造为一款桌面型空气质量监测仪。设备内置一个JU...

关键字: 传感器 LED XIAO RP2040

在本项目中,您将学习如何使用Arduino Uno和Python构建一个低成本的多通道数据采集(DAQ)系统。该系统可实时采集多个通道的传感器数据,并自动将测量结果保存到CSV(逗号分隔值)文件中,便于在电子表格软件或其...

关键字: 数据采集 Arduino LM35传感器

厌倦了普通的不透明LED矩阵吗?在这个项目中,我将向你展示如何使用150颗WS2812B可寻址RGB LED,通过一块柔性透明PCB设计并制作出一个完全透明的15x10 LED矩阵。该PCB没有接地平面,因此在可见铜线的...

关键字: Arduino LED矩阵 柔性PCB WS2812B
关闭