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);
  }
}

Programming the MeinEnigma - Switches

For the next installment of programming the MeinEnigma hardware, we'll look at something simple: reading the positions of the rotary function switch and the "big red switch".



The red switch, used to reset and erase all settings, is connected to analog pin A6 of the Arduino. It is, however, used only at digital levels. I initially tried reading it using the digitalRead() function, bought found that it didn't work. It turns out that while this works for most Arduinos, the documentation mentions that for the Arduino Nano "Analog pins 6 and 7 cannot be used as digital pins." So you need to read the level using analogRead(). Since the values for analog inputs can range from 0 to 1023, a good test is against the mid-point of the range, 512, to determine if it is high or low.



The position switch uses the trick I described in an earlier blog post: it switches some resistor dividers so that the voltage on analog pin A7 changes depending on the switch position. This allows the five switch positions to be read using only one input.

The values change in steps of 1/4 of the full value of 1024, corresponding to 0, 1/4, 2/4, 3/4, and 4/4 of the full value. To test the values you can check against the middle of the values between these, which is what the MeinEnigma code and my example do. The table below shows the typical expected analog values for each switch position, and the ranges used to test. Note that the ranges correspond to 1/8, 3/8, 5/8, and 7/8 of the full 1024 value.

PositionRange Typical Value
Offsame as 5
1896 < val1023  (4/4)
2640 < val < 896768  (3/4)
3384 < val < 640512  (2/4)
4128 < val < 384256  (1/4)
5val < 1280  (0/4)


The Off position is used to power off the MeinEnigma when running on battery power, but when it is powered by USB the unit is powered up and the switch is equivalent to position 5 (fully clockwise). Another quirk that i mentioned in an early post is that if the big red switch is pressed, the unit is also powered up when on battery power.

The example program reads and displays the levels of the red switch and the function switch. Typical output is shown below:

Red switch  = 1023 (released)  Mode switch = 1023 (pos 1)
Red switch  =    0 (pressed)   Mode switch = 1023 (pos 1)
Red switch  = 1023 (released)  Mode switch = 1023 (pos 1)
Red switch  = 1023 (released)  Mode switch = 771 (pos 2)
Red switch  = 1023 (released)  Mode switch = 514 (pos 3)
Red switch  = 1023 (released)  Mode switch = 259 (pos 4)
Red switch  = 1023 (released)  Mode switch = 0 (pos 5)

Here is the full source code:

/*
  MeinEnigma Example

  Read the values of the function switch and the big red switch.
  See results through Arduino IDE serial monitor set to 9600 bps.

  Jeff Tranter

*/

// Pin numbers for the switches.
#define RED_SWITCH  A6
#define MODE_SWITCH A7

void setup() {
  Serial.begin(9600);           // Initialize serial port.
  pinMode(RED_SWITCH,  INPUT);  // Initialize both pins as inputs.
  pinMode(MODE_SWITCH, INPUT);
}

void loop() {

  // Red switch goes to an analog input but is at a digital level.
  // High when released, low when pressed.
  int val = analogRead(RED_SWITCH);
  Serial.print("Red switch  = ");
  Serial.print(val);
  if (val < 512) {
    Serial.print(" (pressed)");
  } else {
    Serial.print(" (released)");
  }

  // Mode switch returns different values depending on position.
  // Can figure out the position based on the value.
  val = analogRead(MODE_SWITCH);
  Serial.print("  Mode switch = ");
  Serial.print(val);
  if (val < 1024 * 1/8) {
    Serial.println(" (pos 5)");
  } else if (val < 1024 * 3/8) {
    Serial.println(" (pos 4)");
  } else if (val < 1024 * 5/8) {
    Serial.println(" (pos 3)");
  } else if (val < 1024 * 7/8) {
    Serial.println(" (pos 2)");
  } else {
    Serial.println(" (pos 1)");
  }

  delay(500);  // Wait for a half second.
}

Friday, May 5, 2017

Programming the MeinEnigma - Playing Sounds



In this next instalment we'll look at how to play sounds on the MeinEnigma hardware.

