ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

树莓派+墨水屏+NB-IoT:低功耗远程显示终端制作全解析

树莓派+墨水屏+NB-IoT:低功耗远程显示终端制作全解析 简介面向嵌入式开发者的墨水屏物联网扩展板示例代码资源包聚焦E-Paper显示、NB-IoT窄带物联网与GPRS通信三大技术模块在HAT标准硬件上的C语言实现。资源共151个文件压缩包约815KB包含40个C源文件、34个头文件、40个编译生成的目标文件以及32个与墨水屏驱动相关的BMP图像资源用于不同尺寸和色彩的显示测试、makefile构建脚本和配置文件文件结构便于开发者按模块拆解学习。已有192人学习下载。代码中涵盖电子纸初始化与刷新流程、NB-IoT/GPRS模块的AT指令交互、TCP/IP协议栈封装等核心环节并针对多种型号墨水屏提供适配示例适合正在开发低功耗远程显示终端或准备入手HAT扩展板项目的物联网工程师参考与二次开发。1. 把墨水屏和 NB-IoT 焊在一块先别急着调功耗第一次拿到 E-Paper_NB-IoT_GPRS_HAT 这块板子时大多数人的第一反应是跑通自带 Demo看墨水屏上滚出一行字就收工。但真正的物联网显示终端难点从来不在“点亮屏幕”而在“怎么用 NB-IoT 把数据拉到屏上同时保证电池能撑过三个月”。墨水屏的刷新特性、NB-IoT 的窄带连接、GPRS 的兼容兜底这三样东西在同一块 HAT 上共存信号链路和代码结构都比单纯点屏复杂一个量级。这个标题拆开看E-Paper 决定显示功耗和刷新方式NB-IoT 决定数据通道和报活周期GPRS 决定弱网兜底HAT 决定物理接口和供电分配Demo Code 则是把这些串起来的最短路径。适合谁读手里已经有树莓派或者 Jetson Nano想快速验证“远程数据上屏”原型的人以及做低功耗远传显示终端选型需要在 NB-IoT 和 GPRS 之间做取舍的工程师。后面要讲的每一段都围绕一个原则先让链路跑通再谈优化。2. 认识 E-Paper_NB-IoT_GPRS_HAT 的信号链路与选型边界2.1 HAT 上 E-Paper、NB-IoT、GPRS 各管什么先捋清楚这块 HAT 同时挂三个通信对象的底层逻辑。E-Paper 走的是 SPI 总线NB-IoT/GPRS 模组走的是 UART 串口两者在树莓派的 GPIO 上互不干扰真正需要关注的是电源和复位时序。大多数 HAT 会用一个跳线帽或者拨码开关来切换模组的开机模式有的板子还带电平转换芯片因为 NB-IoT 模组的串口电平可能是 1.8V而树莓派 GPIO 是 3.3V接反或者漏接转换电路串口收发的第一帧数据就是乱码。显示侧E-Paper 驱动芯片通常支持多种帧率模式和刷新方式全刷、局部刷、快速刷的电压时序完全不同。通信侧NB-IoT 模组虽然主打低功耗但开机注网瞬间的峰值电流能到 1A 左右如果 HAT 的电源设计只考虑了墨水屏的微安级功耗这个瞬间压降会导致模组反复重启。2.2 NB-IoT 与 GPRS 双模共存时的 AT 指令通道这是新手容易踩的第一个大坑。很多 NB-IoT/GPRS 双模模组比如常见的 BC26、SIM7000、SIM7600 系列共用同一个 UART 口用 AT 指令切换网络制式。但 Demo Code 里通常默认走 NB-IoT如果你想直接用手机卡测试 GPRSATCNMP 指令的参数不对模组会一直停留在搜网状态。常见做法是先发 AT 指令确认模组版本和网络注册状态再决定走 NB-IoT 还是 GPRS。下面这一段是典型的探测代码import serial import time ser serial.Serial(/dev/ttyS0, 115200, timeout1) def send_at(cmd, delay0.5): ser.write((cmd \r\n).encode()) time.sleep(delay) resp ser.read(ser.in_waiting).decode(errorsignore) return resp # 探测模组是否在线 print(send_at(AT)) # 查询当前网络制式SIM7000系列用 ATCNMP print(send_at(ATCNMP?)) # NB-IoT 模式 print(send_at(ATCNMP38)) # 或者切到 LTE/GPRSATCNMP2 为自动模式 print(send_at(ATCNMP2))代码逻辑很简单先发 AT 确认串口通再读当前网络模式最后强制切到目标制式。注意ATCNMP的参数值在不同模组厂家的固件里定义不一样有的是 2 代表 CAT-M有的是 38 代表 LTE-only务必先查你手上模组的 AT 指令手册不要照搬。2.3 选型时记住这张参数表参数NB-IoTGPRS对 Demo 的影响峰值电流约 0.5A~1A约 1.5A~2A决定 HAT 供电预留余量连接建立时延2~10 秒5~15 秒影响数据轮询间隔设计常态功耗PSM 下可到 uA 级待机 mA 级决定电池容量估算下行速率约 20~60kbps约 20~40kbps墨水屏图片传输要压缩覆盖能力穿墙能力强依赖 2G 基站仓库地下室选 NB-IoT选型的关键一句话如果你的设备固定在室内且三天才更新一次屏幕NB-IoT 加 PSM 是唯一正解如果要在移动场景下追数据GPRS 的兼容性反而更好。这块 HAT 存在的意义就是把选择权交给你但代码里得留一个配置项来切制式不要把 NB-IoT 写死。3. 在树莓派上用 Python 跑通墨水屏 Demo 的最小代码3.1 启用 SPI 与串口避免权限和映射坑树莓派默认的串口 /dev/ttyAMA0 是给蓝牙用的直接访问会报错或者读到垃圾数据。第一步要把串口映射到 /dev/ttyS0并且关闭系统对该串口的 console 占用。# 编辑 /boot/config.txt追加以下内容 # 关闭蓝牙串口映射启用 PL011 UART dtoverlaydisable-bt enable_uart1 # 重启后检查设备节点 ls -l /dev/ttyS0 /dev/ttyAMA0 # 期望看到 ttyS0 存在ttyAMA0 可能变成了别的用途执行完重启再把当前用户加入 dialout 组避免每次运行都要 sudo。dtoverlaydisable-bt的作用是把 PL011 UART 从蓝牙释放出来给 GPIO 14/15 用这是树莓派 3B/4B 上最常被忽略的一步。3.2 初始化 E-Paper 并刷一张测试图墨水屏的初始化顺序比一般显示屏严格必须先软复位、再发初始化序列、然后清屏最后才能写图像数据。跳过清屏直接写图残影会重叠到新图上。from waveshare_epd import epd7in5_V2 from PIL import Image, ImageDraw, ImageFont import logging logging.basicConfig(levellogging.DEBUG) try: # 实例化屏幕驱动注意型号后缀 V2 对应 7.5 寸新版 epd epd7in5_V2.EPD() # 初始化软复位 读 busy 引脚 epd.init() # 全刷清屏白底黑字 epd.Clear() # 创建一个和屏幕分辨率一致的画布 image Image.new(1, (epd.width, epd.height), 255) draw ImageDraw.Draw(image) font ImageFont.truetype(/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf, 36) # 测试图案一个矩形 一行文字 draw.rectangle((10, 10, 200, 100), outline0, width4) draw.text((20, 120), IoT Display OK, fontfont, fill0) # 把画布写入屏幕并进入睡眠 epd.display(epd.getbuffer(image)) epd.sleep() except Exception as e: logging.error(fE-Paper init failed: {e}) exit(1)这段代码的逻辑先导入 waveshare 官方库实例化屏幕驱动初始化后清屏然后创建一个与屏幕分辨率等大的单色位图用 PIL 画上测试图形最后getbuffer把 PIL 对象转换为驱动需要的字节流。epd.sleep()不是可选操作如果不进睡眠墨水屏的电源会一直保持静态显示功耗能差出几毫安。3.3 打通 NB-IoT 数据采集并驱动局部刷新上一步只是纯显示 Demo现在把 NB-IoT 模组加进来。目标是每 10 分钟通过 CoAP 或者 HTTP 从远端平台拉一条数据然后只更新屏幕上的数字区域。import json import requests from waveshare_epd import epd7in5_V2 from PIL import Image, ImageDraw, ImageFont import serial import time # 串口参数与模组一致SIM7000通常默认115200 ser serial.Serial(/dev/ttyS0, 115200, timeout2) def get_sensor_data(): # 尝试走 NB-IoT 的 TCP 直连方式HTTP 太重 ser.write(bATCHTTPCONhttp://iot.example.com/api/sensor/001\r\n) time.sleep(3) resp ser.read(ser.in_waiting).decode(errorsignore) # 从响应里抠出 JSON 部分生产环境用正则更稳 start resp.find({) end resp.rfind(}) 1 data json.loads(resp[start:end]) return data[temperature], data[humidity] try: epd epd7in5_V2.EPD() epd.init() # 先全刷一次干净底再做局部刷新 epd.Clear() # 做一张固定的背景图只刷新数字部分 bg Image.new(1, (epd.width, epd.height), 255) draw ImageDraw.Draw(bg) font_num ImageFont.truetype(/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf, 60) font_label ImageFont.truetype(/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf, 24) draw.text((30, 50), Temp: %.1f C % get_sensor_data()[0], fontfont_num, fill0) draw.text((30, 150), Humidity: %d%% % get_sensor_data()[1], fontfont_label, fill0) epd.display(epd.getbuffer(bg)) # 进入睡眠前关掉模组用 PSM 挂起 ser.write(bATCPSMS1\r\n) epd.sleep() except Exception as e: print(fUpdate failed: {e}) ser.write(bATCFUN0\r\n)ATCHTTPCON只是示例实际 IoT 平台通常走 MQTT 或者 CoAPTCP 直连也行。注意一个细节每次全刷后进睡眠睡眠前把 NB-IoT 模组用ATCFUN0关掉射频只保留 RTC 唤醒这是省电的核心比任何代码优化都明显。局部刷新不是必须的小屏区域内用全刷反而能避免残影只有区域超过屏幕三分之一时才值得做局部刷新。4. 数据刷新策略与 NB-IoT 功耗参数怎么配合4.1 墨水屏刷新方式挑对了功耗差一倍墨水屏的功耗分为两部分刷新期间的驱动功耗和静态保持功耗。7.5 寸黑白屏全刷一次大约要 3 到 5 秒期间功耗 20 到 40 毫安局部刷新虽然电流相同但时间缩短到 1 秒以内。难点在于局部刷新在低温下容易出现残影且对图像边界有严格对齐要求。实际项目里我一般这么定一天更新 4 次以内直接用全刷代码简单可靠更新频率超过每小时一次才需要拆局部刷新。局部刷新的实现方式是在驱动库里找到displayPartial方法传入一个包含起始坐标和宽高的窗口然后只对该区域的缓冲区做更新。# 局部刷新示例只更新底部的数字区域 # 注意必须先全刷至少一次之后才能局部刷 x0, y0, x1, y1 20, 200, 620, 260 partial_image Image.new(1, (x1 - x0, y1 - y0), 255) draw ImageDraw.Draw(partial_image) draw.text((0, 0), str(new_temperature), fontfont_num, fill0) # 关键参数局部刷新窗口坐标必须和原始画布坐标一致 epd.displayPartial(epd.getbuffer(partial_image), x0, y0, x1, y1)这里的坑是显示缓冲区依然是整屏大小的getbuffer返回的是包含窗口数据的完整帧但驱动只刷新指定区域。如果窗口坐标和缓冲区里的位置对不上屏幕会花掉。4.2 用 PSM 和 eDRX 把 NB-IoT 模组的待机电流压下去NB-IoT 低功耗的根基在 PSMPower Saving Mode和 eDRXExtended Discontinuous Reception。PSM 模式下模组注网后进入空闲态然后被子网关踢进休眠期间不再监听寻呼电流降到微安级直到应用主动发包或者定时器到期才醒来。eDRX 则是拉长监听寻呼的间隔适合需要下行推送但能容忍延迟的场景。配置 PSM 的 AT 指令如下ATPSMCFG1,01000001,00000100第一个参数1开启 PSM第二个字符串01000001是 T3324 定时器移动终端等待寻呼的时长的 8 位编码第三个00000100是 T3412 定时器进入 PSM 前的总等待时长。换算规则是编码值乘 10 秒所以01000001表示 10 秒00000100表示 40 秒。实际项目中TTL 时长要配合平台的推送策略设成 60 到 120 秒太长会错过实时 push太短则省不了电。4.3 供电与天线布局的三个常见坑第一NB-IoT 模组注网瞬间电流会拉出很大的毛刺必须在模组电源引脚旁放一个 470uF 的电解电容和 100nF 的陶瓷电容。第二天线不要贴着 E-Paper 的排线走墨水屏的公共电极在驱动刷新时会产生较强的电场耦合能把 NB-IoT 模组的灵敏度拉低 10dB 以上直接表现是注网变慢或驻网后误码率高。第三如果 HAT 上的模组没有独立的射频开关控制脚要在代码里拉低模组的 PWRKEY 引脚否则模组会一直处于不明状态耗电。天线这块我见过最典型的翻车是把 NB-IoT 天线绕在金属支架上信号直接从满格掉到两格ATCSQ 返回的 RSSI 值从 25 掉到 12但代码里没有做信号质量判断数据发不出去就傻等超时。5. 验证 Demo 跑没跑对方法在 AT 指令的回显里5.1 三个必查的 AT 指令回显Demo 写完之后别急着看屏幕先看模组的状态。接上串口调试工具依次发这三条指令ATCGATT? # 查看是否附着到网络返回 1 表示已附着 ATCSQ # 信号质量返回 25,99 表示信号强度 25约 -75dBm99表示未知 ATCPSMS? # 查看 PSM 配置是否生效ATCGATT?返回CGATT: 0时要排查 SIM 卡是否欠费、天线是否接好、当前区域是否有 NB-IoT 覆盖。ATCSQ是第一位的返回的第一个数字是 RSSI 等级0 到 31 对应 -113 到 -51dBm低于 10 基本没法稳定传数据。5.2 用日志时间戳计算全链路更新耗时跑一次完整的数据采集加屏幕刷新在代码关键节点打时间戳能算出 NB-IoT 网络响应的真实耗时import time t0 time.time() # NB-IoT 数据拉取 ser.write(bATCIPSHUT\r\n) t1 time.time() print(fNetwork teardown: {t1-t0:.2f}s) # 屏幕全刷 epd.init() epd.display(epd.getbuffer(bg)) t2 time.time() print(fFull refresh: {t2-t1:.2f}s)如果 Network teardown 阶段耗时超过 5 秒说明模组没有进入 PSM可能是传输层没关闭。如果 Full refresh 超过 8 秒检查 SPI 时钟频率是不是被树莓派默认配置卡在了 1MHz改成 32MHz 能压到 3 秒内。5.3 低功耗验证没有电流钳也能估得八九不离十用万用表的 mA 挡串进电池正极只测三种状态全刷电流、NB-IoT 发送电流、PSM 休眠电流。以此推算电池寿命公式是总容量除以平均电流。一个 2000mAh 电池一天两次每次平均 100mA 持续 10 秒加上 PSM 下 20uA 的常驻电流一年绰绰有余。关键看模组有没有被ATCFUN0关掉射频前吃超标如果休眠电流超过 1mA 就要检查 GPIO 是不是有外部上拉在漏电。本文还有配套的精品资源点击获取
返回列表