Skip to content

10.output devices#

1.Group work#

Click here for details. https://fabacademy.org/2025/labs/kannai/Weekly_Group_Assignment/week10/

2.individual work#

Click here for information on base production.

Output device used this time#

http://tatsuro.homma.fabcloud.io/fabacademy/tips/networking/OLED/

Let’s make a Pins image#

  • VCC: 3.3V-5V
  • GND: Power Ground
  • SCL: CLK Serial
  • SDA: Serial Data

Networking#

I2C address#

Check I2C address by the position of 0Ω resister in “Address Select” area at OLED.

OLEDs have two I2C addresses, and the address can be set by a resistor.

If there are two I2C addresses, for example, if two OLEDs use different I2C addresses, it is possible to display different information.

What is I2C?#

I2C (Inter-Integrated Circuit; pronounced as “eye-squared-see” or “eye-two-see”), alternatively known as I2C or IIC, is a synchronous, multi-master/multi-slave, single-ended, serial communication bus invented in 1982 by Philips Semiconductors. It is widely used for attaching lower-speed peripheral integrated circuits (ICs) to processors and microcontrollers in short-distance, intra-board communication. An example schematic with one controller (a microcontroller), three target nodes (an ADC, a DAC, and a microcontroller), and pull-up resistors Rp I2C uses only two signals: serial data line (SDA) and serial clock line (SCL). Both are bidirectional and pulled up with resistors.[2] Typical voltages used are +5 V or +3.3 V, although systems with other voltages are permitted. The I2C reference design has a 7-bit address space, with a rarely used 10-bit extension.[3] Common I2C bus speeds are the 100 kbit/s standard mode and the 400 kbit/s fast mode. There is also a 10 kbit/s low-speed mode, but arbitrarily low clock frequencies are also allowed. Later revisions of I2C can host more nodes and run at faster speeds (400 kbit/s fast mode, 1 Mbit/s fast mode plus, 3.4 Mbit/s high-speed mode, and 5 Mbit/s ultra-fast mode). These speeds are more widely used on embedded systems than on PCs.

https://en.wikipedia.org/wiki/I%C2%B2C

Connection#

The connection was made as follows.

Program Code#

