Tuesday, November 16, 2021

Arduino Nano RP2040 Connect generate QR Code and display on ST7735 TFT - software SPI

This post show how to generate QR Code with Nano RP2040 Connect (Arduino Framework) using QRCode library by Richard Moore, also display on ST7735 SPI TFT, and finally adapt to WiFiNINA > AP_SimpleWebServer example to control onboard LED.

Library

Install QRCode library by Richard Moore in Arduino IDE's Libraries Manager.


And, "Adafruit ST7735 and ST7789 Library" and "Adafruit GFX Library" are needed to display with ST7735 SPI TFT.

Connection

The hardware SPI of Arduino Nano RP2040 Connect are:
- (CIPO/MISO) - D12
- (COPI/MOSI) - D11
- (SCK) - D13
- (CS/SS) - Any GPIO (except for A6/A7)


In this exercise, we have to control the onboard LED connected to D13. it's conflict with SCK. So the exercise code here use software SPI.
/************************************************
 * TFT_ST7735 nano RP2040 Connect
 * ------------------------------
 * VCC        3V3
 * GND        GND
 * CS         10
 * RESET      9
 * A0(DC)     8
 * SDA        11
 * SCK        12
 * LED        3V3
 * **********************************************/
Exercise code

nanoRP2040_QRCode.ino
/**
 *  Arduino nano RP2040 Connect exercise to generate QR Code,
 *  with display on Serial Monitor and ST7735 SPI TFT.
 *  Modified from QRCode example.
 *  
 *  Library: QRCode by Richard  Moore
 *  https://github.com/ricmoo/qrcode/
 *  
 */
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include "qrcode.h"

/************************************************
 * TFT_ST7735 nano RP2040 Connect
 * ------------------------------
 * VCC        3V3
 * GND        GND
 * CS         10
 * RESET      9
 * A0(DC)     8
 * SDA        11
 * SCK        12
 * LED        3V3
 * **********************************************/
// TFT display use software SPI interface.
#define TFT_MOSI 11  // Data out
#define TFT_SCLK 12  // Clock out

#define TFT_CS  10  // Chip select line for TFT display
#define TFT_DC   8  // Data/command line for TFT
#define TFT_RST  9  // Reset line for TFT (or connect to VCC)
Adafruit_ST7735 tft_ST7735 = Adafruit_ST7735(TFT_CS, TFT_DC, 
                                  TFT_MOSI, TFT_SCLK, TFT_RST);

void setup() {
    delay(1000);
    Serial.begin(115200);
    delay(1000);
    Serial.println("- setup() started -");
    tft_ST7735.initR(INITR_BLACKTAB);
    tft_ST7735.setRotation(2);
    
    // tft display RGB to make sure it's work properly
    tft_ST7735.fillScreen(ST7735_BLACK);
    delay(300);
    tft_ST7735.fillScreen(ST7735_RED);
    delay(300);
    tft_ST7735.fillScreen(ST7735_GREEN);
    delay(300);
    tft_ST7735.fillScreen(ST7735_BLUE);
    delay(300);
    tft_ST7735.fillScreen(ST7735_WHITE);
    delay(300);

    // Start time
    uint32_t dt = millis();
  
    // Create the QR code
    QRCode qrcode;

    const char *data = "http://arduino-er.blogspot.com/";
    const uint8_t ecc = 0;  //lowest level of error correction
    const uint8_t version = 3;

    uint8_t qrcodeData[qrcode_getBufferSize(version)];
    qrcode_initText(&qrcode, 
                    qrcodeData, 
                    version, ecc, 
                    data);
  
    // Delta time
    dt = millis() - dt;
    Serial.print("QR Code Generation Time: ");
    Serial.print(dt);
    Serial.print("\n\n");
    
    Serial.println(data);
    Serial.print("qrcode.version: ");
    Serial.println(qrcode.version);
    Serial.print("qrcode.ecc: ");
    Serial.println(qrcode.ecc);
    Serial.print("qrcode.size: ");
    Serial.println(qrcode.size);
    Serial.print("qrcode.mode: ");
    Serial.println(qrcode.mode);
    Serial.print("qrcode.mask: ");
    Serial.println(qrcode.mask);
    Serial.println();

    const int xy_scale = 3;
    const int x_offset = (tft_ST7735.width() - xy_scale*qrcode.size)/2;
    const int y_offset = (tft_ST7735.height() - xy_scale*qrcode.size)/2;
    
    
    // Top quiet zone
    Serial.print("\n\n\n\n");
    for (uint8_t y = 0; y < qrcode.size; y++) {

        // Left quiet zone
        Serial.print("        ");

        // Each horizontal module
        for (uint8_t x = 0; x < qrcode.size; x++) {

            // Print each module (UTF-8 \u2588 is a solid block)
            bool mod = qrcode_getModule(&qrcode, x, y);
            //Serial.print(mod ? "\u2588\u2588": "  ");
            if(mod){
              Serial.print("██"); //same as "\u2588\u2588"
                                  //direct paste "██" copied from Serial Monitor
              int px = x_offset + (x * xy_scale);
              int py = y_offset + (y * xy_scale);
              tft_ST7735.fillRect(px, py, xy_scale, xy_scale, ST7735_BLACK);
              
            }else{
              Serial.print("  ");
            }
        }

        Serial.print("\n");
    }

    // Bottom quiet zone
    Serial.print("\n\n\n\n");
}

void loop() {

}


nanoRP2040_QRCode_web.ino
/**
 *  Arduino nano RP2040 Connect exercise to generate QR Code,
 *  WiFiWebServer with QRCode on ST7735 SPI TFT
 *  
 *  Library: QRCode by Richard  Moore
 *  https://github.com/ricmoo/qrcode/
 *  
 */
#include <WiFiNINA.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include "qrcode.h"

/************************************************
 * TFT_ST7735 nano RP2040 Connect
 * ------------------------------
 * VCC        3V3
 * GND        GND
 * CS         10
 * RESET      9
 * A0(DC)     8
 * SDA        11
 * SCK        12
 * LED        3V3
 * **********************************************/
// TFT display use software SPI interface.
//
// Hardware SPI pins are specific to the Arduino board type and
// cannot be remapped to alternate pins.
//
// In this exercise, we are going to implement a web server to
// turn ON/OFF on board LED, which is connected to D13. It's
// conflict with hardware SPI SCK.
// So we cannot use hardware SPI to controll ST7735.
#define TFT_MOSI 11  // Data out
#define TFT_SCLK 12  // Clock out

#define TFT_CS  10  // Chip select line for TFT display
#define TFT_DC   8  // Data/command line for TFT
#define TFT_RST  9  // Reset line for TFT (or connect to VCC)
Adafruit_ST7735 tft_ST7735 = Adafruit_ST7735(TFT_CS, TFT_DC,
                                  TFT_MOSI, TFT_SCLK, TFT_RST);

char ssid[] = "ssid";       // your network SSID (name)
char pass[] = "password";   // your network password (use for WPA, or use as key for WEP)

int status = WL_IDLE_STATUS;
WiFiServer server(80);

void setup() {
    
    delay(1000);
    Serial.begin(115200);
    delay(1000);
    Serial.println("- setup() started -");
    Serial.print("LED_BUILTIN: ");
    Serial.println(LED_BUILTIN);
    pinMode(LED_BUILTIN, OUTPUT);      // set the LED pin mode
    tft_ST7735.initR(INITR_BLACKTAB);
    tft_ST7735.setRotation(2);
    tft_ST7735.setTextWrap(true);
    tft_ST7735.setTextColor(ST77XX_BLACK);
    
    // tft display RGB to make sure it's work properly
    digitalWrite(LED_BUILTIN, HIGH);
    tft_ST7735.fillScreen(ST7735_BLACK);
    delay(300);
    digitalWrite(LED_BUILTIN, LOW);
    tft_ST7735.fillScreen(ST7735_RED);
    delay(300);
    digitalWrite(LED_BUILTIN, HIGH);
    tft_ST7735.fillScreen(ST7735_GREEN);
    delay(300);
    digitalWrite(LED_BUILTIN, LOW);
    tft_ST7735.fillScreen(ST7735_BLUE);
    delay(300);
    digitalWrite(LED_BUILTIN, HIGH);
    tft_ST7735.fillScreen(ST7735_WHITE);
    delay(300);

    // check for the WiFi module:
    if (WiFi.status() == WL_NO_MODULE) {
      Serial.println("Communication with WiFi module failed!");
      tft_ST7735.setCursor(0, 0);
      tft_ST7735.print("Communication with WiFi module failed!");
      // don't continue
      while (true);
    }

    Serial.print("WiFi.firmwareVersion(): ");
    Serial.println(WiFi.firmwareVersion());

    // attempt to connect to WiFi network:
    while (status != WL_CONNECTED) {
      Serial.print("Attempting to connect to SSID: ");
      Serial.println(ssid);
      // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
      status = WiFi.begin(ssid, pass);
      
      // wait 10 seconds for connection:
      delay(10000);
    }
    server.begin();
    // you're connected now, so print out the status:
    printWifiStatus();

    // Start time
    uint32_t dt = millis();
  
    // Create the QR code
    QRCode qrcode;

    char myIpQrData[30] ="http://";
    prepareIpQrData(myIpQrData);
    
    const uint8_t ecc = 0;  //lowest level of error correction
    const uint8_t version = 3;

    uint8_t qrcodeData[qrcode_getBufferSize(version)];
    qrcode_initText(&qrcode, 
                    qrcodeData, 
                    version, ecc, 
                    myIpQrData);
  
    // Delta time
    dt = millis() - dt;
    Serial.print("QR Code Generation Time: ");
    Serial.print(dt);
    Serial.print("\n\n");
    
    Serial.println(myIpQrData);
    Serial.print("qrcode.version: ");
    Serial.println(qrcode.version);
    Serial.print("qrcode.ecc: ");
    Serial.println(qrcode.ecc);
    Serial.print("qrcode.size: ");
    Serial.println(qrcode.size);
    Serial.print("qrcode.mode: ");
    Serial.println(qrcode.mode);
    Serial.print("qrcode.mask: ");
    Serial.println(qrcode.mask);
    Serial.println();

    const int xy_scale = 3;
    const int x_offset = (tft_ST7735.width() - xy_scale*qrcode.size)/2;
    const int y_offset = (tft_ST7735.height() - xy_scale*qrcode.size)/2;
    
    // Top quiet zone
    Serial.print("\n\n\n\n");
    for (uint8_t y = 0; y < qrcode.size; y++) {

        // Left quiet zone
        Serial.print("        ");

        // Each horizontal module
        for (uint8_t x = 0; x < qrcode.size; x++) {

            // Print each module (UTF-8 \u2588 is a solid block)
            bool mod = qrcode_getModule(&qrcode, x, y);
            //Serial.print(mod ? "\u2588\u2588": "  ");
            if(mod){
              Serial.print("██"); //same as "\u2588\u2588"
                                  //direct paste "██" copied from Serial Monitor
              int px = x_offset + (x * xy_scale);
              int py = y_offset + (y * xy_scale);
              tft_ST7735.fillRect(px, py, xy_scale, xy_scale, ST7735_BLACK);
              
            }else{
              Serial.print("  ");
            }
        }

        Serial.print("\n");
    }

    // Bottom quiet zone
    Serial.print("\n\n\n\n");
    digitalWrite(LED_BUILTIN, LOW);
}

