""" MINI_WCH Micropython library for the MINI_WCH(TOUCH*2, MIC*1, Buzzer*1, PWM*2, Matrix8x12, HID) ======================================================= @dahanzimin From the Mixly Team """ import time, math from esp import flash_read from micropython import const from framebuf import FrameBuffer, MONO_VLSB _BOT035_ADDRESS = const(0x13) _BOT5_TOUCH = const(0x01) _BOT035_MIC = const(0x05) _BOT035_SPK = const(0x07) _BOT035_PWM = const(0x0B) _BOT035_FLAG = const(0x0F) _BOT035_LEDS = const(0x10) _BOT035_KB = const(0x1C) _BOT035_MS = const(0x20) _BOT035_STR = const(0x24) _BOT035_STA = const(0x25) _BOT035_PCM = const(0x26) _FONT_W = const(5) _FONT_H = const(8) _LEDS_W = const(12) _LEDS_H = const(8) _FONT5x8_CODE = const(b'\x05\x08\x00\x00\x00\x00\x00\x00\x00_\x00\x00\x00\x07\x00\x07\x00\x14\x7f\x14\x7f\x14$*\x7f*\x12#\x13\x08db6IV P\x00\x08\x07\x03\x00\x00\x1c"A\x00\x00A"\x1c\x00*\x1c\x7f\x1c*\x08\x08>\x08\x08\x00\x80p0\x00\x08\x08\x08\x08\x08\x00\x00``\x00 \x10\x08\x04\x02>QIE>\x00B\x7f@\x00rIIIF!AIM3\x18\x14\x12\x7f\x10\'EEE9A]YN|\x12\x11\x12|\x7fIII6>AAA"\x7fAAA>\x7fIIIA\x7f\t\t\t\x01>AAQs\x7f\x08\x08\x08\x7f\x00A\x7fA\x00 @A?\x01\x7f\x08\x14"A\x7f@@@@\x7f\x02\x1c\x02\x7f\x7f\x04\x08\x10\x7f>AAA>\x7f\t\t\t\x06>AQ!^\x7f\t\x19)F&III2\x03\x01\x7f\x01\x03?@@@?\x1f @ \x1f?@8@?c\x14\x08\x14c\x03\x04x\x04\x03aYIMC\x00\x7fAAA\x02\x04\x08\x10 \x00AAA\x7f\x04\x02\x01\x02\x04@@@@@\x00\x03\x07\x08\x00 TTx@\x7f(DD88DDD(8DD(\x7f8TTT\x18\x00\x08~\t\x02\x18\xa4\xa4\x9cx\x7f\x08\x04\x04x\x00D}@\x00 @@=\x00\x7f\x10(D\x00\x00A\x7f@\x00|\x04x\x04x|\x08\x04\x04x8DDD8\xfc\x18$$\x18\x18$$\x18\xfc|\x08\x04\x04\x08HTTT$\x04\x04?D$<@@ |\x1c @ \x1c<@0@> 26 buffer = bytearray(12 * (font_width // 8 + 1)) flash_read(_Uincode_ADDR + font_address, buffer) return buffer, font_width, 12 def shift(self, x, y, rotate=False): """Shift pixels by x and y""" if x > 0: # Shift Right for _ in range(x): for row in range(0, _LEDS_H): last_pixel = self.pixel(_LEDS_W - 1, row) if rotate else 0 for col in range(_LEDS_W - 1, 0, -1): self.pixel(col, row, self.pixel(col - 1, row)) self.pixel(0, row, last_pixel) elif x < 0: # Shift Left for _ in range(-x): for row in range(0, _LEDS_H): last_pixel = self.pixel(0, row) if rotate else 0 for col in range(0, _LEDS_W - 1): self.pixel(col, row, self.pixel(col + 1, row)) self.pixel(_LEDS_W - 1, row, last_pixel) if y > 0: # Shift Up for _ in range(y): for col in range(0, _LEDS_W): last_pixel = self.pixel(col, _LEDS_H - 1) if rotate else 0 for row in range(_LEDS_H - 1, 0, -1): self.pixel(col, row, self.pixel(col, row - 1)) self.pixel(col, 0, last_pixel) elif y < 0: # Shift Down for _ in range(-y): for col in range(0, _LEDS_W): last_pixel = self.pixel(col, 0) if rotate else 0 for row in range(0, _LEDS_H - 1): self.pixel(col, row, self.pixel(col, row + 1)) self.pixel(col, _LEDS_H - 1, last_pixel) self.show() def shift_right(self, num, rotate=False): """Shift all pixels right""" self.shift(num, 0, rotate) def shift_left(self, num, rotate=False): """Shift all pixels left""" self.shift(-num, 0, rotate) def shift_up(self, num, rotate=False): """Shift all pixels up""" self.shift(0, -num, rotate) def shift_down(self, num, rotate=False): """Shift all pixels down""" self.shift(0, num, rotate) def map_invert(self, own): """Graph invert operation""" result = bytearray() for i in range(len(own)): result.append(~ own[i]) return result def map_add(self, own, other): """Graph union operation""" result = bytearray() for i in range(min(len(own), len(other))): result.append(own[i] | other[i]) return result def map_sub(self, own, other): """Graphic subtraction operation""" result = bytearray() for i in range(min(len(own), len(other))): result.append((own[i] ^ other[i]) & own[i]) return result def set_buffer(self, buffer): for i in range(len(buffer)): self._buffer[i] = self._buffer[i] | buffer[i] def _ascall_bitmap(self, data, x=0 ,space=0): for char in data: buf = self._chardata(char) if -_FONT_W <= x <= _LEDS_W: for _x in range(_FONT_W): for _y in range(_FONT_H): if (buf[_x] >> _y) & 0x1: self.pixel(x + _x, _y, 1) x = _FONT_W + x + space self.show() def _uincode_scroll(self, buffer, space): _len = 0 for buf in buffer: _len = _len + space + (buf[2] if self._way <= 1 else buf[1]) for i in range(_len - space + (_LEDS_W if self._way <= 1 else _LEDS_H)): _step = (_LEDS_W - 1 if self._way <= 1 else _LEDS_H) - i self.fill(0) for buf in buffer: _buf, width, high = buf _xx = (_LEDS_W - width) // 2 if -high < _step < _LEDS_W: for _y in range(12): for _x in range(width): if _buf[_y * ((width + 7) // 8) + _x // 8] & (0x80 >> (_x & 7)): if self._way == 0: self.pixel( _LEDS_W - (_xx + _x) - 2, _LEDS_H - (_step + _y), 1) elif self._way == 1: self.pixel(_xx + _x + 1, _step + _y, 1) elif self._way == 2: self.pixel(_y, _LEDS_H - (_step + _x), 1) elif self._way == 3: self.pixel(_LEDS_W - _y - 1, (_step + _x), 1) _step = _step + space + (high if self._way <= 1 else width) self.show() time.sleep_ms(self._speed) def _gb2312_scroll(self, data, space): for char in data: if ord(char) >= 0xFF: font_buffer = [] for c in data: _buffer = self._uincode(c) font_buffer.append(_buffer) self._uincode_scroll(font_buffer, space) return True def scroll_way(self, way=1, speed=None): """0,1竖,2,3横""" self._way = way % 4 if speed is not None: self._speed = speed def shows(self, data, space=1, center=True): """Display character""" if data is not None: self.fill(0) if type(data) in [bytes, bytearray]: self.set_buffer(data) self.show() else: data = str(data) if not self._gb2312_scroll(data, space): x = (_LEDS_W - len(data) * (_FONT_W + space) + space) // 2 if center else 0 self._ascall_bitmap(data, x) def frame(self, data, delay=500): """Display one frame per character""" if data is not None: data = str(data) for char in data: self.fill(0) if ord(char) >= 0xFF: _way = self._way self._way = 1 self._uincode_scroll([self._uincode(char)], 0) self._way = _way else: self._ascall_bitmap([char], (_LEDS_W - _FONT_W) // 2) time.sleep_ms(delay) def scroll(self, data, space=0, speed=None): """Scrolling characters""" if speed is not None: self._speed = speed if data is not None: data = str(data) if not self._gb2312_scroll(data, space): str_len = len(data) * (_FONT_W + space) - space for i in range(str_len + _LEDS_W + 1): x = _LEDS_W - i self.fill(0) self._ascall_bitmap(data, x) time.sleep_ms(self._speed) def pointern(self, x=_LEDS_W // 2, y=_LEDS_H // 2, l=_LEDS_H // 2, angle=0): radian = math.radians(angle) self.fill(0) self.line(x, y, round(x + l * math.sin(radian)), round(y - l * math.cos(radian)), 1) self.show() def _wreg(self, reg, val): '''Write memory address''' self._i2c.writeto_mem(_BOT035_ADDRESS, reg, val.to_bytes(1, 'little')) def _rreg(self, reg, nbytes=1): '''Read memory address''' self._i2c.writeto(_BOT035_ADDRESS, reg.to_bytes(1, 'little')) return self._i2c.readfrom(_BOT035_ADDRESS, nbytes)[0] if nbytes <= 1 else self._i2c.readfrom(_BOT035_ADDRESS, nbytes) def version(self): _ver = self._rreg(0x00) if _ver == 0x26: self._version = 0 return "v1.7", "Only support CDC serial port" elif _ver == 0x27: self._version = 1 return "v2.5", "Composite devices (CDC, Keyboard and Mouse)" elif _ver == 0x28: self._version = 2 return "v2.9", "Composite devices (CDC, HID, WEBUSB, Keyboard and Mouse)" elif _ver == 0x29: self._version = 3 return "v3.0", "Composite devices (CDC, HID, WEBUSB, Keyboard and Mouse), Support PCM collection for MIC" else: return "vx.x", "Unknown, awaiting update" def reset(self): """Reset SPK, PWM, HID registers to default state""" self._i2c.writeto_mem(_BOT035_ADDRESS, _BOT035_SPK, b'\x0A\x00\x00\x00\x20\x4E\x64\x64\x28') if self._version: self._i2c.writeto_mem(_BOT035_ADDRESS, _BOT035_KB, bytes(10)) def get_brightness(self): return self._brightness def set_brightness(self, brightness): if not 0.0 <= brightness <= 1.0: raise ValueError("Brightness must be a decimal number in the range: 0.0-1.0") self._brightness = brightness self._wreg(_BOT035_FLAG, (self._rreg(_BOT035_FLAG) & 0x0F) | round(10 * brightness)) def show(self): self._i2c.writeto_mem(_BOT035_ADDRESS, _BOT035_LEDS, self._buffer) def buzzer(self, duty=None, freq=None): if duty is not None: duty = max(min(duty, 100), 0) self._wreg(_BOT035_SPK + 2, int(duty)) if freq is not None: freq = max(min(freq, 65535), 10) self._wreg(_BOT035_SPK, freq & 0xFF) self._wreg(_BOT035_SPK + 1, freq >> 8) if freq is None and duty is None: return self._rreg(_BOT035_SPK + 2), self._rreg(_BOT035_SPK) | self._rreg(_BOT035_SPK + 1) << 8 def usben(self, index=1, duty=None, freq=None): index = max(min(index, 2), 1) - 1 if duty is not None: duty = max(min(duty, 100), 0) self._wreg(_BOT035_PWM + index + 2, int(duty)) if freq is not None: freq = max(min(freq, 65535), 10) self._wreg(_BOT035_PWM, freq & 0xFF) self._wreg(_BOT035_PWM + 1, freq >> 8) if freq is None and duty is None: return self._rreg(_BOT035_PWM + index + 2), self._rreg(_BOT035_PWM) | self._rreg(_BOT035_PWM + 1) << 8 def touch(self, index, value=None): index = max(min(index, 1), 0) touch = 4095 - (self._rreg(_BOT5_TOUCH + index * 2) | self._rreg(_BOT5_TOUCH + index * 2 + 1) << 8) return touch > value if value else touch def touched(self, index, value=600): return self.touch(index, value) def touch_slide(self): values = [] for i in range(30): values.append((self.touch(1) - self._touchs[1]) - (self.touch(0) - self._touchs[0])) return round(sorted(values)[15] / 10) def soundlevel(self): values = [] for i in range(50): values.append(self._rreg(_BOT035_MIC) | self._rreg(_BOT035_MIC + 1) << 8) values = sorted(values) return values[-10] - values[10] def hid_keyboard(self, special=0, general=0, release=True): if self._version: self._buf = bytearray(4) self._buf[0] = special if type(general) in (tuple, list): for i in range(len(general)): if i > 2: break self._buf[i + 1] = general[i] else: self._buf[1] = general self._i2c.writeto_mem(_BOT035_ADDRESS, _BOT035_KB, self._buf) if release: time.sleep_ms(10) self._i2c.writeto_mem(_BOT035_ADDRESS, _BOT035_KB, bytes(4)) else: print("Warning: Please upgrade the coprocessor firmware to use this feature") def hid_keyboard_str(self, string, delay=0): if self._version: for char in str(string): self._wreg(_BOT035_STR, ord(char) & 0xFF) time.sleep_ms(20 + delay) else: print("Warning: Please upgrade the coprocessor firmware to use this feature") def hid_keyboard_state(self): state = self._rreg(_BOT035_STA) return bool(state & 0x10), bool(state & 0x20), bool(state & 0x40) def hid_mouse(self, keys=0, move=(0, 0), wheel=0, release=True): if self._version: self._i2c.writeto_mem(_BOT035_ADDRESS, _BOT035_MS, bytes([keys & 0x0F, move[0] & 0xFF, move[1] & 0xFF, wheel & 0xFF])) if release: time.sleep_ms(10) self._i2c.writeto_mem(_BOT035_ADDRESS, _BOT035_MS, bytes(4)) else: print("Warning: Please upgrade the coprocessor firmware to use this feature") def mic_pga(self, value=None): '''0:4x, 1:8x, 2:16x, 3:32x, 4:OFF''' if value is None: return self._rreg(_BOT035_FLAG) >> 4 else: self._wreg(_BOT035_FLAG, (self._rreg(_BOT035_FLAG) & 0x0F) | (value & 0x03) << 4) def pcm_en(self, power=None): if self._version >= 3: if power is None: return bool(self._rreg(_BOT035_STA) & 0x08) else: self._wreg(_BOT035_STA, (self._rreg(_BOT035_STA) & 0xF7) | (power & 0x01) << 3) else: raise OSError("This feature is only supported on version 2.0 ~") def pcm_any(self): return bool(self._rreg(_BOT035_STA) & 0x80) def pcm_read(self, ibuf=1600): return self._rreg(_BOT035_PCM, ibuf) def pcm_record(self, path='mixly.wav', seconds=5, ibuf=1600, timeout=2000): self.pcm_en(True) _star = time.ticks_ms() _size = int(ibuf * seconds * 10) _header = b'RIFF' + (36 + _size).to_bytes(4, 'little') + b'WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00' + (ibuf * 5).to_bytes(4, 'little') + (ibuf * 10).to_bytes(4, 'little') + b'\x02\x00\x10\x00data' + _size.to_bytes(4, 'little') with open(path, 'wb') as f: f.write(_header) while _size > 0: if self.pcm_any(): f.write(self.pcm_read(ibuf)) _size -= ibuf _star = time.ticks_ms() if time.ticks_diff(time.ticks_ms(), _star) > timeout: raise OSError("Timeout write error") self.pcm_en(False) """Graph module""" HEART =b'\x00\x0c\x1e?~\xfc~?\x1e\x0c\x00\x00' HEART_SMALL =b'\x00\x00\x0c\x1e