The following sample code was used.

  /**************************************************************************
  This is an example for our Monochrome OLEDs based on SSD1306 drivers

  Pick one up today in the adafruit shop!
  ------> http://www.adafruit.com/category/63_98

  This example is for a 128x64 pixel display using I2C to communicate
  3 pins are required to interface (two I2C and one reset).

  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,
  with contributions from the open source community.
  BSD license, check license.txt for more information
  All text above, and the splash screen below must be
  included in any redistribution.
  **************************************************************************/

  #include <SPI.h>
  #include <Wire.h>
  #include <Adafruit_GFX.h>
  #include <Adafruit_SSD1306.h>

  #define SCREEN_WIDTH 128 // OLED display width, in pixels
  #define SCREEN_HEIGHT 64 // OLED display height, in pixels

  // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
  // The pins for I2C are defined by the Wire-library. 
  // On an arduino UNO:       A4(SDA), A5(SCL)
  // On an arduino MEGA 2560: 20(SDA), 21(SCL)
  // On an arduino LEONARDO:   2(SDA),  3(SCL), ...
  #define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
  #define SCREEN_ADDRESS 0x3C
  Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

  #define NUMFLAKES     10 // Number of snowflakes in the animation example

  #define LOGO_HEIGHT   16
  #define LOGO_WIDTH    16
  static const unsigned char PROGMEM logo_bmp[] =
  { 0b00000000, 0b11000000,
    0b00000001, 0b11000000,
    0b00000001, 0b11000000,
    0b00000011, 0b11100000,
    0b11110011, 0b11100000,
    0b11111110, 0b11111000,
    0b01111110, 0b11111111,
    0b00110011, 0b10011111,
    0b00011111, 0b11111100,
    0b00001101, 0b01110000,
    0b00011011, 0b10100000,
    0b00111111, 0b11100000,
    0b00111111, 0b11110000,
    0b01111100, 0b11110000,
    0b01110000, 0b01110000,
    0b00000000, 0b00110000 };

  void setup() {
    Serial.begin(9600);

    // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
    if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
      Serial.println(F("SSD1306 allocation failed"));
      for(;;); // Don't proceed, loop forever
    }

    // Show initial display buffer contents on the screen --
    // the library initializes this with an Adafruit splash screen.
    display.display();
    delay(2000); // Pause for 2 seconds

    // Clear the buffer
    display.clearDisplay();

    // Draw a single pixel in white
    display.drawPixel(10, 10, SSD1306_WHITE);

    // Show the display buffer on the screen. You MUST call display() after
    // drawing commands to make them visible on screen!
    display.display();
    delay(2000);
    // display.display() is NOT necessary after every single drawing command,
    // unless that's what you want...rather, you can batch up a bunch of
    // drawing operations and then update the screen all at once by calling
    // display.display(). These examples demonstrate both approaches...

    testdrawline();      // Draw many lines

    testdrawrect();      // Draw rectangles (outlines)

    testfillrect();      // Draw rectangles (filled)

    testdrawcircle();    // Draw circles (outlines)

    testfillcircle();    // Draw circles (filled)

    testdrawroundrect(); // Draw rounded rectangles (outlines)

    testfillroundrect(); // Draw rounded rectangles (filled)

    testdrawtriangle();  // Draw triangles (outlines)

    testfilltriangle();  // Draw triangles (filled)

    testdrawchar();      // Draw characters of the default font

    testdrawstyles();    // Draw 'stylized' characters

    testscrolltext();    // Draw scrolling text

    testdrawbitmap();    // Draw a small bitmap image

    // Invert and restore display, pausing in-between
    display.invertDisplay(true);
    delay(1000);
    display.invertDisplay(false);
    delay(1000);

    testanimate(logo_bmp, LOGO_WIDTH, LOGO_HEIGHT); // Animate bitmaps
  }

  void loop() {
  }

  void testdrawline() {
    int16_t i;

    display.clearDisplay(); // Clear display buffer

    for(i=0; i<display.width(); i+=4) {
      display.drawLine(0, 0, i, display.height()-1, SSD1306_WHITE);
      display.display(); // Update screen with each newly-drawn line
      delay(1);
    }
    for(i=0; i<display.height(); i+=4) {
      display.drawLine(0, 0, display.width()-1, i, SSD1306_WHITE);
      display.display();
      delay(1);
    }
    delay(250);

    display.clearDisplay();

    for(i=0; i<display.width(); i+=4) {
      display.drawLine(0, display.height()-1, i, 0, SSD1306_WHITE);
      display.display();
      delay(1);
    }
    for(i=display.height()-1; i>=0; i-=4) {
      display.drawLine(0, display.height()-1, display.width()-1, i, SSD1306_WHITE);
      display.display();
      delay(1);
    }
    delay(250);

    display.clearDisplay();

    for(i=display.width()-1; i>=0; i-=4) {
      display.drawLine(display.width()-1, display.height()-1, i, 0, SSD1306_WHITE);
      display.display();
      delay(1);
    }
    for(i=display.height()-1; i>=0; i-=4) {
      display.drawLine(display.width()-1, display.height()-1, 0, i, SSD1306_WHITE);
      display.display();
      delay(1);
    }
    delay(250);

    display.clearDisplay();

    for(i=0; i<display.height(); i+=4) {
      display.drawLine(display.width()-1, 0, 0, i, SSD1306_WHITE);
      display.display();
      delay(1);
    }
    for(i=0; i<display.width(); i+=4) {
      display.drawLine(display.width()-1, 0, i, display.height()-1, SSD1306_WHITE);
      display.display();
      delay(1);
    }

    delay(2000); // Pause for 2 seconds
  }

  void testdrawrect(void) {
    display.clearDisplay();

    for(int16_t i=0; i<display.height()/2; i+=2) {
      display.drawRect(i, i, display.width()-2*i, display.height()-2*i, SSD1306_WHITE);
      display.display(); // Update screen with each newly-drawn rectangle
      delay(1);
    }

    delay(2000);
  }

  void testfillrect(void) {
    display.clearDisplay();

    for(int16_t i=0; i<display.height()/2; i+=3) {
      // The INVERSE color is used so rectangles alternate white/black
      display.fillRect(i, i, display.width()-i*2, display.height()-i*2, SSD1306_INVERSE);
      display.display(); // Update screen with each newly-drawn rectangle
      delay(1);
    }

    delay(2000);
  }

  void testdrawcircle(void) {
    display.clearDisplay();

    for(int16_t i=0; i<max(display.width(),display.height())/2; i+=2) {
      display.drawCircle(display.width()/2, display.height()/2, i, SSD1306_WHITE);
      display.display();
      delay(1);
    }

    delay(2000);
  }

  void testfillcircle(void) {
    display.clearDisplay();

    for(int16_t i=max(display.width(),display.height())/2; i>0; i-=3) {
      // The INVERSE color is used so circles alternate white/black
      display.fillCircle(display.width() / 2, display.height() / 2, i, SSD1306_INVERSE);
      display.display(); // Update screen with each newly-drawn circle
      delay(1);
    }

    delay(2000);
  }

  void testdrawroundrect(void) {
    display.clearDisplay();

    for(int16_t i=0; i<display.height()/2-2; i+=2) {
      display.drawRoundRect(i, i, display.width()-2*i, display.height()-2*i,
        display.height()/4, SSD1306_WHITE);
      display.display();
      delay(1);
    }

    delay(2000);
  }

  void testfillroundrect(void) {
    display.clearDisplay();

    for(int16_t i=0; i<display.height()/2-2; i+=2) {
      // The INVERSE color is used so round-rects alternate white/black
      display.fillRoundRect(i, i, display.width()-2*i, display.height()-2*i,
        display.height()/4, SSD1306_INVERSE);
      display.display();
      delay(1);
    }

    delay(2000);
  }

  void testdrawtriangle(void) {
    display.clearDisplay();

    for(int16_t i=0; i<max(display.width(),display.height())/2; i+=5) {
      display.drawTriangle(
        display.width()/2  , display.height()/2-i,
        display.width()/2-i, display.height()/2+i,
        display.width()/2+i, display.height()/2+i, SSD1306_WHITE);
      display.display();
      delay(1);
    }

    delay(2000);
  }

  void testfilltriangle(void) {
    display.clearDisplay();

    for(int16_t i=max(display.width(),display.height())/2; i>0; i-=5) {
      // The INVERSE color is used so triangles alternate white/black
      display.fillTriangle(
        display.width()/2  , display.height()/2-i,
        display.width()/2-i, display.height()/2+i,
        display.width()/2+i, display.height()/2+i, SSD1306_INVERSE);
      display.display();
      delay(1);
    }

    delay(2000);
  }

  void testdrawchar(void) {
    display.clearDisplay();

    display.setTextSize(1);      // Normal 1:1 pixel scale
    display.setTextColor(SSD1306_WHITE); // Draw white text
    display.setCursor(0, 0);     // Start at top-left corner
    display.cp437(true);         // Use full 256 char 'Code Page 437' font

    // Not all the characters will fit on the display. This is normal.
    // Library will draw what it can and the rest will be clipped.
    for(int16_t i=0; i<256; i++) {
      if(i == '\n') display.write(' ');
      else          display.write(i);
    }

    display.display();
    delay(2000);
  }

  void testdrawstyles(void) {
    display.clearDisplay();

    display.setTextSize(1);             // Normal 1:1 pixel scale
    display.setTextColor(SSD1306_WHITE);        // Draw white text
    display.setCursor(0,0);             // Start at top-left corner
    display.println(F("Hello, world!"));

    display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Draw 'inverse' text
    display.println(3.141592);

    display.setTextSize(2);             // Draw 2X-scale text
    display.setTextColor(SSD1306_WHITE);
    display.print(F("0x")); display.println(0xDEADBEEF, HEX);

    display.display();
    delay(2000);
  }

  void testscrolltext(void) {
    display.clearDisplay();

    display.setTextSize(2); // Draw 2X-scale text
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(10, 0);
    display.println(F("scroll"));
    display.display();      // Show initial text
    delay(100);

    // Scroll in various directions, pausing in-between:
    display.startscrollright(0x00, 0x0F);
    delay(2000);
    display.stopscroll();
    delay(1000);
    display.startscrollleft(0x00, 0x0F);
    delay(2000);
    display.stopscroll();
    delay(1000);
    display.startscrolldiagright(0x00, 0x07);
    delay(2000);
    display.startscrolldiagleft(0x00, 0x07);
    delay(2000);
    display.stopscroll();
    delay(1000);
  }

  void testdrawbitmap(void) {
    display.clearDisplay();

    display.drawBitmap(
      (display.width()  - LOGO_WIDTH ) / 2,
      (display.height() - LOGO_HEIGHT) / 2,
      logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
    display.display();
    delay(1000);
  }

  #define XPOS   0 // Indexes into the 'icons' array in function below
  #define YPOS   1
  #define DELTAY 2

  void testanimate(const uint8_t *bitmap, uint8_t w, uint8_t h) {
    int8_t f, icons[NUMFLAKES][3];

    // Initialize 'snowflake' positions
    for(f=0; f< NUMFLAKES; f++) {
      icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
      icons[f][YPOS]   = -LOGO_HEIGHT;
      icons[f][DELTAY] = random(1, 6);
      Serial.print(F("x: "));
      Serial.print(icons[f][XPOS], DEC);
      Serial.print(F(" y: "));
      Serial.print(icons[f][YPOS], DEC);
      Serial.print(F(" dy: "));
      Serial.println(icons[f][DELTAY], DEC);
    }

    for(;;) { // Loop forever...
      display.clearDisplay(); // Clear the display buffer

      // Draw each snowflake:
      for(f=0; f< NUMFLAKES; f++) {
        display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SSD1306_WHITE);
      }

      display.display(); // Show the display buffer on the screen
      delay(200);        // Pause for 1/10 second

      // Then update coordinates of each flake...
      for(f=0; f< NUMFLAKES; f++) {
        icons[f][YPOS] += icons[f][DELTAY];
        // If snowflake is off the bottom of the screen...
        if (icons[f][YPOS] >= display.height()) {
          // Reinitialize to a random position, just off the top
          icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
          icons[f][YPOS]   = -LOGO_HEIGHT;
          icons[f][DELTAY] = random(1, 6);
        }
      }
    }
  }

