#!/usr/bin/env python3 """ LP5562EVM USB-I2C bridge control tool. Controls the LP5562 LED driver via the EVM's MSP430 USB-to-I2C bridge. The MSP430 presents as a CDC serial device (/dev/ttyACM0). Protocol (reverse-engineered): Read: I → "SS DD OK\n" (SS=00 success, DD=data hex) Write: O → "SS OK\n" (SS=00 success) Info: ? → "TI \n" Prerequisites: - LP5562 EN pin must be held high (jumper EN to VDD on EVM P1/P6 header). The MSP430 does NOT drive EN after reset. - CONFIG register (0x0B) is write-only via this bridge — writes take effect but reads always return 0x00. Internal oscillator must be explicitly set. - W_CURRENT (0x09) cannot be written via this bridge (LP5562 quirk or MSP430 limitation). White channel uses reset default (0xAF = 17.5mA). Usage: ./lp5562_evm.py dump # Read all registers ./lp5562_evm.py init # Enable chip + internal clock + direct PWM ./lp5562_evm.py color R G B W # Set RGBW PWM values (0-255) ./lp5562_evm.py off # All LEDs off ./lp5562_evm.py cycle # Cycle through RGBW channels ./lp5562_evm.py breathe # Breathing pattern demo """ import serial import sys import time PORT = '/dev/ttyACM0' BAUD = 115200 LP5562_ADDR = 0x30 # LP5562 register map REG_ENABLE = 0x00 REG_OP_MODE = 0x01 REG_B_PWM = 0x02 REG_G_PWM = 0x03 REG_R_PWM = 0x04 REG_W_PWM = 0x05 REG_B_CURRENT = 0x06 REG_G_CURRENT = 0x07 REG_R_CURRENT = 0x08 REG_W_CURRENT = 0x09 REG_CONFIG = 0x0B REG_STATUS = 0x0C REG_RESET = 0x0D REG_INT_GPO = 0x0E REG_LED_MAP = 0x70 REG_NAMES = { 0x00: 'ENABLE', 0x01: 'OP_MODE', 0x02: 'B_PWM', 0x03: 'G_PWM', 0x04: 'R_PWM', 0x05: 'W_PWM', 0x06: 'B_CURRENT', 0x07: 'G_CURRENT', 0x08: 'R_CURRENT', 0x09: 'W_CURRENT', 0x0B: 'CONFIG', 0x0C: 'STATUS', 0x0D: 'RESET', 0x0E: 'INT_GPO', 0x70: 'LED_MAP', } # ENABLE register bits CHIP_EN = 0x40 LOG_EN = 0x80 # CONFIG register bits CLK_INT = 0x01 # Internal oscillator # OP_MODE: engine modes (2 bits each: [7:6]=E3, [5:4]=E2, [3:2]=E1) MODE_DISABLED = 0b00 MODE_LOAD = 0b01 MODE_RUN = 0b10 MODE_DIRECT = 0b11 class LP5562EVM: def __init__(self, port=PORT, baud=BAUD): self.ser = serial.Serial(port, baud, timeout=0.5) time.sleep(0.1) self.ser.reset_input_buffer() def close(self): self.ser.close() def version(self): self.ser.reset_input_buffer() self.ser.write(b'?') time.sleep(0.2) return self.ser.read(256).decode('ascii', errors='replace').strip() def read(self, reg): """Read LP5562 register. Returns data byte or raises on error.""" self.ser.reset_input_buffer() self.ser.write(f'I{LP5562_ADDR:02X}{reg:02X}'.encode()) time.sleep(0.15) resp = self.ser.read(256).decode('ascii', errors='replace').strip() parts = resp.split() if len(parts) < 3 or parts[-1] != 'OK': raise IOError(f"Read reg 0x{reg:02X} failed: {resp}") status = int(parts[0], 16) if status != 0: raise IOError(f"I2C NAK reading reg 0x{reg:02X} (status={status:02X}). Is EN pin high?") return int(parts[1], 16) def write(self, reg, val): """Write LP5562 register.""" self.ser.reset_input_buffer() self.ser.write(f'O{LP5562_ADDR:02X}{reg:02X}{val:02X}'.encode()) time.sleep(0.15) resp = self.ser.read(256).decode('ascii', errors='replace').strip() parts = resp.split() if not parts or parts[-1] != 'OK': raise IOError(f"Write reg 0x{reg:02X}=0x{val:02X} failed: {resp}") status = int(parts[0], 16) if status != 0: raise IOError(f"I2C NAK writing reg 0x{reg:02X} (status={status:02X}). Is EN pin high?") def dump(self): """Read and display all registers.""" print("LP5562 Register Dump") print("=" * 40) for reg in sorted(REG_NAMES.keys()): try: val = self.read(reg) name = REG_NAMES[reg] print(f" 0x{reg:02X} {name:12s} = 0x{val:02X} ({val:3d}) {val:08b}") except IOError as e: print(f" 0x{reg:02X} {REG_NAMES[reg]:12s} = ERROR: {e}") def reset(self): """Software reset the LP5562.""" self.write(REG_RESET, 0xFF) time.sleep(0.5) def init(self, current_ma=5.0): """Initialize LP5562 for direct PWM control.""" # Reset to known state self.write(REG_RESET, 0xFF) time.sleep(0.5) # Enable chip self.write(REG_ENABLE, CHIP_EN) time.sleep(0.001) # >500us startup # Internal oscillator (write-only: reads back as 0x00, but takes effect) self.write(REG_CONFIG, CLK_INT) # All LEDs mapped to I2C direct control self.write(REG_LED_MAP, 0x00) # Set current for B/G/R (0-255 maps to 0-25.5mA, so 10 = 1mA) # W_CURRENT (0x09) is not writable via EVM bridge — uses default 0xAF current_val = min(255, max(0, int(current_ma * 10))) self.write(REG_B_CURRENT, current_val) self.write(REG_G_CURRENT, current_val) self.write(REG_R_CURRENT, current_val) print(f"LP5562 initialized: chip_en, internal osc, {current_ma:.1f}mA/ch (W=17.5mA default)") def set_color(self, r=0, g=0, b=0, w=0): """Set RGBW PWM values (0-255).""" self.write(REG_R_PWM, r & 0xFF) self.write(REG_G_PWM, g & 0xFF) self.write(REG_B_PWM, b & 0xFF) self.write(REG_W_PWM, w & 0xFF) def off(self): """All LEDs off.""" self.set_color(0, 0, 0, 0) def cmd_dump(evm, args): ver = evm.version() print(f"EVM firmware: {ver}") print() evm.dump() def cmd_init(evm, args): current = float(args[0]) if args else 5.0 evm.init(current_ma=current) evm.dump() def cmd_color(evm, args): if len(args) < 3: print("Usage: color R G B [W]") return r, g, b = int(args[0]), int(args[1]), int(args[2]) w = int(args[3]) if len(args) > 3 else 0 evm.set_color(r, g, b, w) print(f"Set color: R={r} G={g} B={b} W={w}") def cmd_off(evm, args): evm.off() print("All LEDs off") def cmd_cycle(evm, args): step = float(args[0]) if args else 0.5 pwm = 255 print(f"Cycling RGBW channels ({step}s per step). Ctrl+C to stop.") try: while True: evm.set_color(0, 0, pwm, 0) # Blue print(" Blue") time.sleep(step) evm.set_color(0, pwm, 0, 0) # Green print(" Green") time.sleep(step) evm.set_color(pwm, 0, 0, 0) # Red print(" Red") time.sleep(step) evm.set_color(0, 0, 0, pwm) # White print(" White") time.sleep(step) evm.set_color(pwm, pwm, pwm, pwm) # All print(" All on") time.sleep(step) evm.off() print(" Off") time.sleep(step) except KeyboardInterrupt: evm.off() print("\nStopped.") def cmd_breathe(evm, args): """Software breathing effect (ramp PWM up and down).""" print("Breathing pattern on blue channel. Ctrl+C to stop.") try: while True: # Ramp up for i in range(0, 256, 4): evm.set_color(0, 0, i, 0) time.sleep(0.02) # Ramp down for i in range(255, -1, -4): evm.set_color(0, 0, max(0, i), 0) time.sleep(0.02) time.sleep(0.3) except KeyboardInterrupt: evm.off() print("\nStopped.") COMMANDS = { 'dump': cmd_dump, 'init': cmd_init, 'color': cmd_color, 'off': cmd_off, 'cycle': cmd_cycle, 'breathe': cmd_breathe, } def main(): if len(sys.argv) < 2 or sys.argv[1] not in COMMANDS: print("Usage: lp5562_evm.py [args...]") print() print("Commands:") print(" dump Read all registers") print(" init [mA] Enable chip, set current (default 5mA)") print(" color R G B [W] Set RGBW PWM (0-255)") print(" off All LEDs off") print(" cycle [sec] Cycle through RGBW (default 0.5s)") print(" breathe Software breathing pattern") sys.exit(1) evm = LP5562EVM() try: COMMANDS[sys.argv[1]](evm, sys.argv[2:]) finally: evm.close() if __name__ == '__main__': main()