news 2026/8/21 9:05:26

【21届智能车赛轮腿穿越组】基于 CYT4B 的轮腿车开发笔记(七):遥控、菜单系统与实车调试心得

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【21届智能车赛轮腿穿越组】基于 CYT4B 的轮腿车开发笔记(七):遥控、菜单系统与实车调试心得

本期(最终期)核心:人机交互怎么设计?LoRa 摇杆如何映射成速度/转向?菜单系统怎么组织?以及整套系统在实车调试中沉淀下来的通用经验。


1. 先想清楚:人机交互的价值

一辆小车只有算法是不够的——它需要被控制、被配置、被调试。这台轮腿车有两套交互手段:

  1. LoRa 无线遥控器:实车操控、路径录制;
  2. IPS200 屏 + 按键:菜单配置、状态显示。

交互系统的设计目标就一句话:让"操作"和"调试"都能在车边上完成,不用反复烧程序


2. LoRa 遥控:摇杆数据怎么变控制指令

遥控器通过 LoRa 发来摇杆原始值(joystick[2],两个摇杆)和按键状态。代码在remote.c里做两件事:

2.1 摇杆线性映射

/* 摇杆1:前后推 → 目标速度 */int16_tremote_map_value(int16_tx,int16_tin_min,int16_tin_max,int16_tout_min,int16_tout_max){return(x-in_min)*(out_max-out_min)/(in_max-in_min)+out_min;}

把摇杆的模拟范围(如 -2010~2000)线性映射到速度输出范围(如 -600~600):

car.target_speed=remote_map_value(lora_driver_get_joystick(1),JOY1_IN_MIN,JOY1_IN_MAX,JOY1_OUT_MIN,JOY1_OUT_MAX);

2.2 摇杆增量转向(偏航)

摇杆2 不直接给"目标偏航角",而是给"偏航增量"——打杆就往那个方向慢慢转,回杆保持当前方向

floatremote_update_angle_incremental(int16_tjoy_raw,float*cur_angle){floatdelta=0.0f;if(joy_raw>JOY2_DEADZONE){delta=(float)(joy_raw-JOY2_DEADZONE)/(JOY2_IN_MAX-JOY2_DEADZONE)*MAX_ANGLE_STEP;}elseif(joy_raw<-JOY2_DEADZONE){delta=(float)(joy_raw+JOY2_DEADZONE)/(-JOY2_IN_MIN-JOY2_DEADZONE)*MAX_ANGLE_STEP;}*cur_angle+=delta;/* 累加 → 增量转向 */while(*cur_angle>180.0f)*cur_angle-=360.0f;/* 归一化 */return*cur_angle;}

为什么转向用"增量"而速度用"绝对"?

这非常符合操控直觉:

  • 速度:摇杆推到哪,速度就要到哪 → 绝对映射(推杆即加速,回中即停);
  • 转向:打杆只是"给个转动的角速度",松杆后希望车保持方向,而不是回正 → 增量映射。

一句话:位置量用绝对映射,速度/方向量用增量映射。这个原则做任何遥控/航模都适用。

2.3 超时保护

遥控链路断了车必须停,不能继续开。isr 里做超时检测:

voidlora_driver_tick(CarState_t*s){s->lora_timeout_cnt++;if(s->lora_timeout_cnt>LORA_TIMEOUT_MS/10){/* 500ms 没收到数据 */lora3a22_state_flag=0;/* 标记失联 */s->lora_timeout_cnt=0;}}

主循环/中断里一旦发现失联:

if(!lora_driver_is_connected()){car.target_speed=0;/* 失联即停 */}

无线设备必须有超时保护,失联必须有个安全默认动作(这里是停车)。这是遥控系统的安全底线。


3. 按键系统:消抖与"单次触发"

车上有 4 个按键 K1~K4。按键最烦的是抖动长按连发。代码用两招解决:

intkey_driver_scan(void){/* 读 GPIO */ints1=hal_gpio_get(P20_1);...if(s1==1&&s2==1&&s3==1&&s4==1)return0;/* 全高 = 无按键 */hal_system_delay_ms(20);/* 20ms 软件消抖 *//* 再读一遍,确认按下 */s1=hal_gpio_get(P20_1);...if(s1==0)return1;.../* 返回按键号 */return0;}intkey_driver_get_single(void){staticintlast_key=0;intcur=key_driver_scan();if(cur!=0&&last_key==0){/* 只在"从未按下→按下"沿触发 */last_key=cur;returncur;}elseif(cur==0){last_key=0;}return0;}
  • 软件消抖:读两次、间隔 20ms,两次都低才算按下;
  • 边沿触发last_key记住上次状态,只在"释放→按下"的上升沿返回一次,长按不会连发

消抖 + 边沿触发,是按键系统的标配。尤其菜单导航里,长按连发会让菜单乱跳,边沿触发必须做。


4. 菜单系统:页面状态机

菜单用"页面"管理,本质上又是一个状态机:

voidmenu_process(CarState_t*s){intkey=key_driver_get_remote_or_key(s);/* 支持遥控或按键 */if(s->menu_page==PAGE_MAIN){if(key==1)s->menu_page=PAGE_RUN;/* K1: 运行菜单 */if(key==2)s->menu_page=PAGE_DEBUG;/* K2: 调试菜单 */if(key==3){s->run_flag=1;s->menu_page=PAGE_NONE;}/* K3: 开始复现 */if(key==4){s->run_flag=2;s->menu_page=PAGE_NONE;}/* K4: 遥控模式 */s->menu_need_redraw=1;}...}voidmenu_display(CarState_t*s){if(!s->menu_need_redraw)return;/* 只在需要时重绘,省屏寿命/CPU */...if(s->menu_page==PAGE_MAIN){display_driver_show_str(...);...}}

菜单设计的两个要点

  1. 页面状态机:每个页面响应按键跳转,run_flag切换"菜单态/运行态"——菜单和运行是互斥的模式,用标志位切换;
  2. 脏标记重绘menu_need_redraw标志,只有页面切换/参数变化才重绘,平时不刷屏。

菜单系统的本质就是"状态机 + 脏标记"。从单页菜单到多级菜单,骨架都是这个。


5. 参数配置哲学:集中在一个头文件

所有可调参数集中在config/car_config.h——这是全项目最好用的一个文件

/* PID */#definePID_PITCH_KP210.0f#definePID_PITCH_KI0.1f#definePID_PITCH_KD50.0f/* 腿部机械常数 */#defineLEG_L16.0f#defineLEG_L53.7f/* 跳跃时序 */#defineJUMP_T_EXTEND100#defineJUMP_T_RETRACT60

设计规则(代码注释里明明白白写着):

  1. 所有可调参数集中定义,不在 .c 里出现"魔法数字";
  2. 每个参数配注释说明含义
  3. 需要运行时改的参数(曝光、阈值、路段时长)存 Flash,开机读回(第4期)。

为什么集中?调参 = 改一个头文件 = 全项目可见。散落在十几个 .c 文件里的魔法数字,是嵌入式项目最大的维护噩梦。


6. 实车调试心得(踩坑总结)

最后分享这套系统实车调试沉淀下来的通用经验:

6.1 分模块验证,别一次全开

  • 先只测平衡(电机不动、只看俯仰),再测转向,再测遥控,最后才测路径/视觉
  • 一次只调一个变量,其他参数全部固定。

6.2 传感器数据先"打印"再"信"

  • 任何算法上阵前,先把 raw 数据打到屏上/串口人工检查一遍
  • IMU 是否零漂、GPS 星数够不够、摄像头阈值合不合适——不验证数据就调算法,等于盲调

6.3 时间驱动的开环动作,先在地面做

  • 跳跃这类动作,先架空轮子看腿部时序是否合理,再上真车;
  • 跳跃时序JUMP_T_*这种参数,单独抽出单独调,别和平衡耦合。