Execution Result#

Display on OLED with my own code.#

When the button is pressed, the LED lights up and the OLED displays “pushed! is displayed on the OLED in large size, and when the button is released, the OLED will display a lonely “not pushed…” is displayed on the OLED when the button is released.

The code prior to this used Aldino and the Aldino IDE. But this time, the program code is in C++ and the editor is cursor, a forked vscode editor. And as a development environment, I used platformIO, an extension of vscode. This makes Object-Oriented Programming very easy to implement. Therefore, this time I am writing this code in Object-Oriented Programming like.

How to run programs and about programs#

What is platformIO?#

  • PlatformIO
    PlatformIO is an open-source IoT development ecosystem that supports multiple embedded boards and frameworks, providing cross-platform build, library management, debugging, testing, and IDE integration. It specializes in programming in C++.
    For this project, I used PlatformIO to write the code. Therefore, I will also explain how to prepare and install PlatformIO.

1. PlatformIO Setup#

  1. In VS Code (or a VS Code fork), open the Extensions view, search for “PlatformIO,” and install it.

2. Creating the Project Files#

  1. Open PlatformIO Home and select New Project.

  2. Enter the project name, select the board and language, then click Finish. For this project, I chose the XIAO ESP32S3 board and the Arduino framework.

  3. The environment is now ready. In the video below, I create a new sample project so you can see how it behaves.