//prepare ip address in char *,
//to pass to qrcode_initText()
char * prepareIpQrData(char *dest){
  Serial.println("***********************");
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);
  Serial.println(ip[0]);
  Serial.println(ip[1]);
  Serial.println(ip[2]);
  Serial.println(ip[3]);

  Serial.println("-------------");
  char buffer0[3];
  char buffer1[3];
  char buffer2[3];
  char buffer3[3];
  itoa(ip[0], buffer0, 10);
  itoa(ip[1], buffer1, 10);
  itoa(ip[2], buffer2, 10);
  itoa(ip[3], buffer3, 10);

  char str[15] = "";
  char dot[] = ".";

  strcat(dest, buffer0);
  strcat(dest, dot);
  strcat(dest, buffer1);
  strcat(dest, dot);
  strcat(dest, buffer2);
  strcat(dest, dot);
  strcat(dest, buffer3);
  
  Serial.println(dest);
  Serial.println("***********************");
  
}

//The web page part is mainly modified 
//from WiFiNINA example > AP_SimpleWebServer
//that lets you blink an LED via the web.
void loop() {
  // compare the previous status to the current status
  if (status != WiFi.status()) {
    // it has changed update the variable
    status = WiFi.status();

    if (status == WL_AP_CONNECTED) {
      // a device has connected to the AP
      Serial.println("Device connected to AP");
    } else {
      // a device has disconnected from the AP, and we are back in listening mode
      Serial.println("Device disconnected from AP");
    }
  }
  
  WiFiClient client = server.available();   // listen for incoming clients

  if (client) {                             // if you get a client,
    Serial.println("new client");           // print a message out the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      delayMicroseconds(10);                // This is required for the Arduino Nano RP2040 Connect 
                                            // - otherwise it will loop so fast that SPI will 
                                            // never be served.
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        if (c == '\n') {                    // if the byte is a newline character

          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();

            client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            
            // the content of the HTTP response follows the header:
            client.print("Click <a href=\"/H\">here</a> turn the LED on<br>");
            client.print("Click <a href=\"/L\">here</a> turn the LED off<br>");

            // The HTTP response ends with another blank line:
            client.println();
            // break out of the while loop:
            break;
          }
          else {      // if you got a newline, then clear currentLine:
            currentLine = "";
          }
        }
        else if (c != '\r') {    // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }

        // Check to see if the client request was "GET /H" or "GET /L":
        if (currentLine.endsWith("GET /H")) {
          Serial.println("\n===> GET /H");
          digitalWrite(LED_BUILTIN, HIGH);               // GET /H turns the LED on
        }
        if (currentLine.endsWith("GET /L")) {
          Serial.println("\n===> GET /L");
          digitalWrite(LED_BUILTIN, LOW);                // GET /L turns the LED off
        }
      }
    }
    // close the connection:
    client.stop();
    Serial.println("client disconnected");
  }
}

void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your board's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

Related:

Tuesday, November 9, 2021

Arduino Nano RP2040 Connect + ST7735 SPI TFT with SD Card, read and display bitmap, using hardware SPI.

Exercises run on Arduino Nano RP2040 Connect (in Arduino framework) work with 1.8" 128x160 ST7735 SPI TFT/SD Card Module, to:
- simple display something on ST7735 SPI TFT
- Display files in SD Card, and read txt file from SD Card.
- Read bmp from SD Card and display on ST7735 SPI TFT
- Try example of Adafruit_ImageReader
- Simplified version display bitmap from SD and display on TFT using Adafruit_ImageReader.

In the following exercise Hardware SPI is used for ST7735 and SD Card SPI interface. refer to last post, SPI pins in Arduino Nano RP2040 Connect is:
- MISO    - D12
- MOSI    - D11
- SCK      - D13

Connection:
 * Nano RP2040 Connect drive ST7735 SPI and SD using hardware SPI
 * 
 * TFT_ST7735 nano RP2040 Connect
 * ------------------------------
 * VCC        3V3
 * GND        GND
 * CS         10
 * RESET      9
 * A0(DC)     8
 * SDA        11
 * SCK        13
 * LED        3V3
 * 
 * TFT_ST7735 nano RP2040 Connect
 * ------------------------------
 * SD_CS      4
 * SD_MOSI    11
 * SD_MISO    12
 * SD_SCK     13
 ****************************************************/

Exercise code:

Please notice that  "Adafruit ST7735 and ST7789 Library" and "Adafruit GFX Library" are needed, make sure it's installed in Arduino IDE's Libraries Manager.

nanoRP2040_ST7735.ino, a simple exercise to display something on ST7735 SPI TFT.

/***************************************************
 * nano RP2040 Connect exercise
 * + ST7745/SD
 * 
 * On Arduino nanp RP2040 Connect:
 * (CIPO/MISO)  - D12
 * (COPI/MOSI)  - D11
 * (SCK)        - D13
 * (CS/SS) - Any GPIO (except for A6/A7
 * 
 * This example drive ST7735 SPI and SD using hardware SPI
 * 
 * TFT_ST7735 nano RP2040 Connect
 * ------------------------------
 * VCC        3V3
 * GND        GND
 * CS         10
 * RESET      9
 * A0(DC)     8
 * SDA        11
 * SCK        13
 * LED        3V3
 * 
 ****************************************************/

#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SD.h>
#include <SPI.h>

// TFT display using hardware SPI interface.
// Hardware SPI pins are specific to the Arduino board type and
// cannot be remapped to alternate pins.
#define TFT_CS  10  // Chip select line for TFT display
#define TFT_DC   8  // Data/command line for TFT
#define TFT_RST  9  // Reset line for TFT (or connect to +5V)

Adafruit_ST7735 tft_ST7735 = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

void setup(void) {
  delay(1000);
  Serial.begin(115200);
  delay(1000);
  Serial.println("=====================");
  Serial.println("- setup() -");
  tft_ST7735.initR(INITR_BLACKTAB);
  Serial.println("tft: " 
                  + String(tft_ST7735.width()) + " : " 
                  + String(tft_ST7735.height()));

  tft_ST7735.fillScreen(ST7735_BLACK);
  tft_ST7735.setTextWrap(true);
  tft_ST7735.setTextColor(ST77XX_WHITE);
  tft_ST7735.setCursor(0, 0);
  tft_ST7735.print("Arduino nano RP2040 Connect");
  tft_ST7735.println("\n");

  //----------------------------------------
  delay(2000);

  tft_ST7735.setRotation(3);
  tft_ST7735.setCursor(0, 30);
  tft_ST7735.print("rotation: " + String(tft_ST7735.getRotation()));
  tft_ST7735.setCursor(0, 40);
  tft_ST7735.print(String(tft_ST7735.width()) + " x " + String(tft_ST7735.height()));
  
  delay(2000);

  tft_ST7735.fillScreen(ST77XX_RED);
  tft_ST7735.setCursor(50, 50);
  tft_ST7735.print("RED");
  delay(1000);
  tft_ST7735.fillScreen(ST77XX_GREEN);
  tft_ST7735.setCursor(50, 50);
  tft_ST7735.print("GREEN");
  delay(1000);
  tft_ST7735.fillScreen(ST77XX_BLUE);
  tft_ST7735.setCursor(50, 50);
  tft_ST7735.print("BLUE");
  delay(1000);

  delay(1000);
  
  //----------------------------------------

  Serial.println("\n- End of setup() -\n");
}


void loop() {

  tft_ST7735.fillScreen(ST77XX_BLUE);
  for(int offset=0; offset<tft_ST7735.height()/2; offset++){
    int col;
    if(offset%5 == 0)
      col = ST77XX_WHITE;
    else
      col = ST77XX_BLACK;
      
    tft_ST7735.drawRect(offset, offset, 
                 tft_ST7735.width()-2*offset, tft_ST7735.height()-2*offset,
                 col);
    delay(100);
  }

  delay(2000);

  tft_ST7735.fillScreen(ST77XX_BLACK);
  int cx = tft_ST7735.width()/2;
  int cy = tft_ST7735.height()/2;
  for(int r=0; r<tft_ST7735.height()/2; r=r+10){

    tft_ST7735.drawCircle(cx, cy, 
                 r,
                 ST77XX_WHITE);
    delay(200);
  }

  delay(2000);
  delay(2000);
}


nanoRP2040_ST7735_SD.ino, list files in SD card and read the text file hello.txt.
/***************************************************
 * nano RP2040 Connect exercise
 * + ST7745/SD
 * 
 * On Arduino nanp RP2040 Connect:
 * (CIPO/MISO) - D12
 * (COPI/MOSI) - D11
 * (SCK) - D13
 * (CS/SS) - Any GPIO (except for A6/A7
 * 
 * This example drive ST7735 SPI and SD using hardware SPI
 * 
 * TFT_ST7735 nano RP2040 Connect
 * ------------------------------
 * VCC        3V3
 * GND        GND
 * CS         10
 * RESET      9
 * A0(DC)     8
 * SDA        11
 * SCK        13
 * LED        3V3
 * 
 * TFT_ST7735 nano RP2040 Connect
 * ------------------------------
 * SD_CS      4
 * SD_MOSI    11
 * SD_MISO    12
 * SD_SCK     13
 ****************************************************/

#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SD.h>
#include <SPI.h>

// TFT display and SD card will share the hardware SPI interface.
// Hardware SPI pins are specific to the Arduino board type
#define SD_CS    4  // Chip select line for SD card
#define TFT_CS  10  // Chip select line for TFT display
#define TFT_DC   8  // Data/command line for TFT
#define TFT_RST  9  // Reset line for TFT (or connect to +5V)

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

File root;
File myFile;

