ESP32检测调制激光信号程序优化

 

01 调制激光检测


1.裁判系统要求

测试ESP32S基本模块的功能,并验证是否可以应用在AI智能车竞赛检测激光信号中 测试了基于 ESP32 模块来检测 全国大学生智能车竞赛 中的 室内AI视觉组 的车模信号。其中包括两类信号:

▲ 赛道上的AprilTag和路边 的目标靶位

▲ 赛道上的AprilTag和路边 的目标靶位

2.原来算法存在的问题

(1)存在的问题

测试ESP32S基本模块的功能,并验证是否可以应用在AI智能车竞赛检测激光信号中给定的Python程序检测算法存在以下问题:

  • 计算时间偏长;计算一个通道的信号大约需要40ms时间。这样两个通道就需要80ms。
  • 所占用的内存偏高。原来的计算所使用的数据类型都是浮点数。cos,sin表格对应的内存过大。
(2)拟解决方案

将原来的计算都修改为整形数据的运算,同时解决前面的计算实际上以及所需要存储空间大的问题。

 

02 程序优化验证实验


1.改进测试论证

(1)测试浮点数和整形数所占空间
【Ⅰ.测试内存大小方法】

Python 获得对象内存占用内存大小 sys.getsizeof 给出了测试python对象在内存中的大小方法。下面测试一下是否在MicroPython也可以使用。

  • 通过测试,发现在MicroPython中,对应的sys没有对应的 getsizeof()的函数。

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数组

▲ W数组

▲ sindim, cosdim 对应的数组

▲ sindim, cosdim 对应的数组

对于宽度为 τ \tau τ,高度为E的等腰三角信号 f ( t ) f\left( t \right) f(t)对应的频谱为:

F t r i a n g l e ( ω ) = E τ 2 S a 2 ( ω t 4 ) F_{triangle} \left( \omega \right) = {{E\tau } \over 2}Sa^2 \left( {{{\omega t} \over 4}} \right) Ftriangle(ω)=2EτSa2(4ωt)

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的输出数值

▲ 对于不同的频率检测到的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。这就大大提高的车模检测响应时间。

 


■ 相关文献链接:

 

▌附件


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
#============================================================

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卓晴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值