挑战用一百个字节写一个闪烁灯程序!

嵌入式ARM 2023-05-23 16:25

本文字数较多,通篇阅读约10分钟

# 作者:Roff Segger,麦克泰技术测试、翻译和编写

我们使用SEGGER公司的Embedded Studio开发环境进行测试:在一个Cortex-M微控制器上,看看需要使用多少Flash存储器才能够完成一个LED灯的闪烁?


目标:

· 使用少于100个字节的程序完成一个闪烁应用

· 使用人眼容易看到的切换频率(即1-5Hz范围)

· 主程序用C/C++语言编写

· 使用方便得到的硬件

· 不使用或禁用工具链的运行时系统初始化

 

本文将大致介绍我们要使用的每一个字节和每一条指令。这是一个了解系统启动时到底发生了什么,即在到达main()函数之前“底层”发生什么的好途径。

 

简而言之:使用Embedded Studio开发环境可以在使用不到100字节的程序内完成这个工作。

01

硬件

我们使用的硬件是一块STM32跟踪参考板。它非常简单,只有一个STM32F407微控制器、3个LED、一个调试/跟踪接口和一个USB供电端口。

每个J-Trace仿真器交付中包含该开发板,然而,在这里,我仅仅使用常规的J-Link功能下载和调试程序。用户也可以选择任何带LED的硬件测试。


02

生成项目

非常简单,打开Embedded Studio开发环境,从菜单中选择File -> New Project,选择第一个选项,创建可执行文件。

根据提示,选择使用默认值,单击next几次后,我最终得到了一个小项目,如下面的Project Explorer窗口中所示。

选择Build->Build Mini或按F7构建我们的程序。

Debug -> Go或F5启动调试器。

我们现在没有连接硬件,所以Embedded Studio要求我们使用内置模拟器。

点击Yes或点击Enter启动模拟器。

 

调试器停在main()函数处,这是一个标准的 “Hello world”应用程序。

现在,为了实现最小的应用程序,我们将其简化为一个基本上是空的循环。

