基于Step Pico设计的交通灯控制器
设计实现具有行人优先通行功能的交通灯。利用Pico多线程对按钮触发进行检测,并跳转到行人优先模式。转换图片为oled能够读取的格式,并根据当前交通灯状态改变图片内容。
标签
嵌入式系统
MCU
PICO
2023寒假在家练
寒假练
Desjajja
更新2023-03-28
南昌大学
722
太长不看版(演示视频,无解说,外链)
 

一、 项目描述

1.1 项目介绍

行人按钮是一种可以用来触发交通信号灯的按钮,通常位于红绿灯控制器旁边或者路口附近。行人按下按钮后,信号灯会变成行人绿灯,使行人可以安全地穿过道路。在一些地区,如果没有行人按下按钮,交通信号灯可能不会变成行人绿灯,这就需要行人等待更长时间。

本项目旨在利用Step Pico的控制功能结合上扩展板上的相关元件,模拟真实情况下的带有行人按钮的交通灯工作流。

1.2 设计思路

基本构成:由三色led模拟红黄绿灯,k1按钮充当行人按钮,蜂鸣器作为警报装置。当按钮未触发时,三色灯定时切换;当k1被触发,由于触发的持续时间很短,要求某处需要持续记录当前触发事件,因此可以利用多线程技术,在主线程循环交通灯的同时,快速检测k1的触发状态,当检测到触发后,将信号量置为真。此时,主线程在每个循环开始会先检测信号量,若为真,进入行人优先模式,此时信号灯为绿,蜂鸣器启动。

在此基础上,由于日常生活中有很多交通灯整合了带有行人形象的标志灯,本项目也同样模拟了这个功能。通过编写转换模块,把png等格式的图片转码为可被扩展板上oled显示屏直接读取的字节流格式,并根据当前交通灯的状态同步切换显示内容。

1.3 框图和软件流程图

+bprjeAO368AAAAASUVORK5CYII=
逻辑框图

u3GO+SjRhcuYi83YEUAAAQQQQACBsgUIl2UL0z4CCCCAAAIIIBCRAOEyomIzVAQQQAABBBBAoGwBwmXZwrSPAAIIIIAAAghEJEC4jKjYDBUBBBBAAAEEEChbgHBZtjDtI4AAAggggAACEQn8H3thpFjYiBY9AAAAAElFTkSuQmCC

软件流程图

1.4 硬件介绍

Pico麻雀虽小,五脏俱全。Pico是一款由英国树莓派基金会开发的微控制器板,它采用了Arm Cortex-M0+处理器,并搭载了2MB的闪存和264KB的SRAM,还集成了各种常用的硬件接口和控制器,如GPIO、SPI、I2C、PWM、ADC等。Pico的引脚布局与Arduino Uno兼容,可与许多Arduino扩展板兼容,,其可以通过C/C++以及MicroPython编程来学习嵌入式系统的工作原理和应用。

利用micropython SDK, 像我这样只有python开发经验的嵌入式小白也能通过查阅文档很快上手开发。由硬禾课堂提供的例程封装了扩展板的很多功能部件,如oled,led和button。在此基础上,我也封装了蜂鸣器的调用文件buzzer.py。

                    Fine2TE6eh2jfN4GfBcV4LT0C1vS

1.5 实现的功能及图片展示

交通灯默认以绿黄红色交替切换,且oled上显示的图像也根据交通灯的颜色变化。

ZI0B6N9U8SUUnoADJnidggw26OCDaSEI4YQURhQQADs=

绿                                                                                  黄

 

9k=Z

红                                                   行人优先

 

二、 主要代码片段及说明

2.1 循环主体和优先模式的逻辑

循环主体基本构成为:灯亮-屏显内容切换-延时-灯灭,值得注意的是,在黄灯亮起时,为实现行人标志闪烁),使用了poweroff/poweron对oled进行两轮断电加电(而不是清空图片内容),原因在于,断电后,oled的帧缓冲区仍会保存断电前的图片内容,可减少对图片的读写次数,提高性能。

If语句缩进代码块为优先模式的判断逻辑:如果信号量button_pressed=True, 表明检测到行人触发行人按钮,此时会在循环开始之前进入优先模式,蜂鸣器启动,oled图片切换为停车标志。结束后将信号量重置为False。

while True:
    if button_pressed == True:
       # 优先模式
        g.on()
        show_img(data[2])
        buzzer(5)
        g.off()
        button_pressed = False
    #绿灯
    g.on()
    show_img(data[0])
    utime.sleep(5)
    g.off()
    # 黄灯
    y.on()
    for _ in range(2):
        oled.poweroff()
        utime.sleep(0.5)
        oled.poweron()
        utime.sleep(0.5)
    y.off()
    # 红灯
    r.on()
    show_img(data[1])
    utime.sleep(5)
    r.off()

2.2 查询行人按键事件的线程

通过全局变量向主线程传递信息。功能很简单,即,当检测到k1被按下时,置button_pressed为True。需要注意的一点是,与python中的global相同,在函数体内部使用时,为与局部变量相区分,要先添加语句 global <变量名>。

# PV信号量,与button_reader_thread通信
global button_pressed
button_pressed = False

# 每0.01s读取k1的状态,将信号量置为True
def button_reader_thread():
    global button_pressed
    while True:
        if k1.value() == 1:
            button_pressed = True

之后,利用micropython多线程库_thread启动该线程。由于不需要参数传递,start_new_thread的第二个参数为空元组。

