st7789屏幕+esp32+按键==游戏机?

    1、说在前面的

最近在学esp32,做个了小项目,可以ntp同步时间显示当天时间日期;还有快520了,给npy做个小东西送她,于是就有了这个,不说多了直接上图

主屏幕时间0.5s刷新一次,日期和星期60s刷新一次,右上角绿点表示联网成功,如果黄色显示联网失败,当按右上角key3>5次后触发彩蛋,结束时钟显示转为贪吃蛇游戏,按键从右下至右依此为key0,key1,key2,key3,对应贪吃蛇功能下,右,上,左

key0-->down

key1-->right

key2-->up

key3-->left

大概这个逻辑,不想玩了可以按开发板左下的reset键重新开启时钟,它就会恢复为原来的时钟

效果讲完了下面开始讲干货(很干哦记得多喝水‘<’)

完整的代码在最后贴着,讲原理的在前面

2、需要准备的的元件

1、st7789全彩tft240*240屏幕   x1

2、esp32 devkit1开发板,主控是esp32-wroom-32d     x1

3、18650电池和电池盒  x1

4、pcb板,我是用嘉立创免费打的 x1

5、ds1302时钟模块(很多毛病别用,我是直接联网同步的时间)x1

6、typec母座 x1

7、滑动开关 x1

8、轻触开关 x4

9、2.54母座和公坐  x2

10、cd42 锂电池充放电模块 x1

以上元件tb都可以搜名字或者搜图片搜到 

3、研究原理

1、st7789 240*240屏幕

RES -> RESET复位引脚//

DC -> 数据,指令选择引脚,1为数据 、0为指令//

CS -> Chip Select低电平有效//

SCK,SCL, CLK -> SPI串行时钟信号//

BL -> 背光引脚,高电平点亮屏幕,低电平关闭屏幕//

SDA -> SPI串行数据输入输出信号//

GND -> 电源地//

VCC -> 支持3.3v和5v输入/

我的连接顺序:

屏幕(spi从机)-->esp32(spi主机)

vcc,gnd-->vcc,gnd

sck-->14(spi_sck)

sda-->13(spi_mosi)

res-->12

dc-->27

blk-->26

2、按键连接与消抖

key0-->19

key1-->17

key2-->16

key3-->4

消抖我这里参考了micropython官网的一个方法,很巧妙而且很有效,按一下就是一下不会右很多下,链接和代码贴下面,通过判断按键的按下的时间然后再传标志位,调用标志位后记得把标志位清零

这里使用的多线程来保证按键的稳定运行

1. 消除引脚输入的抖动— MicroPython 1.17 documentation

#########四个按键##############
key0=Pin(19,Pin.IN,Pin.PULL_UP)
key1=Pin(17,Pin.IN,Pin.PULL_UP)
key2=Pin(16,Pin.IN,Pin.PULL_UP)
key3=Pin(4,Pin.IN,Pin.PULL_UP)

def wait_pin_change(pin):
    # wait for pin to change value
    # it needs to be stable for a continuous 20ms
    cur_value = pin.value()
    active = 0
    while active < 20:
        if pin.value() != cur_value:
            active += 1
        else:
            active = 0
        time.sleep_ms(1)

key0_flag=0
key1_flag=0
key2_flag=0
key3_flag=0
k0=k1=k2=k3=0
def key_scan():
    global key0_flag,key1_flag,key2_flag,key3_flag
    global k0,k1,k2,k3
    if key0.value()==0:
        wait_pin_change(key0)
        k0+=1
        if k0>=1:
            key0_flag=1
            k0=0
    elif key1.value()==0:
        wait_pin_change(key1)
        k1+=1
        if k1>=1:
            key1_flag=1
            k1=0
    elif key2.value()==0:
        wait_pin_change(key2)
        k2+=1
        if k2>=1:
            key2_flag=1
            k2=0
    elif key3.value()==0:
        wait_pin_change(key3)
        k3+=1
        if k3>=1:
            key3_flag=1
            k3=0
        

############end###############
#############按键任务#################
def key_task(*args, **kwargs):
    global key0_flag,key1_flag,key2_flag,key3_flag
    global k0,k1,k2,k3
    while True:
        key_scan()
        if key0_flag==1:
            print("0")
            key0_flag=0
        elif key1_flag==1:
            print("1")
            key1_flag=0
        elif key2_flag==1:
            print("2")
            key2_flag=0
        elif key3_flag==1:
            print("3")
            key3_flag=0
###########end##############
thread_1 = _thread.start_new_thread(key_task, (1,))

3、贪吃蛇游戏彩蛋

这里参考了下面链接改的

(22条消息) 手把手教你使用ESP32+MicroPython制作贪吃蛇游戏_用ssd1306写一个游戏_空巢青年_rui的博客-CSDN博客

其中按6下key3触发彩蛋是通过按键计数原理,每按一下按键就计数,当计数大于5时从显示时钟的任务退出来进行贪吃蛇任务,按reset重置开发板继续时钟任务

if disp_exit_flag>5:
                return

4、总结

需要往开发板里下两个文件st7789.py和display.py

display.py

from machine import SPI,Pin,Timer,RTC
import st7789,time,network,_thread,ntptime,random
import vga1_16x16,vga1_16x32,vga1_8x16,vga1_8x8,vga1_bold_16x16,vga1_bold_16x32

#############联网#################
ssid="你的WiFi"
passwd="你的password"
def connect(ssid,passwd):
    wlan=network.WLAN(network.STA_IF)
    if not wlan.isconnected():
        wlan.active(True)
        wlan.connect(ssid,passwd)
        while not wlan.isconnected():
            pass
    print(wlan.ifconfig())#('192.168.31.233', '255.255.255.0', '192.168.31.1', '192.168.31.1')IP 地址、子网掩码、网关和 DNS 服务器
    NetMessage=wlan.ifconfig()#ip_add表示ip地址
    global IpAdd
    global NetMask
    IpAdd=NetMessage[0]
    NetMask=NetMessage[1]


#############end####################
##############定时器#################
tim0=Timer(0)
tim1=Timer(1)
tim2=Timer(2)
tim3=Timer(3)
##############end###################
############联网同步时间#############
rtc=RTC()
def sync_ntp():
    ntptime.NTP_DELTA = 3155644800   # 可选 UTC+8偏移时间(秒),不设置就是UTC0
    ntptime.host = 'ntp1.aliyun.com'  # 可选,ntp服务器,默认是"pool.ntp.org"
    try:
        ntptime.settime()   # 修改设备时间,到这就已经设置好了
        print(rtc.datetime())#rtc.datetime第四个数据星期 0表示周日 1~6表示周一到周六
        print('同步成功')
    except:
     print('同步失败')
