import max7219
from machine import Pin, SPI
import time
import image_library # 匯入 image_library 中的圖案
import math # 匯入數學函數庫來進行旋轉計算
class Matrix_4:
def __init__(self, brightness=5):
# 定義 SPI 接腳
DIN_PIN = 4 # GPIO4
CS_PIN = 5 # GPIO5
CLK_PIN = 6 # GPIO6
# 初始化 SPI,這裡使用 SPI(1)
self.spi = SPI(1, baudrate=1000000, polarity=0, phase=0, sck=Pin(CLK_PIN), mosi=Pin(DIN_PIN))
# 初始化 CS 引腳
self.cs = Pin(CS_PIN, Pin.OUT, value=1)
# 串聯的矩陣數量
self.NUM_MATRICES = 4
# 初始化顯示器
self.display = max7219.Matrix8x8(self.spi, self.cs, self.NUM_MATRICES)
# 設定亮度
self.display.brightness(brightness)
# 清除顯示
self.display.fill(0)
self.display.show()
# 從 image_library 中載入自定義圖案
self.custom_images = image_library.custom_images
def text(self, text, x, y):
"""
在指定的位置顯示文字
:param text: 顯示的文字
:param x: x 坐標
:param y: y 坐標
"""
self.display.text(text, x, y, 1)
self.display.show()
def show_custom_image(self, image, x, y, bit_start=0, bit_end=7):
"""
在指定位置顯示自定義圖案,只顯示指定位元範圍,並能根據 x, y 座標顯示
:param image: 8x8 圖案列表
:param x: x 坐標
:param y: y 坐標
:param bit_start: 開始位元 (0-7)
:param bit_end: 結束位元 (0-7)
"""
for row in range(8):
# 循環顯示範圍內的位元
for col in range(bit_start, bit_end + 1):
if image[row] & (1 << (7 - col)):
self.display.pixel(x + (col - bit_start), y + row, 1) # 點亮像素
else:
self.display.pixel(x + (col - bit_start), y + row, 0) # 關閉像素
self.display.show()
def show(self, name, x, y, bit_start=0, bit_end=7):
"""
顯示自定義圖案或文字,並可選擇顯示位元範圍
:param name: 圖案或文字的名稱
:param x: x 坐標
:param y: y 坐標
:param bit_start: 顯示的開始位元 (0-7)
:param bit_end: 顯示的結束位元 (0-7)
"""
if name in self.custom_images:
self.show_custom_image(self.custom_images[name], x, y, bit_start, bit_end)
else:
self.text(name, x, y)
def clock(self, time_string, colon=True):
"""
顯示時鐘樣式的數字,並可選擇是否顯示冒號
:param time_string: 四位數的時間字串,例如 '1247'
:param colon: 是否顯示冒號,預設為 True
"""
if len(time_string) != 4:
raise ValueError("time_string 必須是四位數字")
# 顯示四個數字
self.show(time_string[0], 0, 0) # 第一個數字
self.show(time_string[1], 7, 0) # 第二個數字
# 顯示冒號
if colon:
self.show('冒號', 16, 0, 3, 4) # 顯示冒號
else:
self.show('冒號', 16, 0, 1, 2) # 顯示冒號
self.show(time_string[2], 19, 0) # 第三個數字
self.show(time_string[3], 25, 0) # 第四個數字
def rotate_area(self, x, y, width, height, angle, duration=0):
"""
旋轉指定的座標區域,順時針方向,並可選擇旋轉所需時間
:param x: 區域左上角的 x 坐標
:param y: 區域左上角的 y 坐標
:param width: 區域的寬度
:param height: 區域的高度
:param angle: 旋轉角度(以度數為單位,順時針為正)
:param duration: 旋轉所需時間(以秒為單位),預設為 0 表示立即旋轉
"""
if duration <= 0:
# 立即旋轉
self._apply_rotation(x, y, width, height, angle)
else:
steps = 10 # 定義旋轉步驟數量
angle_increment = angle / steps # 每步增加的角度
delay = duration / steps # 每步之間的延遲時間
# 保存原始像素狀態
original_pixels = []
for row in range(height):
row_pixels = []
for col in range(width):
pixel_x = x + col
pixel_y = y + row
if 0 <= pixel_x < self.NUM_MATRICES * 8 and 0 <= pixel_y < 8:
row_pixels.append(self.display.pixel(pixel_x, pixel_y))
else:
row_pixels.append(0)
original_pixels.append(row_pixels)
for step in range(1, steps + 1):
current_angle = angle_increment * step
self._apply_rotation_with_pixels(x, y, width, height, current_angle, original_pixels)
time.sleep(delay)
def _apply_rotation_with_pixels(self, x, y, width, height, angle, original_pixels):
"""
根據給定的角度和原始像素狀態應用旋轉
:param x: 區域左上角的 x 坐標
:param y: 區域左上角的 y 坐標
:param width: 區域的寬度
:param height: 區域的高度
:param angle: 當前步驟的旋轉角度
:param original_pixels: 原始像素狀態
"""
# 將順時針角度轉換為逆時針弧度
angle_rad = math.radians(-angle) # 負角度表示順時針旋轉
cos_theta = math.cos(angle_rad)
sin_theta = math.sin(angle_rad)
# 計算中心點
center_x = (width - 1) / 2
center_y = (height - 1) / 2
# 創建一個新的區域來保存旋轉後的像素狀態
rotated_pixels = [[0 for _ in range(width)] for _ in range(height)]
for row in range(height):
for col in range(width):
# 計算相對於中心的座標
rel_x = col - center_x
rel_y = row - center_y
# 應用旋轉矩陣
rotated_x = rel_x * cos_theta - rel_y * sin_theta
rotated_y = rel_x * sin_theta + rel_y * cos_theta
# 將旋轉後的座標轉回區域內的像素位置
src_col = int(round(rotated_x + center_x))
src_row = int(round(rotated_y + center_y))
if 0 <= src_col < width and 0 <= src_row < height:
rotated_pixels[row][col] = original_pixels[src_row][src_col]
else:
rotated_pixels[row][col] = 0 # 超出範圍的像素設為關閉
# 將旋轉後的像素狀態寫回顯示器
for row in range(height):
for col in range(width):
pixel_x = x + col
pixel_y = y + row
if 0 <= pixel_x < self.NUM_MATRICES * 8 and 0 <= pixel_y < 8:
self.display.pixel(pixel_x, pixel_y, rotated_pixels[row][col])
self.display.show()
def _apply_rotation(self, x, y, width, height, angle):
"""
立即旋轉指定區域的像素,不進行動畫效果
:param x: 區域左上角的 x 坐標
:param y: 區域左上角的 y 坐標
:param width: 區域的寬度
:param height: 區域的高度
:param angle: 旋轉角度(以度數為單位,順時針為正)
"""
# 將順時針角度轉換為逆時針弧度
angle_rad = math.radians(-angle) # 負角度表示順時針旋轉
cos_theta = math.cos(angle_rad)
sin_theta = math.sin(angle_rad)
# 創建一個暫存區域來保存原始像素狀態
original_pixels = []
for row in range(height):
row_pixels = []
for col in range(width):
pixel_x = x + col
pixel_y = y + row
if 0 <= pixel_x < self.NUM_MATRICES * 8 and 0 <= pixel_y < 8:
row_pixels.append(self.display.pixel(pixel_x, pixel_y))
else:
row_pixels.append(0)
original_pixels.append(row_pixels)
# 計算中心點
center_x = (width - 1) / 2
center_y = (height - 1) / 2
# 創建一個新的區域來保存旋轉後的像素狀態
rotated_pixels = [[0 for _ in range(width)] for _ in range(height)]
for row in range(height):
for col in range(width):
# 計算相對於中心的座標
rel_x = col - center_x
rel_y = row - center_y
# 應用旋轉矩陣
rotated_x = rel_x * cos_theta - rel_y * sin_theta
rotated_y = rel_x * sin_theta + rel_y * cos_theta
# 將旋轉後的座標轉回區域內的像素位置
src_col = int(round(rotated_x + center_x))
src_row = int(round(rotated_y + center_y))
if 0 <= src_col < width and 0 <= src_row < height:
rotated_pixels[row][col] = original_pixels[src_row][src_col]
else:
rotated_pixels[row][col] = 0 # 超出範圍的像素設為關閉
# 將旋轉後的像素狀態寫回顯示器
for row in range(height):
for col in range(width):
pixel_x = x + col
pixel_y = y + row
if 0 <= pixel_x < self.NUM_MATRICES * 8 and 0 <= pixel_y < 8:
self.display.pixel(pixel_x, pixel_y, rotated_pixels[row][col])
self.display.show()
def rotate(self, x, y, radius, angle):
"""
旋轉一個以 (x, y) 為中心的點,半徑為 radius,旋轉角度為 angle
:param x: 中心點的 x 坐標
:param y: 中心點的 y 坐標
:param radius: 半徑距離
:param angle: 旋轉的角度(以度數為單位,順時針為正)
"""
# 將順時針角度轉換為逆時針弧度
angle_rad = math.radians(-angle) # 負角度表示順時針旋轉
# 計算旋轉後的 x 和 y 偏移量
for row in range(-radius, radius+1):
for col in range(-radius, radius+1):
# 計算原始座標距離中心的偏移量
offset_x = col
offset_y = row
# 計算旋轉後的新座標
new_x = int(x + offset_x * math.cos(angle_rad) - offset_y * math.sin(angle_rad))
new_y = int(y + offset_x * math.sin(angle_rad) + offset_y * math.cos(angle_rad))
# 檢查旋轉後的座標是否超出範圍
if 0 <= new_x < self.NUM_MATRICES * 8 and 0 <= new_y < 8:
# 如果原始像素是亮的,那麼旋轉後的位置也應該亮
if self.display.pixel(x + col, y + row):
self.display.pixel(new_x, new_y, 1) # 開啟新的像素
else:
self.display.pixel(new_x, new_y, 0) # 關閉新的像素
self.display.show()
# 測試程式
if __name__ == '__main__':
display = Matrix_4(5)
display.text('1234', 0, 0)
# 在 0.5 秒內將 (0, 0) 區域旋轉 90 度
display.rotate_area(0, 0, 32, 8, 360, 1)