void setup(void) {
  delay(1000);
  Serial.begin(115200);
  delay(1000);
  Serial.println("=====================");
  Serial.println("- setup() -");
  tft.initR(INITR_BLACKTAB);
  Serial.println("tft: " 
                  + String(tft.width()) + " : " 
                  + String(tft.height()));

  tft.fillScreen(ST7735_BLACK);
  tft.setTextWrap(true);
  tft.setTextColor(ST77XX_WHITE);
  tft.setCursor(0, 0);
  tft.print("Arduino nano RP2040 Connect");
  tft.println("\n");

  //----------------------------------------
  Serial.println("Initializing SD card...");
  if (!SD.begin(SD_CS)){
    Serial.println("SD.begin() failed!");
  }
  else{
    Serial.println("SD.begin() Success.");

    root = SD.open("/");
    printDirectory(root, 0);

    Serial.println("===============================");
    // open the file for reading:
    myFile = SD.open("hello.txt");
    if (myFile) {
      Serial.println("test.txt:");
      Serial.println("-------------------------------");
      // read from the file until there's nothing else in it:
      while (myFile.available()) {
      //Serial.write(myFile.read());
      byte b = myFile.read();
      Serial.write(b);
      tft.print((char)b);
      }
      // close the file:
      myFile.close();
    } else {
      // if the file didn't open, print an error:
      Serial.println("error opening test.txt");
    }
    Serial.println("\n===============================\n");

    
  }


  //----------------------------------------

  Serial.println("\n- End of setup() -\n");
}


void loop() {

  delay(100);
}

//
// printDirectory() copy from:
// Examples > SD > listfiles
//

void printDirectory(File dir, int numTabs) {
  while (true) {

    File entry =  dir.openNextFile();
    if (! entry) {
      // no more files
      break;
    }
    for (uint8_t i = 0; i < numTabs; i++) {
      Serial.print('\t');
    }
    Serial.print(entry.name());
    if (entry.isDirectory()) {
      Serial.println("/");
      printDirectory(entry, numTabs + 1);
    } else {
      // files have sizes, directories do not
      Serial.print("\t\t");
      Serial.println(entry.size(), DEC);
    }
    entry.close();
  }
}


nanoRP2040_ST7735_SD_bmp.ino, read bmp files in SD Card, test.bmp, test2.bmp and test3.bmp, and display on ST7735 SPI TFT. It can be noted in bmpDraw(), copy from examples under "Adafruit ST7735 and ST7789 Library" > shieldtest, bmpDepth must be 24. To prepare bmp for this using GIMP, refer to the above video, ~ 6:38.
/***************************************************
 * nano RP2040 Connect exercise
 * + ST7745/SD
 * 
 * On Arduino nanp RP2040 Connect:
 * (CIPO/MISO) - D12
 * (COPI/MOSI) - D11
 * (SCK) - D13
 * (CS/SS) - Any GPIO (except for A6/A7
 * 
 * This example drive ST7735 SPI and SD using hardware SPI
 * 
 * TFT_ST7735 nano RP2040 Connect
 * ------------------------------
 * VCC        3V3
 * GND        GND
 * CS         10
 * RESET      9
 * A0(DC)     8
 * SDA        11
 * SCK        13
 * LED        3V3
 * 
 * TFT_ST7735 nano RP2040 Connect
 * ------------------------------
 * SD_CS      4
 * SD_MOSI    11
 * SD_MISO    12
 * SD_SCK     13
 ****************************************************/

#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SD.h>
#include <SPI.h>

// TFT display and SD card will share the hardware SPI interface.
// Hardware SPI pins are specific to the Arduino board type.
#define SD_CS    4  // Chip select line for SD card
#define TFT_CS  10  // Chip select line for TFT display
#define TFT_DC   8  // Data/command line for TFT
#define TFT_RST  9  // Reset line for TFT (or connect to +5V)

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

File root;
File myFile;

void setup(void) {
  delay(1000);
  Serial.begin(115200);
  delay(1000);
  
  Serial.println("=====================");
  Serial.println("- setup() -");
  tft.initR(INITR_BLACKTAB);
  Serial.println("tft: " 
                  + String(tft.width()) + " : " 
                  + String(tft.height()));

  tft.fillScreen(ST7735_BLACK);
  tft.setTextWrap(true);
  tft.setTextColor(ST77XX_WHITE);
  tft.setCursor(0, 0);
  tft.print("Arduino nano RP2040 Connect");

  //----------------------------------------
  Serial.println("Initializing SD card...");
  if (!SD.begin(SD_CS)){
    Serial.println("SD.begin() failed!");
  }
  else{
    Serial.println("SD.begin() Success.");

    root = SD.open("/");
    printDirectory(root, 0);

  }


  //----------------------------------------

  Serial.println("\n- End of setup() -\n");
}

const int NO_OF_BMP = 3;
char* bmpFiles[NO_OF_BMP] = {"/test.bmp", "/test2.bmp",  "/test3.bmp"};

void loop() {
  for(int i=0; i<NO_OF_BMP; i++){
    bmpDraw(bmpFiles[i], 0, 0);
    delay(2000);
  }
}

//
// printDirectory() copy from:
// Examples > SD > listfiles
//

void printDirectory(File dir, int numTabs) {
  while (true) {

    File entry =  dir.openNextFile();
    if (! entry) {
      // no more files
      break;
    }
    for (uint8_t i = 0; i < numTabs; i++) {
      Serial.print('\t');
    }
    Serial.print(entry.name());
    if (entry.isDirectory()) {
      Serial.println("/");
      printDirectory(entry, numTabs + 1);
    } else {
      // files have sizes, directories do not
      Serial.print("\t\t");
      Serial.println(entry.size(), DEC);
    }
    entry.close();
  }
}

//
// bmpDraw() copy from:
// Examples under "Adafruit ST7735 and ST7789 Library" > shieldtest
//
// This function opens a Windows Bitmap (BMP) file and
// displays it at the given coordinates.  It's sped up
// by reading many pixels worth of data at a time
// (rather than pixel by pixel).  Increasing the buffer
// size takes more of the Arduino's precious RAM but
// makes loading a little faster.  20 pixels seems a
// good balance.

#define BUFFPIXEL 20

void bmpDraw(char *filename, uint8_t x, uint8_t y) {

  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();

  if((x >= tft.width()) || (y >= tft.height())) return;

  Serial.println();
  Serial.print("Loading image '");
  Serial.print(filename);
  Serial.println('\'');

  // Open requested file on SD card
  if ((bmpFile = SD.open(filename)) == NULL) {
    Serial.print("File not found");
    return;
  }

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print("File size: "); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print("Image Offset: "); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print("Header size: "); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print("Bit Depth: "); Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print("Image size: ");
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.startWrite();
        tft.setAddrWindow(x, y, w, h);

        for (row=0; row<h; row++) { // For each scanline...

          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            tft.endWrite();
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          for (col=0; col<w; col++) { // For each pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
              tft.startWrite();
            }

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            tft.pushColor(tft.color565(r,g,b));
          } // end pixel
        } // end scanline
        tft.endWrite();
        Serial.print("Loaded in ");
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

  bmpFile.close();
  if(!goodBmp) Serial.println("BMP format not recognized.");
}

// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(File f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}


nanoRP2040_ST7735_SD_ImageReader.ino, a simplified version to display bitmaps using Adafruit_ImageReader, make sure it's installed.
// Adafruit_ImageReader test for Adafruit ST7735 TFT Breakout for Arduino.
// Demonstrates loading images from SD card or flash memory to the screen,
// to RAM, and how to query image file dimensions.
// Requires three BMP files in root directory of SD card:
// test.bmp, test2.bmp and test3.bmp.
// As written, this uses the microcontroller's SPI interface for the screen
// (not 'bitbang') and must be wired to specific pins.

#include <Adafruit_GFX.h>         // Core graphics library
#include <Adafruit_ST7735.h>      // Hardware-specific library
#include <SdFat.h>                // SD card & FAT filesystem library
#include <Adafruit_ImageReader.h> // Image-reading functions

// TFT display and SD card share the hardware SPI interface, using
// 'select' pins for each to identify the active device on the bus.

#define SD_CS    4 // SD card select pin
#define TFT_CS  10 // TFT select pin
#define TFT_DC   8 // TFT display/command pin
#define TFT_RST  9 // Or set to -1 and connect to Arduino RESET pin

SdFat                SD;         // SD card filesystem
Adafruit_ImageReader reader(SD); // Image-reader object, pass in SD filesys

Adafruit_ST7735      tft    = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
Adafruit_Image       img;        // An image loaded into RAM
int32_t              width  = 0, // BMP image dimensions
                     height = 0;

void setup(void) {

  ImageReturnCode stat; // Status from image-reading functions

  Serial.begin(115200);

  tft.initR(INITR_BLACKTAB); // Initialize screen

  // The Adafruit_ImageReader constructor call (above, before setup())
  // accepts an uninitialized SdFat or FatFileSystem object. This MUST
  // BE INITIALIZED before using any of the image reader functions!
  Serial.print(F("Initializing filesystem..."));

  // SD card is pretty straightforward, a single call...
  if(!SD.begin(SD_CS, SD_SCK_MHZ(10))) { // Breakouts require 10 MHz limit due ...
    Serial.println(F("SD begin() failed"));
    for(;;); // Fatal error, do not continue
  }

  Serial.println(F("OK!"));

  // Fill screen blue. Not a required step, this just shows that we're
  // successfully communicating with the screen.
  tft.fillScreen(ST7735_BLUE);

  // Load full-screen BMP file 'test.bmp' at position (0,0) (top left).
  // Notice the 'reader' object performs this, with 'tft' as an argument.
  Serial.print(F("Loading test.bmp to screen..."));
  stat = reader.drawBMP("/test.bmp", tft, 0, 0);
  reader.printStatus(stat);   // How'd we do?

  // Query the dimensions of image 'test2.bmp' WITHOUT loading to screen:
  Serial.print(F("Querying test2.bmp image size..."));
  stat = reader.bmpDimensions("/test2.bmp", &width, &height);
  reader.printStatus(stat);   // How'd we do?
  if(stat == IMAGE_SUCCESS) { // If it worked, print image size...
    Serial.print(F("Image dimensions: "));
    Serial.print(width);
    Serial.write('x');
    Serial.println(height);
  }

  // Load small BMP 'test3.bmp' into a GFX canvas in RAM.
  Serial.print(F("Loading test3.bmp to canvas..."));
  stat = reader.loadBMP("/test3.bmp", img);
  reader.printStatus(stat); // How'd we do?

  delay(2000); // Pause 2 seconds before moving on to loop()
}

void loop() {

  for(int r=0; r<4; r++) { // For each of 4 rotations...
    tft.setRotation(r);    // Set rotation
    tft.fillScreen(0);     // and clear screen

    reader.drawBMP("/test2.bmp", tft, 0, 0);

    delay(1000); // Pause 1 sec.

    img.draw(tft,                                    // Pass in tft object
        0 ,  // Horiz pos.
        0); // Vert pos

    delay(2000); // Pause 2 sec.
  }

}

