物联网嵌入式【免费下载链接】nodemcu-firmwareLua based interactive firmware for ESP8266, ESP8285 and ESP32项目地址https://gitcode.com/gh_mirrors/no/nodemcu-firmware点击查看免费下载NodeMCU 固件为 ESP8266/ESP8285 提供了内置的xpt2046Lua 模块用于驱动 XPT2046 电阻式触摸控制器——该控制器常见于多种廉价 TFT 显示屏上通常与 ILI9341 显示控制器搭配使用。本文以 docs/modules/xpt2046.md 为骨架结合 app/modules/xpt2046.c 源码完整讲解从 SPI 初始化、模块启用、触摸校准到坐标读取与中断回调的整套实战流程读完即可在你的 NodeMCU 项目中实现屏幕显示 触摸交互。一、模块概览与适用场景XPT2046 是一款 4 线制电阻式触摸屏控制器通过 SPI 接口与主控通信输出 12 位精度的原始坐标。在 NodeMCU 固件中xpt2046模块将其封装为一组简洁的 Lua API让你不必关心底层时序即可完成触摸检测isTouched屏幕像素坐标换算getPosition基于校准值与屏参抗抖动坐标采样getPositionAvg原始 ADC 值读取getRaw用于调试或自定义换算。该模块基于 spapadim/XPT2046 与 PaulStoffregen/XPT2046_Touchscreen 两个开源库移植而来见 app/modules/xpt2046.c 的模块头注释底层依赖 NodeMCU 的spi模块与platform_gpio接口。启用模块xpt2046模块默认并未编译进固件。在 app/include/user_modules.h 中可以看到其开关项默认处于注释状态//#define LUA_USE_MODULES_XPT2046如需使用请在重新编译固件前取消该行注释使其变为#define LUA_USE_MODULES_XPT2046启用后固件编译时会将 app/modules/xpt2046.c 中的NODEMCU_MODULE(XPT2046, xpt2046, xpt2046, NULL)注册进 Lua 运行时Lua 侧即可直接调用xpt2046命名空间下的各函数。二、SPI 初始化前置条件所有触摸读取都通过 SPI 完成因此必须在调用xpt2046.init()之前先调用spi.setup()。官方文档给出的标准配置如下spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 16, spi.FULLDUPLEX)各参数含义对应 app/modules/spi.c 的参数解析参数值说明id1SPI 总线编号此处为 HSPImodespi.MASTER主机模式触摸屏作为从设备cpolspi.CPOL_LOW时钟极性为低cphaspi.CPHA_LOW时钟相位为低databits8每次传输 8 位clock_div16时钟分频应不小于 16duplex_modespi.FULLDUPLEX全双工模式app/modules/spi.c 中SPI_FULLDUPLEX定义为1这里有两点必须特别注意全双工模式是必需的。源码 app/modules/xpt2046.c 中的transfer16()通过platform_spi_send_recv同时收发数据来产生 16 个时钟周期以读取 12 位结果这要求 SPI 处于全双工状态。而spi.setup()的第 7 个参数duplex_mode在 app/modules/spi.c 中默认是半双工SPI_HALFDUPLEX所以必须显式传入spi.FULLDUPLEX。clock_div要取 16 或更高。文档明确说明较低的分频值可能产生不准确的读数源码 app/modules/xpt2046.c 也以注释形式确认了这一点更高的 SPI 时钟速度会导致采样结果失真。电阻式触摸屏的 ADC 采样需要足够的转换时间分频太低意味着单个转换周期过短。与 ucg/u8g2 显示模块共用 SPI 的注意事项由于ucg以及需要 SPI 的显示方案也要求先调用spi.setup()而 SPI 总线的初始化参数只能设置一次因此必须保证整个程序生命周期内spi.setup()只被调用一次且使用spi.FULLDUPLEX。推荐顺序是先做一次spi.setup()再初始化显示屏如ucg最后初始化触摸屏。三、xpt2046.init() —— 初始化触摸模块在完成 SPI 配置后调用init()传入触摸屏的片选CS、中断IRQ引脚以及屏幕的物理尺寸。语法xpt2046.init(cs_pin, irq_pin, height, width)参数参数类型说明cs_pin整数片选信号所接的 GPIO 引脚irq_pin整数触摸中断PENIRQ所接的 GPIO 引脚height整数显示屏高度像素width整数显示屏宽度像素返回值nil源码行为从 app/modules/xpt2046.c 可以看到init()内部完成的工作保存_cs_pin、_irq_pin、_height、_width到模块全局变量将cs_pin配置为输出模式PLATFORM_GPIO_OUTPUT依据传入的宽高自动生成一份默认校准值见下一节拉低 CS 做一次带断电PD标记的丢弃式读取确保 ADC 进入可用状态。示例配合 ucg 显示 ILI9341-- Setup spi with clock_div of 16 and spi.FULLDUPLEX spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 16, spi.FULLDUPLEX) -- SETTING UP DISPLAY (using ucg module) local disp ucg.ili9341_18x240x320_hw_spi(8, 4, 0) disp:begin(0) -- SETTING UP TOUCH xpt2046.init(2, 1, 320, 240) xpt2046.setCalibration(198, 1776, 1762, 273)上例中ucg.ili9341_18x240x320_hw_spi(cs, dc, res)的用法与 docs/modules/ucg.md 中描述的 ILI9341 硬件 SPI 接线一致CS、D/C 可任意选择 GPIORES 可选。注意xpt2046.init的第三个、第四个参数是高度、宽度示例传入了(320, 240)——这里宽高顺序与getPosition返回的 x/y 语义对应若屏幕方向与触摸方向不一致可通过setCalibration修正而无需颠倒传参。四、xpt2046.setCalibration() —— 触摸校准电阻屏的原始 ADC 值范围与屏幕像素范围并不天然对应且不同屏幕个体存在差异因此需要校准。setCalibration()用于设置屏幕两个对角左上与右下对应的原始触摸值。语法xpt2046.setCalibration(x1, y1, x2, y2)参数参数说明x1屏幕左上角的原始 x 值y1屏幕左上角的原始 y 值x2屏幕右下角的原始 x 值y2屏幕右下角的原始 y 值返回值nil获取校准值的方法文档明确给出了校准值的获取途径使用xpt2046.getRaw()分别读取左上角与右下角按压点的原始值。典型流程是临时使用init()自动生成的默认校准值或直接打印getRaw()结果用笔尖点按屏幕左上角记录getRaw()返回的(x, y)作为(x1, y1)点按屏幕右下角记录(x2, y2)将四值填入setCalibration()。源码中的坐标换算原理在 app/modules/xpt2046.c 中校准参数被转换成线性映射系数_cal_dx _width - 2*CAL_MARGIN; _cal_dy _height - 2*CAL_MARGIN; _cal_vi1 vi1; _cal_vj1 vj1; _cal_dvi vi2 - vi1; _cal_dvj vj2 - vj1;随后getPosition()app/modules/xpt2046.c使用这些系数把原始值线性映射到像素坐标*x _cal_dx * (vj - _cal_vj1) / _cal_dvj CAL_MARGIN; *y _cal_dy * (vi - _cal_vi1) / _cal_dvi CAL_MARGIN;注意CAL_MARGIN在源码 app/modules/xpt2046.c 中定义为0即不做边缘裁剪由应用层自行决定。而init()在未手动校准时会依据屏宽高把满量程12 位 ADC最大值ADC_MAX 0x0fff见 app/modules/xpt2046.c按比例分配为默认校准值因此只调用init()不调用setCalibration()也能得到一个线性但可能偏斜的映射正式使用前仍建议实测校准。五、xpt2046.isTouched() —— 触摸检测语法xpt2046.isTouched()返回值true面板被触摸或false未触摸。实现原理该函数直接读取 IRQ 引脚电平app/modules/xpt2046.cstatic int isTouching() { return (platform_gpio_read(_irq_pin) 0); }XPT2046 的 PENIRQ 引脚在有触摸时被拉低因此读回0即代表正在触摸。该函数是轮询式检测的基础也是中断回调方案的先决条件。示例if (xpt2046.isTouched()) then local x, y xpt2046.getPosition() print(x .. - .. y) end六、xpt2046.getPosition() —— 获取校准后的像素坐标getPosition()返回经校准值换算、并按照init()传入的宽高映射到屏幕的坐标是日常交互中最常用的函数。文档特别指出它可以直接用在 IRQ 引脚的中断回调里从而在触摸发生时立刻拿到坐标无需轮询。语法xpt2046.getPosition()返回值返回两个值x与y屏幕像素坐标。触摸中断回调完整示例以下示例完整展示了 GPIO 中断 触摸坐标读取的组合文档原示例-- Setup spi with clock_div of 16 and spi.FULLDUPLEX spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 16, spi.FULLDUPLEX) -- SETTING UP TOUCH cs_pin 2 -- GPIO4 irq_pin 3 -- GPIO0 height 240 width 320 xpt2046.init(cs_pin, irq_pin, width, height) xpt2046.setCalibration(198, 1776, 1762, 273) gpio.mode(irq_pin, gpio.INT, gpio.PULLUP) gpio.trig(irq_pin, down, function() print(xpt2046.getPosition()) end)要点解读gpio.mode(irq_pin, gpio.INT, gpio.PULLUP)将 IRQ 引脚设为带内部上拉的中断输入XPT2046 的 PENIRQ 是开漏/低有效输出上拉是必需的。gpio.trig(irq_pin, down, ...)注册下降沿触摸按下回调回调内调用getPosition()。文档示例中xpt2046.init(cs_pin, irq_pin, width, height)与第一节示例的宽高传参顺序不同此处为(width, height)即(320, 240)实际效果取决于你的屏幕方向约定建议固定一种写法并配合校准值确认 x/y 映射正确。中断期间的底层细节源码 app/modules/xpt2046.c 的getRaw()展示了中断方案的一个关键实现读取坐标前会先关闭 GPIO 中断ETS_GPIO_INTR_DISABLE()读取完成后写清除中断状态位并重新使能中断ETS_GPIO_INTR_ENABLE()因为 SPI 读取时序本身会产生虚假的 PENIRQ 边沿。这意味着你无需在 Lua 回调里手动屏蔽中断底层已经处理。此外读取结束时会发送带断电标记CTRL_LO_SER即 PD0b11 之外的序列的控制字节来关闭 ADC——这是因为差分模式CTRL_LO_DFR要求 PD0b11 才能启用 PENIRQ若不主动关闭 ADC后续的中断信号将失效见 app/modules/xpt2046.c 注释。七、xpt2046.getPositionAvg() —— 三次采样取优平均单次触摸读数易受噪声影响。getPositionAvg()会连续读取三次位置然后取距离最近的两组求平均以获得更稳定的坐标。语法xpt2046.getPositionAvg()返回值返回两个值x与y经平均处理的像素坐标。示例local x, y xpt2046.getPositionAvg() print(x .. - .. y)实现原理源码 app/modules/xpt2046.c 中该函数连续调用三次getPosition()再把三组坐标交给besttwoavg()app/modules/xpt2046.c处理分别计算三组两两间的距离选择距离最小的一对求算术平均。这样能有效剔除单次明显偏离的异常采样。使用建议在需要精确坐标的场景如手写、绘图、小按钮点击优先使用getPositionAvg()在要求低延迟的快速滑动场景getPosition()更合适。八、xpt2046.getRaw() —— 读取原始 ADC 值getRaw()跳过校准换算直接返回触摸点对应的 12 位原始值用于调试、校准取值以及自定义坐标变换。语法xpt2046.getRaw()返回值返回两个值rawX与rawY原始触摸值04095 范围。示例local rawX, rawY xpt2046.getRaw() print(rawX .. - .. rawY)数据读取时序从源码看getRaw()app/modules/xpt2046.c的执行基于 TI 应用笔记 SBAA036 的推荐时序拉低 CS发起第一个控制字节CTRL_HI_X | CTRL_LO_DFR开始 X 通道转换_readLoop()app/modules/xpt2046.c连续发送 16 个时钟周期的控制字直到前后两次读数一致或达到最大采样次数255 次返回稳定的 12 位结果以相同方式读取 Y 通道发送丢弃字节关闭 ADC 并拉高 CS恢复 PENIRQ。因此getRaw()已经内置了读到稳定值的循环返回结果可用于校准如第四节所述也可以完全自定义换算逻辑。九、硬件接线与实战建议结合文档示例与 docs/modules/ucg.md 中 ILI9341 的 SPI 接线说明推荐接线如下信号推荐 GPIO说明SPI CLKGPIO14HSPI 时钟SPI MOSIGPIO13主出从入发送控制字节SPI MISOGPIO12主入从出读回数据全双工模式下必须连接屏 CS任选示例用 GPIO15/GPIO8显示控制器片选屏 D/C任选示例用 GPIO4数据/命令选择屏 RES任选可省略触摸 CS任选示例用 GPIO4XPT2046 片选触摸 IRQ任选示例用 GPIO0PENIRQ需上拉接中断输入最后再强调三条容易踩坑的规则spi.setup()全局只需调用一次且必须带spi.FULLDUPLEX、clock_div 16xpt2046.init()必须在spi.setup()之后调用正式项目务必用getRaw()实测屏幕左上、右下两角完成setCalibration()不要依赖init()的默认校准值。十、API 速查表函数语法返回值xpt2046.initxpt2046.init(cs_pin, irq_pin, height, width)nilxpt2046.setCalibrationxpt2046.setCalibration(x1, y1, x2, y2)nilxpt2046.isTouchedxpt2046.isTouched()true/falsexpt2046.getPositionxpt2046.getPosition()x, yxpt2046.getPositionAvgxpt2046.getPositionAvg()x, yxpt2046.getRawxpt2046.getRaw()rawX, rawY全部 6 个函数均在 app/modules/xpt2046.c 的LROT_BEGIN(xpt2046, ...)模块函数表中注册与文档列出的 API 完全一致。完整官方说明可继续查阅 docs/modules/xpt2046.mdSPI 配置细节见 docs/modules/spi.md。赞分享物联网嵌入式【免费下载链接】nodemcu-firmwareLua based interactive firmware for ESP8266, ESP8285 and ESP32项目地址https://gitcode.com/gh_mirrors/no/nodemcu-firmware点击查看免费下载相关推荐Asterinas触摸屏触摸输入设备驱动实现Asterinas触摸屏触摸输入设备驱动实现 引言 在现代操作系统中触摸屏输入已成为人机交互的重要方式。Asterinas作为一个用Rust编写的安全、高性操作系统内核驱动系统编程上一篇使用colesbury/nogil项目中的urllib模块获取网络资源下一篇5步实现Qwen模型ONNX格式转换终极跨平台部署指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
