代码写完了,你要花多少时间测试?半小时足矣!

来源:公众号【鱼鹰谈单片机】

作者:鱼鹰Osprey

ID   :emOsprey

注意了,鱼鹰这里说的测试只是初步测试,并不是真正意义上的测试。

所谓初步测试就是,能初步达到自己写这份代码的目的,但是在后期长时间测试遇到的那些问题暂时先不管,所以这里说的半小时只是排除那些逻辑错误,基本功能错误,仅此而已。不是说你花半小时就能把这份代码进行完完全全的测试,这几乎是不可能的,因为每一份代码必须长时间经过实际测试才能真正用在产品上。

而且功能也有大有小,不是所有的功能都能很快完成测试。在这篇笔记中你不应该纠结这个时间,而应该从这篇笔记中学到一些调试的技巧,这才是鱼鹰写这篇笔记的目的。

好了,为了在半小时内完成测试,你必须掌握以下知识:

这些单片机调试方法你真的知道吗?

打了多年的单片机调试断点到底应该怎么设置?|颠覆认知

外设寄存器该这么看才对嘛

然后说说调试环境:

1、KEIL 5.x

2、STM32F103

3、JLINK调试器

当然了,扎实的C语言基础,好的代码风格也是很重要的。

待测试的代码是功能是 DMA + FIFO + 串口发送,即需要掌握 DMA + FIFO + UART + 中断系统,这份代码差不多花了一天时间才写完(不是简单的串口发送,还包含了很多东西,这里不细说),但是完成初步测试,鱼鹰只花了很少的时间,所以对于鱼鹰而言,写代码的时间占比最大,因为在写的时候,会考虑很多东西,比如多线程使用情况,中断和线程之间的相互作用等等,如果这些东西不在编写的过程就考虑清楚,那么你必然会花大量的时间去排查这些错误,鱼鹰认为这是得不偿失的。

1、忘记初始化

写完代码后,最常见的错误就是没有调用初始化函数。

鱼鹰编写代码的习惯是,每一个功能模块鱼鹰都会设计一个数据结构,用于管理这个模块,当鱼鹰需要查看这个功能的情况时,都会把这个数据拖到 Watch 窗口进行查看:

比如上一篇关于 IO 滤波的代码,就有一个数据结构,而且能用枚举的都尽量用枚举代替了,这样 KEIL 能帮我们识别每一个枚举值的含义,比如上面的 0,代表了 LEVEL_LOW,我们一看就知道是低电平,根本不用在看代码中查看这个 0 代表什么意思。

但是很多时候我们都会忘记对数据结构进行初始化,而且很多时候因为我们知道全局变量会系统默认初始化为 0,所以很多时候我们都不会对那些默认为 0 的全局变量进行显式初始化,实际上这是很不好的习惯,或者说会给程序留下一些隐患。

因为鱼鹰会设计一个配套的数据结构来管理一个功能模块,所以鱼鹰自然而然的就会设计一个函数用来对这个数据结构进行初始化,同时鱼鹰也要对所需的外设进行初始化,这是在写功能模块的时候就会很自然的就写在模块里面的。

但是因为心思都在功能的实现上,所以自然而然的,当写完了要测试时,有一些函数就会忘记放在合适的地方调用,比如串口外设的初始化、DMA的初始化、中断的初始化、数据结构的数据初始化等,所以鱼鹰除了使用 watch 窗口外,还会打开UART、DMA外设窗口(F4对外设窗口支持不是特别好,但比直接看寄存器会好很多),因为涉及到 DMA 完成中断,鱼鹰还会打开中断窗口查看数据。

当然这些窗口不会同时打开,只会在某些功能出问题了才会打开确认,不然的没有双屏的话就没法看代码了。

但是鱼鹰却不会对忘记初始化而感到自责,因为对于鱼鹰而言,这种问题很快就会查出来的,所以鱼鹰也不会懊恼自己忘记初始化,也不会告诫自己下次一定不要忘记初始化,因为这真的很容易忘记嘛,可以理解。

