【CanMV K210】显示交互 触摸屏画图与 LCD 轨迹绘制

在智能硬件项目中,触摸屏经常承担"输入"和"显示"两个角色。电子画板、设备配置面板、手写签名、交互式控制台、工业设备调试界面,都需要把手指触摸的位置转换成程序能够处理的数据,再通过屏幕反馈成可见图形。对于 Python 硬件编程入门而言,触摸屏画图实验很适合作为图形交互的起点,因为它能直观看到坐标读取、状态判断、图像绘制和屏幕刷新之间的关系。

本实验基于 CanMV K210 开发板实现一个简单的触摸屏画图 Demo。程序会初始化 LCD 显示屏和 I2C 触摸屏,手指按下或移动时,屏幕上会绘制红色触摸点和白色连续轨迹。按下 BOOT_KEY 后,程序会清空画布并重新绘制顶部状态栏。整个实验不是简单显示一张图片,而是让代码持续读取触摸状态,把真实手势转化成屏幕上的线条。

学习目标 说明
理解图形交互 认识触摸输入、图像绘制和 LCD 显示之间的关系
掌握触摸坐标读取 通过 I2C 触摸屏读取触摸状态、X 坐标和 Y 坐标
学会 LCD 画布绘制 使用 image.Image() 创建画布,并绘制文字、圆点和线条
理解轨迹绘制逻辑 通过保存上一帧坐标,把离散触摸点连接成连续轨迹
掌握按键清屏 使用 BOOT_KEY 作为清空画布的输入按钮
建立交互项目思路 为触摸菜单、参数面板、手写输入和硬件控制界面打基础

这个实验的重点不只是"能在屏幕上画线",而是建立触摸屏交互的基本流程:触摸屏负责输入坐标,程序负责判断状态和绘制图形,LCD 负责显示结果,BOOT_KEY 负责触发清屏操作。后续的触摸菜单、屏幕按钮、参数调节界面和 AI 识别结果标注,都可以复用这套结构。

文章目录

理论基础

触摸屏实验同时包含输入和输出两个方向。输入来自触摸屏,程序需要读取手指是否按下、是否移动,以及当前触摸坐标;输出来自 LCD,程序需要把点、线、文字和背景画到图像画布上,再刷新到屏幕显示。这个过程比普通 LED 或按键实验更接近真实的人机交互系统。

LCD 本身主要负责显示,不能直接理解触摸动作。触摸屏则通过 I2C 总线向 K210 提供触摸状态和坐标数据。程序读取到坐标后,并不是直接把坐标显示出来,而是根据状态判断是否需要绘制点或线。当触摸状态为按下或移动时,程序会在当前位置画一个红色小点;如果已经存在上一帧坐标,就把上一帧坐标和当前坐标连成白色线段,从而形成连续轨迹。

BOOT_KEY 在本实验中作为清屏按钮使用。它不参与触摸轨迹绘制,只负责触发 clear_canvas()。按键按下后,程序会清空当前画布,然后重新绘制顶部提示栏。这个设计让触摸屏负责绘图输入,BOOT_KEY 负责功能控制,LCD 负责统一显示,形成一个完整的交互闭环。
手指触摸屏幕

按下 / 移动 / 松开
I2C 触摸屏

读取状态与坐标
CanMV K210

IO30=SCL / IO31=SDA
ts.read()

返回 status, x, y
状态判断

PRESS / MOVE / IDLE
image 画布绘制

点 / 线 / 顶部提示栏
lcd.display(img)

刷新到 LCD
BOOT_KEY

IO16
GPIO1 输入检测
clear_canvas()

清空画布并重绘提示栏

这张流程图展示的是触摸屏画图实验的控制链路。触摸屏通过 I2C 把状态和坐标传给 K210,程序根据坐标在 image 画布上绘制图形,再通过 LCD 显示出来。BOOT_KEY 作为额外输入,负责触发清屏逻辑。整个实验体现了"输入事件 -> 程序处理 -> 图形反馈"的交互过程。

