Showing posts with label HT16K33. Show all posts
Showing posts with label HT16K33. Show all posts

Monday, May 8, 2017

Programming the MeinEnigma - Keyboard



I earlier talked about the HT16K33 chip which controls the LEDs. It also scans the keyboard keys which are across part of the same matrix of rows and columns. The chip scans the keyboard for key closures for a brief time when it is not driving the LEDs.

The code to handle keyboard events is implemented using the same library that we used in the LED examples. It provides several functions related to keyboard including keysPressed(), readKey(), and readKeyRaw().

For the example program we will just use readKey(). It returns zero if no key was pressed, a positive number containing a key code if a key was pressed, and a negative key code if a key was released.

I created a lookup table called keys that returns the ASCII character corresponding to a key code. The four buttons under the alphanumeric displays return characters "1" through "4".

The example is very simple. Initialization in the loop() method consists of setting up the serial port and HT16K33 object.

In the main loop we call readKey(). If a key event occured we display to the serial port the ASCII character for the key and whether if was a key press or release.

Here is some sample output:

Key H pressed
Key H released
Key E pressed
Key E released
Key L pressed
Key L released
Key L pressed
Key L released
Key O pressed
Key O released
Key 1 pressed
Key 1 released

More advanced functions, like detecting multiple keys pressed at the same time, can be done with the other functions provided in the ht16k33 library, but the example shows the most basic case of reading the keyboard.

Here is the listing, which can also be found here,

/*
  MeinEnigma Example

  Demonstrates reading the keyboard.
  Uses code from the MeinEnigma software.

  Jeff Tranter

*/

// External library for HT16K33 chip.
#include "ht16k33.h"

// Lookup table of key codes to characters.
const char keys[] = { 'Q','W','E','R','T','Z','U','I','O','A','S','D','F','P','Y','X','C','V','B','N','M','L','G','H','J','K','1','2','3','4' };

// Object instance for HT18K33 chip.
HT16K33 HT;

void setup() {
  Serial.begin(9600); // Initialize serial port for debug output.
  HT.begin(0x00);     // Need to initialize the chip in order for keyboard to work.
}

void loop() {
  int k;

  k = HT.readKey();
  if (k != 0) {
    Serial.print("Key ");
    Serial.print(keys[abs(k)-1]);
    Serial.print(" ");
    if (k > 0) {
      Serial.println("pressed");
    } else
      Serial.println("released");
  }

  delay(100);
}

Sunday, May 7, 2017

Programming the MeinEnigma - Display LEDs



The four alphanumeric LEDs on the MeinEnigma are controlled by the HT16K33 chip in a similar fashion to the discrete LEDs. The only difference is visual: the alphanumeric LEDs are made up of 16 LED segments that can be individually controlled.

While you could program the segments using appropriate calls to setLed(), it gets tedious to program the right segments and typically you want to display a character. The MeinEnigma software uses a "font", an array defining the 16-bit patterns for each ASCII character. Then it implements a function displayLetter() which accepts an ASCII character and display number and shows the character on the associated LED.

For this example program I have lifted the font and the implementation of displayLetter(). The example program first turns all display segments on and then off using setLedNow() and clearLedNow() to demonstrate controlling the individual segments.

Next it displays the characters "ABCD" by calling displayLetter().

Finally, it displays the entire font character set on the LEDs (which, to save memory, is not the entire ASCII character set).

The listing is short enough to show below.

The full code can be found here.

/*
  MeinEnigma Example

  Demonstrates controlling the 4 alphanumeric LEDs on the main board.
  Uses code from the MeinEnigma software.

  Jeff Tranter

*/

// External library for HT16K33 chip.
#include "ht16k33.h"

// LED font table
#include "asciifont-pinout12.h"

HT16K33 HT;

// Display a character on one of the displays. Leftmost display is 0,
// rightmost is 3.
void displayLetter(char letter, uint8_t dispeno) {
  uint8_t led;
  int8_t i;
  uint16_t val;

  led = (dispeno) * 16;
  if (letter > '`')
    letter -= ('a' - 'A');
  val = pgm_read_word(fontTable + letter - ' ');

  // No lookup table needed, all LEDs are at offset 64
  HT.setDisplayRaw(dispeno * 2 + 64 / 8, val & 0xFF);
  HT.setDisplayRaw(dispeno *2 + 64 / 8 + 1, val>>8);
  HT.sendLed();
}

void setup() {
  // Need to initialize the chip in order for displays to
  // work. This also clears all display segments and LEDs.
  HT.begin(0x00);

  // Clear the RAM to make sure we read data but don't send it.
  for (int i = 0; i < sizeof(HT.displayRam); i++)
    HT.displayRam[i] = 0;
}

void loop() {
  int i;

  // All segments on
  for (i = 64; i <= 127; i++) {
    HT.setLedNow(i);
    delay(50);
  }

  // All segments off
  for (i = 64; i <= 127; i++) {
    HT.clearLedNow(i);
    delay(50);
  }

  // Display some characters
  displayLetter('A', 0);
  displayLetter('B', 1);
  displayLetter('C', 2);
  displayLetter('D', 3);
  delay(1000);

  // Display full character set.
  int j = 0;
  for (char c = ' '; c <= '`'; c++) {
    displayLetter(c, j % 4);
    j++;
    delay(200);
  }
}

Saturday, May 6, 2017

Programming the MeinEnigma - Discrete LEDs



How do you control 26 LEDs? A simplistic approach is a digital output pin per LED, but you would need 26, more than most Arduino boards other than the largest and most expensive models. Another solution is to arrange them in an array of rows and columns with an LED at the intersection of each row and column. By driving the appropriate rows and columns you can control the relevant LEDs. It could now be done with three rows of 9 columns, only requiring 3 + 9 = 12 i/o pins.

