最近做了一个建议的电子围栏系统,在Beetle_ESP32_C3和Pico上通用。
基本的功能如下:
1. 系统启动后,将自动读取GPS数据
2. 当成功读取到GPS定位信息后,将当前位置作为起始位置
3. 但设备移动后,实时计算和起始位置的距离
4. 当达到一定距离后,会使用蜂鸣器报警。
5. 当前的定位信息,会显示在OLED显示屏上
这个项目中,实际使用到的硬件设备如下:
1. 主控板:Beetle_ESP32_C3
2. 扩展板:可以用硬件列表中的扩展板替代,主要方便接线
3. GPS模块:Air530,可以用硬件列表中的GPS模块替代
4. 蜂鸣器:通用
5. OLED:通用I2C OLED,128*64
具体接线和配置如下:
OLED:
I2C_ID = 1
PIN_SDA = 8 #GPIO8
PIN_SCL = 9 #GPIO9
GPS:
UART_ID = 1
PIN_RX = 5 #GPIO5
PIN_TX = 4 #GPIO4
蜂鸣器:
PIN_BUZZER = 0 #GPIO0
代码部分,基于micropython,使用python语言开发。
得益于micropython的扩展性,所使用到的外设,都有很好的驱动库支持。
其中:
1. OLED:使用micropython-nano-gui进行驱动,下载地址:https://github.com/peterhinch/micropython-nano-gui
2. GPS:使用micropyGPS.py进行驱动,下载地址:https://github.com/inmcm/micropyGPS
要在Beetle_ESP32_C3上使用micropython-nano-gui驱动OLED,需要进行配置,对应的配置文件color_setup.py如下:
from machine import Pin, I2C
import gc
from drivers.ssd1306.ssd1306 import SSD1306_I2C as SSD
# PICO_W Pin assignment
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000) #oled connect to I2C1
oled_width = 128
oled_height = 64
gc.collect() # Precaution before instantiating framebuf
ssd = SSD(oled_width, oled_height, i2c)
主程序部分的代码如下:
# https://github.com/inmcm/micropyGPS
# https://www.cnblogs.com/andylhc/p/9481636.html
from machine import Pin, SPI, I2C, UART, PWM
#from ssd1306 import SSD1306_SPI, SSD1306_I2C
import framebuf
from time import sleep
from utime import sleep_ms
from micropyGPS import MicropyGPS
import time
import utime
import uasyncio as asyncio
from math import radians, cos, sin, asin, sqrt
# nano gui
from color_setup import ssd
# On a monochrome display Writer is more efficient than CWriter.
from gui.core.writer import Writer
from gui.core.nanogui import refresh
from gui.widgets.meter import Meter
from gui.widgets.label import Label
# Fonts
import gui.fonts.opposans_m_12 as fontcn
import gui.fonts.arial10 as arial10
import gui.fonts.courier20 as fixed
import gui.fonts.font6 as small
# 安全距离
SAFE_DIS = 3
# 调试信息开关
DEBUG = 1
# 开发板类型
BOARD_TYPE=1 # 0-Pico 1-ESP32-C3
# 显示屏类型
OLED_TYPE = 2 # 0-SPI 1-I2C 2-nano-gui
# 引脚定义
if BOARD_TYPE == 0:
from aswitch import Pushbutton
import bootsel
BOARD_NAME = "Pico-W"
UART_ID = 1
PIN_BUZZER = 0
BUTTON_BOOT = True
# I2C-OLED
I2C_ID = 1
PIN_RX = 5
PIN_TX = 4
PIN_SDA = 8
PIN_SCL = 9
# SPI-OLED
SPI_ID = 1
PIN_MOSI = 11
#PIN_MOSI = -1
PIN_SCL = 10
PIN_DC = 9
PIN_CS = 8
PIN_RST = 1
DELAY_TIME = 0
if BOARD_TYPE == 1:
BOARD_NAME = "ESP32C3"
UART_ID = 1
PIN_BUZZER = 10
BUTTON_BOOT = False
# I2C-OLED
I2C_ID = 0
PIN_RX = 20
PIN_TX = 21
PIN_SDA = 8
PIN_SCL = 9
# SPI-OLED
SPI_ID = 0
PIN_MOSI = 6
#PIN_MISO = -1
PIN_SCL = 4
PIN_DC = 1
PIN_CS = 7
PIN_RST = 2
DELAY_TIME = 3
# 定义音调频率
tones = {'1': 262, '2': 294, '3': 330, '4': 349, '5': 392, '6': 440, '7': 494, '-': 0}
# 定义声音
melodys = ("121212","171717","232323")
# 设置GPIO20口为IO输出,然后通过PWM控制无源蜂鸣器发声
buzzer = PWM(Pin(PIN_BUZZER, Pin.OUT))
if BOARD_TYPE == 0:
uartx = UART(UART_ID, baudrate=9600, rx=Pin(PIN_RX), tx=Pin(PIN_TX), timeout=10)
if BOARD_TYPE == 1:
uartx = UART(UART_ID, baudrate=9600, rx=PIN_RX, tx=PIN_TX, timeout=10)
air551G = MicropyGPS(8) # 东八区的修正
if OLED_TYPE == 0:
spi = SPI(SPI_ID, 100000, mosi=Pin(PIN_MOSI), sck=Pin(PIN_SCL))
oled = SSD1306_SPI(128, 64, spi, Pin(PIN_DC),Pin(PIN_CS), Pin(PIN_RST))
#oled = SSD1306_SPI(WIDTH, HEIGHT, spi, dc,rst, cs) use GPIO PIN NUMBERS
elif OLED_TYPE == 1:
i2c = I2C(I2C_ID, scl=Pin(PIN_SCL), sda=Pin(PIN_SDA), freq=400000) #oled connect to I2C1
oled = SSD1306_I2C(128, 64, i2c)
elif OLED_TYPE == 2:
oled = ssd
#oled.rotate(180)
if BUTTON_BOOT:
btn = bootsel
pb = Pushbutton(btn, suppress=True)
base_loc = [0, 0]
gps_values_float = [0, 0]
def play_sound(idx):
global melodys, tones, buzzer
for tone in melodys[idx-1]:
freq = tones[tone]
if freq:
buzzer.freq(freq)
buzzer.duty_u16(1000)
#beeper.init(duty=1000, freq=freq) # 调整PWM的频率,使其发出指定的音调
else:
buzzer.duty_u16(0) # 空拍时一样不上电
# 停顿一下 (四四拍每秒两个音,每个音节中间稍微停顿一下)
utime.sleep_ms(400)
buzzer.duty_u16(0) # 设备占空比为0,即不上电
utime.sleep_ms(100)
def get_GPS_values():
global gps_values_float, gps_values, rtc # 定义全局变量
cc = uartx.readline()
if DEBUG:
print("调试数据:", cc)
if not cc:
return
for x in cc:
air551G.update(chr(x))
# 经纬度信息
gps_values_float = [air551G.latitude[0] + (air551G.latitude[1] / 60), air551G.longitude[0] + (air551G.longitude[1] / 60)]
gps_values = (
str(air551G.latitude[0] + (air551G.latitude[1] / 60))
+ ","
+ str(air551G.longitude[0] + (air551G.longitude[1] / 60))
)
# 当前时间
date = air551G.date
timestamp = air551G.timestamp
hour = timestamp[0]
rtc = (
str(int(timestamp[0]))
+ ":"
+ str(int(timestamp[1]))
+ ":"
+ str(int(timestamp[2]))
)
return gps_values_float, gps_values, rtc
print("树莓派Pico + Air530Z 测试:")
print("当前时区: +%d\n" % air551G.local_offset)
def set_base_loc():
global base_loc, gps_values_float, tip_field
get_GPS_values()
if gps_values_float[0]>0 and gps_values_float[1]>0:
base_loc = gps_values_float
print("设置初始位置:", base_loc)
#tip_field.value("设置:", base_loc)
play_sound(3)
else:
#tip_field.value("没有读取到坐标")
print("没有读取到坐标")
#公式计算两点间距离(km)
def geodistance(lng1,lat1,lng2,lat2):
print('geodistance', lng1,lat1,lng2,lat2)
# lng1,lat1,lng2,lat2 = (120.12802999999997,30.28708,115.86572000000001,28.7427)
lng1, lat1, lng2, lat2 = map(radians, [float(lng1), float(lat1), float(lng2), float(lat2)]) # 经纬度转换成弧度
dlon=lng2-lng1
dlat=lat2-lat1
a=sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
distance=2*asin(sqrt(a))*6371*1000 # 地球平均半径,6371km
distance=round(distance/1000,3)
return distance
async def main():
global gps_values_float, gps_values, rtc, time_field, loc_field, sat_field, dis_field
if BUTTON_BOOT:
press_press = pb.press_func(print, ("PRESS",))
short_press = pb.release_func(print, (" return SHORT",))
double_press = pb.double_func(print, (" return DOUBLE",))
#long_press = pb.long_func(print, (" return LONG",))
long_press = pb.long_func(set_base_loc, ())
loop = asyncio.get_event_loop()
loop.create_task(pb.buttoncheck()) # Thread runs forever
dis = 0
while True:
await asyncio.sleep(1)
get_GPS_values()
print("当前时间:%s" % rtc) # 输出当前时间
print("当前位置:%s" % gps_values) # 输出GPS信息
print("当前使用的卫星:%s\n" % str(air551G.satellites_in_use)) # 输出当前使用的卫星数量
if not BUTTON_BOOT:
if base_loc[0]==0 and base_loc[1]==0 and gps_values_float[0]>0 and gps_values_float[1]>0:
# 自动初始化
set_base_loc()
if OLED_TYPE == 2:
print(base_loc, gps_values_float)
if base_loc[0]>0 and base_loc[1]>0 and gps_values_float[0]>0 and gps_values_float[1]>0:
dis = geodistance(base_loc[0], base_loc[1], gps_values_float[0], gps_values_float[1])
print("距离:%f\n" % dis) # 输出当前使用的卫星数量
if dis*1000>SAFE_DIS:
play_sound(2)
time_field.value("时间:%s" % rtc)
loc_field.value("位置:")
loc_field2.value("%0.3f,%0.3f" % (gps_values_float[0], gps_values_float[1]))
sat_field.value("卫星:%d" % air551G.satellites_in_use)
dis_field.value("距离:%dm" % (dis*1000))
refresh(ssd)
else:
oled.fill(0)
oled.text("Time:%s" % rtc, 0, 0)
oled.text("Loc:%s" % gps_values, 0, 10)
oled.text("Sat:%d" % air551G.satellites_in_use, 0, 20)
oled.show()
Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it
wri = Writer(ssd, fontcn, verbose=False)
wri.set_clip(False, False, False)
time_field = Label(wri, 0, 2, wri.stringlen('longer'))
loc_field = Label(wri, 16, 2, wri.stringlen('longer'))
sat_field = Label(wri, 32, 2, wri.stringlen('longer'))
dis_field = Label(wri, 48, 2, wri.stringlen('longer'))
tip_field = Label(wri, 15, 10, wri.stringlen('longer'))
tip2_field = Label(wri, 35, 10, wri.stringlen('longer'))
wri2 = Writer(ssd, small, verbose=False)
wri2.set_clip(False, False, False)
loc_field2 = Label(wri2, 16, 32, wri2.stringlen('longer'))
if OLED_TYPE == 2:
ssd.fill(0)
refresh(ssd)
tip_field.value("基于%s的\n GPS电子围栏\n\n HonestQiao" % BOARD_NAME)
refresh(ssd)
play_sound(1)
ssd.fill(0)
if BUTTON_BOOT:
print("等待按键")
tip_field.value("按BOOT开始运行")
tip2_field.value("围栏距离:%dm" % SAFE_DIS)
refresh(ssd)
while not bootsel.value():
time.sleep(0.1)
pass
else:
print("等待%ds" % DELAY_TIME)
tip_field.value("%d秒后开始运行" % DELAY_TIME)
tip2_field.value("围栏距离:%dm" % SAFE_DIS)
refresh(ssd)
time.sleep(DELAY_TIME)
ssd.fill(0)
refresh(ssd)
asyncio.run(main())
上面的代码,兼容Beetle_ESP32_C3和Pico,做好对应的引脚配置即可。
系统启动后,蜂鸣器会响提示启动,并会在屏幕上显示提示信息:
![image.png](https://makelogimg.dfrobot.com.cn/makelog/62680fd3f6f6eea679b32f3e/0644ab776dab225ea3d073d72a9673e7.png)
![image.png](https://makelogimg.dfrobot.com.cn/makelog/62680fd3f6f6eea679b32f3e/1c390467ccfc295d44e50f6a475a21b4.png)
然后屏幕会显示当前的卫星定位信息:
![image.png](https://makelogimg.dfrobot.com.cn/makelog/62680fd3f6f6eea679b32f3e/eec0f1c2ed24ff5c565341c73bdc1683.png)
因为收到卫星定位信息,需要一定的时间,所以一开始,卫星数量为0。
此时移动到室外,一段时间后,将会收到GPS定位信息。
![image.png](https://makelogimg.dfrobot.com.cn/makelog/62680fd3f6f6eea679b32f3e/8f33f6d5d329ffcb3f8de889a27525ca.png)
通过串口工具,也可以查看到输出的信息:
![image.png](https://makelogimg.dfrobot.com.cn/makelog/62680fd3f6f6eea679b32f3e/0a9fe74351b13c3fab4b420a226cea6c.png)
当首次收到GPS定位信息后,蜂鸣器会响,并会自动设置为当前位置。
然后,当距离大于设定距离时,就会自动报警。
后续将会继续完善,实现多个设备信息关联,如果一个此处了范围,就自动通知周边的其他设备,从而实现联动报警。
三春牛-创客2023.08.30
赞赞赞
三春牛-创客2023.08.30
厉害
hacker_2023.08.20
666
hacker_2023.07.26
666
hacker_2023.07.16
666