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/
我要赚赏金
