RP2040支持C语言的开发,也支持MicroPython。Micropython用起来非常的简单,自己又太菜,所以转用mpy来编程啦。
1、烧写固件
在github网站上下载支持这个墨水屏的固件。pimoroni-badger2040-v0.0.5-micropython-with-badger-os.zip烧写后,墨水屏就开始有显示了。
使用Thonny编辑器连接上板子,可以看见固件版本信息。
2、呼吸灯
mpy编程简单了许多,之前使用keil做了呼吸灯,使用PWM波驱动LED灯的亮灭,控制高低电平占比,控制亮度。但是那里输出的是个三角波。这次使用mpy编程,用PWM输出正玄波,这样亮度变化过度更自然。因为固件时定制的,参考着例子,可以看出在mpy中已经定义了LED灯的PWM输出,可以直接使用led方法设置LED灯的亮度。
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()
使用三个按键来控制翻页和切换字体。
在例程中没有找到对墨水屏写点的操作,这样就没有办法实现写汉字的操作了。要写汉字需要重新编译对应的固件。
3、驱动舵机
下单时还购买了舵机和7789的屏幕。这里还在RP2040上使用mpy制作了个贪吃蛇的游戏。为了让游戏代入感更强,计划使用舵机作为力反馈。这里挑选的舵机为:DF9GMS 360度微型舵机 。这个舵机有点点特殊,360°舵机只能控制旋转方向,不能控制旋转角度。所以普通舵机控制代码不好使了。
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脚用杜邦线接出信号,作为驱动信号线。
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个基本单位的矩阵内活动,每次屏幕上会有一个水果,当水果被蛇触碰到后,蛇身体就会长长一个基本单位,然后在活动范围内随机再出现一个水果。通过控制蛇移动的速度来控制难度,当蛇身体变长时,就缩短每次移动的时间间隔,提高难度。当蛇吃到水果或撞墙时,舵机就会旋转一下,作为力反馈。
视频地址:https://www.bilibili.com/video/BV1d2Y5eCEut/