MAX72 LED Matrix Example — WiFi Crypto Ticker - RobotDynOfficial/Documentation GitHub Wiki

LED Matrix WiFi Crypto Ticker

In this example we build a fancy gadget: standalone WiFi device that can display live exchange rates of various cryptocurrencies on a gorgeous Robotdyn LED Matrix display that resembles stock exchange tickers.

MCU

We will use RobotDyn WiFi D1 MINI microcontroller. This tiny board is a full-featured microcontroller with WiFi capability. It can be programmed in Arduino IDE with C++ or MicroPython. In this example we will use Arduino IDE and C++. To program this board with Arduino IDE make sure to set it up for 8266 controllers. If it is not configured for using 8266 controllers, open File/Preferences dialog and copy http://arduino.esp8266.com/stable/package_esp8266com_index.json to the Additional Boards Manager URLs input field. Then go to Tools menu and choose "LOLIN (WEMOS) D1 R2 MINI" in the Boards sub-menu.

MAX7219/7221 LED Matrix

MAX7219/7221 LED matrix driver chip allows to control a LED matrix module with only 5 SPI pins, whereas raw LED matrix would require 8(rows)+8(columns)+1(GND)=17 pins! But not only that, you can daisy-chain LED matrix modules and still need only 5 pins to control them! In this example we've soldered a chain of 4 LED modules, but if you wish, you can add more. Just change MAX_DEVICES accordingly.

Wiring

You only need to connect the MCU to the first of the matrices and solder the matrix modules to each other, SDO to SDI, SCL->SCL, CS->CS, GND->GND, 5V->5V.

MCU LED Matrix
5V 5V
GND GND
D7 SDI
D8 CS
D5 SCL

Network Set-up

In this sketch we will use a hard-coded WiFi SSID and password. Change "SSID" and "PASSWORD" values to correct SSID and password of your WiFi network.

Exchange Data Access

The microcontroller will access APIs of 2 crypto exchanges: Coinbase at https://api.pro.coinbase.com and Binance at https://api.binance.com and retieve trade data in JSON format. The authenticity of connections is being checked by hard-coded SHA-1 fingerprints of these HTTPS sites. This is necessary to make sure that your ticker is not tricked by a malicious hacker to displaying wrong exchange rates leading you to bad financial decisions! If your device fails to connect to any of these services please check the actual SHA-1 fingerprint of the service by opening it's URL in your browser going to the service URL (e.g., api.binance.com) and checking the fingerprint by clicking on the lock symbol in the address bar. If it doesn't match with the value in the sketch, please correct it. Fingerprints are correct as of the time of publication.

Tuning the Sketch to Your Preferences

You can choose what coins you want to display in configureCoins() function and setting the correct number of coins being monitored in TOTAL_COINS #define.

Set the correct number of LED matrix modules in the chain in #define MAX_DEVICES

The Sketch

#include "ESP8266WiFi.h"
#include "WiFiClientSecure.h"
#include "ArduinoJson.h"
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <stdio.h>

#define HARDWARE_TYPE MD_MAX72XX::GENERIC_HW

// Define the number of devices we have in the chain:
#define MAX_DEVICES 4

#define CLK_PIN   D5 // or SCK
#define DATA_PIN  D7 // or MOSI
#define CS_PIN    D8 // or SS

// SPI hardware interface
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

// We always wait a bit between updates of the display
#define  DELAYTIME  100  // in milliseconds

// Number of coins
#define TOTAL_COINS 2

const char* ssid = "SSID";
const char* password = "PASSWORD";

const char* coinbaseHost = "api.pro.coinbase.com";
const char* coinbaseFingerprint = "B4 1C 06 53 0E 93 AB DA CF B3 59 7F 3B F7 42 14 D7 00 16 55";

const char* binanceHost = "api.binance.com";
const char* binanceFingerprint = "41 82 D2 BA 64 E3 36 F1 3C 5E 49 05 2A A0 AA CB D0 F7 2B B7";


struct Coin {
  String url;
  String ticker; 
  bool isCoinbaseCoin;
};

struct Coin cryptoCoins[TOTAL_COINS]; 

void scrollText(char *p) {
    uint8_t charWidth;
    uint8_t cBuf[8];  // this should be ok for all built-in fonts
    mx.clear();
    
    while (*p != '\0')
    {
        charWidth = mx.getChar(*p++, sizeof(cBuf) / sizeof(cBuf[0]), cBuf);
        
        for (uint8_t i=0; i<=charWidth; i++)  // allow space between characters
        {
            mx.transform(MD_MAX72XX::TSL);
            if (i < charWidth)
                mx.setColumn(0, cBuf[i]);
            delay(DELAYTIME);
        }
    }
}

void connectToWIFI() {
    Serial.println();
    Serial.print("connecting to ");
    Serial.println(ssid);
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());

}


