遇到的bug汇总-初学者的Bug List!

作为一个能力不足的人,在日常生活中,总会有那么一些bug让我难以接受。我将其中的部分汇总如下,聊以慰藉。

本文理论上是长期更新的,不要嫌文章短(

时间流逝造成的未知错误

PowerToys 的bug

作为Vim用户,交换Caps Lock和Esc键是常规操作了。为了体现Geek风范,我使用了 MicroSoft维护的PowerToys来实现键盘重映射。

但是,我没有想到的是,当前版本的PowerToys存在假死的情况。假死过后,会强制软件激活Caps Lock,造成我编码的误操作。令人惊奇的是,激活的Caps Lock,我需要按两次才能取消。第一次是硬件激活,第二次是硬件取消。可以算是步调不一致了。

“-target”? 你从哪里来?

先前在调试clang的时候,在系统变量中添加了CXXFLAGS 和它的值 -target XXXX。后来因为clang 始终无法测试成功,便卸载了clang。但是,系统变量没有被删除。

后来,我再调试CMake的时候,无论怎么make都会显示无法通过编译器测试,显示Uknown command line "-target"

我怎么想,都觉得不可思议。明明g++使用格式为--target,为什么CMake会这么不合常理地使用这样的编译选项,反而告诉我无法编译呢?

可想而知,耗费三个小时后,我灵光一闪,删除了系统变量中的CXXFLAGS。编译通过了。

所以clang和g++读取同一个系统变量,这种原因我不得而知。

关我Anaconda何事?

首次使用VSCode编辑Qt6生成的CMake工程时,Intelligence总是提示错误的引用。仔细一看,include到了Anaconda/Library/Qt里面的文件。这就问题很大了。

思索再三,我发现VSCode的C/C++扩展中对include path的定义,是先在系统环境变量中搜索的。那么,Anaconda的路径在Qt之前,便会直接从Anaconda中include,这就产生了错误。

将环境变量的顺序调整了之后,问题不再复现。

编程技术造成的错误

C语言篇

忘记加括号

1
PWM_SetPWM_ByDutyCycle(SteerNumber, (uint16_t)(angle * 2.0 / 180 + 0.5) / 20 * 4096);

这样的代码会将任何angle输入,变成0的。因为uint16_t的作用对象错了,应该是整个表达式的值。表达式的类型是double,在进行计算的时候精度损失可以忽略。但是uint16_t位置不对,提前变换成了整型,这不是胡闹吗?

正确修改如下。

1
PWM_SetPWM_ByDutyCycle(SteerNumber, (uint16_t)((angle * 2.0 / 180 + 0.5) / 20 * 4096));

volatile?并行计算需要好好学习

多次读取一个变化的值,读出来的竟然是相同的。这是正常现象,编译器做出来的优化,只需要加上volatile关键词即可。问题的根本在于并行计算理解不够,有待更加深入学习并行计算理论了。

造轮子是需要水平的,溢出是需要检查的

课程要求我手动造轮子。可惜能力不足,出现了内存溢出。但是我怎么检查,怎么测试,都不可能出现溢出啊?最后发现,是没有赋初始值造成的数据错乱,而不是指向了释放的内存空间。所以,造轮子需谨慎,我仍然是一个菜鸡。

CMake篇

“默认化的默认构造函数不能为constexpr”

首次尝试利用Qt Creator生成mingw工程并利用Visual Studio 2022调试时,就连最基本的代码都无法编译通过。报错就是标题内容,尽管是自动生成的代码。仔细检查后 发现,Visual Studio读取CMake文件时,没有读取到正确的CMake生成器,默认使用了Ninja,这个我可不会。

在VS的某个界面中,修改CMake工程的生成器,为Unix Makefiles,这样就编译通过了。

C++篇

vector数据存储在哪

在阅读OpenGL教程时,发现了如下语句。

1
2
3
vector<Vertex> vertices;
// ... lots of code
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);

这时发现vector被当成了数组使用。但是记得vector是特殊的结构,不能直接读取才对,怎么回事?

这是因为vector本身的存储结构是特殊的,但是对于数据的存储位置来说,数据始终是连续的。这也保证了vector是可以随机读写的。

1
2
3
4
std::vector<int> a = {0, 1, 2, 3, 4, 5};
std::cout << sizeof(a) << std::endl; // 24 Bytes
std::cout << a[0] << std::endl; // 0
std::cout << *((int *)&(a[0])+1) << std::endl; // 1,是索引为1的元素,证明存储是连续的。

通病

无后效性提醒

在一次编程实践中,我发现系统出现了不应该的异常抖动。代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void Motor_Decode(MotorInput_t x, MotorInput_t y, MotorInput_t w) {

// 反向的速度才是正方向
x = -x;
y = -y;

// 保存速度结果,便于调试运动状态
Motor_InputInstance.x = x;
Motor_InputInstance.y = y;
Motor_InputInstance.w = w;

Motor_TargetSpeed[0] = MotorDecodeOutputFix(Motor_InputInstance.Kx * x +
Motor_InputInstance.Ky * y +
Motor_InputInstance.Kw * w);
Motor_TargetSpeed[1] = MotorDecodeOutputFix(-Motor_InputInstance.Kx * x +
Motor_InputInstance.Ky * y -
Motor_InputInstance.Kw * w);
Motor_TargetSpeed[2] = MotorDecodeOutputFix(-Motor_InputInstance.Kx * x +
Motor_InputInstance.Ky * y +
Motor_InputInstance.Kw * w);
Motor_TargetSpeed[3] = MotorDecodeOutputFix(Motor_InputInstance.Kx * x +
Motor_InputInstance.Ky * y -
Motor_InputInstance.Kw * w);
}

