嵌入式小知识2

拉电流和灌电流

想象一下你的单片机(或者其他芯片)的引脚(Pin)是一个水龙头

  1. 拉电流 (Source Current):

    • 动作: 你把这个引脚设置成高电平(比如输出 3.3V)。

    • 比喻: 就像你打开了水龙头(拉),水(电流)从这个引脚流出来,流向外面的电路(比如一个 LED 灯,灯的另一端接地)。

    • 总结: “拉” 就是芯片的引脚往外输出电流,像“拉”着外面的设备工作。此时引脚是电源

  2. 灌电流 (Sink Current):

    • 动作: 你把这个引脚设置成低电平(比如输出 0V,也就是接地)。

    • 比喻: 就像你打开了下水道的盖子(灌),外面电路(比如 VCC 接了电阻,再接 LED,灯的另一端接到这个引脚)的水(电流)流进来,通过这个引脚流向芯片内部的“地”(GND)。

    • 总结: “灌” 就是芯片的引脚吸收电流,像“灌”水一样把外面的电流“喝”进去。此时引脚是

如何读手册

以这段代码为例子。首先要明白systemcoreclock是系统时钟的意思。

提高CPU利用率

像这个代码就有点问题了,每次都要去计算一次strlen(str),所以一般推荐

当然,也可以选择别的方法。因为反斜杠0是每一个数组最后一位

串口一般默认使用高电平

所以要给它一个上拉电阻,对应的IO口上。

串口的波特率

一般不要太高的波特率,比如2Mbps,会有很多问题,这个具体可以问问AI。

stm32还可以自己修改栈大小

你的工程里一定有一个汇编文件,通常叫 startup_stm32f4xx.s(后缀是 .s)。这个文件负责单片机上电后的第一次初始化。

RAM

STM32(以及大多数单片机)中,RAM(随机存取存储器)就像是一个**“大仓库”**。

当你的程序跑起来时,这个仓库里主要堆放了 4 类东西。理解了这 4 类,你就知道你的 RAM 到底被谁吃掉了。

1. 全局变量区 (Global / Static) —— “钉子户”

2. 栈区 (Stack) —— “临时办事处”

3. 堆区 (Heap) —— “自由租赁区”

这块区域是给程序员手动申请用的。

  • 谁在用?

    • malloc()calloc()realloc() 这些 C 标准库函数。

    • 某些第三方库(如 cJSON、某些 MQTT 库)内部可能会用到。

  • 特点

    • 如果你代码里从来不写 malloc,这块消耗通常为 0(或者非常小,取决于启动文件里的 Heap_Size 设置)。

    • 注意:有些工程默认 Heap_Size 开了 0x200 (512字节),如果你不用,可以在启动文件里把它改成 0,省点 RAM。

4. 所谓的“常数” (Const) —— 注意!这通常不占 RAM

  • 误区:很多新手以为 const int a = 10; 占 RAM。

  • 真相:在 STM32 中,加了 const 修饰的只读变量(尤其是大的数组、字符串常量),编译器通常会把它们扔到 Flash(硬盘) 里,而不是 RAM 里。

  • 优化技巧多用 const

    • char msg[] = "Hello"; -> 占 RAM(因为你可以修改它)。

    • const char *msg = "Hello"; -> 字符串本体在 Flash,RAM 只占一个指针(4字节)。

如何省 RAM?(优化建议)

  1. 大数组尽量别做局部变量

    • void func() { char buf[1024]; } -> 危险! 容易爆栈。

    • 改为全局变量 static char buf[1024]; -> 安全,移到了 BSS 区,虽然占总量,但不挤占栈。

  2. 字符串加 const

    • 打印菜单、AT指令字符串,全部加上 const

  3. 调整 Stack 和 Heap

    • 如果完全不用 malloc,把 Heap_Size 设为 0。

    • Stack_Size 够用就行,不要盲目开太大(除非 RAM 很富裕)。

ESP 自动重连”是指当 Wi-Fi 模块(ESP8266/ESP32)因为路由器断电、信号太弱或干扰导致断网后,如何恢复网络连接的机制。

这通常分为两种情况:一种是 ESP 模块自己偷偷干的(固件自带功能),另一种是 STM32 命令它干的(应用层逻辑)。

我们来详细拆解一下这两种方式,以及推荐你怎么做。


方式一:ESP 模块内部的“傻瓜式”自动重连

