├── LICENSE ├── README.md ├── ST7735fb.py ├── fbconsole.py └── petme128.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FBConsole 2 | 3 | Framebuffer console class for MicroPython. 4 | 5 | You can redirect REPL to any framebuffer devices. 6 | 7 | ## Usage 8 | 9 | The example below duplicates REPL output to SSD1306 OLED connected to ESP32 via I2C(SDA=Pin22, SCL=Pin21). 10 | ``` 11 | from machine import Pin, I2C 12 | from ssd1306 import SSD1306_I2C 13 | i2c = I2C(scl=Pin(21), sda=Pin(22), freq=400000) 14 | oled = SSD1306_I2C(128, 64, i2c) 15 | 16 | from fbconsole import FBConsole 17 | import os 18 | scr = FBConsole(oled) 19 | os.dupterm(scr) # redirect REPL output to OLED 20 | help() # and print something 21 | os.dupterm(None) # then disconnect OLED from REPL 22 | scr.cls() # and clear OLED screen 23 | ``` 24 | ![top-page](https://raw.githubusercontent.com/boochow/FBConsole/images/dupterm-oled.gif) 25 | 26 | ## ST7735 Wrapper 27 | 28 | ST7735fb.py is a wrapper class to use FBConsole with ST7735-based small TFT LCD. 29 | It provides some of FrameBuffer class APIs necessary to use FBConsole. 30 | Use this with [ST7735 driver for MicroPython](https://github.com/boochow/MicroPython-ST7735). 31 | Only LCDs with ST7735S(and maybe ST7735R) are supported because this wrapper class requires hardware scroll functionality. 32 | 33 | ## petme128 font 34 | 35 | petme128.py is a font converted from ``petme128-font.c`` used in the framebuf class of MicroPython. 36 | 37 | ## ST7735fb usage 38 | 39 | ``` 40 | # This example is for ESP32 + ST7735S TFT LCD 41 | from ST7735 import TFT 42 | from machine import SPI,Pin 43 | spi = SPI(2, baudrate=20000000, polarity=0, phase=0, sck=Pin(14), mosi=Pin(13), miso=Pin(12)) 44 | tft=TFT(spi,16,17,18) 45 | tft.initb2() 46 | tft.rgb(True) 47 | 48 | # Assign 2 pixels for fixed and invisible area 49 | # (ST7735S frame buffer vertical size is 162 pixels) 50 | tft.setvscroll(1, 1) 51 | 52 | # Wrapper object for FBConsole 53 | from ST7735fb import TFTfb 54 | from petme128 import petme128 55 | fb = TFTfb(tft, petme128) 56 | 57 | # redirect MicroPython terminal to ST7735 58 | from fbconsole import FBConsole 59 | scr = FBConsole(fb, TFT.BLACK, TFT.WHITE) 60 | 61 | import os 62 | os.dupterm(scr) 63 | ``` 64 | ![top-page](https://raw.githubusercontent.com/boochow/FBConsole/images/st7735-dupterm.jpg) 65 | -------------------------------------------------------------------------------- /ST7735fb.py: -------------------------------------------------------------------------------- 1 | # ST7735S driver wrapper for FBConsole 2 | # ST7735 driver: https://github.com/boochow/MicroPython-ST7735 3 | # FBConsole: https://github.com/boochow/FBConsole 4 | 5 | class TFTfb(object): 6 | def __init__(self, tft, font): 7 | self.height = 160 8 | self.width = 128 9 | self.tft = tft 10 | self.font = font 11 | tft.setvscroll(tft.tfa, tft.bfa) 12 | tft.fill(0) 13 | tft.vscroll(0) 14 | self.voffset = 0 15 | 16 | def _abs2tft(self, v) : 17 | ''' convert screen y coord to LCD address''' 18 | return (self.voffset + v) % self.height 19 | 20 | def fill(self, color) : 21 | self.tft.fill(color) 22 | 23 | def fill_rect(self, x, y, w, h, color) : 24 | top = self._abs2tft(y) 25 | bottom = self._abs2tft(y + h) 26 | if (bottom > top): 27 | self.tft.fillrect((x, top), (w, h), color) 28 | else: 29 | self.tft.fillrect((x, top), (w, self.height - top), color) 30 | self.tft.fillrect((x, 0), (w, bottom), color) 31 | 32 | def scroll(self, dx, dy) : 33 | self.voffset = (self.voffset - dy + self.height) % self.height 34 | self.tft.vscroll(self.voffset) 35 | 36 | def hline(self, x, y, w, color) : 37 | self.tft.hline((x, self._abs2tft(y)), w, color) 38 | 39 | def text(self, c, x, y, color) : 40 | self.tft.char((x, self._abs2tft(y)), c, color, self.font, (1, 1)) 41 | -------------------------------------------------------------------------------- /fbconsole.py: -------------------------------------------------------------------------------- 1 | import framebuf 2 | import uio 3 | 4 | class FBConsole(uio.IOBase): 5 | def __init__(self, fb, bgcolor=0, fgcolor=-1, width=-1, height=-1, readobj=None): 6 | self.readobj = readobj 7 | self.fb = fb 8 | if width > 0: 9 | self.width=width 10 | else: 11 | try: 12 | self.width=fb.width 13 | except: 14 | raise ValueError 15 | if height > 0: 16 | self.height=height 17 | else: 18 | try: 19 | self.height=fb.height 20 | except: 21 | raise ValueError 22 | self.bgcolor = bgcolor 23 | self.fgcolor = fgcolor 24 | self.line_height(8) 25 | self.cls() 26 | 27 | def cls(self): 28 | self.x = 0 29 | self.y = 0 30 | self.y_end = 0 31 | self.fb.fill(self.bgcolor) 32 | try: 33 | self.fb.show() 34 | except: 35 | pass 36 | 37 | def line_height(self, px): 38 | self.lineheight = px 39 | self.w = self.width // px 40 | self.h = self.height // px 41 | 42 | def _putc(self, c): 43 | c = chr(c) 44 | if c == '\n': 45 | self._newline() 46 | elif c == '\x08': 47 | self._backspace() 48 | elif c >= ' ': 49 | self.fb.fill_rect(self.x * 8, self.y * self.lineheight, 8, self.lineheight, self.bgcolor) 50 | self.fb.text(c, self.x * 8, self.y * self.lineheight, self.fgcolor) 51 | self.x += 1 52 | if self.x >= self.w: 53 | self._newline() 54 | 55 | def _esq_read_num(self, buf, pos): 56 | digit = 1 57 | n = 0 58 | while buf[pos] != 0x5b: 59 | n += digit * (buf[pos] - 0x30) 60 | pos -= 1 61 | digit *= 10 62 | return n 63 | 64 | def write(self, buf): 65 | self._draw_cursor(self.bgcolor) 66 | i = 0 67 | while i < len(buf): 68 | c = buf[i] 69 | if c == 0x1b: 70 | i += 1 71 | esc = i 72 | while chr(buf[i]) in '[;0123456789': 73 | i += 1 74 | c = buf[i] 75 | if c == 0x4b and i == esc + 1: # ESC [ K 76 | self._clear_cursor_eol() 77 | elif c == 0x44: # ESC [ n D 78 | for _ in range(self._esq_read_num(buf, i - 1)): 79 | self._backspace() 80 | else: 81 | self._putc(c) 82 | i += 1 83 | self._draw_cursor(self.fgcolor) 84 | try: 85 | self.fb.show() 86 | except: 87 | pass 88 | return len(buf) 89 | 90 | def readinto(self, buf, nbytes=0): 91 | if self.readobj != None: 92 | return self.readobj.readinto(buf, nbytes) 93 | else: 94 | return None 95 | 96 | def _newline(self): 97 | self.x = 0 98 | self.y += 1 99 | if self.y >= self.h: 100 | self.fb.scroll(0, -8) 101 | self.fb.fill_rect(0, self.height - self.lineheight, self.width, self.lineheight, self.bgcolor) 102 | self.y = self.h - 1 103 | self.y_end = self.y 104 | 105 | def _backspace(self): 106 | if self.x == 0: 107 | if self.y > 0: 108 | self.y -= 1 109 | self.x = self.w - 1 110 | else: 111 | self.x -= 1 112 | 113 | def _clear_cursor_eol(self): 114 | self.fb.fill_rect(self.x * 8, self.y * self.lineheight, self.width, self.lineheight, self.bgcolor) 115 | for l in range(self.y + 1, self.y_end + 1): 116 | self.fb.fill_rect(0, l * self.lineheight, self.width, self.lineheight, self.bgcolor) 117 | self.y_end = self.y 118 | 119 | def _draw_cursor(self, color): 120 | self.fb.hline(self.x * 8, self.y * self.lineheight + 7, 8, color) 121 | -------------------------------------------------------------------------------- /petme128.py: -------------------------------------------------------------------------------- 1 | petme128 = {"Width": 8, "Height": 8, "Start": 32, "End": 127, "Data": bytearray([ 2 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, # 32= 3 | 0x00,0x00,0x00,0x4f,0x4f,0x00,0x00,0x00, # 33=! 4 | 0x00,0x07,0x07,0x00,0x00,0x07,0x07,0x00, # 34=" 5 | 0x14,0x7f,0x7f,0x14,0x14,0x7f,0x7f,0x14, # 35=# 6 | 0x00,0x24,0x2e,0x6b,0x6b,0x3a,0x12,0x00, # 36=$ 7 | 0x00,0x63,0x33,0x18,0x0c,0x66,0x63,0x00, # 37=% 8 | 0x00,0x32,0x7f,0x4d,0x4d,0x77,0x72,0x50, # 38=& 9 | 0x00,0x00,0x00,0x04,0x06,0x03,0x01,0x00, # 39=' 10 | 0x00,0x00,0x1c,0x3e,0x63,0x41,0x00,0x00, # 40=( 11 | 0x00,0x00,0x41,0x63,0x3e,0x1c,0x00,0x00, # 41=) 12 | 0x08,0x2a,0x3e,0x1c,0x1c,0x3e,0x2a,0x08, # 42=* 13 | 0x00,0x08,0x08,0x3e,0x3e,0x08,0x08,0x00, # 43=+ 14 | 0x00,0x00,0x80,0xe0,0x60,0x00,0x00,0x00, # 44=, 15 | 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, # 45=- 16 | 0x00,0x00,0x00,0x60,0x60,0x00,0x00,0x00, # 46=. 17 | 0x00,0x40,0x60,0x30,0x18,0x0c,0x06,0x02, # 47=/ 18 | 0x00,0x3e,0x7f,0x49,0x45,0x7f,0x3e,0x00, # 48=0 19 | 0x00,0x40,0x44,0x7f,0x7f,0x40,0x40,0x00, # 49=1 20 | 0x00,0x62,0x73,0x51,0x49,0x4f,0x46,0x00, # 50=2 21 | 0x00,0x22,0x63,0x49,0x49,0x7f,0x36,0x00, # 51=3 22 | 0x00,0x18,0x18,0x14,0x16,0x7f,0x7f,0x10, # 52=4 23 | 0x00,0x27,0x67,0x45,0x45,0x7d,0x39,0x00, # 53=5 24 | 0x00,0x3e,0x7f,0x49,0x49,0x7b,0x32,0x00, # 54=6 25 | 0x00,0x03,0x03,0x79,0x7d,0x07,0x03,0x00, # 55=7 26 | 0x00,0x36,0x7f,0x49,0x49,0x7f,0x36,0x00, # 56=8 27 | 0x00,0x26,0x6f,0x49,0x49,0x7f,0x3e,0x00, # 57=9 28 | 0x00,0x00,0x00,0x24,0x24,0x00,0x00,0x00, # 58=: 29 | 0x00,0x00,0x80,0xe4,0x64,0x00,0x00,0x00, # 59=; 30 | 0x00,0x08,0x1c,0x36,0x63,0x41,0x41,0x00, # 60=< 31 | 0x00,0x14,0x14,0x14,0x14,0x14,0x14,0x00, # 61== 32 | 0x00,0x41,0x41,0x63,0x36,0x1c,0x08,0x00, # 62=> 33 | 0x00,0x02,0x03,0x51,0x59,0x0f,0x06,0x00, # 63=? 34 | 0x00,0x3e,0x7f,0x41,0x4d,0x4f,0x2e,0x00, # 64=@ 35 | 0x00,0x7c,0x7e,0x0b,0x0b,0x7e,0x7c,0x00, # 65=A 36 | 0x00,0x7f,0x7f,0x49,0x49,0x7f,0x36,0x00, # 66=B 37 | 0x00,0x3e,0x7f,0x41,0x41,0x63,0x22,0x00, # 67=C 38 | 0x00,0x7f,0x7f,0x41,0x63,0x3e,0x1c,0x00, # 68=D 39 | 0x00,0x7f,0x7f,0x49,0x49,0x41,0x41,0x00, # 69=E 40 | 0x00,0x7f,0x7f,0x09,0x09,0x01,0x01,0x00, # 70=F 41 | 0x00,0x3e,0x7f,0x41,0x49,0x7b,0x3a,0x00, # 71=G 42 | 0x00,0x7f,0x7f,0x08,0x08,0x7f,0x7f,0x00, # 72=H 43 | 0x00,0x00,0x41,0x7f,0x7f,0x41,0x00,0x00, # 73=I 44 | 0x00,0x20,0x60,0x41,0x7f,0x3f,0x01,0x00, # 74=J 45 | 0x00,0x7f,0x7f,0x1c,0x36,0x63,0x41,0x00, # 75=K 46 | 0x00,0x7f,0x7f,0x40,0x40,0x40,0x40,0x00, # 76=L 47 | 0x00,0x7f,0x7f,0x06,0x0c,0x06,0x7f,0x7f, # 77=M 48 | 0x00,0x7f,0x7f,0x0e,0x1c,0x7f,0x7f,0x00, # 78=N 49 | 0x00,0x3e,0x7f,0x41,0x41,0x7f,0x3e,0x00, # 79=O 50 | 0x00,0x7f,0x7f,0x09,0x09,0x0f,0x06,0x00, # 80=P 51 | 0x00,0x1e,0x3f,0x21,0x61,0x7f,0x5e,0x00, # 81=Q 52 | 0x00,0x7f,0x7f,0x19,0x39,0x6f,0x46,0x00, # 82=R 53 | 0x00,0x26,0x6f,0x49,0x49,0x7b,0x32,0x00, # 83=S 54 | 0x00,0x01,0x01,0x7f,0x7f,0x01,0x01,0x00, # 84=T 55 | 0x00,0x3f,0x7f,0x40,0x40,0x7f,0x3f,0x00, # 85=U 56 | 0x00,0x1f,0x3f,0x60,0x60,0x3f,0x1f,0x00, # 86=V 57 | 0x00,0x7f,0x7f,0x30,0x18,0x30,0x7f,0x7f, # 87=W 58 | 0x00,0x63,0x77,0x1c,0x1c,0x77,0x63,0x00, # 88=X 59 | 0x00,0x07,0x0f,0x78,0x78,0x0f,0x07,0x00, # 89=Y 60 | 0x00,0x61,0x71,0x59,0x4d,0x47,0x43,0x00, # 90=Z 61 | 0x00,0x00,0x7f,0x7f,0x41,0x41,0x00,0x00, # 91=[ 62 | 0x00,0x02,0x06,0x0c,0x18,0x30,0x60,0x40, # 92='\' 63 | 0x00,0x00,0x41,0x41,0x7f,0x7f,0x00,0x00, # 93=] 64 | 0x00,0x08,0x0c,0x06,0x06,0x0c,0x08,0x00, # 94=^ 65 | 0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0, # 95=_ 66 | 0x00,0x00,0x01,0x03,0x06,0x04,0x00,0x00, # 96=` 67 | 0x00,0x20,0x74,0x54,0x54,0x7c,0x78,0x00, # 97=a 68 | 0x00,0x7f,0x7f,0x44,0x44,0x7c,0x38,0x00, # 98=b 69 | 0x00,0x38,0x7c,0x44,0x44,0x6c,0x28,0x00, # 99=c 70 | 0x00,0x38,0x7c,0x44,0x44,0x7f,0x7f,0x00, # 100=d 71 | 0x00,0x38,0x7c,0x54,0x54,0x5c,0x58,0x00, # 101=e 72 | 0x00,0x08,0x7e,0x7f,0x09,0x03,0x02,0x00, # 102=f 73 | 0x00,0x98,0xbc,0xa4,0xa4,0xfc,0x7c,0x00, # 103=g 74 | 0x00,0x7f,0x7f,0x04,0x04,0x7c,0x78,0x00, # 104=h 75 | 0x00,0x00,0x00,0x7d,0x7d,0x00,0x00,0x00, # 105=i 76 | 0x00,0x40,0xc0,0x80,0x80,0xfd,0x7d,0x00, # 106=j 77 | 0x00,0x7f,0x7f,0x30,0x38,0x6c,0x44,0x00, # 107=k 78 | 0x00,0x00,0x41,0x7f,0x7f,0x40,0x00,0x00, # 108=l 79 | 0x00,0x7c,0x7c,0x18,0x30,0x18,0x7c,0x7c, # 109=m 80 | 0x00,0x7c,0x7c,0x04,0x04,0x7c,0x78,0x00, # 110=n 81 | 0x00,0x38,0x7c,0x44,0x44,0x7c,0x38,0x00, # 111=o 82 | 0x00,0xfc,0xfc,0x24,0x24,0x3c,0x18,0x00, # 112=p 83 | 0x00,0x18,0x3c,0x24,0x24,0xfc,0xfc,0x00, # 113=q 84 | 0x00,0x7c,0x7c,0x04,0x04,0x0c,0x08,0x00, # 114=r 85 | 0x00,0x48,0x5c,0x54,0x54,0x74,0x20,0x00, # 115=s 86 | 0x04,0x04,0x3f,0x7f,0x44,0x64,0x20,0x00, # 116=t 87 | 0x00,0x3c,0x7c,0x40,0x40,0x7c,0x3c,0x00, # 117=u 88 | 0x00,0x1c,0x3c,0x60,0x60,0x3c,0x1c,0x00, # 118=v 89 | 0x00,0x1c,0x7c,0x30,0x18,0x30,0x7c,0x1c, # 119=w 90 | 0x00,0x44,0x6c,0x38,0x38,0x6c,0x44,0x00, # 120=x 91 | 0x00,0x9c,0xbc,0xa0,0xa0,0xfc,0x7c,0x00, # 121=y 92 | 0x00,0x44,0x64,0x74,0x5c,0x4c,0x44,0x00, # 122=z 93 | 0x00,0x08,0x08,0x3e,0x77,0x41,0x41,0x00, # 123={ 94 | 0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00, # 124=| 95 | 0x00,0x41,0x41,0x77,0x3e,0x08,0x08,0x00, # 125=} 96 | 0x00,0x02,0x03,0x01,0x03,0x02,0x03,0x01, # 126=~ 97 | 0xaa,0x55,0xaa,0x55,0xaa,0x55,0xaa,0x55, # 127 98 | ])} 99 | --------------------------------------------------------------------------------