【得捷电子Follow me第1期】007:扩展任务-天气灯设计
[复制链接]
本帖最后由 sipower 于 2023-6-8 23:43 编辑
一、规定任务的完成情况
首先,我汇总一下前面几贴对规定任务的完成情况。
第一帖,主要介绍了开箱、搭建开发环境和点亮LED的情况。展示了收到的开发套件。测试了Thonny和Mu Editor两种开发软件,并点亮了板上的LED。帖子链接如下:
https://bbs.eeworld.com.cn/thread-1244148-1-1.html
第二贴,介绍驱动两个外设:蜂鸣器和OLED。展示了这两个外设的综合应用:在屏幕上显示小星星,并且用蜂鸣器演奏出来,在演奏同时,LED灯按照节拍闪烁,发布了演示视频。帖子链接如下:
https://bbs.eeworld.com.cn/thread-1244149-1-1.html
第三贴,介绍网络连接和同步时钟。Pico W先是进行连网,然后访问NTP服务器校准RTC。其中我发现使用官方的ntptime.py不管怎么设置,获取到的时间都是UTC0时间,不能设置成UTC8的时间,经过反复查验,终于找到原因,并跟帖说明原因。帖子链接如下:
https://bbs.eeworld.com.cn/thread-1245498-1-1.html
第四贴,介绍GPS定位数据获取并把数据显示在OLED上。硬件上采用UART0连接GNSS模组。软件采用micropyGPS库进行定位信息的解析。实现了GPS模块数据接收,在OLED上显示定位的经纬度,在REPL窗口上输出相关的NIMA报文解析结果。帖子链接如下:
https://bbs.eeworld.com.cn/thread-1245499-1-1.html
第五贴,介绍利用3轴数字加速度计做一个简易水平仪。采用GROVE 3-AXIS DIGITAL ACCEL LIS3D模块读取位置信息,通过在OLED屏幕上显示一个可移动方块代表水平程度,实现基本水平仪功能。除了展示代码,还发布了演示视频。帖子链接如下:
https://bbs.eeworld.com.cn/thread-1245570-1-1.html
第六贴,介绍在水平仪基础上添加WS2812B灯珠做一个摇摇灯。通过读取加速度传感器的数据控制灯珠颜色。为了让颜色变化时,过渡的更丝滑、自然,我选用NeoPixel library的HSV颜色空间。在本贴中,对摇摇灯的工作原理和代码进行了详细介绍,并发布了演示视频。帖子链接如下:
https://bbs.eeworld.com.cn/thread-1245651-1-1.html
以上六篇帖子,除了完成规定任务之外,还测试了加速度传感器和数字灯珠。这些前期工作,都为我的扩展任务,设计一个天气灯打下了基础。
二、扩展任务
经过前面积累,我最终确定下来我的终极扩展任务,设计一个天气灯。下图是实物作品图片。
图1、天气灯实物
1、该作品要实现的功能
a、使用高德API通过网络获取公网IP地址,通过IP地址定位城市
b、利用获得的城市信息通过高德API查询天气信息并显示在OLED屏幕上
c、通过网络NTP服务校准时钟,并在OLED屏幕上显示实时时钟
d、使用加速度传感器进行动作识别,摇晃天气灯可以控制灯光变化,其中:
e、上下摇晃控制灯光开关;左右摇晃控制灯光变亮;前后摇晃控制灯光变暗。
f、每次动作识别成功,蜂鸣器都会进行声音提示。
g、频繁的晃动天气灯,会随机触发声光秀,演奏一曲小星星,并伴随灯光色彩变换。
2、该作品组成
a、PICO W主控模块
b、OLED显示模块
c、蜂鸣器模块
d、加速度传感器模块
e、WS2812B发光模块
f、整个作品由USB供电
g、程序使用microPython设计
3、系统框图
图2、系统框图
4、详细设计
在本设计中,基础的操作,如灯光控制,OLED显示,加速度传感器数值读取,联网,同步时钟等在前面帖子详细介绍过,此处略过,下面重点介绍新增的操作。
首先是获取天气设计。最开始计划采用GPS模块先获取经纬度,然后利用经纬度联网获取城市信息,再用城市信息联网获取天气信息。实际调试中,GPS模块不能确保在室内准确获取经纬度,这就造成后续的操作都不能实现,只好放弃GPS模块方案。
后来看到论坛里大佬发帖,采用公网IP地址方式获取城市信息,然后再获取天气信息实际效果很好,我就拿来主义,直接用上此方案了。大佬帖子链接如下:
https://bbs.eeworld.com.cn/thread-1243482-1-1.html
然后根据帖子中的介绍,我又查阅了高德API相关介绍,在编写程序时,我主要获取了城市名称,天气信息,温度和湿度这四个参数,最终显示在OLED屏幕上。高德接口链接如下:
天气查询
https://lbs.amap.com/api/webservice/guide/api/weatherinfo
为了实现不同天气显示不同颜色,我参照天气现象对照表,在程序中做了一个列表,为每一个天气现象分配了一个颜色数值,这样就实现了不同天气,发出不同颜色的灯光。具体信息可以参见下面程序代码。
天气现象对照表
https://lbs.amap.com/api/webservice/guide/tools/weather-code
然后是动作识别算法。大致思路是,每0.1秒读取一次加速传感器数值,连续读取15次,三个轴分别计算最大最小值,然后做差获得这1.5秒内的动作最大幅度变化值,然后把这个值和事先设定的基准值作比较,大于基准值则认为在对应轴向上有摇晃动作,产生调光信号。实际验证此算法基本可行,操作过程需要训练一下掌握好平衡,否则多个轴向都会触发调光动作。
接下来介绍获取UTC8时间,即北京时间。第三贴中,我发现使用官方库文件ntptime.py不管怎么设置,获取到的时间都是UTC0时间,不能设置成UTC8的时间,为了解决此问题,我修改了ntptime.py这个文件,在最后返回的数值基础上加了8小时,如下代码,另存为ntptime8.py,在放到PICO W板根目录,这样调用ntptime8.settime()校准时间,就是UTC8了。
return val - NTP_DELTA + 28800
代码1、设置UTC8时间
最后介绍声光秀实现。在第二贴中,有一段演奏小星星的蜂鸣器代码,我在驱动每个音节的时候,插入了WS2812B的代码,让其发光颜色跟声音频率关联起来,不同频率的音节对应不同颜色,就实现了声光秀表演。在主程序中,每次有效摇晃动作识别成功,都累计一次数,然后和一个随机数比较,如果大于随机数,就调用声光秀代码,这样就实现了随机触发灯光秀机制。随机触发代码段如下。
tk_num += 1
if tk_num > random.randint(5,20):#小星星触发次数
tk_num = 0
Twinkle()
代码2、随机触发代码
整体的主程序代码如下,其他引用的库文件见附件。
import network
import socket
import machine
import ubinascii
import urequests
import ujson
import time
import ntptime8
import random
from ssd1306 import SSD1306_I2C
import ufont
from neopixel import Neopixel
from micropython_lis3dh import lis3dh
from machine import Pin, PWM
from machine import RTC
TEMP = "25"
HUMI = "55"
CITY = "天街"
WEATH = "晴"
# 定义音调频率
tones = {'1': 262, '2': 294, '3': 330, '4': 349, '5': 392, '6': 440, '7': 494, '-': 0}
# 定义小星星旋律
melody = "1155665-4433221-5544332-5544332-1155665-4433221"
pwm = PWM(Pin(16))# Construct PWM object, with BEEP on Pin(16).
def beep(tone):
pwm.freq(tones[tone]) # 调整PWM的频率,使其发出指定的音调
pwm.duty_u16(10000)
time.sleep_ms(100)
pwm.duty_u16(0)
def Twinkle():
for tone in melody:
freq = tones[tone]
if freq:
pwm.freq(freq) # 调整PWM的频率,使其发出指定的音调
pwm.duty_u16(10000)
led.on()
WS2812B(hue = freq*150, sat=255, val=255)
else:
pwm.duty_u16(0) # 空拍时一样不上电
led.off()
WS2812B(hue = 150, sat=255, val=255)
# 停顿一下 (四四拍每秒两个音,每个音节中间稍微停顿一下)
time.sleep_ms(200)
pwm.duty_u16(0) # 设备占空比为0,即不上电
led.off()
#WS2812B(hue = 150, sat=255, val=255)
time.sleep_ms(100)
strip = Neopixel(3, 0, 22, "GRB") #RGB LED init
def WS2812B(hue=255, sat=255, val=255):
color = strip.colorHSV(hue, sat, val)
strip.fill(color)
strip.show()
def numberMap(x, in_min, in_max, out_min, out_max):
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
i2c0 = machine.I2C(0,sda=machine.Pin("GP8"), scl=machine.Pin("GP9"), freq=400000)
lis = lis3dh.LIS3DH(i2c0)
lis.data_rate = lis3dh.DATARATE_200
led = machine.Pin("LED",machine.Pin.OUT)
i2c1 = machine.I2C(1, sda=machine.Pin("GP6"), scl=machine.Pin("GP7"), freq=400000)
display = SSD1306_I2C(128,64,i2c1)
display.fill(0)
font = ufont.BMFont("unifont-14-12917-16.v3.bmf")
font.text(display, "天气灯", 40, 15, show=True)
font.text(display, "Loading...", 24, 35, show=True)
ssid = 'ssid'
password = 'password '
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] )
ntptime8.host = 'ntp1.aliyun.com' # 可选,ntp服务器,默认是"pool.ntp.org"
ntptime8.settime() # 修改设备时间,到这就已经设置好了
print('NTP OK')
def open_socket():
ip = urequests.get('http://ip.42.pl/raw')
ip_str = ip.content.decode('ascii')
print(ip_str)
ip.close()
str_data = 'https://restapi.amap.com/v3/ip?ip='+ip_str+'&output=JSON&key=1e459c41f6b32822efa6442e74ca09c0'
city = urequests.get(str_data)
city_str = city.content.decode('ascii')
#print(city_str)
parsed_city = ujson.loads(city_str)
adcode = parsed_city["adcode"]
city.close()
r = urequests.get('https://restapi.amap.com/v3/weather/weatherInfo?key=1e459c41f6b32822efa6442e74ca09c0&city='+adcode)
w_str = r.content.decode('ascii')
# print(r.text)
#print('111:'+ w_str)
parsed_w = ujson.loads(w_str)
# print (parsed_w)
#w_f = parsed_w["temperature"]
temp_str = parsed_w["lives"][0]["temperature_float"]
humi_str = parsed_w["lives"][0]["humidity_float"]
str_city = parsed_w["lives"][0]["city"]
str_weather = parsed_w["lives"][0]["weather"]
global TEMP
TEMP = temp_str
global HUMI
HUMI = humi_str
global CITY
CITY = str_city
global WEATH
WEATH = str_weather
print (str_city)
print (str_weather)
print (temp_str)
print (humi_str)
r.close()
try:
open_socket()
time.sleep(0.1)
display.fill(0)
TEMP_disp = "城市:"+CITY
font.text(display, TEMP_disp, 0, 0*16, show=True)
TEMP_disp = "天气:"+WEATH
font.text(display, TEMP_disp, 0, 1*16, show=True)
TEMP_disp = "T:"+TEMP+"℃"+"H:"+HUMI+"%"
font.text(display, TEMP_disp, 0, 2*16, show=True)
# TEMP_disp = "21:22:33"
# font.text(display, TEMP_disp, 0, 3*16, show=True)
except KeyboardInterrupt:
machine.reset()
C_V = 10 #动作比较值
sw = 0 #灯开关
t=0 #检测次数
hue = 0
sat = 0
val = 0
val_set = 255#亮度设置
en_set = 0#设置使能
tk_num = 0#小星星计数触发
rtc_num = 0#时间显示计数触发
x_max=0
x_min=0
y_max=0
y_min=0
z_max=0
z_min=0
weather_hue = {
'晴':21845,'少云':21845,'晴间多云':21845,'多云':21845,'阴':32768,
'有风':38228,'平静':38228,'微风':38228,'和风':38228,'清风':38228,
'强风/劲风':38428,'疾风':38428,'大风':38428,'烈风':38428,'风暴':39428,'狂爆风':39428,'飓风':39428,'热带风暴':39428,
'霾':10000,'中度霾':10000,'重度霾':10000,'严重霾':10000,
'阵雨':30000,'雷阵雨':30000,'雷阵雨并伴有冰雹':30000,'小雨':30000,'中雨':30000,'大雨':30000,
'暴雨':35000,'大暴雨':35000,'特大暴雨':35000,'强阵雨':35000,'强雷阵雨':35000,'极端降雨':35000,
'毛毛雨/细雨':32500,'雨':32500,'小雨-中雨':32500,'中雨-大雨':32500,'大雨-暴雨':32500,'暴雨-大暴雨':32500,'大暴雨-特大暴雨':32500,
'雨雪天气':40000,'雨夹雪':40000,'阵雨夹雪':40000,'冻雨':40000,
'雪':40000,'阵雪':40000,'小雪':40000,'中雪':40000,'大雪':43690,'小雪-中雪':43690,'中雪-大雪':43690,'大雪-暴雪':43690,
'浮尘':1000,'扬沙':1000,'沙尘暴':1000,'强沙尘暴':1000,'龙卷风':1000,
'雾':13000,'浓雾':13000,'强浓雾':13000,'轻雾':13000,'大雾':13000,'特强浓雾':13000,
'热':0,'冷':43690,'未知':54612
}
while True:
x,y,z = lis.acceleration
if x>x_max:
x_max=x
if x<x_min:
x_min=x
if y>y_max:
y_max=y
if y<y_min:
y_min=y
if z>z_max:
z_max=z
if z<z_min:
z_min=z
t+=1
if t > 15:#15次计算一下
t=0
d_x=x_max-x_min
d_y=y_max-y_min
d_z=z_max-z_min
x_max=x
x_min=x
y_max=y
y_min=y
z_max=z
z_min=z
# print (d_x,d_y,d_z)
if d_x>C_V:
en_set = 1
val_set += 50
if val_set > 255:
val_set = 255
if d_y>C_V:
val_set -= 50
en_set = 1
if val_set < 1:
val_set = 1
if d_z>C_V:
en_set = 1
sw ^= 1
# print (val_set)
if sw==1:
val = val_set
else:
val = 0
if en_set == 1:
en_set = 0
hue = weather_hue[WEATH] #需要改成天气查表颜色
sat = 255
WS2812B(hue, sat, val)
beep('6')
tk_num += 1
if tk_num > random.randint(5,20):#小星星触发次数
tk_num = 0
Twinkle()
time.sleep(0.1)
rtc_num += 1
if rtc_num > 10:#每秒刷新一次
rtc_num = 0
rtc = RTC()
# print(rtc.datetime())
display.rect(0,3*16,8*8,16,0,True)
TEMP_disp = "%02d:%02d:%02d" % (rtc.datetime()[4],rtc.datetime()[5],rtc.datetime()[6])
font.text(display, TEMP_disp, 0, 3*16, show=True)
代码3、主程序
通过以上设计,最终完成天气灯这个作品,整个作品的介绍和演示视频如下,链接为高清视频。
bk
https://training.eeworld.com.cn/uploadcourse/67992/lesson
视频1、天气灯演示
三、总结
通过本次活动,我熟悉了树莓派PICO W的基本设计开发流程,对microPython更加了解,也在论坛里认识了更多技术大佬。感谢得捷电子和EEWORLD提供的这次机会,也希望在接下来的活动中,提供更多好玩的板子和题目,让大家一起快乐搞机。
|