Sound support is provided using a DFPlayer Mini sound module which is controlled by a serial interface to the Arduino. It contains a microSD card with MP3 files on it. Serial commands from the Arduino instruct it to play sounds on the SD card. The unit directly drives a small speaker which is mounted on the PCB.

The module is sold by various vendors. The datasheet I found was from Flyron Technology Co., Ltd. for their FN-M16P Embedded MP3 Audio Module.

The interface between the Arduino and sound module is a TTL-level serial interface on digital i/o pins 8 and 9. Since the Arduino only has one hardware serial port which is used for programming through USB, it is not used for this purpose. The MeinEnigma software, and my example program, use the AltSoftSerial.h library to support serial communications on pins 8 and 9. The protocol is mostly done in software.

There is also a hardware BUSY line connected to Arduino pin 12 which is used to determine if the module is currently playing a sound file. It is at a low level when the module is playing and a high level when idle.

Serial communication is at 9600 bps and the module accepts commands in a format documented in the data sheet. A command contains various fields containing parameters. A typical command is to play a specific file on the microSD card or to change the sound volume.

The sample program, listed below, starts with a couple of functions which were lifted from the MeinEnigma code. Function sendCommand() sends an arbitrary command to the sound module.

The function playSound() sends the command to play a sound file, specifying a number. The number is used to determine the filename on the microSD card to play.

The setup() method initializes the serial port and then sends commands to reset the sound module and set the volume.

The main loop() routine plays 26 sound files corresponding to the name of each letter of the alphabet being spoken. The numbers passed to playSound() are 1501 through 1526 and the corresponding sound files on the microSD card are called 1501-a.mp3 through 1526-z.mp3.

It plays each file in turn. At the end it pauses for 10 seconds, and then starts again.

/*
  MeinEnigma Example

  Demonstrates playing sounds with the sound module. The module must
  be present and have MP3 files on the SD card.

  Uses code from the MeinEnigma software.

  Jeff Tranter

*/

#include

AltSoftSerial altSerial;

// For sound module FN_M16P aka DFplayer
#define BUSY 12

#define dfcmd_PLAYNO 0x03       // Play the nth song
#define dfcmd_PLAYNAME 0x12     // Play song in /mp3 named nnnn*mp3
#define dfcmd_VOLUME 0x06       // Set volume
#define dfcmd_RESET  0x0c       // Reset unit
#define dfcmd_GETCNT 0x48       // Get SD card file count
#define dfcmd_GETSTATE 0x42     // Get current status
#define dfcmd_GETFEEDBACK 0x41  // Get feedback from module

// Buffer for sound commands.
uint8_t msgBuf[10]= { 0X7e, 0xff, 0, 0, 0, 0, 0, 0, 0, 0xef};

// Write data to sound board. Send "cmd" with the option "opt".
void sendCommand(uint8_t cmd, uint16_t opt=0) {
  uint8_t i;
  uint16_t csum;

  msgBuf[0]= 0x7e;        // Start
  msgBuf[1]= 0xff;        // Version
  msgBuf[2]= 6;           // Length = always 6
  msgBuf[3]= cmd;         // Command
  msgBuf[4]= 0;           // Feedback with 0x41, 0 if no and 1 if yes
  msgBuf[5]= opt >> 8;    // Optional value high byte
  msgBuf[6]= opt & 0xff;  // Optional value low byte
  csum = 0 - (msgBuf[1] + msgBuf[2] + msgBuf[3] + msgBuf[4] + msgBuf[5] + msgBuf[6]);
  msgBuf[7]= csum >> 8;
  msgBuf[8]= csum & 0xff;
  msgBuf[9]= 0xef;        // End

  for (i = 0; i < 10; i++) {
    altSerial.write(msgBuf[i]);
  }
}