Related:


Saturday, November 6, 2021

SPI pins in Arduino Nano RP2040 Connect

The pins used for SPI (Serial Peripheral Interface) on the Nano RP2040 Connect are the:

  • (CIPO/MISO) - D12
  • (COPI/MOSI) - D11
  • (SCK) - D13
  • (CS/SS) - Any GPIO (except for A6/A7)

ref: https://docs.arduino.cc/tutorials/nano-rp2040-connect/rp2040-01-technical-reference

Please note that SCK on D13 is conflict with the onboard LED.


To get SPI pins programmatically: 



ref: Nano RP2040 Connect pin out:

Friday, October 29, 2021

Arduino IDE, open two independent Serial Monitor

To open two Arduino IDEs, with independent board and Serial Monitor. Tested on Arduino IDE 1.8 in Raspberry Pi OS, Windows 10 and Ubuntu 21.10.

- Open new Arduino IDE from system, or
- from File Explorer.


Saturday, September 11, 2021

Arduino Nano RP2040 Connect (Arduino Framework) + ST7789 SPI LCD, using Adafruit ST7735 and ST7789 Library.

This video show how to install "Adafruit ST7735 and ST7789 Library" on Arduino IDE, tested with Arduino Nano RP2040 Connect to display on 1.54 inch 240x240 and 2 inch 240x320 ST7789 SPI LCD, using hardware SPI and software SPI.

Connection:

Connection between ST7789 SPI to Arduino Nano RP2040 Connect,
using HARDWARE SPI.

ST7789 SPI	Arduino Nano RP2040 Connect
GND		GND
VCC		3V3
SCL		13
SDA		11
RES		9
DC		8
CS		10
BLK		3V3


Connection between ST7789 SPI to Arduino Nano RP2040 Connect,
using SOFTWARE SPI.

ST7789 SPI	Arduino Nano RP2040 Connect
GND		GND
VCC		3V3
SCL		6
SDA		7
RES		9
DC		8
CS		10
BLK		3V3

graphicstest_NanoRP2040_ST7789_240x320_softSPI.ino, modified from graphicstest, to work on Arduino Nano RP2040 Connect, display on 240x320 ST7789 SPI LCD using software SPI.
/**************************************************************************
  This is a library for several Adafruit displays based on ST77* drivers.

  Works with the Adafruit 1.8" TFT Breakout w/SD card
    ----> http://www.adafruit.com/products/358
  The 1.8" TFT shield
    ----> https://www.adafruit.com/product/802
  The 1.44" TFT breakout
    ----> https://www.adafruit.com/product/2088
  The 1.14" TFT breakout
  ----> https://www.adafruit.com/product/4383
  The 1.3" TFT breakout
  ----> https://www.adafruit.com/product/4313
  The 1.54" TFT breakout
    ----> https://www.adafruit.com/product/3787
  The 1.69" TFT breakout
    ----> https://www.adafruit.com/product/5206
  The 2.0" TFT breakout
    ----> https://www.adafruit.com/product/4311
  as well as Adafruit raw 1.8" TFT display
    ----> http://www.adafruit.com/products/618

  Check out the links above for our tutorials and wiring diagrams.
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional).

  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 **************************************************************************/

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <SPI.h>

#if defined(ARDUINO_FEATHER_ESP32) // Feather Huzzah32
  #define TFT_CS         14
  #define TFT_RST        15
  #define TFT_DC         32

#elif defined(ESP8266)
  #define TFT_CS         4
  #define TFT_RST        16                                            
  #define TFT_DC         5

#else
  // For the breakout board, you can use any 2 or 3 pins.
  // These pins will also work for the 1.8" TFT shield.
  #define TFT_CS        10
  #define TFT_RST        9 // Or set to -1 and connect to Arduino RESET pin
  #define TFT_DC         8
#endif

// OPTION 1 (recommended) is to use the HARDWARE SPI pins, which are unique
// to each board and not reassignable. For Arduino Uno: MOSI = pin 11 and
// SCLK = pin 13. This is the fastest mode of operation and is required if
// using the breakout board's microSD card.

// For 1.44" and 1.8" TFT with ST7735 use:
//Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

// For 1.14", 1.3", 1.54", 1.69", and 2.0" TFT with ST7789:
//Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);


// OPTION 2 lets you interface the display using ANY TWO or THREE PINS,
// tradeoff being that performance is not as fast as hardware SPI above.
#define TFT_MOSI 7  // Data out
#define TFT_SCLK 6  // Clock out

// For ST7735-based displays, we will use this call
//Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

// OR for the ST7789-based displays, we will use this call
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);


float p = 3.1415926;

void setup(void) {
  Serial.begin(9600);
  Serial.print(F("Hello! ST77xx TFT Test"));

  // Use this initializer if using a 1.8" TFT screen:
  //tft.initR(INITR_BLACKTAB);      // Init ST7735S chip, black tab

  // OR use this initializer if using a 1.8" TFT screen with offset such as WaveShare:
  // tft.initR(INITR_GREENTAB);      // Init ST7735S chip, green tab

  // OR use this initializer (uncomment) if using a 1.44" TFT:
  //tft.initR(INITR_144GREENTAB); // Init ST7735R chip, green tab

  // OR use this initializer (uncomment) if using a 0.96" 160x80 TFT:
  //tft.initR(INITR_MINI160x80);  // Init ST7735S mini display

  // OR use this initializer (uncomment) if using a 1.3" or 1.54" 240x240 TFT:
  //tft.init(240, 240);           // Init ST7789 240x240

  // OR use this initializer (uncomment) if using a 1.69" 280x240 TFT:
  //tft.init(240, 280);           // Init ST7789 280x240

  // OR use this initializer (uncomment) if using a 2.0" 320x240 TFT:
  tft.init(240, 320);           // Init ST7789 320x240

  // OR use this initializer (uncomment) if using a 1.14" 240x135 TFT:
  //tft.init(135, 240);           // Init ST7789 240x135
  
  // SPI speed defaults to SPI_DEFAULT_FREQ defined in the library, you can override it here
  // Note that speed allowable depends on chip and quality of wiring, if you go too fast, you
  // may end up with a black screen some times, or all the time.
  //tft.setSPISpeed(40000000);

  Serial.println(F("Initialized"));

  uint16_t time = millis();
  tft.fillScreen(ST77XX_BLACK);
  time = millis() - time;

  Serial.println(time, DEC);
  delay(500);

  // large block of text
  tft.fillScreen(ST77XX_BLACK);
  testdrawtext("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur adipiscing ante sed nibh tincidunt feugiat. Maecenas enim massa, fringilla sed malesuada et, malesuada sit amet turpis. Sed porttitor neque ut ante pretium vitae malesuada nunc bibendum. Nullam aliquet ultrices massa eu hendrerit. Ut sed nisi lorem. In vestibulum purus a tortor imperdiet posuere. ", ST77XX_WHITE);
  delay(1000);

  // tft print function!
  tftPrintTest();
  delay(4000);

  // a single pixel
  tft.drawPixel(tft.width()/2, tft.height()/2, ST77XX_GREEN);
  delay(500);

  // line draw test
  testlines(ST77XX_YELLOW);
  delay(500);

  // optimized lines
  testfastlines(ST77XX_RED, ST77XX_BLUE);
  delay(500);

  testdrawrects(ST77XX_GREEN);
  delay(500);

  testfillrects(ST77XX_YELLOW, ST77XX_MAGENTA);
  delay(500);

  tft.fillScreen(ST77XX_BLACK);
  testfillcircles(10, ST77XX_BLUE);
  testdrawcircles(10, ST77XX_WHITE);
  delay(500);

  testroundrects();
  delay(500);

  testtriangles();
  delay(500);

  mediabuttons();
  delay(500);

  Serial.println("done");
  delay(1000);
}

void loop() {
  tft.invertDisplay(true);
  delay(500);
  tft.invertDisplay(false);
  delay(500);
}

void testlines(uint16_t color) {
  tft.fillScreen(ST77XX_BLACK);
  for (int16_t x=0; x < tft.width(); x+=6) {
    tft.drawLine(0, 0, x, tft.height()-1, color);
    delay(0);
  }
  for (int16_t y=0; y < tft.height(); y+=6) {
    tft.drawLine(0, 0, tft.width()-1, y, color);
    delay(0);
  }

  tft.fillScreen(ST77XX_BLACK);
  for (int16_t x=0; x < tft.width(); x+=6) {
    tft.drawLine(tft.width()-1, 0, x, tft.height()-1, color);
    delay(0);
  }
  for (int16_t y=0; y < tft.height(); y+=6) {
    tft.drawLine(tft.width()-1, 0, 0, y, color);
    delay(0);
  }

  tft.fillScreen(ST77XX_BLACK);
  for (int16_t x=0; x < tft.width(); x+=6) {
    tft.drawLine(0, tft.height()-1, x, 0, color);
    delay(0);
  }
  for (int16_t y=0; y < tft.height(); y+=6) {
    tft.drawLine(0, tft.height()-1, tft.width()-1, y, color);
    delay(0);
  }

  tft.fillScreen(ST77XX_BLACK);
  for (int16_t x=0; x < tft.width(); x+=6) {
    tft.drawLine(tft.width()-1, tft.height()-1, x, 0, color);
    delay(0);
  }
  for (int16_t y=0; y < tft.height(); y+=6) {
    tft.drawLine(tft.width()-1, tft.height()-1, 0, y, color);
    delay(0);
  }
}

void testdrawtext(char *text, uint16_t color) {
  tft.setCursor(0, 0);
  tft.setTextColor(color);
  tft.setTextWrap(true);
  tft.print(text);
}

void testfastlines(uint16_t color1, uint16_t color2) {
  tft.fillScreen(ST77XX_BLACK);
  for (int16_t y=0; y < tft.height(); y+=5) {
    tft.drawFastHLine(0, y, tft.width(), color1);
  }
  for (int16_t x=0; x < tft.width(); x+=5) {
    tft.drawFastVLine(x, 0, tft.height(), color2);
  }
}

void testdrawrects(uint16_t color) {
  tft.fillScreen(ST77XX_BLACK);
  for (int16_t x=0; x < tft.width(); x+=6) {
    tft.drawRect(tft.width()/2 -x/2, tft.height()/2 -x/2 , x, x, color);
  }
}