# 启动该线程 
_thread.start_new_thread(button_reader_thread,())

2.3 OLED显示屏的图像显示

Oled屏读取数据的格式时bytearray, 因此在下载到需要的图像之后,需要对其进行转换。

转换脚本接受参数:图片名,宽,高

from io import BytesIO
from PIL import Image
import sys
import pathlib
import os

THRESHOLD = 200


if len(sys.argv) > 1:
    path_to_image = str(sys.argv[1])
    x = int(sys.argv[2])
    y = int(sys.argv[3])
    filename = path_to_image.split('.')[0]
    
    im = Image.open(path_to_image)
    im = im.resize((x,y)).convert('1')
    
    # Define a threshold value to remove the point noise
    threshold_value = THRESHOLD

    # Apply the threshold using the point method
    def threshold(pixel):
        if pixel < threshold_value:
            return 0
        else:
            return 255

    
    
    im = im.point(threshold)

    
    
    pathlib.Path(os.path.join('out', filename)).mkdir(parents=True, exist_ok=True)

    buf = BytesIO()
    im.save(buf, 'ppm')
    im.save(f'./out/{filename}/{filename}-{x}x{y}.png')
    byte_im = buf.getvalue()
    temp = len(str(x) + ' ' + str(y)) + 4

    with open(f'./out/{filename}/{filename}-{x}x{y}.txt', 'w') as f:
        f.write(str(byte_im[temp::]))
    # print(byte_im[temp::])
    print('file converted successfully')
else:
    print("please specify the location of image i.e img2bytearray.py /path/to/image width heigh")

 转换流程:通过PIL先对图像进行预处理(缩放,二值化),然后以ppm格式存储,最后通过python内置io.BytesIO将其转为比特流。

转换完成后,缩放后的图片(png格式)和比特流文件(txt格式)存入以图片名命名的文件夹下,方便后续加载。

硬禾课堂提供的例程封装了oled的接口,但是为了项目内方便调用,在其上再次进行封装。

def load_img(img_name):
    img_root = img_name
    files = os.listdir(img_root)
    txt_files = [file for file in files if file.endswith('.txt')]
    txt_file = txt_files[0]
    _, wxh, _, offset_redundant = txt_file.split('-')
    offset = offset_redundant.split('.')[0]
    width, height = wxh.split('x')
    
    byte_path = img_root + '/' + txt_file
    with open(byte_path, 'r') as f:
        contents = f.read()
    bytes = eval(contents)
    return (bytearray(bytes), int(width), int(height), int(offset))

def show_img(data):
    TH, width, height, offset = data     
    fb = framebuf.FrameBuffer(TH, width, height, framebuf.MONO_HLSB)
    oled.fill(0)
    oled.blit(fb, offset, 0)
    oled.show()

Load_img: 根据上文所述图片名从txt文档中加载比特流,同时,文件名中还标记了图片宽高和位移。为下一步的绘制提供基本信息。

Show_img: 承接load_img返回的数据,底层调用oled模块将图片绘制到oled屏幕上。

交通灯的切换很考验实时性,而图片加载等io操作往往就是性能瓶颈,为避免出现延后等问题,图片在初始化阶段即加载完成。

# 一次性读入图像, 后续循环直接读取data
data = [load_img('walk'), load_img('stand'), load_img('stop_sign')]
# 初始化交通灯
r.off()
g.off()
y.off()

之后的循环内直接读取缓存中的图片:

while True:
    if button_pressed == True:
        g.on()
        show_img(data[2])   # 此处切换图片
        buzzer(5)
        g.off()
        button_pressed = False

    g.on()
    show_img(data[0])      # 此处切换图片
    utime.sleep(5)
    g.off()
    y.on()

    for _ in range(2):
        oled.poweroff()
        utime.sleep(0.5)
        oled.poweron()
        utime.sleep(0.5)
    y.off()
    
    r.on()
    show_img(data[1])     # 此处切换图片
    utime.sleep(5)
    r.off()

三、 遇到的主要难题及解决方法

问题一:彩色图片经转换后,出现大量噪点,使得图片信息损失严重。

                 w9FDQXnEOE8uAAAAABJRU5ErkJggg==                                                                              9k=

解决:之前的图片预处理过程是:先二值化,再进行放缩。而在第一步彩色像素会被转化成黑白混杂的像素块,此时放缩,局部采样就会出现斑点。因此,我先进行放缩,再二值化。有效的提高了图片质量。

                                                                 kNqqc7zAAAAABJRU5ErkJggg==        

问题二:考虑到图片io的性能制约,我一开始时打算再创建一个线程处理oled显示。而micropython多线程库处尚处于实验阶段,arm处理器的每个核只能运行一个线程,所以总共只能运行两个线程。

解决:如上文所述,我改进了图片加载的流程,有效的降低了图片加载时延。

四、 未来的计划或建议

经过这次的锻炼,我对硬件开发这个领域再次产生了浓厚的兴趣(上次是数电考试成绩出分之前)。下次我想试一下FPGA(虽然感觉会很难)。

有一点小建议是,虽然平时看文档也不少,但是涉及到硬件的datasheet让我这个半吊子还是晕头转向,希望可以有课程教大家如何从中有效检索信息。

五、 总结

之前说到嵌入式总想到的是单片机,经过这次上手体验Pico,我意识到小小的MCU也有巨大的潜力。

附件下载
codebase.zip
项目代码包
团队介绍
南昌大学19级大数据
团队成员
Desjajja
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号