JsonObject& getJsonObject(String url, bool isCoinbaseCoin) {
    const size_t capacity = (isCoinbaseCoin) ? JSON_OBJECT_SIZE(7) + 252 : JSON_OBJECT_SIZE(2) + 74;
    DynamicJsonBuffer jsonBuffer(capacity);
    
    // Use WiFiClientSecure class to create TLS connection
    WiFiClientSecure client;
    client.setTimeout(10000);
    
    const char* host = (isCoinbaseCoin) ? coinbaseHost : binanceHost;
    const char* fingerprint = (isCoinbaseCoin) ? coinbaseFingerprint : binanceFingerprint;
    Serial.println(host);
    Serial.printf("Using fingerprint '%s'\n", fingerprint);
    client.setFingerprint(fingerprint);
    
    if (!client.connect(host, 443)) {
        Serial.println("connection failed");
        scrollText("Connection failed!");
        // No further work should be done if the connection failed
        return jsonBuffer.parseObject(client);
    }
    Serial.println(F("Connected!"));
    
    // Send HTTP Request
    String httpEnding = (isCoinbaseCoin) ? " HTTP/1.1\r\n" : " HTTP/1.0\r\n";
    client.print(String("GET ") + url + httpEnding +
                 "Host: " + host + "\r\n" +
                 "User-Agent: BuildFailureDetectorESP8266\r\n" +
                 "Connection: close\r\n\r\n");
    Serial.println("request sent");
    
    // Check HTTP Status
    char status[32] = {0};
    client.readBytesUntil('\r', status, sizeof(status));
    if (strcmp(status, "HTTP/1.1 200 OK") != 0) {
        Serial.print(F("Unexpected response: "));
        Serial.println(status);
        return jsonBuffer.parseObject(client);
    }
    
    // Skip HTTP headers
    char endOfHeaders[] = "\r\n\r\n";
    if (!client.find(endOfHeaders)) {
        Serial.println(F("Invalid response"));
        //scrollText("Invalid Response");
    }
    
    // Parse JSON object
    JsonObject& root = jsonBuffer.parseObject(client);
    if (!root.success()) {
        Serial.println(F("Parsing failed!"));
        //scrollText("JSON Parse Failed!");
    }
    
    // Disconnect
    client.stop();
    jsonBuffer.clear();
    return root;
}

float bitcoinPrice = 0.0;

float convertToUSD(float cryptoPrice) {
    if (bitcoinPrice == 0.0) { getBitcoinPrice(); }
    return bitcoinPrice * cryptoPrice;
}

void getBitcoinPrice() {
    JsonObject& root = getJsonObject("/api/v1/ticker/price?symbol=BTCUSDC", false);
    Serial.print("The Bitcoin price is:");
    Serial.println(root["price"].as<float>());
    bitcoinPrice = root["price"].as<float>();
}

void getCoinPrice(String url, String cryptoName, bool isCoinbaseCoin) {
    JsonObject& root = getJsonObject(url, isCoinbaseCoin);
    Serial.println("==========");
    Serial.println(F("Response:"));
    Serial.print("Symbol: ");
    Serial.println(root["symbol"].as<char*>());
    Serial.print("Price: ");
    Serial.println(root["price"].as<char*>());
    
    float cryptoPrice = root["price"].as<float>();
    cryptoPrice = (isCoinbaseCoin) ? cryptoPrice : convertToUSD(cryptoPrice);
    Serial.println(cryptoPrice);
    Serial.println("==========");
    String output = cryptoName + " $" + String(cryptoPrice);
    Serial.println(output);
    
    // Update the bitcoinPrice if the User requests the Bitcoin Price
    bitcoinPrice = (cryptoName == "BTC") ? cryptoPrice : bitcoinPrice;
    
    char *cstr = new char[output.length() + 1];
    strcpy(cstr, output.c_str());
    scrollText(cstr);
    delete [] cstr;
}

void configureCoins() { 
  Coin bitcoin = { .url = "/products/BTC-USD/ticker", .ticker = "BTC", .isCoinbaseCoin = true }; 
//  Coin ethereum = { .url = "/products/ETH-USD/ticker", .ticker = "ETH", .isCoinbaseCoin = true };
//  Coin litcoin = { .url = "/products/LTC-USD/ticker", .ticker = "LTC", .isCoinbaseCoin = true };
//  Coin bitcoinCash = { .url = "/products/BCH-USD/ticker", .ticker = "BCH", .isCoinbaseCoin = true };

  
  // Lets add in some Binanace Coins
//  Coin ethereumClassic = { .url = "/api/v1/ticker/price?symbol=ETCBTC", .ticker = "ETC", .isCoinbaseCoin = false }; 
  Coin monero = { .url = "/api/v1/ticker/price?symbol=XMRBTC", .ticker = "Monero", .isCoinbaseCoin = false };
//  Coin stellar = { .url = "/api/v1/ticker/price?symbol=XLMBTC", .ticker = "Stellar", .isCoinbaseCoin = false };
//  Coin iota = { .url = "/api/v1/ticker/price?symbol=IOTABTC", .ticker = "IOTA", .isCoinbaseCoin = false };
//  Coin dash = { .url = "/api/v1/ticker/price?symbol=DASHBTC", .ticker = "Dash", .isCoinbaseCoin = false }; 
//  Coin nano = { .url = "/api/v1/ticker/price?symbol=NANOBTC", .ticker = "NANO", .isCoinbaseCoin = false};

  cryptoCoins[0] =  bitcoin;
  cryptoCoins[1] =  monero;
  /*
  cryptoCoins[2] =  litcoin;
  cryptoCoins[3] =  bitcoinCash;
  cryptoCoins[4] = ethereumClassic;
  cryptoCoins[5] =  ethereum;
  cryptoCoins[6] =  stellar;
  cryptoCoins[7] =  iota;
  cryptoCoins[8] =  dash;
  cryptoCoins[9] =  nano;
*/
}

void getAllCoinPrices() {
  Coin currentCoin;
  for(int index=0; index < TOTAL_COINS; index++) {
    currentCoin = cryptoCoins[index];
    getCoinPrice(currentCoin.url, currentCoin.ticker, currentCoin.isCoinbaseCoin);
  }
}

void setup() {
    mx.begin();
    Serial.begin(115200);
    connectToWIFI();
    configureCoins();
}

void loop() {
    getAllCoinPrices();
}

Results

⚠️ **GitHub.com Fallback** ⚠️