// Write data to soundboard. To abort any currently playing sound, set
// wait=false.
void playSound(uint16_t fileno, boolean wait=true) {
  int16_t cnt;
  uint8_t retry;

  if (wait) {  // Should we make sure it's done playing.
              // It's just small snippets so it shouldn't take too long.
    cnt = 0;
    while (digitalRead(BUSY) == LOW && cnt < 300) {
      cnt++;
      delay(10);
    }
  }

  retry = 3;
  do {
    // Send play command.
    sendCommand(dfcmd_PLAYNAME, fileno);
    // Wait for it to start playing.
    cnt = 0;
    while (digitalRead(BUSY) == HIGH && cnt < 200) {
      cnt++;
      delay(1);
    }
    retry--;
  } while (digitalRead(BUSY) == HIGH && retry > 0); // If not started, send again.
}

void setup() {
    altSerial.begin(9600);
    sendCommand(dfcmd_RESET, 0);   // Reset unit.
    delay(500);    
    sendCommand(dfcmd_VOLUME, 30); // Set volume.
}

void loop() {
  for (int i = 1; i <= 26; i++) {
    playSound(1500 + i);
  }
  delay(10000);
}

References


  1. http://www.flyrontech.com/eproducts/84.html
  2. https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
  3. https://github.com/jefftranter/meinEnigma/tree/master/Examples/SoundFiles


Thursday, May 4, 2017

Programming the MeinEnigma - Rotors



One of the unique features of the MeinEnigma is the rotatable rotors that physically resemble those of the original Enigma machine and add realism.

Rotation of the four rotors is sensed using four rotary encoders which output quadrature pulses when turned. The levels are sensed by digital inputs on the Arduino. The Arduino can determine when and how far a rotor is turned, and the direction, but not the absolute position. It can be done using interrupts so the Arduino does not have to constantly poll the encoders.

Each encoder uses two signals, so the four encoders uses a total of eight of the Arduino's I/O pins. These encoders also have a pushbutton built in to the shaft, but this feature is not used.

The encoders are low in cost, reliable, and commonly used in Arduino-based designs. They often come on a small PCB with a 4 pin header. The MeinEnigma uses a custom PCB in order to better fit close to the mainboard.

For my programming example I use a standard library that I have found works well. It supports using interrupts but does not require using them.

The MeinEnigma application uses some custom code to read the rotors and avoid the memory overhead of a larger library which has features that aren't used.

The MeinEnigma rotors are connected to Arduino digital pins 2/3, 4/5, 6/7, 10/11. Note that the polarity of the first rotor is reversed from the others.

The sample program, shown below, creates an Encoder object for each rotor. It reads each rotor position, and if it has changed, reports the new position over the serial interface. Here is some sample output:

Rotor Demonstration
Position1 = 0
Position2 = 0
Position3 = 0
Position4 = 0
Position1 = -1
Position1 = -2
Position2 = -1
Position4 = 1
Position4 = 2
Position4 = 3
Position4 = 4
Position4 = 5
Position4 = 6
Position3 = 1
Position2 = 3
Position1 = 2
Position2 = 4
Position4 = 10

By uncommenting either the line that defines ENCODER_USE_INTERRUPTS or ENCODER_DO_NOT_USE_INTERRUPTS you can configure whether to use interrupts. Either option works on the MeinEnigma.

The program could be made shorter by using some arrays and loops rather than repeating the same code four times for each rotor, but I wanted to make the example easy to understand.

/*
  MeinEnigma Example

  Demonstrates reading the rotors.

  This uses the encoder library and was adapted from the basic example
  at http://www.pjrc.com/teensy/td_libs_Encoder.html

  Jeff Tranter

*/

#define ENCODER_USE_INTERRUPTS
//#define ENCODER_DO_NOT_USE_INTERRUPTS

#include

// Change these two numbers to the pins connected to your encoder.
//   Best Performance: both pins have interrupt capability
//   Good Performance: only the first pin has interrupt capability
//   Low Performance:  neither pin has interrupt capability
// Avoid using pins with LEDs attached

Encoder rotor1(3, 2);
Encoder rotor2(4, 5);
Encoder rotor3(6, 7);
Encoder rotor4(10, 11);

void setup() {
  Serial.begin(9600);
  Serial.println("Rotor Demonstration");
}

