十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

瑞萨RA6M5+Qt遥控小车:从FSP配置到上位机联调实战

瑞萨RA6M5+Qt遥控小车:从FSP配置到上位机联调实战 简介一套基于瑞萨RA6M5野火启明6M5与FSP、Keil、Qt Android联调的遥控小车工程源码面向嵌入式、物联网方向的开发者与高校学生可作为软硬件协同、WiFi无线控制项目的完整参考。项目以电容按键触发启动通过板载ESP8266 WiFi模块连接安卓手机上的Qt服务端App手机端能够实时显示直行、左转、右转、后退以及左右轮相对速度等状态并远程调节转向和轮速正常匀速行驶时板载LED显示绿色速度异常或左右轮差过大时显示蓝色警示超速还会强制停车形成从交互界面到底层驱动的闭环控制逻辑。资源包共255个文件压缩后约12.23MB以h头文件、C源文件和o目标文件为主体另有Keil工程配置、scat链接脚本、hex与axf固件、Qt的pro工程和mp4演示视频等覆盖源码阅读、工程导入、固件烧录和运行效果展示等环节目录结构清晰便于按需查阅。目前已有416人学习下载。包内CTSU触摸、ADC采样、SCI串口、GPT定时器等外设驱动源文件可复用Qt端与小车端的通信协议完整适合复现遥控小车流程也适合移植到其他无线控制项目中。 做嵌入式的人看到「遥控小车」四个字第一反应大概率是这不就是个玩具吗但实际上把一个单片机项目做好真正难的不是某个单独的技术点而是把芯片选型、软件工程、通信协议、上位机开发这些环节串成一条完整链路的工程能力。我手头这个「瑞萨RA6M5-FSP-Qt遥控小车」项目就是非常典型的练习样本主控用瑞萨RA6M5底层外设全部通过FSP图形化配置生成上位机用Qt做了一套桌面遥控面板通过串口和车体通信。整套代码工程压缩在一个zip包里结构清晰非常适合刚入手RA系列、或者想系统做一次「下位机上位机」联调的开发者参考。如果你问我这个项目到底适合谁我大概会这么概括正在做毕业设计或者课程设计、需要一个能演示的完整系统的学生公司项目要切到瑞萨MCU、想提前熟悉FSP和RASC这套工具链的工程师又或者纯粹想看看Qt和嵌入式之间怎么通信、拿小车上手成本最低的爱好者。下面我把整个项目的拆解过程、配置细节以及我实际踩过的一些坑都写出来。1. 项目整体设计与方案选型1.1 核心需求拆解先把这个项目要解决的问题拎清楚一个遥控小车用户坐在电脑前通过桌面程序控制小车的方向与速度。拆开来看无非三件事——下位机能干活、上位机能发令、两者之间能对话。但落到工程上这三个环节各有各的坑下位机涉及MCU的时钟树、PWM定时器、串口外设任何一个配置错误都可能导致「程序跑了但车不动」上位机涉及Qt的串口访问、界面更新与事件循环稍不注意就会出现界面卡死或串口乱码两者之间还需要一套统一的协议否则数据一多就乱套。所以我接到这类项目第一步不是急着点灯而是先把软件架构分层RA6M5这边的代码结构是FSP生成的HAL层加上自己写的应用层应用层里再拆成「协议解析模块」和「电机控制模块」PC端则是Qt的界面层、串口通信层、协议编解码层。协议编解码这一层在两端各有一份职责是对称的这是整个项目里最容易出错但也最关键的地方。把这一层想清楚后面写代码基本就是填空。1.2 为什么选RA6M5这块主控选型这件事很多初学者觉得「随便拿一块板子就能干」但项目做到一半再换平台痛苦程度堪比推倒重来。我这块板子选RA6M5看中的是它的几个硬指标Cortex-M33内核、最高跑到200MHz、带TrustZone安全特性内部Flash最高2MB、SRAM最高640KB给FSP生成的HAL代码留了充足余量外设覆盖SCI串口、GPT定时器、SPI、I2C、CAN、USB HS、以太网项目做完想从「串口遥控」升级成「网络遥控」都方便。如果对比定位更低一些的RA4M1Cortex-M4最高48MHzRA6M5在性能余量上的优势非常明显。当然如果你的小车只是简单跑一跑RA4M1完全够用但考虑到这个项目还承担学习FSP和后续扩展的职能RA6M5的性价比高得多。另外RA6M5系列具体型号比较多选型时注意区分封装和Flash大小我项目里用的是R7FA6M5BF片内2MB Flash小车应用绰绰有余。2. RASC Keil 环境搭建与FSP工程生成2.1 FSP和RASC是干什么的FSP的全称是Flexible Software Package可以理解成瑞萨给RA系列MCU准备的一套HAL驱动库和中间件集合它替你封装好了底层寄存器的操作逻辑你不需要一上来就去撸寄存器手册。RASC是RA Smart Configurator负责图形化配置外设并自动生成代码。两者关系可以类比成你画好原理图RASC配置最后生成PCB生产文件FSP自动生成代码。对习惯Keil的老工程师来说这套组合比e2 studio全家桶上手成本低很多。环境上我推荐「RASC独立模式 Keil MDK」的组合。RASC独立版本生成的工程可以采用Keil工具链输出.uvprojx工程文件然后在Keil里编译调试。整体流程不复杂但有几个细节值得注意一是Keil版本尽量用5.30以上RASC生成的工程对编译器版本有要求二是要用ARM Compiler 6如果电脑里只有Compiler 5编译时会出现一堆莫名其妙的语法错误。2.2 RASC配置时钟、串口与PWM新建RASC工程时第一步是选择芯片具体型号这里选R7FA6M5BF注意区分封装和Flash大小别选错。接着配置时钟树外部晶振12MHz通过PLL倍频到主频200MHz。时钟树是很多人忽略的坑串口波特率对不对、PWM频率准不准根源都在时钟配置上。FSP时钟配置页面会可视化显示各个外设总线的时钟来源建议先截图留档后面排查问题会用到。然后添加自己需要的Stack。小车项目里最基础的三个外设一个UARTSCI实例负责串口通信配置波特率115200、8位数据位、1位停止位、无校验一个GPT定时器配置为PWM模式分两路输出到两个电机的驱动芯片一组GPIO控制电机的正反转方向。配置完成后在RASC里点击Generate Project Content代码就自动生成了。生成完拿到Keil里打开直接编译通常能一次通过但前提是Keil里已经装好了对应的Device Family PackRA6M5的DFP否则打开工程时器件选不到编译报错几千行。2.3 生成代码之后怎么组织工程结构第一次打开FSP工程的人容易被目录结构吓到其实核心就两块ra/目录是瑞萨的HAL库用户自己的代码在src/目录。后期应用代码尽量全部放在src/下面不要动不动去改ra/底层的生成文件。FSP有一个特性重新Generate Project Content会覆盖自动生成的目标文件所以用户代码最好放在独立文件里。这一点我在前几次项目里吃过亏——手动改了生成文件下一次重新配置外设再生成改的东西全没了。我在实际项目中会把应用层拆成motor.c、protocol.c、main_task.c这几个文件每个文件只干一件事。电机控制函数、协议解析函数、主循环调度分别放排查问题时能顺着文件定位不用在几百行代码里大海捞针。这算是工程上最基本的解耦思想。虽然小车项目不算大但代码习惯要从一开始养成后面接真实项目才不会乱。3. 电机控制与PWM调速的实现3.1 硬件连接与驱动方式这一版小车用的是双直流电机驱动——左右两个轮子各一个电机通过一颗电机驱动芯片比如TB6612或DRV8833驱动。RA6M5的GPT产生两路PWM波分别接入驱动芯片的PWMA/PWMB脚另外用两组GPIO控制电机正反转方向。整体连接表大概长这样RA6M5引脚功能连接目标GPT0 PWM输出A左轮PWMTB6612 PWMAGPT0 PWM输出B右轮PWMTB6612 PWMBP300左轮方向1TB6612 AIN1P301左轮方向2TB6612 AIN2P302右轮方向1TB6612 BIN1P303右轮方向2TB6612 BIN2SCI_UART TX/RX串口通信USB转TTL模块需要注意FSP里引脚的复用关系在RASC的Pins页面可以直接查选择GPT外设后会自动把对应GPIO锁定为PWM输出功能不用手动去改寄存器。引脚分配表是项目里最重要的一页文档配完务必截图否则项目隔几个月再翻回去光靠记忆根本想不起来当时为什么这么接。3.2 PWM占空比计算与调速PWM控制速度的原理其实不复杂电机两端电压通过PWM波的平均电压等效调节占空比越高、平均电压越高、电机转速越快。GPT是16位定时器周期值和占空比分别通过两个比较寄存器设置。比如配置周期为20000个计数单位PWM频率就是PCLK时钟除以20000。如果PCLK配置为100MHz那PWM频率就是5kHz这个频率对直流电机来说足够平滑听不到明显的啸叫声。实际代码里调节速度只需要调比较寄存器的值/* 用RASC生成的GPT接口设置占空比为75% */ uint32_t period R_GPT_PeriodGet(g_timer0_ctrl); R_GPT_DutyCycleSet(g_timer0_ctrl, period * 75 / 100, GPT_IO_PIN_GTIOCA);这里有个重点乘除顺序一定要先乘后除避免整数除法直接截断成0。这种细节看起来不起眼但低速调试时影响非常明显——你可能调了一个小时都看不出为什么速度一直是0最后发现是整数运算截断。用带FPU的芯片时也别依赖浮点嵌入式里能用整数就用整数稳定且高效。3.3 差速转向与加减速处理小车的转向没有舵机靠的是两侧轮子的速度差。举个具体例子前进时左右轮都是50%占空比左转时右轮保持50%、左轮降到20%车身就会往左偏如果左轮反转、右轮正转那就是原地旋转。所以通信协议里不仅要传「速度」这一个量还要传「方向」。我在协议里定义了一个方向字节和速度字节下位机根据方向做查表判断映射成左右轮的目标占空比。但实测下来有个经验速度变化的加速度必须限幅。直接从0跳到100%的话小车起步会明显「窜」出去机械结构也会受到冲击。所以我加了一个简单的斜坡函数让PWM占空比每次变化不超过5%这样起停就顺滑很多。这个限幅函数写起来很简单本质上就是判断目标值和当前值的差值超过阈值就按阈值步进。4. 通信协议与消息队列的设计4.1 帧格式与校验算法串口通信最忌讳的就是裸发裸收。如果你只是发一个字节0x01代表前进程序确实能跑但后续一旦要加功能、加参数协议就会变成一场灾难。我在这套项目里用了一个非常经典的帧结构帧头 长度 命令 数据 校验。字节内容说明0帧头0xA5用于识别帧起始1长度Length数据段长度2命令Cmd区分前进、后退、转向、停止等3~N2数据Data参数如速度值N3校验Checksum累加和取反校验用的是最简单的累加和从帧头到数据段所有字节求和后取低8位再取反。这个算法虽然简单但足够检测出绝大多数通信错误。以后想要更严格可以换成CRC32Qt端也有现成的库改造的成本很低。关键是校验算法两端要完全一致否则就会出现「偶发失控」这种极其难查的问题。4.2 下位机串口接收状态机RA6M5的SCI串口接收有两种路子轮询和中断。小车在跑的时候主循环还要处理PWM控制、避障扫描等逻辑轮询会导致丢数据所以必须走中断。FSP生成的UART回调函数里每收到一个字节就进一次回调。我在这里维护了一个简单的状态机来解析帧数据先等帧头0xA5然后收长度长度对了再继续收数据段最后比对校验和。这个状态机是通信稳定性的核心。我之前偷懒用「收到什么就执行什么」的裸方式结果上位机偶发多传一个字节小车就可能疯了一样乱跑。后来改成状态机加校验通信立刻稳定了。还有一点要特别注意帧接收超时要重置状态机。如果帧头收了一半对方突然断了下一个字节来了还在等数据就会导致之后所有帧都解析不出来表现就是「第一次通信正常之后全部失效」。4.3 用消息队列解耦接收与执行当数据帧解析成功后如果直接在串口中断或回调里调用电机控制函数隐患很大——回调函数的执行时间越短越好否则可能影响下一次中断接收。正确做法是把解析好的指令放入一个消息队列再由主循环不断从队列里取指令、执行电机控制。这里即使不上实时操作系统用一个简单的环形缓冲区也可以实现但既然FSP里集成好了FreeRTOS直接用队列API更省事。BaseType_t ret xQueueSend(controlQueueHandle, cmd, 0); if (ret ! pdPASS) { /* 队列满说明主循环处理不过来先丢弃这一帧 */ }我故意在队列满时丢弃数据而不是死等。原因是遥控指令具有时效性旧指令晚执行反而让小车动作滞后还不如丢帧来得安全。这种设计思路在做协议栈的人里很常见本质上是「接收快、执行慢」场景下的标准解法用队列做缓冲用丢帧保证实时性。5. Qt上位机的完整开发记录5.1 环境准备与串口模块安装上位机我用的Qt 5.15.2 Qt Creator选择这个版本的核心原因稳定、资料多、QSerialPort模块在5.x时代已经很成熟了。安装Qt时记得勾选SerialPort模块否则后面#include 直接报找不到头文件。新建工程时用Qt Widgets Application模板即可界面不用太花哨重点是控件的信号槽和串口读取逻辑。很多新手一上来就想做大而全的界面结果程序还没跑通就卡在UI美化上没必要先让功能闭环最重要。Qt安装过程中有个很容易踩的坑MinGW和MSVC两套编译器要分清。如果用MinGW就全程用MinGW中途混用MSVC的库会直接链接失败。另外Qt 5.15.2官方安装包需要注册Qt账号嫌麻烦也可以直接下载开源安装器配合国内镜像源安装速度很快。5.2 串口打开与指令下发在Qt里操作串口核心类就是QSerialPort和QSerialPortInfo。枚举可用串口列表的代码很固定foreach (const QSerialPortInfo info, QSerialPortInfo::availablePorts()) { ui-comboBox-addItem(info.portName()); }打开串口时配置波特率、数据位、停止位、校验位必须和下位机完全一致。我遇到过很多次「上位机发出去没反应」的排查最后发现是串口参数对不上——开发机上默认的8N1正好和下位机一致但换一台电脑或者换一个USB转串口模块默认参数变了通信就断了。建议把参数设置做进界面调试时方便随时切换。发送一条前进指令的代码大致如下QByteArray frame; frame.append(static_castchar(0xA5)); // 帧头 frame.append(static_castchar(0x02)); // 长度 frame.append(static_castchar(0x01)); // 命令前进 frame.append(static_castchar(speed)); // 速度参数 quint8 checksum 0; for (int i 0; i frame.size(); i) { checksum static_castquint8(frame.at(i)); } frame.append(static_castchar(~checksum)); // 校验 serialPort-write(frame);很多人会忽略校验和的计算方式必须和下位机一致只要有一端算得不同就会看到「偶尔动一下、大部分时间失灵」的诡异现象。建议上下位机协议校验代码写成完全对称的版本复制粘贴时仔细核对。5.3 事件循环与异步读取Qt的串口读取是异步的核心用到的机制是信号槽。QSerialPort每收到数据会发射readyRead()信号连接这个信号到自己的槽函数里进行读取。这里要注意不要在槽函数里做耗时操作否则会阻塞Qt的事件循环界面直接卡死。理解Qt事件循环有助于解决很多疑难杂症Qt的事件循环本质是一个消息队列鼠标、键盘、串口数据、定时器等各类事件都会被投递到队列里再由事件循环逐个分发。这也是为什么QCoreApplication::exec()之后的代码几乎不会执行——exec本身就是无限循环处理消息的入口。我之前调试时在exec后面加了一句qDebug()想确认程序跑到哪里结果等了半天没有输出当时还以为是编译器问题后来才反应过来是事件循环的机制。界面上如果要做实时的速度显示、接收日志可以用QTimer定时刷新或者直接在串口槽函数里更新控件。日志输出用QPlainTextEdit最直观配合QFileDialog::getSaveFileName还能把日志保存到本地文件方便后期排查问题。注意日志控件文本量大的时候要做截断否则内存占用一路飙升程序越跑越卡。5.4 发布打包与环境报错处理Qt开发完的上位机发到别的电脑上需要带上动态库。用Qt自带的windeployqt工具可以自动整理发布文件在命令行里进入exe所在目录执行windeployqt app.exe工具会把需要的DLL和插件目录全部复制过来。不过发布后经常出现一种情况开发机上跑得好好的换一台电脑就提示could not find the Qt platform plugin windows。这是典型的平台插件路径问题解决办法是把platforms目录里面有qwindows.dll放到exe同级目录下位置不对就会报错。另外如果你是在Linux上折腾Qt大概率会遇到一个经典报错QXcbConnection: failed to initialize xrandr。别慌这通常是缺少xcb相关依赖库装上libxcb-xinerama0以及配套的xcb库基本就能解决。这一类环境问题虽然烦人但排查思路是固定的把报错关键词复制进搜索引擎先看有没有人遇到过相同问题这类问题基本都已经有现成的解决方案了。6. 联调时的常见问题与排查记录6.1 Keil编译与下载失败用RASC生成Keil工程后第一次编译有可能会出现一大堆错误十有八九是Device Family Pack没装好。检查方式很简单在Keil的Pack Installer里搜索RA6M5装上对应版本的DFP再重新打开工程。下载的时候如果提示找不到目标芯片优先检查调试器连接——SWD接口接触不良就会导致Keil一直报No Target Connected这时重新插拔一下排线基本能解决。还有一个容易被忽略的问题RASC生成的工程默认用的是ARM Compiler 6如果电脑里只有Compiler 5编译报错会非常诡异各种不存在的语法错误都会冒出来。解决方法是装ARM Compiler 6或者在工程选项里手动切换编译器版本。这个问题在多人协作的项目里特别常见因为有人用RASC生成完工程后没有检查编译器配置。6.2 串口乱码与数据错位乱码先查三个方向波特率是否一致、时钟树是否配错、GND是否共地。USB转TTL模块和RA6M5开发板之间必须共地不共地时收发的数据会偶尔跳变这种「时好时坏」的问题最坑人。数据错位则基本是状态机写得不严谨漏了超时重置逻辑帧头收了一半之后突然断了下一个字节来了还在等数据。正确做法是解析失败或超时就重置状态机回到等待帧头状态。调试串口通信时我强烈建议先用串口调试助手分别验证上下位机。先把下位机接调试助手手动发指令帧确认小车能正确响应再让上位机单独对接调试助手确认发送的帧结构正确。两端都验证通过后再联调这样一旦出问题能快速定位是发送端还是接收端的问题。6.3 Qt崩溃与界面卡死Qt程序崩溃最常见的原因是跨线程操作UI。比如在QThread的工作线程里直接调用label-setText()这在Qt里属于未定义行为运气好能跑运气不好直接崩。我用Qt多线程时始终遵循一个原则线程里只发信号UI更新全部放在主线程的槽函数里完成。另外如果想在Qt中使用Breakpad做崩溃日志收集注意要在QCoreApplication::exec()之前初始化好否则异常发生后就没机会捕获了。界面卡死的问题通常和串口槽函数里的耗时操作有关。解决办法是开一个缓冲区先把数据收进来界面用定时器每100ms刷新一次数据积压时优先处理最新的帧、丢弃过期的历史帧。这套思路和前面下位机的消息队列设计如出一辙本质上都是「高频接收低频处理」的缓冲问题。6.4 几个值得长期留存的习惯最后分享几个我在这个项目里比较深的体会。第一每次在RASC里配置完引脚一定要截图留档。这个项目隔几个月再回去看如果没有当时的记录单靠记忆去查配置非常痛苦。第二FSP生成代码后不要手工改ra目录。为了省事直接在HAL文件里加打印信息等下一次重新生成工程时你会发现改的东西全没了。第三协议升级一定要保留版本号字段。早期协议里没有版本号后来加功能时发现老上位机和新下位机兼容不了调试了很长时间才定位到问题。这套「瑞萨RA6M5 FSP Qt」的组合做完一遍基本等于走了一次完整的嵌入式产品开发流程从MCU选型到状态机编程从串口协议到Qt事件循环每一环都有能沉淀下来的经验。如果你最近也在折腾类似的项目可以照着这份复盘去试。遇到问题把报错原文发出来大家一起讨论比自己闷头查手册效率高得多。本文还有配套的精品资源点击获取
返回列表