测试的时候,出现了异常的速度抖动,方向频繁切换。但是,使用如下代码就可以解决。

1
2
Motor_Decode(new_x, new_y, new_z);
Motor_Decode(Motor_InputInstace.x, Motor_InputInstace.y, Motor_InputInstace.w);

需要我写两遍?

不,真正的原因是,代码中的x = -x; y = -y;具有后效性,将速度反向放在这个地方没有任何道理,只会造成相同调用条件产生反向的输出结果。将这两行代码删除后,问题得到解决。

也许单看这个解释读者仍然不能理解。那么看下面的一个使用案例。

1
2
3
4
5
__STATIC_FORCEINLINE void Motor_AddX(MotorInput_t x) {
Motor_InputInstance.x += x;
Motor_Decode(Motor_InputInstance.x, Motor_InputInstance.y,
Motor_InputInstance.w);
}

周期调用这个函数会发生什么?流程大概是这样的

1
2
3
4
5
6
7
8
Motro_InputInstance.x == X0
Motro_InputInstance.x == X0 + x
Motro_InputInstance.x == - X0 - x
// 第一遍执行完毕
Motro_InputInstance.x == - X0 - x
Motro_InputInstance.x == - X0
Motro_InputInstance.x == X0
// 第二遍执行完毕

看到了吗?它的数据在震荡!

硬件设计中出现的问题

程序设计

中断函数提高速度

中断函数中一定不能写延时,中断函数的速度也需要尽可能提高。如果出现了中断重叠,可能会产生意料之外的错误。例如部分外设频繁死机之类,不容易检查的。

外设错误调用

STM32中外设的使用需要提前配置。不仅如此,如果外设配置了某一种调用模式,而你却采用了另一种模式调用,也会出现严重问题,比如,卡死HardFault。举个例子,如果你外设没有配置DMA,或者配置DMA之后使用了阻塞模式,那么,当你下一次尝试使用DMA模式时,会进入HardFault

接线错误

“target dll canceled”

通常来说,STM32的调试SWD位于PA13,PA14。由于PCB绘制过程中,出现了与USART1接口相邻的情况,接线同学在接线时,误将串口线接在了PA14上。所以,无法进行任何烧写动作了。任何操作都会显示无法连接。

重新将串口线接在USART1的接线柱上,问题得到解决。

异常现象

未知重启

2021年10月1日的一次调试中,STM32芯片出现了多次的异常重启。经过分析,一共有两种原因,二者交替触发,造成问题难以定位。这两种原因分别是内存泄漏单片机挂起重启,和电压不稳芯片重启。

正常情况下,内存泄漏应该进入HardFault中断中阻塞全部程序。但本次实验中,STM32在遍历了全部内存空间后,产生了某种异常,形成了软件重启。表现为调试串口信号从起始位置重新开始。

在之后的实验中,出现了杜邦线端口松动,电压断供的情况。表现为调试串口信号乱码,且代码从头开始执行。

本人根据调试串口信号的复位信息得到了上述两种复位方式。在修复了相应漏洞后,程序恢复正常,

晶振不起振

目前遇到的晶振无法起振问题,主要有三种原因。

第一种原因是多次拆焊造成晶振内部结构受损。第二种原因是QFN封装造成的焊盘未连接。第三种原因是气温问题,造成芯片HSI和HSE偏差过大,芯片自检不通过无法起振,或者晶振无法起振。

芯片不正常使用

LDO篇

通常在使用LDO的时候,会用到一颗进行5V->3.3V的芯片。而在调试的时候,通常会使用3.3V供电,这样MCU通电工作,而其他芯片可以保持较低的水平。但是有的时候会忘记这一点,从而调试时发现其他芯片不能正常工作。比如轨降较大的运放,就会出现直接饱和的现象。

运放篇

通常买的的便宜运放都不是轨到轨的。在LDO篇中提到了,调试供电情况下,5V获得的反相电压通常只有2.5V,那么放到运放上,饱和电压就只有1.3V了。这显然会产生很大的测量误差。应当铭记这一点。

官方出现的问题

STM32CubeMX已知问题

CubeMX生成的HAL库文件出现的问题,多半是顺序问题。

这里推荐今后使用assert,方便排查ST自己的问题。因为我已经多次被ST坑到。

I2C异常现象

代码

1
__HAL_RCC_I2C2_CLK_ENABLE();

需要在I2C初始化之前完成。而生成的代码中,该函数通常在函数末尾。这就会造成初始化失败,无法通信。

DMA初始化失败

代码

1
MX_DMA_Init();

必须在调用DMA的外设初始化之前完成。而CubeMX的初始化顺序和CubeMX配置顺序有关,而不是逻辑顺序或者依赖关系。在这一点上,早在20年便有人指出这样的错误。直到今天(2022年3月13日)ST依然没有改正这个错误。