long oldPosition1  = -999;
long oldPosition2  = -999;
long oldPosition3  = -999;
long oldPosition4  = -999;

void loop() {
  long newPosition1 = rotor1.read();
  long newPosition2 = rotor2.read();
  long newPosition3 = rotor3.read();
  long newPosition4 = rotor4.read();
      
  if (newPosition1 != oldPosition1) {
    oldPosition1 = newPosition1;
    Serial.print("Position1 = ");
    Serial.println(newPosition1);
  }
  if (newPosition2 != oldPosition2) {
    oldPosition2 = newPosition2;
    Serial.print("Position2 = ");
    Serial.println(newPosition2);
  }
  if (newPosition3 != oldPosition3) {
    oldPosition3 = newPosition3;
    Serial.print("Position3 = ");
    Serial.println(newPosition3);
  }
  if (newPosition4 != oldPosition4) {
    oldPosition4 = newPosition4;
    Serial.print("Position4 = ");
    Serial.println(newPosition4);
  }
  delay(20);
}

References


  1. http://www.pjrc.com/teensy/td_libs_Encoder.html


Wednesday, May 3, 2017

Programming the MeinEnigma - Real-Time Clock


For our next programming example we'll look at the real-time clock that is present on the MeinEnigma.

A DS3231 real-time clock module allows the MeinEnigma to implement the clock feature that displays the current time. The module is controlled by the Arduino using an I2C interface and maintains the time even when the unit is powered off using a CR2032 coin battery mounted on the module.

You can refer to the datasheet for the device to understand how to program it. It uses an I2C interface, one of several I2C devices present on the MeinEnigma. One of the advantages of I2C is that it allows controlling multiple devices using only two wires (and therefore two Arduino pins). It is well supported by the Arduino Wire library.

To handle reading from and writing to the Real-time clock, I've implemented functions (actually, I've taken the code almost as is from the MeinEnigma source) called i2c_read() and i2c_write(). Writing for example, involves sending the chip's I2C address and then one or more
bytes of data. Reading from a register in the RTC involves sending the address, followed by sending the address of the register to be read, and then reading the returned data from the register. The Wire libraries take care of the details of the I2C protocol. You can read about I2C and Arduino programming in any one of a number of documents and tutorials.

The DS3231 device registers store the date and time, including year, month, day, day of week, hour, minute, and second. One wrinkle is that it stores these values in binary-coded decimal (BCD) format. Compared to binary, BCD makes it easy to extract the individual decimal digits for operations like displaying them on an LED, without having to perform division or multiplication operations. Those operations can be slow and/or complex to perform a low-end microcontroller like a PIC, although an Arduino has no trouble. In some cases, such as a larger program we will look at later, you may choose to convert the values to binary format before working with them.

The example program continuously reads the current date and time from the RTC chip and displays it in readable format over the Arduino's serial interface. For fun, I made it print the month and day of the week as words rather than numbers.

Here is some typical output:

Real-time Clock Demo
18:37:55 02-Mar-2017 Tue
18:37:56 02-Mar-2017 Tue
18:37:57 02-Mar-2017 Tue
18:37:58 02-Mar-2017 Tue

As the code is well commented, I'll just present the source code here for you to review. The latest version can be found on github here.

/*
  MeinEnigma Example

  Demonstrates reading DS3231 real-time clock over I2C.
  See results through Arduino IDE serial monitor set to 9600 bps.

  Jeff Tranter <tranter@pobox.com.

*/

#include

// I2C address of DS3231 real-time clock.
#define DS3231_ADDR 0x68