void testfillrects(uint16_t color1, uint16_t color2) {
  tft.fillScreen(ST77XX_BLACK);
  for (int16_t x=tft.width()-1; x > 6; x-=6) {
    tft.fillRect(tft.width()/2 -x/2, tft.height()/2 -x/2 , x, x, color1);
    tft.drawRect(tft.width()/2 -x/2, tft.height()/2 -x/2 , x, x, color2);
  }
}

void testfillcircles(uint8_t radius, uint16_t color) {
  for (int16_t x=radius; x < tft.width(); x+=radius*2) {
    for (int16_t y=radius; y < tft.height(); y+=radius*2) {
      tft.fillCircle(x, y, radius, color);
    }
  }
}

void testdrawcircles(uint8_t radius, uint16_t color) {
  for (int16_t x=0; x < tft.width()+radius; x+=radius*2) {
    for (int16_t y=0; y < tft.height()+radius; y+=radius*2) {
      tft.drawCircle(x, y, radius, color);
    }
  }
}

void testtriangles() {
  tft.fillScreen(ST77XX_BLACK);
  uint16_t color = 0xF800;
  int t;
  int w = tft.width()/2;
  int x = tft.height()-1;
  int y = 0;
  int z = tft.width();
  for(t = 0 ; t <= 15; t++) {
    tft.drawTriangle(w, y, y, x, z, x, color);
    x-=4;
    y+=4;
    z-=4;
    color+=100;
  }
}

void testroundrects() {
  tft.fillScreen(ST77XX_BLACK);
  uint16_t color = 100;
  int i;
  int t;
  for(t = 0 ; t <= 4; t+=1) {
    int x = 0;
    int y = 0;
    int w = tft.width()-2;
    int h = tft.height()-2;
    for(i = 0 ; i <= 16; i+=1) {
      tft.drawRoundRect(x, y, w, h, 5, color);
      x+=2;
      y+=3;
      w-=4;
      h-=6;
      color+=1100;
    }
    color+=100;
  }
}

void tftPrintTest() {
  tft.setTextWrap(false);
  tft.fillScreen(ST77XX_BLACK);
  tft.setCursor(0, 30);
  tft.setTextColor(ST77XX_RED);
  tft.setTextSize(1);
  tft.println("Hello World!");
  tft.setTextColor(ST77XX_YELLOW);
  tft.setTextSize(2);
  tft.println("Hello World!");
  tft.setTextColor(ST77XX_GREEN);
  tft.setTextSize(3);
  tft.println("Hello World!");
  tft.setTextColor(ST77XX_BLUE);
  tft.setTextSize(4);
  tft.print(1234.567);
  delay(1500);
  tft.setCursor(0, 0);
  tft.fillScreen(ST77XX_BLACK);
  tft.setTextColor(ST77XX_WHITE);
  tft.setTextSize(0);
  tft.println("Hello World!");
  tft.setTextSize(1);
  tft.setTextColor(ST77XX_GREEN);
  tft.print(p, 6);
  tft.println(" Want pi?");
  tft.println(" ");
  tft.print(8675309, HEX); // print 8,675,309 out in HEX!
  tft.println(" Print HEX!");
  tft.println(" ");
  tft.setTextColor(ST77XX_WHITE);
  tft.println("Sketch has been");
  tft.println("running for: ");
  tft.setTextColor(ST77XX_MAGENTA);
  tft.print(millis() / 1000);
  tft.setTextColor(ST77XX_WHITE);
  tft.print(" seconds.");
}

void mediabuttons() {
  // play
  tft.fillScreen(ST77XX_BLACK);
  tft.fillRoundRect(25, 10, 78, 60, 8, ST77XX_WHITE);
  tft.fillTriangle(42, 20, 42, 60, 90, 40, ST77XX_RED);
  delay(500);
  // pause
  tft.fillRoundRect(25, 90, 78, 60, 8, ST77XX_WHITE);
  tft.fillRoundRect(39, 98, 20, 45, 5, ST77XX_GREEN);
  tft.fillRoundRect(69, 98, 20, 45, 5, ST77XX_GREEN);
  delay(500);
  // play color
  tft.fillTriangle(42, 20, 42, 60, 90, 40, ST77XX_BLUE);
  delay(50);
  // pause color
  tft.fillRoundRect(39, 98, 20, 45, 5, ST77XX_RED);
  tft.fillRoundRect(69, 98, 20, 45, 5, ST77XX_RED);
  // play color
  tft.fillTriangle(42, 20, 42, 60, 90, 40, ST77XX_GREEN);
}


Thursday, July 8, 2021

Change Arduino IDE Tab size, by editing preferences.txt.

This video show how to locate Arduino IDE's preferences.txt, and edit it to change Arduino Editor Tab size.

Wednesday, June 30, 2021

UDP communication between Raspberry Pi/Python and Arduino Nano RP2040 Connect.

This exercise run on Raspberry Pi/Python and Arduino Nano RP2040 Connect, communicate in UDP.

WiFiNINA is needed, install it in Arduino IDE's Library Manager. 

Exercise 1: Simple UDP example.

With WiFiNINA installed and board of Arduino Nano RP2040 Connect selected, you can load WiFiNINA example WiFiUdpSendReceiveString. Remember to update ssid and password in arduino_secrets.h to match your WiFi network. Upload it to Nano RP2040, open Serial Monitor, it will connect to the WiFi network. Check the ip show in Serial Monitor.

In Raspberry Pi side, run the Python code with matched UDP_IP; simple send message to nano RP2040 via UDP.

pyUDP_client_20210630.py
# ref:
# https://wiki.python.org/moin/UdpCommunication
import socket

#UDP_IP = "127.0.0.1"
#UDP_PORT = 5005
UDP_IP = "192.168.197.39"
UDP_PORT = 2390
MESSAGE = b"Hello, World!"

print("UDP target IP: %s" % UDP_IP)
print("UDP target port: %s" % UDP_PORT)
print("message: %s" % MESSAGE)

sock = socket.socket(socket.AF_INET, # Internet
                     socket.SOCK_DGRAM) # UDP
sock.sendto(MESSAGE, (UDP_IP, UDP_PORT))

Exercise 2: With GUI using PyQt5

Keep WiFiUdpSendReceiveString run on Nano RP2040.

In Raspberry Pi side, run following code with GUI.

pyQt5_UDP_client_20210630.py
import sys
from pkg_resources import require

from PyQt5.QtWidgets import (QApplication, QWidget, QLabel,
                             QTextEdit, QPushButton,
                             QVBoxLayout, QMessageBox)
from PyQt5.QtGui import QFont

from PyQt5.QtNetwork import QUdpSocket, QHostAddress

UDP_IP = "192.168.197.39"
UDP_PORT = 2390

print("Python version")
print(sys.version)
print()

class AppWindow(QWidget):
    
    def __init__(self):
        super().__init__()
        
        self.sock = QUdpSocket(self)
        self.sock.bind(QHostAddress(UDP_PORT), UDP_PORT)
        self.sock.readyRead.connect(self.sock_readyRead_slot)
        
        lbAppTitle = QLabel('Python UDP Client to send msg')
        lbAppTitle.setFont(QFont('Anton', 15, QFont.Bold))
        lbSysInfo = QLabel('Python:\n' + sys.version)
        vboxInfo = QVBoxLayout()
        vboxInfo.addWidget(lbAppTitle)
        vboxInfo.addWidget(lbSysInfo)
        
        self.edMsg = QTextEdit()
        btnSend = QPushButton("Send")
        btnSend.clicked.connect(self.btnSend_Clicked)
        vboxMsg = QVBoxLayout()
        vboxMsg.addWidget(self.edMsg)
        vboxMsg.addWidget(btnSend)
        
        vboxMain = QVBoxLayout()
        vboxMain.addLayout(vboxInfo)
        vboxMain.addLayout(vboxMsg)
        vboxMain.addStretch()
        self.setLayout(vboxMain)
        
        self.setGeometry(100, 100, 500,400)
        self.show()
        
    def sock_readyRead_slot(self):
        while self.sock.hasPendingDatagrams():
            datagram, host, port = self.sock.readDatagram(
                self.sock.pendingDatagramSize()
            )
            
            print("rx:")
            message = '{}\nHost: {}\nPort: {}\n\n'.format(datagram.decode(),
                                                          host.toString(),
                                                          port)

            print(message)
            print()
        
    def btnSend_Clicked(self):
        msgToSend = self.edMsg.toPlainText()
        print("tx:")
        print(msgToSend)
        print()
        
        datagram = msgToSend.encode()
        self.sock.writeDatagram(datagram, QHostAddress(UDP_IP), UDP_PORT)
        
    def closeEvent(self, event):
        close = QMessageBox.question(
            self,
            "QUIT",
            "Close Application?",
            QMessageBox.Yes | QMessageBox.No)
        if close == QMessageBox.Yes:
            print("Close")
            event.accept()
        else:
            event.ignore()

if __name__ == '__main__':
    print('run __main__')
    app = QApplication(sys.argv)
    window = AppWindow()
    sys.exit(app.exec_())

print("- bye -")

Exercise 3: Raspberry Pi/Python remote control Nano RP2040 onboard RGB LED via UDP.

In Nano RP2040, modify to control onboard RGB LED base on incoming command: start with "#RGB", follow with three bytes for R, G and B.
WiFiUdp_RGB__20210630.ino
/*
  WiFi UDP to control Nano RP2040 Connect onboard RGB
 */
#include <SPI.h>
#include <WiFiNINA.h>
#include <WiFiUdp.h>

int status = WL_IDLE_STATUS;
#include "arduino_secrets.h" 
///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = SECRET_SSID;  // your network SSID (name)
char pass[] = SECRET_PASS;  // your network password (use for WPA, or use as key for WEP)
int keyIndex = 0;           // your network key index number (needed only for WEP)

unsigned int localPort = 2390;      // local port to listen on

char packetBuffer[256]; //buffer to hold incoming packet
char  ReplyBuffer[] = "acknowledged";       // a string to send back

WiFiUDP Udp;

void setup() {
  //Initialize serial and wait for port to open:
  Serial.begin(9600);

  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);

  analogWrite(LEDR, 0);
  analogWrite(LEDG, 0);
  analogWrite(LEDB, 0);
  
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  analogWrite(LEDR, 255);
  analogWrite(LEDG, 255);
  analogWrite(LEDB, 255);

  // check for the WiFi module:
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Communication with WiFi module failed!");
    // don't continue
    while (true);
  }

  String fv = WiFi.firmwareVersion();
  if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
    Serial.println("Please upgrade the firmware");
  }

  // attempt to connect to WiFi network:
  while (status != WL_CONNECTED) {
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    status = WiFi.begin(ssid, pass);

    // wait 10 seconds for connection:
    delay(10000);
  }
  Serial.println("Connected to WiFi");
  printWifiStatus();

  Serial.println("\nStarting connection to server...");
  // if you get a connection, report back via serial:
  Udp.begin(localPort);

  analogWrite(LEDR, 0);
  analogWrite(LEDG, 0);
  analogWrite(LEDB, 0);
}

