基于ESP32的智能车竞赛新版裁判系统的软件功能要求与实现
Posted 卓晴
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于ESP32的智能车竞赛新版裁判系统的软件功能要求与实现相关的知识,希望对你有一定的参考价值。
简 介: 本文给出了第十六届智能车竞赛裁判系统最终的软件设计和功能实现。并进行了验证,是它可以满足比赛基本计时要求。 最后给出了完整的程序。
关键词
: 智能车竞赛,ESP32,视觉AI
§01 智能车竞赛裁判系统
在 基于ESP32智能车竞赛裁判系统第二版硬件调试-6-26 对于 智能车竞赛裁判系统 的第二版本的硬件进行初步调试。下面对于它软件进行实现和设计。
在 基于ESP32的竞赛裁判系统功能调试-与微机通讯 已经就竞赛软件通信协议进行了初步实现。
1、功能设定
(1)接口定义
下图显示了主控核心板的外部接口位置和功能。
▲ 图1.1 主控板的外部接口
端口名称 | 功能说明 | 备注 |
---|---|---|
电源接口 | 提供给出主板电源; 可以外接+5V电源,或者4.8V锂电池 | 如果USB接口连接到PC机USB,主板电源由USB接口提供 |
Type-C USB接口 | 主机与计算机通信 | 该端口同时提供主板工作电源 |
功能设定拨码开关 | 设定模块工作模式以及检测灵敏度 | 详细参见后面的【表格1-2 主板拨码开关设置】 |
计时线圈1 | 连接外部计时线圈1 | 在工作时计时线圈与光电板只能连接其中一个 |
计时线圈2 | 连接外部计时线圈2 | 在工作时计时线圈与光电板只能连接其中一个 |
光电板1 | 连接外部计时光电板1 | 在工作室计时线圈与光电板只能连接其中一个 |
光电板2 | 连接外部计时光电板2 | 在工作室计时线圈与光电板只能连接其中一个 |
目标板接口 | 连接带有激光检测功能的目标板 |
(2)工作模式
SW1 | SW2 | SW3 | SW4 |
---|---|---|---|
检测灵敏度设置 | 功能设置1 | 功能设置2 | 功能设置3 |
-
● 检测灵敏度设置(SW1):
-
(1) OFF
:灵敏度高
(2) ON
:灵敏度低
● 功能设置(SW2,SW3,SW4):
-
(1) OFF,OFF,OFF
:普通计时
(2) ON,OFF,OFF
:(待定)
(3) OFF,ON,OFF
:(待定)
(4) ON,ON,OFF
:(待定)
(5) OFF,OFF,ON
:水果激光检测
(6) ON,OFF,ON
:动物激光检测
(7) OFF,ON,ON
:奇数三岔路口
(8) ON,ON,ON
:偶数三岔路口
2、工作模式
下面对于裁判系统的各个工作模式进行介绍。设定裁判硬件工作模式是通过拨码开关中 SW2,3,4
进行定义的。
工作模式的初始化有两种方式:
- 核心模块重新上电;
- 任何一个拨码开关变动(
SW1,SW2,SW3,SW4
); - LED,SPEAKER显示板后面的复位按钮;详见【1-4-1:增加一个按钮】
注:由于原来的电路板没有设置一个专门的按钮,用来表示模块的初始化,所以建议在后期的裁判系统中设置一个专门的按钮,用来模块的初始化。
下面给出不同模式的功能描述。其中:
- 对于检测车模通过的方法有线圈和LED光板两种,它们的功能是相同的,因此在叙述的时候只叙述检测线圈;
- 检测激光传感器包括中心店和边缘点检测两种;
关于应用于室内AI视觉组别(水果、动物、数字识别)的相关规则,来自于 AI视觉组基于ESP32的裁判系统第一版本设计要求 中的响应描述,相关文献还包括 第十六届全国大学生智能车竞赛竞速组-室内视觉组补充说明 。
工作模式 | SW设置 | 功能描述 |
---|---|---|
普通计时 | OFF,OFF,OFF | 这种模式应用于普通计时功能。任何一个计时线圈被触发都可以触发计时开始与结束。它们的功能是相同的。 |
(待定1) | ON,OFF,OFF | |
(待定2) | OFF,ON,OFF | |
(待定3) | ON,ON,OFF | |
水果激光检测 | OFF,OFF,ON | (1)在初始状态下,中心光电检测到激光,四周光电没有检测到激光表示打靶成功; (2)但如果四周光电检测到激光,无论中心是否检测到激光都表示打靶失败; (3)打靶成功和打靶失败进入结果显示状态;五秒钟之后返回初始状态。在状态显示时,不检测激光信号。 (4)打靶成功,绿色LED点亮,蜂鸣器持续响;打败识别,红灯断续闪烁,蜂鸣器断续鸣响,10Hz,占空比50%。 (5)如果两个线圈在前后5秒钟之内相继被触发,但没有打靶(打靶成功,或者失败),红色和绿色LED以及蜂鸣器都同时断续打开,表示车模没有安装要求进行打靶。 |
动物激光检测 | ON,OFF,ON | (1)两个检测线圈相继被触发,如果时间小于3秒钟表示停车动作失败,红色LED闪烁,蜂鸣器断续鸣响,时间5秒; (2)如果时间大于3秒,则表示停车动作成功,绿色灯点亮,蜂鸣器持续鸣响,时间5秒。 (3)如果中心和四周光电检测到信号则表示目标识别错误,红灯闪烁,蜂鸣器断续鸣响; |
三岔路口(奇数数字) | OFF,ON,ON | 同时检测线圈1,线圈2 线圈1被触发,点亮左边绿灯 线圈2被触发,点亮右边红灯 |
三岔路口(偶数数字) | ON,ON,ON | 同时检测线圈1,线圈2 线圈1被触发,点亮左边绿灯 线圈2被触发,点亮右边红灯 |
3、用于触发目标板的激光信号
根据以下博文的工作:
用于触发目标板的激光信号需要使用小型的半导体激光器发送的红色调制激光信号,激光信号的调制频率为125Hz。调制的方法是使用方波电压信号驱动激光器。
下面是在 基于ESP32智能车竞赛比赛系统硬件初步调试-5-6 中使用Arduino Nano驱动的小型半导体激光器产生调制的触发信号。该信号可以使用任何其他的信号源来产生,只要其中包括有125Hz的调制信号即可,占空比也可以不是50% 。
▲ 图1.2 利用Arduino Nano 驱动的小型半导体激光器
- 利用D2输出125Hz的50%占空比的波形。
/*
**==============================================================================
** TEST2PWM.C: -- by Dr. ZhuoQing, 2021-06-07
**
**==============================================================================
*/
#define ON(pin) digitalWrite(pin, HIGH)
#define OFF(pin) digitalWrite(pin, LOW)
#define VAL(pin) digitalRead(pin)
#define IN(pin) pinMode(pin, INPUT)
#define OUT(pin) pinMode(pin, OUTPUT)
const int LED_PIN = 2;
//------------------------------------------------------------------------------
void setup(void) {
pinMode(LED_PIN, OUTPUT);
}
//------------------------------------------------------------------------------
void loop(void) {
ON(LED_PIN);
delay(4);
OFF(LED_PIN);
delay(4);
}
//==============================================================================
// END OF FILE : TEST2PWM.C
//------------------------------------------------------------------------------
▲ 图1.3 Arduino PIN2输出的125Hz 的激光驱动调制信号
4、模块硬件、软件修改建议
(1)增加一个按钮
增加一个按钮,用于以下功能:
- 用户对于不同功能进行初始化;
- 进行修改参数;
将Ai-一体板上的接口上的PIN6(对应 ESP32 GPIO32
)按钮一个按钮。用于作为初始功能使用。
▲ 图1.4 将LED,Speaker板增加一个按钮
▲ 图1.4.1 在AI-一体板上的按钮
§02 软件实现
1、计时功能
首先将SW开关设置为:
SW1 | SW2 | SW3 | SW4 |
---|---|---|---|
OFF或者ON | OFF | OFF | OFF |
注:SW1设置检测灵敏度,它设置ON,OFF都不影响工作模式
- 硬件配置: 只需要连接一个检测线圈即可。
(1)接入USB
使用Type-C USB电缆将核心模块接入PC。确认微机设备管理-端口
出现USB-SERIAL CH340(COMn)
串口。其中COMn中的端口号跟具体微机配置有关系。假设模块对应的USB的串口为COM9
。
▲ 图2.1 出现USB-SERIAL CH340虚拟串口
(2)测试比赛程序
打开智能车竞赛比赛系统软件,选择“预赛”控制面板。通过“打开串口”右侧的选择栏选择串口“COM9”,然后按动“打开串口”,可以看到下面信息串口出现“Open COM9:115200 OK !
”提示。同时在“当前时间”右侧的时间栏出现时间递增的变化。这说明比赛系统已经能够正常使用了。
▲ 图2.2 通过比赛系统打开串口连接硬件
2、三岔路口
SW1 | SW2 | SW3 | SW4 |
---|---|---|---|
OFF或者ON | ON 或者OFF | ON | ON |
注:SW1设置检测灵敏度,它设置ON,OFF都不影响工作模式;
- 硬件配置: 需要将两个两个线圈都连接上。将线圈1放置在三岔路口的左侧;将线圈2放置在三岔路口的右侧。
(1)测试步骤
使用测试磁铁分别划过两个线圈,可以看到指示板上的红色和绿色的LED被分别点亮。按动背面的按钮可以将它们熄灭。
▲ 图2.3 测试三岔路口功能
3、水果标靶
工作模式SW设置如下表所示:
SW1 | SW2 | SW3 | SW4 |
---|---|---|---|
ON或者OFF | OFF | OFF | ON |
- 硬件配置: 将两个线圈布置在Apriltag码前后各50厘米的位置;
(1)车模直通
车模通过两个检测线圈,并没有停止完成靶标射击;此时指示牌显示错误,也就是红灯闪烁;
下图显示了手持磁铁连续通过两个线圈,并没有停留打靶,指示牌显示错误。
▲ 图2.4.1 手持磁铁直接通过两个线圈,没有停留
(2)车模射击到靶标中心
车模在Apriltag区域停止,直接瞄准靶标射击;在此过程中,没有射击到靶标边缘;此时指示牌显示成功,绿灯点亮;
在绿灯点亮的过程中,即使激光再次投射到靶标边缘,也不会报错;
下面显示了激光直接照射靶心的光电管,指示板显示正确。
▲ 图2.4.2 使用激光直接照射靶心光电管,指示板显示正确
(3)车模射击到靶标边缘
车模在经过Apriltag区域,使用激光射击到靶标边缘,指示板显示错误,即红灯闪烁。
下图显示了激光照射到靶标的边缘,引起指示牌显示错误。
▲ 图2.4.3 激光照射到靶标边缘,引起指示板显示错误
4、动物标靶
工作模式SW的设置如下:
SW1 | SW2 | SW3 | SW4 |
---|---|---|---|
ON或者OFF | ON | OFF | ON |
- 硬件配置: 将两个线圈布置在Apriltag码前后各50厘米的位置;
(1)车模直通
车模通过两个检测线圈,并没有在两个线圈之间停留够3秒钟;此时指示牌显示错误,也就是红灯闪烁;
下图手持磁铁直接通过两个线圈,模拟车模没有停留超过三秒,此时指示板显示错误。
▲ 图2.4.1 手持磁铁直接通过两个线圈,没有停留
(2)车模停留三秒钟
车模通过第一个线圈,在Apriltag区域停留超过三秒钟(小于六秒钟),然后在通过第二个线圈,指示牌显示正确,绿灯点亮;
如果停留时间超过6秒钟,通过第二个线圈,指示牌不显示,也不报错。
下图显示了 手持磁铁经过第一个线圈,停留超过3秒在经过第二个线圈,指示板显示正确。
▲ 图2.4.2 手持磁铁经过第一个线圈,停留超过3秒在经过第二个线圈
(3)车模射击到靶标中心
车模在Apriltag区域停止,直接瞄准靶标射击;在此过程中,没有射击到靶标边缘;指示板显示错误,即红灯闪烁。
下图显示了 靶标中心被激光照射,靶标显示错误的过程。
▲ 图2.5.1 靶标中心被激光照射,靶标显示错误
(4)车模射击到靶标边缘
车模在经过Apriltag区域,使用激光射击到靶标边缘,指示板显示错误,即红灯闪烁。这个过程与上面的靶标中心被激光照射的效果一样。
▌附件
1、ESP32程序
#!/usr/local/bin/python
# -*- coding: gbk -*-
#============================================================
# TESTALL.PY -- by Dr. ZhuoQing 2021-06-13
#
# Note:
#============================================================
from machine import UART,Pin,Timer,ADC,PWM
import time
import machine, math
#------------------------------------------------------------
machine.freq(240000000)
#------------------------------------------------------------
adc1 = ADC(Pin(36))
adc2 = ADC(Pin(39))
adc3 = ADC(Pin(34))
adc4 = ADC(Pin(35))
adc1.atten(ADC.ATTN_6DB)
adc2.atten(ADC.ATTN_6DB)
adc3.atten(ADC.ATTN_6DB)
adc4.atten(ADC.ATTN_6DB)
#------------------------------------------------------------
button = Pin(32, Pin.IN, Pin.PULL_UP)
sw4 = Pin(15, Pin.IN, Pin.PULL_UP)
sw3 = Pin(2, Pin.IN, Pin.PULL_UP)
sw2 = Pin(19, Pin.IN, Pin.PULL_UP)
sw1 = Pin(4, Pin.IN, Pin.PULL_UP)
led1 = Pin(5, Pin.OUT)
led2 = Pin(18, Pin.OUT)
led1.off()
led2.off()
bz1 = Pin(21, Pin.OUT)
bz1.off()
gled = Pin(25, Pin.OUT)
rled = Pin(33, Pin.OUT)
speaker = Pin(26, Pin.OUT)
gled.off()
rled.off()
speaker.off()
def sw1234Read():
workmode = 0
if sw2.value() == 0: workmode |= 0x1
if sw3.value() == 0: workmode |= 0x2
if sw4.value() == 0: workmode |= 0x4
return sw1.value(), workmode
#------------------------------------------------------------
WORKMODE_TIMER = 0 # Check two line
WORKMODE_NULL1 = 1 #
WORKMODE_NULL2 = 2 #
WORKMODE_NULL3 = 3 #
WORKMODE_FRUIT = 4 # Target is fruit, check laser
WORKMODE_ANIMAL = 5 # Target is Animal, check laser
WORKMODE_DIGIT1 = 6 # Target is digit, check first pass
WORKMODE_DIGIT2 = 7 # Target is digit, check first pass
#------------------------------------------------------------
SAMPLE_NUM = const(500)
ad1dim = [0] * SAMPLE_NUM
ad2dim = [0] * SAMPLE_NUM
LASER_THRESHOLD = 50000 # Laser light check threshold
SAMPLE_AVERAGE_LENGTH = 40
ad3average = [0] * SAMPLE_AVERAGE_LENGTH
ad4average = [0] * SAMPLE_AVERAGE_LENGTH
ad34point = 0
ad3sigma = 0
ad4sigma = 0
#------------------------------------------------------------
AD34_BASE_ALPHA = 0.0005
ad3baseline = 0
ad4baseline = 0
AD34_CHECK_THRESHOLD_LOW = 250
AD34_CHECK_THRESHOLD_HIGH = 500
AD34_CHECK_THRESHOLD = AD34_CHECK_THRESHOLD_HIGH
ad3checktime = 0
ad4checktime = 0
#------------------------------------------------------------
sample_mode = 0 # 0 : sample adc3, adc4
# 1 : sample adc1, adc2
#------------------------------------------------------------
sample_point = 0
stop_flag = 0
total_count = 0
#------------------------------------------------------------
uart1 = UART(2, baudrate=115200, rx=16, tx=17, timeout=10)
#------------------------------------------------------------
DSCMD_NONE = 0xff
DSCMD_HELLO = 0x00
DSCMD_INIT = 0x01
DSCMD_STARTSEND = 0x10
DSCMD_STOPSEND = 0x11
DSCMD_BEEP = 0x12
DSCMD_SENDSNAPSHOT = 0x13
DSCMD_TURNOFFLIGHT = 0x14
DSCMD_SETCONTINUECHECK = 0x15
DSCMD_SETBEACONSEQUENCY = 0x16
DSCMD_BEACONSTART = 0x17
DSCMD_BEACONSTOP = 0x18
DSCMD_GETBEACONSTATE = 0x19
DSCMD_BEACONINIT = 0x1a
DSRET_NCHECK = 0x80
DSRET_CHECK = 0x81
DSRET_LAST = 0x82
DSRET_HELLO = 0x90
DSRET_INIT = 0x91
SENDFLAG_LAST = 0x82
SENDFLAG_CHECK = 0x81
SENDFLAG_NOCHECK = 0x80
SENDFLAG_STOP = 0xff
SENDFLAG_SNAPSHOT = 0x83
#------------------------------------------------------------
def receCmd():
if uart1.any() == 0: return DSCMD_NONE,0
framebyte = uart1.read(4)
if len(framebyte) != 4: return DSCMD_NONE, 0
framelist = list(framebyte)
time = int.from_bytes(framebyte[1:3], 'big')
sumnum = sum(framelist[0:3]) & 0xff ^ 0xff
if sumnum != framelist[3]: return DSCMD_NONE, 0
return framelist[0], time
#------------------------------------------------------------
def sendCmd(cmd, time):
senddim = [0x55,cmd]
senddim.extend(list(time.to_bytes(4, 'big')))
sumnum = sum(senddim)&0xff^0xff
senddim.extend([sumnum])
sendbytes = bytes(senddim)
uart1.write(sendbytes)
#------------------------------------------------------------
def procCmd(cmd, time):
global count32,sendflag,delay3s,count32,sendenableflag
global initflag,lastcount32,snapshot32,speakercount,senddelay,sendcount
if cmd == DSCMD_NONE: return
if cmd == DSCMD_HELLO:
sendCmd(DSRET_HELLO, 0x0)
return
if cmd == DSCMD_INIT:
sendCmd(DSRET_INIT, 0x0)
count32 = 0x0
delay3s = time
count3s = time
initflag = 1
lastcount32 = 0
snapshot32 = 0
sendcount = 0
sendenableflag = 1
sendflag = SENDFLAG_STOP
return
if cmd == DSCMD_STARTSEND:
sendCmd(SENDFLAG_LAST, lastcount32)
count3s = delay3s
sendflag = 0x0
sendenableflag = 0x1
senddelay = time
sendcount = 0
return
if cmd == DSCMD_STOPSEND:
sendCmd(SENDFLAG_CHECK, count32)
sendenableflag = 0
sendflag = 0
sendcount = 0
return
if cmd == DSCMD_BEEP:
speakercount = 500
return
if cmd in (DSCMD_SETBEACONSEQUENCY,
DSCMD_BEACONSTART,
DSCMD_BEACONSTOP,
DSCMD_GETBEACONSTATE,
DSCMD_TURNOFFLIGHT,
DSCMD_BEACONINIT):
return
print(cmd, time)
#------------------------------------------------------------
speakercount = 0
flash50mscount = 0
flash50inc = 0
resultflag = 0 # 1 :OK; 2:ERROR; 0:NULL
buzzcount = 00
def resultOK():
global resultflag, speakercount, flash50mscount, flash50inc
resultflag = 1
speakercount = 4500
flash50mscount = 0
flash50inc = 0
def resultERROR():
global resultflag, speakercount, flash50mscount, flash50inc
resultflag = 2
speakercount = 4500
flash50mscount = 0
flash50inc = 0
#------------------------------------------------------------
def speaker1ms():
global resultflag, speakercount, flash50mscount, flash50inc
global detectflag1, detectflag2, detectcount, buzzcount
#--------------------------------------------------------
if detectflag1 != detectflag2:
detectcount += 1
#--------------------------------------------------------
if buzzcount > 0:
buzzcount -= 1
#--------------------------------------------------------
if speakercount > 0:
speakercount -= 1
if speakercount == 0:
if resultflag > 0:
resultflag = 0
rled.off()
gled.off()
#----------------------------------------------------
flash50mscount += 1
if flash50mscount >= 50:
flash50mscount = 0
flash50inc += 1
if resultflag == 0:
speaker.on()
elif resultflag == 1:
gled.on()
speaker.on()
elif resultflag == 2:
if flash50inc & 0x1 == 0:
rled.on()
speaker.on()
else:
rled.off()
speaker.off()
#------------------------------------------------------------
def sendTime():
global count32,snapshot32,lastcount32,sendflag
if sendflag == SENDFLAG_STOP: return
if initflag == 0: return
timesend 基于ESP32的竞赛裁判系统功能调试-与微机通讯