###############end############################

#########四个按键##############
key0=Pin(19,Pin.IN,Pin.PULL_UP)
key1=Pin(17,Pin.IN,Pin.PULL_UP)
key2=Pin(16,Pin.IN,Pin.PULL_UP)
key3=Pin(4,Pin.IN,Pin.PULL_UP)

def wait_pin_change(pin):
    # wait for pin to change value
    # it needs to be stable for a continuous 20ms
    cur_value = pin.value()
    active = 0
    while active < 20:
        if pin.value() != cur_value:
            active += 1
        else:
            active = 0
        time.sleep_ms(1)

key0_flag=0
key1_flag=0
key2_flag=0
key3_flag=0
k0=k1=k2=k3=0
def key_scan():
    global key0_flag,key1_flag,key2_flag,key3_flag
    global k0,k1,k2,k3
    if key0.value()==0:
        wait_pin_change(key0)
        k0+=1
        if k0>=1:
            key0_flag=1
            k0=0
    elif key1.value()==0:
        wait_pin_change(key1)
        k1+=1
        if k1>=1:
            key1_flag=1
            k1=0
    elif key2.value()==0:
        wait_pin_change(key2)
        k2+=1
        if k2>=1:
            key2_flag=1
            k2=0
    elif key3.value()==0:
        wait_pin_change(key3)
        k3+=1
        if k3>=1:
            key3_flag=1
            k3=0
############end#############
###########随机数生成随机颜色#########
def random_color0():
    i=random.randint(0,255)
    j=random.randint(0,255)
    k=random.randint(0,255)
    return i,j,k
def random_color1():
    i=random.randint(0,255)
    j=random.randint(0,255)
    k=random.randint(0,255)
    return i,j,k
def random_color2():
    i=random.randint(0,255)
    j=random.randint(0,255)
    k=random.randint(0,255)
    return i,j,k
def random_color3():
    i=random.randint(0,255)
    j=random.randint(0,255)
    k=random.randint(0,255)
    return i,j,k
###########end############
##############屏幕st7789###############
class Display():
    def __init__(self):
        self.tft = st7789.ST7789(SPI(2, baudrate=10000000, polarity=1, sck=Pin(14), mosi=Pin(13)), 240, 240, reset=Pin(12, Pin.OUT), dc=Pin(27, Pin.OUT),backlight=Pin(26,Pin.OUT))
        self.tft.rotation(0)
        self.WHITE = st7789.color565(255, 255, 255)#RGB
        self.BLACK = st7789.color565(0, 0, 0)
        self.RED = st7789.color565(255, 0, 0)
        self.GREEN = st7789.color565(0, 255, 0)
        self.BLUE = st7789.color565(0, 0, 255)
        self.YELLOW = st7789.color565(255, 255, 0)
        self.PURPLE=st7789.color565(255,0,255)
        self.PINK=st7789.color565(241,158,194)
        self.ORANGE=st7789.color565(255,165,0)
        self.DEEP_GREEN=st7789.color565(9,209,9)
        self.GRAY=st7789.color565(150,150,150)
        
        self.last_hour = 0
        self.last_minute = 0
        self.last_second = 0
        self.last_year = 0
        self.last_month = 0
        self.last_day = 0
        self.init_show()
        
    def init_show(self):
        '''
        初始化显示画面
        '''
        self.RANDOM0=st7789.color565(random_color0())
        self.tft.text(vga1_bold_16x32, ':', 20+24*2, 100, self.WHITE, self.BLACK)
        self.tft.text(vga1_bold_16x32, ':', 20+24*5, 100, self.WHITE, self.BLACK)
        self.tft.text(vga1_bold_16x32, '00', 20, 100, self.WHITE, self.BLACK)
        self.tft.text(vga1_bold_16x32, '00', 20+24*3, 100, self.WHITE, self.BLACK)
        self.tft.text(vga1_bold_16x32, '00', 20+24*6, 100, self.WHITE, self.BLACK)
        self.tft.text(vga1_8x16, 'Please set a hotspot like it:', 0, 160, self.WHITE, self.BLACK)
        self.tft.text(vga1_8x16, 'Wifiname:%s'%ssid, 0, 180, self.WHITE, self.BLACK)
        self.tft.text(vga1_8x16, 'password:%s'%passwd, 0, 200, self.WHITE, self.BLACK)
        time.sleep(2)
        self.text('ZHQ I LOVE YOU')
        time.sleep(2)
        self.text('              ')
        self.tft.text(vga1_8x16, '<h>NOW TIME</h>', 0, 0, self.DEEP_GREEN, self.GRAY)
        self.tft.text(vga1_8x16, 'CWK', 120, 220, self.BLUE, self.BLACK)
        self.tft.text(vga1_8x16, 'LOVE', 160, 220, self.ORANGE, self.BLACK)
        self.tft.text(vga1_8x16, 'ZHQ', 210, 220, self.GREEN, self.BLACK)
        self.tft.text(vga1_8x16, '                              ', 0, 160, self.WHITE, self.BLACK)
        self.tft.text(vga1_8x16, '                              ', 0, 180, self.WHITE, self.BLACK)
        self.tft.text(vga1_8x16, '                              ', 0, 200, self.WHITE, self.BLACK)
        wlan=network.WLAN(network.STA_IF)
        if wlan.isconnected() is True:
            self.tft.fill_rect(235,0,5,5,st7789.GREEN)
        elif wlan.isconnected() is False:
            self.tft.fill_rect(235,0,5,5,st7789.RED)
    def text(self,text):
        self.tft.text(vga1_bold_16x32, text, 0, 50, self.RANDOM0, self.BLACK)

    def show_time(self,t):
        
        self.RANDOM1=st7789.color565(random_color1())
        self.RANDOM2=st7789.color565(random_color2())
        self.RANDOM3=st7789.color565(random_color3())
        hour = t[4]
        minute = t[5]
        second = t[6]
        
        ti = "{:0>2d}:{:0>2d}:{:0>2d}".format(hour,minute,second)
        
        if hour != self.last_hour:
            self.tft.text(vga1_bold_16x32, '{:0>2d}'.format(hour), 20, 100, self.RANDOM1, self.BLACK)
            self.last_hour = hour
        if minute != self.last_minute:
            self.tft.text(vga1_bold_16x32, '{:0>2d}'.format(minute), 20+24*3, 100, self.RANDOM2, self.BLACK)
            self.last_minute = minute
        if second != self.last_second:
            self.tft.text(vga1_bold_16x32, '{:0>2d}'.format(second), 20+24*6, 100, self.RANDOM3, self.BLACK)
            self.last_second = second
       
        
        
    def show_date(self,t):
        weekdays=('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday')
        year = t[0]
        month = t[1]
        day = t[2]
        weekday=t[3]
        da = "{:0>2d} {:0>2d} {:0>2d}".format(year,month,day)
        if year != self.last_year:
            self.tft.text(vga1_16x32,'{:0>2d}'.format(year), 20, 160, self.RANDOM2, self.BLACK)
            self.last_year = year
        if month != self.last_month:
            self.tft.text(vga1_16x32,'{:0>2d}'.format(month), 20+24*3, 160, self.RANDOM3, self.BLACK)
            self.last_month = month
        if day != self.last_day:
            self.tft.text(vga1_16x32,'{:0>2d}'.format(day), 20+24*6, 160, self.RANDOM1, self.BLACK)
            self.last_day = day
        
        if weekday==0:
            self.tft.text(vga1_16x32,weekdays[0],0,50,self.BLUE,self.BLACK)
        if weekday==1:
            self.tft.text(vga1_16x32,weekdays[1],0,50,self.GREEN,self.BLACK)
        if weekday==2:
            self.tft.text(vga1_16x32,weekdays[2],0,50,self.YELLOW,self.BLACK)
        if weekday==3:
            self.tft.text(vga1_16x32,weekdays[3],0,50,self.PINK,self.BLACK)
        if weekday==4:
            self.tft.text(vga1_16x32,weekdays[4],0,50,self.PURPLE,self.BLACK)
        if weekday==5:
            self.tft.text(vga1_16x32,weekdays[5],0,50,self.RED,self.BLACK)
        if weekday==6:
            self.tft.text(vga1_16x32,weekdays[6],0,50,self.ORANGE,self.BLACK)
        
        
    def start(self):
        i=0
        while True:
            if disp_exit_flag>5:
                return
            t = (rtc.datetime())
            self.show_time(t)
            time.sleep(0.5)
            i+=1
            if i==60:
                self.show_date(t)#一分钟刷新一次
                i=0
        
    def __del__(self):
        pass
    