void loop() {

  // if there's data available, read a packet
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    Serial.print("Received packet of size ");
    Serial.println(packetSize);
    Serial.print("From ");
    IPAddress remoteIp = Udp.remoteIP();
    Serial.print(remoteIp);
    Serial.print(", port ");
    Serial.println(Udp.remotePort());

    // read the packet into packetBufffer
    int len = Udp.read(packetBuffer, 255);
    if (len > 0) {
      packetBuffer[len] = 0;
    }
    Serial.println("Contents:");
    Serial.println(packetBuffer);

    // send a reply, to the IP address and port that sent us the packet we received
    Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
    Udp.write(ReplyBuffer);
    Udp.endPacket();

    String cmd = String(packetBuffer);
    if(cmd.startsWith("#RGB")){
      Serial.println("CMD: #RGB");
      int valR = (int)(packetBuffer[4]);
      int valG = (int)(packetBuffer[5]);
      int valB = (int)(packetBuffer[6]);
      Serial.println("R: " + String(valR));
      Serial.println("G: " + String(valG));
      Serial.println("B: " + String(valB));

      analogWrite(LEDR, 255-valR);
      analogWrite(LEDG, 255-valG);
      analogWrite(LEDB, 255-valB);
    }else{
      Serial.println("NOT MATCH");
    }

  }
}


void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your board's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

In Raspberry Pi side:

pyQt5_UDP_client_RGB_20210630.py
import sys
from pkg_resources import require

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel,
                             QSlider, QPushButton,
                             QVBoxLayout, QHBoxLayout, QMessageBox)
from PyQt5.QtGui import QFont

from PyQt5.QtNetwork import QUdpSocket, QHostAddress

UDP_IP = "192.168.197.39"
UDP_PORT = 2390

print("Python version")
print(sys.version)
print()

class AppWindow(QWidget):
    
    def __init__(self):
        super().__init__()
        
        self.sock = QUdpSocket(self)
        self.sock.bind(QHostAddress(UDP_PORT), UDP_PORT)
        self.sock.readyRead.connect(self.sock_readyRead_slot)
        
        lbAppTitle = QLabel('Python UDP Client to control RGB')
        lbAppTitle.setFont(QFont('Anton', 15, QFont.Bold))
        lbSysInfo = QLabel('Python:\n' + sys.version)
        vboxInfo = QVBoxLayout()
        vboxInfo.addWidget(lbAppTitle)
        vboxInfo.addWidget(lbSysInfo)

        
        lbR = QLabel(' R: ')
        self.sliderR = QSlider(Qt.Horizontal, self)
        self.sliderR.setRange(0, 255)
        self.sliderR.valueChanged.connect(self.sliderRGB_valueChanged_slot)
        boxR = QHBoxLayout()
        boxR.addWidget(lbR)
        boxR.addWidget(self.sliderR)
        
        lbG = QLabel(' G: ')
        self.sliderG = QSlider(Qt.Horizontal, self)
        self.sliderG.setRange(0, 255)
        self.sliderG.valueChanged.connect(self.sliderRGB_valueChanged_slot)
        boxG = QHBoxLayout()
        boxG.addWidget(lbG)
        boxG.addWidget(self.sliderG)
        
        lbB = QLabel(' B: ')
        self.sliderB = QSlider(Qt.Horizontal, self)
        self.sliderB.setRange(0, 255)
        self.sliderB.valueChanged.connect(self.sliderRGB_valueChanged_slot)
        boxB = QHBoxLayout()
        boxB.addWidget(lbB)
        boxB.addWidget(self.sliderB)
        
        boxRGB = QVBoxLayout()
        boxRGB.addLayout(boxR)
        boxRGB.addLayout(boxG)
        boxRGB.addLayout(boxB)
        
        btnSend = QPushButton("Update")
        btnSend.clicked.connect(self.btnSend_Clicked)
        vboxMsg = QVBoxLayout()
        vboxMsg.addLayout(boxRGB)
        vboxMsg.addWidget(btnSend)
        
        vboxMain = QVBoxLayout()
        vboxMain.addLayout(vboxInfo)
        vboxMain.addLayout(vboxMsg)
        vboxMain.addStretch()
        self.setLayout(vboxMain)
        
        self.setGeometry(100, 100, 500,400)
        self.show()
        
    def sock_readyRead_slot(self):
        while self.sock.hasPendingDatagrams():
            datagram, host, port = self.sock.readDatagram(
                self.sock.pendingDatagramSize()
            )
            
            print("rx:")
            message = '{}\nHost: {}\nPort: {}\n\n'.format(datagram.decode(),
                                                          host.toString(),
                                                          port)

            print(message)
            print()
        
    def btnSend_Clicked(self):
        print("tx:")
        
        valueR = self.sliderR.value()
        valueG = self.sliderG.value()
        valueB = self.sliderB.value()
        
        CMD_RGB = "#RGB"
        bCMD_RGB = str.encode(CMD_RGB) + bytes([valueR, valueG, valueB])
        print(type(bCMD_RGB))
        print("CMD: ", bCMD_RGB)
        
        self.sock.writeDatagram(bCMD_RGB, QHostAddress(UDP_IP), UDP_PORT)
        
        """
        msgToSend = self.edMsg.toPlainText()
        print("tx:")
        print(msgToSend)
        print()
        
        datagram = msgToSend.encode()
        self.sock.writeDatagram(datagram, QHostAddress(UDP_IP), UDP_PORT)
        """
        
    def sliderRGB_valueChanged_slot(self):
        print("sliderRGB_valueChanged_slot")
        valueR = self.sliderR.value()
        valueG = self.sliderG.value()
        valueB = self.sliderB.value()
        print(" R: ", valueR, " G: ", valueG, " B: ", valueB)
        
        CMD_RGB = "#RGB"
        bCMD_RGB = str.encode(CMD_RGB) + bytes([valueR, valueG, valueB])
        print(type(bCMD_RGB))
        print(bCMD_RGB)
        
    def closeEvent(self, event):
        close = QMessageBox.question(
            self,
            "QUIT",
            "Close Application?",
            QMessageBox.Yes | QMessageBox.No)
        if close == QMessageBox.Yes:
            print("Close")
            event.accept()
        else:
            event.ignore()

if __name__ == '__main__':
    print('run __main__')
    app = QApplication(sys.argv)
    window = AppWindow()
    sys.exit(app.exec_())

print("- bye -")


~ More exercise of Arduino Nano RP2040 Connect.

Thursday, June 24, 2021

Arduino Nano RP2040 Connect + ILI9341 SPI TFT, using adafruit_ILI9341 library.

To drive ILI9341 SPI TFT on Arduino Nano RP2040 Connect (Arduino framework), Adafruit_ILI9341 library can be used.

Install Library:

In Arduino IDE, open Library Manager, search and install Adafruit ILI9341, Adafruit GFX library is needed also.


You will be asked to install extra libraries needed, click Install All.

Connection:

Connection:
ILI9341 TFT	Nano RP2040 Connect
-------------------------------
VCC		3V3
GND		GND
CS		D10
RESET		RESET
DC		D9
SDI(MOSI)	D11 (SPI0 TX)
SCK		D13 (SPI0 SCK)
LED		3V3
SDA(MISO)	D12 (SPI0 RX)
After then, you can try Adafruit_ILI9341 example graphicstest.

~ More exercise of Arduino Nano RP2040 Connect.

Wednesday, June 23, 2021

BLE Communication between Nano RP2040 Connect (Peripheral) and ESP32 (Central), in Arduino framework.

Exercise of BLE Communication between Nano RP2040 Connect (Peripheral) and ESP32 (Central), in Arduino framework.

nanoRP2040_BLE_Peripheral_20210623a.ino run on Arduino Nano RP2040 Connect, act as BLE Peripheral (server), wait to be connected.

/*
 * Arduino nano RP2040 Connect exercise:
 * as BLE Peripheral 
 * 
 * reference:
 * ArduinoBLE library-
 * https://www.arduino.cc/en/Reference/ArduinoBLE
 * 
 * In my implementation:
 * BLEByteCharacteristic written event checked by polling.
 * BLEStringCharacteristic/BLEIntCharacteristic using event haandler.
 * 
 */
 #include <ArduinoBLE.h>

/*  Visit Online GUID / UUID Generator
 *  (https://www.guidgenerator.com/)
 *  to generate your uuid
 */
const char* serviceUUID =    "20a07a95-8c12-484c-94fa-b828c4465a3c";
const char* byteCharUUID =   "d34dda3b-7b4a-4ce3-9666-a8338db4e604";
const char* stringCharUUID = "41281b9c-8dc4-4649-8cbe-c39fa01513e2";
const char* intCharUUID =    "c7d27dc6-f4d8-4523-b060-3e2e4c187808";

const char* myLocalName = "MyBLE";
 
BLEService myBLEService(serviceUUID);
BLEByteCharacteristic myBLEByteCharacteristic(byteCharUUID, 
                              BLERead|BLEWrite);
BLEStringCharacteristic myBLEStringCharacteristic(stringCharUUID, 
                              BLERead|BLEWrite|BLENotify, 24);  //max length = 10
BLEIntCharacteristic myBLEIntCharacteristic(intCharUUID,
                              BLERead|BLEWrite);


void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  Serial.begin(115200);
  delay(100);
  digitalWrite(LED_BUILTIN, HIGH);

  //Started, connect Serial to begin
  while(!Serial);
  Serial.println("\n---Start ---");
  digitalWrite(LED_BUILTIN, LOW);

  Serial.println("Initialize BLE...");
  if(!BLE.begin()){
    Serial.println("Starting BLE failed!");
    while(1);
  }
  Serial.println("BLE initialized.");
  Serial.print("MAC: ");
  Serial.println(BLE.address());
  Serial.println("Service UUIID: \t\t\t" + String(serviceUUID));
  Serial.println("Byte Characteristic UUIID: \t" + String(byteCharUUID));
  Serial.println("String Characteristic UUIID: \t" + String(stringCharUUID));
  Serial.println("Int Characteristic UUIID: \t" + String(intCharUUID));
  
  Serial.println();

  BLE.setLocalName(myLocalName);
  BLE.setAdvertisedService(myBLEService);
  myBLEService.addCharacteristic(myBLEByteCharacteristic);
  myBLEService.addCharacteristic(myBLEStringCharacteristic);
  myBLEService.addCharacteristic(myBLEIntCharacteristic);
  BLE.addService(myBLEService);

  myBLEStringCharacteristic.setEventHandler(BLEWritten, myBLEString_Written_Hndl);
  myBLEIntCharacteristic.setEventHandler(BLEWritten, myBLEInt_Written_Hndl);

  BLE.advertise();

}