3. Configuring the platformio.ini File#

The Arduino IDE uses a “one sketch = one project” model, automatically retaining board selection and build settings via GUI and internal settings files (~/.arduinoIDE/settings.json / arduino-cli.yaml), so you don’t need a per-project config file.
However, PlatformIO declaratively manages multiple platforms (MCUs), frameworks, build options, and library dependencies on a per-project basis to reproduce the same source across different environments. Therefore, you need a platformio.ini file:

ini files
[env:seeed_xiao_esp32s3]
platform = espressif32
board = seeed_xiao_esp32s3
framework = arduino
upload_speed = 115200
monitor_speed = 115200
upload_port = /your/port
board_build.partitions = huge_app.csv
build_flags = 
    -DARDUINO_USB_MODE=1
    -DARDUINO_USB_CDC_ON_BOOT=1
lib_deps = 
    bblanchon/ArduinoJson @^6.21.2
    adafruit/Adafruit GFX Library@^1.12.1
    adafruit/Adafruit SSD1306@^2.5.14
lib_ldf_mode = deep

4. project file configuration#

Place the files in the following configuration.

.
├── include
   ├── Button.h
   ├── LED.h
   └── OLEDdisplay.h

└── src
    ├── Button.cpp
    ├── LED.cpp
    ├── main.cpp
    └── OLEDdisplay.cpp