// Lookup tables for names of the days of the week and months of the year.
const char *dayName[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
const char *monthName[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

// Read a byte from a specific I2C address. Send one byte (address to read) and read a byte.
uint8_t i2c_read(uint8_t unitaddr, uint8_t addr) {
  i2c_write(unitaddr, addr);
  Wire.requestFrom(unitaddr, (uint8_t)1);
  return Wire.read();    // Read one byte
}

// Write a single byte to I2C.
uint8_t i2c_write(uint8_t unitaddr, uint8_t val) {
  Wire.beginTransmission(unitaddr);
  Wire.write(val);
  return Wire.endTransmission();
}

// Print a BCD number to the serial port with leading zero added if it
// is less than 0x10, e.g. prints "01" for 1, "11" for 11.
void printLeadingZero(int val)
{
  if (val < 0x10) {
    Serial.print("0");
  }
  Serial.print(val, HEX);
}

// Get time and date from RTC and print it out the serial port.
// Note that these could be wrong, especially day of the week, if you
// have not set the real-time clock.
void printTime() {
  uint8_t hour, minute, second;
  uint8_t year, month, day, dow;

  year   = i2c_read(DS3231_ADDR, 6);
  month  = i2c_read(DS3231_ADDR, 5);
  day    = i2c_read(DS3231_ADDR, 4);
  dow    = i2c_read(DS3231_ADDR, 3);
  hour   = i2c_read(DS3231_ADDR, 2 );
  minute = i2c_read(DS3231_ADDR, 1);
  second = i2c_read(DS3231_ADDR, 0);

  printLeadingZero(hour);
  Serial.print(":");
  printLeadingZero(minute);
  Serial.print(":");
  printLeadingZero(second);
  Serial.print(" ");
  printLeadingZero(day);
  Serial.print("-");
  Serial.print(monthName[month - 1]);
  Serial.print("-");
  Serial.print("20"); // RTC does not store century.
  printLeadingZero(year);
  Serial.print(" ");
  Serial.println(dayName[dow - 1]);
}

void setup() {
  Serial.begin(9600); // Initialize serial port.
  Wire.begin();       // Initialize the Wire library.
  Serial.println("Real-time Clock Demo");
}

void loop() {
  printTime(); // Display the current time.
  delay(1000); // Wait one second.
}

References


  1. https://www.arduino.cc/en/reference/wire
  2. https://www.maximintegrated.com/en/products/digital/real-time-clocks/DS3231.html
  3. https://github.com/jefftranter/meinEnigma/tree/master/Examples/RTC

Tuesday, May 2, 2017

Programming the MeinEnigma - Decimal Points


In the next programming example we'll look at another relatively simple piece of hardware - the decimal point indicators on the displays.

The MeinEnigma display uses four 16-segment alphanumeric LEDs which can display letters and numbers as well as a decimal point. The 16 alphanumeric segments of the displays are driven by an HT16K33 LED Controller Driver chip. We'll look at this in some future example programs. The chip doesn't directly support driving the decimal points however, so some additional circuitry on the board (7402 and 4002 logic gates) are used to handle controlling the decimal points.

The HT16K33 chip is still used to control the timing of this circuitry, so it needs to be properly initialized. We'll do that using an external library that has been written for the HT16K33. We'll cover this library in more detail in future blog posts.

Once the HT16K33 chip is set up, the four decimal point indicators can be controlled by the Arduino's A0 through A3 outputs. While these happen to be analog outputs, they are only used here as digital outputs. You might think that we could control the brightness of the decimal points using the analog outputs, but they are not driven directly by the Arduino, they go through the previously mentioned digital gates, so they can only be driven high or low.

The example program is shown below (you can access the latest version from the git repository here).

In the setup() function we create an instance of ana HT16K33 object from the external library for the chip. We set the four i/o pins to be outputs. Then we call the HT16K33 library's begin() method to initialize the chip. We don't need to do anything else with the chip in this program.

In the main program we can control the decimal point segments. A0 controls the leftmost digit decimal point. A3 controls the rightmost. Setting an output to 1 (high) turns the associated decimal point off. Setting an output to 0 (low) turns the decimal point on. In a larger program (as we'll see later) we would probably want to hide the details of controlling the decimal points in a convenient function that we could call.

In this example I provided two demonstrations. The first makes the decimal points count from 0 to 15 in binary. The other "walks" the decimal points from left to right.


You can enable either or both of the sections of code by adjusting the "#if 1" and "#if 0" directives.

/*
  MeinEnigma Example

  Demonstrates controlling the decimal points on the 4-digit
  alphanumeric LED display. These are controlled differently from the
  other display segments.

  Jeff Tranter

*/

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

void setup() {
  HT16K33 HT;

  // Set the pins used for output. These happen to be analog outputs
  // but are only used at digital levels.
  pinMode(A0, OUTPUT);
  pinMode(A1, OUTPUT);
  pinMode(A2, OUTPUT);
  pinMode(A3, OUTPUT);

  // Need to initialize the chip in order for the decimal points to
  // work. This also clears all display segments.
  HT.begin(0x00);
}

// A0 controls the leftmost digit decimal point. A3 controls the
// rightmost. Setting an output to 1 (high) turns the associated
// decimal point off. Setting an output to 0 (low) turns the decimal
// point on.
void loop() {

#if 1
  // Make the decimal points count from 0 to 15 in binary.
  for (int i = 0; i < 16; i++) {
    digitalWrite(A0, !(i & 8));
    digitalWrite(A1, !(i & 4));
    digitalWrite(A2, !(i & 2));
    digitalWrite(A3, !(i & 1));
    delay(200);
  }
#endif

#if 0
  // Alternative pattern. Walk decimal points from left to right.
  for (int i = 0; i <= 4; i++) {
    digitalWrite(A0, !(i == 1));
    digitalWrite(A1, !(i == 2));
    digitalWrite(A2, !(i == 3));
    digitalWrite(A3, !(i == 4));
    delay(200);
  }
#endif
}

Monday, May 1, 2017

Programming the MeinEnigma - Getting Started


The MeinEnigma is a great platform for learning Arduino programming. The wide variety of on-board hardware provides the opportunity to learn how to program different peripheral devices, some simple and some much more complex. Over the next few blog posts I'll present some example Arduino programs that will run on the MeinEnigma.

The programs are standalone and replace the the standard MeinEnigma software that emulates an Enigma machine. You can easily compile and upload the examples using the Ardino IDE (1) running on your desktop computer, attached to the MeinEnigma using USB.

The code was written to be straightforward and readable but not necessarily the most efficient. For example, in most cases I use variables of type int, whereas often a smaller value type could be used that would save a few bytes of memory. This can be important for larger programs, but can make the code harder to understand.

Buzzer Example

The first example is a freebie - playing the buzzer built in to the MeinEnigma. It happens to be on the same i/o port that most Arduinos use for their built-in LED, so it is identical to the example program most beginning Arduino developers start with to flash the LED. It makes a good first programming task to confirm that you have a working Arduino development environment.

The code is shown below in it's entirety. In the setup() method, which is run once on powerup, we configure the digital i/o pin connected to the buzzer to be an output.

In the loop() method, which is run repeatedly after setup() is called, we drive the output high for half a second then drive it low for the same length of time, causing the buzzer to turn on and off at a one second rate.

Note that for this type of piezoelectic buzzer hardware, a high level will produce a tone at a fixed frequency determined by the buzzer itself. We can't change the frequency or volume level (at least, not without some more complicated programming). As I mentioned in an earlier blog post, you may find the buzzer a litte loud and covering it with some electrical tape is a simple way to reduce the volume.

#define BUZZER 13

void setup() {
  pinMode(BUZZER, OUTPUT);    // Initialize digital pin 13 as an output.
}

void loop() {
  digitalWrite(BUZZER, HIGH); // Turn the buzzer on.
  delay(500);                 // Wait for half a second.
  digitalWrite(BUZZER, LOW);  // Turn the buzzer off.
  delay(500);                 // Wait for half a second.
}

Conclusions

As this blog series progresses I will introduce coding examples that are increasing more complex (and, hopefully, interesting). At the end we will tie it all together with a larger program that makes use of most of the code examples and hardware on the MeinEnigma.

I hope you will follow along and try the examples, and maybe make some changes of your own to the code.

All source code can be found at link 2 under References below.

References


  1. https://www.arduino.cc/en/Main/Software
  2. https://github.com/jefftranter/meinEnigma/tree/master/Examples