Day 1 我们组选了个B题,同轴电缆长度与终端负载检测
同轴电缆长度测定原理:
向同轴电缆发一道脉冲,由于一端开路,同轴电缆会反弹一道脉冲,这就意味着在同轴线一端测量波形,我们能得到两个波形:一个是发射波,一个是反弹波。
 
GitHub上捞了个代码 ,但是ChibiOS/os/hal/ports/STM32/STM32F0xx/hal_lld.c报错,换成另一个 就搞定了(第二个现在还在更新,而且两个差别其实不是特别大,我们就可以比较方便地套用了)
主要是仪器还没到,不能刷固件确认。其实只要固件有了,基本都没什么太大问题,然后就是写论文了。
睡觉了,等第二天器材到了就开始忙了。
Day 2 快递早上就到了,接下来就是用micropython对NanoVNA的操作了。
之前没搞过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就没有问题。类似问题的帖子 ,提到:
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'  
貌似没问题,时间来不及了,没时间验证,明天再说。
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,剩下的方案执行交给佬们。
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
 
依赖安装貌似还是有点问题,总之先这么装:
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点了。
graph TD
GlobalCapacitance(线路阻抗)
InitBox(初始化) --> OLEDRefresh(屏幕刷新)
OLEDRefresh --> Length[测量线长] --> MesureLength[扫频并获取频点数据 
搞定,装箱,明天回家。