Arduino 小白的 DIY 空气质量检测仪(5)- OLED显示模块、按钮模块

最终章

这一章把剩下的OLED显示模块、按钮模块分享一下,当前这个离线无存储的版本,基本告一段落。

如果后续能进化成🈶存储、联网版本,就再开一个小系列分享一下。

逐个分析

display.h

#include <Arduino.h>
#include <Wire.h>

// OLED 0.96 库
#include <ssd1306.h>

// OLED 0.96
// 接口:GND->GND、VDD->VCC(5V)、SCK->SCK/A5、SDA->SDA/A4
// 协议:I2C
// 地址:0x3C

namespace SSD_1306 {
unsigned int width = 128;
unsigned int lineHeight = 8;
unsigned int charMax = 24;
}

namespace Display {

struct _OLED {
  void init() {
    // 初始化OLED
    Wire.begin();
    ssd1306_128x64_i2c_init();
    ssd1306_setFixedFont(ssd1306xled_font6x8);
    ssd1306_clearScreen();
  }

  void printRaw(unsigned int left, unsigned int top, char* str, unsigned int style = STYLE_NORMAL) {
    ssd1306_printFixed(left, top, str, style);
  }

  void printNRaw(unsigned int left, unsigned int top, char* str, unsigned int style = STYLE_NORMAL) {
    ssd1306_printFixedN(left, top, str, style, 1);
  }

  void print(char* str, unsigned int left, unsigned int top) {
    printRaw(left, SSD_1306::lineHeight * top, str);
  }

  void printRight(char* str, int top) {
    uint16_t left = getLeft(str);
    print(str, left, top);
  }

  void drawBuffer(unsigned int left, unsigned int top, uint8_t* buffer) {
    ssd1306_drawBuffer(left, top, 3, 8, buffer);
  }

  void drawLine(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) {
    ssd1306_drawLine(x1, y1, x2, y2);
  }

  void clearBlock(unsigned int left, unsigned int top, unsigned int w, unsigned int h) {
    ssd1306_clearBlock(left, top, w, h);
  }

  void clearBlockCenter(unsigned int left, unsigned int right, unsigned int top) {
    clearBlock(left, top, SSD_1306::width - left - right, SSD_1306::lineHeight);
  }

  unsigned int getTextSize(char* str) {
    return ssd1306_getTextSize(str, 0);
  }

  unsigned int getLeft(char* str) {
    int w = getTextSize(str);
    return SSD_1306::width - w;
  }

  void clearScreen() {
    ssd1306_clearScreen();
  }
} OLED;

}

通讯方式是 I2C

这个 OLED 模块,分辨率只有 128x64,一行文字占 8 个像素的高度,一行大概可以容纳 24 个字母。

支持这个模块的库很多,有的依赖了别的库、有的带开屏广告、有的。。。最后我选了 ssd1306.h 感觉比较顺手,不过它的 API 的命名比较简单粗暴。

初始化、字体设置、清屏 很好理解,而绘制文字 ssd1306_printFixed 和 ssd1306_printFixedN 的区别,也只是 ssd1306_printFixedN 多一个放大倍数的参数输入,1 就是放大一倍。设计理念是基于 8 像素这个行高的。

比较重要的是,绘制有个特点,内容更新是需要考虑“清空”的,而这个“清空”多数时候是局部的,例如:

假如,第一秒数值是 1234,显示如下:

1234

第二秒数值是 234,如果不进行“清空”,显示将如下:

2344

改变的字母“234”区域更新了,但是原来未改变的“4”依然显示,因此,是需要清空“4”这个区域的,才能变成:

234

此库提供一个相应的方法:

void ssd1306_clearBlock(uint8_t x, uint8_t y, uint8_t w, uint8_t h){}

x、y 是开始位置,w、h 是处理范围。

问题来了,我如何知道从哪个像素开始“清空”呢?那将需要另外一个 API:

lcduint_t ssd1306_getTextSize(const char *text, lcduint_t *height){}

它可以通过字符串的内容,计算字符串所需占用的宽高,这里返回值就是宽,输入的第二参数是高(本项目只需要宽,高都以默认 8 像素计算)。

在本项目中,一行将显示 2 个传感器数值,也就是说需要左右各自对齐贴边:

在这里插入图片描述

这里“清空”的区域就要考虑左右两个数值的字符串宽度了,就是说,每次更新数值的时候,需要“清空”的区域大概是:

在这里插入图片描述

举个例子,本项目中,最后一行显示,最外层的方法是:

// arduino-air-monitor.ino

void process(bool display) {
// ...略

  if (display) {
    // ...略
    Display::OLED.clearBlockCenter(printCO2(Module::CO2.getValue(), 7, false), printHum(Module::Humidity.getValue(), 7, true), 7);
  }
}

