背景(一)
寒假里做了一个灯带控制器:
理想情况下我应该在一个星期内完成这个项目,但实际上它耗费了我几乎一整个寒假,因为涉及到很多未曾尝试的方案。在这种不是很赶时间的、可以自定目标、*发挥的项目中,我喜欢这么做。
简要介绍一下这个项目。硬件部分:
-
12V直流电源供电,开关降压到4V,LDO降压到3.3V,三路供电;
-
使用STM32G030KBT6单片机,基于Cortex-M0+;
-
12V灯带用异步降压驱动;
-
WS2812B灯带的数据用SPI信号加上一定的逻辑运算生成;
-
OLED屏通过I²C连接单片机;
-
无定位旋转编码器连接定时器的两个通道。
软件部分:
-
用多项式拟合把ADC读到的值转换成温度;
-
指令与数据调度、两级流水线式OLED更新策略;
-
完全自主设计的简单GUI,包括绘图与控制逻辑。
大约一半时间花在两种灯带的方案验证上,我制作了两块PCB才最终确定以上硬件方案;另有一部分时间花在STM32和HAL的调试上,但这本来就在我的STM32学习计划中;剩下的主要是OLED和GUI,以及写不出来时的摸鱼行为。
我赶在去学校当天的上午完成了项目,在砍掉一点完成度的前提下。有那么几个小时,我很后悔把整个寒假都花在了这个项目上,没有学该学的数学、写该写的博客。但是现在回想起来,这个项目让我对单片机开发有了新的认识。
背景(二)
曾经AVR单片机教程唯一的读者要开始独立做项目了,这是对我文章质量的一次考验。从我们之间的交流来看,我没有通过这次考验——
-
我说从供电开始思考:通常USB可以提供5V1A,如果需要5V2A,就应该考虑从12V1A转换。她问如何转换,于是我给她介绍了降压转换器。那么如何确定电流呢?主要耗电器件是32个LED。按我的思路,也可以说是绝大多数业内人士的思路,每个灯不会超过20mA,加起来640mA,其余器件耗电忽略,1A肯定是够的。而她的思路是,算出每个LED的等效电阻,并联起来,再计算电流。她又问,如果电源是5V2A,但负载只有5V1A,会烧坏吗?
-
然后决定用什么灯。两年半以前我送给她一个礼物,用32个LED组成一颗心,灯是红蓝交错的。她想做得比这个高级一点,用32个RGB灯,但这么多灯的连接是个问题,以及RGB三种颜色之间的亮度匹配。又想到WS2812B,它在功能上很强,但是控制稍微麻烦一点。我提示她可以用PWM来生成控制信号,她又搞不清这里的PWM和PWM呼吸灯的区别。
-
可能是因为在第二期里学过OLED屏和u8g2,在电赛中又用过一遍,又收到了带OLED屏的礼物,她觉得OLED屏很简单,想在项目中也放一块。
暂时不作评价,继续说事。后来又有两人从博客园后台联系我,以成本价拿到了开发板,成为了教程的读者(至此我已经没有多余的开发板了)。一位读者刚开始学C,他常常问我两类我不希望他问的问题(请这位读者不要生气):一类是与单片机无关的纯C语言问题,另一类是与教程进度不符的、我想隐藏的实现细节问题。
他们让我开始反思教程的内容是否充足、顺序编排是否合理。
回顾(一)
他们问我的这些问题,很多也是我曾经困惑的。
预初的那个暑假,我正式学习了C语言。此后,我想写一个C程序来加密文件,把若干字节作为一组处理一下,写进新的文件,就算是加密了。文件长度不一定是整组的,最后一组可能要加入几个空的字节,这种现在看来很基础的控制逻辑,我当时想了很久都写不出来。
我也长期以来不知道电池充电的电流是如何控制的,在我看来把5V电压加到3.7V锂电池上根本不可控啊!后来才知道降压、参考电流、反馈控制之类的。某些营销号还说5V2A充电器给5V1A手机充电会造成损坏,也难怪非专业人士容易受误导了。
高一的那个暑假,我完成了第一个像样的项目,就是前面提到的那颗心。32个LED分别串联470Ω排阻,接到4片级联的74HC595上,用GPIO驱动。放到现在,如果不改变设计目标,我会选择8*4动态扫描的连接方式,还可能用上一片TM16xx来驱动这个阵列。
按下按键切换模式,这个很简单的功能在当时实现得非常乱:先定义一个宏,每次如果按下按键就return
,然后在循环中多次调用它。现在我会在定时器中断里处理按键,具体来讲还有多种方法。
这些不懂的问题、不好的实现,都是经验。有些不好的实现,比如上面这个按键,不值得尝试,所以我会在回答相关问题时提供我认为合理的方法。类似的有很多,所以我一直尽力回答每个问题,帮人避雷,也好让我审视一下自己的方法。
回顾(二)
AVR单片机教程的一期介绍了简单外设的用法。我假设读者有良好的C语言基础,所以没有把基础单片机和基础C语言结合起来讲,而是在前几讲的作业题中对读者的C功底提出了较高的要求。针对当时读者的问题,我专门写了一篇元教程,强调了要广泛地了解相关知识。
从按键开始我加快了节奏,否则按照一个LED都能写4篇的速度我现在大概刚刚讲到LCD1602。原计划一期结束后还有大量内容,但是我当时已经不大想写了,所以挑了点必要的组成了第二期的6讲。
现在有读者反映第二期太难,其实不是太难而是两期之间有明显断层。在与读者交流的过程中,我还发现有很多我强调过的点还强调得不够:有些是我没讲清楚,有些是读者真的做不到,比如完成作业——我把很多关键的、实际应用中需要的问题放在了作业中,它们容易被忽略,至少没有被足够严肃地对待。
这些问题,以及上面带有责怪情感的读者提问,都是我的失职。
展望
我决定开始写AVR单片机教程第三期。
首先是查漏补缺,这还得再细分几类:
-
外设。I²C和定时器输入在前两期的正文中没有涉及到,会在第三期补上。
-
语言。我不应假设读者有良好的C基础,但我真的没法面对完完全全的新手,所以我将补充一些非单片机编程中不常用的特性,以及高级但实用的用法。
第三期的很多内容用C++讲起来会更加方便,所以我会简单介绍那些需要用的C++语法,它们也是我在自己项目中常用的C++功能子集。
-
作业。如前所述,很多重要的内容都在作业里,我将提供解题思路和优化建议。
然后是结构:
-
与C++绑定起来的对象的概念,包括基于对象与面向对象;
-
第二期最后提到的事件驱动范式,原本是单独的一期,将成为第三期的一部分;
-
有些硬件结构可以启发设计(有的可能最初来自软件),包括双缓冲、FIFO、状态机、流水线等;
-
大型程序和库的结构。
此外,单片机项目的共同问题,比如供电、制板、测试等,也会集中讲解一下。
由此可见,这一期“AVR单片机教程”实际上可以把前三个字去掉,尽管我们还是基于AVR平台讲解。
我设计了新的开发板,与第一版相比缩小了尺寸,去掉了部分扩展资源,使它更加核心。扩展资源被移动到了单独的模块上,多个模块提供不同方向的器件。
原来的开发板仍然提供支持。新开发板有开放小批量订购的计划,目前还在原型设计阶段。因此,我将从不依赖于AVR的C和C++开始。
希望第三期能为读者建立完善的概念,能让读者学会必要的单片机编程方法,在例子中积累经验,独立设计并制作出与文首项目难度相当的单片机系统。