ESP 的官方 AT 固件里内置了一个功能。只要你曾经连上过一次 Wi-Fi,它就会把账号密码记住(保存在 Flash 里)。

  • 机制:一旦断网,ESP 内部的底层代码会不断尝试去连接刚才保存的那个路由器。

  • 指令AT+CWAUTOCONN=<enable>

    • AT+CWAUTOCONN=1:开启自动重连(出厂默认通常是开启的)。

    • AT+CWAUTOCONN=0:关闭。

  • 优点:STM32 代码写起来简单。STM32 只需要在开机时发一次连接命令,后面如果断网了,STM32 甚至都不用管,ESP 会自己拼命连。

  • 缺点:STM32 失去了控制权。STM32 不知道现在到底连没连上(除非不断查询),如果这时候 STM32 发送 HTTP 请求,必然会报错。


方式二:STM32 控制的“智能”重连(强烈推荐)

这是更稳健的做法。STM32 不依赖 ESP 的自动功能,而是通过代码逻辑来监控状态。

核心逻辑是: STM32 充当“监工”,每隔几秒钟问一下 ESP:“你还连着吗?”

  1. 如果连着,就正常干活(比如获取天气)。

  2. 如果断了,STM32 再次发送 AT+CWJAP 命令让它重新连接。

为什么推荐这种方式?

因为你的代码里实现了 wifi_is_connected()esp_at_http_get()。如果不做判断:

  • 假设断网了,ESP 正在尝试内部重连。

  • 此时你的 STM32 不知道,还在不停地发 AT+HTTPCLIENT...

  • ESP 会返回 ERRORbusy

  • 你的程序可能会卡住,或者得到错误的数据。

RTC

本身就是一个计数器,单片机一般都是32位,而且是无符号类型的,所以计数会非常舒服。当通过VBAT引脚给单片机供电,stm32芯片就可以继续维持其芯片上的一块叫后备区域的地方继续执行。

由于其耗电少,所以一般是可以由纽扣电池为其供电。一般都是低速外部时钟提供时钟信号。

中断清除

就比如这个EXTI_ClearITPendingBit(EXTI_LineX);

FPU

有这个就说明了打开了FPU了。

电容消抖

如何理解呢?

就是电容它要么吸收电流要么放出电流,不会同时进行。

当打开开关的时候,电容一直吃电流,直到满了,这个时候打开开关,吃的电流就全部往外吐了。吐给gnd,速度非常快。虽然会有抖动,但是抖动的时间,就比如时关时开,但是这个时间太短了,无法把电容充满电,也就无法让KEY3这个端口读到高电平。

我们拆解一下这“精彩的一瞬”:

  1. 动作: 你按下按键,电容里的电瞬间吐光了(变成 0V)。

  2. 抖动发生: 0.1毫秒后,金属片因为弹性**“意外断开”**了一下(电路变成了充电状态)。

  3. 电容的反应:

    • 这时候,电源 (3.3V) 确实想赶紧给电容“喂饭”(充电)。

    • 但是! 因为有一个 10kΩ 的大电阻 (R22) 卡在中间,喂饭的速度太慢了

    • 抖动只持续了极短的时间(比如 0.1ms)。

    • 在这 0.1ms 里,电容只来得及“吃”进一口饭,电压可能才从 0V 涨到 0.1V。

  4. 结果:

    • 对于单片机 (KEY3) 来说,0.1V 依然算作 低电平

    • 单片机根本没发现中间断开过。它觉得你一直按得死死的。

电容就是一个“反应迟钝的仓库”

  • 放电快(因为没电阻挡着,直接吐给GND)。

  • 充电慢(因为有 10kΩ 电阻挡着)。

编译和链接

编译:将文件变为可执行的二进制机器指令。保存这些二进制机器指令的文件叫做目标文件。每一个源文件对应目标文件。

3. 所谓的“残缺”长什么样?

既然不知道地址,编译器也不能罢工啊。于是,它决定**“留个坑”**(或者填个假地址)。

它生成的目标文件(机器指令)大概是这样的:

这就轮到链接器出场了:

这时候,指令才变成了完整的:“跳转到 0x08000555”

这就是为什么说它是“残缺”的——如果你现在强行运行这个文件,CPU 执行到这就跳转到 0x00000000 去了,程序直接崩溃。