How about reading 26 keyboard switches? Again, you could use 26 separate inputs, but an array of switches arranged in rows and columns will also work like it did for LEDs, only requiring 12 pins for a 3 x 9 matrix.

LEDs are diodes which only pass current in one direction. If we connected the LEDs and switches to the same matrix, we can apply voltage in one direction to drive each LED. In the reverse direction, the LED will not conduct and we can detect if a switch (in series with a resistor) is connected across any row/column intersection. We will have to turn off the LED while we check for a keyswitch, but we can do this for only a brief time and the user will not notice, provided that the LEDs are on most of the time.

The HT16K33 LED Controller Driver with keyscan is the chip used to do this on the MeinEnigma. It can control up to a 16x8 matrix of LEDs and a 3x13 matrix of keys. It takes care of driving the LEDs that have been programmed on or off and scanning the keyboard for brief times so the LEDs still appear illuminated. It has some other nice features like the ability to adjust the LED brightness, flash the LEDs at a specific rate, and to handle multiple keys pressed at the same time. It does all this over a 2-wire I2C interface, so it only takes two Arduino pins and we can share them with the other I2C devices (like the real-time clock) because they can all have unique I2C addresses.

The chip controls not only the 26 discrete LEDs, and 26 + 4 keys, but also the four 16-segment alphanumeric displays.

Programming the HT16K33 is relatively complex, but fortunately an Arduino library has been written that takes care of the details of programming it. Using the library we can simply call methods like setLed() and clearLed().

The routines provided are listed and documented in the header file ht16k33.h. The implementation is in the file ht16k33.cpp.

I don't have room here to cover the programming of the chip in detail; maybe I will do so in a future blog post. If you want to understand it, take a look at the device data sheet.

For this example we'll look at how to program the 26 discrete LEDs on the lamp and key board. Future examples will look at the alphanumeric displays and keyboard keys which are controlled by the same chip.

Let's step through the program which is listed below and available from here. We first include the "ht16k33.h" library header file and create an instance of the HT16K33 object called HT.

In order to map the LED numbers connected to the chip with the order they are physically arranged on the board, we create a lookup table called ledTable.

In the setup() method we initialize the chip by calling the HT16K33 begin() method passing it the I2C address, zero.

In the main program we demonstrate a number of ways of controlling the LEDs.

The simplest API is to call setLed(), passing the LED number to turn an LED on (or off). We do this for all LEDs in a loop. The LEDs will not actually get turned on until we call sendLed() to send the command.

Next, now that the LEDS are all on, I illustrate changing the brightness by calling setBrightness(). It supports 16 levels of brightness, implemented by changing the duty cycle that the LEDs are on.

Next we turn all LEDs off, and then turn each LED on one at a time by calling setLedNow() which immediately sets the LEDs state without needing to call sendLed().

We then turn all LEDs off in a similar fashion.

Next, we create a little light show by walking one LED on at a time, then turning them all on, and walking one LED off at at time.

Finally, we turn random LEDs on or off for 10 seconds, after which the entire cycle repeats.

Here is an animated image file showing some of the patterns:



And here is the source code:

/*
  MeinEnigma Example

  Demonstrates controlling the 26 discrete LEDs on the lamp and key
  board.

  Jeff Tranter

*/

// External library for HT16K33 chip.
#include "ht16k33.h"

HT16K33 HT;

// Lookup table of LEDs.
byte ledTable[] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 28, 29, 30, 31, 19, 20, 21, 22, 23, 24, 25, 26, 27 };

void setup() {
  // Need to initialize the chip in order for displays to
  // work. This also clears all display segments and LEDs.
  HT.begin(0x00);
}

void loop() {
  int i;

  // All on.
  for (i = 0; i < sizeof(ledTable); i++) {
    HT.setLed(ledTable[i]);
  }
  HT.sendLed();
  delay(1000);

  // Change brightness.
  for (i = 16; i >= 0; i--) {
    HT.setBrightness(i);
    delay(100);
  }
  for (i = 0; i <= 16; i++) {
    HT.setBrightness(i);
    delay(100);
  }

  // All off.
  for (i = 0; i < sizeof(ledTable); i++) {
    HT.clearLed(ledTable[i]);
  }
  HT.sendLed();
  delay(1000);

  // Walk all LEDs on.
  for (i = 0; i < sizeof(ledTable); i++) {
    HT.setLedNow(ledTable[i]);
    delay(100);
  }
  
  // Walk all LEDs off.
  for (i = 0; i < sizeof(ledTable); i++) {
    HT.clearLedNow(ledTable[i]);
    delay(100);
  }
  
  // Walk one LED on.
  for (i = 0; i < sizeof(ledTable); i++) {
    HT.setLedNow(ledTable[i]);
    delay(100);
    HT.clearLedNow(ledTable[i]);
  }

  // All on.
  for (i = 0; i < sizeof(ledTable); i++) {
    HT.setLed(ledTable[i]);
  }
  HT.sendLed();
  
  // Walk one LED off
  for (i = 0; i < sizeof(ledTable); i++) {
    HT.clearLedNow(ledTable[i]);
    delay(100);
    HT.setLedNow(ledTable[i]);
  }

   // All off.
  for (i = 0; i < sizeof(ledTable); i++) {
    HT.clearLed(ledTable[i]);
  }
  HT.sendLed();
  
  // Random.
  for (int j=0; j < 500; j++ ) {
    i = random(sizeof(ledTable));
    if (random(2) == 1) {
      HT.setLedNow(ledTable[i]);
    } else{
      HT.clearLedNow(ledTable[i]);
    }
    delay(20);
  }
}