材料准备 和 MAX7219 点阵屏
MAX7219 驱动的点阵屏在网上各种 Arduino 教程中无处不在,不过在本页面中,将结合使用 MD_Parola
和 MD_MAX72XX
这两个 Arduino 库,使用这两个库将会使在点阵屏上显示滚动文本和其他动画变得非常容易。 本文示例的最终效果如下图所示:
MAX7219 LED 驱动器可用于控制 64 个单独的LED,该驱动器通过 SPI 与 Arduino 进行通信。由于 MAX7219 最多可以控制 64 个LED,它可以驱动的最大尺寸的点阵显示器是 8×8 像素。然而,将多个驱动器和点阵屏串联起来,可以轻松控制更大的显示屏,如 8×32、8×64,甚至更大。
在本例中将使用到以下一些电子模块或者材料配件:
电子模块或者材料配件 | 数量 |
8×32 MAX7219 LED点阵显示屏 | 1 |
D1 mini 控制板 | 1 |
连接线 | 若干 |
Micro USB 数据线 | 1 |
下面是一个典型的 MAX7219 8×32 LED 点阵屏的规格:
电压 | 5 V |
驱动 | MAX7219 x 4 |
亮度等级 | 16 |
尺寸 | 32 x 128 x 15 mm |
像素点 | 8×32, ⌀ 3 mm |
硬件接线方式
MAX7219 LED 点阵屏驱动器通过 SPI(Serial Peripheral Interface,串行外设接口)与 Arduino 进行通信。通过 SPI 接口,一个主设备(Arduino)控制外围设备(也称为从属设备)。通过 Arduino 的硬件 SPI 接口 或 三个任意的数字引脚(软件 SPI)来控制点阵屏。硬件 SPI 引脚(MOSI、MISO 和 SCK)在每种 Arduino 板上有其特定的位置。这比起使用软件 SPI 要快,但需要使用以下固定的输出引脚。各个常用的 Arduino 硬件 SPI 针脚位置如下表所示:
控制板 | MOSI | MISO | SCK | 电压 |
---|---|---|---|---|
Arduino Uno | 11 or ICSP-4 | 12 or ICSP-1 | 13 or ICSP-3 | 5 V |
Arduino Mega | 51 or ICSP-4 | 50 or ICSP-1 | 52 or ICSP-3 | 5 V |
Arduino Leonardo | ICSP-4 | ICSP-1 | ICSP-3 | 5 V |
而在本例中将使用 D1 mini 进行 MAX7219 点阵屏的控制,连接方式如下表所示。如果将第一个点阵屏的 DOUT 连接到下一个点阵屏的 DIN 上,可以使所有点阵屏之间共享 VCC、GND、CLK 和 CS。
MAX7219 LED 点阵屏 | D1 mini |
CLK | D2 |
DATA 或 DIN | D4 |
CS | D3 |
GND | GND |
VCC | 5V |
一般来说,这样大小的点阵屏(4块 8*8 串联)组合使用控制板的供电就没问题,但是如果想控制一个大的点阵屏组合,建议使用外部电源。
库文件安装
如何安装库文件可以查看这篇文章:Arduino 制造光污染 | 全彩灯带 WS2812 中“找一个合适的库”这一节。
MD_Parola 库可用于创建许多不同的文本动画,如滚动文本效果。这个库依赖 MD_MAX72XX 库,它实现了 LED 点阵屏的硬件功能。在本例中软件部分将使用 Arduino IDE,此外还将使用到以下一些库文件:
为了配合本页中的示例代码,还需要以下库文件以及 ESP8266 的 Arduino 官方库:
库名称 | 作用 | 下载地址 |
NTPClient | 提供与 NTP 服务器主机的连线,并回传日期时间。 | Github |
示例代码
在 Arduino 代码中设置点阵屏时,需要将 HARDWARE_TYPE 设置为 FC16_HW,并指定所连接的设备数量。一个 8×8 的点阵屏算作一个设备,所以如果想控制一个 8×32 的模块,那需要将 MAX_DEVICES 设置为 4(一个8×32的点阵屏包含 4 个 MAX7219),设置针脚和参数如下图所示:
#include <ESP8266WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
String formattedDate;
String dayStamp;
String timeStamp;
#define DEMO_DURATION 5000
typedef void (*Demo)(void);
int modeNumber = 0;
// Define the number of devices we have in the chain and the hardware interface
// NOTE: These pin numbers are for ESO8266 hardware SPI and will probably not
// work with your hardware and may need to be adapted
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define CLK_PIN D2 // or SCK
#define DATA_PIN D4 // or MOSI
#define CS_PIN D3 // or SS
#define buttonIO D1
// SOFTWARE SPI
MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
// Scrolling parameters
uint8_t scrollSpeed = 25; // default frame delay value
textEffect_t scrollEffect = PA_SCROLL_LEFT;
textPosition_t scrollAlign = PA_LEFT;
uint16_t scrollPause = 2000; // in milliseconds
// Global message buffers shared by Wifi and Scrolling functions
#define BUF_SIZE 512
char curMessage[BUF_SIZE] = { "" };
char newMessage[BUF_SIZE] = { "Hello! Enter new message?" };
bool newMessageAvailable = true;
int state = 0;
int buttonState = 0;
int timeCount = 0;
const char* ssid = "ce";
const char* password = "ce";
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);
void setup() {
Serial.begin(57600);
P.begin();
WiFi.begin(ssid, password);
Serial.print("\n\r \n\rWorking to connect");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
timeClient.begin();
// 时区 *60分 * 60秒,例如:
// GMT +1 = 3600
// GMT +8 = 28800
// GMT 0 = 0
timeClient.setTimeOffset(28800);
pinMode(buttonIO, INPUT);
}
void loop() {
while (!timeClient.update()) {
timeClient.forceUpdate();
}
buttonState = digitalRead(buttonIO);
if (buttonState == LOW) {
state = 2;
}
if (P.displayAnimate()) {
// formattedDate 取得的日期格式为:2018-05-28T16:00:13Z
formattedDate = timeClient.getFormattedDate();
Serial.println(formattedDate);
// 获取日期
int splitT = formattedDate.indexOf("T");
dayStamp = formattedDate.substring(0, splitT);
Serial.print("DATE: ");
Serial.println(dayStamp);
// 获取时间
timeStamp = formattedDate.substring(splitT + 1, formattedDate.length() - 1);
Serial.print("TIME:");
Serial.println(timeStamp);
switch (state) {
case 0: {
timeStamp.toCharArray(newMessage, timeStamp.length() + 1);
strcpy(curMessage, newMessage);
newMessageAvailable = true;
state = 1;
P.displayText(curMessage, scrollAlign, scrollSpeed, scrollPause, scrollEffect, scrollEffect);
break;
}
case 1: {
dayStamp.toCharArray(newMessage, dayStamp.length() + 1);
strcpy(curMessage, newMessage);
newMessageAvailable = true;
state = 0;
P.displayText(curMessage, scrollAlign, scrollSpeed, scrollPause, scrollEffect, scrollEffect);
break;
}
case 2: {
scrollPause = random(50, 800);
scrollSpeed = random(5, 25);
timeCount = timeCount + scrollPause + scrollSpeed * 32;
Serial.println(timeCount);
String newMessageTemp = "666";
newMessageTemp.toCharArray(newMessage, newMessageTemp.length() + 1);
strcpy(curMessage, newMessage);
newMessageAvailable = true;
P.displayText(curMessage, scrollAlign, scrollSpeed, scrollPause, scrollEffect, scrollEffect);
state = 3;
break;
}
case 3: {
scrollPause = 3000;
scrollSpeed = 50;
String newMessageTemp = String(timeCount)+" ms";
newMessageTemp.toCharArray(newMessage, newMessageTemp.length() + 1);
strcpy(curMessage, newMessage);
newMessageAvailable = true;
state = 0;
P.displayText(curMessage, scrollAlign, scrollSpeed, scrollPause, scrollEffect, scrollEffect);
timeCount = 0;
break;
}
}
P.displayReset();
}
}