这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » Let'sDo第2期任务-转战MicroPython

共3条 1/1 1 跳转至

Let'sDo第2期任务-转战MicroPython

菜鸟
2024-08-06 10:23:02     打赏

RP2040支持C语言的开发,也支持MicroPython。Micropython用起来非常的简单,自己又太菜,所以转用mpy来编程啦。

1、烧写固件

github网站上下载支持这个墨水屏的固件。pimoroni-badger2040-v0.0.5-micropython-with-badger-os.zip烧写后,墨水屏就开始有显示了。


efaed5ba5057bd9aee604e00a79653e.jpg使用Thonny编辑器连接上板子,可以看见固件版本信息。

QQ_1722907269903.png

2、呼吸灯

mpy编程简单了许多,之前使用keil做了呼吸灯,使用PWM波驱动LED灯的亮灭,控制高低电平占比,控制亮度。但是那里输出的是个三角波。这次使用mpy编程,用PWM输出正玄波,这样亮度变化过度更自然。因为固件时定制的,参考着例子,可以看出在mpy中已经定义了LED灯的PWM输出,可以直接使用led方法设置LED灯的亮度。

QQ_1722909600291.png

import badger2040
import math
import time

breath=3			#一个完整的呼吸周期长度 秒
breathsplit=50		#周期细分
display = badger2040.Badger2040()

if __name__ == "__main__":
    while True:
        for i in range(breath*breathsplit):
            time.sleep(float(1/breathsplit))
            light=int(math.sin(math.pi/(breath*breathsplit)*i)*255)
            print(light)
            display.led(light)

3、电子书

在这个固件下使用墨水屏显示文字还是挺方便的。但是目前不支持中文。先准备一个小说文本放入"/book"文件夹下,我这里找了个英文诗歌《西风颂》,文件为纯文本文件。

import badger2040
import gc
import badger_os
from machine import Pin

# **** Put the name of your text file here *****
text_file = "/books/Ode_to_the_WestWind.txt"  # File must be on the MicroPython device

gc.collect()

# Global Constants
WIDTH = badger2040.WIDTH
HEIGHT = badger2040.HEIGHT

ARROW_THICKNESS = 3
ARROW_WIDTH = 18
ARROW_HEIGHT = 14
ARROW_PADDING = 2

TEXT_PADDING = 4
TEXT_WIDTH = WIDTH - TEXT_PADDING - TEXT_PADDING - ARROW_WIDTH

FONTS = ["sans", "gothic", "cursive", "serif"]
THICKNESSES = [2, 1, 1, 2]
# ------------------------------
#      Drawing functions
# ------------------------------


