第 2 篇:变量与数据类型——嵌入式为什么不用 int
第 2 篇变量与数据类型——嵌入式为什么不用 int回顾第1篇的「小车工厂模型」STM32麦轮小车是一座自动驾驶小工厂main是工厂大门#include是各类工具箱XXX_Init()是开工前设备自检while(1)是不停循环的流水线。本篇我们走进工厂仓库聊仓库里用来存放数据的储物盒变量以及怎么选尺寸合适的盒子数据类型。0. 引子变量就是贴标签的储物盒小车运行时需要存一大堆临时数据遥控指令、四个轮子转速、陀螺仪角度、上电计时……这些临时存放的数据在C语言里统一叫变量。通俗比喻变量就是仓库里带标签的储物盒往盒子里填数字叫赋值要用数据时打开盒子读取。而数据类型就是盒子的规格决定盒子能装多大数字、只能存正数还是正负都能放、占用多少仓库空间。盒子规格选错麻烦特别多小盒子装大数会溢出算错、规格不统一会让硬件通信乱套所以单片机写代码选类型比电脑写程序讲究很多。1. 为什么不随便用 int固定尺寸类型才是嵌入式标配电脑初学C语言大家习惯直接写int a;但打开我们小车的main.c几乎看不到裸int全是带数字的类型int16_tAX,AY,AZ,GX,GY,GZ;// 陀螺仪原始数据 (main.c:52)volatileint16_tSpeed1,Speed2,Speed3,Speed4;// 四个轮子的速度 (main.c:55)volatileuint8_tcar_mode1;// 小车模式位 (main.c:57)externvolatileuint32_tTimer7_5ms_Count;// 5ms 节拍计数 (main.c:54)int16_t、uint8_t、uint32_t看着很怪下面用大白话拆开讲。1.1 int 是尺寸不固定的“中杯饮料”单纯的int理论上是整数但它占用多少存储空间没有统一标准8位51单片机里int占16位我们32位STM32上int占32位。生活化比喻int就像奶茶店的中杯每家门店中杯容量不一样换个门店大小直接变。电脑软件只用在固定系统问题不大但单片机控制硬件必须精准会出现两个致命问题硬件通信、寄存器读写要求固定字节无线模块、串口协议、芯片寄存器的数据长度都是卡死的int尺寸飘忽不定换芯片/编译器就会数据错位小车直接失控。代码跨芯片移植不方便如果以后想把小车代码移植到别的单片机大量int会导致数值范围错乱改起来工程量巨大。1.2 定宽类型尺寸永远不变的标准储物盒stdint.h属于C语言标准库里面专门提供一套固定大小的数据类型盒子尺寸焊死不管什么芯片都统一规格名字很好分辨类型通俗解释能存放数字范围int8_t小号盒可存正负-128 ~ 127uint8_t小号盒只存0和正数0 ~ 255int16_t中号盒可存正负-32768 ~ 32767uint16_t中号盒只存0和正数0 ~ 65535int32_t大号盒可存正负±21亿左右uint32_t大号盒只存0和正数0 ~ 42亿左右比喻uint8_t就是标准小号盒子永远只能装0~255的数字换到任何芯片尺寸不变这就是“定宽类型”的优势。1.3 带u和不带u区分能不能存负数类型开头带uunsigned无符号盒子只能放0和正数不带u可以存正数、负数。对照小车代码就能看懂选型逻辑轮子速度Speed1~4用int16_t小车能前进、后退速度有正有负需要支持负数计时计数器Timer7_5ms_Count用uint32_t上电计时只会不断增加不会出现负数无符号能存更大数值小车模式car_mode用uint8_t模式编号只有0~7最小盒子足够节省单片机内存。小提醒盒子装满再加数字会循环归零类似汽车里程表9999直接跳0选类型一定要预估最大数值避免数值溢出出现诡异bug。2. float浮点小数整数运算容易踩坑小车角度计算、PID控制离不开小数看这段陀螺仪换算代码floatgz(float)GZ/16.4f*(3.1415926f/180.0f);// 角速度 °/s - rad/s这里两个新手高频坑简单讲清楚2.1 整数相除会直接丢掉小数C语言规则两个整数做除法结果只会保留整数小数直接舍弃。intres1/2;// 结果是0不是0.5所以代码里必须用(float)GZ把整数强制转成小数两边统一为浮点型才能算出带小数的正确结果。比喻整数和小数不能直接混算好比散装零食和整箱货物需要全部拆成散装才能统一计算。2.2 数字末尾的f有什么用16.4f、3.1415926f里的f代表这是float单精度小数只占4字节内存如果不写f默认是double双精度占用8字节。单片机内存不大能用float就不用double节省空间。3. #define宏定义给固定数字贴专属标签代码开头一堆#define宏main.c:43-49、72#defineANGLE_DIR-1// 角度环旋转方向#defineBASELINE_REFRESH_MS1000// 航向基准刷新间隔#defineNRF_LINK_TIMEOUT_MS500// 遥控断链超时#defineOPENLOOP_PWM135// 平移力度工厂比喻宏定义就是给固定数字贴标签相当于仓库货架的标识牌。编译前代码里所有OPENLOOP_PWM1会自动替换成35。两大好处代码可读性高不用到处看光秃秃的数字参数统一管理只改一行宏定义全工程同步更新调试小车参数超方便。4. 全局变量 vs 局部变量两种不同存放位置的盒子打开main.c能发现两种变量写在所有函数外面陀螺仪数据AX,AY,AZ、轮子速度Speed1~4这类全局变量写在函数内部中断里的float gz这类局部变量。用仓库逻辑区分全局变量函数外局部变量函数内使用范围整个项目所有文件都能读取修改仅当前函数内部可用存活时间小车一上电就存在断电消失进入函数创建执行完直接销毁通俗比喻工厂公共置物架全员可用工人随身便签盒下班直接丢弃int16_tSpeed1;// 全局中断、主循环都能读写voidTIM7_IRQHandler(void){floatgz...;// 局部仅本次中断有效中断结束销毁}伏笔提示代码里全局变量大多带volatile还有static、extern关键字这些和中断多任务、多文件代码相关新手容易踩坑我放到第3、4篇单独细讲本篇只需要分清全局、局部变量即可。5. 本篇小结记住嵌入式选数据类型核心逻辑盒子尺寸固定、分清正负避免莫名其妙的bug。知识点通俗理解普通int少用盒子尺寸不固定跨芯片、硬件通信容易出错int16_t / uint8_t / uint32_t标准固定尺寸储物盒全平台统一来自标准库stdint.h名字带u无符号只能存放0和正数(float)、数字后缀f整数小数混合运算要统一格式节省内存#define宏定义给固定参数命名方便阅读与调试全局/局部变量工厂公共置物架 vs 工人随身便签盒6. 第1篇趣味小思考答案思考1删掉上电2秒延时Delay_ms_Timer7(2000);会出现哪些奇怪现象这段延时放在全部硬件初始化完成、主循环启动之前是给所有外设预留稳定时间删掉后大概率出现这些偶发故障无线遥控NRF24L01上电短时间连不上等几秒才能正常接收指令OLED屏幕上电瞬间花屏、前几帧无显示陀螺仪刚上电数据漂移角度计算出现异常上电瞬间电机轻微抖动安全问题小车还没稳定就直接进入运行模式容易误动作。这类故障时好时坏属于最难排查的偶发bug所以上电延时不是多余等待是硬件稳定缓冲时间。思考2项目大量使用uint8_t/uint32_t很少用普通int/char尺寸完全固定uint8_t永远1字节通信协议、寄存器读写要求精准字节int尺寸随芯片变化极易数据错位正负一眼区分带u代表无负数速度、计时、模式标志分类清晰减少符号相关隐藏bug节约内存小数据用uint8_t只占1字节比4字节int节省大量单片机运行内存。简单总结普通int是“大概够用”定宽类型是“精准可控”控制硬件的单片机代码优先选择精准的定宽类型。本篇趣味思考答案见第3篇末尾如果把记录轮子速度的int16_t Speed1改成uint16_t小车运行时会出现什么异常情况同样是存储0~200的数字分别用int和uint8_t二者会占用多少内存哪种更适合单片机7. 交流我目前也是嵌入式自学新手本篇内容是结合自己麦轮小车项目实操总结而来部分知识点理解可能存在偏差讲解也有不够严谨的地方。技术大佬如果发现文中存在错误、逻辑漏洞欢迎评论区指正我会及时修正文章零基础小白有看不懂的知识点、实操踩坑问题都可以留言提问我看到会逐条回复大家也可以分享自己做智能小车的经验、代码优化思路一起交流学习共同进步祝愿各位大佬项目开发一路顺畅调试零bug思路源源不断祝愿所有入门小白少踩弯路快速吃透STM32早日做出属于自己的智能小车下篇预告《第 3 篇把逻辑写清楚——控制流、函数与枚举》。我们会讲if / switch / for / while怎么选、函数怎么拆、以及用一个enum把小车模式写成状态机代码瞬间清爽。