IOT主流MCU: STM32和ESP32的学习了解

本文详细介绍了STM32和ESP32微控制器在编译、运行过程中的差异,包括编译本质、目标代码分段、系统的启动和应用程序加载。STM32采用单一文件架构或Loader/APP分离,而ESP32具有两级Loader,且其引导过程更为复杂。在低功耗设计上,STM32L系列的STOP模式提供了完美的低功耗解决方案,而ESP32的Deep Sleep模式在唤醒后需要重新初始化。此外,文章还探讨了FreeRTOS在ESP32上的多核支持和MicroPython的实现。
摘要由CSDN通过智能技术生成

1. 背景和总体介绍
一直以来都忙于做各种项目和产品,使得始终未能抽空很好的总结一下积累的在STM32L系列和ESP32这些MCU上的各种知识点,正好这次开发完之前的主体框架及版本后距离下一个大版本有了个空档期,我打算详细的记录一下这些知识点和学习心得并和各位进行交流。希望在和大家交流的同时,也能得到反馈并更正自己认识上的不足或者错误。

由于接触和掌握的局限性,这里记录的内容只是我自己在工作和学习中了解和使用到的,我不打算把别的不熟悉的内容也整理在一起增强篇幅,也不打算把一些我认为是大家都理解的东西再次详述一下。但是,为了叙述的必要,特别是和一些工程师讨论和接触后,发现很多人其实对某些基础知识不是非常清楚,关于这些有用的(或者是必要的)基础知识,我将以我az的观点进行一下简单描述。因此我整理了以下提纲作为这份资料的范畴:

	编译及运行
	编译的本质以及目标代码的分段定义
	编译后的文件
	系统的启动
	应用程序加载
	FreeRTOS的ESP32多核支持
	MicroPython的ESP32版本 
	低功耗设计

2. 编译及运行
应用程序主要由一些C或者类C语言编写,编译后烧写到MCU的内置或者FLASH上然后再引导运行。把这些语言代码转变为机器代码的过程就叫编译。
2.1. 编译的本质以及目标代码分段
MCU的运转是按照数据流水线和指令流水线分工进行的,顾名思义数据对应的就是全局或者局部变量的读或者写操作,指令对应的就是各种运算和分支控制。而所有这些都是基于编译好的二进制指令进行的,这些编译后的指令按照属性不同被分为不同的段(SEGMENT):

	代码段(TEXT SEGMENT)
	非全零数据段(DATA SEGMENT)
	全零数据段(BSS SEGMENT)

以上是编译阶段的三个典型目标,而我们所说的某个编译后的最终ELF或者BIN文件实质上就是上述不同分段的最终合成文件。这个过程是由编译器完成的,具体实行中编译器又分了两步来实现这个过程:
1) 编译(COMPILE)
根据编译规则和各MCU的特点、限制将原始代码转变为机器代码或者二进制数据块。以C语言为例,这一编译过程会产生很多.o结尾的二进制文件。
2) 链接(LINK)
把所有需要的各段机器代码或者数据块(以上若干.o文件)拼接为一个完整的最终目标文件。

除了以上三个典型分段之外,代码执行中还有两个非常重要的元素:

	堆(HEAP)
	栈(STACK)

堆本质上就是利用空余的内存进行运行中的动态分配和释放,这个空余的内存在大多数实现上用的都是以上三段的最高端结尾指针和堆栈的下端之间的的空余内存(中间可能预留部分缓冲)。对于很多裸操作系统的上位机MCU实现来说,压根就没用到堆。从程序运转来说,堆并不是一个必须的元素,完全取决于应用的需要,或者OS的倾好。

栈比堆则重要很多,我们口中经常提到的堆栈二字,大多数情况下说的其实只有栈而不包含堆。栈的操作有别与所有其它的,因为它是按照子上而下,或者从后向前的顺序进行的。以STACK_TOP = 0x20008000为例,当栈是全空时SP栈指针指向0x20008000,此时如果需要分配一个UINT32变量,那么该变量的地址则是SP-4 = 0x20007FFFC。同样,如果该局部操作结束,局部变量重新返回栈后,SP=SP+4=0x20007FFFC+4=0x20008000。这样的操作特点和我们的函数调用逻辑完全匹配,也即只有被调用者退出释放栈后,调用者才能继续退出并释放栈。但是这个设计对于程序的栈踩踏将是灾难的,假如被调用者的局部变量地址指针为uint32 *callee = (*uint32)0x20007FFF8,而调用者的局部变量指针为uint32 *caller=(*uint32)0x20007FFFC,如果被调用者出现程序故障并踩踏了下一个内存(callee[1]=0x12345678),那么这个操作实际上就会踩踏上一级调用者的空间(&callee[1] = 0x20007FFF8 + 4 = 0x20007FFFC)而不是向前空余的栈空间。以上栈踩踏是代码设计中应该严格检查并避免的。
注:
1) 以上caller/callee仅仅只是个举例,实际栈操作时还会压入调用者的下一步返回地址,这样被调用者通过获得这个地址可以实现函数结束返回上级调用者的下一步进行继续处理。假如栈踩踏的是这个返回地址,那么情况更糟,可能会出现意想不到的灾难性结果。

编译过程产生的.MAP文件可以非常方便的查看所有代码的符号地址、所属分段以及各分段地址。下面我们来看一段代码对各段的划分进行了解。

static char buf[1024];
void main(void) {
   
	char *str;
	const char *pattern = “hello,world!;
	str = (char*)malloc(1024);
	str[0] = 0;
	for(  int i = 0; i <3; i++ ) 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值