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) ] #try to init output.
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)) # For each step, add the delta.
return out

验证:

1
2
3
4
5
6
7
8
9
def linspaceTest():
# Test 1:
result = libNumpy.linspace(2.0, 3.0, num=5)
print(result)
print("Should be: [2. , 2.25, 2.5 , 2.75, 3. ]")
# Test 2:
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() # init GPIO on rasp
_cr() # draw screen
while True:
if not GPIO.input(4):
_cr() # almost the same as _tdr()
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) # read from vna
#stats(oled, 1, length, terminal_category, terminal_parameter)
stats(oled, 0, length, terminal_category, terminal_parameter) # render
# cr(nv, 97e-12)

stats()用于更新屏幕,画出屏幕上的东西,比如各种参数:

1
2
3
4
5
6
7
8
9
10
11
12
def stats(oled, Condition, length, terminal_category, terminal_parameter): #render func
oled.clear() #clear the screen
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):.2f} 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): # terminal type
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): # current pi 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) # read from vna
#stats(oled, 1, length, terminal_category, terminal_parameter)
stats(oled, 0, length, terminal_category, terminal_parameter) # render
# cr(nv, 97e-12)

_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) #read from vna
#stats(oled, 1, length, terminal_category, terminal_parameter)
stats(oled, 0, length, terminal_category, terminal_parameter) # render

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) # 200 MHz :线材够长
cable_len, s11 = tdr(nv, 50000, 200000000)
print(cable_len)
if cable_len <= 2.5:
cable_len, s11 = tdr(nv, 50000, 1500000000) # 1.5 GHz:线材小于2.5米
cable_len, s11 = tdr(nv, 50000, 1500000000)
print(cable_len)
elif cable_len <= 5:
cable_len, s11 = tdr(nv, 50000, 750000000) # 750 MHz :线材小于5米
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(
# 500000表示500kHz
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(
# 500000表示500kHz
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)
# print(s11[1][0])
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
# print(f'Cable length: {cable_len} m')
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(屏幕刷新)

搞定,装箱,明天回家。

⬆︎TOP