##################贪吃蛇class################
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

SCREEN_WIDTH = 240
SCREEN_HEIGHT = 240


# snake config
SNAKE_PIECE_SIZE = 5  # 蛇的每一格占用3*3个像素
MAX_SNAKE_LENGTH = 200  # 蛇的最长长度
MAP_SIZE_X = 47  # 活动范围
MAP_SIZE_Y = 47
START_SNAKE_SIZE = 5  # 初始长度
SNAKE_MOVE_DELAY = 20  # 移动延时
screen = st7789.ST7789(SPI(2, baudrate=10000000, polarity=1, sck=Pin(14), mosi=Pin(13)), 240, 240, reset=Pin(12, Pin.OUT), dc=Pin(27, Pin.OUT),backlight=Pin(26,Pin.OUT))
screen.rotation(0)
################ 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.display = screen
        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(st7789.BLACK)
        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]))
            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

    @staticmethod
    def button_press():
        """是否有按键按下"""
        for pin in left_flag, right_flag, up_flag, down_flag:
            if pin == 1:  #当一个的flag为1
                return True
        return False

    def read_direction(self):
        """读取新的按键方向,不能与当前方向相反"""
        for direction, pin in enumerate((up_flag, left_flag, down_flag, right_flag)):
            if pin == 1 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.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, st7789.GREEN)
        # 绘制地图边界, 边界占一个像素,但是绘制时在内侧留一个像素,当蛇头部到达内部一个像素时,即判定为碰撞
        self.display.rect(offset_map_x - 2,
                          0,
                          SNAKE_PIECE_SIZE * MAP_SIZE_X + 4,
                          SNAKE_PIECE_SIZE * MAP_SIZE_Y + 4, st7789.GREEN)
        # 绘制蛇
        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, st7789.GREEN)

    def show_score(self):
        """显示得分"""
        score = self.snake_length - START_SNAKE_SIZE
        #self.display.text('Score:%d' % score, 0, 2, 1)
        self.display.text(vga1_8x16,'Score:%d' % score, 0, 2, st7789.GREEN, st7789.BLACK)

    def show_press_to_start(self):
        """提示按任意键开始游戏"""
        #self.display.text('Press', 0, 16, 1)
        #self.display.text('button', 0, 26, 1)
        #self.display.text('start!', 0, 36, 1)
        self.display.text(vga1_8x16,'Press any button to start!', 0, 20, st7789.GREEN, st7789.BLACK)

    def show_game_over(self):
        """显示游戏结束"""
        #self.display.text('Game', 0, 30, 1)
        #self.display.text('Over!', 0, 40, 1)
        self.display.text(vga1_8x16,'GAME OVER', 0, 20, st7789.GREEN, st7789.BLACK)
#################end#########################    
#############按键任务#################
disp_exit_flag=0
up_flag=down_flag=left_flag=right_flag=0
def key_task(*args, **kwargs):
#     print('keytaskis:%d'%_thread.get_ident())keytaskis:1073582188
    global key0_flag,key1_flag,key2_flag,key3_flag
    global up_flag,down_flag,left_flag,right_flag
    global k0,k1,k2,k3
    global disp_exit_flag
    while True:
        key_scan()
        if key0_flag==1:
            print("0")
            down_flag=1
            key0_flag=0
        elif key1_flag==1:
            print("1")
            right_flag=1
            key1_flag=0
        elif key2_flag==1:
            print("2")
            up_flag=1
            key2_flag=0
        elif key3_flag==1:
            print("3")
            left_flag=1
            disp_exit_flag+=1
            key3_flag=0
#####################end#####################
connect(ssid,passwd)
time.sleep(1)
sync_ntp()

thread_1 = _thread.start_new_thread(key_task, (1,))
#thread_2 = _thread.start_new_thread(disp_task, (2,))
D = Display()
D.start()
disp_exit_flag=0#连续5下key3后跳出start()
snake = Snake()
move_time = 0
while True:
    if snake.game_state == State.START:
        if Snake.button_press():
            up_flag=down_flag=left_flag=right_flag=0
            snake.game_state = State.RUNNING

    elif snake.game_state == State.RUNNING:
        move_time += 1
        snake.read_direction()
        up_flag=down_flag=left_flag=right_flag=0
        if move_time >= SNAKE_MOVE_DELAY:
            snake.direction = snake.new_direction
            snake.display.fill(0)
            if not snake.move_snake():
                snake.game_state = State.GAMEOVER
                snake.show_game_over()
                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(20) 



