2020年9月21日,我忽然收到一位教授的邀請。這位教授是我高中時課題研究的指導老師,他知道個人電子與計算機大概是什麼水平,而他邀請我參加的正是電子設計競賽。html
我去作了點功課。往年,電子設計競賽都在暑假裏舉辦,今年由於特殊狀況改到開學之後。教授則是學校裏負責該競賽的「出口」。由於以上種種機緣巧合,我纔能有此次機會。git
比賽需三人組隊,這是比較難辦的,相比之下歷屆題目比較簡單,若是是我來選題的話。我所處的理科試驗班都是些次頂尖的學數學物理的人,找不到人一塊兒參賽,只能先把密院ECE專業的女友拉下水,讓她再去找一我的。有幾我的表示有興趣可是沒時間,儘管如今看來「有興趣」的意思是「有興趣一塊兒拿個獎」。咱們找了一我的聊聊,我簡述了我準備比賽的計劃,對方只是點點頭,對着電腦不知看些什麼。結束之後我跟女友商量說,要不就咱們倆參賽吧。github
中午咱們去電院開會。除了咱們組以外,別人都是人工智能大二學生,參賽能夠在電子電路系統實驗課程中得高分。我是來體驗一下的,由於我一直不清楚本身的水平。之前作過幾個項目,但都是本身定的目標,如今要在別人出的題中選一道完成,我能作到嗎?個人項目都是肝出來的,從原理圖到PCB到焊接到編程通常都要一個星期,如今要在4天3夜裏完成,我能作到嗎?一直都是一我的作的,如今要合做,雖然都是本身人但仍是跟獨立完成不同的,我能作到嗎?算法
因此我要好好準備。仔細分析了一下2019年的題,感受只有C題和D題是有可能作的。這類測控題好就好在只需電路,不涉及小車、無人機等機械結構,後者是我沒有接觸過的。編程
我備賽的宗旨「別讓比勝過程中的小事搞壞心態」。好比,去年C題和D題都要求設備能輸出必定頻率的正弦波,因此得把DAC或DDS調通;頻率高了之後ADC跟不上,就得用精密整流電路和施密特觸發器來檢測幅度和相位;這些都不該該留到比賽現場才完成。更基本地,用按鍵或旋轉編碼器選擇功能、蜂鳴器響表示測量完成、屏幕上顯示測量結果等功能都是必需的。數組
這樣一來就能夠決定要做什麼準備了:緩存
再作一塊咱們熟悉的開發板;框架
作一個IO模塊,放經常使用的輸入輸出設備,它經過I²C與開發板鏈接;異步
設計一系列模擬電路模塊,實現各類經常使用功能,如線性運算、精密整流等。函數
當時還在準備9月30日的數分高代考試,就沒有直接參與準備,把IO模塊的設計丟給女友讓她設計年輕人的第一塊PCB。要說工做也是有的,我在淘寶上下了12個單買元器件和模塊。
↑有關的、無關的PCB
十一期間只在家裏待了三天半就去學校了,這三天半里也有大半都是焊接。做爲老焊工,焊接的過程就省略了。在確認下載器能夠檢測到單片機之後,我就帶着這些去學校了,可是且慢……
彼時個人元器件庫存已經比較成系統了,但新到的那麼多快遞仍是帶來了不小的壓力。最麻煩的是我要帶一些元器件和模塊到學校去,由於聽說實驗室裏的阻容不怎麼齊全。根據個人日記描述,僅後來選題之後感受能用上的就有:
挺全的色環電阻;
挺全的獨石電容;
少許電解電容;
2N390四、2N3906(直插很少,貼片買了,有轉接板);
模擬開關405一、405二、405三、4066(各2片);
繼電器(5個),二極管;
運放LMV358(5片)或LM358(20片);
數字電位器X9C103S(5片);
直插74HC595(5片);
實際帶的還有各類模塊,包括DAC、DDS等,還有一些小車能用上的,但整體來講仍是把賭注往模擬電路上押的。
在學校,除了一天半的做業時間之外,別的都是寫bug與debug。全部這些bug都是有關總線的,一個I²C的,別的都是SPI的。
忘了說IO模塊的結構了。ATmega328P單片機,I²C接開發板,SPI上接74HC59五、16五、DAC和OLED屏,用一片138來作片選。595接4個LED和OLED的兩個控制信號,165接4個按鍵和旋轉編碼器。SPI上掛了4個設備,不免出現各類bug。
首先是595調不出來,不管怎麼寫都是QH
亮。把板子翻過來一看,原來是595和165焊反了。用熱風槍拆下來後焊到正確的位置,過程當中還經歷了學校的焊錫不化和烙鐵頭不沾錫的問題。而後165的C
引腳始終讀到0
,考慮到這片165是用熱風槍拆了兩次的,就認爲這個引腳壞了,因而用飛線把這個點和一個未使用的GPIO接起來了。我想過換一片165,可是找不到能夠換的片,並且鋪銅上的綠皮也由於不得不開到400度的電烙鐵而脫落了很多,換個片極可能致使整塊板報廢。
用GPIO驅動165很是順利,可是改用SPI後就發生了很奇怪的現象:第1位能正常讀到,可是第2位讀到的始終與第1位相同,後面的很亂講不清楚,大致就是位與位之間有關聯。我又開始懷疑165損壞,可是換用GPIO的程序仍是正常。試了幾回之後我把SPI時鐘頻率從4 MHz降到0.5 MHz,終於能正確讀取了。原來我爲了避免讓165的輸出與下載器的MISO
衝突,給前者串聯了一個1k電阻,這使該信號的頻率上限嚴重降低,再加上那片165多少沾點nt,4MHz就這樣爆了。
某天半夜調DAC。DAC復位後應該輸出0V,但寫了正確的指令後用ADC測出來像是高阻。把10位數據的低4位都寫0,其他從零開始增加,獲得分段增加的輸出電壓,我感受是數據被移位了。果真,以前想固然地選擇SPI mode 3,而實際上該用mode 2。
OLED的RES
和D/C
控制信號接在了595上,SPI與單片機直接相連,跨了兩層,問題也出在這裏。屏幕和接線都跟之前我作過的一個模塊同樣,只把u8g2的GPIO回調函數改了一下,可是沒法顯示,測電壓之後發現電荷泵都沒打開。依然是先懷疑屏幕壞了,但這要軟件上調不出才能下結論。我在回調函數里加了點串口輸出,發現每次在SPI上發送一兩個字節以前都會先設置D/C
的電平,該電平須要寫595才能設置,而寫595會破壞OLED的片選信號,致使始終沒有完整的指令發送給OLED。解決方案是切斷原來595輸出到D/C
的鏈接,改用飛線鏈接到另外一個未使用的GPIO上。
後來屏幕成功點亮。初始化中執行了清空緩存並更新現實,可是屏幕上只有前8行像素被清零,其他的很隨機,是未初始化的狀態。最後發現是隊友把初始化函數選錯了,應該選擇後綴爲_f
的,在內存中存放整個顯存。
debug的這幾天,新作的PCB也到貨了,是24個模擬電路模塊,兩個方向上都是20*4+15+1間距*4=99 mm。沒有作V割,由於出不起拼板費。我新買的小臺鋸就是用來切PCB的,只是放在了家裏。我想着學校應該會有可用的設備,但跑遍學校也只有激光切割機,工做人員說沒有切過PCB,試了一下的確不行。沒辦法,只能讓家裏人把臺鋸送過來。
10月8日,假期的最後一天,去實驗室熟悉了一下設備。直流電源、信號發生器和數字萬用表比較平凡,示波器得好好研究,尤爲是觸發功能。那天去的實驗室裏是Rohde & Schwarz的示波器,不當心碰了一下屏幕之後咱們發現它居然是觸摸屏的!總之就是很是厲害。焊了一個滯回比較器模塊,用帶噪音的正弦波做輸入,輸出沒有在輸入接近地時快速抖動,這說明實驗是成功的。在這種時候作這麼簡單的實驗只是想找回一點自信心,由於此前幾天的debug過程讓咱們很是擔憂比賽時會繼續遇到各類各樣的bug。
↑VSSOP-10封裝,0.5 mm引腳間距
10月9日固然沒什麼心情上課啦。晚上調着DAC和DDS模塊,忽然羣裏轟來好幾個文件,一看居然是比賽題目!是網站方的失誤,把題目提早放了出來,組委會也決定將錯就錯,提早開始了比賽,但不提前結束。
咱們開始選題。A題涉及兩個早就公佈的芯片,咱們查過淘寶上已經有不少方案了,可見別的組都早有準備;B題電源,沒學過;C題小車,沒學過;D題無人機,沒學過;E題模擬電路,是咱們準備過的方向;F題上海不選,能選也不會;G題識別,沒學過。綜上所述,選E題。
我連夜寫了一份非正式報告,包括實現方案、附加功能、可能遇到的問題等,寫到早上5點。10日,教授說電子系的全都選了E題,我切身體會到了什麼叫內卷。
可是換題已經沒有可能了,咱們只好卷下去。咱們先去另外一間實驗室熟悉環境——咱們將在那裏待上4天3夜。開了一臺示波器,從Windows的logo就開始不對勁,開機之後發現是花屏了。又開了一臺,也花屏。我開始懷疑這間實驗室是廢舊儀器倉庫,後來教授帶着終於湊齊了一套好用的電源、信號發生器、示波器和萬用表。
審題放在下一節講。方案離不開共射放大電路,我開始用Multisim來調參數。無失真、頂部失真、交越失真都沒有問題,雙向失真暫時沒有思路,而在底部失真的仿真中我發現了很奇怪的現象:底部失真本應是輸出波形的底部被削平,但仿真結果倒是底部波形上翻。用麪包板搭建電路後也是如此。問教授後得知通常發射極都要加電容,在保持直流工做點的同時提升放大倍數。這下雙向失真也很容易實現了。至於不加電容時波形會上翻,是由於集電極電壓不能低於發射極,而放大倍數不很大時基極電壓的變化就會反映在輸出上。
隊友用麪包板搭建了一個兩級負反饋三極管放大電路,原本打算用做前級小信號放大的。搭面包板的過程當中,咱們發現仍是本身帶的電阻電容比較齊全。
晚上,隊友寫了個C++程序試試FFT和THD。把FFT採樣頻率改到與正弦波頻率的幾倍差一點,發現THD比較小,這說明THD的測量對採樣頻率的精度要求不高。
說說別的組。我一早上就去實驗室了,其餘3組到中午纔有人來,有一組的另兩我的晚上纔來,這才完成了選題。此時,咱們已經有了方案,就等次日開始製做了。
↑模擬電路模塊
我一開始的想法是設計5個放大電路,用模擬開關來切換通道。教授說能夠經過改變放大倍數實現,我以爲這個想法也不錯,可是仔細思考又感受不對:放大倍數小的時候是正弦波,大了之後要麼頂部失真,要麼底部失真,再大變成雙向失真,因此爲了同時得到頂部和底部失真須要調整直流工做點;並且這種方法不能涵蓋交越失真。這些意味着須要好多級的切換。雖然各類模擬開關我都有,可是會讓設計的層次不那麼分明,比較複雜。最終仍是採用了原來的方案。
在內卷的競爭中,咱們必須在附加功能上花工夫才能脫穎而出,因此除了題目要求的功能之外咱們還打算加入波形顯示和聲音輸出的功能。
設備共由4塊板組成:一塊洞洞板對外接12 V電源、輸入和輸出,板上有LDO、模擬開關和三極管放大電路,以及與其餘板鏈接的引腳,直流工做點均可以用電位器來調整;一塊洞洞板上焊接兩個模塊並粘了一個揚聲器,負責信號的運算,即把12 V範圍內的信號耦合到5 V之內;一塊開發板;一個IO模塊做UI。
算法部分,FFT選用了Arduino FHT庫,以1 kHz的32倍頻採樣,FFT規模256,計算16位線性幅度。而後根據題目裏的公式取相應的FFT結果來計算THD——fht_lin_out[0]
是直流份量的幅度,[8]
是基頻,[16]
是兩倍頻等。一開始是16倍採樣頻率,規模128,爲了精度和頻率分辨率再加一倍,可是效果不太理想,最後只可以用就好了。
在25 MHz主頻下,1 kHz的32倍頻對應781.25個週期,儘管沒法讓ADC採樣精確地發生在分數週期時刻,但也要避免0.25週期累加產生的影響。爲此,以4次爲週期,OCR1A
寄存器的值取1次781
、3次780
。後來發現這點精度損失徹底能夠忽略不計。
IO模塊的按鍵是事件驅動的,按下時IO模塊會經過I²C通知開發板。程序切換一個波形後,須要先等待1秒讓直流工做點穩定,再以0.5 s爲週期執行採集、計算、顯示等操做,只有空閒的時候才容許退出。儘管定義過事件啓用禁用的指令,可是一是怕出現一些破壞原子性的狀況,二是異步終止一個過程的流程很差寫,因此我又把異步事件改回同步——在回調函數裏設置標誌位,在流程裏須要的時刻輪詢。
THD計算中須要開根號,FFT到頻譜圖須要取對數,均可以用二分算法來實現。開根號的過程是對結果進行二分查找。一個小細節是AVR開發中int
默認是16位,uint16_t
乘uint16_t
會溢出,要先轉換爲uint32_t
。取對數是把0-10000
映射到0-48
。先在Excel中生成指數表,做爲數組放進程序,寫一個相似std::lower_bound
的二分查找算法獲取index,即爲結果。
示波器穩定圖像的方法是使用觸發功能,基於這種想法咱們在256長的數據中找出第一個在512±16
滯回比較器中的上升沿,做爲2波形週期64項的開始。後來有了硬件滯回比較器,但仍是沿用了軟比較器做觸發。
最後一天還想加點功能,教授說題目裏沒有明確說明要測THD的都是1 kHz的信號。用FFT直接計算基頻是不許的,由於頻率分辨率只有31.25 Hz,但FFT能給出基頻的範圍,即第一個峯值附近,我定義爲比左右兩邊都大且超過最大值的一半的第一個值。找出這個index,把它右邊一格對應的週期做爲週期的下限。而後用鏈接到硬件滯回比較器的GPIO檢測上升沿,累加上升沿間隔直到超過下限,記錄下通過的週期數。測量16次,排序,取中間8個的平均值做爲信號的週期。而後32倍頻ADC採樣,後續流程相同。
↑滯回比較器的輸入與輸出波形(先用運放湊合一下)
11日開始製做設備。上一次焊洞洞板仍是剛接觸PCB的時候,那時作了一個電位器調音量的電路,最後失敗了,因此我對洞洞板一直不太看好。此次畫PCB已經來不及了,只好硬着頭皮上洞洞板了。
洞洞板上共有6個放大電路:前級放大和5種波形對應的電路。每一個子電路和電源都鏈接到排針做輸入輸出,兩級之間暫時用杜邦線鏈接。先焊好前級,測試一下沒問題,內心就有底了。一下午焊完了6個電路,調好電位器後都能正常工做。
反面的接線挺亂的,儘管元器件是按照信號方向從左到右、電壓高低從上到下的規律排列的。典型的如GND
分佈在每一個子電路的多個電阻上,我直接用電阻的引腳拉到旁邊的GND
上,每一個子電路匯聚到一個點再接到最下方的一長條焊錫上。有些短程的鏈接要跨線,我用剪下的引腳或飛線跨接。
模擬開關進入戰局後狀況就變得不太樂觀了。首先是我不知爲什麼要用兩片CD4051,其實只要輸出端一片就夠了,這幾乎把接線的複雜程度翻了一倍。模擬開關工做在12V,單片機輸出信號須要轉換才能用,我就在洞洞板背面用貼片三極管和貼片電阻焊了3個反相器,輸入接排針,輸出用飛線接到兩片4051上。這一步很困難,原本就是三極管集電極和貼片電阻之間一點點地方,還要接到兩個引腳上,還那麼靠近。爲了測試方便,在排針上面還加了一排高電平,這樣用跳線就能夠選擇通道了。而後又把LDO、輸入輸出耦合等加進來,一步步地很費時間,作完已是次日凌晨了。
咱們須要爲4塊板找一塊底板固定起來。實驗室找不到新的大洞洞板,只能用一塊用過一點的大板,把用過的部分切下來。而後用臺鑽鑽3 mm孔,用銅柱和螺絲固定上去。板上的電源和輸入輸出用的是香蕉和BNC插座,爲了焊到板上也是要先鑽孔。BNC插座的外層不沾錫,我拿從杜邦線中拆出的銅絲繞引腳一圈焊上,才使鏈接穩定。
最後咱們聽取教授的建議,爲了穩定把杜邦線鏈接改成焊接,個人隊友完成了這項工做。第三天晚上完成這些,而後一塊兒把報告寫了,隊友負責軟件部分,我負責其他所有。最後一天作的就沒有寫進報告裏了。
↑成品
實際過程固然沒有這麼順利——又到了喜聞樂見的debug環節。先講硬件問題。
虛焊出現過兩次。用探頭測波形,要按下才會有波形顯示,開始幾回稍微用力便可,後來要按到底才行。從側面看,按到底的時候焊點已經碰到了桌面,因此我推測是焊接問題。把用轉接板和排針鏈接的三極管換成直插的,解決了問題。此後三極管只敢用直插的了。還有一次是用錫走線沒有鏈接徹底形成的。
輸出失真波形的平直段傾斜,測了各級的輸出後發現是最後一級耦合到地的問題,把105獨石電容換成10 μF電解電容解決了問題。
功放芯片只找到LM386。隊友按照datasheet上的電路焊了洞洞板,但揚聲器裏的聲音是必定頻率的爆音,且爆音頻率與輸入電壓正相關。想到多是輸入耦合的問題,加了電容可是沒有改善。我又用麪包板搭了一樣的電路,換了多個芯片,都是一樣的問題。此時她正由於前兩天沒能幫上什麼忙而難過,這下電路沒作成更加沮喪,而我也一會兒沒想到什麼可行的方案。但我深知不能兩我的一塊兒頹廢,情急之下我想到了以前作的模塊(還記得準備階段的目標嗎?),正好有一個用三極管擴大運放輸出電流的模塊能夠用做功放。板子早就切好了,焊上電阻電容二三極管,外部只需再加一個電解電容,揚聲器就能響了。
12日晚,臨寫報告時又讀了一遍題,發現忘了題中的K1
和K2
兩個SPDT。實驗室一時半會也找不到開關,我急中生智把開發板上兩個用不到的撥動開關拆了下來,焊在洞洞板上,再小改了一下接線。
而後是軟件問題。動手寫代碼以前我就意識到了異步事件的問題,個人隊友是搞不定的,因此我就安排她把ADC->FFT->THD的過程寫成一個函數,不用管函數調用的位置以及先後須要作什麼。可是吧,問題就在於機械革命到了Ryzen這一代品控仍是繼續差,隊友的筆記本掉屏幕了!還好掉得早,已完成的只佔一小部分,後面的代碼就都是我在我用了近三年的筆記本上寫的了。這是電腦的硬件問題,開發過程當中的軟件問題。
第一次跑半完整程序的時候,啓動時間遠長於預期,至少到了秒的數量級,而正常的啓動時間應該不到100 ms。在代碼一開始加上翻轉引腳電平的指令,接邏輯分析儀發現啓動過程有三次復位,而且都是硬件復位。我把初始化過程當中調用的函數一個個註釋掉,最後發現是ADC的初始化致使了復位。進一步分析,可能的緣由是開啓ADC時AREF
上的0.1 μF電容會被充電,使5 V電壓忽然降低,降到了brown-out電壓如下,觸發單片機復位。我把brown-out電壓從4.3 V改到2.7 V,單片機就再也不復位了。
用邏輯分析儀看波形時發現I²C指令之間有10 ms量級長的間隔。兩邊用的I²C驅動是我本身寫的異步寫同步讀,只有在緩衝區滿時纔會阻塞,從機端的處理也所有在中斷中進行。仔細觀察波形後我發現是從機在地址字節以後把SCL
拉低了,阻止主機發送後續數據,而這是由於程序沒有及時進入相應的中斷去寫寄存器,實際上程序還在處理上一個繪圖指令,繪圖是比較耗時的。我想到能夠用相似的方法把指令放在緩衝區中,在定時器中斷中取指令並執行。後來發現效果不怎麼理想,一是間隔仍然存在,二是緩衝區受不了128根柱子的數據量,形成數據丟失,因此又換回來了。
Arduino FHT輸入爲16位帶符號數,ADC只能讀到10位,這樣輸出的幅度很小,精度不高。把ADC讀到的值左移6位再在低位補上高6位做爲輸入,卻得不到正確的FFT輸出和THD值。檢查後發現忘記考慮符號了,只需把MSB翻轉便可。
開發板處屢次出現內存不足的狀況。一開始FFT規模爲128,OLED緩衝區佔1KB,要顯示的字符串太多,佔用了不少內存,我就把它們移到flash中去,留出了一百多字節的空餘內存。後來FFT規模改到256,內存怎麼都省不出來了,只能改OLED的顯示方式,只給它分配256字節顯存,每幅圖像分4次繪製。
以上bug其實佔用了有效比賽時間的大部分,因此我實際上是沒什麼時間睡覺的。天天回一次寢室拿東西,晚上睡在實驗室樓下大廳的沙發上。有一天打算1點睡,結果debug到4點才睡;又有一天打算4點睡,隊友開玩笑說我怕是要7點睡了,結果我真的7點準時叫她起牀了。
↑10月4日,外灘燈光秀
比賽結束後我好好睡了一夜,次日睡了一上午、一中午、一下午和一夜。
15日校測,咱們帶着箱子到陌生的實驗室測試。咱們復原後測試時,發現雙向失真的輸出波形達不到2 Vpp了,調電位器也沒有用。我想起製做時由於雙向失真輸出峯峯值明顯高於其餘幾個波形,就把下端的分壓電阻並聯上一個相同阻值的。如今只要把這個額外的電阻取下來就能夠了,不用動電烙鐵,直接用鉗子剪斷一端就行了。
咱們很快就準備好了,開始關注其餘組的狀況,儘管不能看。有一組沒有加K1
和K2
,從而有項目沒法測試。我以前注意到這點的時候思考過這兩個SPDT有什麼用,思考的結果是爲了驗證THD是真實的,即給一個外來的波形要求測THD,不然THD是能夠用現成設備測好後硬編碼進程序的,儘管我在實驗室沒有找到過能夠測THD的設備。大三的幾組都作了很大的板,板上甚至有三極管的陣列,以致於我覺得他們是選電源題的。後來據說這是他們爲了大做業作的。
咱們的測試很順利,而且兩個開關的功能正如我所預料的。題目要求能測量1 kHz、2 Vpp的方波和三角波的THD,而後與標準答案比對。咱們的設備測量得有一點偏差,但在合理的偏差範圍內。
到了18日位於隔壁華師大的市測,測試的項目大體相同,波形換成了理論THD爲8%和20%的,頻率仍然是1 kHz。進入這個模式前專家先告訴我,設備顯示的THD要能在切換波形時自動更新,這是一個得分點。專家讓我調設備進入測量模式,此時尚未信號源,所以我以設備須要先測量信號頻率爲由拒絕了,並要求先開啓信號源,專家贊成了。由於要自動更新,因此得在屏幕上THD的瞬時值和平均值中選擇前者來讀,第一個是8.5%,第二個20.5%,上下波動均不超過0.2%,我很滿意。
最後得了一等獎,但此時這已是次要的了。我很累。據說電賽不能找室友組隊,由於室友之後還要一塊兒住;而我選擇了最親近的人一塊兒參賽,能夠想象咱們經歷了多少困難。若是還有機會,我不想再參賽了。
回想起來,比賽的幾天我沒有過任何重大技術突破,全部技術要點都是炒冷飯。那麼時間都用哪去了呢?大概是以上十幾個bug吧。我不想之後回憶起這段參賽經歷只想得起我睡得多少、有多累,而是經驗——那些踩過的坑、避過的雷、de過的bug,故爲此文。
那些借用來的技術要點以及它們的來源包括:
I²C通訊、整個流程控制框架,來自我高中時的課題,本博客沒有;
三極管放大電路、FFT,自制藍牙音箱的手冊;
示波器,AVR單片機教程——示波器;
二分算法,來自遙遠的C++、DSA記憶;
硬件、軟件的debug經驗,分佈於個人開發經歷中;
等等……
我願稱之爲「縫合怪」。
↑本身作的電賽限定版PCB鑰匙扣