轨迹绘制的关键是"上一点"和"当前点"。如果每次触摸只画一个圆点,手指快速移动时屏幕上会出现一串分散的点,轨迹不够连续。代码中使用 last_xlast_yhas_last_point 记录上一帧触摸位置,当新的触摸点到来时,程序用 draw_line() 把两个点连接起来。这样手指移动时,屏幕上就会形成连续线条。

硬件设施

本实验围绕 LCD 显示屏、I2C 触摸屏和 BOOT_KEY 按键展开。LCD 负责显示画布和触摸轨迹,触摸屏通过 I2C 总线向 K210 提供触摸状态与坐标数据,BOOT_KEY 作为清空画布的输入按钮。软件侧主要使用 lcdimagetouchscreenmachine.I2Cmaix.GPIOfpioa_manager.fm,分别负责显示刷新、图像绘制、触摸读取、I2C 通信、按键输入和引脚功能映射。

实验接线和运行效果可以通过下面的图片建立整体印象。触摸屏负责输入坐标,LCD 负责显示轨迹,BOOT_KEY 用于清空画布。检查硬件时重点关注触摸屏 I2C 通信、LCD 显示是否正常,以及 BOOT_KEY 是否能被程序读取。

硬件 / 软件 作用 说明
CanMV K210 开发板 实验运行平台 负责执行 Python 程序,连接 LCD、触摸屏和 BOOT_KEY
LCD 显示屏 图像输出设备 显示顶部提示信息、触摸点和连续轨迹
I2C 触摸屏 坐标输入设备 通过触摸状态、X 坐标和 Y 坐标反馈手指操作
BOOT_KEY 清空画布按钮 按下后触发 clear_canvas(),用于重新开始绘制
IO30 I2C 时钟线 根据代码和注释推导,对应触摸屏 SCL
IO31 I2C 数据线 根据代码和注释推导,对应触摸屏 SDA
IO16 按键输入引脚 根据代码推导,注册为 GPIO1 后读取 BOOT_KEY 状态
lcd 屏幕控制模块 用于初始化屏幕、清屏和显示图像
image 图像绘制模块 创建画布,并绘制矩形、文字、圆点和线条
touchscreen 触摸屏模块 初始化触摸屏并读取触摸状态和坐标
machine.I2C I2C 通信模块 创建触摸屏通信总线
maix.GPIO GPIO 控制模块 用于读取 BOOT_KEY 的按下状态
fpioa_manager.fm 引脚映射模块 将物理引脚 IO16 注册为 GPIO1

接线关系可以从代码中的 I2C 配置、按键注册和注释说明中推导出来。触摸屏通过 I2C 与 K210 通信,SCL 对应 IO30,SDA 对应 IO31。BOOT_KEY 使用 IO16,并通过 fm.register(CLEAR_KEY_PIN, CLEAR_KEY_GPIO, force=True) 注册为 GPIO1。LCD 的具体硬件连接没有在代码中展开,程序通过 lcd.init()lcd.display(img) 直接调用显示接口。

物理引脚 / 接口 功能引脚 / 总线 代码变量 对应硬件 说明
IO30 I2C SCL I2C_SCL_PIN 触摸屏 SCL 用作触摸屏 I2C 时钟线
IO31 I2C SDA I2C_SDA_PIN 触摸屏 SDA 用作触摸屏 I2C 数据线
IO16 GPIO1 btn_clear BOOT_KEY 按下后用于清空画布
LCD 显示接口 LCD 控制接口 lcdimg LCD 屏幕 通过 LCD 模块接口完成显示
I2C0 触摸通信总线 TOUCH_I2C_ID 触摸屏通信 用于读取触摸状态和坐标
VCC / GND 供电与地线 LCD 与触摸屏 保证屏幕和触摸通信正常工作