CO2 的显示方法 printCO2:

unsigned int printCO2(unsigned int value, unsigned int row, bool isRight) {
  char str[SSD_1306::charMax] = "";
  strcat(str, "CO2:");

  char numStr[SSD_1306::charMax] = "";
  itoa(value, numStr, 10);
  strcat(str, numStr);
  strcat(str, "ppm");

  if (isRight) {
    Display::OLED.printRight(str, row);
  } else {
    Display::OLED.print(str, 0, row);
  }

  Serial.println(str);

  return Display::OLED.getTextSize(str);
}

湿度的显示方法 printHum:

unsigned int printHum(float value, unsigned int row, bool isRight) {
  char str[SSD_1306::charMax] = "";
  strcat(str, "Hum:");

  char numStr[SSD_1306::charMax] = "";
  dtostrf(value, 1, 1, numStr);
  strcat(str, numStr);
  strcat(str, "%");

  if (isRight) {
    Display::OLED.printRight(str, row);
  } else {
    Display::OLED.print(str, 0, row);
  }

  Serial.println(str);

  return Display::OLED.getTextSize(str);
}

这里设计思路,是每个数值的显示方法,最后都会返回字符串占用的宽度,用于计算中间“清空”区域。

  void clearBlockCenter(unsigned int left, unsigned int right, unsigned int top) {
    clearBlock(left, top, SSD_1306::width - left - right, SSD_1306::lineHeight);
  }
  void clearBlock(unsigned int left, unsigned int top, unsigned int w, unsigned int h) {
    ssd1306_clearBlock(left, top, w, h);
  }

关于靠右显示,也是利用 ssd1306_getTextSize,用 128 显示宽度减去字符串的宽度,就是靠右显示的起始位置了。

最后,说说 2 个要自己实现的显示字符:“立方”和“度”,是不支持此类特殊字符的:

在这里插入图片描述

这个时候,就需要利用此库绘制位图:

unsigned int printPower3(unsigned int left, unsigned int top) {
  // 绘制立方"³"符号
  // 位图方向:从左往右、从下往上
  // 0 0 0
  // 0 0 0
  // 0 0 0
  // 1 1 1
  // 0 0 1
  // 1 1 1
  // 0 0 1
  // 1 1 1
  // 第一列 00010101 -> 0x15
  // 第二列 00010101 -> 0x15
  // 第三列 00011111 -> 0x1F
  // js转换示例:parseInt('00011111',2) -> (31).toString(16) -> 1f
  uint8_t buffer[3] = { 0x15, 0x15, 0x1F };
  Display::OLED.drawBuffer(left, top, buffer);

  return 4;
}

unsigned int printDeg(unsigned int left, unsigned int top) {
  // 绘制"°"符号
  // 位图方向:从左往右、从下往上
  // 0 0 0
  // 0 0 0
  // 0 0 0
  // 0 0 0
  // 0 0 0
  // 0 1 0
  // 1 0 1
  // 0 1 0
  uint8_t buffer[3] = { 0x02, 0x05, 0x02 };
  Display::OLED.drawBuffer(left, top, buffer);

  return 4;
}

请看注释,实际上就是:在格子中填0/1,1 就是代表像素的亮。

计算的逻辑,可以参考 printPower3 的注释:

在这里插入图片描述

上面说的“位图方向:从左往右、从下往上”,不是很严谨,其实这里只是使用该 API 得出的特点(我也不是很明白为何颠倒过来了,等哪位大神可以解答一下最好),位图按维基百科应该下面那样才符合直觉:
在这里插入图片描述

最后,我使用下来,发现使用 String 类型会出现各种无法解释的异常乱码,个人建议这里使用 C 风格的字符串。

上边基本上就是我遇到的一些比较值得注意的坑吧。

buttons.h

// buttons.h

#include <Arduino.h>

#define _Pin_Btn_1 12

namespace Buttons {

enum Status {
  Ready = 0,
  Down = 1,
  Up = 2
};

struct _Btn_1 {
  Status status = Ready;

  void init() {
    pinMode(_Pin_Btn_1, INPUT_PULLUP);
  }

  void loop() {
    if (status == Ready && digitalRead(_Pin_Btn_1) == LOW) {
      status = Down;
    }

    if (status != Ready && digitalRead(_Pin_Btn_1) == HIGH) {
      status = Up;
    }
  }

  bool getValue() {
    bool result = status == Up;

    if (result) {
      status = Ready;
    }
    return result;
  }
} Btn_1;

}

按网页开发的直觉,按钮不就是用 digitalRead 得到该按钮引脚如果低电平,就知道按了,就 if 一下去干一件事情就可以了吗?

实际上,在这里,“点击”是需要自己处理按钮的状态的,我抽象成 Ready 等待(HIGH)、Down 按下中(LOW)、Up 释放中(HIGH),可以看出来 Ready 和 Up 都是 HIGH,这应该如何区分?

