## 支持MicroPython的手持式嵌入式系统学习平台 {{drawio>lpc55_block.png}} 方案构成框图 ### 1. 主要功能及特性 - 基于NXP的LPC55S69微控制器 - 扩展存储: - 通过SPI连接外部的串行Flash存储器,容量为8MB,能够存储一些文件、音频、图片等 - 数字输入: - 2个按键输入,功能待定 - 一个拨轮开关,相当于3个按键 - 一个姿态传感器,采用QMA7981(国产器件,同Rohm的一款管脚兼容),通过I2C跟控制器连接,并有INT信号用于外部中断 - 数字输出: - 240x240 TFT彩色LCD显示,高速SPI接口,支持背光控制 - 一个LED灯,用于显示程序的运行状态(待定义) - 模拟输入: - 2路模拟电压输入,输入电压的范围为10Vpp,通过10:3.3衰减以后送到MCU的ADC进行量化采集 - 模拟输出: - 1路模拟波形发生器,通过PWM/DDS的方式生成常用的测试波形,比如三角波、正弦波等,输出信号的幅度为最大8Vpp,可调,并可以调整输出信号的直流偏移(在数字域实现) - 2路直流电压输出,通过PWM + LPF得到,输出直流电压的范围为-4V到+4V可调,通过软件可以设置这两路直流电压的关系 - MCU可以工作于内部振荡器模式,同时也提供了一个16MHz的外部晶体 - 板上提供一个SWD插座,由SWDIO、SWCLK、RST、3.3V、GND组成,可以通过外部SWD调试器对MCU进行调试 - 板上提供一个24Pin的双排插座,Pin1-16的管脚信号兼容市面上常用的摄像头模块的管脚定义,可以直接连接摄像头,同时还有其它7根数据线用于其它用途,在不连接摄像头的场景,总计有21根数据线可以用于外设的扩展;在这21根数据线中,有一组完整的SPI总线 - 采用MicroPython编程 ### 2. 相关设计文档 * [[https://micropython.org|MicroPython的官方链接]] * [[https://github.com/micropython|MicroPython在Github上的资源链接]] * [[https://thonny.org|MicroPython的IDE环境Thonny的官方链接]] * {{:rpi_pipico_digital_v10.pdf|树莓派PICO上使用MicroPython}} * [[https://datasheets.raspberrypi.org/pico/raspberry-pi-pico-python-sdk.pdf|RP2040运行MicroPython的SDK说明]] * {{:micropython-docs.pdf|MicroPython的技术规范}} * [[https://www.arducam.com/raspberry-pi-pico-tensorflow-lite-micro-person-detection-arducam/|在树莓派PICO上运行TinyML]] * {{:miniscope_lpc55.pdf|基于LPC55S69的口袋学习系统的原理图PDF}},使用KiCad绘制 * {{ :lpc55instru_t.png |基于LPC55S69的口袋学习系统的效果图 - 正面电路板图}} 基于LPC55S69的口袋学习系统的效果图 - 正面电路板图 * {{ :lpc55instru_b.png |基于LPC55S69的口袋学习系统的效果图 - 背面电路板图}} 基于LPC55S69的口袋学习系统的效果图 - 背面电路板图 * {{:micropython_with_lpc55s69_iot_kit.pdf|梁平老师关于LPC55S69的MicroPython设计指导手册}} ### 3. 相关器件资料 * [[https://www.nxp.com/part/LPC55S69JBD100#/|LPC55S69JBD100的网站链接]] * [[https://www.nxp.com/docs/en/data-sheet/LPC55S6x.pdf|LPC55S69的数据手册]] * [[https://www.qstcorp.com/productinfo/114108.html|3轴姿态传感器QMA7981的网站链接]] * {{:qma7981datasheet.pdf|QMA7981的数据手册}} ### 4. 示例代码 本硬件平台可以在Windows下通过PUTTY或Thony来进行MicroPython的编程,在Mac下只能识别到板上的U盘,可以通过将py文件copy进去的方式让程序执行。 以下是部分功能代码的示例: #### 1. LCD屏幕显示代码 WHITE = const(0xFFFF) BLACK = const(0x0000) BLUE = const(0x001F) BRED = const(0XF81F) GRED = const(0XFFE0) RED = const(0xF800) MAGENTA=const(0xF81F) GREEN = const(0x07E0) CYAN = const(0x7FFF) YELLOW= const(0xFFE0) BROWN = const(0XBC40) BRRED = const(0XFC07) GRAY = const(0X8430) from display import LCD154 from lpc55 import Pin from lpc55 import SPI import lpc55 lcd_spi = lpc55.SPI(8, SPI.MASTER, bits=8, baudrate=25000000, polarity=1, phase=1) lcd_rst=lpc55.Pin('PIO1_3', Pin.OUT, pull=Pin.PULL_UP, value=1) lcd_cs =Pin('PIO0_20', Pin.OUT, pull=Pin.PULL_UP, value=0) lcd = LCD154(lcd_spi, lcd_rst) lcd.clear(BLUE) # 清除屏幕为棕色 fb0 = lcd.getframe() # 获取基础缓冲区 fb0.string(10,100,"Hello World!", color=WHITE, font=32) #### 2. 弹球游戏 WHITE = const(0xFFFF) BLACK = const(0x0000) BLUE = const(0x001F) BRED = const(0XF81F) GRED = const(0XFFE0) RED = const(0xF800) MAGENTA=const(0xF81F) GREEN = const(0x07E0) CYAN = const(0x7FFF) YELLOW= const(0xFFE0) BROWN = const(0XBC40) BRRED = const(0XFC07) GRAY = const(0X8430) BALL_SIZE = 32 # 圆球区域大小 SCREEN_WIDTH = 240 # 屏幕宽度 SCREEN_HEIGHT = 240 # 屏幕高度 X_BORDER = SCREEN_WIDTH - BALL_SIZE # 圆球活动边界 Y_BORDER = SCREEN_HEIGHT - BALL_SIZE # 圆球活动边界 import math import machine import lpc55 from lpc55 import SPI from lpc55 import Pin from display import LCD154 class path(): # Param: width & height of the moving area def __init__(self, width, height): def rand(): # mpy version self.x0 = lpc55.rng() % width # starting point self.y0 = lpc55.rng() % height # starting point self.a0 = 15 + lpc55.rng() % 60 # starting angle self.a0 = self.a0 + 90 * lpc55.rng() % 4 self.a0 = self.a0 * (2 * math.pi) / 360 # Conver to angle def rand3(): # Version for Python3 self.x0 = random.randrange(width) # starting point self.y0 = random.randrange(height) # starting point self.a0 = 30 + random.randrange(30) # starting angle self.a0 = self.a0 + 90 * random.randrange(4) self.a0 = self.a0 * (2 * math.pi) / 360 self.width = width self.height = height rand() self.quadrant(self.a0) self.prev = [self.x0, self.y0] def quadrant(self, angle): self.tan = math.tan(self.a0) self.step = 1 if angle < (math.pi / 2): self.delta = (1, 1) self.pathx = self.tan <= 1 # print("quadrant-1, pathx=%d" % self.pathx) elif angle < math.pi: self.delta = (-1, 1) self.pathx = -self.tan <= 1 # print("quadrant-2, pathx=%d" % self.pathx) elif angle < math.pi * 1.5: self.delta = (-1, -1) self.pathx = self.tan <= 1 # print("quadrant-3, pathx=%d" % self.pathx) else: self.delta = (1, -1) self.pathx = -self.tan <= 1 # print("quadrant-4, pathx=%d" % self.pathx) def next(self): if self.pathx: x1 = self.x0 + self.step * self.delta[0] y1 = self.y0 + self.step * self.delta[0] * self.tan else: x1 = self.x0 + self.step * self.delta[1] / self.tan y1 = self.y0 + self.step * self.delta[1] if y1 < 0 or y1 >= self.height: angle = math.pi * 2 - self.a0 elif x1 < 0 or x1 >= self.width: angle = math.pi * 3 - self.a0 if angle > math.pi*2: angle -= math.pi*2 else: self.prev[0] = int(x1) self.prev[1] = int(y1) self.step += 1 return self.prev self.a0 = angle self.x0 = self.prev[0] self.y0 = self.prev[1] self.quadrant(angle) return None #self.next() class Panel(): def __init__(self, id, rst, cs = None): lcd_spi = lpc55.SPI(id, SPI.MASTER, bits=8, baudrate=50000000, polarity=1, phase=1) lcd_rst=Pin(rst, Pin.OUT, pull=Pin.PULL_UP, value=1) if cs: lcd_cs =Pin(cs, Pin.OUT, pull=Pin.PULL_UP, value=0) self.lcd = LCD154(lcd_spi, lcd_rst) if not cs: self.lcd.orientation(2) self.fb0 = self.lcd.getframe() # Get the basic framebuf self.track = path(X_BORDER, Y_BORDER) def setup(self, color_table): self.desk = self.lcd.framebuf(0, 0, 240, 240, bits=2, lut=color_table) self.desk.clear(2) self.desk.color(0,1) for x in range(20,240,20): self.desk.line(x, 0, x, SCREEN_HEIGHT) for y in range(20,240,20): self.desk.line(0, y, SCREEN_WIDTH, y) self.desk.string(24, 100, "Hello World!", color=3, font=32, mode=1) self.desk.show() self.ball = self.lcd.framebuf(0, 100, 32, 32, bits=1, lut=(BLACK, RED)) self.ball.clear() self.ball.circle(15,15,14,color=1,style=1) def update(self): pos = self.track.next() if pos: self.ball.move(pos[0], pos[1]) self.ball.showwith(self.desk, mode=1) ############################ def timer_cb(x): lcd1.update() lcd2.update() lcd3.update() lcd1 = Panel(6, 'P6_6', 'P6_7') lcd1.setup((BLACK, MAGENTA, YELLOW, BLUE)) lcd2 = Panel(4, 'P5_6', 'P5_7') lcd2.setup((WHITE, BLUE, CYAN, BLACK)) lcd3 = Panel(8,'PIO1_3', 'PIO0_20') lcd3.setup( (WHITE, BLACK, GREEN, BLUE) ) tick = machine.Timer(-1) tick.init(freq=300, callback=timer_cb) #### 3. 示波器波形显示 WHITE = const(0xFFFF) BLACK = const(0x0000) BLUE = const(0x001F) BRED = const(0XF81F) GRED = const(0XFFE0) RED = const(0xF800) MAGENTA=const(0xF81F) GREEN = const(0x07E0) CYAN = const(0x7FFF) YELLOW= const(0xFFE0) BROWN = const(0XBC40) BRRED = const(0XFC07) GRAY = const(0X8430) SCREEN_WIDTH = const(240) # 屏幕宽度 SCREEN_HEIGHT = const(240) # 屏幕高度 SCOPE_HEIGHT = const(200) # 波形区域高度 SCOPE_WIDTH = SCREEN_WIDTH-10 SCOPE_GRID = 30 WAVE_NUM = const(5) # 波形数目 WAVE_HEIGHT = SCOPE_HEIGHT//WAVE_NUM import math import lpc55 from lpc55 import SPI from lpc55 import Pin from display import LCD154 class Panel(): def __init__(self, id, rst, cs): lcd_spi = lpc55.SPI(id, SPI.MASTER, baudrate=50000000, polarity=1, phase=1) lcd_rst=Pin(rst, Pin.OUT, pull=Pin.PULL_UP, value=1) if cs: lcd_cs =Pin(cs, Pin.OUT, pull=Pin.PULL_UP, value=0) self.lcd = LCD154(lcd_spi, lcd_rst) if not cs: self.lcd.orientation(2) # This LCD is placed up side down self.fb0 = self.lcd.getframe() # Get the basic framebuf self.fb0.clear(BROWN) self.fb0.string(18,0,"Oscilloscope DEMO", color=BLACK, font=24,mode=1) # Top banner def setup(self, color_table): BANNER_HEIGHT = 24 Scope_x = (SCREEN_WIDTH-SCOPE_WIDTH)//2 self.ss = self.lcd.scope(Scope_x,BANNER_HEIGHT,SCOPE_WIDTH,SCOPE_HEIGHT, bits=3,lut=color_table) self.ss.clear(0) self.ss.color(5,2) y_low_banner = BANNER_HEIGHT + SCOPE_HEIGHT self.fb0.line(0, y_low_banner, SCREEN_WIDTH, y_low_banner, size=16, color=0xE79C) # 绘制下面的刻度条 for x in range(SCOPE_GRID, SCOPE_WIDTH, SCOPE_GRID): self.ss.line(x, 0, x, SCOPE_HEIGHT, color=6) marker_x = Scope_x + x - len(str(x)) * 4 # 显示刻度字体 self.fb0.string(marker_x, y_low_banner, str(x), font=16, color=0x1C, mode=1) for y in range(SCOPE_GRID, SCOPE_HEIGHT, SCOPE_GRID): self.ss.line(0, y, SCOPE_WIDTH, y, color=6) self.ss.string(48,10,"Hello World!", font=24) # 字体居上 self.ss.show() # Create channels WAVE_COLOR = (RED, GREEN, BLUE, YELLOW, MAGENTA) self.channel = [0,]*WAVE_NUM for ch in range(WAVE_NUM): self.channel[ch] = self.ss.channel(ch + 1) self.channel[ch].init(WAVE_HEIGHT//2 + ch * WAVE_HEIGHT, WAVE_COLOR[ch], offset=0, ratio=WAVE_HEIGHT/2-1) # lcd1 = Panel(4, 'P5_6', 'P5_7') lcd1 = Panel(8,'PIO1_3','PIO0_20') # lcd3 = Panel(6, 'P6_6', 'P6_7') lcd1.setup((BLACK, WHITE, RED, GREEN, BLUE, YELLOW, 0x300, MAGENTA)) # lcd2.setup((BLACK, WHITE, RED, GREEN, BLUE, YELLOW, 0x300, MAGENTA)) # lcd3.setup((BLACK, WHITE, RED, GREEN, BLUE, YELLOW, 0x300, MAGENTA)) Samples = 720 STEP=const(4) # 每次刷新,需要显示的数据数目 wave = [0,]*STEP while True: for x in range(0, Samples, STEP): # start = lpc55.millis() delta = math.pi/120 for i in range(x, x+STEP): wave[i-x] = math.sin(i*delta) for ch in range(WAVE_NUM): lcd1.channel[ch].wave(wave) # lcd2.channel[ch].wave(wave) delta += math.pi/90 for i in range(x, x+STEP): wave[i-x] = wave[i-x] + math.sin(i*delta) # print(lpc55.elapsed_millis(start), end='\t') lcd1.ss.refresh() # print(lpc55.elapsed_millis(start), end='\t') # lcd2.ss.refresh() # print(lpc55.elapsed_millis(start)) # lcd3.ss.refresh() ### 5. 界面显示示例效果图 {{drawio>instru240.png}} ### 6. 要实现的功能 设计一个控制/菜单显示的界面,实现一款多功能口袋仪器的功能: - 可调输出直流电压 - 通过设置PWM的占空比来调节输出的直流电压值,可以调节每一路的输出从-4V到+4V之间变化 - 可编程任意波形发生器 - 设置波形(正弦波、三角波、锯齿波、方波)、设置输出信号的频率(精确到1Hz)、设置输出信号的幅度为最大8Vpp(-4V 到+4V),幅度调节精度为10mV - 双通道示波器:可以调节横轴显示分度 - s/div、纵轴显示分度v/div、自动触发、开机自动校准、自动显示被测信号的频率/周期、峰峰值、平均值,测量范围为外部峰峰值最高10Vpp的模拟信号,模拟信号的带宽上限为100KHz;可以将上述可编程任意波形发生器产生的信号通过杜邦线连接到示波器的输入端作为测试信号源; - 对示波器采集到的信号能够进行FFT变换,并将变换后的频谱显示在屏幕上,做到波形和频谱能够切换显示