teensy 4.1 + OLED 4x20 NHD-0420CW-AY3

iniitu

Member
Hello,
i am trying to use the Teensy 4.1 with an OLED 4x20 display, with I²C communications ;
it works about 99,9% of the time well, but there is an occasionnal glitch,
as you can see at the end of the 2nd line : there should be 2 more squares

20210103_103841.jpg

i used a code found on the newHaven forum, barely modified ( delay values ) :

Code:
#include <Arduino.h>

/*
 * Demo_NHD0420CW-Ax3_I2C.ino
 * // from http://www.nhdforum.newhavendisplay.com/index.php?topic=914.0
 *
 * Tutorial sketch for use of character OLED slim display family by Newhaven with Arduino Uno, using
 * only Wire (I2C) library.  Models: NHD0420CW-Ax3, NHD0220CW-Ax3, NHD0216CW-Ax3. Controller: US2066
 * in this example, the display is connected to Arduino via I2C interface.
 *
 * Displays on the OLED alternately a 4-line message and a sequence of character "block".
 * This sketch assumes the use of a 4x20 display; if different, modify the values of the two variables
 * ROW_N e COLUMN_N.
 * The sketch uses the minimum possible of Arduino's pins; if you intend to use also /RES line,
 * the related instructions are already present, it's sufficient to remove the comment markers.
 *
 * The circuit:
 * OLED pin 1 (Vss)          to Arduino pin ground
 * OLED pin 2 (VDD)          to Arduino pin 5V
 * OLED pin 3 (REGVDD)       to Arduino pin 5V
 * OLED pin 4 (SA0)          to Vss ground   (to assign I2C address 0x3D, connect to VDD 5V)
 * OLED pin 5 and 6          to Vss ground
 * OLED pin 7 (SCL)          to Arduino pin A5 (SCL); 10K pull-up resistor on OLED pin
 * OLED pin 8 and 9 (SDAin,SDAout) to Arduino pin A4 (SDA); 10K pull-up resistor on OLED pin
 * OLED pin 10 to 15         to Vss ground
 * OLED pin 16 (/RES)        to Arduino pin Reset or VDD 5V (or to Arduino pin D13, to control reset by sw)
 * OLED pin 17 (BS0)         to Vss ground
 * OLED pin 18 (BS1)         to VDD 5V
 * OLED pin 19 (BS2)         to Vss ground
 * OLED pin 20 (Vss)         to Vss ground
 *
 * Original example created by Newhaven Display International Inc.
 * Modified and adapted to Arduino Uno 15 Mar 2015 by Pasquale D'Antini
 * Modified 19 May 2015 by Pasquale D'Antini
 *
 * This example code is in the public domain.
 */

#include <Wire.h>

const byte ROW_N = 4;     // Number of display rows
const byte COLUMN_N = 20; // Number of display columns

//const byte RES = 13;                // Arduino's pin assigned to the Reset line (optional, can be always high)

const byte SLAVE2W = 0x3C; // Display I2C address, in 7-bit form: 0x3C if SA0=LOW, 0x3D if SA0=HIGH

const byte TEXT[4][21] = {"1-Newhaven Display--",
                          "2-------Test--------",
                          "3-16/20-Characters--",
                          "4!@#$%^&*()_+{}[]<>?"}; // Strings to be displayed