int main(void) {  int i;     do {    i++;  } while (1);

结果只占用了158字节的Flash。这已经非常不错了,但是在添加实际LED闪烁功能之前,我需要了解内存的占用,以及如何使我的程序最小化。

为了做到这一点,我可以查看Memory Usage Window、链接器映射文件、生成的ELF文件,或者简单地查看Project Explorer。

从Project Explorer窗口可以知道,这个可执行文件由3个源程序文件构成,以及它们使用了多少Code + RO空间。请注意,这些是编译器生成对像的数值。对于最终的可执行文件,链接器可以消除未使用的功能,或者在必要时添加一些结合层代码(从Flash跳到RAM或从Thumb指令跳到ARM指令)和填充(如:保证4字节对齐)。

另一个使用Flash存储器的地方,可能是从库中链接进来的代码,例如:C运行时库。然而,我们的小项目并没有使用库函数,因此我们不必考虑库代码的空间占用。

而且,Project Explorer展示了每个源文件的内存使用情况(2、128和24字节)和项目可执行文件总的内存使用情况:158字节。这和我们在Output窗口中看到的数值相同。

03

理解项目结构

这三个文件的用途?我们的应用程序只是一个简单的main()函数。为什么我还需要另外两个文件呢?

🔹main.c – 应用程序。

🔹C ortex_M_Startup.s – CPU相关代码,包含中断向量表。

🔹SEGGER_THUMB_Startup.s – 应用编程人员不需要修改的代码。

让我们更详细地了解它们,以揭开大家都想知道的谜团:启动代码是如何工作的?

有了这些知识,让我们看看如何缩小我们的应用程序。

04

main.c

main.c包含我们的应用,一个最简单的main()函数。

我们的编译器足够智能。它可以看出这个程序什么都不做,并将其优化为只使用一条指令或两个字节代码的空循环。

我怎么知道的?我们可以看看main.o,这是编译器产生的输出。在Project Explorer中,右键单击main.c->Show Disassembly,或者展开它,双击Output files中的main.o。它揭示了主程序只有一个分支。

这是我们的主应用程序。我们已经没有办法再简化它了。

05

Cortex_M_Startup.s

Cortex_M_Startup.s包含了应用程序在Cortex-M硬件上执行所需要的CPU相关代码。它包含中断向量表和上电或复位时执行的函数:Reset_Handler。

此文件使用了大部分Flash空间。让我们仔细看看它产生了什么。

Cortex_M_Startup.o显示其包含中断向量表 .vectors段及默认的异常处理程序实现。

section .vectors<_vectors>00000000 .word 0x0000000000000000 .word 0x0000000000000000 .word 0x0000000000000000 .word 0x0000000000000000 .word 0x0000000000000000 .word 0x0000000000000000 .word 0x0000000000000000 .word 0x0000000000000000 .word 0x0000000000000000 .word 0x0000000000000000 .word 0x0000000000000000 .word 0x0000000000000000 .word 0x0000000000000000 .word 0x0000000000000000 .word 0x0000000000000000 .word 0x00000000 section .init.NMI_HandlerE7FE b 0x00000000 section .init.MemManage_HandlerE7FE b 0x00000000 section .init.BusFault_HandlerE7FE b 0x00000000 section .init.UsageFault_HandlerE7FE b 0x00000000 section .init.SVC_HandlerE7FE b 0x00000000 section .init.DebugMon_HandlerE7FE b 0x00000000 section .init.PendSV_HandlerE7FE b 0x00000000 section .init.SysTick_HandlerE7FE b 0x00000000 section .init.Reset_HandlerF7FFFFFE bl 0x00000000F7FFBFFE b.w 0x00000004 section .init.HardFault_Handler4908 ldr r1,680A ldr r2, [r1]2A00 cmp r2, #0D4FE bmi 0x00000006F01E0F04 tst lr, #4BF0C ite eqF3EF8008 mrseq r0, mspF3EF8009 mrsne r0, pspF0424200 orr r2, r2, #0x80000000600A str r2, [r1]6981 ldr r1, [r0, #24]3102 adds r1, #26181 str r1, [r0, #24]4770 bx lrE000ED2C .word 0xE000ED2C

这就是罪魁祸首。

 

ARM内核定义了向量表中的前16个表项,然后是设备外部中断的表项。该文件提供了一个有16个表项(或64个字节)的向量表。这些条目仅用于该表。

在应用程序中,我们没有处理任何故障或中断,实际上我们只需要Reset_Handler,这是复位立即执行的代码。我们还需要向量表中的第一个表项,它在复位时完成堆栈指针(SP)的初始化。

因此,我们删除所有不必要的表项,将此表删减为两个表项,同时将消除默认的异常处理程序。

我们重新生成应用程序。不错!现在应用减少为42个字节。

让我们看看输出的elf文件的内容。

 

从0x0000 0000开始的8个字节:向量表,包含初始化SP和指向Reset_Handler的指针。

从0x0000 001E 开始的8个字节: Reset_Handler,两条4字节指令。链接器插入的一条nop指令,代替SystemInit的调用(在应用程序中不存在),以及一个跳转到_start的指令。

从0x0000 0008开始的20字节:SEGGER_THUMB_Startup.s的通用运行时初始化,它执行链接器生成的对来自SEGGER_init_table的初始化函数的调用,然后,调用main,如果main返回,则停在exit循环中。

从0x0000 0028开始4字节:链接器生成了SEGGER_init_table,

其中包含需要在main之前调用的初始化函数。它可能包含段初始化(复制初始化的数据)、段填充(用于0初始化的静态变量或堆栈的预填充)、堆初始化或全局C++对象的构造函数调用。这些都没有在我们的应用程序中使用。

最后一条(唯一)指令是跳到运行时初始化的末尾,调用main函数。 

加上从0x00000026开始的为对齐SEGGER_int_table的 2个字节的填充,总共是42个字节。

因为应用中没有使用SystemInit功能,所以我们可以删除bl SystemInit语句,并用nop取代,以节省4个字节,并减少到38+2=40个字节。

我们的应用程序已经是尽可能小了。下面我们开始添加闪烁代码!

06

添加闪烁代码

我们编写了一些用于初始化和控制参考板上LED的代码和一个简单的延迟函数。

有了这些代码,我们就可以创建带有闪烁功能的主应用程序了,如下所示:

/****************************************** main()** Function description* Application entry point.*/int main(void) {   _InitLED();   for (;;) {     _SetLED();    _Delay(NUM_DELAY_LOOPS);    _ClrLED();    _Delay(NUM_DELAY_LOOPS);   }}

完整的源代码工程可以访问(可点击“阅读原文”):https://blog.segger.com/wp-content/uploads/2020/08/Blinky_Mini.zip


让我们重新构建并检查输出。 

成功了!应用程序的大小只有96个字节(需要使用release模式构建,使用debug模式代码体积会比较大)。

它真的可以运行吗?让我们试一试。我们将电路板连接到J-Link,并将J-Link连接到我们的计算机。按F5键运行它。就像这个项目开始时一样,调试会话开始并运行到main函数,只是这次是在实际硬件而非模拟器上。当我们再次点击F5继续执行时,我们可以看到开发板上的LED0在闪烁。

07

小结

用C语言写的闪烁程序确实可以放在不到100字节的程序(或者更准确地说是只读)存储器中。

启动代码不需要那么复杂。它只是完成了硬件的初始化(SystemInit的用途)和运行时系统的初始化。

运行时系统初始化由Embedded Studio和SEGGER链接器负责。它确保只包含必要的代码,以使生成的可执行文件尽可能小。

SEGGER链接器还能够包括特定的初始化,例如:在需要的时候,完成堆的初始化和调用构造函数。这些功能是由链接器中的脚本控制。

initialize by symbol __SEGGER_init_heap { block heap }; // Init the heap if there is oneinitialize by symbol __SEGGER_init_ctors { block ctors }; // Call constructors for globalobjects which need

SEGGER链接器生成的启动代码非常小,并且易于理解。联合高效的SEGGER编译器与模块化的运行库和主机端输出printf()函数,我们就可以傲视群雄了。

看看电脑上简单的“Hello World”程序的大小,也许我们还应该提供一个可以在电脑上生成相同小程序的SEGGER Studio。

你程序还能更精简吗?用你的工具链试试,挑战用100字节写一个闪烁程序!我相信,在同样的硬件上,这将是很难被击败的。

08

这个项目的代码还能更紧凑吗?

令人惊讶的答案是:是的。

 

首先:一些微控制器具有切换寄存器,这允许将循环切割为_ToggleLED() / Delay()。

还有,初始化内容需要的代码量各不相同,在其他硬件上可能会更小。

但是即使在相同的硬件上,我们也可以进一步减小程序大小。

我们可以将_start放入向量表中,这样程序就可以在通用启动代码中开始执行,从而节省了4字节的跳转空间。 

我们可以删除exit() 和2字节的分支,因为我们知道main()程序中永远不会返回。 

因为我只想要不到100个字节的程序,所以,让我们到此为止吧。

 

最后,祝大家编码快乐!

END

来源:麦克泰技术

版权归原作者所有,如有侵权,请联系删除。

推荐阅读
嵌入式开发成本有多高?
【非常C结构】简单而不失强大的表格
状态机——单片机的万能语言(附代码)

→点关注,不迷路←

嵌入式ARM 关注这个时代最火的嵌入式ARM,你想知道的都在这里。
评论 (0)
  • 为增加微孔加湿器的雾化量,以及从外观和功能设计角度,进一步差异化桌面小型加湿器,市场上出现了越来越多的双头甚至多头的微孔雾化加湿器,用两根棉棒连接两个微孔雾化片,可以同时工作雾化(如下图所示,图片来自网络,仅供参考,侵删),也可以只是其中某一个微孔陶瓷片单独工作雾化。这种双头雾化的设计,存在一个刚需的规格要求:双头的一致性,也就是要保证两个微孔在雾化时的流量大致相同,雾态从外观上不能有明显的差别。消费者语言简单说就是:不能一个高一个低。实际市场的反馈,有公司已经收到不少针对“双头雾化明显不一致”
    Loximonline 2025-03-10 22:11 84浏览
  • 质量管理体系可以依公司场址所有产品与服务过程管理,输入与输出活動來推行使用,例如电动自行车产业包括一阶委外加工供应商、客供品管理、风险管理与质量一致性车辆审验作业等。中小企业要确保组织质量系统的程序及政策得以落实。有效的执行质量保证责任,以满足客户的需求,成公司的目标质量政策,需制文件程序化。质量管理体系定义落实公司质量管理而建立的组织架构、工作职责、作业程序等并将其文件化管理。一般中小企业质量系统依据当地政府法令与ISO国际标准规范要求,以追求客户满意需求过程导向、公司的质量政策制定的。其文件
    优思学院 2025-03-11 11:25 91浏览
  • J599系列光纤连接器的特点标准J599 III系列光纤连接器、J599 A8系列光纤连接器和J599 A6系列光纤连接器均具有相同的符合GJB599B标准规定的插座法兰尺寸。其中,J599 A8系列光纤连接器和J599 A6系列光纤连接器可提供APC的端面类型,其插入损耗和回损损耗性能更优。J599系列光纤连接器的未来发展方向随着国内光纤通信技术的日趋成熟,光纤处理工艺水平的不断提高,以及对光纤连接器需求的多样化,J599系列光纤连接器正在向低损耗、高密度、高可靠方向发展。中连讯科J599 I
    用户1741596356358 2025-03-11 14:24 90浏览
  •        传统语音芯片在复杂场景下的高功耗问题长期困扰行业。以某主流智能音箱为例,其待机日均耗电0.05度,年耗电量超18度,相当于一盏5W LED灯全年耗电量的3.6倍。思必驰TH1520芯片通过双核DSP架构与40nm先进制程的协同创新,将典型场景功耗压缩至15-80mW,仅为行业平均值的1/5,成功破解了这一难题,并在美的空调、小鹏P7等产品中实现商用落地。       双核DSP架构的分工与协作是TH15
    中科领创 2025-03-11 15:07 132浏览
  • 随着全球对光伏、风电等可再生能源需求的持续增长,在全球能源转型的浪潮中,储能技术凭借着可平衡能源供需、提高能源利用效率等优势,已成为实现 “双碳” 目标的核心支撑。据国家能源局公布数据显示,截至2024年底,我国新型储能装机规模突破7000万千瓦,约为“十三五”末的20倍,比2023年底增长超过130%,市场前景持续向好。目前,储能系统正朝着高电压(1500V+)与长寿命(≥10 年)等方向稳步演进,然而,愈发高压化、复杂化的储能系统亦面临着严峻的安全挑战。例如,储能电池组与控制电路的直接连接可
    华普微HOPERF 2025-03-12 11:16 39浏览
  • 文/郭楚妤编辑/cc孙聪颖‍2024年9月起,家电以旧换新政策在全国范围内广泛落地。8大类产品的覆盖、15%—20%的强劲补贴力度,对消费的拉动超越预期。1月15日,国家发改委和财政部联合发布了《关于2025年加力扩围实施大规模设备更新和消费品以旧换新政策的通知》(以下简称“通知”),明确了补贴设备类型和补贴幅度。2025年以旧换新政策覆盖范围新增手机、平板、智能手表手环3类数码产品,要求单件售价不超过6000元,按产品售价的15%给予补贴。每位消费者每类产品可享受补贴一次,且每件不超过500元
    华尔街科技眼 2025-03-11 09:40 58浏览
  •        记得二十多年前,年轻的我刚入行,加入了当年的全球最大的企业也是医疗器械的龙头老大,在企业内医疗器械分公司业务被誉为“皇冠上的宝石”,在他众多的卓越分公司中熠熠生辉格外耀眼。而当年年轻的我就加入了行业中的“黄埔军校”中茁壮成长,开始了追梦(也是噩梦)之旅,工作中得知该公司工程师在美国很受尊敬,路上出示工作证连警方都会优先放行。而在国内的工作让我深切感受到了一辆跑车是如何奔驰在崎岖不平的山路上,感恩这些年来祖国变的富强,建设出了条条康庄大道。风流
    广州铁金刚 2025-03-12 12:09 47浏览
  • 故障现象一辆单电机,前驱的纯电动汽车。方向盘往一边打死过弯,急加速下,听到底盘传来“呜呜”声。在车内能听到,但不算太明显,要仔细听才能听到。类似轴承的声音,车速在30~40km/h下也能重现。故障诊断初步判断根据故障现象的描述,初步怀疑可能的故障点是:1) 左边轮胎的轴承响2) 右边轮胎的轴承响3) 中间的减速器响4) 驱动电机响为了更精确地定位故障,我们使用虹科Pico NVH异响设备来捕捉故障出现时的实时振动和声音数据,用数据说话!设备连接与探头布置如图1,将四通道分别通过四个NVH接口盒与
    虹科Pico汽车示波器 2025-03-12 13:35 61浏览
  • CS5228 是一款单端口 HDMI/DVI 电平转换 / 中继器,具有重新定时功能。它支持高达 6.0 Gbps 运行速率的交流和直流耦合输入T-MDS 信号,具备可编程均衡和抖动清理功能。它包含 TypeC双模式 DP 线缆适配器寄存器,可用于识别线缆适配器的性能。抖动清理 PLL(锁相环)能够消除输入抖动,并完全重置系统抖动容限,因此能更好地满足更高数据速率下 HDMI 抖动合规性要求。设备的运行和配置可通过引脚设置或 I2C 总线实现。自动断电和静噪功能提供了灵活的电源管理。2. 特性
    QQ1540182856 2025-03-11 15:35 67浏览
  • 行车记录仪存储卡现状描述行车记录仪作为现代驾驶中的重要安全设备,其记录的视频资料在关键时刻往往能起到至关重要的作用。然而,许多车主在使用行车记录仪时,经常会遇到存储卡提示需要格式化的情况。这一提示不仅让车主们感到困惑,更可能让他们担心行车记录仪的数据安全问题。行车记录仪存储卡提示需要格式化,意味着存储卡上的数据可能无法被正常读取,或者存储卡本身存在某种故障。这种情况下,行车记录仪可能无法继续录制新的视频,而原有的视频资料也可能面临丢失的风险。分析行车记录仪存储卡提示需要格式化怎么办当行车记录仪存
    172林 2025-03-12 14:00 59浏览
  • 在捷配PCB板厂的官网计价页中,材料选择难住了很多新打板的工程师,接下来就提供一些疑问解答:一、PCB 原材料有哪些?PCB 制造需要诸多原材料,对于 PCB 厂家而言,这是生产的基础要素。其中包括覆铜板、绝缘金属基板、半固化片、铜箔、阻焊油墨、丝印油墨以及一些化学药剂等。在柔性 PCB 制造时,原材料则有聚酰亚胺和聚酯柔性覆铜板、覆盖膜、胶粘剂、电磁屏蔽膜,有时还会用到钢板或 FR - 4 作为补强材料。 二、原材料在 PCB 成本中的占比情况如何?原材料成本在 PCB 运营成本中所
    捷配科技 2025-03-12 09:16 60浏览
  • 随着科学技术的不断发展,触摸芯片在我们的生活中开始扮演着越来越重要的角色,大到工业设备,小到家用电器中都能找到它的身影。相信大家都很好奇触摸芯片到底是怎样一个神奇的存在呢?那我们今天就来一探究竟。要了解触摸芯片,首先我们要知道触摸芯片的定义。触摸芯片是一种感知人机交互的电子元器件,一般分为电阻式和电容式两种触控技术,目前市场上大部分触摸芯片是基于电容式触控感应技术的触摸芯片。我们就以此为例来说明。<iframe height=340 width=100% allowscriptaccess
    ICMAN 2025-03-12 09:37 50浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