【得捷电子Follow me第1期】开发过程总结
[复制链接]
1 开发环境搭建和编程初体验
感谢EEWorld论坛和得捷电子举行的Follow Me活动,在论坛里和大家一起学习RP2040 Pico W这款开发板上的micropython程序开发。
1.1 开发环境搭建
收到开发板以及套件后,本次活动提供的硬件有以下几项:
主控板:Raspberry Pi Pico w ——RP2040 收发器; 802.11 b/g/n(Wi-Fi,WiFi,WLAN) 2.4GHz 评估板;
扩展板:方便将pico w开发板的信号引出,连接到各种 grove 设备;
屏幕: I2C接口 单色 OLED显示屏,可以通过编程显示各种文字、图案;
外设1:蜂鸣器,可以通过GPIO驱动发出声音;
外设2:高性能、高集成度、低功耗的多模卫星定位导航模块,支持GPS/北斗/Galileo等多种定位系统;
在主控板的两侧焊接上排针,插在扩展板上,通过Type-B的USB连接到电脑上就完成了硬件环境搭建。效果如下图:
1.2 固件烧写
查看官方的手册《Raspberry Pi Pico Python SDK》,其中1.1节给出了树莓派官方的介绍页面,在MircoPython页面上可以找到下载mircopython固件的链接。
下载固件后,将开发板连接到电脑上,如果出现了名为RPI-RP2的USB设备,说明当前开发板上还未烧写固件,图中可以下载如下固件。
rp2-pico-w-20230426-v1.20.0.uf2
将固件复制到USB设备中,就可以开始固件的烧写,完成烧写后,USB设备会消失。如果要烧写其它类型的固件,在断电状态下,按住开发板上的BOOTSEL按钮,再给设备上电,会再出现名为RPI-RP2的USB设备,从而修改开发板上运行的固件。
1.3 Mu Editor软件安装
Mu Editor可以用于连接运行mircopython的硬件,并向其中写入可执行的python代码。下图为其官方网站。
其安装过程很简单,全部选择默认选项即可,其模式选择为RP2040即可,如下图所示:
1.4 编程初体验
Mu Editor安装完毕后,其界面如下所示:
软件右下角的图标表示是否有硬件连接,工具栏中提供了python代码编辑、上传和下载的功能,便于代码编辑和应用的修改。
查看树莓派官方最新的《Raspberry Pi Pico Python SDK》可以查看如何控制开发板上的LED灯的闪烁。
以下是其中的示例代码,编写成文件后,将其保存到设备中,再次上电后会自动运行该脚本。
# 在这里写上你的代码 :-)blink led
from machine import Pin, Timer
led = Pin("LED", Pin.OUT)
tim = Timer()
def tick(timer):
global led
led.toggle()
tim.init(freq=10, mode=Timer.PERIODIC, callback=tick)
2 驱动外设
在完成Pico W的硬件和软件开发环境的搭建,micropython语法、程序的下载和运行后,接下来对如何使用其驱动外部进行学习和分享。
2.1 驱动板载LED
在开发板上有一个可供用户控制的LED灯,点灯程序是接触一块开发板的第一个示例程序,使用《Raspberry Pi Pico Python SDK》手册中示例代码即可驱动LED运行,示例代码如下:
from machine import Pin,Timer
led=Pin("LED",Pin.OUT)
tim=Timer()
def tick(timer):
global led
led.toggle()
tim.init(freq=2.5,mode=Timer.PERIODIC,callback=tick)
2.2 驱动蜂鸣器
活动要求的外设中有蜂鸣器,蜂鸣器的控制使用GPIO控制或者PWM波控制其发出声音的大小和频率,从而实现通过蜂鸣器播放音乐的功能;
使用GPIO控制蜂鸣器和控制LED亮灭类似,控制代码如下:
# BEEP using GPIO Example
from machine import Pin
from time import sleep
# 蜂鸣器管脚定义GPIO20
IO_buzzer = 20
# 设置蜂鸣器GPIO口为输出模式
buzzer = Pin(IO_buzzer, Pin.OUT)
# 死循环
while True:
# 设置低电平,关闭蜂鸣器
buzzer.value(0)
# 延时
sleep(0.8)
# 设置高电平,打开蜂鸣器
buzzer.value(1)
# 延时
sleep(0.2)
在论坛里看到有大佬使用开源的蜂鸣器驱动实现了音乐的播放,其实质上是使用PWM波输出来实现音乐的播放。模块的实现代码如下
"""
Micropython (Raspberry Pi Pico)
Plays music written on onlinesequencer.net through a passive piezo buzzer.
Uses fast arpeggios with a single buzzer to simulate polyphony
Also supports multiple buzzers at once for real polyphony
https://github.com/james1236/buzzer_music
"""
from machine import Pin, PWM
from math import ceil
tones = {
'C0':16,
'C#0':17,
'D0':18,
'D#0':19,
'E0':21,
'F0':22,
'F#0':23,
'G0':24,
'G#0':26,
'A0':28,
'A#0':29,
'B0':31,
'C1':33,
'C#1':35,
'D1':37,
'D#1':39,
'E1':41,
'F1':44,
'F#1':46,
'G1':49,
'G#1':52,
'A1':55,
'A#1':58,
'B1':62,
'C2':65,
'C#2':69,
'D2':73,
'D#2':78,
'E2':82,
'F2':87,
'F#2':92,
'G2':98,
'G#2':104,
'A2':110,
'A#2':117,
'B2':123,
'C3':131,
'C#3':139,
'D3':147,
'D#3':156,
'E3':165,
'F3':175,
'F#3':185,
'G3':196,
'G#3':208,
'A3':220,
'A#3':233,
'B3':247,
'C4':262,
'C#4':277,
'D4':294,
'D#4':311,
'E4':330,
'F4':349,
'F#4':370,
'G4':392,
'G#4':415,
'A4':440,
'A#4':466,
'B4':494,
'C5':523,
'C#5':554,
'D5':587,
'D#5':622,
'E5':659,
'F5':698,
'F#5':740,
'G5':784,
'G#5':831,
'A5':880,
'A#5':932,
'B5':988,
'C6':1047,
'C#6':1109,
'D6':1175,
'D#6':1245,
'E6':1319,
'F6':1397,
'F#6':1480,
'G6':1568,
'G#6':1661,
'A6':1760,
'A#6':1865,
'B6':1976,
'C7':2093,
'C#7':2217,
'D7':2349,
'D#7':2489,
'E7':2637,
'F7':2794,
'F#7':2960,
'G7':3136,
'G#7':3322,
'A7':3520,
'A#7':3729,
'B7':3951,
'C8':4186,
'C#8':4435,
'D8':4699,
'D#8':4978,
'E8':5274,
'F8':5588,
'F#8':5920,
'G8':6272,
'G#8':6645,
'A8':7040,
'A#8':7459,
'B8':7902,
'C9':8372,
'C#9':8870,
'D9':9397,
'D#9':9956,
'E9':10548,
'F9':11175,
'F#9':11840,
'G9':12544,
'G#9':13290,
'A9':14080,
'A#9':14917,
'B9':15804
}
#Time, Note, Duration, Instrument (onlinesequencer.net schematic format)
#0 D4 8 0;0 D5 8 0;0 G4 8 0;8 C5 2 0;10 B4 2 0;12 G4 2 0;14 F4 1 0;15 G4 17 0;16 D4 8 0;24 C4 8 0
class music:
def __init__(self, songString='0 D4 8 0', looping=True, tempo=3, duty=2512, pin=None, pins=[Pin(0)]):
self.tempo = tempo
self.song = songString
self.looping = looping
self.duty = duty
self.stopped = False
self.timer = -1
self.beat = -1
self.arpnote = 0
self.pwms = []
if (not (pin is None)):
pins = [pin]
self.pins = pins
for pin in pins:
self.pwms.append(PWM(pin))
self.notes = []
self.playingNotes = []
self.playingDurations = []
#Find the end of the song
self.end = 0
splitSong = self.song.split(";")
for note in splitSong:
snote = note.split(" ")
testEnd = round(float(snote[0])) + ceil(float(snote[2]))
if (testEnd > self.end):
self.end = testEnd
#Create empty song structure
while (self.end > len(self.notes)):
self.notes.append(None)
#Populate song structure with the notes
for note in splitSong:
snote = note.split(" ")
beat = round(float(snote[0]));
if (self.notes[beat] == None):
self.notes[beat] = []
self.notes[beat].append([snote[1],ceil(float(snote[2]))]) #Note, Duration
#Round up end of song to nearest bar
self.end = ceil(self.end / 8) * 8
def stop(self):
for pwm in self.pwms:
pwm.deinit()
self.stopped = True
def restart(self):
self.beat = -1
self.timer = 0
self.stop()
self.pwms = []
for pin in self.pins:
self.pwms.append(PWM(pin))
self.stopped = False
def resume(self):
self.stop()
self.pwms = []
for pin in self.pins:
self.pwms.append(PWM(pin))
self.stopped = False
def tick(self):
if (not self.stopped):
self.timer = self.timer + 1
#Loop
if (self.timer % (self.tempo * self.end) == 0 and (not (self.timer == 0))):
if (not self.looping):
self.stop()
return False
self.beat = -1
self.timer = 0
#On Beat
if (self.timer % self.tempo == 0):
self.beat = self.beat + 1
#Remove expired notes from playing list
i = 0
while (i < len(self.playingDurations)):
self.playingDurations[i] = self.playingDurations[i] - 1
if (self.playingDurations[i] <= 0):
self.playingNotes.pop(i)
self.playingDurations.pop(i)
else:
i = i + 1
#Add new notes and their durations to the playing list
"""
#Old method runs for every note, slow to process on every beat and causes noticeable delay
ssong = song.split(";")
for note in ssong:
snote = note.split(" ")
if int(snote[0]) == beat:
playingNotes.append(snote[1])
playingDurations.append(int(snote[2]))
"""
if (self.beat < len(self.notes)):
if (self.notes[self.beat] != None):
for note in self.notes[self.beat]:
self.playingNotes.append(note[0])
self.playingDurations.append(note[1])
#Only need to run these checks on beats
i = 0
for pwm in self.pwms:
if (i >= len(self.playingNotes)):
if hasattr(pwm, 'duty_u16'):
pwm.duty_u16(0)
else:
pwm.duty(0)
else:
#Play note
if hasattr(pwm, 'duty_u16'):
pwm.duty_u16(self.duty)
else:
pwm.duty(self.duty)
pwm.freq(tones[self.playingNotes[i]])
i = i + 1
#Play arp of all playing notes
if (len(self.playingNotes) > len(self.pwms)):
p = self.pwms[len(self.pwms)-1];
if hasattr(p, 'duty_u16'):
p.duty_u16(self.duty)
else:
p.duty(self.duty)
if (self.arpnote > len(self.playingNotes)-len(self.pwms)):
self.arpnote = 0
self.pwms[len(self.pwms)-1].freq(tones[self.playingNotes[self.arpnote+(len(self.pwms)-1)]])
self.arpnote = self.arpnote + 1
return True
else:
return False
2.3 驱动OLED
OLED屏幕是一块以SSD1315为驱动芯片的I2C接口的OLED屏幕,大小为128×64,屏幕的特点如下:
单色清晰显示:在继承了高对比度、高亮度和低功耗的特点的同时,显示效果得到了提升
工业级工作温度:工作温度范围宽:-40℃~+85℃
高性价比:显示器采用升级的SSD1315芯片,性能更强,价格更低。
Grove统一套接字:该产品确保“即插即用”,可用于各种应用。
从论坛之前的帖子可以得知,SSD1306的驱动是可以兼容这块屏幕的,SSD1306的驱动代码如下:
from micropython import const
import framebuf
# register definitions
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xA4)
SET_NORM_INV = const(0xA6)
SET_DISP = const(0xAE)
SET_MEM_ADDR = const(0x20)
SET_COL_ADDR = const(0x21)
SET_PAGE_ADDR = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP = const(0xA0)
SET_MUX_RATIO = const(0xA8)
SET_COM_OUT_DIR = const(0xC0)
SET_DISP_OFFSET = const(0xD3)
SET_COM_PIN_CFG = const(0xDA)
SET_DISP_CLK_DIV = const(0xD5)
SET_PRECHARGE = const(0xD9)
SET_VCOM_DESEL = const(0xDB)
SET_CHARGE_PUMP = const(0x8D)
# Subclassing FrameBuffer provides support for graphics primitives
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
class SSD1306(framebuf.FrameBuffer):
def __init__(self, width, height, external_vcc):
self.width = width
self.height = height
self.external_vcc = external_vcc
self.pages = self.height // 8
self.buffer = bytearray(self.pages * self.width)
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
self.init_display()
def init_display(self):
for cmd in (
SET_DISP | 0x00, # off
# address setting
SET_MEM_ADDR,
0x00, # horizontal
# resolution and layout
SET_DISP_START_LINE | 0x00,
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
SET_MUX_RATIO,
self.height - 1,
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
SET_DISP_OFFSET,
0x00,
SET_COM_PIN_CFG,
0x02 if self.width > 2 * self.height else 0x12,
# timing and driving scheme
SET_DISP_CLK_DIV,
0x80,
SET_PRECHARGE,
0x22 if self.external_vcc else 0xF1,
SET_VCOM_DESEL,
0x30, # 0.83*Vcc
# display
SET_CONTRAST,
0xFF, # maximum
SET_ENTIRE_ON, # output follows RAM contents
SET_NORM_INV, # not inverted
# charge pump
SET_CHARGE_PUMP,
0x10 if self.external_vcc else 0x14,
SET_DISP | 0x01,
): # on
self.write_cmd(cmd)
self.fill(0)
self.show()
def poweroff(self):
self.write_cmd(SET_DISP | 0x00)
def poweron(self):
self.write_cmd(SET_DISP | 0x01)
def contrast(self, contrast):
self.write_cmd(SET_CONTRAST)
self.write_cmd(contrast)
def invert(self, invert):
self.write_cmd(SET_NORM_INV | (invert & 1))
def show(self):
x0 = 0
x1 = self.width - 1
if self.width == 64:
# displays with width of 64 pixels are shifted by 32
x0 += 32
x1 += 32
self.write_cmd(SET_COL_ADDR)
self.write_cmd(x0)
self.write_cmd(x1)
self.write_cmd(SET_PAGE_ADDR)
self.write_cmd(0)
self.write_cmd(self.pages - 1)
self.write_data(self.buffer)
class SSD1306_I2C(SSD1306):
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
self.i2c = i2c
self.addr = addr
self.temp = bytearray(2)
self.write_list = [b"\x40", None] # Co=0, D/C#=1
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.temp[0] = 0x80 # Co=1, D/C#=0
self.temp[1] = cmd
self.i2c.writeto(self.addr, self.temp)
def write_data(self, buf):
self.write_list[1] = buf
self.i2c.writevto(self.addr, self.write_list)
class SSD1306_SPI(SSD1306):
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
self.rate = 10 * 1024 * 1024
dc.init(dc.OUT, value=0)
res.init(res.OUT, value=0)
cs.init(cs.OUT, value=1)
self.spi = spi
self.dc = dc
self.res = res
self.cs = cs
import time
self.res(1)
time.sleep_ms(1)
self.res(0)
time.sleep_ms(10)
self.res(1)
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(0)
self.cs(0)
self.spi.write(bytearray([cmd]))
self.cs(1)
def write_data(self, buf):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(1)
self.cs(0)
self.spi.write(buf)
self.cs(1)
OLED的演示代码如下:
import machine
import time
from ssd1306 import SSD1306_I2C
from machine import Pin, I2C
i2c1 = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
display = SSD1306_I2C(128,64,i2c1)
display.text('Follow me 1', 0, 0, 1)
display.text('MicroPython', 0, 12, 1)
display.text('Pico W', 0, 24, 1)
display.text('OLED Demo', 0, 36, 1)
display.text('EPTmachine', 0, 48, 1)
display.show()
3 连接Wifi以及同步网络时间
Pico W开发板上搭载了Infineon CYW43439 2.4GHz无线芯片,该芯片有以下特性:
Wifi4(802.11n),Single-band(2.4 GHz)
WPA3
SoftAP(Up to 4 clients)
天线为经过ABRACON认证的板载天线,RP2040通过SPI接口与无线模块连接。由于IO数量的限制,控制WIFI的引脚与ADC和LED冲突,在使用WIFI模块时无法使用其他功能。
3.1 使用WIFI模块进行联网操作
查看《Connecting-to-the-internet-with-pico-w》的3.6节,按照其中的示例代码,使用network和socket两个库可以控制开发板连接到指定的Wifi,从而可以上网。建立连接时,需要提供要连接的Wifi的SSID和密码,格式为字符串。
具体的连接代码如下
import network
import time
ssid = "HOST_BWG"
password = "Xdy_China_Mobile"
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid , password)
while not wlan.isconnected() and wlan.status() >= 0:
print("Connecting:")
time.sleep(1)
print(wlan.ifconfig())
程序运行效果如图所示
3.2 同步网络时间
RP2040支持RTC实时时钟功能,但是一旦设备掉电后,RTC就无法保存相关的时间信息。以下为使用time模块查询本地时间,并打印相关信息到OLED屏幕上的代码。
import machine
import time
from ssd1306 import SSD1306_I2C
from machine import Pin, I2C
i2c1 = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
display = SSD1306_I2C(128,64,i2c1)
timezone=8
now = time.time()
now += timezone * 3600
t = time.localtime(now)
year, month, day, hour, minute, second, *_ = t
time_str = f"{year}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
print("时间:", time_str)
display.text(time_str, 0, 0, 1)
display.show()
注意到当前的时间是不对的。
不过开发板是支持联网功能的,连接上Wifi后,可以通过查询网络时间发布服务器的信息来同步本地的RTC时钟。micropython有一个开源的获取网络时间的库ntptime.py。下载其源码后,其中的网络时间服务器是可以配置的,这里修改配置为aliyun的服务器。
# The NTP host can be configured at runtime by doing: ntptime.host = 'myhost.org'
host = "ntp1.aliyun.com"
# The NTP socket timeout can be configured at runtime by doing: ntptime.timeout = 2
timeout = 1
将ntptime.py导入到开发板上,使用以下代码通过联网获取网络时间,使本地时间与网络时间同步,并获取更新后的本地时间,将时间信息打印到OLED屏幕上。
import machine
import time
import network
import ntptime
from ssd1306 import SSD1306_I2C
from machine import Pin, I2C
from machine import RTC
ssid = 'HOST_BWG'
password = 'Xdy_China_Mobile'
i2c1 = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
display = SSD1306_I2C(128,64,i2c1)
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
# Wait for connect or fail
max_wait = 10
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
print('waiting for connection...')
time.sleep(1)
# Handle connection error
if wlan.status() != 3:
raise RuntimeError('network connection failed')
else:
print('connected')
status = wlan.ifconfig()
print( 'ip = ' + status[0] )
def sync_ntp():
ntptime.host = 'ntp1.aliyun.com'
ntptime.settime()
sync_ntp()
timezone=8
now = time.time()
now += timezone * 3600
t = time.localtime(now)
year, month, day, hour, minute, second, *_ = t
time_str = f"{year}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
print("时间:", time_str)
display.text(time_str, 0, 0, 1)
display.show()
可以观察到本地时间已经同步到现实时间。
4 GPS定位模块使用
本次活动中使用的GPS模块(Air530)是一款高性能、高集成度的多模式卫星定位以及导航模块。支持GPS/Beidou/Glonass/Galileo/QZSS/SBAS等,适用于车载导航、智慧天气和农业等GNSS定位应用。
4.1 硬件连接
GPS模块通过串口与MCU通讯,将模块连接到转接板的UART0上,OLED屏幕连接到I2C0上。实物连接的效果如图所示:
4.2 模块代码编写
串口接收的到的数据格式如图所示
由于编写代码处在室内,信号遮挡严重,所以长时间无法获取到卫星信号,在靠近窗台或者开阔地带使用时,GPS模块工作一段时间,大约30秒,即可正常连接上卫星并更新位置和时间信息。
接收到串口发送过来的报文后,需要按照协议的格式对其进行解析,在论坛之前的帖子中了解到有现成的轮子micropyGPS。将模块的代码导入到Pico W中,同时编写相关的调用函数,提取其中相关的经纬度、时间等信息,输出到OLED屏幕上显示。
模块代码如下:
"""
# MicropyGPS - a GPS NMEA sentence parser for Micropython/Python 3.X
# Copyright (c) 2017 Michael Calvin McCoy (calvin.mccoy@protonmail.com)
# The MIT License (MIT) - see LICENSE file
"""
# TODO:
# Time Since First Fix
# Distance/Time to Target
# More Helper Functions
# Dynamically limit sentences types to parse
from math import floor, modf
# Import utime or time for fix time handling
try:
# Assume running on MicroPython
import utime
except ImportError:
# Otherwise default to time module for non-embedded implementations
# Should still support millisecond resolution.
import time
class MicropyGPS(object):
"""GPS NMEA Sentence Parser. Creates object that stores all relevant GPS data and statistics.
Parses sentences one character at a time using update(). """
# Max Number of Characters a valid sentence can be (based on GGA sentence)
SENTENCE_LIMIT = 90
__HEMISPHERES = ('N', 'S', 'E', 'W')
__NO_FIX = 1
__FIX_2D = 2
__FIX_3D = 3
__DIRECTIONS = ('N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W',
'WNW', 'NW', 'NNW')
__MONTHS = ('January', 'February', 'March', 'April', 'May',
'June', 'July', 'August', 'September', 'October',
'November', 'December')
def __init__(self, local_offset=0, location_formatting='ddm'):
"""
Setup GPS Object Status Flags, Internal Data Registers, etc
local_offset (int): Timzone Difference to UTC
location_formatting (str): Style For Presenting Longitude/Latitude:
Decimal Degree Minute (ddm) - 40° 26.767′ N
Degrees Minutes Seconds (dms) - 40° 26′ 46″ N
Decimal Degrees (dd) - 40.446° N
"""
#####################
# Object Status Flags
self.sentence_active = False
self.active_segment = 0
self.process_crc = False
self.gps_segments = []
self.crc_xor = 0
self.char_count = 0
self.fix_time = 0
#####################
# Sentence Statistics
self.crc_fails = 0
self.clean_sentences = 0
self.parsed_sentences = 0
#####################
# Logging Related
self.log_handle = None
self.log_en = False
#####################
# Data From Sentences
# Time
self.timestamp = [0, 0, 0.0]
self.date = [0, 0, 0]
self.local_offset = local_offset
# Position/Motion
self._latitude = [0, 0.0, 'N']
self._longitude = [0, 0.0, 'W']
self.coord_format = location_formatting
self.speed = [0.0, 0.0, 0.0]
self.course = 0.0
self.altitude = 0.0
self.geoid_height = 0.0
# GPS Info
self.satellites_in_view = 0
self.satellites_in_use = 0
self.satellites_used = []
self.last_sv_sentence = 0
self.total_sv_sentences = 0
self.satellite_data = dict()
self.hdop = 0.0
self.pdop = 0.0
self.vdop = 0.0
self.valid = False
self.fix_stat = 0
self.fix_type = 1
########################################
# Coordinates Translation Functions
########################################
@property
def latitude(self):
"""Format Latitude Data Correctly"""
if self.coord_format == 'dd':
decimal_degrees = self._latitude[0] + (self._latitude[1] / 60)
return [decimal_degrees, self._latitude[2]]
elif self.coord_format == 'dms':
minute_parts = modf(self._latitude[1])
seconds = round(minute_parts[0] * 60)
return [self._latitude[0], int(minute_parts[1]), seconds, self._latitude[2]]
else:
return self._latitude
@property
def longitude(self):
"""Format Longitude Data Correctly"""
if self.coord_format == 'dd':
decimal_degrees = self._longitude[0] + (self._longitude[1] / 60)
return [decimal_degrees, self._longitude[2]]
elif self.coord_format == 'dms':
minute_parts = modf(self._longitude[1])
seconds = round(minute_parts[0] * 60)
return [self._longitude[0], int(minute_parts[1]), seconds, self._longitude[2]]
else:
return self._longitude
########################################
# Logging Related Functions
########################################
def start_logging(self, target_file, mode="append"):
"""
Create GPS data log object
"""
# Set Write Mode Overwrite or Append
mode_code = 'w' if mode == 'new' else 'a'
try:
self.log_handle = open(target_file, mode_code)
except AttributeError:
print("Invalid FileName")
return False
self.log_en = True
return True
def stop_logging(self):
"""
Closes the log file handler and disables further logging
"""
try:
self.log_handle.close()
except AttributeError:
print("Invalid Handle")
return False
self.log_en = False
return True
def write_log(self, log_string):
"""Attempts to write the last valid NMEA sentence character to the active file handler
"""
try:
self.log_handle.write(log_string)
except TypeError:
return False
return True
########################################
# Sentence Parsers
########################################
def gprmc(self):
"""Parse Recommended Minimum Specific GPS/Transit data (RMC)Sentence.
Updates UTC timestamp, latitude, longitude, Course, Speed, Date, and fix status
"""
# UTC Timestamp
try:
utc_string = self.gps_segments[1]
if utc_string: # Possible timestamp found
hours = (int(utc_string[0:2]) + self.local_offset) % 24
minutes = int(utc_string[2:4])
seconds = float(utc_string[4:])
self.timestamp = [hours, minutes, seconds]
else: # No Time stamp yet
self.timestamp = [0, 0, 0.0]
except ValueError: # Bad Timestamp value present
return False
# Date stamp
try:
date_string = self.gps_segments[9]
# Date string printer function assumes to be year >=2000,
# date_string() must be supplied with the correct century argument to display correctly
if date_string: # Possible date stamp found
day = int(date_string[0:2])
month = int(date_string[2:4])
year = int(date_string[4:6])
self.date = (day, month, year)
else: # No Date stamp yet
self.date = (0, 0, 0)
except ValueError: # Bad Date stamp value present
return False
# Check Receiver Data Valid Flag
if self.gps_segments[2] == 'A': # Data from Receiver is Valid/Has Fix
# Longitude / Latitude
try:
# Latitude
l_string = self.gps_segments[3]
lat_degs = int(l_string[0:2])
lat_mins = float(l_string[2:])
lat_hemi = self.gps_segments[4]
# Longitude
l_string = self.gps_segments[5]
lon_degs = int(l_string[0:3])
lon_mins = float(l_string[3:])
lon_hemi = self.gps_segments[6]
except ValueError:
return False
if lat_hemi not in self.__HEMISPHERES:
return False
if lon_hemi not in self.__HEMISPHERES:
return False
# Speed
try:
spd_knt = float(self.gps_segments[7])
except ValueError:
return False
# Course
try:
if self.gps_segments[8]:
course = float(self.gps_segments[8])
else:
course = 0.0
except ValueError:
return False
# TODO - Add Magnetic Variation
# Update Object Data
self._latitude = [lat_degs, lat_mins, lat_hemi]
self._longitude = [lon_degs, lon_mins, lon_hemi]
# Include mph and hm/h
self.speed = [spd_knt, spd_knt * 1.151, spd_knt * 1.852]
self.course = course
self.valid = True
# Update Last Fix Time
self.new_fix_time()
else: # Clear Position Data if Sentence is 'Invalid'
self._latitude = [0, 0.0, 'N']
self._longitude = [0, 0.0, 'W']
self.speed = [0.0, 0.0, 0.0]
self.course = 0.0
self.valid = False
return True
def gpgll(self):
"""Parse Geographic Latitude and Longitude (GLL)Sentence. Updates UTC timestamp, latitude,
longitude, and fix status"""
# UTC Timestamp
try:
utc_string = self.gps_segments[5]
if utc_string: # Possible timestamp found
hours = (int(utc_string[0:2]) + self.local_offset) % 24
minutes = int(utc_string[2:4])
seconds = float(utc_string[4:])
self.timestamp = [hours, minutes, seconds]
else: # No Time stamp yet
self.timestamp = [0, 0, 0.0]
except ValueError: # Bad Timestamp value present
return False
# Check Receiver Data Valid Flag
if self.gps_segments[6] == 'A': # Data from Receiver is Valid/Has Fix
# Longitude / Latitude
try:
# Latitude
l_string = self.gps_segments[1]
lat_degs = int(l_string[0:2])
lat_mins = float(l_string[2:])
lat_hemi = self.gps_segments[2]
# Longitude
l_string = self.gps_segments[3]
lon_degs = int(l_string[0:3])
lon_mins = float(l_string[3:])
lon_hemi = self.gps_segments[4]
except ValueError:
return False
if lat_hemi not in self.__HEMISPHERES:
return False
if lon_hemi not in self.__HEMISPHERES:
return False
# Update Object Data
self._latitude = [lat_degs, lat_mins, lat_hemi]
self._longitude = [lon_degs, lon_mins, lon_hemi]
self.valid = True
# Update Last Fix Time
self.new_fix_time()
else: # Clear Position Data if Sentence is 'Invalid'
self._latitude = [0, 0.0, 'N']
self._longitude = [0, 0.0, 'W']
self.valid = False
return True
def gpvtg(self):
"""Parse Track Made Good and Ground Speed (VTG) Sentence. Updates speed and course"""
try:
course = float(self.gps_segments[1]) if self.gps_segments[1] else 0.0
spd_knt = float(self.gps_segments[5]) if self.gps_segments[5] else 0.0
except ValueError:
return False
# Include mph and km/h
self.speed = (spd_knt, spd_knt * 1.151, spd_knt * 1.852)
self.course = course
return True
def gpgga(self):
"""Parse Global Positioning System Fix Data (GGA) Sentence. Updates UTC timestamp, latitude, longitude,
fix status, satellites in use, Horizontal Dilution of Precision (HDOP), altitude, geoid height and fix status"""
try:
# UTC Timestamp
utc_string = self.gps_segments[1]
# Skip timestamp if receiver doesn't have on yet
if utc_string:
hours = (int(utc_string[0:2]) + self.local_offset) % 24
minutes = int(utc_string[2:4])
seconds = float(utc_string[4:])
else:
hours = 0
minutes = 0
seconds = 0.0
# Number of Satellites in Use
satellites_in_use = int(self.gps_segments[7])
# Get Fix Status
fix_stat = int(self.gps_segments[6])
except (ValueError, IndexError):
return False
try:
# Horizontal Dilution of Precision
hdop = float(self.gps_segments[8])
except (ValueError, IndexError):
hdop = 0.0
# Process Location and Speed Data if Fix is GOOD
if fix_stat:
# Longitude / Latitude
try:
# Latitude
l_string = self.gps_segments[2]
lat_degs = int(l_string[0:2])
lat_mins = float(l_string[2:])
lat_hemi = self.gps_segments[3]
# Longitude
l_string = self.gps_segments[4]
lon_degs = int(l_string[0:3])
lon_mins = float(l_string[3:])
lon_hemi = self.gps_segments[5]
except ValueError:
return False
if lat_hemi not in self.__HEMISPHERES:
return False
if lon_hemi not in self.__HEMISPHERES:
return False
# Altitude / Height Above Geoid
try:
altitude = float(self.gps_segments[9])
geoid_height = float(self.gps_segments[11])
except ValueError:
altitude = 0
geoid_height = 0
# Update Object Data
self._latitude = [lat_degs, lat_mins, lat_hemi]
self._longitude = [lon_degs, lon_mins, lon_hemi]
self.altitude = altitude
self.geoid_height = geoid_height
# Update Object Data
self.timestamp = [hours, minutes, seconds]
self.satellites_in_use = satellites_in_use
self.hdop = hdop
self.fix_stat = fix_stat
# If Fix is GOOD, update fix timestamp
if fix_stat:
self.new_fix_time()
return True
def gpgsa(self):
"""Parse GNSS DOP and Active Satellites (GSA) sentence. Updates GPS fix type, list of satellites used in
fix calculation, Position Dilution of Precision (PDOP), Horizontal Dilution of Precision (HDOP), Vertical
Dilution of Precision, and fix status"""
# Fix Type (None,2D or 3D)
try:
fix_type = int(self.gps_segments[2])
except ValueError:
return False
# Read All (up to 12) Available PRN Satellite Numbers
sats_used = []
for sats in range(12):
sat_number_str = self.gps_segments[3 + sats]
if sat_number_str:
try:
sat_number = int(sat_number_str)
sats_used.append(sat_number)
except ValueError:
return False
else:
break
# PDOP,HDOP,VDOP
try:
pdop = float(self.gps_segments[15])
hdop = float(self.gps_segments[16])
vdop = float(self.gps_segments[17])
except ValueError:
return False
# Update Object Data
self.fix_type = fix_type
# If Fix is GOOD, update fix timestamp
if fix_type > self.__NO_FIX:
self.new_fix_time()
self.satellites_used = sats_used
self.hdop = hdop
self.vdop = vdop
self.pdop = pdop
return True
def gpgsv(self):
"""Parse Satellites in View (GSV) sentence. Updates number of SV Sentences,the number of the last SV sentence
parsed, and data on each satellite present in the sentence"""
try:
num_sv_sentences = int(self.gps_segments[1])
current_sv_sentence = int(self.gps_segments[2])
sats_in_view = int(self.gps_segments[3])
except ValueError:
return False
# Create a blank dict to store all the satellite data from this sentence in:
# satellite PRN is key, tuple containing telemetry is value
satellite_dict = dict()
# Calculate Number of Satelites to pull data for and thus how many segment positions to read
if num_sv_sentences == current_sv_sentence:
# Last sentence may have 1-4 satellites; 5 - 20 positions
sat_segment_limit = (sats_in_view - ((num_sv_sentences - 1) * 4)) * 5
else:
sat_segment_limit = 20 # Non-last sentences have 4 satellites and thus read up to position 20
# Try to recover data for up to 4 satellites in sentence
for sats in range(4, sat_segment_limit, 4):
# If a PRN is present, grab satellite data
if self.gps_segments[sats]:
try:
sat_id = int(self.gps_segments[sats])
except (ValueError,IndexError):
return False
try: # elevation can be null (no value) when not tracking
elevation = int(self.gps_segments[sats+1])
except (ValueError,IndexError):
elevation = None
try: # azimuth can be null (no value) when not tracking
azimuth = int(self.gps_segments[sats+2])
except (ValueError,IndexError):
azimuth = None
try: # SNR can be null (no value) when not tracking
snr = int(self.gps_segments[sats+3])
except (ValueError,IndexError):
snr = None
# If no PRN is found, then the sentence has no more satellites to read
else:
break
# Add Satellite Data to Sentence Dict
satellite_dict[sat_id] = (elevation, azimuth, snr)
# Update Object Data
self.total_sv_sentences = num_sv_sentences
self.last_sv_sentence = current_sv_sentence
self.satellites_in_view = sats_in_view
# For a new set of sentences, we either clear out the existing sat data or
# update it as additional SV sentences are parsed
if current_sv_sentence == 1:
self.satellite_data = satellite_dict
else:
self.satellite_data.update(satellite_dict)
return True
##########################################
# Data Stream Handler Functions
##########################################
def new_sentence(self):
"""Adjust Object Flags in Preparation for a New Sentence"""
self.gps_segments = ['']
self.active_segment = 0
self.crc_xor = 0
self.sentence_active = True
self.process_crc = True
self.char_count = 0
def update(self, new_char):
"""Process a new input char and updates GPS object if necessary based on special characters ('$', ',', '*')
Function builds a list of received string that are validate by CRC prior to parsing by the appropriate
sentence function. Returns sentence type on successful parse, None otherwise"""
valid_sentence = False
# Validate new_char is a printable char
ascii_char = ord(new_char)
if 10 <= ascii_char <= 126:
self.char_count += 1
# Write Character to log file if enabled
if self.log_en:
self.write_log(new_char)
# Check if a new string is starting ($)
if new_char == '$':
self.new_sentence()
return None
elif self.sentence_active:
# Check if sentence is ending (*)
if new_char == '*':
self.process_crc = False
self.active_segment += 1
self.gps_segments.append('')
return None
# Check if a section is ended (,), Create a new substring to feed
# characters to
elif new_char == ',':
self.active_segment += 1
self.gps_segments.append('')
# Store All Other printable character and check CRC when ready
else:
self.gps_segments[self.active_segment] += new_char
# When CRC input is disabled, sentence is nearly complete
if not self.process_crc:
if len(self.gps_segments[self.active_segment]) == 2:
try:
final_crc = int(self.gps_segments[self.active_segment], 16)
if self.crc_xor == final_crc:
valid_sentence = True
else:
self.crc_fails += 1
except ValueError:
pass # CRC Value was deformed and could not have been correct
# Update CRC
if self.process_crc:
self.crc_xor ^= ascii_char
# If a Valid Sentence Was received and it's a supported sentence, then parse it!!
if valid_sentence:
self.clean_sentences += 1 # Increment clean sentences received
self.sentence_active = False # Clear Active Processing Flag
if self.gps_segments[0] in self.supported_sentences:
# parse the Sentence Based on the message type, return True if parse is clean
if self.supported_sentences[self.gps_segments[0]](self):
# Let host know that the GPS object was updated by returning parsed sentence type
self.parsed_sentences += 1
return self.gps_segments[0]
# Check that the sentence buffer isn't filling up with Garage waiting for the sentence to complete
if self.char_count > self.SENTENCE_LIMIT:
self.sentence_active = False
# Tell Host no new sentence was parsed
return None
def new_fix_time(self):
"""Updates a high resolution counter with current time when fix is updated. Currently only triggered from
GGA, GSA and RMC sentences"""
try:
self.fix_time = utime.ticks_ms()
except NameError:
self.fix_time = time.time()
#########################################
# User Helper Functions
# These functions make working with the GPS object data easier
#########################################
def satellite_data_updated(self):
"""
Checks if the all the GSV sentences in a group have been read, making satellite data complete
:return: boolean
"""
if self.total_sv_sentences > 0 and self.total_sv_sentences == self.last_sv_sentence:
return True
else:
return False
def unset_satellite_data_updated(self):
"""
Mark GSV sentences as read indicating the data has been used and future updates are fresh
"""
self.last_sv_sentence = 0
def satellites_visible(self):
"""
Returns a list of of the satellite PRNs currently visible to the receiver
:return: list
"""
return list(self.satellite_data.keys())
def time_since_fix(self):
"""Returns number of millisecond since the last sentence with a valid fix was parsed. Returns 0 if
no fix has been found"""
# Test if a Fix has been found
if self.fix_time == 0:
return -1
# Try calculating fix time using utime; if not running MicroPython
# time.time() returns a floating point value in secs
try:
current = utime.ticks_diff(utime.ticks_ms(), self.fix_time)
except NameError:
current = (time.time() - self.fix_time) * 1000 # ms
return current
def compass_direction(self):
"""
Determine a cardinal or inter-cardinal direction based on current course.
:return: string
"""
# Calculate the offset for a rotated compass
if self.course >= 348.75:
offset_course = 360 - self.course
else:
offset_course = self.course + 11.25
# Each compass point is separated by 22.5 degrees, divide to find lookup value
dir_index = floor(offset_course / 22.5)
final_dir = self.__DIRECTIONS[dir_index]
return final_dir
def latitude_string(self):
"""
Create a readable string of the current latitude data
:return: string
"""
if self.coord_format == 'dd':
formatted_latitude = self.latitude
lat_string = str(formatted_latitude[0]) + '° ' + str(self._latitude[2])
elif self.coord_format == 'dms':
formatted_latitude = self.latitude
lat_string = str(formatted_latitude[0]) + '° ' + str(formatted_latitude[1]) + "' " + str(formatted_latitude[2]) + '" ' + str(formatted_latitude[3])
else:
lat_string = str(self._latitude[0]) + '° ' + str(self._latitude[1]) + "' " + str(self._latitude[2])
return lat_string
def longitude_string(self):
"""
Create a readable string of the current longitude data
:return: string
"""
if self.coord_format == 'dd':
formatted_longitude = self.longitude
lon_string = str(formatted_longitude[0]) + '° ' + str(self._longitude[2])
elif self.coord_format == 'dms':
formatted_longitude = self.longitude
lon_string = str(formatted_longitude[0]) + '° ' + str(formatted_longitude[1]) + "' " + str(formatted_longitude[2]) + '" ' + str(formatted_longitude[3])
else:
lon_string = str(self._longitude[0]) + '° ' + str(self._longitude[1]) + "' " + str(self._longitude[2])
return lon_string
def speed_string(self, unit='kph'):
"""
Creates a readable string of the current speed data in one of three units
:param unit: string of 'kph','mph, or 'knot'
:return:
"""
if unit == 'mph':
speed_string = str(self.speed[1]) + ' mph'
elif unit == 'knot':
if self.speed[0] == 1:
unit_str = ' knot'
else:
unit_str = ' knots'
speed_string = str(self.speed[0]) + unit_str
else:
speed_string = str(self.speed[2]) + ' km/h'
return speed_string
def date_string(self, formatting='s_mdy', century='20'):
"""
Creates a readable string of the current date.
Can select between long format: Januray 1st, 2014
or two short formats:
11/01/2014 (MM/DD/YYYY)
01/11/2014 (DD/MM/YYYY)
:param formatting: string 's_mdy', 's_dmy', or 'long'
:param century: int delineating the century the GPS data is from (19 for 19XX, 20 for 20XX)
:return: date_string string with long or short format date
"""
# Long Format Januray 1st, 2014
if formatting == 'long':
# Retrieve Month string from private set
month = self.__MONTHS[self.date[1] - 1]
# Determine Date Suffix
if self.date[0] in (1, 21, 31):
suffix = 'st'
elif self.date[0] in (2, 22):
suffix = 'nd'
elif self.date[0] == (3, 23):
suffix = 'rd'
else:
suffix = 'th'
day = str(self.date[0]) + suffix # Create Day String
year = century + str(self.date[2]) # Create Year String
date_string = month + ' ' + day + ', ' + year # Put it all together
else:
# Add leading zeros to day string if necessary
if self.date[0] < 10:
day = '0' + str(self.date[0])
else:
day = str(self.date[0])
# Add leading zeros to month string if necessary
if self.date[1] < 10:
month = '0' + str(self.date[1])
else:
month = str(self.date[1])
# Add leading zeros to year string if necessary
if self.date[2] < 10:
year = '0' + str(self.date[2])
else:
year = str(self.date[2])
# Build final string based on desired formatting
if formatting == 's_dmy':
date_string = day + '/' + month + '/' + year
else: # Default date format
date_string = month + '/' + day + '/' + year
return date_string
# All the currently supported NMEA sentences
supported_sentences = {'GPRMC': gprmc, 'GLRMC': gprmc,
'GPGGA': gpgga, 'GLGGA': gpgga,
'GPVTG': gpvtg, 'GLVTG': gpvtg,
'GPGSA': gpgsa, 'GLGSA': gpgsa,
'GPGSV': gpgsv, 'GLGSV': gpgsv,
'GPGLL': gpgll, 'GLGLL': gpgll,
'GNGGA': gpgga, 'GNRMC': gprmc,
'GNVTG': gpvtg, 'GNGLL': gpgll,
'GNGSA': gpgsa,
}
if __name__ == "__main__":
pass
功能函数调用以及信息显示代码如下:
from machine import UART, Pin
import time
import hashlib
from micropyGPS import MicropyGPS
# Display Image & text on I2C driven ssd1306 OLED display
from ssd1306 import SSD1306_I2C
from machine import Pin, I2C
import framebuf
i2c1 = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
oled = SSD1306_I2C(128,64,i2c1)
uart0 = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1))
time.sleep(0.1)
rxData = bytes()
my_gps = MicropyGPS()
while True:
if uart0.any():
stat = my_gps.update(uart0.read(1).decode('ascii')) # Note the conversion to to chr, UART outputs ints normally
if stat:
oled.fill(0)
lat_disp = my_gps.latitude_string()
oled.text(lat_disp,5,10)
lon_disp = my_gps.longitude_string()
oled.text(lon_disp,5,30)
time_disp = my_gps.date_string('s_dmy')
oled.text(time_disp,5,50)
oled.show()
print('Latitude:', my_gps.latitude_string())
print('Longitude:', my_gps.longitude_string())
print('Speed:', my_gps.speed_string('kph'), 'or', my_gps.speed_string('mph'), 'or', my_gps.speed_string('knot'))
print('Date (Long Format):', my_gps.date_string('long'))
print('Date (Short D/M/Y Format):', my_gps.date_string('s_dmy'))
print('timestamp (Short [H,M,S] Format):', my_gps.timestamp)
stat = None
效果如图所示:
5 闹钟功能
扩展任务的要求是实现包含网络、显示、声音功能的Demo,我选择实现一个闹钟功能。
上电后联网更新本地时间
未到达指定时间时,正常显示日期和时间
到达指定时间后,显示提示语,并发出声音
5.1 硬件连接
本设计使用到的硬件包括Pico W、OLED屏幕以及蜂鸣器。OLED屏幕连接在I2C0上、蜂鸣器连接在D20接口上,硬件的连接如图所示。
5.2 软件编写
设计思路很简单,设备上电后,对使用到的外设进行初始化操作。接下来进行联网操作,根据设定的Wifi名称和密码,连接到指定的Wifi上,并向网络时间服务器申请查询当前的网络时间,根据获得的时间更新本地的RTC时间。后续检测当前时间和设定的闹钟时间是否一致,一致蜂鸣器发出声音提示到达指定时间。
首先导入需要使用的库函数
import machine
import time
import network
import ntptime
from ssd1306 import SSD1306_I2C
from machine import Pin, I2C
from machine import RTC
对使用到的外设进行初始化
ssid = 'HOST_BWG'
password = 'Xdy_China_Mobile'
set_hour = 21
set_minute = 36
set_second = 00
i2c1 = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
display = SSD1306_I2C(128,64,i2c1)
IO_buzzer = 20
buzzer = Pin(IO_buzzer, Pin.OUT)
指定需要连接的Wifi的名称和密码。
连接到网络后,更新本地的RTC时钟,并在OLED上显示当前的时间信息
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
# Wait for connect or fail
max_wait = 10
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
print('waiting for connection...')
time.sleep(1)
# Handle connection error
if wlan.status() != 3:
raise RuntimeError('network connection failed')
else:
print('connected')
status = wlan.ifconfig()
print( 'ip = ' + status[0] )
def sync_ntp():
ntptime.host = 'ntp1.aliyun.com'
ntptime.settime()
sync_ntp()
timezone=8
now = time.time()
now += timezone * 3600
t = time.localtime(now)
y, mt, d, h, m, s, *_ = t
day_str=f"{y}-{mt:02d}-{d:02d}"
time_str = f"{h:02d}:{m:02d}:{s:02d}"
display.fill(0)
display.text(day_str, 0, 0, 1)
display.text(time_str, 0, 12, 1)
display.show()
print("时间:", time_str)
定义一个函数用于检查当前时间与设定的闹钟时间是否一致,一致则控制蜂鸣器发出提示音,同时在OLED屏幕上显示提示语。
def check_alarm(set_hour,set_minute,set_second):
if set_hour == int(h) and set_minute == int(m) and set_second == int(s) :
while True:
display.fill(0)
display.text("Wake Up!", 0, 0, 1)
display.show()
time.sleep(0.2)
buzzer.low()
time.sleep(0.2)
buzzer.high()
while True:
now = time.time()
now += timezone * 3600
t = time.localtime(now)
y, mt, d, h, m, s, *_ = t
day_str=f"{y}-{mt:02d}-{d:02d}"
time_str = f"{h:02d}:{m:02d}:{s:02d}"
print("时间:", time_str)
display.fill(0)
display.text(day_str, 0, 0, 1)
display.text(time_str, 0, 12, 1)
display.show()
check_alarm(set_hour,set_minute,set_second)
5.3 运行效果
上述程序的运行效果如图所示,正常显示
到达指定时间OLED屏幕显示提示信息:
6 总结
通过参加本次活动,我熟悉了如何在RP2040 Pico W上烧写micropython的固件,同时初步学会了python的语法和python程序的组织结构。在使用相关的库后,感叹micropython的生态的强大,很适合快速验证产品的可行性,同时适合初学者建立对嵌入式的兴趣。本次活动中的Wifi模块和GPS模块的使用,建立了我对其工作原理和编程思想的初步认识,后续学习相关知识时,就有一定的理性认知,知道应该看哪些方面的知识。
俗话说“师傅领进门,修行看个人”,感谢EEWorld和得捷电子举行的这次活动,让我接触到了Pico W和相关外设的使用方法,期待下次活动能继续学习新的知识。
视频混剪
|