物理引脚可以理解成开发板连接真实硬件的入口,功能引脚则是程序对这些入口赋予的角色。IO30 和 IO31 被用于 I2C 通信,负责让 K210 与触摸屏交换坐标数据;IO16 被注册成 GPIO1,负责读取按键是否被按下;LCD 显示部分由 lcd 模块统一管理,程序只需要把绘制好的 img 画布刷新到屏幕上。

实验现象 正常表现 异常提示
程序启动 LCD 显示顶部提示栏,画布区域为空 无显示时检查 LCD 初始化和程序运行状态
手指按下 触摸位置出现红色小点 无触摸响应时检查 I2C 触摸屏初始化
手指移动 屏幕显示白色连续轨迹 轨迹断续时检查刷新频率和触摸采样速度
手指松开 轨迹停止延伸,下一次触摸重新记录起点 松开后仍乱画时检查触摸状态读取
按下 BOOT_KEY 画布清空,顶部提示栏保留 无法清屏时检查 IO16 和按键电平
顶部区域无法画线 y 坐标小于等于 30 时不绘制轨迹 这是为了保留状态栏,不属于故障

软件代码

本实验代码围绕触摸屏画图功能展开。程序先配置 LCD 尺寸、I2C 引脚、清屏按键、颜色参数和防抖时间,再分别初始化 LCD、触摸屏、BOOT_KEY 和画布。主循环中不断读取触摸状态,触摸移动时绘制连续线条,按键按下时清空画布,并持续刷新 LCD 显示。

软件环境 作用 检查重点
CanMV IDE 编辑、运行和调试 K210 程序 能识别开发板串口,并能正常运行基础测试程序
CanMV 固件 提供 lcdimagetouchscreenmachine.I2C 等模块 固件环境需要支持 LCD 和触摸屏相关接口
LCD 显示模块 显示画布和轨迹 程序启动后能看到顶部提示栏
I2C 触摸屏模块 返回触摸状态和坐标 手指按下或移动时坐标会变化
BOOT_KEY 清空画布输入 按下后能触发清屏
串口终端 查看触摸状态变化 状态变化时能看到 Touch Status 输出
python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
CanMV K210 触摸屏画图实验 Demo

功能说明:
1. 初始化 LCD 显示屏
2. 初始化 I2C 触摸屏
3. 手指按下或移动时,在屏幕上绘制连续轨迹
4. 按下 BOOT_KEY 清空画布
5. 屏幕顶部显示简单状态提示