# Draw a upward arrow
def draw_up(x, y, width, height, thickness, padding):
    border = (thickness // 4) + padding
    display.line(x + border, y + height - border,
                 x + (width // 2), y + border)
    display.line(x + (width // 2), y + border,
                 x + width - border, y + height - border)


# Draw a downward arrow
def draw_down(x, y, width, height, thickness, padding):
    border = (thickness // 2) + padding
    display.line(x + border, y + border,
                 x + (width // 2), y + height - border)
    display.line(x + (width // 2), y + height - border,
                 x + width - border, y + border)


# Draw the frame of the reader
def draw_frame():
    display.set_pen(15)
    display.clear()
    display.set_pen(12)
    display.rectangle(WIDTH - ARROW_WIDTH, 0, ARROW_WIDTH, HEIGHT)
    display.set_pen(0)
    if state["current_page"] > 0:
        draw_up(WIDTH - ARROW_WIDTH, (HEIGHT // 4) - (ARROW_HEIGHT // 2),
                ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
    draw_down(WIDTH - ARROW_WIDTH, ((HEIGHT * 3) // 4) - (ARROW_HEIGHT // 2),
              ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)


# ------------------------------
#        Program setup
# ------------------------------

# Global variables
state = {
    "last_offset": 0,
    "current_page": 0,
    "font_idx": 0,
    "text_size": 0.5,
    "offsets": []
}
badger_os.state_load("ebook", state)

text_spacing = int(34 * state["text_size"])


# Create a new Badger and set it to update FAST
display = badger2040.Badger2040()
display.led(128)
display.set_update_speed(badger2040.UPDATE_FAST)


# ------------------------------
#         Render page
# ------------------------------

def render_page():
    row = 0
    line = ""
    pos = ebook.tell()
    next_pos = pos
    add_newline = False
    display.set_font(FONTS[state["font_idx"]])
    display.set_thickness(THICKNESSES[state["font_idx"]])

    while True:
        # Read a full line and split it into words
        words = ebook.readline().split(" ")

        # Take the length of the first word and advance our position
        next_word = words[0]
        if len(words) > 1:
            next_pos += len(next_word) + 1
        else:
            next_pos += len(next_word)  # This is the last word on the line

        # Advance our position further if the word contains special characters
        if '\u201c' in next_word:
            next_word = next_word.replace('\u201c', '\"')
            next_pos += 2
        if '\u201d' in next_word:
            next_word = next_word.replace('\u201d', '\"')
            next_pos += 2
        if '\u2019' in next_word:
            next_word = next_word.replace('\u2019', '\'')
            next_pos += 2

        # Rewind the file back from the line end to the start of the next word
        ebook.seek(next_pos)

        # Strip out any new line characters from the word
        next_word = next_word.strip()

        # If an empty word is encountered assume that means there was a blank line
        if len(next_word) == 0:
            add_newline = True

        # Append the word to the current line and measure its length
        appended_line = line
        if len(line) > 0 and len(next_word) > 0:
            appended_line += " "
        appended_line += next_word
        appended_length = display.measure_text(appended_line, state["text_size"])

        # Would this appended line be longer than the text display area, or was a blank line spotted?
        if appended_length >= TEXT_WIDTH or add_newline:

            # Yes, so write out the line prior to the append
            print(line)
            display.set_pen(0)
            display.text(line, TEXT_PADDING, (row * text_spacing) + (text_spacing // 2) + TEXT_PADDING, WIDTH, state["text_size"])

            # Clear the line and move on to the next row
            line = ""
            row += 1

            # Have we reached the end of the page?
            if (row * text_spacing) + text_spacing >= HEIGHT:
                print("+++++")
                display.update()

                # Reset the position to the start of the word that made this line too long
                ebook.seek(pos)
                return
            else:
                # Set the line to the word and advance the current position
                line = next_word
                pos = next_pos

            # A new line was spotted, so advance a row
            if add_newline:
                print("")
                row += 1
                if (row * text_spacing) + text_spacing >= HEIGHT:
                    print("+++++")
                    display.update()
                    return
                add_newline = False
        else:
            # The appended line was not too long, so set it as the line and advance the current position
            line = appended_line
            pos = next_pos


# ------------------------------
#       Main program loop
# ------------------------------

launch = True
changed = False
# 定义中断处理函数
def button_interrupt_handler(pin):
    if(str(pin).find("GPIO12")>0) :
        print("A key is passed")
        state["current_page"] += 1
        draw_page()        
    elif(str(pin).find("GPIO13")>0) :
        state["font_idx"] += 1
        if (state["font_idx"] >= len(FONTS)):
            state["font_idx"] = 0
        state["offsets"] = []
        ebook.seek(0)
        state["current_page"] = 0
        draw_page()
    elif(str(pin).find("GPIO14")>0) :
        print("C key is passed")
        if state["current_page"] > 0:
            state["current_page"] -= 1
            if state["current_page"] == 0:
                ebook.seek(0)
            else:
                ebook.seek(state["offsets"][state["current_page"] - 1])  # Retrieve the start position of the last page
            changed = True
        draw_page()


#绘制页面
def draw_page():
    draw_frame()
    render_page()
    # Is the next page one we've not displayed before?
    if state["current_page"] >= len(state["offsets"]):
        state["offsets"].append(ebook.tell())  # Add its start position to the state["offsets"] list
    badger_os.state_save("ebook", state)


    
# 配置按键连接的GPIO引脚
#button_pin = Pin(14, Pin.IN, Pin.PULL_UP)
button_a = Pin(12, Pin.IN,Pin.PULL_UP)
button_b = Pin(13, Pin.IN,Pin.PULL_UP)
button_c = Pin(14, Pin.IN,Pin.PULL_UP)

# 配置中断触发条件为下降沿(按键按下时为低电平)
button_a.irq(trigger=Pin.IRQ_FALLING, handler=button_interrupt_handler)
button_b.irq(trigger=Pin.IRQ_FALLING, handler=button_interrupt_handler)
button_c.irq(trigger=Pin.IRQ_FALLING, handler=button_interrupt_handler)

# Open the book file
ebook = open(text_file, "r")
if len(state["offsets"]) > state["current_page"]:
    ebook.seek(state["offsets"][state["current_page"]])
else:
    state["current_page"] = 0
    state["offsets"] = []

while True:
    # Sometimes a button press or hold will keep the system
    # powered *through* HALT, so latch the power back on.
    display.keepalive()
    
    if launch and not changed:
        print("come launch")
        if state["current_page"] > 0 and len(state["offsets"]) > state["current_page"] - 1:
            ebook.seek(state["offsets"][state["current_page"] - 1])
        changed = True
        launch = False
    
    if changed:
        draw_page()
        changed = False
  
    display.halt()

使用三个按键来控制翻页和切换字体。

1c9d9348bbe9dbfdaf5404a961204b2.jpg

a0c118f8f52cc0ce2bfdbd09e483fc0.jpg

6672ecf2f65f5bc1de5b1beb407debd.jpg

9c2e3b3fd754edba1747530bbabfb98.jpg

在例程中没有找到对墨水屏写点的操作,这样就没有办法实现写汉字的操作了。要写汉字需要重新编译对应的固件。


3、驱动舵机

下单时还购买了舵机和7789的屏幕。这里还在RP2040上使用mpy制作了个贪吃蛇的游戏。为了让游戏代入感更强,计划使用舵机作为力反馈。这里挑选的舵机为:DF9GMS 360度微型舵机 。这个舵机有点点特殊,360°舵机只能控制旋转方向,不能控制旋转角度。所以普通舵机控制代码不好使了。

image.png

from machine import Pin,PWM
import time
  
pwm = PWM(Pin(28))
pwm.freq(50)
 
for _ in range(3):
    pwm.duty_u16(1600)
    time.sleep(1)
 
    pwm.duty_u16(4815)
    time.sleep(1)
 
    pwm.duty_u16(7953)
    time.sleep(1)
 
    pwm.duty_u16(4815)
    time.sleep(1)

与传统的舵机控制相同,都是使用50Hz方波进行控制,不同的是方波高低电平占空比是不一样的。

4、贪吃蛇

贪吃蛇是一款老游戏了。这里使用1.14屏幕作为显示,屏幕左侧5向按钮作为操纵杆,完成了贪吃蛇游戏。舵机作为力反馈装置,用胶条粘在了屏幕侧面,从屏幕模块取5V电压作为驱动电压,从28脚用杜邦线接出信号,作为驱动信号线。

image.png

image.png

import random
import time
from machine import Pin,PWM
from lcd import LCD_1inch14

SCREEN_WIDTH=240
# 上下左右引脚, 通过上拉电阻设为高电平
UP_PIN    = Pin(2, Pin.IN, Pin.PULL_UP)
DOWN_PIN  = Pin(18, Pin.IN, Pin.PULL_UP)
LEFT_PIN  = Pin(16, Pin.IN, Pin.PULL_UP)
RIGHT_PIN = Pin(20, Pin.IN, Pin.PULL_UP)

# snake config
SNAKE_PIECE_SIZE = 5  # 蛇的每一格占用3*3个像素
MAX_SNAKE_LENGTH = 150  # 蛇的最长长度
MAP_SIZE_X = 32  # 活动范围
MAP_SIZE_Y = 25
START_SNAKE_SIZE = 5  # 初始长度
SNAKE_MOVE_DELAY = 30  # 移动延时


MID=4900
# game config
class State(object):
    START = 0
    RUNNING = 1
    GAMEOVER = 2

    @classmethod
    def setter(cls, state):
        if state == cls.START:
            return cls.START
        elif state == cls.RUNNING:
            return cls.RUNNING
        elif state == cls.GAMEOVER:
            return cls.GAMEOVER


class Direction(object):
    # 注意顺序
    UP = 0
    LEFT = 1
    DOWN = 2
    RIGHT = 3

    @classmethod
    def setter(cls, dirc):
        if dirc == cls.UP:
            return cls.UP
        elif dirc == cls.DOWN:
            return cls.DOWN
        elif dirc == cls.LEFT:
            return cls.LEFT
        elif dirc == cls.RIGHT:
            return cls.RIGHT


################ Snake 功能实现 ###################
class Snake(object):
    def __init__(self):
        self.snake = []  # 初始位置[(x1,y1),(x2,y2),...]一个元组列表
        self.fruit = []  # 水果,[x,y]
        self.snake_length = START_SNAKE_SIZE
        self.direction = Direction.RIGHT  # 当前前进方向
        self.new_direction = Direction.RIGHT  # 用户按键后的前进方向
        self.game_state = None
        self.servo = PWM(Pin(28))	#添加舵机
        self.servo.freq(50)			#驱动频率为50
        self.display = LCD_1inch14()
        self.setup_game()
        

    def setup_game(self):
        """初始化游戏"""
        self.game_state = State.START
        direction = Direction.RIGHT
        new_direction = Direction.RIGHT
        self.reset_snake()
        self.generate_fruit()
        self.display.fill(self.display.magenta)
        self.draw_map()
        self.show_score()
        self.show_press_to_start()
        self.display.show()


    def reset_snake(self):
        """重设蛇的位置"""
        self.snake = []  # 重置
        self.snake_length = START_SNAKE_SIZE
        for i in range(self.snake_length):
            self.snake.append((MAP_SIZE_X // 2 - i, MAP_SIZE_Y // 2))

    def check_fruit(self):
        """检测蛇是否吃到水果,能否继续吃水果"""
        if self.snake[0][0] == self.fruit[0] and self.snake[0][1] == self.fruit[1]:
            if self.snake_length + 1 < MAX_SNAKE_LENGTH:
                self.snake_length += 1
                # 吃到水果后,将蛇增加一格
                self.snake.insert(0, (self.fruit[0], self.fruit[1]))
                snake.servo_shake(timelong=120)
            self.generate_fruit()

    def generate_fruit(self):
        """随机生成水果位置,注意不能生成在蛇身上"""
        while True:
            self.fruit = [random.randint(1, MAP_SIZE_X - 1), random.randint(1, MAP_SIZE_Y - 1)]
            fruit = tuple(self.fruit)
            if fruit in self.snake:
                # 生成在蛇身上
                continue
            else:
                print('fruit: ', self.fruit)
                break
    
    def servo_shake(self,val=2000,timelong=200):
        """舵机震动"""
        self.servo.duty_u16(4700+val)
        time.sleep_ms(timelong)    
        self.servo.duty_u16(4700-val-500)
        time.sleep_ms(timelong)    
        self.servo.duty_u16(4700)
        time.sleep_ms(timelong)
        
    @staticmethod
    def button_press():
        """是否有按键按下"""
        for pin in UP_PIN, DOWN_PIN, LEFT_PIN, RIGHT_PIN:
            if pin.value() == 0:  # 低电平表示按下
                return True
        return False

    def read_direction(self):
        """读取新的按键方向,不能与当前方向相反"""
        for direction, pin in enumerate((UP_PIN, LEFT_PIN, DOWN_PIN, RIGHT_PIN)):
            if pin.value() == 0 and not (direction == (self.direction + 2) % 4):
                self.new_direction = Direction.setter(direction)
                return

    def collection_check(self, x, y):
        """检查蛇社否撞到墙或者(x,y)位置"""
        for i in self.snake:
            if x == i[0] and y == i[1]:
                return True
        if x < 0 or y < 0 or x >= MAP_SIZE_X or y >= MAP_SIZE_Y:
            return True
        return False

    def move_snake(self):
        """按照方向键移动蛇,返回能否继续移动的布尔值"""
        x, y = self.snake[0]
        new_x, new_y = x, y

        if self.direction == Direction.UP:
            new_y -= 1
        elif self.direction == Direction.DOWN:
            new_y += 1
        elif self.direction == Direction.LEFT:
            new_x -= 1
        elif self.direction == Direction.RIGHT:
            new_x += 1

        if self.collection_check(new_x, new_y):  # 不能继续移动
            return False

        self.snake.pop()  # 去除最后一个位置
        self.snake.insert(0, (new_x, new_y))  # 在开头添加新位置
        return True  # 能继续移动

    def draw_map(self):
        """绘制地图区域: 蛇、水果、边界"""
        offset_map_x = SCREEN_WIDTH - SNAKE_PIECE_SIZE * MAP_SIZE_X - 2
        offset_map_y = 2

        # 绘制水果
        self.display.fill_rect(self.fruit[0] * SNAKE_PIECE_SIZE + offset_map_x,
                          self.fruit[1] * SNAKE_PIECE_SIZE + offset_map_y,
                          SNAKE_PIECE_SIZE, SNAKE_PIECE_SIZE, self.display.red)
        # 绘制地图边界, 边界占一个像素,但是绘制时在内侧留一个像素,当蛇头部到达内部一个像素时,即判定为碰撞
        self.display.rect(offset_map_x - 2,
                          0,
                          SNAKE_PIECE_SIZE * MAP_SIZE_X + 4,
                          SNAKE_PIECE_SIZE * MAP_SIZE_Y + 4, self.display.black)
        # 绘制蛇
        for x, y in self.snake:
            self.display.fill_rect(x * SNAKE_PIECE_SIZE + offset_map_x,
                                   y * SNAKE_PIECE_SIZE + offset_map_y,
                                   SNAKE_PIECE_SIZE,
                                   SNAKE_PIECE_SIZE, self.display.black)

    def show_score(self):
        """显示得分"""
        score = self.snake_length - START_SNAKE_SIZE
        self.display.text('Score:%d' % score, 0, 2, self.display.black)

    def show_press_to_start(self):
        """提示按任意键开始游戏"""
        self.display.text('Press', 0, 16, self.display.black)
        self.display.text('button', 0, 26, self.display.black)
        self.display.text('start!', 0, 36, self.display.black)

    def show_game_over(self):
        """显示游戏结束"""
        self.display.text('Game', 0, 30, self.display.black)
        self.display.text('Over!', 0, 40, self.display.black)

#################  循环运行程序  ##################
if __name__ == '__main__':
    # print('******** Start ********')
    snake = Snake()
    move_time = 0
    while True:
        if snake.game_state == State.START:
            if Snake.button_press():
                snake.game_state = State.RUNNING

        elif snake.game_state == State.RUNNING:
            move_time += 1
            snake.read_direction()
            if move_time >= SNAKE_MOVE_DELAY:
                snake.direction = snake.new_direction
                snake.display.fill(snake.display.magenta)
                if not snake.move_snake():
                    snake.game_state = State.GAMEOVER
                    snake.show_game_over()
                    snake.servo_shake(timelong=220)
                    #time.sleep(1)
                snake.draw_map()
                snake.show_score()
                snake.display.show()
                snake.check_fruit()
                move_time = 0

        elif snake.game_state == State.GAMEOVER:
            if Snake.button_press():
                time.sleep_ms(500)
                snake.setup_game()
                print('******** new game ********')
                snake.game_state = State.START
        time.sleep_ms(5)

程序中设定了5X5个像素为一个基本单位,初始化时设置蛇的尺寸为5个基本单位。活动范围为25X32个基本单位的矩阵内活动,每次屏幕上会有一个水果,当水果被蛇触碰到后,蛇身体就会长长一个基本单位,然后在活动范围内随机再出现一个水果。通过控制蛇移动的速度来控制难度,当蛇身体变长时,就缩短每次移动的时间间隔,提高难度。当蛇吃到水果或撞墙时,舵机就会旋转一下,作为力反馈。

image.png

image.png

image.png

视频地址:https://www.bilibili.com/video/BV1d2Y5eCEut/

代码:https://share.eepw.com.cn/share/download/id/393189 


高工
2024-08-06 11:25:55     打赏
2楼

厉害了


工程师
2024-08-07 12:56:23     打赏
3楼

666666


共3条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]