void loop() {
  BLEDevice central = BLE.central();

  if(central){
    digitalWrite(LED_BUILTIN, HIGH);
    Serial.print("Connected: ");
    Serial.println(central.address());

    while(central.connected()){

      if(myBLEByteCharacteristic.written()){
        byte valByte = myBLEByteCharacteristic.value();
        Serial.print("myBLEByteCharacteristic received: ");
        Serial.println(valByte, HEX);
      }
    }

    digitalWrite(LED_BUILTIN, LOW);
    Serial.println("Disconnected.");
  }
}

// Event Handler for myBLEStringCharacteristic Written
// Print received Sring, and write back in upper case.
void myBLEString_Written_Hndl(BLEDevice central, BLECharacteristic characteristic) {
  Serial.print("BLEStringCharacteristic event, written: ");

  Serial.println("myBLEStringCharacteristic received: len=" + 
                  String(myBLEStringCharacteristic.valueLength()));
  String valString = myBLEStringCharacteristic.value();
  Serial.println(valString);
  valString.toUpperCase();
  Serial.println(valString);
  myBLEStringCharacteristic.setValue(valString);
}

// Event Handler for myBLEIntCharacteristic Written
void myBLEInt_Written_Hndl(BLEDevice central, BLECharacteristic characteristic) {
  Serial.print("BLEIntCharacteristic event, written: ");
  
  int valInt = myBLEIntCharacteristic.value();
  Serial.println(valInt);
  
}


ESP32_BLE_Central_20210623a.ino run on ESP32 Dev. Board, act as Central (client), scan and connect to Peripheral, and send something.
/*
 * ESP32 BLE exercise, as Central (Client)
 * connect to nanoRP2040 BLE Peripheral
 * 
 */

 #include "BLEDevice.h"

const String targetName = "MyBLE";

// The remote service/characteristic we wish to connect to.
// UUID(s) have to match with Peripheral side.
const char* serviceUUID =    "20a07a95-8c12-484c-94fa-b828c4465a3c";
const char* byteCharUUID =   "d34dda3b-7b4a-4ce3-9666-a8338db4e604";
const char* stringCharUUID = "41281b9c-8dc4-4649-8cbe-c39fa01513e2";
const char* intCharUUID =    "c7d27dc6-f4d8-4523-b060-3e2e4c187808";

static BLEUUID BLEUUID_service(serviceUUID);
static BLEUUID BLEUUID_byteChar(byteCharUUID);
static BLEUUID BLEUUID_stringChar(stringCharUUID);
static BLEUUID BLEUUID_intChar(intCharUUID);

static BLERemoteCharacteristic* pRemoteChar_byte;
static BLERemoteCharacteristic* pRemoteChar_string;
static BLERemoteCharacteristic* pRemoteChar_int;

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;

static BLEAdvertisedDevice* myDevice;

int notifyDur;

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
    Serial.println("pRemoteChar_string notify callback -");
    Serial.println(pBLERemoteCharacteristic->getUUID().toString().c_str());
    Serial.print(" of data length ");
    Serial.println(length);
    Serial.print("data: ");
    Serial.println((char*)pData);
}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    Serial.println("onDisconnect");
  }
};

bool connectToServer() {
    Serial.print("Forming a connection to ");
    Serial.println(myDevice->getAddress().toString().c_str());
    
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    pClient->connect(myDevice);
    Serial.println(" - Connected to server");

    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(BLEUUID_service);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(BLEUUID_service.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our service");


    // Obtain a reference to the characteristic in the service of the remote BLE server.
    pRemoteChar_byte = pRemoteService->getCharacteristic(BLEUUID_byteChar);
    pRemoteChar_string = pRemoteService->getCharacteristic(BLEUUID_stringChar);
    pRemoteChar_int = pRemoteService->getCharacteristic(BLEUUID_intChar);

    //assume all characteristics found, skip checking
    /*
    if (pRemoteChar_string == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(BLEUUID_stringChar.toString().c_str());
      pClient->disconnect();
      return false;
    }
    */
    Serial.println(" - Found our characteristic");

    // Read the value of the characteristic.
    if(pRemoteChar_string->canRead()) {
      std::string value = pRemoteChar_string->readValue();
      Serial.print("The characteristic value was: ");
      Serial.println(value.c_str());
    }

    if(pRemoteChar_string->canNotify()){
      Serial.println("pRemoteChar_string CAN Notify");
      pRemoteChar_string->registerForNotify(notifyCallback);
    }else{
      Serial.println("pRemoteChar_string CANNOT Notify");
    }

    connected = true;
    return true;
}

/**
 * Scan for BLE servers and find the first one matched advertises service
 */
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
 /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    //Serial.print("BLE Advertised Device found: ");
    //Serial.println(advertisedDevice.toString().c_str());
    String devName = advertisedDevice.getName().c_str();
    //Serial.println(devName);

    if(devName == targetName){
      Serial.println("Target found-");
      // We have found a device,
      // let us now see if it contains the service we are looking for.
      Serial.println(advertisedDevice.getServiceUUID().toString().c_str());
      
      if (advertisedDevice.haveServiceUUID() && 
            advertisedDevice.isAdvertisingService(BLEUUID_service)) {
        Serial.println("BLEUUID_service match -");
        BLEDevice::getScan()->stop();
        myDevice = new BLEAdvertisedDevice(advertisedDevice);
        doConnect = true;
        doScan = true;
      } // Found our server
    }
  } // onResult
}; // MyAdvertisedDeviceCallbacks



void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("\n--- ESP32 Start ---");

  BLEDevice::init("");

  // Retrieve a Scanner and set the callback we want to use to be informed when we
  // have detected a new device.  Specify that we want active scanning and start the
  // scan to run for 5 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
}

// This is the Arduino main loop function.
void loop() {

  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are 
  // connected we set the connected flag to be true.
  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
    }
    doConnect = false;
  }

  // If we are connected to a peer BLE Server, update the characteristic each time we are reached
  // with the current time since boot.
  if (connected) {
    notifyDur = millis();
    String newValue = "time since boot: " + String(notifyDur/1000);
    
    // Set the characteristic's value to be the array of bytes that is actually a string.
    pRemoteChar_string->writeValue(newValue.c_str(), newValue.length());
    pRemoteChar_int->writeValue(notifyDur, true);
  }else if(doScan){
    BLEDevice::getScan()->start(0);
  }
  
  delay(1000); // Delay a second between loops.
} // End of loop




~ More exercise of Arduino Nano RP2040 Connect.

Monday, June 21, 2021

ArduinoBLE exercise on Nano RP2040 Connect: BLE Peripheral

ArduinoBLE exercise run on Nano RP2040 Connect, act as BLE Peripheral. Tested with nRF Connect app on Android.


nanoRP2040_BLE_Peripheral_20210622a.ino
/*
 * Arduino nano RP2040 Connect exercise:
 * as BLE Peripheral 
 * 
 * reference:
 * ArduinoBLE library-
 * https://www.arduino.cc/en/Reference/ArduinoBLE
 * 
 * In my implementation:
 * BLEByteCharacteristic written event checked by polling.
 * BLEStringCharacteristic/BLEIntCharacteristic using event haandler.
 * 
 */
 #include <ArduinoBLE.h>

/*  Visit Online GUID / UUID Generator
 *  (https://www.guidgenerator.com/)
 *  to generate your uuid
 */
const char* serviceUUID =    "20a07a95-8c12-484c-94fa-b828c4465a3c";
const char* byteCharUUID =   "d34dda3b-7b4a-4ce3-9666-a8338db4e604";
const char* stringCharUUID = "41281b9c-8dc4-4649-8cbe-c39fa01513e2";
const char* intCharUUID =    "c7d27dc6-f4d8-4523-b060-3e2e4c187808";

const char* myLocalName = "MyBLE";
 
BLEService myBLEService(serviceUUID);
BLEByteCharacteristic myBLEByteCharacteristic(byteCharUUID, 
                              BLERead|BLEWrite);
BLEStringCharacteristic myBLEStringCharacteristic(stringCharUUID, 
                              BLERead|BLEWrite, 10);  //max length = 10
BLEIntCharacteristic myBLEIntCharacteristic(intCharUUID,
                              BLERead|BLEWrite);


void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  Serial.begin(115200);
  delay(100);
  digitalWrite(LED_BUILTIN, HIGH);

  //Started, connect Serial to begin
  while(!Serial);
  Serial.println("\n---Start ---");
  digitalWrite(LED_BUILTIN, LOW);

  Serial.println("Initialize BLE...");
  if(!BLE.begin()){
    Serial.println("Starting BLE failed!");
    while(1);
  }
  Serial.println("BLE initialized.");
  Serial.print("MAC: ");
  Serial.println(BLE.address());
  Serial.println("Service UUIID: \t\t\t" + String(serviceUUID));
  Serial.println("Byte Characteristic UUIID: \t" + String(byteCharUUID));
  Serial.println("String Characteristic UUIID: \t" + String(stringCharUUID));
  Serial.println("Int Characteristic UUIID: \t" + String(intCharUUID));
  
  Serial.println();

  BLE.setLocalName(myLocalName);
  BLE.setAdvertisedService(myBLEService);
  myBLEService.addCharacteristic(myBLEByteCharacteristic);
  myBLEService.addCharacteristic(myBLEStringCharacteristic);
  myBLEService.addCharacteristic(myBLEIntCharacteristic);
  BLE.addService(myBLEService);

  myBLEStringCharacteristic.setEventHandler(BLEWritten, myBLEString_Written_Hndl);
  myBLEIntCharacteristic.setEventHandler(BLEWritten, myBLEInt_Written_Hndl);

  BLE.advertise();

}

void loop() {
  BLEDevice central = BLE.central();

  if(central){
    digitalWrite(LED_BUILTIN, HIGH);
    Serial.print("Connected: ");
    Serial.println(central.address());

    while(central.connected()){

      if(myBLEByteCharacteristic.written()){
        byte valByte = myBLEByteCharacteristic.value();
        Serial.print("myBLEByteCharacteristic received: ");
        Serial.println(valByte, HEX);
      }
    }

    digitalWrite(LED_BUILTIN, LOW);
    Serial.println("Disconnected.");
  }
}

