Day 1
我们组选了个B题,同轴电缆长度与终端负载检测
上午还真没啥事,8点等题,上午买买器材,吃个午饭
同轴电缆长度测定原理: >
向同轴电缆发一道脉冲,由于一端开路,同轴电缆会反弹一道脉冲,这就意味着在同轴线一端测量波形,我们能得到两个波形:一个是发射波,一个是反弹波。
GitHub上捞了个代码 ,但是ChibiOS/os/hal/ports/STM32/STM32F0xx/hal_lld.c报错,换成另一个 就搞定了(第二个现在还在更新,而且两个差别其实不是特别大,我们就可以比较方便地套用了)
主要是仪器还没到,不能刷固件确认。其实只要固件有了,基本都没什么太大问题,然后就是写论文了。
我们也是有能干起来的活的,比如测量的UI,不知道我能不能帮的上忙。(我C稀碎,而且python也是完全不会)
睡觉了,等第二天器材到了就开始忙了。
Day 2
快递早上就到了,接下来就是用micropython对NanoVNA的操作了。
之前没搞过micropython,踩了一些坑,跟着这篇文章 走了一遍,就能上了。
首先是环境,安装所需的requirements,然后看到有个模块叫machine
这个模块是micropython生成的,所以应该这么搞: 进入虚拟环境:
1 source ./venv/bin/activate
Windows:
1 ./venv/scripts/activate.bat
之后(使用阿里云的镜像加速):
1 pip install micropy-cli esptool -i https://mirrors.aliyun.com/pypi/simple
擦除ESP32S3的固件:
1 esptool.py --chip esp32s3 --port /dev/yourDevice erase_flash
将micropython的固件刷上ESP32:
1 esptool.py --chip esp32s3 --port /dev/youtDevice write_flash -z 0 /path/to/BIN_FILE.bin
串口连接ESP32S3,波特率115200,就可以了。
第一天的时候其实是这样的:打算使用官方的开源代码进行逆向,获取数据。今天找到了这个:
有这个软件:NanoVNA-Saver ,是在Windows、Linux和MacOS上用于提取NanoVNA的数据的,也就是说NanoVNA-H使用串口进行调试,那么就可以通过串口指令提取数据:
> A multiplatform tool to save Touchstone files from the NanoVNA,
sweep frequency spans in segments to gain more than 101 data points, and
generally display and analyze the resulting data. > > This
software connects to a NanoVNA and extracts the data for display on a
computer and allows saving the sweep data to Touchstone files.
接下来就是串口截取数据了。Windows下可以使用CommMonitor 进行监视,Linux一开始用的是这个“HighSpeedSerialMonitor” ,但是用这个的话会报错闪退,ERROR
- too many
retries。我猜估计是占用问题,Windows下那个CommMonitor就没有问题。
搜到stackexchange.com貌似有个类似问题的帖子 ,提到:
> Googling
“socat serial port pty tee debug” will point you to several “standard
procedure” examples, one being:
1 2 socat /dev/ttyS0,raw,echo =0 \ SYSTEM:'tee in.txt | socat - "PTY,link=/tmp/ttyV0,raw,echo=0,waitslave" | tee out.txt'
貌似没问题,时间来不及了,没时间验证,明天再说。
明天要验证socat端口转发,两个佬晚上通宵搞定软件和硬件,明天写完论文就搞定了。
Day 3
nanovna.py line 260 to line 270:
1 2 3 4 5 6 7 8 9 10 11 def tdr (self, x ): pl.grid(True ) window = np.blackman(len (x)) NFFT = 256 td = np.abs (np.fft.ifft(window * x, NFFT)) time = 1 / (self .frequencies[1 ] - self .frequencies[0 ]) t_axis = np.linspace(0 , time, NFFT) pl.plot(t_axis, td) pl.xlim(0 , time) pl.xlabel("time (s)" ) pl.ylabel("magnitude" )
问题是Micropython没有numpy,还有一个方案是捞个树莓派用,但是我觉得很臃肿,试着手搓一下Blackman,ittf和linspace,剩下的方案执行交给佬们。
linespace:
1 2 3 4 5 6 7 8 9 def linspace (start, stop, num=50 , endpoint=True , retstep=False , dtype=None , axis=0 ): out = [ float (start) ] if not endpoint: delta = float ((stop - start) / (num)) else : delta = float ((stop - start) / (num - 1 )) for i in range (0 ,num - 1 ): out.append(float (out[i] + delta)) return out
验证:
1 2 3 4 5 6 7 8 9 def linspaceTest (): result = libNumpy.linspace(2.0 , 3.0 , num=5 ) print (result) print ("Should be: [2. , 2.25, 2.5 , 2.75, 3. ]" ) result = libNumpy.linspace(2.0 , 3.0 , num=5 , endpoint=False ) print (result) print ("Should be: [2. , 2.2, 2.4, 2.6, 2.8]" )
强制转换的时候精度丢了,要改。
佬们做完树莓派的硬件适配了,可以不用管了,等会儿直接上树莓派就行了。
摸了一下午鱼,佬们把python写完了,捞了份源码下来。 > CR.py >
main.py > nanovna.py > TDR.py > utils (dir) > ├──
Formatting.py > ├── RFTools.py > └── SITools.py
依赖安装貌似还是有点问题,总之先这么装:
1 2 3 pip install luma pip install luma.core pip install luma.oled
main.py为入口:
1 2 3 4 5 6 7 8 if __name__ == "__main__" : GPIO_init() _cr() while True : if not GPIO.input (4 ): _cr() if not GPIO.input (17 ): _tdr()
首先初始化GPIO:
1 2 3 4 def GPIO_init (): GPIO.setmode(GPIO.BCM) GPIO.setup(4 , GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(17 , GPIO.IN, pull_up_down=GPIO.PUD_UP)
第4,17针脚用于接出两个按钮,所以是GPIO.IN,具体定义见GPIO。
接下来准备屏幕:
1 2 3 4 5 6 7 def _cr (): print ("CR" ) stats(oled, 1 , 0 , 0 , "Unknown" ) length, terminal_category, terminal_parameter = cr(nv, 97e-12 ) stats(oled, 0 , length, terminal_category, terminal_parameter)
stats()用于更新屏幕,画出屏幕上的东西,比如各种参数:
1 2 3 4 5 6 7 8 9 10 11 12 def stats (oled, Condition, length, terminal_category, terminal_parameter ): oled.clear() with canvas(oled) as draw: draw.text((1 ,10 ), "condition" , fill="white" ) draw.text((1 ,20 ), "length" , fill="white" ) draw.text((1 ,30 ), "Load_Type" , fill="white" ) draw.text((1 ,40 ), "Load_Para" , fill="white" ) draw.text((80 ,10 ), str (Piget_condition(Condition)), fill="white" ) draw.text((50 ,20 ), f"{(length*100 ):.2 f} cm" , fill="white" ) draw.text((60 ,30 ), str (Piget_terminal_category(terminal_category)[0 ]), fill="white" ) draw.text((60 ,40 ), str (terminal_parameter), fill="white" ) draw.text((80 ,40 ), str (Piget_terminal_category(terminal_category)[1 ]), fill="white" )
还挺显而易见的。
Piget_terminal_category()用于返回获取终端负载的类型的状态和单位,有开路,容性负载,和电阻:
1 2 3 4 5 6 7 8 def Piget_terminal_category (terminal_category ): if terminal_category ==0 : Show_terminal_category = ["open" ,"" ] elif terminal_category ==1 : Show_terminal_category = ["C" ,"pF" ] elif terminal_category ==2 : Show_terminal_category = ["R" ,"Ohm" ] return Show_terminal_category
Piget_condition()用来获取树莓派的状态的文本,0是正在等待,1是正在测量:
1 2 3 4 5 6 def Piget_condition (Condition ): if Condition == 1 : Show_Condition = "TESTING" elif Condition ==0 : Show_Condition = "HOLD" return Show_Condition
_cr()是用来提取从vna获取的数据的:
1 2 3 4 5 6 7 def _cr (): print ("CR" ) stats(oled, 1 , 0 , 0 , "Unknown" ) length, terminal_category, terminal_parameter = cr(nv, 97e-12 ) stats(oled, 0 , length, terminal_category, terminal_parameter)
_tdr()也差不多:
1 2 3 4 5 6 def _tdr (): print ("TDR" ) stats(oled, 1 , 0 , 0 , "Unknown" ) length, terminal_category, terminal_parameter = cr(nv, 97e-12 ) stats(oled, 0 , length, terminal_category, terminal_parameter)
cr()用于从vna生数据做转换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 def cr (nv, offset ): """ nv: NanoVNA对象; offset: 每米线电容大小(单位:F), 1F = 1e12pF Return """ cable_len, s11 = tdr(nv, 50000 , 200000000 ) cable_len, s11 = tdr(nv, 50000 , 200000000 ) print (cable_len) if cable_len <= 2.5 : cable_len, s11 = tdr(nv, 50000 , 1500000000 ) cable_len, s11 = tdr(nv, 50000 , 1500000000 ) print (cable_len) elif cable_len <= 5 : cable_len, s11 = tdr(nv, 50000 , 750000000 ) cable_len, s11 = tdr(nv, 50000 , 750000000 ) print (cable_len) imp = RFTools.gamma_to_impedance(complex (s11[1 ][0 ][0 ], s11[1 ][0 ][1 ])) imp_p = RFTools.serial_to_parallel(imp) capacitance = RFTools.impedance_to_capacitance(imp, 500000 ) resistance = imp_p.real if capacitance - (offset * cable_len) >= 90e-12 : cap_str = format_capacitance( RFTools.impedance_to_capacitance(imp, 500000 ) ) print (f'类型:电容 电容值:{cap_str} 线长:{cable_len} ' ) return cable_len, 1 , cap_str if 0 < resistance <= 60 : resistance_str = format_resistance(imp_p.real) print (f'类型:电阻 电阻值:{resistance_str} 线长:{cable_len} ' ) return cable_len, 2 , resistance_str if not ((capacitance - (offset * cable_len) >= 90e-12 ) or (0 < resistance <= 60 )): print (f'开路 线长:{cable_len} ' ) return cable_len, 0 , 0 print (capacitance) print (resistance) cap_str = format_capacitance( RFTools.impedance_to_capacitance(imp, 500000 ) ) resistance_str = format_resistance(imp_p.real) print (f'电容值为:{cap_str} 电阻值为:{resistance_str} 电缆长度为:{cable_len} m' )
tdr()用于获取vna生数据:
1 2 3 4 5 6 7 8 9 10 11 def tdr (nv, start, end ): nv.fetch_frequencies() nv.fetch_frequencies() s11 = nv.new_scan(start, end) t_axies = nv.tdr(s11[0 ]) d_axies = t_axies[0 ] * 0.71 * speed_of_light index_peak = np.argmax(t_axies[1 ]) cable_len = d_axies[index_peak] / 2 return cable_len, s11
RFTools.py是用来计算的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def gamma_to_impedance (gamma: complex , ref_impedance: float = 50 ) -> complex : """Calculate impedance from gamma""" try : return ((-gamma - 1 ) / (gamma - 1 )) * ref_impedance except ZeroDivisionError: return math.inf def impedance_to_capacitance (z: complex , freq: float ) -> float : """Calculate capacitive equivalent for reactance""" if freq == 0 : return -math.inf return math.inf if z.imag == 0 else -(1 / (freq * 2 * math.pi * z.imag)) def impedance_to_inductance (z: complex , freq: float ) -> float : """Calculate inductive equivalent for reactance""" return 0 if freq == 0 else z.imag * 1 / (freq * 2 * math.pi) def serial_to_parallel (z: complex ) -> complex : """Convert serial impedance to parallel impedance equivalent""" z_sq_sum = z.real**2 + z.imag**2 if z.real == 0 and z.imag == 0 : return complex (math.inf, math.inf) if z.imag == 0 : return complex (z_sq_sum / z.real, math.copysign(math.inf, z_sq_sum)) if z.real == 0 : return complex (math.copysign(math.inf, z_sq_sum), z_sq_sum / z.imag) returnrs complex (z_sq_sum / z.real, z_sq_sum / z.imag)
SITools.py是用来写科学计数法的。
Day 4
上面这个代码分析算是半梦半醒之间写出来的,弄完已经第二天早上6点了。
睡到中午12点,佬们开始重构代码了,要我读一遍写个框图。
graph TD
GlobalCapacitance(线路阻抗)
InitBox(初始化) --> OLEDRefresh(屏幕刷新)
OLEDRefresh --> Length[测量线长] --> MesureLength[扫频并获取频点数据 Gamma转负阻抗 串联阻抗转并联阻抗 阻抗转容性负载] --> |存储| GlobalCapacitance(线路阻抗) --> OLEDRefresh1
GlobalCapacitance1(线路阻抗) --> CalculateLoad
OLEDRefresh --> Load[测量负载] --> MesureLoad[扫频并获取频点数据 Gamma转负阻抗 串联阻抗转并联阻抗 阻抗转容性负载] --> CalculateLoad[计算负载] --> OLEDRefresh1(屏幕刷新)
搞定,装箱,明天回家。