一、模块介绍
1. PICO模块的主要功能如下图所示
- PICO模块拥有264KB的SRAM(RP2040片内自带)和2MB的Flash(模块上外扩),存储空间比较大,适合于MicroPython编程学习
- GPIO方面的优势,能够灵活配置,支持实时性比较强的应用
- 内部ADC支持两路模拟信号的输入,采样率为500Kbps,并可以提供外部参考电压
- 23数字GPIO + 3个模拟/数字复用的输入管脚,这3个管脚可以做为ADC的输入
- 通过USB供电和程序配置
2.拓展板外设介绍
- 控制输入 - 2个按键 + 1个光电旋转编码器,用以对PICO进行控制和参数设置
- LCD显示 - 通过SPI连接,1.54寸、分辨率为240 * 240的真彩色LCD模块
- 模拟Mic音频输入 - 麦克风 + 运放 + 低通滤波器利用内部的ADC制作电压表或简易示波器
- 蜂鸣器音频输出
- 双声道音频输出,通过耳机插座输出
- 三轴姿态传感器(MMA7660)- 通过I2C连接姿态传感器,在姿态传感器上还将中断管脚INT连接到GPIO上,用以对姿态变化的快速中断响应
- SD卡
- 红外发射/接收(本次开发板没有)
- UART接口 - 1个UART插座(TX、RX、3.3V、GND),可以同其它板卡通过UART进行通信
- I2C接口 - 在连接了板上姿态传感器的同时还可以连接其它I2C的传感器或I2C外设(每个I2C外设地址不同)
二、项目介绍
项目3 - 设计/移植一款游戏
-
设计或移植一款经典的游戏,通过LCD屏显示,通过按键和旋转编码器控制
-
在游戏中要通过蜂鸣器播放背景音乐
本次设计的是贪吃蛇,通过蜂鸣器播放背景音乐,显示将贪吃蛇显示到240X240的屏幕上,使用mma7660三轴姿态传感器给贪吃蛇切换方向,使用光电旋转编码器给贪吃蛇改变速度,顺时针为速度+2,逆时针为速度-2,贪吃蛇撞墙则游戏结束,并显示分数。贪吃蛇长度达到30X30-1的长度时则显示胜利,并显示分数。
三、代码解释
1.初始化LCD显示器
st7789_res = 0
st7789_dc = 1
disp_width = 240
disp_height = 240
spi_sck=machine.Pin(2)
spi_tx=machine.Pin(3)
spi0=machine.SPI(0,baudrate=4000000, phase=0, polarity=1, sck=spi_sck, mosi=spi_tx)
display = st7789.ST7789(spi0, disp_width, disp_width,
reset=machine.Pin(st7789_res, machine.Pin.OUT),
dc=machine.Pin(st7789_dc, machine.Pin.OUT),
xstart=0, ystart=0, rotation=0)
2.初始化mma7660三轴姿态传感器
SCL = Pin(11) #mma7660 11号引脚
SDA = Pin(10) #mma7660 10号引脚
BUS = 1
pin = Pin(9, Pin.IN, Pin.PULL_DOWN) #mma7660 9号引脚
mma7660 = I2C(BUS,scl=SCL, sda=SDA, freq=40000000)
address = mma7660.scan() #获取装置位置
print("激活 MMA7660")
mma7660.writeto_mem(76,7,b'1') #向0x07寄存器写入01,执行主动模式
display.fill(st7789.WHITE)
3.初始化旋转编码器
key = machine.Pin(6,machine.Pin.IN)
keyleft=machine.Pin(4,machine.Pin.IN)
keyright=machine.Pin(5,machine.Pin.IN)
4.构建一个Snake类,在其中实现贪吃蛇绘制,出现随机事物,绘制实物,贪吃蛇尾部刷新,贪吃蛇吃东西成长,移动蛇身,播放音乐的功能。
class Snake:
# 构建snake
def __init__(self,cube=8):
#默认网格大小4
self.cube_width = cube
self.cube_color = 0xc618 # 50%灰色0x7bef 20%灰色 0xc618
self.snake_color= 0x0000 #黑色
self.disp_width_num, self.disp_height_num = disp_width // self.cube_width, disp_height // self.cube_width
self.snake_body = []
self.snake_body.append((int(self.disp_width_num // 2 * self.cube_width),
int(self.disp_height_num //2 * self.cube_width))) #贪吃蛇的头
self.food_position = [20,20] # 产生食物,第一个位置,不定义create_food会定义为一个元祖,导致报错
self.direction = Direction.LEFT
self.last_pos = None
①绘制网格:没多大实际作用,可以不画出来,比较适合纠错。
def draw_grads(self): #绘制网格
for i in range(self.disp_width_num + 1):
display.vline(self.cube_width*i,0,disp_height,self.cube_color)
for i in range(self.disp_height_num + 1):
display.hline(0,self.cube_width*i,disp_width,self.cube_color)
②绘制贪吃蛇
def draw_snake(self): #绘制贪吃蛇
print('贪吃蛇初始位置:{}'.format(self.snake_body))
for i in self.snake_body:
display.fill_rect(i[0],i[1],self.cube_width,self.cube_width,0x0000)
③出现随机食物,使用random函数,将食物的随机坐标显示出来。
def create_food(self): #随机食物
position = (random.randint(0, self.disp_width_num - 1), random.randint(0,self.disp_height_num - 1))
self.food_position = position
④绘制食物。
def draw_food(self): #绘制食物
display.fill_rect(self.food_position[0]*self.cube_width,self.food_position[1]*self.cube_width,self.cube_width,self.cube_width,st7789.RED)
⑤吃东西变长,蛇头的坐标跟食物的坐标一样,则表示吃到了食物。
def grow_up(self): #吃东西成长
if self.snake_body[0][0] == self.food_position[0] * self.cube_width and \
self.snake_body[0][1] == self.food_position[1] * self.cube_width :
return True
else:
return False
⑥更新蛇的位置,包括屁股后的位置,主要功能为跟上蛇头,并将最后一个点设置为空白色。
def refresh(self): #更新蛇的位置
for i in range(len(self.snake_body) - 1, 0, -1):
self.snake_body[i] = self.snake_body[i-1]
# for i in self.snake_body:
display.fill_rect(self.last_pos[0],self.last_pos[1],self.cube_width,self.cube_width,0xffff)
⑦移动蛇身,上则是蛇头的纵坐标-1,下则是纵坐标+1,其他方向同理,只需要改变蛇头的位置。后面跟着接上就好了。
def move(self): # 移动蛇身
if self.direction == Direction.UP:
self.snake_body[0] = (self.snake_body[0][0],self.snake_body[0][1] - self.cube_width)
elif self.direction == Direction.DOWN:
self.snake_body[0] = (self.snake_body[0][0],self.snake_body[0][1] + self.cube_width)
elif self.direction == Direction.LEFT:
self.snake_body[0] = (self.snake_body[0][0] - self.cube_width,self.snake_body[0][1])
elif self.direction == Direction.RIGHT:
self.snake_body[0] = (self.snake_body[0][0] + self.cube_width,self.snake_body[0][1])
⑧播放音乐
def song(self): #播放一声音乐
mySong = music(song2)
mySong.tick()
5.创建一个游戏控制函数class Game,判断输赢,判断方向,判断旋转编码器,游戏分数,运行函数,游戏的初始速度为4 。
①判断输赢,如果贪吃蛇的长度=格子数的乘积-1,就判断赢,如果0<=蛇的坐标>=边长,就输。
def is_win(self): # 判断是否赢
return len(self.get_body) == self.snake.disp_width_num * self.snake.disp_height_num - 1
def is_fail(self): # 判断是否输
if not 0 <= self.get_body[0][0] < disp_width+1 or not 0 <= self.get_body[0][1] < disp_height+1:
return True
return False
②判断方向,如果是前进则不能直接倒退,其他方向同理,不能屁股变为头。
def get_mma7660(self):
TILTS = mma7660.readfrom_mem(76,3,8) # 获取方向数据
TILT=TILTS[0:1:1] # 取8位字符的第一个位
if TILT== b'\x19':
if self.snake.direction != Direction.UP: # 不能直接调转,屁股变尾巴。
self.snake.direction = Direction.DOWN
elif TILT == b'\x15':
if self.snake.direction != Direction.DOWN:
self.snake.direction = Direction.UP
elif TILT == b'\x05':
if self.snake.direction != Direction.RIGHT:
self.snake.direction = Direction.LEFT
elif TILT == b'\t':
if self.snake.direction != Direction.LEFT:
self.snake.direction = Direction.RIGHT
else:
pass
# self.snake.direction = None
③旋转编码器,逆时针速度-2,顺时针速度+2
def int_handler(self,pin): # 旋转编码器控制贪吃蛇速度,中断
keyleft.irq(handler=None)
if keyleft.value() == 0:
if keyright.value() == 1:
self.fps +=2
if keyright.value() == 0:
self.fps -=2
if keyleft.value() == 1:
if keyright.value() == 0:
self.fps +=2
if keyright.value() == 1:
self.fps -=2
keyleft.irq(handler=self.int_handler)
④统计游戏分数:贪吃蛇的长度-1
@property
def score(self): # 游戏分数
return len(self.get_body) - 1
⑤运行函数
def run(self):
self.state = GameState.PLAYING
update_time = time.ticks_ms()
self.snake.draw_grads() #画出网格
self.snake.song()
display.fill(st7789.WHITE)
keyleft.irq(trigger=machine.Pin.IRQ_FALLING|machine.Pin.IRQ_RISING, handler=self.int_handler) #中断实现
while self.state == GameState.PLAYING:
self.get_mma7660() #方向检测
# self.snake_speed() #贪吃蛇速度
self.snake.last_pos = self.get_body[-1] #保存尾部的位置,小蛇吃了食物,需要在尾部增长
if time.ticks_diff(time.ticks_ms(), update_time) > (1000 // self.fps):
self.snake.refresh() #更新身体位置
self.snake.move() #改变头部的位置
if self.snake.grow_up(): # 是否吃到食物,吃到就再生随机成一个食物,并身体加1
self.snake.song() # 播放音乐
self.snake.create_food()
self.get_body.append(self.snake.last_pos)
print(self.get_body)
# self.snake.Area_refresh() # 刷新蛇屁股后面
# display.fill(st7789.WHITE)
self.snake.draw_food() #画食物
self.snake.draw_snake() # 画蛇
update_time = time.ticks_ms() # 刷新帧时间
if self.is_fail(): # 判断if输
self.state = GameState.FAIL
break
if self.is_win(): # 判断if赢
self.state = GameState.WIN
break
if self.state == GameState.FAIL:
display.fill(st7789.BLACK)
display.text(font2,'Game over!',50,80,st7789.WHITE)
display.text(font2,"Score:%d" % self.score,50,130,st7789.WHITE)
if self.state == GameState.WIN:
display.fill(st7789.BLACK)
display.text(font2,'You WIN!',50,100,st7789.WHITE)
if self.state == GameState.PAUSE:
display.fill(st7789.BLACK)
display.text(font2,'PAUSE',50,80,st7789.WHITE)
display.text(font2,'Press 2 to continue',20,100,st7789.WHITE)
四、总结
显示内容时需要吧st7789.py放置到pico板内部;如果需要显示文字,则需在pico板内创建fonts文件夹,并将vga1_16x32.py vga2_8x8.py 两个文件放置到fonts文件夹内;如果需要播放音乐,则需要将buzzer_music.py放置到pico内部。如果需要开机运行贪吃蛇,则需要将主文件更名为main.py并放置到pico内部。
pico板的thonny软件开发时不能使用一些python的模块,并且模块有的还需要放置到pico内才能使用,这略显不足,并且该thonny没有自动纠错的功能,有的时候很多错误很简单就是找不到。
总的来说本次项目收获良多,学习到了pico的使用,thonny的使用,并且得到了一些编程的思路,希望电子森林越做越红火吧。谢谢