电赛

容小狸 Lv3

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(屏幕刷新)

搞定,装箱,明天回家。

  • 标题: 电赛
  • 作者: 容小狸
  • 创建于 : 2023-08-04 00:11:22
  • 更新于 : 2023-08-04 00:11:22
  • 链接: https://blog.rongxiaoli.top/2023/08/03/TI-cup/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论