st7789.py

"""
Copyright (c) 2020, 2021 Russ Hughes

This file incorporates work covered by the following copyright and
permission notice and is licensed under the same terms:

The MIT License (MIT)

Copyright (c) 2019 Ivan Belokobylskiy

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

The driver is based on devbis' st7789py_mpy module from
https://github.com/devbis/st7789py_mpy.

This driver adds support for:

- 320x240, 240x240 and 135x240 pixel displays
- Display rotation
- Hardware based scrolling
- Drawing text using 8 and 16 bit wide bitmap fonts with heights that are
  multiples of 8.  Included are 12 bitmap fonts derived from classic pc
  BIOS text mode fonts.
- Drawing text using converted TrueType fonts.
- Drawing converted bitmaps

"""

import time
from micropython import const
import ustruct as struct

# commands
ST7789_NOP = const(0x00)
ST7789_SWRESET = const(0x01)
ST7789_RDDID = const(0x04)
ST7789_RDDST = const(0x09)

ST7789_SLPIN = const(0x10)
ST7789_SLPOUT = const(0x11)
ST7789_PTLON = const(0x12)
ST7789_NORON = const(0x13)

ST7789_INVOFF = const(0x20)
ST7789_INVON = const(0x21)
ST7789_DISPOFF = const(0x28)
ST7789_DISPON = const(0x29)
ST7789_CASET = const(0x2A)
ST7789_RASET = const(0x2B)
ST7789_RAMWR = const(0x2C)
ST7789_RAMRD = const(0x2E)

ST7789_PTLAR = const(0x30)
ST7789_VSCRDEF = const(0x33)
ST7789_COLMOD = const(0x3A)
ST7789_MADCTL = const(0x36)
ST7789_VSCSAD = const(0x37)

ST7789_MADCTL_MY = const(0x80)
ST7789_MADCTL_MX = const(0x40)
ST7789_MADCTL_MV = const(0x20)
ST7789_MADCTL_ML = const(0x10)
ST7789_MADCTL_BGR = const(0x08)
ST7789_MADCTL_MH = const(0x04)
ST7789_MADCTL_RGB = const(0x00)

ST7789_RDID1 = const(0xDA)
ST7789_RDID2 = const(0xDB)
ST7789_RDID3 = const(0xDC)
ST7789_RDID4 = const(0xDD)

COLOR_MODE_65K = const(0x50)
COLOR_MODE_262K = const(0x60)
COLOR_MODE_12BIT = const(0x03)
COLOR_MODE_16BIT = const(0x05)
COLOR_MODE_18BIT = const(0x06)
COLOR_MODE_16M = const(0x07)

# Color definitions
BLACK = const(0x0000)
BLUE = const(0x001F)
RED = const(0xF800)
GREEN = const(0x07E0)
CYAN = const(0x07FF)
MAGENTA = const(0xF81F)
YELLOW = const(0xFFE0)
WHITE = const(0xFFFF)

_ENCODE_PIXEL = ">H"
_ENCODE_POS = ">HH"
_DECODE_PIXEL = ">BBB"

_BUFFER_SIZE = const(256)

_BIT7 = const(0x80)
_BIT6 = const(0x40)
_BIT5 = const(0x20)
_BIT4 = const(0x10)
_BIT3 = const(0x08)
_BIT2 = const(0x04)
_BIT1 = const(0x02)
_BIT0 = const(0x01)

# Rotation tables (width, height, xstart, ystart)[rotation % 4]

WIDTH_320 = [(240, 320,  0,  0),
             (320, 240,  0,  0),
             (240, 320,  0,  0),
             (320, 240,  0,  0)]

WIDTH_240 = [(240, 240,  0,  0),
             (240, 240,  0,  0),
             (240, 240,  0, 80),
             (240, 240, 80,  0)]

WIDTH_135 = [(135, 240, 52, 40),
             (240, 135, 40, 53),
             (135, 240, 53, 40),
             (240, 135, 40, 52)]

# MADCTL ROTATIONS[rotation % 4]
ROTATIONS = [0x00, 0x60, 0xc0, 0xa0]


def color565(red, green=0, blue=0):
    """
    Convert red, green and blue values (0-255) into a 16-bit 565 encoding.
    """
    try:
        red, green, blue = red  # see if the first var is a tuple/list
    except TypeError:
        pass
    return (red & 0xf8) << 8 | (green & 0xfc) << 3 | blue >> 3


def _encode_pos(x, y):
    """Encode a postion into bytes."""
    return struct.pack(_ENCODE_POS, x, y)


def _encode_pixel(color):
    """Encode a pixel color into bytes."""
    return struct.pack(_ENCODE_PIXEL, color)


