9. Output Devices

Assignment

Group Assignment

Link to this week's group assignment

SSD1315 128x64 OLED Display

We have some OLED display modules lying around the lab, so I decided to use my new capacitve sensing board to do some screen display.

The display module we have uses the SSD1315 display driver with I2C communication protocol.

The SSD1315 display driver is the new version of the widely used SSD1306 driver chip, and it is designed to be compatible with the old SSD1306, so the display driver control code for SSD1306 can be used to drive the SSD1315.

Wiring

The ATmega328p has dedicated pins for I2C communication and I reserved them on the PCB. Just connect the A4/A5 pins which correspond to the SDA/SCL pins for I2C communication and connect the power pins.

Arduino Driver Library

The Adafruit SSD1306 library is a good library to work with the display, the example comes with the library is a good starting point for creating my own program.

Setting the Correct Address

During my testing, there was only a small problem. The Adafruit library sets the I2C address of the 128x64 screen to 0x3D, but the display I'm using has an address of 0x3C, which can be found using the i2c_scanner example that comes with the Arduino IDE.

After setting the right address, I can control the screen with no problem, then I just need to upload my program to my own board. I exposed the pins for I2C communication (A4/A5) on my capacitive sensing board, below is a program that displays the current sensor state and the processing time on the OLED.

#include <CapacitiveSensor.h>
#include <Adafruit_SSD1306.h>

const int w = 128, h = 64;
const int addr = 0x3C;
Adafruit_SSD1306 display(w, h);

CapacitiveSensor pads[] = {
  { 2, 3 }, { 2, 4 }, { 2, 5 }, { 2, 6 },
  { 2, 7 }, { 2, 8 }, { 2, 9 }, { 2, 10 },
  { 2, 11 }, { 2, 12 },
};
#define array_len(a) (sizeof(a) / sizeof *(a))

void setup() {
  Serial.begin(9600);
  pinMode(13, OUTPUT);

  if (!display.begin(SSD1306_SWITCHCAPVCC, addr)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);  // Don't proceed, loop forever
  }
  display.setTextColor(WHITE);
  display.setTextSize(2);
}

void loop() {
  unsigned long start = millis();

  bool touched = false;
  for (int i = 0; i < array_len(pads); ++i) {
    long val = pads[i].capacitiveSensor(30);
    Serial.print(i);
    Serial.print(": ");
    Serial.print(val);
    Serial.print("\t");
    if (val > 200) {
      touched = true;
      break;
    }
  }
  digitalWrite(13, touched);

  unsigned long processing_time = millis() - start;

  Serial.println();
  Serial.println(processing_time);

  display.clearDisplay();
  display.setCursor(0, 0);
  display.println(touched ? "Touched!" : "No touch");
  display.print("T:");
  display.println(processing_time);
  display.display();
}

Buzzer

Since my final project is a custom synthesizer and the capacitive sensing board is suppose to be its input for tones, the plan is to make it generate input for the base frequency of the synth.

The ideal functionality would be that it can output signal that combines multiple fequencies when more than one pad is touched at the same time, but I couldn't figure out how to make it work with the current design only with a ATmega328p.

Still, as a test, I tried to use the built in tone function to generate a single frequency on the remaing output pin (D13) to drive a buzzer to see if the board can at least make a sound.

The buzzer I'm using is a generic 1206 passive buzzer, which can take voltage from 2V to 7V, and has a resonant frequency of 4000Hz. It can be driven by the digital pin on my board and controled by the Arduino tone() function.

The following program tries to detect the pad that is last touched when multiple pads are being touched, and outputs the coresponding frequency of that pad.

Memory Issue

After I first added the code for keeping track of the last touched pad, the board did nothing after I uploaded the program. After some print debugging, I found that the program has already crashed when initializing the OLED screen at the very begining. It even failed to output the failure message. It started working again after I removed some pad checking code, or removing the capacitive sensor library, turns out that putting all these functionality together consumes too much of the board's memory. It passes the program size check of the Arduino IDE but will fail when initializing the memory for the display buffer of the OLED screen.

So I commented out the code for the OLED screen and ended up inspecting the output from the serial monitor, which is less convient.

#include <CapacitiveSensor.h>

// After adding the code for checking the last touched pad,
// there were not enough memory for the display, so I disabled
// the code for the screen temporarily
//#define SCREEN

#ifdef SCREEN
#include <Adafruit_SSD1306.h>
#define W 128
#define H 64
#define ADDR 0x3C
Adafruit_SSD1306 display(W, H);
#endif

CapacitiveSensor pads[] = {
  { 2, 3 }, { 2, 4 }, { 2, 5 }, { 2, 6 },
  { 2, 7 }, { 2, 8 }, { 2, 9 }, { 2, 10 },
  { 2, 11 }, { 2, 12 },
};
#define array_len(a) (sizeof(a) / sizeof *(a))

void setup() {
  Serial.begin(9600);
  Serial.println();
  pinMode(13, OUTPUT);

#ifdef SCREEN
  if (!display.begin(SSD1306_SWITCHCAPVCC, ADDR)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);  // Don't proceed, loop forever
  }
  Serial.println(F("Init display"));
  display.setTextColor(WHITE);
  display.setTextSize(2);
#endif
}

unsigned long last_touch_time[array_len(pads)] = {};

void loop() {
  unsigned long start = millis();

  int last_touched = -1;
  for (int i = 0; i < array_len(pads); ++i) {
    long val = pads[i].capacitiveSensor(30);
    Serial.print(i);
    Serial.print(": ");
    Serial.print(val);
    Serial.print("\t");

    bool current = val > 200;
    if (current) {
      if (last_touch_time[i] == 0) {
        last_touch_time[i] = millis();
      }
      if (last_touched < 0 || last_touch_time[last_touched] < last_touch_time[i]) {
        last_touched = i;
      }
    } else {
      last_touch_time[i] = 0;
    }
  }
  Serial.println();

  if (last_touched < 0) {
    noTone(13);
  } else {
    tone(13, 300 + last_touched * 100);
  }

  unsigned long processing_time = millis() - start;

  Serial.print(F("Last touched: "));
  Serial.println(last_touched);
  Serial.println(processing_time);

#ifdef SCREEN
  display.clearDisplay();
  display.setCursor(0, 0);
  if (last_touched < 0) {
    display.println(F("No touch"));
  } else {
    display.print(F("Touched "));
    display.println(last_touched);
  }
  display.print(F("T:"));
  display.println(processing_time);
  display.display();
#endif
}

Source files