2026年电赛B题------无源电流表攻略(测量电路篇)
本文提供一种能解决这个赛题的系统设计,能在 0.08A 下启动工作,测量精度达到了 0.5%,视频后续会上传B站(待填坑)
项目代码与标定数据已上传 GitHub: https://github.com/Kreeli/Sourceless_Ammeter_G3507
文前的絮絮叨叨
二十多天以前,电赛省赛开赛,本人作为老师摇来的援兵,给同学们做指导。我负责带 B 题的同学,提供一些基础思路以及器件使用的建议。
比赛过程真的很令人难忘:有尝试了好几套方案拿下省一的组,也有到电子市场给整个实验室买到了优质磁环的雷锋,有最后一天突发奇想发明出优秀电路的发明家组,当然也有最后一天才解决问题、最后一天烧掉系统的可惜的组,还有队友不给力拉慢进度的组。奈何本人仅仅是一员助教,或者说是指导,我可以为提出要求的同学提供建议、提供器材,但我不便,或者说是不愿意给大家提供非常实质性的帮助。我更希望比赛考验到大家、让大家享受到比赛,不希望最后大家认为学长偏袒了哪个组,或者学长是强外援、大家无脑照着做,让电赛演变成人脉大赛。因此比赛过程中,我始终没有拿着完整的电路给所有人参考。
本人在这场比赛中算是全程陪跑,包括测评。同学们的测评结果其实并不理想,指标做不全。不过省内的参赛队伍好像都做得不是很好,之前在 xhs 上看到这道题在本省的最优队伍,指标也没有做全,他们参考的是 TI 的一个无源电流表设计,也是我此前看过的。比赛结束后的几天,也就是测评结束、8 月 6 号之后,本着尽善尽美,或者是兴趣使然,也或者是想给大家留下优秀方案、做一回活雷锋,我也开始着手去完成这道题目的优解方案。大概用了四个下午,每天 6 小时,一个人就把电路 A 完成了,甚至代码都是自己手写的。本来说等蓝牙到了做一套完整方案,结果前几天把蓝牙模块搞坏了,焊盘都脱了,而且功耗不满足要求,所以一拖再拖。马上本人要参加另外一个比赛了,来不及做这个东西了。手上很多记录的图已经丢失,系统也从测试系统拆掉了,连忙把半成品写一篇博客,免得以后再恢复测试、留存资料太麻烦(现在已经缺了很多图片了,抱歉)。
题目解读
回归正题,粗略看了一下三道仪器题:G 题是比较传统的波形显示,感觉很简单,采个样、频谱算一算,然后弄个波形显示,搞个单片机就能做。剩下的两个,B 题和 F 题,非常猎奇。
先说说跟本文无关的 F 题。乍一看,是用锁相环锁相得到整数频率的信号,做李萨如图形;到了最后一问话锋一转,要求抛去硬件连接,用摄像头盯着示波器显示稳定的李萨如图形,一问占 40 分总值。这题直让人摇头,这种题目有什么意义,简直是为了出题而出题。
看到 B 题,无源电流表,这题有意思。我们先贴出题目的图片:
图 1 题目要求(1)
图 2 题目要求(2)
图 3 题目要求(3)
题目的意思,就是用一个电流互感器,把原边电流感应一部分能量到副边:一方面用这个信号测量原边电流的有效值,一方面用感应的能量为整个测量电路供电。这就很苛刻,题目要求测量电路功耗越低越好、启动时间越短越好,测量精度在 0.5% 以内。0.5% 的精度还是挺令人发怵的,本人做基础测量题目的时候,一般都是以 1% 的精度做基本盘,更高的精度往往需要更多的操作。但是考虑到本题只需要测量一个总电流的有效值,0.5% 的精度似乎也不用太担心。
值得注意的是,这个线圈好像得用手绕。虽然题目没有提及互感器的型号,也没有做限制,但是图里面实实在在画的是一个圆圈,搞不好得手绕一个互感器。如果能用现成的,能省不少时间,可惜省不得,怕不合要求。
除了测量电路以外,还要做一个无线模块和一个读表器。按照题目的意思,读表器可以用手机(但是要把手机封箱在作品箱里)。那要是我做的话,肯定选择用手机或者平板了,毕竟现在 Vibe coding 这么发达,下个开发平台,让 AI 写个代码就是几句话的事情。
至于无线模块,算是一个令人头疼的东西,因为需要考虑到发射消耗的功率。如果用一些高功耗的无线模块,比如 WiFi6 或者经典的蓝牙,功耗高,互感器感应的能量肯定顶不住。我们稍微算一笔账:测量电路肯定要有一个 MSPM0 系列的低功耗单片机,还得有一个显示屏,加上一堆器件,又要加上一个无线模块,其实功耗控制还是很麻烦的。
抛开这些不谈,绕线圈真是一个麻烦活。我绕了三次,两次失败了,最后终于找到秘诀,4 个小时才绕完:
图 4 绕制完成的互感器线圈
其实匝数并不重要,因为能量毕竟是守恒的,我们只要适量地多绕,保证磁场能尽量被包裹住就行了。匝数太大了,反而会导致互感器阻抗过大,影响原边的电流。我估计是绕了几百匝,多少我也不清楚,反正这个线圈 90% 都被我用副边包裹住了。
互感器特性
不同于大部分解题思路直接把最终的结果呈递给读者,我这里将我的思考过程也放在后续的章节,让大家能理解电路为什么这么设计。
首先,无源电流表,一定要能测电流,一定要能正常工作,这就引出了这道题两个最重要的问题:测量和电源管理。这都是绕不开互感器性质的。本人不是电力电子的科班,对互感器、变压器的认知仅仅停留在高中物理课的水平,所以先给专业能力比较强的读者道个歉。我们需要用比较直觉的、带着实际测量图的方式进行讲解。
高中所学到的变压器,大家只记得两个公式,不考虑漏磁、铁损之类的损耗:n0:n1 = u0:u1,n0×I0 = n1×I1。实际上,这两个公式都是有条件的,首先就是不能磁饱和。这两个公式其实描述的意思是,输入变压器的能量 = 变压器输出的能量。测线圈的过程中,很多同学都干了一件傻事:把副边的输出直接接在示波器上,然后拿着这个波形问,学长学长,这个感应出来的波形怎么这么奇怪呀?如下图:
图 5 副边空载(直接接示波器探头)时的输出波形
输出波形有几十伏的峰峰值。很明显,由于负载太轻,或者说是负载的阻抗太大(示波器探头 10MΩ 13pF),导致负载电流太小,副边电流都流到了励磁线圈,使能量很多流向了磁场,消耗在了铁芯中,这就是常说的铁损、铜损。互感器感应出来的能量释放不出来,线圈里面的磁场能量饱和,最后就会导致这种波形。
这里贴一张互感器副边的简化实际图,让大家理解一下副边的电流流向与能量去向:
图 6 互感器副边简化等效电路
其中,ilm 就是流向励磁线圈的励磁电流。电流源输出的总电流实际上还是满足安匝平衡的,但是由于流向负载的电流太小,剩下的电流都用来励磁了,线圈里面的磁场一大就容易饱和,影响感应效果,这样输出的电压波形就会失真。
虽然互感器能输出的总功率就这么大,但安全问题还是令人担心。于是我要求大家测试互感器的时候,给互感器接一个 200Ω 的负载,让示波器看到的电阻上的电压能够尽量满足安匝平衡,这样能够检查互感器输出正不正常。
下面再贴一张加了 200Ω 负载的输出波形。可以看到电压很小,但是波形很漂亮,接近市电的实际情况。用这种小电压给系统供电显然不现实,但是很适合测量信号:
图 7 副边接 200Ω 负载后的输出波形
没想到,就因为这样的一个提醒,犯了大错------我没有提醒他们什么时候应该拆掉它。这让有的同学直到倒数第二天晚上还没有拆掉这个电阻,以为这个电阻是必须的,更有甚者认为我在坑他们,太让我伤心了💔。
这里就需要注意到互感器两个非常重要的性质了,这两个性质是后续系统设计的基石:
- 负载接近开路的时候,电压波形失真,但是电压很大;
- 负载很重、接近短路的时候,很大程度上可以认为流过负载的电流和原边电流满足安匝平衡
n0×I0 = n1×IR(IR 为流过负载的电流)。
测量电路和供电电路设计
如果是分开设计系统供电电路和互感器测量电路,这个问题相当简单,非常简单,超级简单,如下图:
供电电路:全波整流 + Buck 电路降压
图 8 供电电路方案:全波整流 + Buck 降压
测量电路:直接接负载,加运放输给 ADC
图 9 测量电路方案:负载 + 运放 + ADC
但是把两个功能直接拼在一起,就会导致电压太小、电容充不进电,这显然是不可行的:就算把电阻值选大,无论把电阻放在线圈输出的位置,还是整流桥输出的位置,如果电容充电和信号测量无法分开,这两者必然会相互影响。
那么就有同学动歪脑筋了:我直接绕两个副边,一个匝数大,负责供电;一个匝数小,负责测量;两个副边共地,不就合成一个系统了吗?我一开始也是这么想的,但是实际用高中的计算方法算了一下,发现可行性很差。下面是我当时的计算过程:
图 10 双副边方案的手写推导
如果不愿意看,我可以大概地讲讲结论:副边 1 的电流和副边 2 的电流会相互影响,电流越大影响越大,匝数越大影响越大。
后面有一组同学尝试了一下双副边的方法,确实发现,绕了第二个副边以后,供电副边充不进电了(悲,学长也帮不上你们什么忙)。
而且,第二个副边的电压不好控制,因为是有正有负的信号。
当时,为了解决供电和测量的问题,整个实验室都在想办法。提到最多的办法就是 MOS 管,我本身就不太会用 MOS 管,所以我当时摆烂了,让大家自己去想。结果大家想出来的办法千奇百怪,有的甚至比较猎奇,甚至有人发明出了继电器控制(小功耗继电器)。
P.S.:我当时认为这样不可行,功耗太大了,但是实际证明了他们是对的,最后拿了省一。他们很强,我认可。
比赛结束之后,我自己想了一下,没想到很快就想到了一个解决办法(捂脸哭,对不起,相信我的同学们)。
如下图,整流桥输出通过一个单向的二极管给电容充电,在二极管前面加上一个 100Ω 电阻,再经过一个 NMOS 管,用一个 5V 开漏引脚控制通断:
图 11 充电/测量切换电路(初版)
当 PA0 输出低电平(Vgs = 0),MOS 管关闭,电容充电。这个时候由于负载接近无穷大,给电容充电的电压非常大,电容不接负载的情况下,能充到上十伏。我自己拿的一个 1mF、100V 耐压的电容,0.1A 下能充到接近 20V。我们把这个状态命名为充电状态。
当 PA0 输出高电平(Vgs = 5V),MOS 管导通。这个时候,在互感器副边的视角下看,负载是一个整流桥、一个很小的到地的 100Ω 电阻、还有一个二极管到充了很多电的电容。那么基本上,这个负载的阻抗仅仅由 100Ω 的电阻和二极管决定。这个时候,回想一下上节我们提到的互感器副边接小电阻的输出波形,只有 mV 到几伏的大小。如果电容已经有 10V 了,电容的电根本不能通过二极管流回来,那么电容就跟前面的电路断开了!也就是我们想要的测量和供电分开!我们直接测量电阻两边的电压就可以测电流了!
那么就会有细心的读者提出下面的问题:
- 你用整流桥的输出给电阻,二极管的压降不考虑了?精度怎么办?
- 怎么做一个合适的启动电路,让电容冲到 10V 电路才正常工作?
- 100Ω 电阻一端在 Vgs = 0 的时候,电压高达十几伏甚至几十伏,把调理电路烧坏了怎么办?
本节,我们先说明问题 1,这实际上也与互感器的特性息息相关。
我们假设互感器在某一时间,输出的电压小于二极管的导通电压,流过二极管的电流很小。那么此时,励磁电流增大,磁场变强,磁通量变大,使得接下来感应出来的电压/电流更大,直至导通二极管。
用另一种思考方式,主观一点,从互感器性质的视角分析:在互感器看来,二极管没导通的时候,是阻抗非常大的负载。那么根据互感器的特性,对于阻抗大的负载,输出电压很大,所以输出电压会急剧抬高,直至导通二极管。这时,后面的负载对于互感器而言就是阻抗非常小的负载,波形不会急剧增大,按照安匝平衡给负载输出电流。
用一张图表明互感器两边电压与采样电阻两边电压的波形。这个图我没有留存示波器的实测图了,但是我是确实测过的,和我的图一致。这里没有实测图可能有点缺乏真实性,抱歉:
图 12 互感器输出电压与采样电阻电压波形示意
可以注意到,导通电压造成的死区是不会出现在采样电阻两端的,因为互感器直接"跳过"了这一部分。
整体系统设计
充电/测量切换电路
这一节,我们说明包括单片机在内的整个测量系统的设计。
上一节提到的问题 3,实际称不上问题,我们只需要对电路做一个小小改动,就能够完美解决:
图 13 改进后的充电/测量切换电路
直接把 NMOS 管和采样电阻换一个位置,未导通情况下,S 端电压为 0,测量电路不会接触高压;导通之后,采集到小电压。
下面介绍一下设计这个电路的重要参数------采样电阻阻值。如果 Vg 足够大,比如 5V,由于安匝平衡,流过电阻的电流只和原边电流及匝数比有关。我们选择尽可能小的采样电阻阻值,使 Vs 的电压尽可能小一点(注意不要让 Vgs 小于导通电压,否则电压太大、Vgs 不足无法导通,会导致采样电阻两端的波形削顶),这样就能够实现比较稳定的导通。
但是,实际使用上,由于电路存在瞬态响应,波形是存在波动的。我用示波器抓过波形,导通一瞬间,电阻两边的波形会震荡一会儿,然后稳定。如果要解决这个问题,就需要特殊设计这个电路。考虑到低功耗和其他因素,我就直接让单片机休眠 400ms 之后再开始采集电压了。
不过,本人由于是初次设计,一开始并没有用这个电路,而是用了另外一种多余的电路:
图 14 初版冗余电路:多加一个 MOS 管控制采样
多加了一个 MOS 管控制采样的开启,一开始没注意到简单的修改方法,所以用了这种很唐的方法。
需要注意的是,无论是哪种电路,最好都在 OPA/ADC 的输入端接上两个二极管防止烧坏。测试过程中,我发现开关切换的一瞬间,总是出现很高的正尖峰和很低的负尖峰,所以最佳做法是在采样电阻上接一个到 3V3 的二极管和一个到 GND 的二极管,保护电路。
接下来解决第二个问题:怎么确定电路的启动电压。这就不得不请出我心目中的第一 Buck 同步整流器芯片 SY8303 了。一方面,我们用这个芯片完成电容高电压到 5V 稳定电压的转换;另一方面,我们用这个芯片的使能 EN 引脚完成电路的阈值启动。
图 15 SY8303 同步 Buck 模块
我们设计的电路如下(我是拿现成的模块爆改的):
图 16 基于 SY8303 的供电与阈值启动电路
如图,实测 SY8303 从关断到启动的最小电压是 1.2V 左右,从启动到关断的最大电压是 0.9V 左右。那么我们设置一个分压网络,把分压的电压作为 SY8303 的使能引脚输入。这样在 1.2V 掉到 0.9V 的期间,Buck 电路还是能正常工作的,5V 供电电压不会掉,如果我们能保证电容的电压一直在这个范围,系统就能稳定工作。
有的读者可能会想直接用这个引脚控制充电/测量切换,但是这是不合理的:因为本题需要用无线模块,模块一断电,就要再重启、再连接,因此 Buck 电路需要持续工作,才能保证系统稳定运行。所以我们这里只用这个电路作为系统初次启动的设置,而不是后面的充电/测量切换控制。
那么有的读者可能就会着急了:你弄了这么久,还是没有说明白,到底怎么进行充电/测量切换?因为无论是 MOS 管还是测量,都是需要单片机工作的,单片机功耗一大,电容就会充不进电。OK,到了现在,我们已经做好了所有的铺垫,现在轮到 MSPM0G3507 闪亮登场了。
首先我们需要明确一点:我们的电路里面,电容能不能充进电,影响它的就三个因素:
- 采样 MOS 管有没有打开------如果打开了,电容只能放电;
- MOS 管没打开的时候,电容后面的负载重不重,或者说功耗大不大------如果功耗不大,或者说没有功耗,那么 SY8303 的高效率能保证电容仍然充得进电;
- 直接用电容电压供电的其他负载的功耗大不大。
假设 MOS 管不开启,我们想让电容充电,同时 Buck 电路还能工作,就只能降低 MCU 和其他电路的功耗。我们刚才用到的 EN 引脚分压网络是 1.12MΩ,基本没有功耗,所以我们接下来从 MCU 入手。
众所周知,TI 的 MSPM0L1306、MSPM0G3507 都是低功耗的翘楚。本人实测:把核心板所有的耗电器件拆掉,只留下 LDO 和 MCU,主频调到 2.25MHz,在 STOP2 和 RUN 模式间切换,用 3V3 供电(也好像是 5V 带 LDO 的情况,我记不清了),电流如下:
图 17 MSPM0 在 STOP2/RUN 切换下的实测电流
这甚至还不是最低的,我用了一次 Standby 模式,5V 供电,电流能降到 0.087uA。这里没有图片留存了,但是这是实测的,实事求是,没有虚假。
这种情况下,单片机基本就跟砖一样,没什么功耗。测试发现,睡眠情况下,用 SY8303 带一个单片机,能从 0.08A 下启动,非常猛!
那么接下来介绍如何用 MCU 控制电路在充电/测量下切换,实现超低电流启动和超高精度测量。
MCU 与外围电路
前面铺垫得很明显了:充电状态当然是单片机和所有外设美美休眠,电容美美充电;测量状态是,打开 MOS 管 400ms,等待波形稳定(忘记为什么要等待 400ms 的读者,回到前面介绍 MOS 管电路那里再看看),然后采样。有效值的计算为了准确,我们当然需要多测几个周期、多测一些点数,我选择了采样 10 个周期,一个周期 40 点,用 ADC + DMA 负责操作。DMA 会消耗 0.2s,期间 MCU 的 CPU 休眠。
我们这里需要考虑到测量的范围 0.1A 到 2A(保证精度的范围),还需要检测到电流过大,所以实际上要测到 2.5A。只考虑保证精度的范围,我们有 20 倍动态范围,对于片上 12bit ADC 是足够的。但是!我们要卷精度,小信号就需要被放大。正好,全波整流之后电压都是正的,所以我们直接用片上的 OPA 简单配置成跟随器和放大器,然后给每个电流设置增益,就能做程控测量了,结果也是非常准确呀!(测哥狂喜)
图 18 片内 OPA 程控增益配置
有的同学可能会灵机一动:可以配两个数据缓冲区,每次唤醒只计算上一次缓冲区的数据,这样在 ADC 采样、等待 DMA 的时候就可以计算,能够省功耗。但实际使用下来,系统的体验感很差,而且电网是会波动的,有时候上一次采样得到的数据和这次实际的数据有细微的差别,影响标定,总之就是标定不方便。
本题需要一个屏幕显示。我手上找了一圈,也没有低功耗的屏幕,比如万用表那样的屏幕,或者墨水屏(看到有华科的队伍用了墨水屏)。没辙,只能和当时的参赛队用一样的条件,弄个 OLED。OLED 的功耗其实不小,为了控制 OLED 不浪费功耗,我直接用一个高驱动引脚给 OLED 供电,不用的时候直接关断;然后用 2.25MHz 的 SPI 和 OLED 快速通信,通信结束之后立马给 SPI 断电,显示 0.5s 之后屏幕关掉,把省电做到极致。这样的操作后面还有很多,我不一一介绍。UART、SPI、TIMER、比较器、ADC、OPA,这些片上的外设我都是用完就直接断电,省功耗。
单片机的休眠唤醒采用 Standby 模式,功耗超低,但是只有少量外设能够唤醒,我用的是 TimerG0 作为唤醒源,周期 4s。
但是 4s 似乎太长了:小电流的情况下刚刚好,大电流的情况下能量充足,没必要这样,于是我开始了操作。
我配置了一个片上的比较器检测电容的电压,如图:
图 19 片内比较器检测电容电压
G3507 的比较器在休眠模式下依然能工作,这里选择它的超低功耗模式。我把定时器调成 1.5s 中断一次,然后在中断里检测比较器输出是否为高电平:如果是高电平就唤醒 MCU,反之继续睡觉。调节之后,单片机显示的数据方便看了许多。同时还有一个好处:我们可以调节负端的 DAC8 输出,切换唤醒电平。这样后续加蓝牙的时候,可以把唤醒电平调高,让电容充更多的电、多运行一会儿,让蓝牙正常发送数据。
哦,还忘了说,我还加了两个调试开关:一个是给电容放电的,开关导通之后 LED 发光直到没电;另一个打开之后 Buck 电路开始接受电容的电压。
不过后面这个不需要,只是我调试用的。
下面我们给出电路 A 的整体框图:
图 20 电路 A 整体框图
实物图如下:
图 21 电路 A 实物图
G3507 低功耗代码
这里单开一节,记录一下用 Driverlib 和 SysConfig 实现低功耗的代码。毕竟这也花了本人大概一下午的时间才研究清楚,因此单开一节,细细说明一下如何做低功耗。
先说明白G3507这个芯片的睡眠/唤醒逻辑,睡眠实际上两句话就能实现
DL_SYSCTL_setPowerPolicySTANDBY1();//设置休眠策略
while(0 == gWakeFlag)//等待TimerG0更新中断将标志位置1
__WFE();//休眠,等待唤醒
gWakeFlag = 0;
前一个函数是设置休眠策略的,而__WFE()函数其实就是进入休眠模式的意思,直到产生能唤醒MCU的事件/中断,才会从__WFE()推出来,回到循环头。
我们再贴一张G3507的功耗策略图
图 27 G3507 功耗策略图
还有一张电源域的图
图 28 G3507 电源域图
这里不过多解释,懂的人自然懂,不懂得可以问问AI。我们只管STANDBY模式下,只有PD0的东西能正常工作,而且只有PD0的中断能够唤醒。
所以我们开一个TIMERG0,连续下降模式,作为唤醒的中断源,1.5s中断一次,检测一下比较器的输出是否是高电平,如果是,就把标志位置1。
sysconfig配置就不放出来了,在进入睡眠的时候,把不要的外设全部关闭
void DeepSleep(void){
DL_SYSCTL_setPowerPolicySTANDBY1();
DL_GPIO_setPins(LED_PORT,LED_D3_PIN);
DL_SPI_reset(SPI_0_INST);
DL_ADC12_reset(ADC_Current_INST);
DL_OPA_reset(OPA_0_INST);
DL_TimerG_reset(TIMER_1_INST);
DL_TimerG_startCounter(TIMER_0_INST);
while(0 == gWakeFlag)
__WFE();
DL_TimerG_stopCounter(TIMER_0_INST);
void TIMER_0_INST_IRQHandler(void)
{
switch (DL_TimerG_getPendingInterrupt(TIMER_0_INST)) {
case DL_TIMER_IIDX_ZERO:
if(DL_COMP_getComparatorOutput(COMP_0_INST))//检查电容的电压状态,如果电压大于阈值,继续开机,反之,继续睡觉
gWakeFlag = 1;
break;
default:
break;
}
}
void Peripheral_reset(void){//外设重新启动的函数
DL_SPI_enablePower(SPI_0_INST);
DL_ADC12_enablePower(ADC_Current_INST);
DL_OPA_enablePower(OPA_0_INST);
DL_TimerG_enablePower(TIMER_1_INST);
SYSCFG_DL_SPI_0_init();
SYSCFG_DL_ADC_Current_init();
SYSCFG_DL_OPA_0_init();
SYSCFG_DL_DMA_init();
SYSCFG_DL_TIMER_1_init();
return;
}
前两个函数配合一下,逻辑就是,睡觉之前,打开定时器,每1.5s单片机醒一下,检测一下电容电足够吗,不够就继续睡,反之就起床,开始工作,关掉定时器。
Peripheral_reset函数其实就是给所有外设初始化的函数,这个内容可以在Sysconfig自动生成的代码里面找到,直接到 SYSCFG_DL_init()函数里面就可以直接抄这些函数了,不需要自己重新配置寄存器
其实有些外设可以不用手动关闭的,但是为保险起见还是都手动关了
代码中偶尔还需要一些小的延迟函数,或者是让CPU等待ADC DMA,UART DMA的函数,这段时间的功耗也要节省下来,所以再开一个定时器TimerG1,计数分辨率是10ms多一点点(时钟周期2.25M分不出来很准的时间了),作为CPU的休眠计数器
void CPU_sleep(uint16_t ms_10){
DL_SYSCTL_setPowerPolicyRUN0SLEEP0();
DL_TimerG_setLoadValue(TIMER_1_INST,ms_10);//设置定时器计数值,单位为10ms
DL_TimerG_startCounter(TIMER_1_INST);
while(0 == gCPUSleepFlag)
__WFE();
gCPUSleepFlag = 0;
}
void TIMER_1_INST_IRQHandler(void)
{
switch (DL_TimerG_getPendingInterrupt(TIMER_1_INST)) {
case DL_TIMER_IIDX_ZERO:
gCPUSleepFlag = 1;
break;
default:
break;
}
}
这两个函数配合,就能实现CPU的休眠,省一点功耗是一点功耗,该省省,该花花
我们再加上一个OLED的控制函数,在采完样之后,需要显示0.5s的结果,然后熄灭,这个过程其他外设都可以关掉
void OLED_Work(double result){
static char OLED_buf[100] = {};
/*屏幕显示期间,不用的外设通通关掉*/
DL_GPIO_setPins(LED_PORT,LED_D3_PIN);
OLED_On();
OLED_RES_Set();//RESET的引脚也会给oled供电,所以到现在才开启
snprintf(OLED_buf,100,"%.5fA",result);//这个位置实际上要放一个延时,等待OLED开机,不过这个函数的时间可以充当延时
OLED_Init();
OLED_ShowString(0,0,OLED_buf);
OLED_Refresh();
DL_SPI_reset(SPI_0_INST);//SPI用完就关
CPU_sleep(50);//显示500ms
}
void OLED_Sleep(void){
OLED_Off();
OLED_RES_Clr();
DL_SPI_reset(SPI_0_INST);
}
至于ADC采样,等待,这都是固定套路了
void ADC_DMA_Start(void){//开始采样
DL_ADC12_enableDMA(ADC_Current_INST);
DL_DMA_setSrcAddr(DMA,DMA_CH0_CHAN_ID,DL_ADC12_getMemResultAddress(ADC_Current_INST,DL_ADC12_MEM_IDX_0));
DL_DMA_setDestAddr(DMA,DMA_CH0_CHAN_ID,(uint32_t)buf);
DL_DMA_setTransferSize(DMA,DMA_CH0_CHAN_ID,BUF_SIZE);
DL_DMA_enableChannel(DMA,DMA_CH0_CHAN_ID);
DL_ADC12_startConversion(ADC_Current_INST);
}
void ADC_Current_INST_IRQHandler(void){
switch(DL_ADC12_getPendingInterrupt(ADC_Current_INST)){
case DL_ADC12_IIDX_DMA_DONE:
DMA_done = 1;
DL_GPIO_clearPins(MOS_PORT,MOS_SAMPLE_PIN | MOS_PROTECT_PIN);//采样结束,两个mos管关掉
break;
default:
break;
}
return;
}
然后就是计算有效值和运放增益自动切换,这里简单的写了一下
double GetRMS(void){
long long int rms = 0;
for(uint16_t i = 0;i<BUF_SIZE;i++)//在等待的过程,计算上一次DMA搬完的数据
rms += buf[i]*buf[i];
double rms_f64 = sqrt(rms *1.0 / BUF_SIZE)/4096.0*3.3;
rms_f64 = (0.91817 * rms_f64 + 0.0034012)/gain;
switch(gain){
case 1:
rms_f64 = 0.990359 * rms_f64 + 0.016488;
break;
case 2:
rms_f64 = 1.022102*rms_f64 - 0.012788;
break;
case 4:
rms_f64 = 0.971941*rms_f64 + 0.013603;
break;
case 8:
rms_f64 = 1.021771*rms_f64- 0.000994;
break;
case 16:
rms_f64 = 0.998849*rms_f64 + 0.002479;
break;
default:
rms_f64 = rms_f64 / 0.1;
break;
}
return rms_f64;
}
void OPA_SetGain(OA_Regs *OPA_INST,double result){
static DL_OPA_Config gOPA_0Config_nselRTOP = {//nsel为RTOP,增益为1
.pselChannel = DL_OPA_PSEL_IN0_POS,
.nselChannel = DL_OPA_NSEL_RTOP,
.mselChannel = DL_OPA_MSEL_GND,
.gain = DL_OPA_GAIN_N1_P2,
.outputPinState = DL_OPA_OUTPUT_PIN_ENABLED,
.choppingMode = DL_OPA_CHOPPING_MODE_DISABLE,
};
static DL_OPA_Config gOPA_0Config_nselRTAP = {//nsel为RTAP,增益可以配置
.pselChannel = DL_OPA_PSEL_IN0_POS,
.nselChannel = DL_OPA_NSEL_RTAP,
.mselChannel = DL_OPA_MSEL_GND,
.gain = DL_OPA_GAIN_N1_P2,
.outputPinState = DL_OPA_OUTPUT_PIN_ENABLED,
.choppingMode = DL_OPA_CHOPPING_MODE_DISABLE,
};
if(result > 1){//如果电流大于0.5A,增益改为1
if(gain != 1)
gain = 1;
}
else if(result >0.5){
if(gain != 2)
gain = 2;
}
else if(result >0.25){
if(gain != 4)
gain = 4;
}
else if(result >0.125){
if(gain != 8)
gain = 8;
}
else{
if(gain != 16)
gain = 16;
}
if(gain == 1){//进入到这个分支,由于不能直接改nsel,要重新配置,很史
DL_OPA_reset(OPA_INST);
DL_OPA_enablePower(OPA_INST);
delay_cycles(POWER_STARTUP_DELAY);
DL_OPA_enableRailToRailInput(OPA_0_INST);
DL_OPA_init(OPA_INST, &gOPA_0Config_nselRTOP);
DL_OPA_enable(OPA_0_INST);
}
else {
DL_OPA_reset(OPA_INST);
DL_OPA_enablePower(OPA_INST);
delay_cycles(POWER_STARTUP_DELAY);
DL_OPA_enableRailToRailInput(OPA_0_INST);
DL_OPA_init(OPA_INST, &gOPA_0Config_nselRTAP);
DL_OPA_enable(OPA_0_INST);
switch(gain){
case 2:
DL_OPA_setGain(OPA_INST,DL_OPA_GAIN_N1_P2);
break;
case 4:
DL_OPA_setGain(OPA_INST,DL_OPA_GAIN_N3_P4);
break;
case 8:
DL_OPA_setGain(OPA_INST,DL_OPA_GAIN_N7_P8);
break;
case 16:
DL_OPA_setGain(OPA_INST,DL_OPA_GAIN_N15_P16);
break;
case 32:
DL_OPA_setGain(OPA_INST,DL_OPA_GAIN_N31_P32);
break;
default:
DL_OPA_setGain(OPA_INST,DL_OPA_GAIN_N1_P2);
break;
}
}
return;
}
片载运放的框图可以在Sysconfig里面找到
图 29 片内 OPA 框图
如果负端选择RTOP,就会变成跟随器,负端选择RTAP,就会变成同相放大器,不过切换这个配置需要重新配置,很史
最后把所有的这些小函数都超级拼装一下,凑出来一个main函数
int main(void){
SYSCFG_DL_init();
double result = 0;
NVIC_EnableIRQ(ADC_Current_INST_INT_IRQN);
NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
NVIC_EnableIRQ(TIMER_1_INST_INT_IRQN);
/*第一次开机,先测一轮,显示一回,以提高启动时间的指标*/
DL_GPIO_clearPins(LED_PORT,LED_D3_PIN);//第一次开机开灯,指示现在正在工作
DL_GPIO_setPins(MOS_PORT,MOS_SAMPLE_PIN | MOS_PROTECT_PIN);//打开采样用的两个mos管
CPU_sleep(60);//前600ms还不太稳定,先等待一会
ADC_DMA_Start();//采一次样
while(DMA_done == 0)//等待DMA完成
;
DL_GPIO_clearPins(MOS_PORT,MOS_SAMPLE_PIN | MOS_PROTECT_PIN);//采样结束,两个mos管关掉
DL_ADC12_reset(ADC_Current_INST);//ADC OPA关掉
DL_OPA_reset(OPA_0_INST);
result = GetRMS();//计算电流有效值
DMA_done = 0;
OLED_Work(result);
OLED_Sleep();
while (1){
DeepSleep();
gWakeFlag = 0;
Peripheral_reset();//外设上电
DL_GPIO_clearPins(LED_PORT,LED_D3_PIN);//开灯,指示现在正在工作
DL_GPIO_setPins(MOS_PORT,MOS_SAMPLE_PIN | MOS_PROTECT_PIN);//打开采样用的两个mos管
OPA_SetGain(OPA_0_INST,result);//OPA增益自动设置
CPU_sleep(60);//前600ms还不太稳定,先等待一会
ADC_DMA_Start();//采一次样
DL_SYSCTL_setPowerPolicyRUN0SLEEP0();
while(!DMA_done)//等待DMA完成
__WFE();//等待的过程中,让CPU睡一会
DL_GPIO_clearPins(MOS_PORT,MOS_SAMPLE_PIN | MOS_PROTECT_PIN);//采样结束,两个mos管关掉
DL_ADC12_reset(ADC_Current_INST);//ADC OPA关掉
DL_OPA_reset(OPA_0_INST);
result = GetRMS();//计算电流有效值
DMA_done = 0;
OLED_Work(result);
OLED_Sleep();
}
}
程序很简单,大家就凑合看看吧,想要源码可以在文章最前面或者最后面到我的仓库自取。
Next
电路 A 就到此为止了,接下来是制作无线模块,这后面的内容下一期再发一篇完善,本篇仅仅介绍选型,这段时间有其他事情需要忙。
最先想到的当然是上课学到的 LoRa、ZigBee,还有低功耗蓝牙。这些都是工业中常用的电表组网协议,我这里挑了一个离我们近的:低功耗蓝牙。
很多人第一次用蓝牙模块,用的都是 HC-05:
图 22 HC-05 蓝牙模块
不过这个功耗有点太高了,不太合适。我们这里尽量用 uA 级别的模块,找来找去,我也就看中了两款:
图 23 两款低功耗蓝牙模块
这个模块有两种:一种是上图的邮票孔版本,一种是把供电和串口引出来、移除了低功耗引脚的排针版本。当时赛前我推荐同学们采购排针的那款,没想到它居然没法进入睡眠------没有指令可以控制睡眠!?一直都有 2.8mA 的电流(悲)。
邮票孔的 MX02,在 3V3 休眠情况下电流约一两百 uA,还是有点大的,比我的系统还要大一点,可以设置成 10s 广播一次。
但是这个模块我始终调不通,一直卡在睡眠模式出不来,给它发送 AT 指令也仅仅能回复一两次。我以为板子没有焊好,拆的时候把焊盘拆坏了,遂放弃。下面是焊了一半的模块图(笑):
图 24 焊了一半的 MX02 模块
后来我又发现了更🐂的一款:
图 25 超低功耗蓝牙模块
休眠功耗 1.2uA×3.3V,那还说啥了,就是一块砖接到电路上了。
发射功耗也不高,这种量级的功耗,我就算把模块挂在电路里面功耗都不会明显增加:
图 26 模块发射功耗实测
完整代码与标定数据请前往 GitHub 仓库获取: https://github.com/Kreeli/Sourceless_Ammeter_G3507
(未完待续)