万花尺但是Arduino

![万花尺](/static/uploads/images/c0357bce9b78905fc5ee8330881cf4cc.png) 这两天当硬设助教,拿了块Arduino UNO单片机,打算整点小活。之前上谷老师的《信号与系统》听了关于Z变换和数字自激振荡器的讲解,就想实现一回基于数字振荡器的万花尺,致敬一下传奇《电电实验2》。 ## Arduino能跑多快? `Arduino`用的`ATMega328`芯片号称主频能跑到10多M(远远不能逼近最喜欢的曲水流觞大作业x)。但是具体我写端口能写多快还得看`digitalWrite()`要多快。于是写了一个简单程序: ```cpp void setup(){ } void loop(){ digitalWrite(7,HIGH); digitalWrite(7,LOW); } ``` 然后拿示波器抓一下`7`引脚的电平: ![](/static/uploads/images/e73655c78d3bc5355cafd20e176ac70f.jpg) ![抓呀抓呀抓电平](/static/uploads/images/90176ebe0b5fc699edd194083bd17e58.jpg) 得到这玩意频率在`148k`。看起来也够用了。 ## DAC怎么搞? 众所周知`Arduino`的模拟输出其实输出的是`PWM`波而不是模拟电平。因此,需要想办法搞一个DAC出来。然而如果真的搞一个DAC模块,首先等快递就要一段时间,我等不及;其次这种成品DAC又贵又慢(因为通常都是用`I2C`之类的协议串行通信的,我倒是不需要连个管脚都这么扣扣嗖嗖)。因此我直接使用最喜欢的电阻分压进行DAC。 ![](/static/uploads/images/485a26e47dc9359f42977d3d241a3a9d.png) 只要取$R_1=1k\Omega\,,R_2=2k\Omega\,,\cdots R_5=16k\Omega\,,$就得到了一个很简单的(近似的)5bit并行DAC。于是我火速下楼拿了一盒电电实验kit并硬凑出了两路DAC。 ![两路5b-DAC,左右各一路](/static/uploads/images/026eea19adc1cb179cbce1576dc2a1cf.jpg) 于是便搞定了“模拟输出”的问题 ## 信号怎么搞? 考虑下面这个系统 ![](/static/uploads/images/5e2cee3cbece47555191f5bfdb6a8270.png) 容易写出$y_C(n)\,,y_S(n)$的传递函数 $$ H_C(z)=\frac{1-z^{-1}\cos\Omega}{1-2z^{-1}\cos\Omega+z^{-2}} $$ $$ H_S(z)=\frac{z^{-1}\sin\Omega}{1-2z^{-1}\cos\Omega+z^{-2}} $$ 结合部分分式法即可逆z变换得到两个单位样值响应 $$ h_C(z)=\cos(n\Omega)\,,h_S(z)=\sin(n\Omega) $$ 于是为了得到我们想要的波形 $$ \begin{cases} x(t)=(1-d)\cos(2\pi f_1t)+d\sin(2\pi f_2t)\\ y(t)=(1-d)\sin(2\pi f_1t)+d\cos(2\pi f_2t)\\ \end{cases} $$ 离散化为 $$ \begin{cases} y_A(n)=(1-d)\cos(n\Omega_1)+d\sin(n\Omega_2)\\ y_B(n)=(1-d)\sin(n\Omega_1)+d\cos(n\Omega_2)\\ \end{cases} $$ 同时考虑到我们的DAC只能输出正的,因此把电平抬高即可。只需要构造系统为 ![](/static/uploads/images/760a27e4c3ed54df0bc20c9da7cd06b4.png) 即可得到万花尺。使用的代码可见文末。 ## 与“模拟”方案的对比 之前上《电电实验2》做了一个数字分频-模拟滤波的版本如下 ![](/static/uploads/images/38c23e6f4135b4685ca48747ac60a407.jpg) ![](/static/uploads/images/c041dc349322d271a0971219784e1aac.png) ![](/static/uploads/images/0f8d753bcb89df2014e8282712fa719c.jpg) ![](/static/uploads/images/158c41e8617514bcabedf7ca223b92d0.png) 对比下来纯写代码还是更“干净”更好调试,换一组数据只需要重新compile一下就可以了。当然问题也多,比如说数字版的最后频率只有5Hz左右,而模拟版的好像工作在几k左右。 ## 效果展示 最后附上一组展示。首先是实验课同款的$\Omega_1:\Omega_2=2:5\,,d=0.6$: ![](/static/uploads/images/599bee06b53295547664aa1ab4e98d81.jpg) 改变d值可以改变图形的胖瘦,比如取$\Omega_1:\Omega_2=2:5\,,d=0.5$: ![](/static/uploads/images/73c6351946a205dcd841c0c4ad573b65.jpg) $\Omega_1:\Omega_2=2:5\,,d=0.3$: ![](/static/uploads/images/3933637c0d62a3838c5e932133ada40e.jpg) 改变频率比可以改变图形的瓣数。比如$\Omega_1:\Omega_2=3:5\,,d=0.5$可以观察到8片: ![](/static/uploads/images/429af40681ed82cfac54e0a9a1de3941.jpg) 而$\Omega_1:\Omega_2=2:3\,,d=0.5$可以观察到5片: ![](/static/uploads/images/9a4b85d91f8c5fa45eb85762829806ae.jpg) 修改为$\Omega_1:\Omega_2=2:3\,,d=0.3$就变成五角星: ![](/static/uploads/images/21bb28ed8d2c89d3993e2eec1639e2b2.jpg) 令$d=0$又可以得到一个圆圈: ![](/static/uploads/images/db6780b758eb91f8766a8d872255550d.jpg) ~~最后,这个如果拿去验收电电实验疑似可以得到102分,因为实现了105分,但用了额外的元件扣最多3分~~ 最后感谢谷老师和《信号与系统》,**信号与系统真的很有趣!** ## 源代码 附上Arduino用的源代码,直接粘进IDE即可: ```c++ #define TESTPIN 7 #define PI 3.14159265358979323846 #define AMP 15.8 #define OMEGA_1 (PI/60/5)* 2 #define OMEGA_2 (PI/60/5)* 3 #define COEF 0.3 // d const double COEF_1 = 1-COEF; const double COEF_2 = COEF; const double C1_COS_OMEGA_1=COEF_1*cos(OMEGA_1); const double DOUBLE_COS_OMEGA_1=2*cos(OMEGA_1); const double SIN_OMEGA_1=sin(OMEGA_1); const double C1_SIN_OMEGA_1=COEF_1*sin(OMEGA_1); const double C2_COS_OMEGA_2=COEF_2*cos(OMEGA_2); const double DOUBLE_COS_OMEGA_2=2*cos(OMEGA_2); const double SIN_OMEGA_2=sin(OMEGA_2); const double C2_SIN_OMEGA_2=COEF_2*sin(OMEGA_2); double w1 = AMP; double w1_1 = 0; double w1_2 = 0; double w2 = AMP; double w2_1 = 0; double w2_2 = 0; int CHA_PINS[]={13,12,11,10,9}; int CHB_PINS[]={6,5,4,3,2}; int y_A = 0 ; int y_B = 0 ; int MASK[]= {16,8,4,2,1}; int bit = 0; int BIT = 0; void setup() { // put your setup code here, to run once: for(int i=0;i<5;i++){ pinMode(CHA_PINS[i],OUTPUT); pinMode(CHB_PINS[i],OUTPUT); } } void loop() { /* 更新w */ w1_2 = w1_1; w1_1 = w1; w1 = DOUBLE_COS_OMEGA_1 * w1_1 - w1_2; w2_2 = w2_1; w2_1 = w2; w2 = DOUBLE_COS_OMEGA_2 * w2_1 - w2_2; /* 更新Y */ y_A =int( COEF_1 * w1 - C1_COS_OMEGA_1 * w1_1 + C2_SIN_OMEGA_2 * w2_1)+AMP; y_B =int( COEF_2 * w2 - C2_COS_OMEGA_2 * w2_1 + C1_SIN_OMEGA_1 * w1_1)+AMP; /* digitalWrite */ for ( int i=0;i<5; i++){ BIT = (y_A&MASK[i])==0? LOW: HIGH; digitalWrite(CHA_PINS[i],BIT); BIT = (y_B&MASK[i])==0? LOW: HIGH; digitalWrite(CHB_PINS[i],BIT); } delayMicroseconds(1000); } ```

评论

留下评论

邮箱仅用于识别,不会公开

支持 KaTeX:$...$ 行内 · $$...$$ 独立公式
加载评论中...