车载终端
驱动类
💣 坑 1:cc1: error: code model kernel does not support PIC mode
现象:make 编译内核模块时报错,提示不支持 PIC 模式。
原因:GCC 默认开启了 PIE(位置无关可执行文件)选项,但 Linux 内核要求强制禁用 PIC。同时,没有指定 ARCH=arm 和 CROSS_COMPILE,导致调用了 x86 的 gcc 去编译 ARM 内核代码。
解决:在 Makefile 中添加:
ARCH := arm
CROSS_COMPILE := arm-linux-gnueabihf-
ccflags-y := -fno-pic
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules教训:不要相信宿主机的默认 gcc,交叉编译必须显式指定架构和工具链前缀。
💣 坑 2:设备树的"鬼打墙" —— 改了等于没改
这个阶段,我至少花了 8 个小时 在不同的 .dts 文件、不同的分区、不同的文件名之间反复横跳。
2.1 改错了 .dts 文件
现象:在 imx6ull-14x14-evk.dts 里加了节点,编译、替换、重启,把 imx6ull-14x14-evk.dtb 加载进板子里面。结果 /proc/device-tree/ 里啥也没有。
原因:U-Boot 加载的设备树文件根本不是 imx6ull-14x14-evk.dtb,而是正点原子定制的带屏幕参数的版本(如 imx6ull-14x14-emmc-4.3-800x480-c.dtb)。
解决:在开发板上执行 fw_printenv fdt_file,找到 U-Boot 真正加载的文件名,然后用那个名字编译对应的 .dts 文件。
# 查看 U-Boot 加载的 dtb 文件名
printenv fdt_file
# 输出:imx6ull-14x14-emmc-4.3-800x480-c.dtb教训:永远不要猜设备树文件名,直接问 U-Boot。
💣 坑 3:覆盖 /boot/ 无效
现象:编译了 .dtb,用 scp 传到 /boot/,重启,没变化。
原因:/boot/ 是 Linux 根文件系统里的一个文件夹,而 U-Boot 引导程序根本不读这里。U-Boot 读的是 eMMC 或 SD 卡的 FAT 格式启动分区,在 Linux 下挂载在 /run/media/mmcblkXp1/。
1代表从 eMMC 启动。

解决:把 .dtb 复制到真正的 FAT 启动分区:
# 正点原子 I.MX6ULL eMMC 版
cp new.dtb /run/media/mmcblk1p1/
# 如果插了 SD 卡,也覆盖 SD 卡的 FAT 分区
cp new.dtb /run/media/mmcblk0p1/教训:Linux 下的 /boot 和 U-Boot 读取的 FAT 分区是两码事。U-Boot 只认 FAT 分区里的文件。
💣 坑 4:插着 SD 卡,改 eMMC 永远无效
现象:明明覆盖了 /run/media/mmcblk1p1/,重启后还是旧设备树。
原因:U-Boot 的启动优先级是 SD 卡优先于 eMMC。只要插着 SD 卡,U-Boot 就会从 SD 卡的 FAT 分区加载 .dtb,完全忽略 eMMC。改了 eMMC,但 U-Boot 读的是 SD 卡。
没插 SD 卡:

插了 SD 卡,dtb 不见了:

