▌01
1.裁判系统要求
在 测试ESP32S基本模块的功能,并验证是否可以应用在AI智能车竞赛检测激光信号中 测试了基于 ESP32 模块来检测 全国大学生智能车竞赛 中的 室内AI视觉组 的车模信号。其中包括两类信号:
- 第一类是车模想目标靶发送的 调制信号(125Hz)
- 第二类型号是车模运行在目标靶位前后位置检测信号;原来定义为光电检测信号。现在考虑将其改成 传统的比赛系统所使用的感应线圈。
▲ 赛道上的AprilTag和路边 的目标靶位
2.原来算法存在的问题
(1)存在的问题
在测试ESP32S基本模块的功能,并验证是否可以应用在AI智能车竞赛检测激光信号中给定的Python程序检测算法存在以下问题:
- 计算时间偏长;计算一个通道的信号大约需要40ms时间。这样两个通道就需要80ms。
- 所占用的内存偏高。原来的计算所使用的数据类型都是浮点数。cos,sin表格对应的内存过大。
(2)拟解决方案
将原来的计算都修改为整形数据的运算,同时解决前面的计算实际上以及所需要存储空间大的问题。
▌02
1.改进测试论证
(1)测试浮点数和整形数所占空间
【Ⅰ.测试内存大小方法】
在 Python 获得对象内存占用内存大小 sys.getsizeof 给出了测试python对象在内存中的大小方法。下面测试一下是否在MicroPython也可以使用。
在 Getting the size of an object in MicroPython - MicroPython Forum 给出了使用以下两个函数来获得对象所占用的内存的大小方法:
- gc.collect()
- gc.mem_free()
- 测量浮点list内存大小
结果:[1.24 for _ in range(512)] : 2096
from machine import Pin
import usys
gc.collect()
mem1 = gc.mem_free()
a = [1.24 for _ in range(512)]
gc.collect()
mem2 = gc.mem_free()
print(mem1, mem2, mem1-mem2)
- 测量整形list大小:
结果:[0 for _ in range(512)] : 2096
居然,在MicroPython中浮点数与整形数所占的内存是相同的。都是4个字节。
【II.整形数最大值】
根据 MicroPython usys.maxsize 给出测定当前平台上可以表达的最大整形数:32bit。
from machine import Pin
import usys
print('%x'%usys.maxsize)
7fffffff
因此,通过上面测试可以看到,使用整数和浮点数对于内存占用,在MicroPython中是无法改变的。
(2)测试浮点数和整形数乘加运算时间
【Ⅰ.测试代码】
from machine import Pin
import usys
import time
gc.collect()
mem1 = gc.mem_free()
a = [0.1 * i for i in range(512)]
b = [0.2 * i for i in range(512)]
startms = time.ticks_ms()
sum = 0
for i in range(512):
sum += (a[i] * b[i])
totalms = time.ticks_diff(time.ticks_ms(), startms)
print(totalms)
【Ⅱ.测试结果】
浮点数消耗时间:
运行时间:16ms 整形数消耗时间:
运行时间:12ms
可以看使用整形数进行计算,的确可以将计算机时间提高,但只是提高大约25%,并不明显。
2.提高系统时钟频率
(1)测量系统工作频率
from machine import Pin
import usys
import time
import machine
print(machine.freq())
machine.freq(240000000)
print(machine.freq())
运行结果:
160000000
240000000
这说明模块原来的工作频率就已经有160MHz,所以提高的240MHz可以提高50%的运行速度。
(2)重新测试运行时间
【Ⅰ.测试代码】
from machine import Pin
import usys
import time
import machine
print(machine.freq())
machine.freq(240000000)
print(machine.freq())
a = [0.1*i for i in range(512)]
b = [0.2*i for i in range(512)]
startms = time.ticks_ms()
sum = 0
for i in range(512):
sum += (a[i] * b[i])
totalms = time.ticks_diff(time.ticks_ms(), startms)
print(totalms)
【Ⅱ.测试结果】
运行时间: 11ms
通过测试结果来看,时间从16ms缩短到11ms,这与系统频率的提升(160MHz 提升到240MHz)是相对应的。
frequency must be 20MHz, 40MHz, 80Mhz, 160MHz or 240MHz
3. 优化运算结构
根据前面的测试,如果能够综合结合将计算类型修改成整型,并提高系统工作频率,这样就可以大约能够将速度提高1倍左右。
通过实际测试,修改成整形运算之后,在240Mhz运行,实际计算的时间只有8ms左右。
(1)修改程序结果
在前面的计算过程,由于使用了循环,这可能会浪费时间。如果将循环去掉,直接使用MicroPython内置的sum, zip函数,可以节省程序流程。比如:
sumd = sum([aa*bb for aa,bb in zip(a,b)])
此时,测试程序执行的时间只有:4ms! 到此为止,程序执行速度提高了整整 4倍。
▌03
1.构建整型的加窗sin.cos系数
(1)代码
def angle1(n):
return n*2*3.1415926*SAMPLE_PERIOD / 1000.0 * FREQUENCY_MOD
def wval(w):
if w < SAMPLE_NUM / 2:
return w * 2 / SAMPLE_NUM
else: return (SAMPLE_NUM - w) * 2 / SAMPLE_NUM
wdim = [wval(w) for w in range(SAMPLE_NUM)]
cosdim = [int(math.sin(angle1(a)) * wdim[a] * 0x7ff) for a in range(SAMPLE_NUM)]
sindim = [int(math.cos(angle1(a)) * wdim[a] * 0x7ff) for a in range(SAMPLE_NUM)]
(2)系数波形
▲ W数组
▲ sindim, cosdim 对应的数组
对于宽度为,高度为E的等腰三角信号对应的频谱为:
2.修改计算程序
(1)修改后的代码
def sample_amp(s):
global cosdim,sindim,sample_point
cos_sam = 0
sin_sam = 0
scopy = s.copy()
if sample_point > 0:
scopy = scopy[sample_point:] + scopy[:sample_point]
cos_sum = sum([s*w for s,w in zip(scopy,cosdim)]) / SAMPLE_NUM
sin_sum = sum([s*w for s,w in zip(scopy,sindim)]) / SAMPLE_NUM
return math.sqrt(cos_sum**2 + sin_sum**2)
(2)测试运行时间
计算出一个系数的时间:11ms。速度提高了大约 4倍。
3.测试检测性能
(1)频率响应曲线
测试所使用的程序:
ESP32 程序:参见附件1
测试程序:参见附件2:
使用 DG1062 产生峰峰值1V正弦波,频率从110Hz变化到140Hz,输入的ESP32AD通道。计算检测信号的输出如下图所示:
▲ 对于不同的频率检测到的125Hz的输出数值
这个波形符合理论上三角加窗函数的频谱值。第一个过零点分别位于121Hz与129Hz。是采样时间(0.5s)对应频率的两倍。
因此,经过改动之后,使用整型数所得到的结果与浮点数是相同的。
(2)检测检测时间
这是用来测量当光照在传感器上,到程序给出相应之间的时间差。
- 所使用的程序: 参见附件3
测试方案,是将上面的程序烧制在ESP32内部的boot.py。当检测到调制激光之后,便将GPIO15提高。外部使用ESP8266来测试对应的时间差。
测试时间差使用 ESP-12F模块转接板测试版调试说明,下载MicroPython程序。ESP8266-12F 模块。
▲ 测试响应时间
测试资源:
使用GPIO5:产生PWM(125Hz)调制激光管; – PIN12
使用GPIO4:测量时间差。–PIN11
【Ⅰ.测量代码】
from machine import Pin,PWM
import time
pin4 = Pin(4, Pin.IN, Pin.PULL_UP)
pwm = PWM(Pin(5))
pwm.freq(125)
pwm.duty(512)
def timedelay():
pwm.duty(0)
time.sleep_ms(500)
startms = time.ticks_ms()
pwm.duty(512)
while True:
if pin4.value() > 0:
break
totalms = time.ticks_diff(time.ticks_ms(), startms)
return totalms
while True:
print(timedelay())
【Ⅱ.测量结果】
将激光器距离光电管65厘米,测试一百次的检测结果,统计结果如下:
- 平均值: 201.2ms
- 方差:4.76
def thonnycmd(cmd):
tspsendwindowkey('Thonny', 's', alt=1, noreturn=1)
tspsendwindowkey('Thonny', '%s
'%cmd, noreturn=1)
def thonnyshs(cmd='', wait=0):
tspsendwindowkey('Thonny', 's', alt=1, noreturn=1)
if len(cmd) > 0:
tspsendwindowkey('Thonny', '%s
'%cmd, noreturn=1)
if wait > 0:
time.sleep(wait)
tspsendwindowkey('Thonny', 'ac', control=1, noreturn=1)
return clipboard.paste()
tspsendwindowkey('Thonny', 's', alt=1)
tspsendwindowkey('Thonny', 'ac', control=1)
pastestr = clipboard.paste()
v = [float(s) for s in pastestr.split('[')[-1].split(']')[0].split(',')]
printf(v)
printf(sum(v) / len(v))
printf(std(v))
如果将激光器靠近接收光电管,此时相应时间就会降低。如果远离,检测时间就会加长。这对应信号占整个采样时间比例不同。如果信号强,那么0.5秒中只需要200毫秒数据就可以检测到的阈值满足要求。如果信号强,这个时间可以短。如果信号弱,这个时间就会加长。时间变化从100ms到500ms。
▌实验总结
经过前面讨论,可以看到使用整形数来进行存储、计算激光信号。在MicroPython工作环境中,并没有减少对于内存的需求,但是通过提前构造已经加窗(三角窗口)的cos,sin整形数组,最终提高的监测计算速度。
经过测试可以看到计算一个通道的0.5秒(500个采样数据)对应的125Hz的频谱幅值,只需要11ms左右。检测两路则只需要22ms。这就大大提高的车模检测响应时间。
■ 相关文献链接:
- 测试ESP32S基本模块的功能,并验证是否可以应用在AI智能车竞赛检测激光信号中
- ESP32串口转WiFi双天线ESP32-S模组
- 第十六届全国大学智能汽车竞赛竞速比赛规则
- 第十六届全国大学生智能车竞赛竞速组-室内视觉组补充说明
- 傅里叶帮我看看,谁在照射我?
- 信标组裁判系统原理与实现
- Python 获得对象内存占用内存大小 sys.getsizeof
- Getting the size of an object in MicroPython - MicroPython Forum
- MicroPython usys.maxsize
- DG1062可编程信号源
▌附件
1.ESP32测试程序
#!/usr/local/bin/python
# -*- coding: gbk -*-
#============================================================
# TEST11.PY -- by Dr. ZhuoQing 2021-04-11
#
# Note:
#============================================================
from machine import Pin,ADC,PWM
import time
from machine import Timer
import math
import cmath
import machine
#------------------------------------------------------------
machine.freq(240000000)
#------------------------------------------------------------
pwm = PWM(Pin(2))
pwm.freq(125)
pwm.duty(512)
led = Pin(18, Pin.OUT)
#------------------------------------------------------------
adc1 = ADC(Pin(36))
adc2 = ADC(Pin(39))
adc3 = ADC(Pin(34))
adc1.atten(ADC.ATTN_6DB)
adc2.atten(ADC.ATTN_6DB)
adc3.atten(ADC.ATTN_6DB)
SAMPLE_NUM = const(500)
AVERAGE_NUM = 16
ad1dim = [0] * SAMPLE_NUM
ad2dim = [0] * SAMPLE_NUM
ad3dim = [0] * SAMPLE_NUM
sample_point = 0
stop_flag = 0
def ADC3Sample(_):
global ad1dim,ad2dim,ad3dim
global sample_point
global adc1,adc2,adc3
global stop_flag
ad1dim[sample_point] = adc1.read()
ad2dim[sample_point] = adc2.read()
ad3dim[sample_point] = adc3.read()
sample_point += 1
if sample_point >= SAMPLE_NUM:
sample_point = 0
#------------------------------------------------------------
SAMPLE_PERIOD = 1
time0 = Timer(0)
time0.init(period=SAMPLE_PERIOD, mode=Timer.PERIODIC, callback=ADC3Sample)
#------------------------------------------------------------
FREQUENCY_MOD = 125
def angle1(n):
return n*2*3.1415926*SAMPLE_PERIOD / 1000.0 * FREQUENCY_MOD
def wval(w):
if w < SAMPLE_NUM / 2:
return w * 2 / SAMPLE_NUM
else: return (SAMPLE_NUM - w) * 2 / SAMPLE_NUM
wdim = [wval(w) for w in range(SAMPLE_NUM)]
cosdim = [int(math.sin(angle1(a)) * wdim[a] * 0x7ff) for a in range(SAMPLE_NUM)]
sindim = [int(math.cos(angle1(a)) * wdim[a] * 0x7ff) for a in range(SAMPLE_NUM)]
#------------------------------------------------------------
def sample_amp(s):
global cosdim,sindim,sample_point
scopy = s.copy()
if sample_point > 0:
scopy = scopy[sample_point:] + scopy[:sample_point]
cos_sum = sum([s*w for s,w in zip(scopy,cosdim)]) / SAMPLE_NUM
sin_sum = sum([s*w for s,w in zip(scopy,sindim)]) / SAMPLE_NUM
return math.sqrt(cos_sum**2 + sin_sum**2)
#------------------------------------------------------------
time.sleep_ms(500)
startms = time.ticks_ms()
sample_amp(ad1dim)
totalms = time.ticks_diff(time.ticks_ms(), startms)
print(totalms)
while True:
ins = input('Input: ')
if ins == 'q':
break
print((sample_amp(ad1dim), sample_amp(ad2dim), sample_amp(ad2dim)))
time.sleep_ms(500)
#------------------------------------------------------------
# END OF FILE : test11.PY
#============================================================
2.测量不同频率下的检测输出
#!/usr/local/bin/python
# -*- coding: gbk -*-
#============================================================
# TEST2.PY -- by Dr. ZhuoQing 2021-04-11
#
# Note:
#============================================================
from headm import *
from tsmodule.tsvisa import *
def thonnycmd(cmd):
tspsendwindowkey('Thonny', 's', alt=1, noreturn=1)
tspsendwindowkey('Thonny', '%s'%cmd, noreturn=1)
def thonnyshs(cmd='', wait=0):
tspsendwindowkey('Thonny', 's', alt=1, noreturn=1)
if len(cmd) > 0:
tspsendwindowkey('Thonny', '%s'%cmd, noreturn=1)
if wait > 0:
time.sleep(wait)
tspsendwindowkey('Thonny', 'ac', control=1, noreturn=1)
return clipboard.paste()
#------------------------------------------------------------
dg1062open()
freqset = linspace(110, 140, 100)
value = []
for f in freqset:
dg1062freq(1, f)
time.sleep(1)
cmdstr = thonnyshs('\r',0.1).split('(')[-1].split(',')[-3]
value.append(float(cmdstr))
printff(f, cmdstr)
tspsave('Scan3', f=freqset, v=value)
plt.plot(freqset, value)
plt.xlabel("Frequency(Hz)")
plt.ylabel("Value")
plt.grid(True)
plt.tight_layout()
plt.show()
#------------------------------------------------------------
# END OF FILE : TEST2.PY
#============================================================
3.测量响应时间测试程序
#!/usr/local/bin/python
# -*- coding: gbk -*-
#============================================================
# TEST11.PY -- by Dr. ZhuoQing 2021-04-11
#
# Note:
#============================================================
from machine import Pin,ADC,PWM
import time
from machine import Timer
import math
import cmath
import machine
#------------------------------------------------------------
machine.freq(240000000)
#------------------------------------------------------------
led1 = Pin(18, Pin.OUT)
led2 = Pin(5, Pin.OUT)
checkpin = Pin(2, Pin.IN, Pin.PULL_UP)
outpin = Pin(15, Pin.OUT)
#------------------------------------------------------------
adc1 = ADC(Pin(36))
adc2 = ADC(Pin(39))
adc3 = ADC(Pin(34))
adc1.atten(ADC.ATTN_6DB)
adc2.atten(ADC.ATTN_6DB)
adc3.atten(ADC.ATTN_6DB)
SAMPLE_NUM = const(500)
AVERAGE_NUM = 16
ad1dim = [0] * SAMPLE_NUM
ad2dim = [0] * SAMPLE_NUM
ad3dim = [0] * SAMPLE_NUM
sample_point = 0
stop_flag = 0
def ADC3Sample(_):
global ad1dim,ad2dim,ad3dim
global sample_point
global adc1,adc2,adc3
global stop_flag
ad1dim[sample_point] = adc1.read()
ad2dim[sample_point] = adc2.read()
ad3dim[sample_point] = adc3.read()
sample_point += 1
if sample_point >= SAMPLE_NUM:
sample_point = 0
#------------------------------------------------------------
SAMPLE_PERIOD = 1
time0 = Timer(0)
time0.init(period=SAMPLE_PERIOD, mode=Timer.PERIODIC, callback=ADC3Sample)
#------------------------------------------------------------
FREQUENCY_MOD = 125
def angle1(n):
return n*2*3.1415926*SAMPLE_PERIOD / 1000.0 * FREQUENCY_MOD
def wval(w):
if w < SAMPLE_NUM / 2:
return w * 2 / SAMPLE_NUM
else: return (SAMPLE_NUM - w) * 2 / SAMPLE_NUM
wdim = [wval(w) for w in range(SAMPLE_NUM)]
cosdim = [int(math.sin(angle1(a)) * wdim[a] * 0x7ff) for a in range(SAMPLE_NUM)]
sindim = [int(math.cos(angle1(a)) * wdim[a] * 0x7ff) for a in range(SAMPLE_NUM)]
#------------------------------------------------------------
def sample_amp(s):
global cosdim,sindim,sample_point
scopy = s.copy()
if sample_point > 0:
scopy = scopy[sample_point:] + scopy[:sample_point]
cos_sum = sum([s*w for s,w in zip(scopy,cosdim)]) / SAMPLE_NUM
sin_sum = sum([s*w for s,w in zip(scopy,sindim)]) / SAMPLE_NUM
return math.sqrt(cos_sum**2 + sin_sum**2)
#------------------------------------------------------------
#time.sleep_ms(500)
#startms = time.ticks_ms()
#sample_amp(ad1dim)
#totalms = time.ticks_diff(time.ticks_ms(), startms)
#print(totalms)
#------------------------------------------------------------
print("Set pin2 low to return REPL..")
loopcount = 0
THRESHOLD_VAL = const(50000)
while True:
check1 = sample_amp(ad1dim)
check2 = sample_amp(ad2dim)
flag = 0
if check1 >= THRESHOLD_VAL:
led1.on()
flag = 1
else: led1.off()
if check2 >= THRESHOLD_VAL:
led2.on()
flag = 1
else: led2.off()
if flag == 0:
outpin.off()
else: outpin.on()
if checkpin.value() == 0:
break
print("User stop.")
#------------------------------------------------------------
#while True:
# ins = input('Input: ')
# if ins == 'q':
# break
# print((sample_amp(ad1dim), sample_amp(ad2dim), sample_amp(ad2dim)))
# time.sleep_ms(500)
#------------------------------------------------------------
# END OF FILE : test11.PY
#============================================================