该模块里面,我也定义了一个 loop 方法,就意味着要放在 程序入口 的 loop 方法中。

// arduino-air-monitor.ino

void loop() {
  Buttons::Btn_1.loop();

  bool clicked = Buttons::Btn_1.getValue();

  if (clicked) {
    oledDisplay = !oledDisplay;
  }

  // oledDisplay 就是通过按钮切换的一个 true/false 状态

  // ...略
}

流程图表达:

在这里插入图片描述

可以看出,只要按下不动,就会变成且持续是 Down 状态,放手释放的时候,就会变成且持续是 Up 状态。

那什么时候才会变回 Ready 进行下一次“点击”识别?是读取是否“点击”了的时候:

// buttons.h

  bool getValue() {
    bool result = status == Up;

    if (result) {
      status = Ready;
    }
    return result;
  }
// arduino-air-monitor.ino

bool clicked = Buttons::Btn_1.getValue();

这样子,就可以在持续不断的 loop 中,识别出“点击”的操作,毕竟“点击”是由“按下”和“释放”两个动作构成的,类似网页中 click 约等于 mousedown + mouseup。

好羡慕 PCB 设计、3D 打印 的大神们,如果拥有这两块的能力,做成真正的成品该多好呀~~~~

多多支持其它文章
https://blog.csdn.net/xachary2

又或者请我喝杯奶茶😍
vue3-zoom-drag

项目完整代码仓库在这
arduino-air-monitor

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/948447.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WandB使用笔记

最近看代码&#xff0c;发现代码中有wandb有关的内容&#xff0c;搜索了一下发现是一个模型训练工具&#xff0c;然后学习了一下&#xff0c;这里记录一下使用过程&#xff0c;方便以后查阅。 WandB使用笔记 登录WandB 并 创建团队安装 WandB 并 登录模型训练过程跟踪模型版本管…

一文理解ssh,ssl协议以及应用

在使用基于密钥的认证方式的时候&#xff0c;私钥的位置一定要符合远程服务器规定的位置&#xff0c;否则找不到私钥的位置会导致建立ssh连接失败 SSH 全称是 “Secure Shell”&#xff0c;即安全外壳协议。 它是一种网络协议&#xff0c;用于在不安全的网络中安全地进行远程登…

Elasticsearch 创建索引 Mapping映射属性 索引库操作 增删改查

Mapping Type映射属性 mapping是对索引库中文档的约束&#xff0c;有以下类型。 text&#xff1a;用于分析和全文搜索&#xff0c;通常适用于长文本字段。keyword&#xff1a;用于精确匹配&#xff0c;不会进行分析&#xff0c;适用于标签、ID 等精确匹配场景。integer、long…

【Ubuntu】 Ubuntu22.04搭建NFS服务

安装NFS服务端 sudo apt install nfs-kernel-server 安装NFS客户端 sudo apt install nfs-common 配置/etc/exports sudo vim /etc/exports 第一个字段&#xff1a;/home/lm/code/nfswork共享的目录 第二个字段&#xff1a;指定哪些用户可以访问 ​ * 表示所有用户都可以访…

【谷歌开发者月刊】十二月精彩资讯回顾,探索科技新可能

我们在今年的尾声中回顾本月精彩&#xff0c;开发者们借助创新技术为用户打造温暖的应用体验&#xff0c;展现技术与实用的结合。欢迎您查阅本期月刊&#xff0c;掌握最新动态。 本月看点 精彩看点多多&#xff0c;请上下滑动阅览 01DevFest 北京站和上海站圆满举办&#xff0c…

浙江中医药大学携手云轴科技ZStack荣获“鼎信杯”金鼎实践奖

近日&#xff0c;2024“鼎信杯”信息技术发展论坛&#xff08;以下简称“论坛”&#xff09;在北京隆重召开。本次论坛汇聚多位领导和专家&#xff0c;以及业内骨干企业、研究机构、用户单位、行业组织代表等500余人&#xff0c;共同探讨信息技术应用创新产业趋势&#xff0c;分…

嵌入式linux系统中CMake的基本用法

第一:CMake的基本使用 在上篇文章中,我们聊了聊 Makefile。虽然它是 C/C++ 项目编译的“老司机”,但写起来真的是让人头大。尤其是当项目文件一多,手写依赖就像在搬砖,费时又费力。 那么问题来了,难道我们就没有更优雅的工具了吗?答案是:有! 这时候,CMake 就像一个…

vulnhub Earth靶机

搭建靶机直接拖进来就行 1.扫描靶机IP arp-scan -l 2.信息收集 nmap -sS -A -T4 192.168.47.132 得到两个DNS; 在443端口处会让我们加https dirb https://earth.local/ dirb https://terratest.earth.local/ #页面下有三行数值 37090b59030f11060b0a1b4e0000000000004312170a…