解决:
- 调试时拔掉 SD 卡,只保留 eMMC。
- 或者每次都同时覆盖两个分区。
教训:U-Boot 的物理启动顺序优先于你脑子里的"我以为"。
💣 坑 5:compatible 字符串必须一字不差
现象:dmesg 里死活看不到 probe 打印,驱动就是没匹配上。/sys/bus/platform/drivers/ 下能看到驱动加载,但 /dev/、/sys/class/ 下没有自己定义的目录名。
原因:设备树里写的是 compatible = "my_first-led",驱动匹配表里写的是 "atkalpgha_gpioled"。多一个下划线、少一个连字符都不行。
解决:让两者完全一致。
// 设备树
compatible = "alientek,key";// 驱动中
static const struct of_device_id key_of_match[] = {
{ .compatible = "alientek,key" }, // 必须一模一样
{ /* Sentinel */ }
};教训:字符串匹配容不得半点马虎,复制粘贴是最安全的。
💣 坑 6:echo "1" > /dev/xxx 点不亮灯
现象:用 echo 1 > /dev/dstplatled 写数据,灯不亮。但用 printf "\x01" > /dev/dstplatled 就亮了。
原因:echo 写入的是 ASCII 字符 '1',对应的十六进制是 0x31。而驱动里判断的是 if (ledstat == LEDON),LEDON 定义的是数字 1。0x31 != 0x01,所以条件永远不成立。
解决:
- 方法一:用
printf "\x01"写入二进制值。 - 方法二:在驱动里把 ASCII 转成数字:
ledstat = databuf[0] - '0'。 - 方法三:使用编译好的应用程序(
ledApp里用atoi将字符串转成整数,写入的是二进制值)。
教训:应用层和内核驱动传数据时,必须明确数据的格式(ASCII 还是二进制)。echo 是给人看的,不是给内核看的。
💣 坑 7:写错设备节点名
现象:驱动里定义 LEDDEV_NAME "dstplatled",应用层写 open("/dev/dtsplatled"),一直报 No such file or directory。
原因:dst 和 dts 就差一个字母。
教训:设备和节点名称用宏统一管理,不要手动输入,否则早晚在这个"小问题"上浪费半小时。
💣 坑 8:Driver 'ap3216c' is already registered
现象:insmod ap32.ko 报错 Error: Driver 'ap3216c' is already registered。
原因:系统里已经有了一个同名的 I2C 驱动(内核自带的 ap3216c),它已经占用了 ap3216c 这个名字。I2C 子系统不允许重名驱动注册。
解决:把 ap3216c_driver.driver.name 改成独一无二的名字,比如 "ap3216c_my"。
static struct i2c_driver ap3216c_driver = {
.driver = {
.name = "ap3216c_my", // 改这里
.of_match_table = ap3216c_of_match,
},
};注意:仅改名还不够,还需要配合坑 9 的解绑操作。
教训:修改 /dev 下的设备名(AP3216C_NAME)没用,要改的是 I2C 驱动注册名(.driver.name),两者是独立的。
💣 坑 9:I2C 设备被系统驱动抢占
现象:设备树加好了,/sys/bus/i2c/devices/ 下也出现了 I2C 地址 0-001e,但 insmod 后 probe 就是不执行。
原因:系统自带的 ap3216c 驱动已经通过 compatible 匹配成功,抢先执行了 probe,占用了该 I2C 设备。一个 I2C 设备只能绑定一个驱动(一对一),你的驱动虽然注册了,但设备已经被别人"抢车位"了。
解决:把系统驱动从设备上解绑,腾出车位:
echo 0-001e > /sys/bus/i2c/devices/0-001e/driver/unbind
insmod ap32.ko # 这次 probe 就会执行教训:看到 0-001e 这类 I2C 地址目录,先执行 ls -l 0-001e/driver 看看被谁占用了。解绑大法(unbind)是调试 I2C 驱动的必备技能。
💣 坑 10:gpio_request 返回 -EBUSY
现象:insmod mykey.ko 后 dmesg 报 gpio_request failed,错误码 -16(-EBUSY)。
原因:GPIO 18 已经被系统自带的 USER-KEY1 按键驱动占用了。通过 /sys/kernel/debug/gpio 可以查看:
cat /sys/kernel/debug/gpio | grep gpio-18
# 输出:gpio-18 (USER-KEY1) in hi解决:在设备树中找到 gpio_keys 节点,禁用冲突的子节点,或修改驱动使用另一个空闲 GPIO。也可以和 I2C 一样使用临时解绑。
key1@1 {
label = "USER-KEY1";
gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
status = "disabled"; // 禁用冲突节点
};教训:GPIO 是独占资源,gpio_request 失败时,去 /sys/kernel/debug/gpio 查岗,看谁占了你的坑。
网络类
待补充
Qt 类
见文章 嵌入式Qt
摄像头
一、V4L2 格式设置问题
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
| 格式设置失败 | Failed to set format | 摄像头不支持 MJPEG,但代码优先尝试了它 | 从 try_formats 中移除 MJPEG 条目,只保留 YUYV 和 RGB565 |
内核报错 unknown pixelformat:'MJPG' | dmesg 频繁打印 invalid Fourcc format | CSI 驱动不支持 MJPEG,尝试无效格式导致内核报错 | 彻底删除 MJPEG 相关代码,避免无效尝试 |
| 只有 YUYV 格式可用 | 最终只有 YUYV 被接受 | 硬件(CSI 接口 + OV5640)本身不支持 MJPEG 压缩输出 | 接受 YUYV,实现 YUYV→RGB565 软件转换 |
二、内存管理与崩溃问题
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
| Remote process crashed | 点击停止时程序崩溃 | QImage 使用了外部缓冲区,但在使用前释放了内存 | 使用 .copy() 深拷贝,确保 QImage 拥有独立数据 |
| 内核 Oops 崩溃 | Unable to handle kernel paging request | 线程退出时 munmap 未执行或重复释放 | 统一资源管理,所有退出路径调用 cleanup() |
Failed to request buffers | 停止后再启动时申请缓冲区失败 | 设备未完全释放(STREAMOFF 未执行) | 在 stopCapture() 中强制清理设备资源 |
goto 跳过变量初始化 | 编译错误 crosses initialization | C++ 不允许 goto 跨越带初始化的变量声明 | 将所有可能被跳过的变量声明移到函数开头 |
三、线程同步问题
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
| 点击停止clear()后 Label 不清空 | 画面暂停但不清除 | updateImage 在停止后仍被调用 | 在 updateImage 中添加 if (!m_capturing) return; |
QThread 资源竞争 | 崩溃或卡死 | run() 和 stopCapture() 同时操作 m_fd | stopCapture() 只发信号,由 run() 自行调用 cleanup() |
| 析构函数重复清理 | 二次释放导致崩溃 | 析构函数和 stopCapture() 都释放了缓冲区 | 析构函数只调用 stopCapture(),不再手动清理 |
四、性能问题
| 问题 | 现象 | 原因 | 优化方案 |
|---|---|---|---|
| 帧率低(7 FPS) | 640x480 时只有 7 FPS | YUYV→RGB 转换消耗 CPU,I.MX6ULL 性能有限 | 降低分辨率到 320x240,帧率提升到 25 FPS |
| CPU 占用高 | 接近 100% | 每帧 new/delete 和 copy() 额外开销 | 预分配缓冲区,减少动态内存分配 |
| 画面卡顿 | 显示延迟明显 | 分辨率与硬件性能不匹配 | 根据场景选择:预览用 320x240,识别用 640x480 |
五、开发环境问题
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
apt-get 无法使用 | /var/lib/dpkg/status 不存在 | 出厂文件系统被裁剪,无包管理支持 | 交叉编译所需工具(如 v4l-utils)后手动拷贝 |
v4l2-ctl 不可用 | 无法查询摄像头格式 | 系统未安装 v4l-utils | 在代码中枚举格式,或交叉编译后拷贝 |
✅ 最终稳定的工作状态
| 参数 | 值 |
|---|---|
| 像素格式 | YUYV(V4L2_PIX_FMT_YUYV) |
| 分辨率 | 320x240(优先)或 640x480 |
| 帧率 | 320x240: ~25 FPS,640x480: ~7 FPS |
| 显示格式 | RGB565(QImage::Format_RGB16) |
| 转换方式 | yuyv_to_rgb565() 整数定点运算 |
| 资源管理 | 统一 cleanup(),所有退出路径保证释放 |
| 停止行为 | m_capturing 标志阻止后续更新,clear() 立即清空 |
📌 关键教训
- 先查摄像头能力,再写代码:用
v4l2-ctl或代码枚举格式,避免盲目尝试。 - 嵌入式平台性能有限:降低分辨率是提升帧率最有效的方法。
- QImage 内存管理要谨慎:外部缓冲区必须保证生命周期,使用
.copy()深拷贝。 - 线程退出必须清理资源:所有退出路径都要调用清理函数,避免资源泄漏。
- C++
goto注意变量初始化:提前声明变量,避免跨越初始化。
DHT11
一、设备树配置问题
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
| compatible 不匹配 | probe 不执行,dmesg 无 matched 打印 | 设备树和驱动中的 compatible 字符串不一致 | 确保设备树 compatible = "my-dht11" 与驱动 { .compatible = "my-dht11" } 完全一致 |
| 引脚编号不一致 | GPIO 3 request successfully 但实际引脚不对 | pinctrl 配置了 MX6UL_PAD_GPIO1_IO01,但 GPIO 属性用了 &gpio1 3 | 统一使用同一引脚:pinctrl 和 GPIO 属性必须匹配 |
| pinctrl 未定义 | GPIO 请求成功但电平不变化 | 设备树中未配置 pinctrl-0 或 pinctrl 节点缺失 | 添加 pinctrl 节点,明确引脚复用为 GPIO 功能 |
| PAD 控制值不合适 | 通信超时,无法读取数据 | 0x17059 速度太慢(50MHz),无法满足微秒级时序 | 改用 0x10B0(100MHz 速度,更适合 DHT11) |
设备树最终正确配置:
pinctrl_dht11: dht11 {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
>;
};
dht11 {
compatible = "my-dht11";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_dht11>;
my-dht11-gpio = <&gpio1 3 GPIO_ACTIVE_HIGH>;
status = "okay";
};二、GPIO 冲突问题
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
gpio_request 返回 -EBUSY | failed to request GPIO 3 | GPIO 3 已被 LED 驱动或其他设备占用 | 更换为未使用的 GPIO 引脚(如 GPIO1_IO18),或禁用冲突设备节点 |
| GPIO 被系统驱动占用 | cat /sys/kernel/debug/gpio 显示 gpio-3 (?) out lo | 旧驱动或设备树节点未禁用 | 在设备树中禁用冲突节点(如 LED 或按键节点),或更换引脚 |
排查命令:
cat /sys/kernel/debug/gpio | grep gpio-3三、时序通信问题
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
| 起始信号拉低时间不够 | wait for response low timeout | 使用 udelay(20) 只有 20 微秒,DHT11 需要 ≥18 毫秒 | 改为 mdelay(20)(20 毫秒) |
| 上拉电阻缺失 | 无响应,电平不变化 | DHT11 DATA 脚需要 4.7kΩ~10kΩ 上拉电阻到 VCC | 外接上拉电阻或使用带底板模块 |
| GPIO 速度太慢 | 读取数据超时 | PAD 配置中 SPEED=01(50MHz),微秒级时序不精确 | 改用 0x10B0(SPEED=10,100MHz) |
| 引脚接错 | wait for response low timeout | DATA 线未连接到正确的 GPIO 引脚 | 确认硬件接线与设备树配置一致 |
| 供电不足 | 传感器无响应 | DHT11 供电电压不匹配(需 3.3V 或 5V) | 检查模块供电电压,使用正确的 VCC |
正确的起始信号时序:
dht11_set_output(1);
udelay(5); // 稳定高电平
dht11_set_output(0);
mdelay(20); // ⚠️ 必须 ≥18ms
dht11_set_output(1);
udelay(30); // 20-40us
dht11_set_input(); // 切换为输入四、驱动代码问题
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
DHT11_TIMEOUT 未定义 | 编译错误 | 代码中使用了 DHT11_TIMEOUT 但未定义宏 | 添加 #define DHT11_TIMEOUT 100 |
| 函数名大小写不一致 | 隐式声明警告 | 定义 dht11_Read_Byte,调用 DHT11_Read_Byte | 统一函数名,保持大小写一致 |
DHT11_TIMEOUT 过紧 | 偶尔读取失败,超时 | 100us 对某些传感器太短 | 增加到 200-300us |
udelay(45) 判断点偏移 | 数据位识别错误 | 不同传感器对 '0'/'1' 的判定时间有差异 | 尝试 40-50us 之间的值 |
五、硬件问题汇总
没有上拉电阻的DHT11,时序会出问题。板子所有引脚都没有上拉电阻,得买一个有PCB板的DHT11
| 检查项 | 要求 | 验证方法 |
|---|---|---|
| 上拉电阻 | DATA 脚必须外接 4.7kΩ~10kΩ 到 VCC | 空闲时测量 DATA 脚电压应为 3.3V |
| 供电电压 | 3.3V 或 5V(根据模块标称) | 用万用表测量 VCC 与 GND |
| 接线 | DATA、VCC、GND 一一对应 | 对照原理图确认排针位置 |
| 传感器型号 | 确认是 DHT11 而非 DHT22(时序不同) | 查看模块丝印 |
| 传感器状态 | 模块是否损坏 | 更换另一个 DHT11 模块测试 |
✅ 最终稳定工作状态
| 参数 | 值 |
|---|---|
| GPIO 引脚 | GPIO1_IO03(或其他空闲引脚) |
| PAD 配置 | 0x10B0(100MHz + 上拉) |
| 起始信号 | 拉低 20ms,拉高 30us |
| 超时阈值 | 100-200us |
| 数据读取 | 5 字节(湿度整数/小数 + 温度整数/小数 + 校验和) |
| 帧率 | 约 1-2 次/秒(DHT11 本身响应慢) |
📌 关键教训
- GPIO 冲突先查表:
cat /sys/kernel/debug/gpio是排查 GPIO 占用的必备命令。 - pinctrl 和 GPIO 编号必须一致:pinctrl 配置哪个引脚,GPIO 属性就必须用同一个。
- PAD 速度要够快:DHT11 需要微秒级精度,SPEED 至少 100MHz(
0x10B0)。 - 上拉电阻是刚需:DHT11 不能没有上拉电阻,否则通信必定失败。
- 起始信号拉低必须用毫秒级延时:
mdelay(20)而不是udelay(20)。 - 不同传感器可能稍有差异:如果数据读不出来,微调
udelay(45)的值(40-50us)。
好的,以下是你从零开始构建 GPS 定位 + 百度地图显示模块过程中遇到的完整问题清单与解决方案总结,按技术领域分类:
📡 GPS + 地图模块开发问题总结
一、串口通信问题
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
| 串口无数据 | cat /dev/ttymxc2 无输出,minicom 正常 | minicom 退出后未正确释放串口,或设备被占用 | 重启开发板或 sudo fuser -k /dev/ttymxc2 释放占用 |
| 波特率不匹配 | minicom 能看到数据,但程序读不到 | minicom 配置了 38400,但代码中是 9600 | 将代码中波特率改为与 minicom 一致(38400) |
| 串口设备路径错误 | 程序无法打开串口 | 设备节点不是 /dev/ttymxc2 | 用 ls /dev/ttymxc* 确认实际设备名 |
readByte 返回 -1 | 循环意外退出 | 串口设备被关闭或权限问题 | 检查 errno,用 sudo chmod 666 /dev/ttymxc2 临时解决 |
二、GPS NMEA 解析问题
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
hasValidFix() 一直返回 false | 没有经纬度输出 | 室内无 GPS 信号,$GNGGA 定位状态字段为 '0' | 将设备移到室外,或在代码中强制设置测试坐标 |
parseGGA 函数签名冲突 | 编译错误 declaration of 'std::string fields [20]' shadows a parameter | 函数参数和局部变量重名,且设计冗余 | 修改为 bool parseGGA(const std::string &frame),内部调用 splitFields |
splitFields 未定义 | 链接错误 | 声明了但未实现 | 在 .cpp 中添加 static int splitFields(...) 实现 |
状态机未识别 \n | parseFrame 没有被调用 | GPS 模块使用 \r\n 作为换行,状态机只识别 \n | 在收到 \n 时检查缓冲区末尾是否为 \r,如果是则移除 |
三、多线程与死锁问题
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
线程死锁(卡在 QMutexLocker) | qDebug() 只打印 "1" 不打印 "2" | startWork() 持有锁时调用了 runLoop(),而 runLoop() 中的循环再次尝试获取同一把锁 | 将 runLoop() 调用移到锁外,只在锁内修改 m_running |
runLoop 退出后线程未停止 | stopWork() 调用后线程仍在运行 | 锁的作用域未正确控制,m_running 检查未生效 | 在循环每次迭代开始时检查 m_running,并正确释放锁 |
emit positionUpdated 在子线程中发射 | UI 更新延迟或卡顿 | Qt 信号在接收者所在线程执行,onPositionUpdated 在主线程执行,但频繁调用会阻塞 UI | 使用定时器限流,减少主线程负载 |
四、Qt 界面与地图显示问题
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
| UI 卡死,无法操作 | 窗口无响应 | GPS 线程频繁发射 positionUpdated,主线程不断调用 updateMap() 发起网络请求,请求队列堆积 | 在 map 中添加定时器(2 秒更新一次),GPS 信号只保存最新位置,定时刷新 |
| 地图只请求一次,不更新 | 只有首次请求,后续无网络请求 | updateMap() 中 center 被硬编码为 "成都" 或 "宜宾",URL 未变化,QNetworkAccessManager 可能缓存或去重 | 使用真实经纬度构建 URL,并在 URL 后添加时间戳参数强制刷新 |
ReceiveMapImg 频繁调用 | 界面闪烁、CPU 占用高 | 每次收到定位都下载图片并 setStyleSheet 重绘 | 限流后解决 |
| 图片无法显示 | setStyleSheet 不生效 | 相对路径 ./map.png 可能不在程序运行目录 | 使用绝对路径或 QCoreApplication::applicationDirPath() + 文件名 |
五、代码设计问题(逐步发现并修复)
| 问题 | 表现 | 原因 | 解决方案 |
|---|---|---|---|
GpsWorker 与 QThread 生命周期混乱 | 程序退出时崩溃或资源泄漏 | worker 和 thread 的 deleteLater 顺序不当 | 使用 connect(m_gpsThread, &QThread::finished, m_gpsWorker, &QObject::deleteLater) 自动清理 |
m_hasValidPosition 和 m_currentLat 未同步更新 | 地图显示上次位置 | updateMap() 在 onPositionUpdated 中调用时,m_hasValidPosition 已被更新,但 m_currentLat 等值未正确赋值 | 确保 onPositionUpdated 中同时更新所有相关变量 |
GpsParser 解析结果未重置 | 无效定位时仍保留旧坐标 | 解析器在定位无效时只设置 m_hasValidFix = false,但未清零经纬度 | 在 parseGGA 的无效分支中将经纬度设为 0.0 |
没有 Q_OBJECT 宏 | 信号槽连接失败或编译警告 | GpsWorker 类继承 QObject 但未声明 Q_OBJECT | 在头文件中添加 Q_OBJECT 宏,并重新运行 qmake |
六、性能问题与优化措施
| 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
| CPU 占用高(100%) | 开发板发热、系统卡顿 | readByte 超时 1000ms,但 while 循环在超时前一直运行,几乎没有休眠 | 在循环末尾添加 usleep(10000) 或使用 QThread::msleep(10) |
| 网络请求频率过高 | 流量消耗大,UI 卡顿 | GPS 每秒更新 1-10 次,每次更新都触发地图请求 | 使用定时器限流,2 秒才刷新一次地图 |
| 每次刷新都重新下载整张图片 | 带宽浪费 | 百度地图静态图只能全量下载 | 接受此限制,但通过限流减少请求次数 |
✅ 最终稳定的工作状态
| 组件 | 状态 |
|---|---|
| 串口通信 | ✅ 稳定读取(38400 bps) |
| GPS 解析 | ✅ $GNGGA 解析正常,无效定位时返回 0 |
| GPS 线程 | ✅ 独立线程运行,无死锁 |
| 地图刷新 | ✅ 2 秒更新一次,UI 响应正常 |
| 数据流 | ✅ 串口 → GPS 解析 → 信号 → 地图刷新,链路完整 |
📌 核心教训总结
- 锁的作用域要精确:
QMutexLocker只在需要保护的最小代码块中使用,避免在持有锁时调用可能阻塞或递归获取锁的函数。 - Qt 信号槽是异步的:跨线程信号会自动排队,但发射频率过高会压垮主线程事件循环,必须限流。
- 网络请求要防重复:相同 URL 的请求可能被
QNetworkAccessManager去重或缓存,添加时间戳参数可强制刷新。 - 嵌入式开发需要边界感知:I.MX6ULL 性能有限,GPS 解析、网络请求、UI 绘制都要考虑 CPU 和内存限制。
- 分层设计让调试更容易:
SerialPort(硬件)→GpsParser(协议)→GpsWorker(线程)→BaiduMap(UI),每层职责清晰,问题定位快。 - 不要假设硬件正常:用
cat、minicom等工具先确认硬件通路,再编写软件逻辑。
这份总结覆盖了你从开始到现在的所有踩坑经历,可以作为后续项目的避坑指南。如果还有未覆盖的问题,可以随时补充。