class ST7789():
    """
    ST7789 driver class

    Args:
        spi (spi): spi object **Required**
        width (int): display width **Required**
        height (int): display height **Required**
        reset (pin): reset pin
        dc (pin): dc pin **Required**
        cs (pin): cs pin
        backlight(pin): backlight pin
        rotation (int): display rotation
            - 0-Portrait
            - 1-Landscape
            - 2-Inverted Portrait
            - 3-Inverted Landscape
    """
    def __init__(self, spi, width, height, reset=None, dc=None,
                 cs=None, backlight=None, rotation=0):
        """
        Initialize display.
        """
        if height != 240 or width not in [320, 240, 135]:
            raise ValueError(
                "Unsupported display. 320x240, 240x240 and 135x240 are supported."
            )

        if dc is None:
            raise ValueError("dc pin is required.")

        self._display_width = self.width = width
        self._display_height = self.height = height
        self.xstart = 0
        self.ystart = 0
        self.spi = spi
        self.reset = reset
        self.dc = dc
        self.cs = cs
        self.backlight = backlight
        self._rotation = rotation % 4

        self.hard_reset()
        self.soft_reset()
        self.sleep_mode(False)

        self._set_color_mode(COLOR_MODE_65K | COLOR_MODE_16BIT)
        time.sleep_ms(50)
        self.rotation(self._rotation)
        self.inversion_mode(True)
        time.sleep_ms(10)
        self._write(ST7789_NORON)
        time.sleep_ms(10)
        if backlight is not None:
            backlight.value(1)
        self.fill(0)
        self._write(ST7789_DISPON)
        time.sleep_ms(500)

    def _write(self, command=None, data=None):
        """SPI write to the device: commands and data."""
        if self.cs:
            self.cs.off()

        if command is not None:
            self.dc.off()
            self.spi.write(bytes([command]))
        if data is not None:
            self.dc.on()
            self.spi.write(data)
            if self.cs:
                self.cs.on()

    def hard_reset(self):
        """
        Hard reset display.
        """
        if self.cs:
            self.cs.off()
        if self.reset:
            self.reset.on()
        time.sleep_ms(50)
        if self.reset:
            self.reset.off()
        time.sleep_ms(50)
        if self.reset:
            self.reset.on()
        time.sleep_ms(150)
        if self.cs:
            self.cs.on()

    def soft_reset(self):
        """
        Soft reset display.
        """
        self._write(ST7789_SWRESET)
        time.sleep_ms(150)

    def sleep_mode(self, value):
        """
        Enable or disable display sleep mode.

        Args:
            value (bool): if True enable sleep mode. if False disable sleep
            mode
        """
        if value:
            self._write(ST7789_SLPIN)
        else:
            self._write(ST7789_SLPOUT)

    def inversion_mode(self, value):
        """
        Enable or disable display inversion mode.

        Args:
            value (bool): if True enable inversion mode. if False disable
            inversion mode
        """
        if value:
            self._write(ST7789_INVON)
        else:
            self._write(ST7789_INVOFF)

    def _set_color_mode(self, mode):
        """
        Set display color mode.

        Args:
            mode (int): color mode
                COLOR_MODE_65K, COLOR_MODE_262K, COLOR_MODE_12BIT,
                COLOR_MODE_16BIT, COLOR_MODE_18BIT, COLOR_MODE_16M
        """
        self._write(ST7789_COLMOD, bytes([mode & 0x77]))

    def rotation(self, rotation):
        """
        Set display rotation.

        Args:
            rotation (int):
                - 0-Portrait
                - 1-Landscape
                - 2-Inverted Portrait
                - 3-Inverted Landscape
        """

        rotation %= 4
        self._rotation = rotation
        madctl = ROTATIONS[rotation]

        if self._display_width == 320:
            table = WIDTH_320
        elif self._display_width == 240:
            table = WIDTH_240
        elif self._display_width == 135:
            table = WIDTH_135
        else:
            raise ValueError(
                "Unsupported display. 320x240, 240x240 and 135x240 are supported."
            )

        self.width, self.height, self.xstart, self.ystart = table[rotation]
        self._write(ST7789_MADCTL, bytes([madctl]))

    def _set_columns(self, start, end):
        """
        Send CASET (column address set) command to display.

        Args:
            start (int): column start address
            end (int): column end address
        """
        if start <= end <= self.width:
            self._write(ST7789_CASET, _encode_pos(
                start+self.xstart, end + self.xstart))

    def _set_rows(self, start, end):
        """
        Send RASET (row address set) command to display.

        Args:
            start (int): row start address
            end (int): row end address
       """
        if start <= end <= self.height:
            self._write(ST7789_RASET, _encode_pos(
                start+self.ystart, end+self.ystart))

    def _set_window(self, x0, y0, x1, y1):
        """
        Set window to column and row address.

        Args:
            x0 (int): column start address
            y0 (int): row start address
            x1 (int): column end address
            y1 (int): row end address
        """
        self._set_columns(x0, x1)
        self._set_rows(y0, y1)
        self._write(ST7789_RAMWR)

    def vline(self, x, y, length, color):
        """
        Draw vertical line at the given location and color.

        Args:
            x (int): x coordinate
            Y (int): y coordinate
            length (int): length of line
            color (int): 565 encoded color
        """
        self.fill_rect(x, y, 1, length, color)

    def hline(self, x, y, length, color):
        """
        Draw horizontal line at the given location and color.

        Args:
            x (int): x coordinate
            Y (int): y coordinate
            length (int): length of line
            color (int): 565 encoded color
        """
        self.fill_rect(x, y, length, 1, color)

    def pixel(self, x, y, color):
        """
        Draw a pixel at the given location and color.

        Args:
            x (int): x coordinate
            Y (int): y coordinate
            color (int): 565 encoded color
        """
        self._set_window(x, y, x, y)
        self._write(None, _encode_pixel(color))

    def blit_buffer(self, buffer, x, y, width, height):
        """
        Copy buffer to display at the given location.

        Args:
            buffer (bytes): Data to copy to display
            x (int): Top left corner x coordinate
            Y (int): Top left corner y coordinate
            width (int): Width
            height (int): Height
        """
        self._set_window(x, y, x + width - 1, y + height - 1)
        self._write(None, buffer)

    def rect(self, x, y, w, h, color):
        """
        Draw a rectangle at the given location, size and color.

        Args:
            x (int): Top left corner x coordinate
            y (int): Top left corner y coordinate
            width (int): Width in pixels
            height (int): Height in pixels
            color (int): 565 encoded color
        """
        self.hline(x, y, w, color)
        self.vline(x, y, h, color)
        self.vline(x + w - 1, y, h, color)
        self.hline(x, y + h - 1, w, color)

    def fill_rect(self, x, y, width, height, color):
        """
        Draw a rectangle at the given location, size and filled with color.

        Args:
            x (int): Top left corner x coordinate
            y (int): Top left corner y coordinate
            width (int): Width in pixels
            height (int): Height in pixels
            color (int): 565 encoded color
        """
        self._set_window(x, y, x + width - 1, y + height - 1)
        chunks, rest = divmod(width * height, _BUFFER_SIZE)
        pixel = _encode_pixel(color)
        self.dc.on()
        if chunks:
            data = pixel * _BUFFER_SIZE
            for _ in range(chunks):
                self._write(None, data)
        if rest:
            self._write(None, pixel * rest)

    def fill(self, color):
        """
        Fill the entire FrameBuffer with the specified color.

        Args:
            color (int): 565 encoded color
        """
        self.fill_rect(0, 0, self.width, self.height, color)

    def line(self, x0, y0, x1, y1, color):
        """
        Draw a single pixel wide line starting at x0, y0 and ending at x1, y1.

        Args:
            x0 (int): Start point x coordinate
            y0 (int): Start point y coordinate
            x1 (int): End point x coordinate
            y1 (int): End point y coordinate
            color (int): 565 encoded color
        """
        steep = abs(y1 - y0) > abs(x1 - x0)
        if steep:
            x0, y0 = y0, x0
            x1, y1 = y1, x1
        if x0 > x1:
            x0, x1 = x1, x0
            y0, y1 = y1, y0
        dx = x1 - x0
        dy = abs(y1 - y0)
        err = dx // 2
        ystep = 1 if y0 < y1 else -1
        while x0 <= x1:
            if steep:
                self.pixel(y0, x0, color)
            else:
                self.pixel(x0, y0, color)
            err -= dy
            if err < 0:
                y0 += ystep
                err += dx
            x0 += 1

    def vscrdef(self, tfa, vsa, bfa):
        """
        Set Vertical Scrolling Definition.

        To scroll a 135x240 display these values should be 40, 240, 40.
        There are 40 lines above the display that are not shown followed by
        240 lines that are shown followed by 40 more lines that are not shown.
        You could write to these areas off display and scroll them into view by
        changing the TFA, VSA and BFA values.

        Args:
            tfa (int): Top Fixed Area
            vsa (int): Vertical Scrolling Area
            bfa (int): Bottom Fixed Area
        """
        struct.pack(">HHH", tfa, vsa, bfa)
        self._write(ST7789_VSCRDEF, struct.pack(">HHH", tfa, vsa, bfa))

    def vscsad(self, vssa):
        """
        Set Vertical Scroll Start Address of RAM.

        Defines which line in the Frame Memory will be written as the first
        line after the last line of the Top Fixed Area on the display

        Example:

            for line in range(40, 280, 1):
                tft.vscsad(line)
                utime.sleep(0.01)

        Args:
            vssa (int): Vertical Scrolling Start Address

        """
        self._write(ST7789_VSCSAD, struct.pack(">H", vssa))

    def _text8(self, font, text, x0, y0, color=WHITE, background=BLACK):
        """
        Internal method to write characters with width of 8 and
        heights of 8 or 16.

        Args:
            font (module): font module to use
            text (str): text to write
            x0 (int): column to start drawing at
            y0 (int): row to start drawing at
            color (int): 565 encoded color to use for characters
            background (int): 565 encoded color to use for background
        """
        for char in text:
            ch = ord(char)
            if (font.FIRST <= ch < font.LAST
                    and x0+font.WIDTH <= self.width
                    and y0+font.HEIGHT <= self.height):

                if font.HEIGHT == 8:
                    passes = 1
                    size = 8
                    each = 0
                else:
                    passes = 2
                    size = 16
                    each = 8

                for line in range(passes):
                    idx = (ch-font.FIRST)*size+(each*line)
                    buffer = struct.pack(
                        '>64H',
                        color if font.FONT[idx] & _BIT7 else background,
                        color if font.FONT[idx] & _BIT6 else background,
                        color if font.FONT[idx] & _BIT5 else background,
                        color if font.FONT[idx] & _BIT4 else background,
                        color if font.FONT[idx] & _BIT3 else background,
                        color if font.FONT[idx] & _BIT2 else background,
                        color if font.FONT[idx] & _BIT1 else background,
                        color if font.FONT[idx] & _BIT0 else background,
                        color if font.FONT[idx+1] & _BIT7 else background,
                        color if font.FONT[idx+1] & _BIT6 else background,
                        color if font.FONT[idx+1] & _BIT5 else background,
                        color if font.FONT[idx+1] & _BIT4 else background,
                        color if font.FONT[idx+1] & _BIT3 else background,
                        color if font.FONT[idx+1] & _BIT2 else background,
                        color if font.FONT[idx+1] & _BIT1 else background,
                        color if font.FONT[idx+1] & _BIT0 else background,
                        color if font.FONT[idx+2] & _BIT7 else background,
                        color if font.FONT[idx+2] & _BIT6 else background,
                        color if font.FONT[idx+2] & _BIT5 else background,
                        color if font.FONT[idx+2] & _BIT4 else background,
                        color if font.FONT[idx+2] & _BIT3 else background,
                        color if font.FONT[idx+2] & _BIT2 else background,
                        color if font.FONT[idx+2] & _BIT1 else background,
                        color if font.FONT[idx+2] & _BIT0 else background,
                        color if font.FONT[idx+3] & _BIT7 else background,
                        color if font.FONT[idx+3] & _BIT6 else background,
                        color if font.FONT[idx+3] & _BIT5 else background,
                        color if font.FONT[idx+3] & _BIT4 else background,
                        color if font.FONT[idx+3] & _BIT3 else background,
                        color if font.FONT[idx+3] & _BIT2 else background,
                        color if font.FONT[idx+3] & _BIT1 else background,
                        color if font.FONT[idx+3] & _BIT0 else background,
                        color if font.FONT[idx+4] & _BIT7 else background,
                        color if font.FONT[idx+4] & _BIT6 else background,
                        color if font.FONT[idx+4] & _BIT5 else background,
                        color if font.FONT[idx+4] & _BIT4 else background,
                        color if font.FONT[idx+4] & _BIT3 else background,
                        color if font.FONT[idx+4] & _BIT2 else background,
                        color if font.FONT[idx+4] & _BIT1 else background,
                        color if font.FONT[idx+4] & _BIT0 else background,
                        color if font.FONT[idx+5] & _BIT7 else background,
                        color if font.FONT[idx+5] & _BIT6 else background,
                        color if font.FONT[idx+5] & _BIT5 else background,
                        color if font.FONT[idx+5] & _BIT4 else background,
                        color if font.FONT[idx+5] & _BIT3 else background,
                        color if font.FONT[idx+5] & _BIT2 else background,
                        color if font.FONT[idx+5] & _BIT1 else background,
                        color if font.FONT[idx+5] & _BIT0 else background,
                        color if font.FONT[idx+6] & _BIT7 else background,
                        color if font.FONT[idx+6] & _BIT6 else background,
                        color if font.FONT[idx+6] & _BIT5 else background,
                        color if font.FONT[idx+6] & _BIT4 else background,
                        color if font.FONT[idx+6] & _BIT3 else background,
                        color if font.FONT[idx+6] & _BIT2 else background,
                        color if font.FONT[idx+6] & _BIT1 else background,
                        color if font.FONT[idx+6] & _BIT0 else background,
                        color if font.FONT[idx+7] & _BIT7 else background,
                        color if font.FONT[idx+7] & _BIT6 else background,
                        color if font.FONT[idx+7] & _BIT5 else background,
                        color if font.FONT[idx+7] & _BIT4 else background,
                        color if font.FONT[idx+7] & _BIT3 else background,
                        color if font.FONT[idx+7] & _BIT2 else background,
                        color if font.FONT[idx+7] & _BIT1 else background,
                        color if font.FONT[idx+7] & _BIT0 else background
                    )
                    self.blit_buffer(buffer, x0, y0+8*line, 8, 8)

                x0 += 8

    def _text16(self, font, text, x0, y0, color=WHITE, background=BLACK):
        """
        Internal method to draw characters with width of 16 and heights of 16
        or 32.

        Args:
            font (module): font module to use
            text (str): text to write
            x0 (int): column to start drawing at
            y0 (int): row to start drawing at
            color (int): 565 encoded color to use for characters
            background (int): 565 encoded color to use for background
        """
        for char in text:
            ch = ord(char)
            if (font.FIRST <= ch < font.LAST
                    and x0+font.WIDTH <= self.width
                    and y0+font.HEIGHT <= self.height):

                each = 16
                if font.HEIGHT == 16:
                    passes = 2
                    size = 32
                else:
                    passes = 4
                    size = 64

                for line in range(passes):
                    idx = (ch-font.FIRST)*size+(each*line)
                    buffer = struct.pack(
                        '>128H',
                        color if font.FONT[idx] & _BIT7 else background,
                        color if font.FONT[idx] & _BIT6 else background,
                        color if font.FONT[idx] & _BIT5 else background,
                        color if font.FONT[idx] & _BIT4 else background,
                        color if font.FONT[idx] & _BIT3 else background,
                        color if font.FONT[idx] & _BIT2 else background,
                        color if font.FONT[idx] & _BIT1 else background,
                        color if font.FONT[idx] & _BIT0 else background,
                        color if font.FONT[idx+1] & _BIT7 else background,
                        color if font.FONT[idx+1] & _BIT6 else background,
                        color if font.FONT[idx+1] & _BIT5 else background,
                        color if font.FONT[idx+1] & _BIT4 else background,
                        color if font.FONT[idx+1] & _BIT3 else background,
                        color if font.FONT[idx+1] & _BIT2 else background,
                        color if font.FONT[idx+1] & _BIT1 else background,
                        color if font.FONT[idx+1] & _BIT0 else background,
                        color if font.FONT[idx+2] & _BIT7 else background,
                        color if font.FONT[idx+2] & _BIT6 else background,
                        color if font.FONT[idx+2] & _BIT5 else background,
                        color if font.FONT[idx+2] & _BIT4 else background,
                        color if font.FONT[idx+2] & _BIT3 else background,
                        color if font.FONT[idx+2] & _BIT2 else background,
                        color if font.FONT[idx+2] & _BIT1 else background,
                        color if font.FONT[idx+2] & _BIT0 else background,
                        color if font.FONT[idx+3] & _BIT7 else background,
                        color if font.FONT[idx+3] & _BIT6 else background,
                        color if font.FONT[idx+3] & _BIT5 else background,
                        color if font.FONT[idx+3] & _BIT4 else background,
                        color if font.FONT[idx+3] & _BIT3 else background,
                        color if font.FONT[idx+3] & _BIT2 else background,
                        color if font.FONT[idx+3] & _BIT1 else background,
                        color if font.FONT[idx+3] & _BIT0 else background,
                        color if font.FONT[idx+4] & _BIT7 else background,
                        color if font.FONT[idx+4] & _BIT6 else background,
                        color if font.FONT[idx+4] & _BIT5 else background,
                        color if font.FONT[idx+4] & _BIT4 else background,
                        color if font.FONT[idx+4] & _BIT3 else background,
                        color if font.FONT[idx+4] & _BIT2 else background,
                        color if font.FONT[idx+4] & _BIT1 else background,
                        color if font.FONT[idx+4] & _BIT0 else background,
                        color if font.FONT[idx+5] & _BIT7 else background,
                        color if font.FONT[idx+5] & _BIT6 else background,
                        color if font.FONT[idx+5] & _BIT5 else background,
                        color if font.FONT[idx+5] & _BIT4 else background,
                        color if font.FONT[idx+5] & _BIT3 else background,
                        color if font.FONT[idx+5] & _BIT2 else background,
                        color if font.FONT[idx+5] & _BIT1 else background,
                        color if font.FONT[idx+5] & _BIT0 else background,
                        color if font.FONT[idx+6] & _BIT7 else background,
                        color if font.FONT[idx+6] & _BIT6 else background,
                        color if font.FONT[idx+6] & _BIT5 else background,
                        color if font.FONT[idx+6] & _BIT4 else background,
                        color if font.FONT[idx+6] & _BIT3 else background,
                        color if font.FONT[idx+6] & _BIT2 else background,
                        color if font.FONT[idx+6] & _BIT1 else background,
                        color if font.FONT[idx+6] & _BIT0 else background,
                        color if font.FONT[idx+7] & _BIT7 else background,
                        color if font.FONT[idx+7] & _BIT6 else background,
                        color if font.FONT[idx+7] & _BIT5 else background,
                        color if font.FONT[idx+7] & _BIT4 else background,
                        color if font.FONT[idx+7] & _BIT3 else background,
                        color if font.FONT[idx+7] & _BIT2 else background,
                        color if font.FONT[idx+7] & _BIT1 else background,
                        color if font.FONT[idx+7] & _BIT0 else background,
                        color if font.FONT[idx+8] & _BIT7 else background,
                        color if font.FONT[idx+8] & _BIT6 else background,
                        color if font.FONT[idx+8] & _BIT5 else background,
                        color if font.FONT[idx+8] & _BIT4 else background,
                        color if font.FONT[idx+8] & _BIT3 else background,
                        color if font.FONT[idx+8] & _BIT2 else background,
                        color if font.FONT[idx+8] & _BIT1 else background,
                        color if font.FONT[idx+8] & _BIT0 else background,
                        color if font.FONT[idx+9] & _BIT7 else background,
                        color if font.FONT[idx+9] & _BIT6 else background,
                        color if font.FONT[idx+9] & _BIT5 else background,
                        color if font.FONT[idx+9] & _BIT4 else background,
                        color if font.FONT[idx+9] & _BIT3 else background,
                        color if font.FONT[idx+9] & _BIT2 else background,
                        color if font.FONT[idx+9] & _BIT1 else background,
                        color if font.FONT[idx+9] & _BIT0 else background,
                        color if font.FONT[idx+10] & _BIT7 else background,
                        color if font.FONT[idx+10] & _BIT6 else background,
                        color if font.FONT[idx+10] & _BIT5 else background,
                        color if font.FONT[idx+10] & _BIT4 else background,
                        color if font.FONT[idx+10] & _BIT3 else background,
                        color if font.FONT[idx+10] & _BIT2 else background,
                        color if font.FONT[idx+10] & _BIT1 else background,
                        color if font.FONT[idx+10] & _BIT0 else background,
                        color if font.FONT[idx+11] & _BIT7 else background,
                        color if font.FONT[idx+11] & _BIT6 else background,
                        color if font.FONT[idx+11] & _BIT5 else background,
                        color if font.FONT[idx+11] & _BIT4 else background,
                        color if font.FONT[idx+11] & _BIT3 else background,
                        color if font.FONT[idx+11] & _BIT2 else background,
                        color if font.FONT[idx+11] & _BIT1 else background,
                        color if font.FONT[idx+11] & _BIT0 else background,
                        color if font.FONT[idx+12] & _BIT7 else background,
                        color if font.FONT[idx+12] & _BIT6 else background,
                        color if font.FONT[idx+12] & _BIT5 else background,
                        color if font.FONT[idx+12] & _BIT4 else background,
                        color if font.FONT[idx+12] & _BIT3 else background,
                        color if font.FONT[idx+12] & _BIT2 else background,
                        color if font.FONT[idx+12] & _BIT1 else background,
                        color if font.FONT[idx+12] & _BIT0 else background,
                        color if font.FONT[idx+13] & _BIT7 else background,
                        color if font.FONT[idx+13] & _BIT6 else background,
                        color if font.FONT[idx+13] & _BIT5 else background,
                        color if font.FONT[idx+13] & _BIT4 else background,
                        color if font.FONT[idx+13] & _BIT3 else background,
                        color if font.FONT[idx+13] & _BIT2 else background,
                        color if font.FONT[idx+13] & _BIT1 else background,
                        color if font.FONT[idx+13] & _BIT0 else background,
                        color if font.FONT[idx+14] & _BIT7 else background,
                        color if font.FONT[idx+14] & _BIT6 else background,
                        color if font.FONT[idx+14] & _BIT5 else background,
                        color if font.FONT[idx+14] & _BIT4 else background,
                        color if font.FONT[idx+14] & _BIT3 else background,
                        color if font.FONT[idx+14] & _BIT2 else background,
                        color if font.FONT[idx+14] & _BIT1 else background,
                        color if font.FONT[idx+14] & _BIT0 else background,
                        color if font.FONT[idx+15] & _BIT7 else background,
                        color if font.FONT[idx+15] & _BIT6 else background,
                        color if font.FONT[idx+15] & _BIT5 else background,
                        color if font.FONT[idx+15] & _BIT4 else background,
                        color if font.FONT[idx+15] & _BIT3 else background,
                        color if font.FONT[idx+15] & _BIT2 else background,
                        color if font.FONT[idx+15] & _BIT1 else background,
                        color if font.FONT[idx+15] & _BIT0 else background
                    )
                    self.blit_buffer(buffer, x0, y0+8*line, 16, 8)
            x0 += font.WIDTH

   
    def text(self, font, text, x0, y0, color=WHITE, background=BLACK):
        """
        Draw text on display in specified font and colors. 8 and 16 bit wide
        fonts are supported.

        Args:
            font (module): font module to use.
            text (str): text to write
            x0 (int): column to start drawing at
            y0 (int): row to start drawing at
            color (int): 565 encoded color to use for characters
            background (int): 565 encoded color to use for background
        """
        if font.WIDTH == 8:
            self._text8(font, text, x0, y0, color, background)
        else:
            self._text16(font, text, x0, y0, color, background)

    def bitmap(self, bitmap, x, y, index=0):
        """
        Draw a bitmap on display at the specified column and row

        Args:
            bitmap (bitmap_module): The module containing the bitmap to draw
            x (int): column to start drawing at
            y (int): row to start drawing at
            index (int): Optional index of bitmap to draw from multiple bitmap
                module

        """
        bitmap_size = bitmap.HEIGHT * bitmap.WIDTH
        buffer_len = bitmap_size * 2
        buffer = bytearray(buffer_len)
        bs_bit = bitmap.BPP * bitmap_size * index if index > 0 else 0

        for i in range(0, buffer_len, 2):
            color_index = 0
            for _ in range(bitmap.BPP):
                color_index <<= 1
                color_index |= (bitmap.BITMAP[bs_bit // 8]
                                & 1 << (7 - (bs_bit % 8))) > 0
                bs_bit += 1

            color = bitmap.PALETTE[color_index]
            buffer[i] = color & 0xff00 >> 8
            buffer[i + 1] = color_index & 0xff

        to_col = x + bitmap.WIDTH - 1
        to_row = y + bitmap.HEIGHT - 1
        if self.width > to_col and self.height > to_row:
            self._set_window(x, y, to_col, to_row)
            self._write(None, buffer)

    # @micropython.native
    def write(self, font, string, x, y, fg=WHITE, bg=BLACK):
        """
        Write a string using a converted true-type font on the display starting
        at the specified column and row

        Args:
            font (font): The module containing the converted true-type font
            s (string): The string to write
            x (int): column to start writing
            y (int): row to start writing
            fg (int): foreground color, optional, defaults to WHITE
            bg (int): background color, optional, defaults to BLACK
        """
        buffer_len = font.HEIGHT * font.MAX_WIDTH * 2
        buffer = bytearray(buffer_len)
        fg_hi = (fg & 0xff00) >> 8
        fg_lo = fg & 0xff

        bg_hi = (bg & 0xff00) >> 8
        bg_lo = bg & 0xff

        for character in string:
            try:
                char_index = font.MAP.index(character)
                offset = char_index * font.OFFSET_WIDTH
                bs_bit = font.OFFSETS[offset]
                if font.OFFSET_WIDTH > 1:
                    bs_bit = (bs_bit << 8) + font.OFFSETS[offset + 1]

                if font.OFFSET_WIDTH > 2:
                    bs_bit = (bs_bit << 8) + font.OFFSETS[offset + 2]

                char_width = font.WIDTHS[char_index]
                buffer_needed = char_width * font.HEIGHT * 2

                for i in range(0, buffer_needed, 2):
                    if font.BITMAPS[bs_bit // 8] & 1 << (7 - (bs_bit % 8)) > 0:
                        buffer[i] = fg_hi
                        buffer[i + 1] = fg_lo
                    else:
                        buffer[i] = bg_hi
                        buffer[i + 1] = bg_lo

                    bs_bit += 1

                to_col = x + char_width - 1
                to_row = y + font.HEIGHT - 1
                if self.width > to_col and self.height > to_row:
                    self._set_window(x, y, to_col, to_row)
                    self._write(None, buffer[:buffer_needed])

                x += char_width

            except ValueError:
                pass

    def write_width(self, font, string):
        """
        Returns the width in pixels of the string if it was written with the
        specified font

        Args:
            font (font): The module containing the converted true-type font
            string (string): The string to measure
        """
        width = 0
        for character in string:
            try:
                char_index = font.MAP.index(character)
                width += font.WIDTHS[char_index]

            except ValueError:
                pass

        return width




  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值