【AI日记】25.01.04 kaggle 比赛 3-3 | 王慧玲与基层女性

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】 工作 参加&#xff1a;kaggle 比赛 Forecasting Sticker Sales时间&#xff1a;6 小时 读书 书名&#xff1a;基层女性时间&#xff1a;3 小时原因&#xff1a;虽然我之前就知道这个作者&#xff0c;因为我…

《learn_the_architecture_-_aarch64_exception_model》学习笔记

1.当发生异常时&#xff0c;异常级别可以增加或保持不变&#xff0c;永远无法通过异常来转移到较低的权限级别。从异常返回时&#xff0c;异常级别可能会降低或保持不变&#xff0c;永远无法通过从异常返回来移动到更高的权限级别。EL0级不进行异常处理&#xff0c;异常必须在比…

linux上安装MySQL教程

1.准备好MySQL压缩包&#xff0c;并进行解压 tar -xvf mysql-5.7.28-1.el7.x86_64.rpm-bundle.tar -C /usr/local 2.检查是否有mariadb数据库 rpm -aq|grep mariadb 关于mariadb:是MySQL的一个分支&#xff0c;主要由开源社区在维护&#xff0c;采用GPL授权许可 MariaDB的目…

量子力学复习

黑体辐射 热辐射 绝对黑体&#xff1a; &#xff08;辐射能力很强&#xff0c;完全的吸收体&#xff0c;理想的发射体&#xff09; 辐射实验规律&#xff1a; 温度越高&#xff0c;能量越大&#xff0c;亮度越亮 温度越高&#xff0c;波长越短 光电效应 实验装置&#xf…

OSI模型的网络层中产生拥塞的主要原因?

&#xff08; 1 &#xff09;缓冲区容量有限&#xff1b;&#xff08; 1.5 分&#xff09; &#xff08; 2 &#xff09;传输线路的带宽有限&#xff1b;&#xff08; 1.5 分&#xff09; &#xff08; 3 &#xff09;网络结点的处理能力有限&#xff1b;&#xff08; 1 分…

Spring Boot 的自动配置,以rabbitmq为例,请详细说明

Spring Boot 的自动配置特性能够大大简化集成外部服务和组件的配置过程。以 RabbitMQ 为例&#xff0c;Spring Boot 通过 spring-boot-starter-amqp 提供了自动配置支持&#xff0c;开发者只需在应用中添加相关依赖并配置必要的属性&#xff0c;Spring Boot 会自动配置所需的连…

visual studio 安全模式

一、安全模式&#xff1a; 在 Visual Studio 中&#xff0c;安全模式是一种启动方式&#xff0c;允许你在禁用所有扩展和自定义设置的情况下启动 Visual Studio。这个模式可以帮助排除插件或扩展引起的问题&#xff0c;特别是在 Visual Studio 无法正常启动时。 二、安全模式下…

使用SSH建立内网穿透,能够访问内网的web服务器

搞了一个晚上&#xff0c;终于建立了一个内网穿透。和AI配合&#xff0c;还是得自己思考&#xff0c;AI配合才能搞定&#xff0c;不思考只依赖AI也不行。内网服务器只是简单地使用了python -m http.server 8899&#xff0c;但是对于Gradio建立的服务器好像不行&#xff0c;会出…

服务器信息整理:用途、操作系统安装日期、设备序列化、IP、MAC地址、BIOS时间、系统

文章目录 引言I BIOS时间Windows查看BIOS版本安装日期linux查看BIOS时间II 操作系统安装日期LinuxWindowsIII MAC 地址IV 设备序列号Linux 查看主板信息知识扩展Linux常用命令引言 信息内容:重点信息:用途、操作系统安装日期、设备序列化、IP、MAC地址、BIOS时间、系统 Linux…

java项目之读书笔记共享平台(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的闲一品交易平台。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 读书笔记共享平台的主要使…

【信息系统项目管理师】【综合知识】【备考知识点】【思维导图】第十一章 项目成本管理

word版☞【信息系统项目管理师】【综合知识】【备考知识点】第十一章 项目成本管理 移动端【思维导图】☞【信息系统项目管理师】【思维导图】第十一章 项目成本管理

1、单片机寄存器-io输入实验笔记

1、硬件 时钟总线如下&#xff1a; PB端口挂载在AHB1总线上&#xff0c;因此要对该位进行使能。 引脚 LED0和LED1挂载在PB0和PB1上&#xff1a;推挽输出、100M、 上拉默认高电平&#xff0c;低电平点亮。 2、软件 位带操作 #ifndef _IO_BIT_H_ #define _IO_BIT_H_#define …