尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CW32L012串口上位机攻略:免拆板更新外部SPI Flash

CW32L012串口上位机攻略:免拆板更新外部SPI Flash 先说这个上位机是干什么的。CW32L012这颗芯片主打超低功耗但内部Flash容量摆在那里产品里要放字库、提示音、配置文件这类大数据时根本不够用所以很多方案都会外挂一颗SPI接口的串行Flash比如W25Q系列。以前调试这种板子最头疼的就是更新外部Flash内容代码改一行配置要么用烧录器夹子去夹SPI Flash要么干脆把芯片拆下来用座子烧效率极低。后来我基于串口写了这套配套上位机把bin文件通过上位机软件经串口发给MCU再让MCU通过SPI写入外部Flash整个过程免拆板、免夹子一条USB转串口线就能搞定。这篇教程会把这套方案的原理、通信协议、实操步骤和避坑经验一次性讲透适合正在用CW32L012做产品、需要外部Flash存数据的工程师参考。1. 先搞清楚这个上位机到底在解决什么问题1.1 数据为什么要放外部串行FlashCW32L012属于Cortex-M0内核的低功耗MCU芯片定位是电池供电、传感器采集、简单控制这类场景。这类芯片的内部Flash容量通常不会做得很大毕竟成本和功耗都要控制。但实际产品里一块段码液晶的字库、几段语音提示、一组设备校准参数、一份运行配置表随便算算就要几十KB。把这些数据全部塞进内部Flash不是说完全不可能但会带来一个实际问题程序固件和数据强耦合。每次修改一段提示音或一个界面文本都要重新编译整个固件再整体下载一次程序风险和工作量都增加了。所以工程上最常见的做法是程序放内部Flash数据放外部SPI Flash。外部Flash容量从1MB到16MB随便选功耗也低价格还便宜。不过这个方案有个新的麻烦就是数据更新。程序烧录可以用调试器外部Flash里的数据却没有独立的调试接口得想办法把数据灌进去。1.2 调试和产线中绕不过去的三个痛点我最初用CW32L012做一款低功耗记录仪时外部Flash里放的是采集配置和升级叠加的字库三天两头要改。踩过的坑基本集中在三个方面。第一素材改版太频繁。字库和配置信息这类数据往往是后期才定稿的硬件已经做好了总不能每改一版就回产线重新贴片。用烧录器夹子夹SPI Flash也难受夹子容易接触不良而且很多烧录器对在线烧录的支持并不好。第二烧录器离线烧录效率低。量产阶段如果要先烧好Flash再贴片就要单独做烧录工序多一台设备多一个工位还容易出现漏烧、烧错版本的问题。第三现场想更新数据却没有入口。设备已经部署出去了如果要更新字库或配置总不能派人带着烧录器挨个拆机。后来我把这个问题拆解成一套方案MCU里跑一个下载引导程序PC端用上位机软件通过UART把数据下发到MCUMCU再通过SPI把数据写入外部Flash。这样整个更新链路只需要一条串口线既适合研发调试也适合产线半自动工位甚至现场维护也能用。1.3 上位机加MCU加Flash的整体架构这套系统的架构其实非常清晰四个部分组成。PC端的上位机软件负责读取bin文件、按协议分帧、通过串口发送数据、接收应答并显示进度。MCU侧的程序负责初始化SPI外设、接收串口数据帧、解析命令、操作外部Flash的擦除和写入。串口链路负责PC与MCU之间的数据搬运通常用CH340或CP2102这类USB转串口模块。外部SPI Flash是数据的最终存储介质常见型号是W25Q32、W25Q64、W25Q128等。通信流程是这样的上位机打开串口后先发送握手命令MCU收到后返回设备信息和Flash ID双方确认连接。接着上位机发送擦除命令把目标区域擦干净。然后按帧发送要写入的数据每发一帧等MCU应答收到确认再发下一帧。全部数据发送完后上位机发送校验命令MCU把Flash里的数据读回并进行CRC校验把结果返回给上位机。这个架构里最核心的一点是上位机从来不直接操作SPI Flash它只通过串口和MCU对话。MCU承担了所有和Flash硬件相关的动作这样上位机的逻辑就可以做到完全独立换一颗MCU或者换一种Flash型号上位机只需要改协议参数不需要动整体框架。2. 配套上位机与CW32L012之间是怎样咬合的2.1 串口到SPI的透传链路原理有人第一次接触这套工具时会问上位机为什么不能直接控制SPI Flash答案很简单PC机上没有物理的SPI接口更不可能直接连接到板子上的Flash芯片引脚。数据的实际通路是PC到MCU再到FlashMCU就是那个中间翻译官。这里要特别注意串口和SPI的速度差异。串口115200波特率下有效数据速率大约只有10KB/s左右而SPI跑个几MHz甚至几十MHz轻轻松松。所以整条链路的速度瓶颈在串口MCU侧的SPI操作必须做到快速响应不能每次都等Flash写完再处理下一帧。实际处理时我一般会把Flash页写入通常一页256字节当作一次原子操作。上位机每帧数据控制在256字节以内MCU收到一帧后立即写入Flash的一页然后返回应答。这样做的好处是即使某一帧数据出错重传的粒度也小不会出现写了一半卡死的尴尬情况。另外串口接收端要设计好缓冲区。上位机发送数据是连续的MCU串口中断如果处理不及时很容易丢字节。我常用的做法是串口空闲中断加DMA把一整帧数据先搬到内存再通过状态机解析处理这样能最大程度避免丢帧。2.2 通信协议设计帧头、命令、地址、数据、校验上位机和MCU之间必须定义一套明确的通信协议这是整套方案里最重要、也最容易被忽略的部分。我第一次做这个项目时就是吃了协议的亏帧格式没定好读写状态搅在一起调试了两天才理清楚。后面重新设计了下面的帧格式稳定性和可扩展性都好了很多。帧格式采用固定帧头加变长数据的方式每帧的结构如下帧头(2字节) 命令字(1字节) 数据长度(2字节) 地址(4字节) 数据(N字节) CRC16校验(2字节)具体规定是帧头固定为0xA5 0x5A命令字表示功能类型数据长度表示本次数据域的有效字节数地址字段表示Flash目标地址或返回地址数据域按需填充校验采用CRC16-MODBUS覆盖从命令字到数据域的所有字节。命令字我建议这样规划命令码方向功能说明0x01上位机到MCU握手请求获取设备信息和Flash ID0x81MCU到上位机握手响应返回芯片型号、Flash容量、固件版本0x02上位机到MCU擦除指定扇区0x82MCU到上位机擦除完成应答返回状态0x03上位机到MCU写入一页数据0x83MCU到上位机写入完成应答返回状态和校验值0x04上位机到MCU读回指定区域数据0x84MCU到上位机返回读出的数据0x05上位机到MCU跳转到App运行0x85MCU到上位机跳转状态返回MCU侧的状态机逻辑也很关键。下载程序上电后先处于空闲状态串口收到0xA5 0x5A帧头后进入帧接收状态按照长度字段收完整个帧再计算CRC16校验。校验通过就根据命令字分发处理校验失败就丢弃并返回错误码。只有握手成功后才允许执行擦除、写入等操作这样能防止误操作。这个协议还有一个隐藏的好处扩展性好。比如后面要增加读取设备唯一ID、绑定产品序列号、升级下载程序本身这些功能只需要在命令字表里加新条目不影响已有逻辑。2.3 CW32L012侧引导程序的几个要点MCU侧的程序是整套方案的另外一个关键点。它要解决的问题是芯片上电后怎么判断自己是该进入下载模式还是正常运行App。最常用的做法是上电时检测一个GPIO引脚的电平。我习惯把BOOT引脚设置为下载模式选择脚高电平进入下载模式低电平正常运行App。接线时把这个引脚连接到串口模块的一个IO口上位机在发起下载前先把该引脚拉高再给MCU复位MCU检测到高电平就留在下载程序里。引脚检测的时序要注意一个坑MCU上电到GPIO稳定之间有一段毫秒级的时间如果上位机拉高引脚太晚MCU可能已经跑进App了。解决办法是硬件上用一个RC延时或者三极管控制复位引脚软件上上位机在发送握手命令前先延时200到300毫秒连续发送握手请求直到收到响应。Flash操作的细节也不能漏。外部SPI Flash的写入前必须先擦除而且擦除的最小单位是扇区常见的是4KB或64KB。写入时按页写入每页256字节。擦除和写入过程中Flash不能响应其他命令MCU必须等待操作完成标志。地址映射同样要提前规划好。外部Flash的逻辑地址从0x00000000开始但实际文件里可能有文件头、版本号、数据区、校验区不同的划分。我习惯在bin文件的前16字节预留一个固定格式的文件头存放魔数、版本号、数据长度、CRC32校验值。这样上位机解析bin文件时就能拿到这些信息下载时还能做二次校验。3. 上位机实操从打开软件到一次完整下载3.1 运行环境与连接准备先说软件运行环境。这套上位机是基于C#开发的目标框架建议选用.NET Framework 4.7.2或.NET 6以上版本。Win10和Win11系统可以直接安装运行Win7系统则需要确认.NET Framework是否装全。如果拿到的是源码需要先用Visual Studio打开编译。这里要提醒一句如果用VS2019创建的工程文件项目格式可能是新的SDK-style格式直接用VS2015打开会报不兼容这个后面我会单独说解决办法。硬件连接方面准备一个USB转TTL串口模块就行CH340和CP2102都很常见。接线按照下面的表来串口模块引脚开发板引脚说明3.3V或5VVCC供电具体电压看开发板要求GNDGND共地必须连接TXDRXD模块发送接MCU接收RXDTXD模块接收接MCU发送任意IOBOOT引脚用于控制进入下载模式连接好之后先在设备管理器里确认串口号。如果插上模块没有识别到新的COM口多半是驱动没装好CH340需要装CH341SER.EXE驱动CP2102需要装CP210x驱动这一步不过关后面一切免谈。3.2 软件界面与关键参数配置软件界面我按功能分成五个区域连接区、文件区、参数区、日志区和操作区。连接区负责选择串口号和波特率文件区负责加载bin文件参数区设置起始地址和校验方式日志区实时显示通信过程操作区放下载、校验、擦除、跳转几个功能按钮。参数配置里最需要理解的是三个地方。第一个是波特率。115200是默认值最稳妥。有些场景想追求速度可以提到460800甚至921600但这要求串口模块支持高波特率而且通信线不能太长。我做实测时发现用1米左右的杜邦线连接460800波特率下偶发丢字节115200就非常稳定所以日常调试建议安分用115200。第二个是起始地址。这个地址对应外部Flash的逻辑地址不是文件偏移。默认从0x00000000开始如果bin文件里已经包含了文件头就直接全文件下载。如果bin文件是纯数据、不带地址信息就需要手动指定写入的起始地址。第三个是校验方式。我提供了CRC32读回校验和文件MD5比对两种方式。前者下载完成后把Flash数据读回来算CRC和上位机本地算的值做对比后者是把Flash数据读回后计算MD5再和源文件的MD5比较。两种方式效果差不多CRC32快一些MD5更严格。3.3 一次完整下载的操作步骤连接好硬件、打开软件后按照下面的流程操作就能完成一次完整下载。在连接区选择正确的串口号波特率选115200点击“打开串口”。如果串口打开成功日志区会显示“串口已打开等待连接”。点击“加载文件”按钮选择要下载的bin文件。软件会自动解析文件大小并在界面上显示。在参数区设置起始地址通常使用默认的0x00000000。如果设备在运行App状态先点击“进入下载模式”按钮软件会拉高BOOT引脚并复位设备。如果BOOT引脚已经拉高设备停留在下载模式中可以直接进行下一步。点击“握手连接”按钮。如果MCU正确回应日志区会显示芯片型号、Flash厂商ID和容量信息。点击“下载”按钮。软件会先发送擦除命令把目标区域擦除完毕然后开始逐帧发送数据。日志区会显示进度条和当前写地址。下载完成后软件自动执行读回校验。校验通过日志区显示绿色提示“校验成功下载完成”。点击“跳转运行”按钮MCU退出下载模式跳转到App执行。整个流程跑下来一个1MB的bin文件在115200波特率下大约需要100秒左右。如果嫌慢可以先用小文件验证功能确认协议稳定再刷大文件。下面给出一段C#串口发送关键代码方便有二次开发需求的读者参考private void SendFrame(byte cmd, uint address, byte[] data) { Listbyte frame new Listbyte(); frame.Add(0xA5); frame.Add(0x5A); frame.Add(cmd); byte[] lenBytes BitConverter.GetBytes((ushort)(data null ? 0 : data.Length)); byte[] addrBytes BitConverter.GetBytes(address); frame.Add(lenBytes[0]); frame.Add(lenBytes[1]); frame.AddRange(addrBytes); if (data ! null) { frame.AddRange(data); } byte[] crc Crc16Modbus(frame.ToArray(), 2, frame.Count - 2); frame.Add(crc[0]); frame.Add(crc[1]); serialPort.Write(frame.ToArray(), 0, frame.Count); }串口接收侧用SerialPort.DataReceived事件把数据缓存到内存里解析时按帧头、长度、校验依次处理代码比较冗长就不全部贴了思路和MCU侧的帧解析完全一致。3.4 下载完成后怎么验证Flash内容我见过不少人下载完数据就直接断电结果产品上电后花屏或者乱码又排查半天才发现是Flash数据不完整。养成下载后验证的好习惯能省掉不少麻烦。验证方法有三个层次。第一层是读回校验。上位机把Flash目标区域的内容读回来和本地bin文件的对应区域做逐字节比较。这套上位机默认就是这样做的所以下载进度走完后看到“校验成功”基本就稳了。第二层是应用层验证。如果App里有文件头校验逻辑下载完跳转运行后App启动时会主动读取外部Flash的文件头计算CRC32并比对不一致就报错提示更新失败。这一层验证的是整条链路在真实运行环境下的健康度。第三层是全片读取比对。把整个Flash内容读出发成hex或bin文件和源文件做整体比对。这个方法最彻底但耗时长一般只在分析问题时使用。实际项目中我建议至少做到前两层。第一层缺了数据损坏无法及时发现第二层缺了即使数据错了也无法快速定位到Flash内容本身。4. 实操中一定会撞上的几个坑4.1 串口打不开驱动和端口占用排查串口打不开是最常见的问题而且新手遇到时常常一头雾水。排查链路其实很固定。先在设备管理器里看“端口(COM和LPT)”下有没有对应的串口设备。如果看不到问题一定在驱动层面重新安装USB转串口芯片的驱动。如果看到了但设备前面有个黄色感叹号说明驱动安装有问题把驱动卸载再重装一次。如果设备管理器里一切正常但软件点击打开串口时弹窗报错问题可能是串口被其他程序占用。我遇到过串口调试助手、另一个上位机实例占着COM口的情况关掉全部占用程序再试就解决了。还有一个隐蔽的问题有些USB转串口模块的驱动枚举出来的COM号会变今天COM3明天COM5。解决方法是右键设备管理器里的串口设备进入“端口设置-高级”在COM端口号下拉里固定一个不常用的编号这样以后每次插入都是同一个串口号。4.2 握手失败芯片没进下载模式握手失败很大概率不是协议问题而是MCU没进下载模式。具体表现为上位机一直发握手请求MCU没有任何响应。排查思路是这样的。先用串口调试助手直接发一帧0xA5 0x5A 0x01 0x00 0x00 0x00 0x00 0x00 0x00加上CRC的原始数据看MCU有没有回包。如果回包正常说明通信链路没问题问题出在上位机的握手逻辑或BOOT控制时序上。如果没有回包再用示波器或逻辑分析仪量MCU的RX引脚下发时有没有波形如果有波形但MCU不动问题在MCU程序没有跑起来检查BOOT引脚电平和复位电路。实际操作中我发现最容易出问题的是BOOT引脚的时序控制。上位机软件拉高BOOT引脚后要等待一段时间再给MCU复位。有些USB转串口模块的IO口是弱驱动直接影响BOOT引脚电平这种情况下就要加个上拉电阻或者换一个带强输出的模块。还有一种情况是MCU上电速度太快。如果BOOT引脚上电默认是低电平MCU会直接跑App上位机的握手命令发给App之后被当成普通串口数据处理自然没有应答。解决办法是在下载命令里面增加“先拉高BOOT再复位”的联动操作并把复位后的延时拉长到500毫秒左右确保MCU稳定停在下载模式中。4.3 Flash写入失败扇区擦除和地址越界写入失败的问题主要集中在两个原因上。第一个原因是没擦除就写入。串行Flash的特性是写入只能把1变成0要把0变成1必须先执行擦除操作。如果不擦除就写入数据会出现字节对不上的情况看起来像是写入失败。实际编码时要保证每一页数据所在扇区在写入前都被擦除过。最简单的策略是下载一开始就把目标区域整体擦除虽然耗时稍长但逻辑简单不容易出错。第二个原因是地址越界。比如选的是W25Q324MB容量但起始地址设成0x400000以上或者文件太大超出了Flash剩余空间。CW32L012的外设本身不会报错因为地址是通过协议字段传给MCU的MCU如果不对地址做范围检查就会把数据写到不存在的地址上。所以MCU侧一定要有地址边界判断超过容量上限就直接返回错误码上位机收到错误码后停止发送并给出提示。我实际做固件时把所有写地址都经过一层地址转换函数函数里统一做容量检查。后来项目因为成本换了更小的Flash容量只需要改这个函数里的上限宏其余代码不用动维护起来方便很多。4.4 大文件下载中途失败超时重传机制大文件下载的耗时决定了中途失败的概率。1MB文件在115200波特率下耗时100秒如果通信线接触不良或环境有干扰中间偶尔丢一帧很正常。关键是怎么处理丢失的帧。我的方案是上位机每发一帧数据就启动一个500毫秒的超时定时器如果在超时时间内没有收到MCU的应答帧上位机自动重发当前帧。重发超过3次仍然无应答就停止下载并提示用户检查连接。MCU侧则要处理重复帧的问题如果收到一帧地址和上一次相同的数据直接忽略或重新写入同一页但必须返回正常应答。这套机制之下只要通信链路不是完全断开下载过程都能自己恢复。如果中途断电或拔线上位机会因为连续重试失败而退出Flash里的内容处于不完整状态。所以我在Flash的固定地址区域存了一个下载状态标记正常完成下载后写入0x5A5A下载开始时清零。App启动时检查这个标记如果不是0x5A5A就认为数据不完整主动提示进入下载模式重新烧录。这样即使中途断电也不会误判Flash内容可用。4.5 VS2019开发的C#工程能否用VS2015打开搜索关键词里有人专门问这个问题我在项目里也撞过。这个问题的根源是工程文件格式的差异。VS2019默认创建的C#项目如果选择了.NET Core或.NET Standard采用的是SDK-style csproj格式文件内容比传统格式简洁非常多。但这种新格式是VS2017及以上版本才支持的VS2015根本识别不了打开工程时会直接报“不兼容”或“无法加载项目”。解决方案有三个。第一种在VS2019里把项目保存成传统格式具体做法是新建项目时选择.NET Framework版本VS2019就会生成老式csproj文件这种文件VS2015可以打开。第二种手动把SDK-style项目改造成传统格式在csproj文件里加入元素、引用和属性节点工作量大不推荐。第三种把源码文件Form.cs、Program.cs、Properties等手动添加到VS2015新建的传统工程里重新引用NuGet包本质上就是重建项目不过程序员最常用的办法。如果是工程里用了高版本的C#语法特性比如可空引用类型、switch表达式VS2015即使打开了工程也无法编译还需要把语法降级到C# 6.0或更早版本。所以最省事的建议是如果项目要兼容老版本IDE一开始就锁定.NET Framework 4.x加传统csproj格式语言版本选C# 7.3以下。5. 从“能下载”到“好用的产线工具”后续还能怎么改5.1 命令行参数化与产线半自动化研发调试阶段界面操作完全够用。但到了产线阶段工位操作员每天要烧几百片板子如果还要手动点“打开串口”、“加载文件”、“下载”效率和准确性都跟不上。我给这套上位机增加过命令行模式支持通过参数传入串口号、波特率、bin文件路径、起始地址比如这样FlashDownLoader.exe -port COM3 -baud 115200 -file app.bin -addr 0x00000000 -auto带-auto参数时软件启动后自动打开串口、自动握手、自动下载、自动校验最后把结果写到日志文件。产线工装固定好串口连接后操作员只需要放好板子、执行启动脚本几秒钟后看日志文件里的PASS或FAIL标记就行。这里还可以用脚踏开关触发脚本执行把手完全解放出来。5.2 日志记录与SN绑定追溯当产品出了问题需要分析“是不是Flash内容刷错了”的时候完整日志就是救命稻草。我在产线版本的上位机里增加了操作日志数据库每完成一次下载就记录一条记录内容包括时间戳、操作员编号、bin文件MD5、起始地址、下载结果、MCU返回的芯片唯一ID。CW32L012的芯片信息区有唯一的UID通过协议扩展命令能读回来。把这个UID和下载记录绑定在一起后期出问题时可以按SN定位到具体的下载批次、下载了哪个版本的文件。日志数据库我用的是SQLite单文件存储产线工位上部署简单甚至不需要装数据库服务。数据量再大也不怕一万条记录只有几MB大小。5.3 双备份升级与失败回滚如果这套下载方案不只是用于出厂烧录还想做设备的远程升级或现场维护升级那就要考虑升级失败后的回滚能力。常见的做法是外部Flash划分出两个数据区A区和B区每个区都有完整的数据和版本号。下载新数据时先写备用区全部写完并校验通过后再把版本号切换到新数据区。如果写入过程中断电或校验失败版本号依然指向旧数据区设备上电后继续用旧版本不会变砖。在CW32L012这个方案里实现起来不复杂。SPI Flash容量足够大的话多划一块区域成本几乎可以忽略。关键是Flash分区表必须一开始就规划好不然后期想加回滚功能重新分区会导致旧设备的升级逻辑混乱。5.4 数据加密下发有些产品对外部Flash存储的数据有保密要求比如配置参数、算法系数、设备密钥。如果直接用串口明文下发用逻辑分析仪抓一下串口数据就能把内容抄走。解决办法是在上位机侧对bin文件做加密MCU收到后再解密写入。加密算法不需要太复杂AES-128就能满足绝大多数场景。密钥可以预置在MCU内部Flash的专用区域或者用芯片唯一ID派生这样每台设备的密钥都不一样即使一台设备被破解也不影响其他设备。MCU侧解密操作会占用一些事件和程序空间CW32L012的算力跑AES-128没问题但要注意解密后数据的存储时机。我建议MCU先把整帧加密数据收齐放入RAM缓冲区解密后再写入Flash避免半页加密数据因断电残留而产生不可用状态。最后再分享一点个人体会。这套上位机MUC引导程序的方案我前后迭代了三个版本最大的感受是上位机界面永远是最好做的部分真正决定项目成败的是通信协议的稳定性和MCU侧状态机的严谨程度。开发初期的协议文档一定不要偷懒帧格式和命令字定义得越清楚后面兼容性问题和调试成本越低。最后一个调试小技巧不管上位机还是MCU侧所有关键节点都要输出日志。上位机把每一帧收发记录写到文件里MCU把状态机跳转和错误码通过一个调试串口外发出来。遇到疑难问题能靠日志还原整个通信过程比拿示波器一点一点追波形快得多。这套日志思路用到今天几乎每个项目都能靠它快速定位到问题根因。
返回列表