byte new_line[4] = {0x80, 0xA0, 0xC0, 0xE0}; // DDRAM address for each line of the display
byte rows = 0x08;                            // Display mode: 1/3 lines or 2/4 lines; default 2/4 (0x08)
byte tx_packet[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// Packet to be transmitted (max 20 bytes)
// _______________________________________________________________________________________

void send_packet(byte x);

void command(byte c) // SUBROUTINE: PREPARES THE TRANSMISSION OF A COMMAND
{
  tx_packet[0] = 0x00; // Control Byte; C0_bit=0, D/C_bit=0 -> following Data Byte contains command
  tx_packet[1] = c;    // Data Byte: the command to be executed by the display
  send_packet(2);      // Transmits the two bytes
}
// _______________________________________________________________________________________

void data(byte d) // SUBROUTINE: PREPARES THE TRANSMISSION OF A BYTE OF DATA
{
  tx_packet[0] = 0x40; // Control Byte; C0_bit=0, D/C_bit=1 -> following Data Byte contains data
  tx_packet[1] = d;    // Data Byte: the character to be displayed
  send_packet(2);      // Transmits the two bytes
}
// _______________________________________________________________________________________

void send_packet(byte x) // SUBROUTINE: SEND TO THE DISPLAY THE x BYTES STORED IN tx_packet
{
  byte ix = 0; // Bytes index

  Wire.beginTransmission(SLAVE2W); // Begin the transmission via I2C to the display with the given address
  for (ix = 0; ix < x; ix++)       // One byte at a time,
  {
    Wire.write(tx_packet[ix]); //  queue bytes for transmission
  }
  Wire.endTransmission(); // Transmits the bytes that were queued
}
// _______________________________________________________________________________________

void output(void) // SUBROUTINE: DISPLAYS THE FOUR STRINGS, THEN THE SAME IN REVERSE ORDER
{
  byte r = 0; // Row index
  byte c = 0; // Column index

  command(0x01); // Clears display (and cursor home)
  delay(200);      // After a clear display, a minimum pause of 1-2 ms is required

  for (r = 0; r < ROW_N; r++) // One row at a time,
  {
    command(new_line[r]);          //  moves the cursor to the first column of that line
    for (c = 0; c < COLUMN_N; c++) // One character at a time,
    {
      data(TEXT[r][c]); //  displays the correspondig string
    }
  }

  delay(2000); // Waits, only for visual effect purpose

  for (r = 0; r < ROW_N; r++) // One row at a time,
  {
    command(new_line[r]);          //  moves the cursor to the first column of that line
    for (c = 0; c < COLUMN_N; c++) // One character at a time,
    {
      data(TEXT[3 - r][c]); //  displays the correspondig string (in reverse order)
    }
  }
}
// _______________________________________________________________________________________

void blocks(void) // SUBROUTINE: FILLS THE ENTIRE DISPLAY WITH THE CHARACTER "BLOCK"
{
  byte r = 0; // Row index
  byte c = 0; // Column index

  command(0x01); // Clear display (and cursor home)
  delay(2);      // After a clear display, a minimum pause of 1-2 ms is required

  for (r = 0; r < ROW_N; r++) // One row at a time,
  {
    command(new_line[r]);          //  moves the cursor to the first column of that line
    for (c = 0; c < COLUMN_N; c++) // One character at a time,
    {
      data(0xDB); //  displays the character 0xDB (block)
      delay(5);  // Waits, only for visual effect purpose
    }
    delay(500); // Waits, only for visual effect purpose
  }
}
// _______________________________________________________________________________________

void setup(void) // INITIAL SETUP
{
  //   pinMode(RES, OUTPUT);            // Initializes Arduino pin for the Reset line (optional)
  //   digitalWrite(RES, HIGH);         // Sets HIGH the Reset line of the display (optional, can be always high)
  delayMicroseconds(200); // Waits 200 us for stabilization purpose
  Wire.begin();           // Initiate the Wire library and join the I2C bus as a master
  Wire.setSCL(19);
  Wire.setSDA(18);

  delay(10); // Waits 10 ms for stabilization purpose

  if (ROW_N == 2 || ROW_N == 4)
    rows = 0x08; // Display mode: 2/4 lines
  else
    rows = 0x00; // Display mode: 1/3 lines

  command(0x22 | rows); // Function set: extended command set (RE=1), lines #
  command(0x71);        // Function selection A:
  data(0x5C);           //  enable internal Vdd regulator at 5V I/O mode (def. value) (0x00 for disable, 2.8V I/O)
  command(0x20 | rows); // Function set: fundamental command set (RE=0) (exit from extended command set), lines #
  command(0x08);        // Display ON/OFF control: display off, cursor off, blink off (default values)
  command(0x22 | rows); // Function set: extended command set (RE=1), lines #
  
  command(0x79);        // OLED characterization: OLED command set enabled (SD=1)
  command(0xD5);        // Set display clock divide ratio/oscillator frequency:
  command(0x70);        //  divide ratio=1, frequency=7 (default values)
  command(0x78);        // OLED characterization: OLED command set disabled (SD=0) (exit from OLED command set)

  if (ROW_N > 2)
    command(0x09); // Extended function set (RE=1): 5-dot font, B/W inverting disabled (def. val.), 3/4 lines
  else
    command(0x08); // Extended function set (RE=1): 5-dot font, B/W inverting disabled (def. val.), 1/2 lines

  command(0x06);        // Entry Mode set - COM/SEG direction: COM0->COM31, SEG99->SEG0 (BDC=1, BDS=0)
  command(0x72);        // Function selection B:
  data(0x0A);           //  ROM/CGRAM selection: ROM C, CGROM=250, CGRAM=6 (ROM=10, OPR=10)
  command(0x79);        // OLED characterization: OLED command set enabled (SD=1)
  command(0xDA);        // Set SEG pins hardware configuration:
  command(0x10);        //  alternative odd/even SEG pin, disable SEG left/right remap (default values)
  command(0xDC);        // Function selection C:
  command(0x00);        //  internal VSL, GPIO input disable
  command(0x81);        // Set contrast control:
  command(0x60);        //  contrast value 
  command(0xD9);        // Set phase length:
  command(0xF1);        //  phase2=15, phase1=1 (default: 0x78)
  command(0xDB);        // Set VCOMH deselect level:
  command(0x40);        //  VCOMH deselect level=1 x Vcc (default: 0x20=0,77 x Vcc)
  command(0x78);        // OLED characterization: OLED command set disabled (SD=0) (exit from OLED command set)
  command(0x20 | rows); // Function set: fundamental command set (RE=0) (exit from extended command set), lines #
  command(0x01);        // Clear display
  delay(2);             // After a clear display, a minimum pause of 1-2 ms is required
  command(0x80);        // Set DDRAM address 0x00 in address counter (cursor home) (default value)
  command(0x0C);        // Display ON/OFF control: display ON, cursor off, blink off
  delay(250);           // Waits 250 ms for stabilization purpose after display on

  if (ROW_N == 2)
    new_line[1] = 0xC0; // DDRAM address for each line of the display (only for 2-line mode)

    Serial.begin(9600);
}

void loop(void) // MAIN PROGRAM
{
   output();    // Execute subroutine "output"
  delay(2000); // Waits, only for visual effect purpose
   blocks();    // Execute subroutine "blocks"
  delay(2000);
}

i did try adding a pullup resistor on pin 7 & 8 of the NHD but without effect.

any idea how i could make this work better ?
 
Does the output() function properly display the text strings?

If so then try making string line 2 different to see if it still works.

That will confirm the deivce is talking and displaying that data correctly.
 
yes, the output function, using regular characters, seems to be completely ok ( a bit more difficult to spot an occasional glitch tough )
if i use another character for the blocks function, it's the same : mostly OK, but occasionnally blank, often the last block of the line

ps : time necessary for displaying 1 character is 576 microseconds, which is great ( i'll use this screen for a sequencer, so i'm careful with delays )
 
Last edited:
Is the display running on 5V?

Hopefully all I/O can work with 3.3V and not hurt the Teensy.

This line in setup() just caught attention:

data(0x5C); // enable internal Vdd regulator at 5V I/O mode (def. value) (0x00 for disable, 2.8V I/O)

Wondering if that should be 0x00 to interface to the 3.3V of the Teensy for threshold detection of HIGH value?
 
the display has no external power, i take the + from the teensy 3.3V pin
i tried changing the line you pointed, it still does exactly the same
the strange thing is that it's a the end of the lines... is the I²C expecting something to transmit and fails to do so when no new character comes ?

20210103_121529.jpg
 
the "end of line" is just an artifact, due to the fact that the cursor moves one position and that the new lines starts again at x=0
actually upon further testing, this appears randomly, once in a while - it also appears with the regular letters, but is less obvious
i also think that inserting pull-up resistors makes it slightly better. i dont have 10K so i used 6.8K
can we use the internal pullup for this purpose or does it have to be hardware ?
 
If it works from powered with 3.3V that answers that - just reading the comments at code start suggested it might be powered at 5V and that change might be needed for best use at 3.3V. Mouser does show specs 2.8V,5V and stat sheets shows "2.4V~5.5V Supply Voltage"
Though this line on spec page 6 shows Min/Avg/Max volatages of 4.4V and above for REGVDD? :: Supply Voltage for I/O Regulator REGVDD VDD = 5V 4.4 5.0 5.5 V

The internal pullups on Teensy are not significant enough alone in general for i2c usage. Often seen external pullups at 4.7K or even 2.2K for 3.3V i2c.

It doesn't seem to be limited to the end of line #2 based on p#5 pic - odd though that both missing block pics are 2 blocks short - not just the last one.

Browsing the data sheet (page 5) it shows unused lines on the parallel interface should be tied low. Is that done?

If reading page 16 right it should work up to 400 Mhz? Not sure what the clock speed in use is?

I'd verify the command used in setup and after against the spec PDF to be sure none are missing or misused?

Perhaps take the delay(5) out of blocks() - between blocks to run at normal speed like the sample in that PDF:
Code:
void output()
{
int i;
command(0x01); //clear display
command(0x02); //return home
for(i=0;i<20;i++)
{
data(0x1F); //write solid blocks
}
command(0xA0); //line 2
for(i=0;i<20;i++)
{
data(0x1F); //write solid blocks
}
command(0xC0); //line 3
for(i=0;i<20;i++)
{
data(0x1F); //write solid blocks
}
command(0xE0); //line 4
for(i=0;i<20;i++)
{
data(0x1F); //write solid blocks
}
}

Perhaps pass in the data value to write as :: void output( char dataVal )
Then alternate with calls of 0x1F and 0xDB for filled and open blocks?
 
i seem to have a stable and 100% acccurate version now with the changes you suggested :

Code:
//  data(0x5C);           //  enable internal Vdd regulator at 5V I/O mode (def. value) (0x00 for disable, 2.8V I/O)
  data(0x00);           //  enable internal Vdd regulator at 5V I/O mode (def. value) (0x00 for disable, 2.8V I/O)

and putting back 2 pullup resistors of 6.8K ( i dont know how to calculate the value required, i put the closest ones to 10K i had ) on pins 7 and 8 of the OLED

and yes, all the unused pins are linked to ground.

running since 5 minutes without glitch, seems accurate enough for my use ! thx for the help !
 
Great you got it working!

Looks like a nice clear display - not cheap enough to toss or have bad function from.
 
sure, here are some pictures, the general wiring and the code that works...
it's a beautiful screen, big, very relaxing for the eyes, works under every angle. really delighted with it.

20210107_205507.jpg
20210107_205548.jpg
20210107_205643.jpg

this is adapted from http://www.nhdforum.newhavendisplay.com/index.php?topic=914.0

* The circuit:
* OLED pin 1 (Vss) to Teensy 4.1 pin ground
* OLED pin 2 (VDD) to Teensy 4.1 pin 3.3V
* OLED pin 3 (REGVDD) to Teensy pin 3.3V
* OLED pin 4 (SA0) to Vss ground
* OLED pin 5 and 6 to Vss ground
* OLED pin 7 (SCL) to Teensy pin 19 (SCL); 6.8K pull-up resistor on OLED pin ( doc recommends 10K but i dont have any, and 6.8K works fine )
* OLED pin 8 and 9 (SDAin,SDAout) to Teensy pin 18 (SDA); 6.8K pull-up resistor on OLED pin ( doc recommends 10K, idem )
* OLED pin 10 to 15 to Vss ground
* OLED pin 16 (/RES) to Teensy pin 3.3V
* OLED pin 17 (BS0) to Vss ground
* OLED pin 18 (BS1) to Teensy pin 3.3V
* OLED pin 19 (BS2) to Vss ground
* OLED pin 20 (Vss) to Vss ground

the adaptated code :

Code:
/*
 * Tutorial sketch for use of character OLED slim display family by Newhaven with Teensy 4.1, using
 * only Wire (I2C) library.  Models: NHD0420CW-Ax3, NHD0220CW-Ax3, NHD0216CW-Ax3. Controller: US2066
 * in this example, the display is connected to Teensy 4.1 via I2C interface.
 *
 * Displays any string on the OLED  
 * This sketch assumes the use of a 4x20 display; if different, modify the values of the two variables
 * ROW_N e COLUMN_N.
 * The sketch uses the minimum possible of Teensy's pins; if you intend to use also /RES line,
 * please check the original version
 * 
 * Original example created by Newhaven Display International Inc.
 * Modified and adapted to Arduino Uno 15 Mar 2015 by Pasquale D'Antini
 * Modified 19 May 2015 by Pasquale D'Antini
 * Modified 7 jan 2021 by Sylvain van iniitu ( www.iniitu.net ) 
 *
 * This example code is in the public domain.
 */

#include <Arduino.h>
#include <Wire.h>

const byte ROW_N = 4;     // Number of display rows
const byte COLUMN_N = 20; // Number of display columns

//const byte RES = 13;                // Arduino's pin assigned to the Reset line (optional, can be always high)

const byte SLAVE2W = 0x3C; // Display I2C address, in 7-bit form: 0x3C if SA0=LOW, 0x3D if SA0=HIGH

byte new_line[4] = {0x80, 0xA0, 0xC0, 0xE0}; // DDRAM address for each line of the display
byte rows = 0x08;                            // Display mode: 1/3 lines or 2/4 lines; default 2/4 (0x08)
byte tx_packet[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// Packet to be transmitted (max 20 bytes)
// _______________________________________________________________________________________

void send_packet(byte x);

void command(byte c) // SUBROUTINE: PREPARES THE TRANSMISSION OF A COMMAND
{
  tx_packet[0] = 0x00; // Control Byte; C0_bit=0, D/C_bit=0 -> following Data Byte contains command
  tx_packet[1] = c;    // Data Byte: the command to be executed by the display
  send_packet(2);      // Transmits the two bytes
}
// _______________________________________________________________________________________

void data(byte d) // SUBROUTINE: PREPARES THE TRANSMISSION OF A BYTE OF DATA
{
  tx_packet[0] = 0x40; // Control Byte; C0_bit=0, D/C_bit=1 -> following Data Byte contains data
  tx_packet[1] = d;    // Data Byte: the character to be displayed
  send_packet(2);      // Transmits the two bytes
}
// _______________________________________________________________________________________

void send_packet(byte x) // SUBROUTINE: SEND TO THE DISPLAY THE x BYTES STORED IN tx_packet
{
  byte ix = 0; // Bytes index

  Wire.beginTransmission(SLAVE2W); // Begin the transmission via I2C to the display with the given address
  for (ix = 0; ix < x; ix++)       // One byte at a time,
  {
    Wire.write(tx_packet[ix]); //  queue bytes for transmission
  }
  Wire.endTransmission(); // Transmits the bytes that were queued
}
// _______________________________________________________________________________________

void oledWriteCharXY(byte i, byte x, byte y)
{
  command(new_line[y] + x); // moves the cursor 
  data(i);                  //  fills DDRAM with char, and updates cursor 
}

void writeStringAtXY(String S, byte x, byte y)
{
  byte j;
  for (unsigned int i = 0; i < S.length(); i++)
  {
    j = S[i];
    oledWriteCharXY(j, x + i, y);
  }
}
 
void setup(void) // INITIAL SETUP
{
  delayMicroseconds(200); // Waits 200 us for stabilization purpose
  Wire.begin();           // Initiate the Wire library and join the I2C bus as a master
  Wire.setSCL(19);
  Wire.setSDA(18);

  delay(10); // Waits 10 ms for stabilization purpose

  if (ROW_N == 2 || ROW_N == 4)
    rows = 0x08; // Display mode: 2/4 lines
  else
    rows = 0x00; // Display mode: 1/3 lines
 
  command(0x22 | rows); // Function set: extended command set (RE=1), lines #
  command(0x71);        // Function selection A:

  //  data(0x5C);           //  enable internal Vdd regulator at 5V I/O mode (def. value) (0x00 for disable, 2.8V I/O)
  data(0x00); //  enable internal Vdd regulator at 5V I/O mode (def. value) (0x00 for disable, 2.8V I/O)

  command(0x20 | rows); // Function set: fundamental command set (RE=0) (exit from extended command set), lines #
  command(0x08);        // Display ON/OFF control: display off, cursor off, blink off (default values)
  command(0x22 | rows); // Function set: extended command set (RE=1), lines #

  command(0x79); // OLED characterization: OLED command set enabled (SD=1)
  command(0xD5); // Set display clock divide ratio/oscillator frequency:
  command(0x70); //  divide ratio=1, frequency=7 (default values)
  command(0x78); // OLED characterization: OLED command set disabled (SD=0) (exit from OLED command set)

  if (ROW_N > 2)
    command(0x09); // Extended function set (RE=1): 5-dot font, B/W inverting disabled (def. val.), 3/4 lines
  else
    command(0x08); // Extended function set (RE=1): 5-dot font, B/W inverting disabled (def. val.), 1/2 lines

  command(0x06);        // Entry Mode set - COM/SEG direction: COM0->COM31, SEG99->SEG0 (BDC=1, BDS=0)
  command(0x72);        // Function selection B:
  data(0x0A);           //  ROM/CGRAM selection: ROM C, CGROM=250, CGRAM=6 (ROM=10, OPR=10)
  command(0x79);        // OLED characterization: OLED command set enabled (SD=1)
  command(0xDA);        // Set SEG pins hardware configuration:
  command(0x10);        //  alternative odd/even SEG pin, disable SEG left/right remap (default values)
  command(0xDC);        // Function selection C:
  command(0x00);        //  internal VSL, GPIO input disable
  command(0x81);        // Set contrast control:
  command(0x10);        //  contrast value
  command(0xD9);        // Set phase length:
  command(0xF1);        //  phase2=15, phase1=1 (default: 0x78)
  command(0xDB);        // Set VCOMH deselect level:
  command(0x40);        //  VCOMH deselect level=1 x Vcc (default: 0x20=0,77 x Vcc)
  command(0x78);        // OLED characterization: OLED command set disabled (SD=0) (exit from OLED command set)
  command(0x20 | rows); // Function set: fundamental command set (RE=0) (exit from extended command set), lines #
  command(0x01);        // Clear display
  delay(2);             // After a clear display, a minimum pause of 1-2 ms is required
  command(0x80);        // Set DDRAM address 0x00 in address counter (cursor home) (default value)
  command(0x0C);        // Display ON/OFF control: display ON, cursor off, blink off
  delay(250);           // Waits 250 ms for stabilization purpose after display on

  if (ROW_N == 2)
    new_line[1] = 0xC0; // DDRAM address for each line of the display (only for 2-line mode)
 
  Serial.begin(9600);
}
// _______________________________________________________________________________________

void loop(void) // MAIN PROGRAM
{

  writeStringAtXY("Hello", 1, 0);
 
}
 
Dear group,

My NHD-0420CW-Axx works perfect with a Teensy 3.2, now I changed it for a Teensy4.0 and the display is not working. Are the Teensy's not compatible. The NHD is a SPI device and connected to 13= SCK, MOSI=11 and CS=10. As mentioned the 3.2 has no problems can any help? I suspect that the SPI bus is not 100% compatible....

Best,
Johan
 
Problem solved for this display on a Teensy4, works perfect. I made a mistake to let the display run on 5V instead of 3.3V.

Best,
Johan
 
Question to iniitu

Hi iniitu,

I'm an electronics newbie and was very glad to find your post. I try to get this display to run and would like to understand your reasoning.

* OLED pin 3 (REGVDD) to Teensy pin 3.3V
* OLED pin 4 (SA0) to Vss ground
* OLED pin 7 (SCL) to Teensy pin 19 (SCL); 6.8K pull-up resistor on OLED pin ( doc recommends 10K but i dont have any, and 6.8K works fine )
* OLED pin 8 and 9 (SDAin,SDAout) to Teensy pin 18 (SDA); 6.8K pull-up resistor on OLED pin ( doc recommends 10K, idem )

I try to understand your setup based on the data sheet. Most of it is clear. I have three little questions though:

1. Why do you connect OLED pin 3 to 3.3V? I thought the OLED should run in the 2.8V mode. According to the data sheet, pin 3 should therefore be set to 0V. Am I missing something?

2. What's the slave address select signal, and how do you know it must be 0V?

3. How do you know that the pins connected to the Teensy need pull up resistors?

Thank you very much for any insights
Best, Boris
 
Back
Top