接线说明:
触摸屏通常通过 I2C 与 K210 通信
SCL -> IO30
SDA -> IO31
BOOT_KEY -> IO16
"""

import time
import lcd
import image
import touchscreen as ts

from machine import I2C
from maix import GPIO
from fpioa_manager import fm


# =========================
# 硬件配置区
# =========================

LCD_WIDTH = 320
LCD_HEIGHT = 240

I2C_SCL_PIN = 30
I2C_SDA_PIN = 31

CLEAR_KEY_PIN = 16
CLEAR_KEY_GPIO = fm.fpioa.GPIO1

TOUCH_I2C_ID = I2C.I2C0
TOUCH_I2C_FREQ = 400000

PEN_COLOR = (255, 255, 255)
TEXT_COLOR = (0, 255, 0)
POINT_COLOR = (255, 0, 0)
BG_COLOR = (0, 0, 0)

CLEAR_DEBOUNCE_MS = 300


# =========================
# 全局状态
# =========================

img = None
btn_clear = None

last_x = 0
last_y = 0
last_status = ts.STATUS_IDLE
has_last_point = False
last_clear_time = 0


# =========================
# 初始化函数
# =========================

def init_lcd():
    """初始化 LCD 显示屏"""
    lcd.init()
    lcd.clear()


def init_touchscreen():
    """初始化触摸屏"""
    i2c = I2C(
        TOUCH_I2C_ID,
        freq=TOUCH_I2C_FREQ,
        scl=I2C_SCL_PIN,
        sda=I2C_SDA_PIN
    )

    ts.init(i2c)

    # 首次使用触摸屏位置不准确时,可以取消下面这一行注释进行校准
    # ts.calibrate()


def init_clear_button():
    """初始化清空按钮"""
    global btn_clear

    fm.register(CLEAR_KEY_PIN, CLEAR_KEY_GPIO, force=True)
    btn_clear = GPIO(GPIO.GPIO1, GPIO.IN)


def create_canvas():
    """创建画布"""
    global img

    img = image.Image(size=(LCD_WIDTH, LCD_HEIGHT))
    clear_canvas()


def init_hardware():
    """统一初始化硬件"""
    init_lcd()
    init_touchscreen()
    init_clear_button()
    create_canvas()


# =========================
# 画布函数
# =========================

def draw_header():
    """绘制顶部提示信息"""
    img.draw_rectangle(0, 0, LCD_WIDTH, 28, color=(20, 20, 20), fill=True)
    img.draw_string(8, 8, "Touch Draw Demo", color=TEXT_COLOR, scale=1)
    img.draw_string(180, 8, "BOOT: Clear", color=TEXT_COLOR, scale=1)


def clear_canvas():
    """清空画布"""
    global has_last_point

    img.clear()
    draw_header()
    has_last_point = False
    lcd.display(img)


def draw_touch_point(x, y):
    """绘制当前触摸点"""
    if y > 30:
        img.draw_circle(x, y, 2, color=POINT_COLOR, fill=True)


def draw_touch_line(x1, y1, x2, y2):
    """绘制触摸轨迹线"""
    if y1 > 30 and y2 > 30:
        img.draw_line((x1, y1, x2, y2), color=PEN_COLOR)


# =========================
# 触摸处理函数
# =========================

def is_touching(status):
    """判断当前是否处于触摸状态"""
    return status == ts.STATUS_PRESS or status == ts.STATUS_MOVE


def handle_touch():
    """读取触摸数据并绘制轨迹"""
    global last_x, last_y, last_status, has_last_point

    status, x, y = ts.read()

    if is_touching(status):
        if has_last_point:
            draw_touch_line(last_x, last_y, x, y)
        else:
            has_last_point = True

        draw_touch_point(x, y)

        last_x = x
        last_y = y

    else:
        has_last_point = False

    if status != last_status:
        print("Touch Status:", status, "X:", x, "Y:", y)
        last_status = status


# =========================
# 按键处理函数
# =========================

def handle_clear_button():
    """检测 BOOT_KEY 是否被按下,按下后清空画布"""
    global last_clear_time

    now = time.ticks_ms()

    if btn_clear.value() == 0:
        if time.ticks_diff(now, last_clear_time) > CLEAR_DEBOUNCE_MS:
            print("Canvas cleared")
            clear_canvas()
            last_clear_time = now


# =========================
# 主循环
# =========================

def loop():
    """主循环"""
    while True:
        handle_touch()
        handle_clear_button()
        lcd.display(img)
        time.sleep_ms(10)


# =========================
# 程序入口
# =========================

if __name__ == '__main__':
    try:
        init_hardware()
        loop()
    except KeyboardInterrupt:
        lcd.clear()
        print("Program stopped")

这段程序可以分成硬件配置、全局状态、初始化函数、画布函数、触摸处理函数、按键处理函数和主循环。硬件配置区集中保存 LCD 尺寸、I2C 引脚、清屏按键、颜色参数和防抖时间;初始化函数负责准备 LCD、触摸屏、BOOT_KEY 和画布;画布函数负责绘制顶部提示栏、清空画面、绘制点和线;触摸处理函数负责读取坐标并更新轨迹;按键处理函数负责检测清屏操作;主循环负责持续刷新。

代码中所有图形都先绘制到 img 画布中,再通过 lcd.display(img) 显示到屏幕。这样做的好处是绘制逻辑比较集中,后续增加文字、按钮、边框或状态提示时,都可以继续在同一张画布上操作。顶部提示栏通过 draw_header() 绘制,画图区域通过 y > 30 限制,避免手指在顶部区域绘图时覆盖提示文字。

函数名 功能 对应现象
init_lcd() 初始化 LCD 显示屏 屏幕进入可显示状态,并清理旧画面
init_touchscreen() 初始化 I2C 触摸屏 触摸屏可以返回触摸状态和坐标
init_clear_button() 初始化 BOOT_KEY 按键可以被程序读取,用于清空画布
create_canvas() 创建图像画布 生成一张 320×240 的绘图区域
init_hardware() 统一初始化硬件 程序启动后完成屏幕、触摸和按键准备
draw_header() 绘制顶部提示栏 屏幕顶部显示 Demo 名称和清屏提示
clear_canvas() 清空画布 轨迹被清除,顶部提示栏重新出现
draw_touch_point() 绘制触摸点 手指触摸位置出现红色小点
draw_touch_line() 绘制触摸轨迹 手指移动路径形成白色连续线条
is_touching() 判断触摸状态 区分按下、移动和空闲状态
handle_touch() 读取触摸并绘图 手指按下或移动时,屏幕显示轨迹
handle_clear_button() 检测清屏按键 按下 BOOT_KEY 后清空画布
loop() 持续执行主循环 程序不断读取触摸、检测按键并刷新屏幕

handle_touch() 是轨迹绘制的核心。ts.read() 返回触摸状态、X 坐标和 Y 坐标,当状态为按下或移动时,程序会判断是否已经有上一个触摸点。如果有,就把上一点和当前点连成线;如果没有,就从当前点开始记录。手指松开后,has_last_point 会被重置,下一次触摸会重新开始新的轨迹,避免不同笔画之间被错误连线。

handle_clear_button() 使用了按键防抖逻辑。BOOT_KEY 按下时,程序先判断距离上一次清屏是否超过 CLEAR_DEBOUNCE_MS。只有超过 300 毫秒,才执行清屏操作。这样可以减少机械按键抖动造成的重复清屏。主循环每 10 毫秒运行一次,既能保持较好的触摸响应速度,也能避免程序无意义地高速空转。

扩展应用

触摸屏画图实验同时涉及显示、I2C 通信、触摸坐标、按键输入和刷新频率。排查时需要把问题拆开看,屏幕不显示优先检查 LCD 初始化,触摸无反应优先检查 I2C 和触摸屏初始化,清屏无效优先检查 BOOT_KEY 引脚和电平逻辑。

问题现象 可能原因 处理思路
LCD 没有显示内容 LCD 初始化失败、屏幕连接异常、程序没有运行 检查 lcd.init() 是否正常执行,确认程序已经进入主循环
触摸没有轨迹 I2C 引脚不匹配、触摸屏没有初始化成功、触摸模块异常 核对 IO30 和 IO31 是否对应 SCL、SDA,检查 ts.init(i2c) 是否报错
轨迹位置偏移 触摸屏校准数据不准确 取消 ts.calibrate() 注释进行校准,再重新测试坐标位置
顶部区域无法画线 代码限制 y > 30 才允许绘图 这是为了保留顶部提示栏,属于程序设计逻辑
按下 BOOT_KEY 没有清屏 IO16 映射不正确、按键电平逻辑不匹配、按键没有接通 检查 fm.register()GPIO.GPIO1 是否一致,观察 btn_clear.value() 的返回值
按键触发多次清屏 按键抖动或防抖时间过短 适当增大 CLEAR_DEBOUNCE_MS,例如从 300 调整到 500
画线不连续 循环刷新太慢、触摸采样间隔过大、手指移动速度太快 适当减小 time.sleep_ms(10),或降低手指移动速度进行测试
画面刷新有闪烁感 LCD 刷新频率与绘图频率不匹配 保持画布绘制后统一刷新,避免在多个函数里频繁刷新屏幕
串口持续输出较少 代码只在触摸状态变化时打印状态 这是正常设计,持续移动时主要观察 LCD 轨迹变化

触摸屏画图实验的价值不只在于做一个简易电子画板,更重要的是建立图形交互思维。程序从触摸屏读取坐标,再把坐标转换成图形、线条和状态变化,这个过程可以迁移到很多实际项目中。设备参数设置、手写签名、交互菜单、坐标调试、轨迹采集和屏幕控制面板,本质上都可以建立在同样的触摸读取与图像绘制逻辑之上。

应用场景 实现思路 可扩展能力
电子画板 继续使用触摸坐标绘制点和线 可增加画笔颜色、线宽、橡皮擦和保存图片功能
手写签名采集 将触摸轨迹记录为坐标序列或图像 可扩展为签名确认、轨迹存档和身份验证实验
设备触控面板 把屏幕区域划分为不同按钮 可扩展菜单切换、参数设置和模式选择
触摸坐标调试 在屏幕上显示触摸点位置 可用于校准屏幕、检查坐标方向和识别触摸区域
简易白板工具 在 LCD 上绘制文字、线条和提示信息 可扩展为教学演示、会议草图或调试标记工具
轨迹采集实验 保存手指移动路径的坐标变化 可扩展为手势识别、运动轨迹分析和交互实验
智能硬件控制入口 使用触摸区域触发不同动作 后续可扩展蜂鸣器、LED、电机或传感器联动
AI 识别结果标注 在摄像头画面上叠加触摸标记 可扩展为目标选择、区域标注和交互式识别

从工程结构看,当前代码已经具备较好的扩展基础。硬件初始化被单独封装,画布操作和触摸处理相互独立,清屏按键也被拆成单独函数。新增功能时,可以围绕 handle_touch() 增加触摸区域判断,也可以围绕 draw_touch_line() 扩展画笔样式,还可以在 clear_canvas() 中加入更多界面元素。后续接入 LED、蜂鸣器、电机或传感器时,触摸屏就可以从"画图工具"升级成"硬件控制入口"。

总结

本实验通过 CanMV K210 完成了一个触摸屏画图 Demo,核心能力包括 LCD 初始化、I2C 触摸屏通信、GPIO 按键输入、图像画布创建、坐标读取、状态判断、线条绘制、屏幕刷新和按键防抖。代码把真实触摸动作转换成坐标数据,再通过 image 模块绘制成可见轨迹,完整展示了 Python 程序处理硬件交互的过程。

这类实验非常适合作为图形化硬件交互入门案例。触摸状态对应程序分支,坐标变化对应屏幕图形,按键输入对应画布清空,主循环负责把这些动作持续串联起来。理解这套流程后,后续可以继续扩展到触摸菜单、参数配置界面、手势识别、传感器数据显示、摄像头图像标注和 AI 识别交互等方向。触摸屏不只是显示模块,而是智能硬件项目中非常重要的人机交互入口。

相关推荐
Zfox_1 小时前
【LangGraph】持久化(Persistence)
开发语言·人工智能·redis·langchain·ai编程·langgraph
_Evan_Yao1 小时前
计算机大一新生如何选择方向(前端/后端/AI/运维)?
运维·前端·人工智能·后端
通信仿真爱好者1 小时前
【无标题】
人工智能·算法·机器学习
70asunflower1 小时前
7.3 分类 —— 预测一个类别
人工智能·分类·数据挖掘·数据分析
web守墓人1 小时前
【深度学习】Pytorch gpu加速原理探究
人工智能·pytorch·深度学习
落叶无情1 小时前
从语义驱动到认知架构驱动:论ICEF框架对AI认知能力的系统化重构
人工智能
落羽的落羽2 小时前
【算法札记】练习 | Week3
linux·服务器·数据结构·c++·人工智能·算法·动态规划
HackTwoHub2 小时前
网络设备基线检查AI工具、内置专业基线库批量配置合规检测、自动生成安全整改报告
人工智能·安全·web安全·网络安全·系统安全·安全架构
147API2 小时前
GPT 上线指标怎么设计:采纳率、错误率和调用成本
人工智能·gpt