首先,如果因为没有调用外设初始化函数,必然无法实现你想要的功能,此时,你会自然的看有没有进行外设初始化,当你打开外设,发现外设这些寄存器的值在程序运行后没有任何变化,那么可以确认,外设没有进行初始化,或者调用外设初始化了,但是初始化函数有问题,那么你就可以针对性的对外设初始化函数进行检查了。

其次,就是你设计的数据结构没有进行初始化,如果你的程序设计得足够健壮的话,这个问题其实也很容易发现。

比如 FIFO 的缓存地址你没有进行初始化就直接拿来用了,如果是鱼鹰设计的程序,

利用前面鱼鹰给道友准备的断点设置知识点,在调试模式下,程序会自动在这个函数停止执行:

这样只要一运行没有初始化的代码,KEIL 马上就会告诉你哪里出现了问题,进而快速的解决问题。

当然前提条件是,你会对函数传入的参数进行检查,并且开启了断言功能:

从这里也可以看出,对入参进行检查是多么的有必要(标准库都对参数的合理性进行了检查),虽然多写了一些代码,但是它除了保证程序的健壮性外,也能让你在调试时快速定位问题!

并且一般来说,除非产品量产了,否则都不建议关闭这个断言功能,因为它能帮你在运行出错时快速检查出来,以防产生更严重的问题,比如破坏栈数据(这种问题特别难查)。

2、非法访问

如果你不小心没有对数据进行初始化(全局变量),那也没有关系,在鱼鹰设计的程序里面,只要程序对地址0 进行非法访问,那么程序很快会自动停止在 Hardfault_Hander 函数里面,当然这个技巧还是使用的断点设置,有了它,只要出错了,程序很快就会停止运行,此时你就可以根据鱼鹰前面的笔记快速的定位非法访问的位置了。

这也是鱼鹰为什么要为公众号的道友准备汇编C语言两个版本的原因了。

3、对外设理解不清

这个问题其实才是调试最耗时间,因为对某些知识了解不清,导致写出来的代码存在问题,那么为了解决这个问题还是需要不少时间的。

比如,鱼鹰一直以为 DMA 传输完成后, 使能位 CE 应该会自动清除的,但是在运行过程中,出现了问题,鱼鹰一查外设窗口,发现 CE 位并不为0,那么鱼鹰以此作为传输结束条件就有问题了(鱼鹰为了节省一个变量,没有用变量锁定DMA外设使用),所以只能打开F1的参考手册查看相关章节,发现并没有说正常模式下 DMA传输完成后就会自动清零 CE,只是会自动停止 DMA 传输,而 CNDTR 寄存器是等于 0 的,也就是说可以通过 CNDTR 寄存器来确定传输完成了,所以鱼鹰很快修改了这个问题。

而对于多线程的使用对中断和线程同时使用 DMA 的情况,鱼鹰在写的时候就充分考虑了,在后续的测试的过程中并没有发现问题,而对于函数可重入问题,鱼鹰也是做了充分考虑的,但是智者千虑必有一失,在测试过程还是出现一个意外的问题,所以后来为了解决这个问题,又花了半天时间修改。

所以说,代码写完之后,完成初步测试只是最基本的操作,除此之外,还要让你的代码在实际运行过程中进行长时间的充分测试,只有这样,你的代码才算经受了考验,可以真正运用在产品上了,否则没有经过充分测试的代码,只是废代码,只有参考价值,无法直接拿来使用,而即使经过了充分测试,如果你应用的环境和测试环境不同,那么同样需要经历长时间的测试,只有这样,你写的代码才算是真正融入到系统中,可以放心使用了。

推荐阅读:

KEIL下如何准确测量代码执行时间?

终极串口接收方式,极致效率

为什么说你一定要掌握 KEIL 调试方法?

延时功能进化论(合集)

指针,很难吗?| 解析指针的过程与意义(一)

如何写一个健壮且高效的串口接收程序?

KIEL 调试那些事儿之窗口展示——变量(二)

打了多年的单片机调试断点到底应该怎么设置?| 颠覆认知

-THE END-


如果对你有帮助,记得转发分享哦

微信公众号「鱼鹰谈单片机

每周一更单片机知识

长按后前往图中包含的公众号关注

鱼鹰,一个被嵌入式耽误的畅销书作家

个人微信「EmbeddedOsprey

长按后打开对方的名片关注