4. 链接器的修补(生成可执行文件)

  • 此时的“目标文件”(Object File)虽然是二进制指令,但这时的指令是**“甚至是残缺的”**(比如函数地址还是空的),所以它还不能运行,只能躺在硬盘里。如何理解残缺呢?

  • 假设你的代码是这样的:

    // main.c 文件
    void main() {
        LED_ON();  // 调用点灯函数
    }
    

    但是!LED_ON() 这个函数的具体代码,是写在另一个文件 led.c 里的。

    2. 编译器的尴尬(生成目标文件 .o)

    当你编译 main.c 时,编译器是一个很死板的翻译官,它从上往下翻译:

  • 看到 void main() -> 翻译成机器码 0101...(意思是建立函数栈帧)。

  • 看到 LED_ON(); -> 编译器知道这是“跳转”指令。

    • 它的心里活动是:“我知道你要跳到 LED_ON 那里去执行,但是!LED_ON 到底在哪?

    • 因为 LED_ONled.c 里,编译器现在只盯着 main.c 看,它根本不知道 LED_ON 在内存的第几号房间。

  • 指令意思: CALL (呼叫/跳转)

  • 目标地址: 0x00000000 (这是一个假地址/占位符)

  • 附带一张小纸条(重定位表): “注意!这里原本要填 LED_ON 的地址,但我不知道,先空着,麻烦后面来的人(链接器)帮我填一下。”

  • 它手里拿着 main.o(有一张小纸条说缺地址)。

  • 它手里还拿着 led.o(里面有 LED_ON 的真实代码)。

  • 它经过计算,发现 LED_ON 最终会被放在内存的 0x08000555 这个位置。

  • 回填: 它跑回 main.o 的那个坑里,把 0x00000000 涂改掉,填上 0x08000555

这就轮到链接器出场了:

  1. 它手里拿着 main.o(有一张小纸条说缺地址)。

  2. 它手里还拿着 led.o(里面有 LED_ON 的真实代码)。

  3. 它经过计算,发现 LED_ON 最终会被放在内存的 0x08000555 这个位置。

  4. 回填: 它跑回 main.o 的那个坑里,把 0x00000000 涂改掉,填上 0x08000555

这时候,指令才变成了完整的:“跳转到 0x08000555”

链接器将目标文件打包成可执行程序。编译器除了打包这个还会打包标准库等现成库。链接器(Linker)就像装修工,不仅要把你自己造的家具(目标文件)摆好,还得把买来的现成家电(标准库,比如 printf 的实现代码)也接好电线。

拿helloworld来说,编译器在遇到printf的时候,它不知道这个是啥玩意,并不是完全不知道,这不是编译器关心的东西,编译器只能看到局部,只能看到一个原文件内部,它虽然不管 printf 的代码在哪里,但它必须知道 printf 长什么样(这就需要写 #include <stdio.h>)。

  • 如果你不写头文件,编译器会抱怨:“我都没见过这个人的‘名片’,你让我怎么给你打包?”

编译器只关心**“名片”(声明),不关心“真人”**(定义)。

链接器才是关心printf哪里来的。它是真的要去把 printf 的**“真人”**(二进制代码)找出来,塞进你的程序里。

结合extern

房间 A (sensor.c) —— 有实物

// 这里真正定义了变量,分配了内存空间
int temperature = 25; 

房间 B (main.c) —— 想借用

void main() {
    // 我想打印 temperature,但是编译器不知道它是谁!
    printf("%d", temperature); 
}

2. 这里的矛盾(编译报错)

当你尝试编译 main.c 时,编译器(那个死板的翻译官)会立马报错:

Error: 'temperature' undeclared (first use in this function). 翻译: “你不讲武德!在 main.c 这个房间里,我压根没看到你定义 temperature,你凭什么用它?”

因为编译器是**“单文件主义者”**,它编译 main.c 时,完全看不到 sensor.c 里有什么。

为了让编译器闭嘴,你需要用 extern 给它写一张“承诺书”。

我们修改 房间 B (main.c)

// 这就是 extern 的作用!
extern int temperature; 

void main() {
    printf("%d", temperature); // 现在编译器放行了
}

这句话的意思是:

“亲爱的编译器,请不要报错。虽然在这个文件里没看到 temperature 的定义,但我承诺:它一定在别的什么文件里定义过了。你先给我生成一个‘残缺’的目标文件,回头让链接器去找它。”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值