STM32与威纶通触摸屏RS485通信实战:Modbus RTU协议与避坑指南
1. 项目概述当STM32遇上威纶通触控屏在工业控制、智能设备或者自动化产线上你经常会看到一个核心组合一个负责逻辑运算和实时控制的“大脑”通常是像STM32这样的微控制器和一个负责信息交互与状态显示的“窗口”比如威纶通的工业触控屏。让这两个家伙顺畅地“对话”是项目从原型走向实用的关键一步。这个“对话”过程就是我们常说的通讯。今天要聊的就是如何实现STM32与威纶通触控屏之间稳定、可靠的通讯这几乎是所有嵌入式工控开发者都会遇到的经典课题。乍一看这似乎只是简单的数据收发但实际操作中从协议选择、硬件连接到软件调试每一步都藏着细节和“坑”。为什么是STM32因为它资源丰富、生态成熟是嵌入式开发的主流选择。为什么是威纶通触控屏因为它性价比高、软件易用在国内工控市场占有率很高。而将它们连接起来的桥梁最常见的就是RS485总线。这不仅仅是一个技术实现更是一个系统工程涉及到硬件电气匹配、通讯协议制定、数据帧解析、错误处理以及人机交互逻辑的联动。接下来我将以一个实际项目为背景拆解从设计思路到代码实现的完整过程分享那些手册上不会写的实操心得和避坑指南。2. 通讯方案选型与核心思路拆解在动手接线写代码之前我们必须先明确“怎么通信”。STM32和威纶通屏都支持多种通讯方式选择哪种决定了后续所有工作的方向。2.1 主流通讯方式对比与RS485的胜出理由STM32通常具备UART通用异步收发器、I2C、SPI等片上外设而威纶通触摸屏以MT8000系列为例通常支持串口RS232/RS485、以太网、CAN等接口。对于大多数中小型、中低速、有抗干扰需求的现场应用RS485串行通讯是性价比最高的选择。RS232简单点对点通讯距离短通常15米抗干扰能力弱不适合工业环境。以太网速度快距离远支持多设备但协议栈复杂成本相对高适合大数据量或需要远程监控的场景。CAN抗干扰能力极强多主结构但协议同样复杂常用于汽车或高可靠性的分布式控制。RS485差分信号传输抗共模干扰能力强支持多点总线拓扑一条总线上可以挂接多个设备屏和多个STM32通讯距离可达千米级硬件成本低廉。这正是它成为工控领域标配串行通讯方式的原因。因此我们的核心思路确定为利用STM32的UART外设搭配RS485收发器芯片通过Modbus RTU协议与威纶通触摸屏建立主从式通讯。威纶通屏通常作为主站Master主动轮询数据STM32作为从站Slave响应请求。Modbus协议开源、简单、通用是工控领域的事实标准威纶通软件原生支持极大降低了开发难度。2.2 系统框架与数据流设计在确定了RS485Modbus RTU的路径后我们需要规划整个系统的数据流。这不仅仅是通信更是业务逻辑的映射。物理层STM32的某个UART TX/RX引脚如USART1的PA9/PA10连接至RS485收发器芯片如SP3485、MAX485的DI/RO引脚收发器的A/B线接入RS485总线。威纶通屏的RS485端口也并联到该总线上并正确配置终端电阻通常在总线首尾端各接一个120Ω电阻。协议层威纶通屏的组态软件EasyBuilder Pro中需要设置正确的通讯参数波特率、数据位、停止位、校验位并定义与STM32对应的Modbus从站地址。STM32端则需要实现一个Modbus RTU从站协议栈用于解析屏发来的指令帧并组织响应帧。应用层这是核心。我们需要定义一套清晰的“变量映射表”。例如威纶通屏上的一个“启动按钮”地址0x0001按下对应向STM32的Modbus线圈地址0x0000写入值1。STM32的一个IO口状态如报警信号需要映射到Modbus离散输入地址0x1000供屏读取显示。STM32采集的模拟量如温度值需要映射到Modbus保持寄存器地址0x4000屏可以读取并显示在数值显示元件或曲线图上。屏上的一个参数设置框如目标速度对应向STM32的Modbus保持寄存器地址0x5000写入一个数值。注意提前规划好这份地址映射表至关重要它相当于双方通信的“字典”。务必在屏的软件和STM32的代码中使用完全一致的定义避免地址错乱导致通讯失败。建议用Excel或文档详细记录每个地址的用途、数据类型16位/32位整数、浮点数和读写属性。3. 硬件连接与关键电路解析可靠的通讯始于可靠的硬件连接。RS485电路看似简单但几个关键点处理不好会导致通讯不稳定、误码率高甚至损坏设备。3.1 RS485收发器电路设计与要点STM32的UART是TTL电平必须通过RS485收发器转换为差分信号。以常用的SP3485芯片为例典型电路如下但有几个细节必须关注VCC3.3V | R1 (4.7KΩ) | STM32 GPIO----|---- RE# (Pin2) |---- DE (Pin3) | | SP3485 | DI(Pin4)---- TX from STM32 UART RO(Pin1)---- RX to STM32 UART | |--- A (Pin6) ----- 连接到RS485总线A线 |--- B (Pin7) ----- 连接到RS485总线B线 | GND关键设计解析方向控制RE#和DERS485是半双工同一时刻只能发送或接收。这两个引脚通常连接在一起由一个STM32的GPIO控制。GPIO输出高电平时芯片处于发送模式输出低电平时处于接收模式。这是软件控制的核心。上下拉电阻R1图中R1是偏置电阻。在总线空闲或所有节点都处于接收状态时A、B线之间的电压差应大于200mV以确保接收器输出确定的逻辑状态。通常会在总线的A线接一个上拉电阻如4.7kΩ到VCCB线接一个下拉电阻如4.7kΩ到GND。这个电阻可以放在主设备屏端也可以放在从设备端但通常只需在总线两端配置。终端电阻当通讯距离较长例如超过100米或速率较高时信号在总线末端会发生反射造成波形畸变。需要在总线最远两端的设备的A、B线之间各并联一个120Ω的电阻以匹配电缆的特性阻抗双绞线通常为120Ω消除反射。电源与地线确保STM32和SP3485使用稳定、干净的3.3V电源。数字地DGND要连接良好避免地电位差引入噪声。实操心得很多通讯不稳定的问题都出在硬件上。务必使用双绞线作为RS485总线屏蔽层单点接地。如果设备现场干扰严重可以考虑在A、B线对地之间加入TVS管如SMBJ6.5CA进行浪涌保护以及串联小阻值电阻如22Ω限流。3.2 STM32与威纶通屏的接线确认威纶通触摸屏的RS485接口通常是接线端子形式标识为“DATA”或“A”、“485”和“DATA-”或“B-”、“485-”。正确接法STM485收发器的A线接屏的DATAB线接屏的DATA-。绝对禁忌接反了通常也能通讯但抗干扰性能会下降。最怕的是将RS485线误接到屏的24V电源或其它接口上那会直接烧毁端口。接线完成后最好用万用表测量一下总线A、B之间的电压差。空闲时由于上下拉电阻的存在电压差应在零点几伏到2伏之间A比B高。如果电压为0或接近电源电压则说明接线或偏置电阻有问题。4. STM32端软件实现详解硬件准备就绪后重头戏在软件。STM32端需要完成UART驱动、RS485方向控制、Modbus协议栈实现以及应用数据管理。4.1 UART与GPIO初始化配置我们以STM32F103系列使用USART1波特率96008数据位无校验1停止位为例。// 1. 初始化控制收发方向的GPIO假设使用PA8 void RS485_DIR_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); RS485_SetRxMode(); // 默认设置为接收模式 } // 设置发送模式 void RS485_SetTxMode(void) { GPIO_SetBits(GPIOA, GPIO_Pin_8); // DE/RE 1 } // 设置接收模式 void RS485_SetRxMode(void) { GPIO_ResetBits(GPIOA, GPIO_Pin_8); // DE/RE 0 } // 2. 初始化UART void USART1_Init(uint32_t baudrate) { USART_InitTypeDef USART_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置TX(PA9)为复用推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 配置RX(PA10)为浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); // 配置USART参数 USART_InitStructure.USART_BaudRate baudrate; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, USART_InitStructure); // 使能接收中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 配置USART1中断优先级 NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); // 使能USART USART_Cmd(USART1, ENABLE); }4.2 Modbus RTU从站协议栈的实现要点Modbus RTU协议帧格式为[从站地址][功能码][数据][CRC校验]。STM32需要实现一个状态机来解析接收到的帧。核心数据结构typedef struct { uint8_t addr; // 本设备Modbus地址 uint8_t rxBuf[256]; // 接收缓冲区 uint8_t txBuf[256]; // 发送缓冲区 uint16_t rxIndex; // 接收索引 uint16_t rxLength; // 接收到的数据长度 uint32_t lastRxTime; // 上次接收到字节的时间戳用于帧间隔判断 // 映射到Modbus地址的实际数据区 uint8_t coils[256]; // 线圈可读可写位 uint8_t discreteInputs[256]; // 离散输入只读位 uint16_t holdingRegs[256]; // 保持寄存器可读可写字 uint16_t inputRegs[256]; // 输入寄存器只读字 } ModbusRTU_t; ModbusRTU_t g_modbus;帧接收与超时判断在USART中断服务函数中RTU模式依靠帧间静默时间通常为3.5个字符时间来判断一帧的结束。例如在9600波特率下一个字符时间约1.04ms3.5个字符约3.64ms。void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { uint8_t data USART_ReceiveData(USART1); uint32_t currentTime SysTick_GetTick(); // 获取系统滴答时钟 // 如果距离上次接收时间超过3.5个字符时间认为是新帧开始 if(currentTime - g_modbus.lastRxTime 4) { // 留点余量用4ms g_modbus.rxIndex 0; } g_modbus.lastRxTime currentTime; if(g_modbus.rxIndex sizeof(g_modbus.rxBuf)) { g_modbus.rxBuf[g_modbus.rxIndex] data; } } }主循环中处理完整帧void Modbus_Process(void) { // 判断是否收到一帧完整数据通过超时 if((SysTick_GetTick() - g_modbus.lastRxTime 4) (g_modbus.rxIndex 0)) { g_modbus.rxLength g_modbus.rxIndex; g_modbus.rxIndex 0; // 1. CRC校验 uint16_t crc Modbus_CRC16(g_modbus.rxBuf, g_modbus.rxLength - 2); uint16_t rxCRC (g_modbus.rxBuf[g_modbus.rxLength-1] 8) | g_modbus.rxBuf[g_modbus.rxLength-2]; if(crc ! rxCRC) { // CRC错误可丢弃或回复异常码 return; } // 2. 检查地址是否匹配广播地址0通常也处理 if(g_modbus.rxBuf[0] ! g_modbus.addr g_modbus.rxBuf[0] ! 0) { return; // 不是发给本机的忽略 } // 3. 解析功能码并执行相应操作 uint8_t funcCode g_modbus.rxBuf[1]; switch(funcCode) { case 0x01: // 读线圈 Modbus_ReadCoils(); break; case 0x03: // 读保持寄存器 Modbus_ReadHoldingRegisters(); break; case 0x05: // 写单个线圈 Modbus_WriteSingleCoil(); break; case 0x06: // 写单个寄存器 Modbus_WriteSingleRegister(); break; case 0x10: // 写多个寄存器 Modbus_WriteMultipleRegisters(); break; // ... 其他功能码 default: // 不支持的功能码构造异常响应 Modbus_ExceptionResponse(funcCode, 0x01); // 非法功能 break; } } }以功能码0x03读保持寄存器为例的响应函数void Modbus_ReadHoldingRegisters(void) { // 请求帧[Addr][0x03][StartRegHi][StartRegLo][RegNumHi][RegNumLo][CRCLo][CRCHi] uint16_t startAddr (g_modbus.rxBuf[2] 8) | g_modbus.rxBuf[3]; uint16_t regNum (g_modbus.rxBuf[4] 8) | g_modbus.rxBuf[5]; // 检查地址和数量是否合法 if((startAddr regNum) MAX_HOLDING_REGS) { Modbus_ExceptionResponse(0x03, 0x02); // 非法数据地址 return; } // 构造响应帧 uint8_t byteCount regNum * 2; // 每个寄存器2字节 g_modbus.txBuf[0] g_modbus.addr; g_modbus.txBuf[1] 0x03; g_modbus.txBuf[2] byteCount; for(int i0; iregNum; i) { uint16_t regValue g_modbus.holdingRegs[startAddr i]; g_modbus.txBuf[3 i*2] regValue 8; // 高字节在前 g_modbus.txBuf[4 i*2] regValue 0xFF; // 低字节在后 } uint16_t crc Modbus_CRC16(g_modbus.txBuf, 3 byteCount); g_modbus.txBuf[3 byteCount] crc 0xFF; g_modbus.txBuf[4 byteCount] crc 8; // 发送数据 RS485_SetTxMode(); for(int i0; i (5 byteCount); i) { USART_SendData(USART1, g_modbus.txBuf[i]); while(USART_GetFlagStatus(USART1, USART_FLAG_TC) RESET); // 等待发送完成 } RS485_SetRxMode(); // 切回接收模式 }注意事项在发送数据前切换到发送模式发送完成后必须立即切换回接收模式。这个切换时机非常关键如果切换慢了可能会错过屏紧接着发来的下一帧。通常建议在发送完最后一个字节后延迟几个微秒再切换。另外CRC校验码是低字节在前高字节在后这是Modbus RTU的规定不要搞错顺序。5. 威纶通触摸屏组态配置实战STM32准备就绪后需要在威纶通的EasyBuilder Pro软件中对触摸屏进行配置使其能够主动与STM32通信。5.1 通讯参数设置与设备连接新建工程与设备选择打开EasyBuilder Pro根据你的屏型号新建工程。在“系统参数设置”中找到“设备列表”。添加设备在设备列表右键“添加设备”。在驱动列表中找到“Modbus RTU”驱动可能位于“PLC”或“其他”分类下。配置接口参数接口类型选择你屏上实际使用的物理端口如“COM2 (RS485 2W)”。波特率、数据位、停止位、校验必须与STM32端的UART设置完全一致例如“9600, 8, 1, None”。站号填写STM32的Modbus从站地址例如“1”。通讯测试连接好硬件编译下载工程到触摸屏。可以在软件中使用“在线模拟”功能或者直接观察屏的通讯指示灯。如果配置正确屏会周期性地向STM32发送查询帧。5.2 元件地址映射与数据关联这是将屏上按钮、指示灯、数值显示与实际STM32数据关联起来的关键步骤。理解地址格式威纶通软件使用特定的地址格式来对应Modbus的四种数据类型0x线圈对应Modbus线圈可读可写。在屏上地址表示为0x0001对应Modbus地址0x0000。1x离散输入对应Modbus离散输入只读。屏上地址表示为1x0001。3x输入寄存器对应Modbus输入寄存器只读。屏上地址表示为3x0001。4x保持寄存器对应Modbus保持寄存器可读可写。屏上地址表示为4x0001。注意屏上地址通常是“1起始”而Modbus协议地址是“0起始”。例如屏上地址4x0001在STM32代码中应映射到holdingRegs[0]。元件关联示例按钮放置一个“位状态设置开关”。在其属性中将“读取地址”和“写入地址”都设置为0x0001对应STM32的coils[0]。当按钮按下时屏会向STM32发送功能码0x05写单个线圈或0x0F写多个线圈的指令。指示灯放置一个“位状态指示灯”。将“读取地址”设置为1x0001对应STM32的discreteInputs[0]。STM32只需更新这个内存位屏就会自动读取并显示。数值显示与输入放置一个“数值显示”元件。将“读取地址”设置为4x4000对应STM32的holdingRegs[0x4000]。如果要支持输入再勾选“输入”功能并设置“写入地址”为同一个4x4000。对于32位浮点数需要在元件属性中选择“32-bit Float”格式并注意Modbus的字节顺序威纶通通常为“CDAB”或“ABCD”需与STM32端解析顺序匹配。避坑技巧对于浮点数、长整型等多寄存器数据字节顺序Endianness和字顺序Word Order是最大的坑。威纶通和STM32端必须使用相同的顺序。常见的顺序有“ABCD”高字在前高字节在前、“CDAB”低字在前高字节在前等。最稳妥的方法是先在屏上定义一个浮点数变量关联到两个连续的4x寄存器然后在STM32端发送一个已知的浮点数值如123.456观察屏上接收到的两个寄存器的值从而反推出屏的解析顺序再调整STM32的打包顺序。6. 联合调试与故障排查实录硬件连好代码写好屏工程也下载了但通讯指示灯就是不亮或者数据不对。别急这是最考验耐心和经验的阶段。6.1 调试工具与步骤串口调试助手这是最直接的武器。将STM32的UART TX线RS485收发器的RO引脚之前暂时接到一个USB转TTL模块上连接到电脑。用串口调试助手监听STM32接收到的原始数据。你应该能看到威纶通屏发来的规整的Modbus指令帧一串16进制数。这能立刻判断屏是否在发送、发送的数据是否正确。逻辑分析仪或示波器如果条件允许用它们观察RS485总线上的A、B线差分波形。可以清晰地看到起始位、数据位、停止位以及帧间的静默时间。这是诊断物理层问题的终极手段。威纶通屏的“系统信息”页面在屏上做一个画面放几个“系统信息”元件可以实时查看通讯的“发送计数”、“接收计数”、“错误计数”。如果发送计数在涨但接收计数不变说明STM32没响应或响应错误。6.2 常见问题与解决方案速查表下表总结了调试过程中最常见的问题及其排查思路问题现象可能原因排查步骤与解决方案完全无通讯1. 电源未接通或电压不对。2. RS485线A/B接反或断开。3. 波特率等参数不一致。4. 终端电阻未接或接错位置。5. STM32程序未运行或UART初始化失败。1. 用万用表测量电源和地。2. 检查A/B线接线确保A接AB接B。3. 双重确认屏和STM32的波特率、数据位、停止位、校验位。4. 长距离通讯时在总线两端接120Ω终端电阻。5. 用LED或调试器确认STM32程序已运行检查UART初始化代码。通讯时好时坏数据错乱1. 总线干扰严重。2. 未使用双绞线。3. 地线噪声或共地问题。4. 软件上RS485收发方向切换时机不当。5. STM32中断优先级过低导致接收字节丢失。1. 使用带屏蔽层的双绞线屏蔽层单点接地。2. 远离强电线路。3. 确保所有设备共地良好。4. 优化方向控制时序发送完成后延迟几十微秒再切回接收。5. 提高UART接收中断的优先级确保及时响应。屏显示“通讯超时”或“无设备响应”1. STM32的Modbus从站地址与屏设置不一致。2. STM32的CRC校验计算错误。3. STM32响应帧格式错误。4. 屏的轮询间隔太快STM32处理不过来。1. 核对屏上设备站号和STM32代码中的g_modbus.addr。2. 用在线CRC计算工具对比STM32计算的CRC值。3. 用串口调试助手抓取STM32的响应帧与Modbus标准对比。4. 适当增加屏的“通讯延迟时间”或“轮询周期”。数据能读但不能写1. STM32未实现对应的写功能码如0x06, 0x10。2. 屏上元件的“写入地址”设置错误或未启用写入功能。3. STM32端写操作处理函数中数据更新后未同步到应用层。1. 检查STM32代码是否实现了Modbus_WriteSingleRegister等函数。2. 检查屏上输入元件的属性确认“写入地址”已正确设置且使能。3. 在写寄存器函数中更新holdingRegs[]数组后应触发相应的应用处理如更新PWM占空比。浮点数显示错误字节顺序或字顺序不匹配。使用“已知值测试法”STM32发送一个易于辨认的浮点数如1.0的十六进制表示为0x3F800000在屏上观察接收到的两个寄存器的值确定屏的解析顺序然后调整STM32数据打包代码。6.3 软件层面的稳定性加固除了解决故障我们还可以在软件上增加鲁棒性超时与重发STM32在发送响应后可以启动一个定时器。如果一段时间内没有收到新的请求可以复位通讯状态防止因某帧错误导致整个通讯卡死。数据边界保护在Modbus处理函数中严格检查请求帧中的起始地址和寄存器数量防止数组越界访问。看门狗确保STM32开启了独立看门狗IWDG在程序跑飞时能自动复位。异常响应对于所有非法请求非法功能码、非法数据地址、非法数据值都必须按照Modbus协议规范回复相应的异常码这有助于上位机诊断问题。7. 项目进阶与扩展思考当基础通讯稳定后可以考虑一些进阶功能让系统更强大、更易用。7.1 多寄存器与复杂数据类型的处理实际项目中我们经常需要传输超过单个寄存器的数据比如32位整数、浮点数、ASCII字符串甚至是结构体。浮点数传输如前所述关键在于统一字节序。可以定义一个统一的转换函数。typedef union { float f; uint8_t c[4]; uint16_t w[2]; } FloatUnion_t; void FloatToRegs(float f, uint16_t *regHi, uint16_t *regLo) { FloatUnion_t fu; fu.f f; // 假设屏端顺序为“CDAB”低字在前高字节在前 *regLo (fu.c[2] 8) | fu.c[3]; // 低字 *regHi (fu.c[0] 8) | fu.c[1]; // 高字 }字符串传输可以将字符串按字节拆分存入连续的保持寄存器中。屏端使用“ASCII显示”元件并设置正确的起始地址和字符长度。7.2 模拟多从站与数据同步一条RS485总线上可以挂接多个STM32从站。只需为每个STM32设置不同的Modbus地址即可。威纶通屏可以添加多个“Modbus RTU”设备每个设备对应不同的站号。在屏的宏指令或脚本中可以编写逻辑来协调多个从站的数据实现复杂的联动控制。7.3 使用HMI脚本增强交互逻辑威纶通的EasyBuilder Pro支持类C语言的宏指令。我们可以利用脚本实现更灵活的逻辑例如登录权限管理根据输入的用户名和密码使能或禁用某些操作按钮。数据记录与导出定期将4x寄存器的值记录到屏的内部存储或U盘。复杂动画与条件显示根据STM32传来的状态字动态切换画面或控制元件的显示状态。例如一个简单的按钮互锁脚本在按钮的“释放”事件中// 假设“启动”按钮地址为 LB0“停止”按钮地址为 LB1 if (GetData(LB0) 1) { // 如果启动按钮被按下 SetData(LB1, 0); // 强制将停止按钮复位 }实现STM32与威纶通触控屏的通讯就像为两个来自不同国度的专家搭建了一座沟通的桥梁。硬件连接是桥墩必须稳固Modbus协议是共同的语言必须准确而软件实现则是翻译官必须高效可靠。这个过程会不断遇到问题但每一次排查和解决都会让你对底层通讯、硬件交互和系统设计的理解更深一层。我最深的体会是一份清晰的《变量地址映射表》和一套可靠的字节序处理方案能节省掉后期一大半的调试时间。当屏上的按钮第一次能点亮STM32的LED或者STM32采集的温度值第一次稳定地显示在屏上时那种成就感就是嵌入式开发最朴实的乐趣所在。