回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页
best-icon

基于Beetle_ESP32_C3的GPS电子围栏 中等

头像 HonestQiao 2023.07.08 173 5

最近做了一个建议的电子围栏系统,在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
image.png

 

然后屏幕会显示当前的卫星定位信息:

image.png

 

因为收到卫星定位信息,需要一定的时间,所以一开始,卫星数量为0。

此时移动到室外,一段时间后,将会收到GPS定位信息。

 

image.png

 

通过串口工具,也可以查看到输出的信息:

image.png

 

 

当首次收到GPS定位信息后,蜂鸣器会响,并会自动设置为当前位置。

然后,当距离大于设定距离时,就会自动报警。

 

后续将会继续完善,实现多个设备信息关联,如果一个此处了范围,就自动通知周边的其他设备,从而实现联动报警。

评论

user-avatar
  • 三春牛-创客

    三春牛-创客2023.08.30

    赞赞赞

    0
    • 三春牛-创客

      三春牛-创客2023.08.30

      厉害

      0
      • hacker_

        hacker_2023.08.20

        666

        0
        • hacker_

          hacker_2023.07.26

          666

          0
          • hacker_

            hacker_2023.07.16

            666

            0