5. Various programs#

h file#

This is the file where only each class is initially declared. Behaviors are implemented in individual cpp files.

Button.h
#pragma once
#include <Arduino.h>

class Button
{

public:
    Button(uint8_t pin);
    void begin();
    bool wasPressed(); 
    bool isPressed();  

private:
    uint8_t pin;
    bool lastState;
};

LED.h
#pragma once
#include <Arduino.h>

class LED
{
public:
    LED(uint8_t pin);
    void begin();
    void on();
    void off();
    void toggle();
    bool isOn();

private:
    uint8_t pin;
    bool state;
};
Declaring pragma once prevents it from being included multiple times. This is useful. I wanted to change the text size for each message, so I set the textSize argument to showMessage.
OLEDdisplay.h
#pragma once
#include <Adafruit_SSD1306.h>

class OLEDdisplay
{
public:
    OLEDdisplay(uint8_t width, uint8_t height);
    void begin();
    void showMessage(const String &text, uint8_t textSize = 1);

private:
    Adafruit_SSD1306 display;
    String currentText;
};

cpp file (class behavior)#

Button.cpp
#include "Button.h"

Button::Button(uint8_t pin) : pin(pin), lastState(HIGH) {}

void Button::begin()
{
    Serial.println("btn code reading start");
    pinMode(pin, INPUT_PULLUP);
}

bool Button::wasPressed()
{
    bool current = digitalRead(pin);
    bool pressed = (lastState == HIGH && current == LOW);
    lastState = current;
    return pressed;
}

bool Button::isPressed()
{
    Serial.println("Button is Pressed pin:" + String(digitalRead(pin)));
    return digitalRead(pin) == LOW;
}
I didn’t use it this time, but I thought it might be useful in the future, so I have implemented the toggle behavior. Each time toggle is called, the high and low states are reversed.
LED.cpp
#include "LED.h"

LED::LED(uint8_t pin) : pin(pin), state(false) {}

void LED::begin()
{
    Serial.println("LED code reading start");
    pinMode(pin, OUTPUT);
    digitalWrite(pin, LOW);
}

void LED::on()
{
    if (!state)
    {
        Serial.println("LED on");
        digitalWrite(pin, HIGH);
        state = true;
    }
}