// Event Handler for myBLEStringCharacteristic Written
// Print received Sring, and write back in upper case.
void myBLEString_Written_Hndl(BLEDevice central, BLECharacteristic characteristic) {
  Serial.print("BLEStringCharacteristic event, written: ");

  Serial.println("myBLEStringCharacteristic received: len=" + 
                  String(myBLEStringCharacteristic.valueLength()));
  String valString = myBLEStringCharacteristic.value();
  Serial.println(valString);
  valString.toUpperCase();
  Serial.println(valString);
  myBLEStringCharacteristic.setValue(valString);
}

// Event Handler for myBLEIntCharacteristic Written
void myBLEInt_Written_Hndl(BLEDevice central, BLECharacteristic characteristic) {
  Serial.print("BLEIntCharacteristic event, written: ");
  
  int valInt = myBLEIntCharacteristic.value();
  Serial.println(valInt);
  
}



~ More exercises for Arduino Nano RP2040 Connect

Sunday, June 13, 2021

Raspberry Pi/Python BLE Central + Arduino Nano RP2040 Connect BLE Peripheral

This exercise implement Python 3 code run on Raspberry Pi act as BLE Central, connect to Arduino Nano RP2040 Connect act as BLE Peripheral. And send data (0x01/0x00) to turn the Nano RP2040 Connect's onboard LED ON/OFF.


Arduino Nano RP2040 Connect act as BLE Peripheral side:

- Make sure ArduinoBLE library is installed.

- The Arduino code is modified from ArduinoBLE Examples > Peripheral > CallbackLED. With MAC, Service UUID, Characteristic UUID, and BLE written data displayed. Such that we can program Central side accordingly.

nanoRP2040_BLE_CallbackLED.ino
/*
  Callback LED

  This example creates a BLE peripheral with service that contains a
  characteristic to control an LED. The callback features of the
  library are used.

  The circuit:
  - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT,
    Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board.

  You can use a generic BLE central app, like LightBlue (iOS and Android) or
  nRF Connect (Android), to interact with the services and characteristics
  created in this sketch.

  This example code is in the public domain.
*/

#include <ArduinoBLE.h>

BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // create service

// create switch characteristic and allow remote device to read and write
BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);

const int ledPin = LED_BUILTIN; // pin to use for the LED

void setup() {
  Serial.begin(9600);
  while (!Serial);
  delay(1000);
  Serial.println("\n--- Start---");
  
  pinMode(ledPin, OUTPUT); // use the LED pin as an output

  // begin initialization
  if (!BLE.begin()) {
    Serial.println("starting BLE failed!");

    while (1);
  }

  Serial.print("My BLE MAC:\t\t ");
  Serial.println(BLE.address());
  Serial.print("Service UUID:\t\t ");
  Serial.println(ledService.uuid());
  Serial.print("Characteristic UUID:\t ");
  Serial.println(switchCharacteristic.uuid());
  Serial.println();

  // set the local name peripheral advertises
  BLE.setLocalName("LEDCallback");
  // set the UUID for the service this peripheral advertises
  BLE.setAdvertisedService(ledService);

  // add the characteristic to the service
  ledService.addCharacteristic(switchCharacteristic);

  // add service
  BLE.addService(ledService);

  // assign event handlers for connected, disconnected to peripheral
  BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler);
  BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);

  // assign event handlers for characteristic
  switchCharacteristic.setEventHandler(BLEWritten, switchCharacteristicWritten);
  // set an initial value for the characteristic
  switchCharacteristic.setValue(0);

  // start advertising
  BLE.advertise();

  Serial.println(("Bluetooth device active, waiting for connections..."));
}

void loop() {
  // poll for BLE events
  BLE.poll();
}

void blePeripheralConnectHandler(BLEDevice central) {
  // central connected event handler
  Serial.print("Connected event, central: ");
  Serial.println(central.address());
}

void blePeripheralDisconnectHandler(BLEDevice central) {
  // central disconnected event handler
  Serial.print("Disconnected event, central: ");
  Serial.println(central.address());
}

void switchCharacteristicWritten(BLEDevice central, BLECharacteristic characteristic) {
  // central wrote new value to characteristic, update LED
  Serial.println("Characteristic event, written: ");

  byte charValue = switchCharacteristic.value();
  Serial.println(charValue);

  if (charValue) {
    Serial.println("LED on");
    digitalWrite(ledPin, HIGH);
  } else {
    Serial.println("LED off");
    digitalWrite(ledPin, LOW);
  }
}

Raspberry Pi/Python BLE Central Side:
- bluepy library is used to control Bluetooth BLE.

To install bluepy for Python3, enter the command:
$ sudo pip3 install bluepy

- tkinter is used for GUI.

py_BLE_Central_LedControl.py connect to Peripheral with matched MAC, and search for service/characteristic with match UUID. Then send 0x01/0x00 to turn ON/OFF the Nano RP2040 Connect's onboard LED.

"""
Python/Raspberry Pi BluetoothLE exercise:
using bluepy library

Work with Arduino Nano RP2040 Connect example:
ArduinoBLE > Peripheral > CallbackLED

Connect to Peripheral with matched MAC.
Check for service uuid and characteristic uuid,
if matched found: send bytes 0x01 and 0x00 for three time
to turn Nano RP2040 Connect onboard LED ON/OFF.
"""

from bluepy import btle
import time

#Have to match with Peripheral
MAC = "84:cc:a8:2e:8d:76"
SERVICE_UUID = "19b10000-e8f2-537e-4f6c-d104768a1214"
CHARACTERISTIC_UUID = "19b10001-e8f2-537e-4f6c-d104768a1214"

nanoRP2040_Char = None

print("Hello")

print("Connect to:" + MAC)
dev = btle.Peripheral(MAC)
print("\n--- dev ----------------------------")
print(type(dev))
print(dev)

print("\n--- dev.services -------------------")
for svc in dev.services:
    print(str(svc))
    
print("\n------------------------------------")
print("Get Serice By UUID: " + SERVICE_UUID)
service_uuid = btle.UUID(SERVICE_UUID)
service = dev.getServiceByUUID(service_uuid)

print(service)
print("\n--- service.getCharacteristics() ---")
print(type(service.getCharacteristics()))
print(service.getCharacteristics())

#----------------------------------------------
characteristics = dev.getCharacteristics()
print("\n--- dev.getCharacteristics() -------")
print(type(characteristics))
print(characteristics)
    
for char in characteristics:
    print("----------")
    print(type(char))
    print(char)
    print(char.uuid)
    if(char.uuid == CHARACTERISTIC_UUID ):
        print("=== !CHARACTERISTIC_UUID matched! ==")
        nanoRP2040_Char = char
        print(char)
        print(dir(char))
        #print(char.getDescriptors)
        #print(char.propNames)
        #print(char.properties)
        #print(type(char.read()))
        print(char.read())
        
bytes_ON = b'\x01'
bytes_OFF = b'\x00'

if nanoRP2040_Char != None:
    print("\nnanoRP2040_Char found")
    print(nanoRP2040_Char)
    for i in range(3):
        nanoRP2040_Char.write(bytes_ON, True)
        print(nanoRP2040_Char.read())
        time.sleep(1.0)
        nanoRP2040_Char.write(bytes_OFF, True)
        print(nanoRP2040_Char.read())
        time.sleep(1.0)
else:
    print("\nnanoRP2040_Char NOT found!")
#=============================================
dev.disconnect()
print("\n--- bye ---\n")
pyTk_BLE_Central_LedControl.py implement GUI with tkinter, user click on button to turn ON/OFF LED.
"""
Python/Raspberry Pi BluetoothLE exercise:
using bluepy library

Work with Arduino Nano RP2040 Connect example:
ArduinoBLE > Peripheral > CallbackLED

Connect to Peripheral with matched MAC.
Check for service uuid and characteristic uuid,
if matched found:
    Start GUI to control Nano RP2040 Connect onboard LED
"""

from bluepy import btle
import time
import tkinter as tk

#Have to match with Peripheral
MAC = "84:cc:a8:2e:8d:76"
SERVICE_UUID = "19b10000-e8f2-537e-4f6c-d104768a1214"
CHARACTERISTIC_UUID = "19b10001-e8f2-537e-4f6c-d104768a1214"

nanoRP2040_Char = None

def toggle():
    if toggle_btn.config('relief')[-1] == 'sunken':
        toggle_btn.config(relief="raised")
        nanoRP2040_Char.write(bytes_OFF, True)
        toggle_btn['text'] = 'Turn LED ON'
    else:
        toggle_btn.config(relief="sunken")
        nanoRP2040_Char.write(bytes_ON, True)
        toggle_btn['text'] = 'Turn LED OFF'

print("Hello")

print("Connect to:" + MAC)
dev = btle.Peripheral(MAC)
print("\n--- dev ----------------------------")
print(type(dev))
print(dev)

print("\n--- dev.services -------------------")
for svc in dev.services:
    print(str(svc))
    
print("\n------------------------------------")
print("Get Serice By UUID: " + SERVICE_UUID)
service_uuid = btle.UUID(SERVICE_UUID)
service = dev.getServiceByUUID(service_uuid)

print(service)
print("\n--- service.getCharacteristics() ---")
print(type(service.getCharacteristics()))
print(service.getCharacteristics())

#----------------------------------------------
characteristics = dev.getCharacteristics()
print("\n--- dev.getCharacteristics() -------")
print(type(characteristics))
print(characteristics)
    
for char in characteristics:
    print("----------")
    print(type(char))
    print(char)
    print(char.uuid)
    if(char.uuid == CHARACTERISTIC_UUID ):
        print("=== !CHARACTERISTIC_UUID matched! ==")
        nanoRP2040_Char = char
        print(char)
        print(dir(char))
        #print(char.getDescriptors)
        #print(char.propNames)
        #print(char.properties)
        #print(type(char.read()))
        print(char.read())
        
bytes_ON = b'\x01'
bytes_OFF = b'\x00'

if nanoRP2040_Char != None:
    print("\nnanoRP2040_Char found")
    print(nanoRP2040_Char)
    
    root = tk.Tk()
    label = tk.Label( root, text="Toggle button to Turn ON/OFF the nano RP040 Connect LED")
    label.pack(pady=10)
    toggle_btn = tk.Button(text="Turn LED ON", width=12, relief="raised", command=toggle)

    toggle_btn.pack(pady=10)
    root.geometry("500x200")

    #Place tkinter window center
    root.eval('tk::PlaceWindow %s center' % root.winfo_pathname(root.winfo_id()))
    root.title("arduino-er.blogspot.com")
    root.mainloop()
else:
    print("\nnanoRP2040_Char NOT found!")
#=============================================
dev.disconnect()
print("\n--- bye ---\n")


~ More exercises of Arduino Nano RP2040 Connect.