6.4 保护机制越早加越好

  • 按键急停、遥控失联停车、腿长越界钳制——这些保护一开始就加,后期省下无数麻烦;
  • 出现 NAN、越界,全部要有兜底(第3期讲的ik_solveNAN 处理)。

6.5 参数可持久化,调参才高效

  • 曝光、阈值、路段时长这些要存 Flash、菜单可改、开机读回
  • 不然每改一次参数烧一次程序,调试效率极低。

7. 系列总结

回到第1期的一句话:思路大于实现。回头看这 7 期,其实核心思路就几条:

主题核心思路
架构分层 + 状态体通信 + 分级调度
平衡级联 PID,越内层越快,符号解耦
腿部IK 逆解 + 时间状态机跳跃
路径投影 → 骨架 → 样条 → 纯追踪
跟踪三问(在哪/往哪/多快)+ 路段状态机
视觉二值化 → 边线 → PD 修正叠加
交互绝对/增量映射 + 状态机 + 集中配置

代码会过时,架构和思路不会。把这套骨架换成别的 MCU、别的传感器、别的小车,照样能跑。


8. 后记

本项目代码已开源在 GitHub:

https://github.com/L-F-S-max/wheel-legged-robot

欢迎 Star / Issue 交流。如有问题或想深入某个模块,欢迎评论区留言。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/21 9:04:30

【JMeter 学习打卡 Day 4】多用户登录 + 动态 token 关联

一、学习总览天数主题核心产出Day 1JMeter 入门与基础概念弄清 JMeter 是什么、能干什么、不能干什么Day 2第一个测试计划 HTTP 请求能独立搭出一个最小可运行的 HTTP 测试计划Day 3断言&#xff1a;让测试结果有意义理解为什么没有断言&#xff0c;错误率永远是 0Day 4参数化…

作者头像 李华
网站建设 2026/8/21 9:02:25

HEIR编译器实战:基于同态加密的私有化AI推理完整指南

在AI应用遍地开花的今天&#xff0c;数据隐私与安全已成为悬在开发者与企业头顶的“达摩克利斯之剑”。你是否遇到过这样的困境&#xff1a;想将AI能力集成到医疗、金融等敏感业务中&#xff0c;却因用户数据不能离境、模型参数需要保密等合规要求而束手无策&#xff1f;传统的…

作者头像 李华
网站建设 2026/8/21 8:57:30

【王道操作系统 | 第二章】进程管理、处理机调度与死锁

操作系统第二章的主线&#xff0c;是回答一个问题&#xff1a;多个程序同时运行时&#xff0c;操作系统如何管理它们、分配处理机、协调共享资源&#xff0c;并处理资源互相等待的情况&#xff1f; 本文从进程和线程出发&#xff0c;依次梳理进程控制、进程通信、处理机调度、同…

作者头像 李华
网站建设 2026/8/21 8:56:29

FastAPI实战指南:从零构建高性能Python API与Todo项目

在Python后端开发领域&#xff0c;选择一个高效、现代且易于上手的Web框架是项目成功的关键。如果你厌倦了传统框架的繁琐配置&#xff0c;或者正在寻找一个能快速构建高性能API的方案&#xff0c;那么FastAPI无疑是当前最值得投入学习的框架之一。它凭借其极简的设计、自动化的…

作者头像 李华
网站建设 2026/8/21 8:56:04

从零搭建工业控制系统(八):工作流编排系统——把阶段串成流水线

工作流编排系统&#xff1a;把阶段串成流水线这是「从零搭建工业控制系统」系列第8篇。前两篇讲了配方和序列——配方管参数&#xff0c;序列管编排。但一个完整的生产流程不只是跑序列&#xff0c;还有进料、预热、检测、出料等阶段。这篇讲最外层的工作流编排。序列不够用吗 …

作者头像 李华