void LED::off()
{
    if (state)
    {
        Serial.println("LED off");
        digitalWrite(pin, LOW);
        state = false;
    }
}

void LED::toggle()
{
    Serial.println("LED toggle");
    state = !state;
    digitalWrite(pin, state ? HIGH : LOW);
}

bool LED::isOn()
{
    Serial.println("LED's state is " + String(state));
    return state;
}

The OLEDdisplay removes the previous display and puts the behavior such as text position and text color in one showMessage. Also, the text is displayed anew only when the text is updated to avoid unnecessary updates over and over again.

OLEDdisplay.cpp
#include "OLEDdisplay.h"

OLEDdisplay::OLEDdisplay(uint8_t width, uint8_t height)
    : display(width, height, &Wire, -1), currentText("") {}

void OLEDdisplay::begin()
{
    Serial.println("OLEDdisplay code reading start");
    if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C))
    {
        Serial.println("display Initialization Failure.So I freeze....");
        while (true)
            ; 
    }
    display.clearDisplay();
    display.display();
}

void OLEDdisplay::showMessage(const String &text, uint8_t textSize)
{
    if (text != currentText)
    {
        currentText = text;
        display.clearDisplay();
        display.setTextSize(textSize);
        display.setTextColor(SSD1306_WHITE);
        display.setCursor(5, 5);
        display.println(text);
        display.display();
    }
}

cpp file (main)#

The code in one file is very clear because we only need to load the various classes and write simple instructions. Glad to see the cognitive load reduced! 😊

main.cpp
#include <Arduino.h>
#include "LED.h"
#include "Button.h"
#include "OLEDdisplay.h"
LED statusLED(4);
OLEDdisplay oled(128, 64);
Button button(1);

unsigned long lastPressTime = 0;
const unsigned long displayDuration = 10000;

void setup()
{
    Serial.begin(115200);
    Serial.println("setup start.");
    statusLED.begin();
    oled.begin();
    button.begin();
    oled.showMessage("Waiting...");
    Serial.println("setup finished.");
}

void loop()
{
    static bool lastButtonState = false;
    bool currentState = button.isPressed();

    if (currentState != lastButtonState)
    {
        if (currentState)
        {
            lastPressTime = millis();
            oled.showMessage("pushed!!!", 4);
            statusLED.on();
        }
        else
        {
            oled.showMessage("not pushed...", 2);
            statusLED.off();
        }
        lastButtonState = currentState;
    }

    delay(10);
}

Display on OLED with my own board.#

I ran the same program on the board I built for the final project.

Modified program section#

Change only main.cpp as follows.

main.cpp
#include <Arduino.h>
#include "Button.h"
#include "OLEDdisplay.h"
Button button(1);
OLEDdisplay oled(128, 64);

unsigned long lastPressTime = 0;
const unsigned long displayDuration = 10000;

void setup()
{
  Serial.begin(115200);
  Serial.println("setup start.");
  oled.begin();
  button.begin();
  oled.showMessage("Waiting...");
  Serial.println("setup finished.");
}

void loop()
{
  static bool lastButtonState = false;
  bool currentState = button.isPressed();

  if (currentState != lastButtonState)
  {
    if (currentState)
    {
      lastPressTime = millis();
      oled.showMessage("pushed!!!", 4);
    }
    else
    {
      oled.showMessage("not pushed...", 2);
    }
    lastButtonState = currentState;
  }

  delay(10);
}

pcb design and cuttingyy#

fixing holes on the PCB and widened the copper wire. Overall spaciousness was added.

Once the PCB design was complete, the exported PNG file was uploaded to Mods to create gcode data.

Warning

When creating mods, traces are fine with ISO 1/64, but be careful with holes and outlines because the drill is 7mm.

Again, a drill is used to cut the copper plate. This time, I cut a total of four PCBs for the other members.

Improved board

Perform soldering 1. apply flux. 2. solder the ends of the components. 3. solder the other parts as well.

Soldering was done.

3. Download link#

week10 board files