Showing posts with label MeinEnigma. Show all posts
Showing posts with label MeinEnigma. Show all posts

Wednesday, May 10, 2017

Programming the MeinEnigma - Alarm Clock Example



In this installment we'll tie together much of what we looked at in this blog series with a larger application.

I decided to implement a simple digital clock program with alarm. These were the basic requirements and features:

  1. Time display, 12 hour mode: 100 through 1159 with rightmost decimal point indicating pm. 24 hour mode: 0000 through 2359. The second decimal point will toggle at a one second on/one second off rate whenever the time is displayed. The time is also displayed in binary using the discrete LEDs. The hours are shown on the LEDs in the first row, minutes on the second row, and seconds on the third row.
  2. Time set function: pressing four keys below display will perform as follows (left to right): go back one hour, advance one hour, go back one minute, advance one minute.
  3. Chime: When enabled, will beep on the hour using the buzzer.
  4. Alarm: When enabled, buzzer will sound at one second on/one second off rate until any key is pressed. Leftmost decimal point indicates alarm on.

Keyboard keys:

D - Display date briefly, e.g "JA 1" "2017" or "DE24" "2018". Shows month and day, then year, then goes back to time mode.

C - Toggle chime. Briefly Display "CH Y" or "CH N", then goes back to time mode.

M - Toggle between 12 and 24 hour mode. Briefly display "12HR" or "24HR", then go back to time mode.

A - Toggle alarm on/off. Briefly display "AL N" or AL Y", then go back to time mode. Leftmost decimal point goes on when alarm is enabled.

S - Set alarm. Pressing keys under display will set alarm time, similar to time set. Pressing S again will exit alarm set mode. Leave alarm set mode if no key pressed for more than 5 seconds.

T - Set date. Pressing keys under display will set month and day, similar to time set. Pressing T again will exit date set mode. Leave date mode if no key pressed for more than 5 seconds.

I won't go over the source code line by line. The software can be found here.

It consists of one main loop which performs various functions:
  1. Get the current time from the RTC.
  2. Display current time, alarm time, or date depending on mode in effect.
  3. Display time in binary on the discrete LEDs.
  4. Check if it is time to play the chime (hour rolled over).
  5. Check if it is time to play the alarm. Toggle alarm beep if it is active.
  6. If no keys pressed for 5 seconds, exit alarm or date set modes and revert to time mode.
  7. Check if key pressed.
  8. If alarm is active, pressing any key will turn it off.
  9. Update how long since a key was pressed.
  10. Handle time/alarm/date set keys 1-4.
  11. Handle date key.
  12. Handle toggle chime key.
  13. Handle 12/24 hour mode key.
  14. Toggle alarm on/off key.
  15. Handle alarm mode key.
  16. Handle set date keys.
  17. Delay 1 second, unless a key was pressed.
  18. Toggle seconds decimal point.
The program was written for clarity and not intended as a polished application. Several things could could be made more efficient and there are some obvious enhancements, some of which are listed under "to do" at the top of the file.

It doesn't use the the sound module or the rotors. These could obviously used for sounds and for setting the time or date.

Incidentally, there are Arduino-based clocks on the market. The "new" Heathkit, for example, offers one as a kit which looks like a traditional digital clock and is Arduino based.

Tuesday, May 9, 2017

Programming the MeinEnigma - Plugboard



The plugboard circuitry uses two MCP23017 16-bit i/o expander chips. This chip features two 16-bit bidirectional i/o ports controlled over an I2C interface.

The chip offers a number of nice features including the ability to individually set each pin as an input or output, optional pullup resistors, polarity inversion, and interrupts when a pin changes level or changes from a default value. It is programmed via 22 registers.

With 16 i/o pins, two MCP23017 chips are needed to support the 26 signals on the plugboard. The signals are connected as listed below:

Chip:   U301
Port:   GPA0 GPA1 GPA2 GPA3 GPA4 GPA5 GPA6 GPA7 GPB0 GPB1 GPB2 GPB3 GPB4 GPB5 GPB6 GPB7
Plug #:  1    2    3    4    5    6    7    8    9    10   11   12   13   14   15   16
Letter:  Q    W    E    R    T    Z    U    I    O    A    S    D    F    G    H    J

Chip:   U302
Port:   GPA0 GPA1 GPA2 GPA3 GPA4 GPA5 GPA6 GPA7 GPB0 GPB1 GPB2 GPB3 GPB4 GPB5 GPB6 GPB7
Plug #:  17   18   19   20   21   22   23   24   25   26   NC   NC   NC   NC   NC   NC
Letter:  K    P    Y    X    C    V    B    N    M    L

The plugboard is really a general purpose i/o interface. You can read the level (high or low) of any of the plug signals, or drive them high or low.

When used as a physical plugboard for the Enigma machine, the way it works is that each plugboard port is configured as a output and driven low, one port at a time. The other ports are configured as inputs with a pullup resistor enabled. If a cable is connected to the tested port and one or more other ports, the corresponding ports will be pulled low. Reading the input ports will detect which ports are low.

Every port is tested in turn as an output and the other ports read to determine what it is connected to. Normally a cable connects one port to at most one other port, but this can handle the general case of connections between multiple ports.

Unlike the LED/keyboard chip. the MeinEnigma software doesn't make use of any software library for the MCP23017 chip, it reads and writes the ports directly. My example program makes use of the some of code from the MeinEnigma software that controls the chips.

The program first show the values of all of the ports of each of the two chips. These will normally all be high unless you jumper a port to ground while the example program is running (the unused holes in the plugboard below each ports are connected to ground, by the way).

Next it shows the values of each plug by name using some lookup tables.

The final step is more representative of the MeinEnigma. We determine what jumpers are present by driving one port at a time low and seeing what other ports go low. It checks for multiple connections.

Here is some sample output. At the beginning I had connected plug 1 to ground. Then I made some plugboard jumper connections.

Plugboard Demonstration

U301 GPIOA = 11111110 GPIOB = 11111111 U302 GPIOA = 11111111 GPIOB = 11111111
U301 GPIOA = 11111110 GPIOB = 11111111 U302 GPIOA = 11111111 GPIOB = 11111111
U301 GPIOA = 11111110 GPIOB = 11111111 U302 GPIOA = 11111111 GPIOB = 11111111
U301 GPIOA = 11111110 GPIOB = 11111111 U302 GPIOA = 11111111 GPIOB = 11111111
U301 GPIOA = 11111110 GPIOB = 11111111 U302 GPIOA = 11111111 GPIOB = 11111111
U301 GPIOA = 11111110 GPIOB = 11111111 U302 GPIOA = 11111111 GPIOB = 11111111
U301 GPIOA = 11111110 GPIOB = 11111111 U302 GPIOA = 11111111 GPIOB = 11111111
U301 GPIOA = 11111110 GPIOB = 11111111 U302 GPIOA = 11111111 GPIOB = 11111111
U301 GPIOA = 11111110 GPIOB = 11111111 U302 GPIOA = 11111111 GPIOB = 11111111
U301 GPIOA = 11111110 GPIOB = 11111111 U302 GPIOA = 11111111 GPIOB = 11111111

Plug 1 "Q" is low
Plug 2 "W" is high
Plug 3 "E" is high
Plug 4 "R" is high
Plug 5 "T" is high
Plug 6 "Z" is high
Plug 7 "U" is high
Plug 8 "I" is high
Plug 9 "O" is high
Plug 10 "A" is high
Plug 11 "S" is high
Plug 12 "D" is high
Plug 13 "F" is high
Plug 14 "G" is high
Plug 15 "H" is high
Plug 16 "J" is high
Plug 17 "K" is high
Plug 18 "P" is high
Plug 19 "Y" is high
Plug 20 "X" is high
Plug 21 "C" is high
Plug 22 "V" is high
Plug 23 "B" is high
Plug 24 "N" is high
Plug 25 "M" is high
Plug 26 "L" is high

Plug 1 "Q" is connected to L
Plug 2 "W" is connected to K
Plug 3 "E" is connected to Z
Plug 4 "R" is connected to I
Plug 5 "T" is connected to U
Plug 6 "Z" is connected to E
Plug 7 "U" is connected to T
Plug 8 "I" is connected to R
Plug 9 "O" is connected to P
Plug 10 "A" is connected to F
Plug 11 "S" is connected to D
Plug 12 "D" is connected to S
Plug 13 "F" is connected to A
Plug 14 "G" is connected to J
Plug 15 "H" is connected to
Plug 16 "J" is connected to G
Plug 17 "K" is connected to W
Plug 18 "P" is connected to O
Plug 19 "Y" is connected to M
Plug 20 "X" is connected to
Plug 21 "C" is connected to
Plug 22 "V" is connected to
Plug 23 "B" is connected to
Plug 24 "N" is connected to
Plug 25 "M" is connected to Y
Plug 26 "L" is connected to Q

Plugboard connections: QL WK EZ RI TU OP AF SD GJ YM

Based on this code you can imagine other uses for the plugboard like driving outputs or reading input devices.

The full listing is below. This completes our examples of programming the hardware on the MeinEnigma. In the next installment we will tie things together with a larger example application that does something more useful and uses most of the hardware we have covered.

/*
  MeinEnigma Example

  Demonstrates controlling the plugboard.
  Uses code from the MeinEnigma software.

  Jeff Tranter

*/

#include

/*
  The plugboard uses two MCP23017 16-bit i/o expander chips. The ports
  are assigned as follows:

  Chip:   U301
  Port:   GPA0 GPA1 GPA2 GPA3 GPA4 GPA5 GPA6 GPA7 GPB0 GPB1 GPB2 GPB3 GPB4 GPB5 GPB6 GPB7
  Plug #:  1    2    3    4    5    6    7    8    9    10   11   12   13   14   15   16
  Letter:  Q    W    E    R    T    Z    U    I    O    A    S    D    F    G    H    J

  Chip:   U302
  Port:   GPA0 GPA1 GPA2 GPA3 GPA4 GPA5 GPA6 GPA7 GPB0 GPB1 GPB2 GPB3 GPB4 GPB5 GPB6 GPB7
  Plug #:  17   18   19   20   21   22   23   24   25   26   NC   NC   NC   NC   NC   NC
  Letter:  K    P    Y    X    C    V    B    N    M    L

*/

//  MCP23017 registers, all as seen from bank 0.
//
#define mcp_address 0x20 // I2C Address of MCP23017 (U301)
#define IODIRA      0x00 // I/O Direction Register Address of Port A
#define IODIRB      0x01 // I/O Direction Register Address of Port B
#define IPOLA       0x02 // Input polarity port register 
#define IPOLB       0x03 // "
#define GPINTENA    0x04 // Interrupt on change
#define GPINTENB    0x05 // "
#define DEFVALA     0x06 // Default value register
#define DEFVALB     0x07 // "
#define INTCONA     0x08 // Interrupt on change control register
#define INTCONB     0x09 // "
//#define IOCON     0x0A // Control register
#define IOCON       0x0B // "
#define GPPUA       0x0C // GPIO Pull-up resistor register
#define GPPUB       0x0D // "
#define INTFA       0x0E // Interrupt flag register
#define INTFB       0x0F // "
#define INTCAPA     0x10 // Interrupt captured value for port register
#define INTCAPB     0x11 // "
#define GPIOA       0x12 // General purpose i/o register
#define GPIOB       0x13 // "
#define OLATA       0x14 // Output latch register
#define OLATB       0x15 // "

// Lookup table of i/o port addresses for each plug number.
char address[26] = { mcp_address, mcp_address,  mcp_address,  mcp_address,  mcp_address,  mcp_address,  mcp_address,  mcp_address,  mcp_address,  mcp_address,  mcp_address,  mcp_address,  mcp_address,  mcp_address,  mcp_address,  mcp_address, mcp_address + 1, mcp_address + 1, mcp_address + 1, mcp_address + 1, mcp_address + 1, mcp_address + 1, mcp_address + 1, mcp_address + 1, mcp_address + 1, mcp_address + 1 };

// Lookup table of register addresses for each plug number.
char reg[26] = { GPIOA, GPIOA,  GPIOA,  GPIOA,  GPIOA,  GPIOA,  GPIOA,  GPIOA, GPIOB, GPIOB, GPIOB, GPIOB, GPIOB, GPIOB, GPIOB, GPIOB, GPIOA, GPIOA, GPIOA, GPIOA, GPIOA, GPIOA, GPIOA, GPIOA, GPIOB, GPIOB };

// Lookup table of i/o port bits for each plug number.
char bit[26] = { 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 0, 1 };

// Lookup table of letters for each plug #.
char plug[27] = "QWERTZUIOASDFGHJKPYXCVBNML";

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

// Write two bytes to i2c.
uint8_t i2c_write2(uint8_t unitaddr, uint8_t val1, uint8_t val2) {
  Wire.beginTransmission(unitaddr);
  Wire.write(val1);
  Wire.write(val2);
  return Wire.endTransmission();
}

// Read two bytes starting at a specific address.
// Read is done in two sessions - as required by MCP23017 (page 5 in datasheet).
uint16_t i2c_read2(uint8_t unitaddr, uint8_t addr) {
  uint16_t val = 0;
  i2c_write(unitaddr, addr);
  Wire.requestFrom(unitaddr, (uint8_t)1);
  val = Wire.read();
  Wire.requestFrom(unitaddr, (uint8_t)1);
  return Wire.read() << 8 | val;
}

// Read a byte from specific 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.
}

void setup() {

  Serial.begin(9600);
  Serial.println("Plugboard Demonstration");

  Wire.begin(); // Enable the Wire library.

  // Initialize U301
  // Set up the port multiplier.
  // Init value for IOCON, bank(0)+INTmirror(no)+SQEOP(addr
  // inc)+DISSLW(Slew rate disabled)+HAEN(hw addr always
  // enabled)+ODR(INT open)+INTPOL(act-low)+0(N/A)
  i2c_write2(mcp_address, IOCON, 0b00011110);
  i2c_write2(mcp_address, IODIRA, 0xff); // Set all ports to inputs.
  i2c_write2(mcp_address, IODIRB, 0xff); // Set all ports to inputs.
  i2c_write2(mcp_address, GPPUA, 0xff);  // Enable pullup.
  i2c_write2(mcp_address, GPPUB, 0xff);  // "

  // Initialize U302 - same as above
  i2c_write2(mcp_address + 1, IOCON, 0b00011110);
  i2c_write2(mcp_address + 1, IODIRA, 0xff);
  i2c_write2(mcp_address + 1, IODIRB, 0xff);
  i2c_write2(mcp_address + 1, GPPUA, 0xff);
  i2c_write2(mcp_address + 1, GPPUB, 0xff);
}

void loop() {
  int val;

  // Display levels of each hardware port.
  for (int i = 0; i < 10; i++ ) {
    Serial.print("U301");
    Serial.print(" GPIOA = ");
    val = i2c_read(mcp_address, GPIOA);
    Serial.print(val, BIN);
    Serial.print(" GPIOB = ");
    val = i2c_read(mcp_address, GPIOB);
    Serial.print(val, BIN);

    Serial.print(" U302");
    Serial.print(" GPIOA = ");
    val = i2c_read(mcp_address + 1, GPIOA);
    Serial.print(val, BIN);
    Serial.print(" GPIOB = ");
    val = i2c_read(mcp_address + 1, GPIOB);
    Serial.println(val, BIN);
  }
  Serial.println();
  delay(3000);

  // Display level of each plug by name.
  for (int i = 0; i < 26; i++) {
    Serial.print("Plug ");
    Serial.print(i + 1);
    Serial.print(" \"");
    Serial.print(plug[i]);
    Serial.print("\" is ");
    val = (i2c_read(address[i], reg[i]) >> bit[i]) & 1;
    if (val) {
      Serial.println("high");
    } else {
      Serial.println("low");
    }
  }
  Serial.println();
  delay(3000);

  // Figure out what jumpers are connected by driving one port at a time
  // low and seeing what input port(s) go low.
  for (int i = 0; i < 26; i++) {

    // Set port i to be an output.
    i2c_write2(address[i], reg[i] - 0x12, ~(1 << bit[i])); // 0x12 is offset between GPIO and IODIR
    // And set it low.
    i2c_write2(address[i], reg[i], ~(1 << bit[i]));

    Serial.print("Plug ");
    Serial.print(i + 1);
    Serial.print(" \"");
    Serial.print(plug[i]);
    Serial.print("\" is connected to");

    for (int j = 0; j < 26; j++) {
      if (i == j) { // Don't check for jumper connected to itself.
        continue;
      }
      val = (i2c_read(address[j], reg[j]) >> bit[j]) & 1;
      if (val == 0) {
        Serial.print(" ");
        Serial.print(plug[j]);
      }
    }
    Serial.println();

    // Set port i back to being an input.
    i2c_write2(address[i], reg[i] - 0x12, ~(1 << bit[i]));
    // And set it high.
    i2c_write2(address[i], reg[i], 0xff);

  }
  Serial.println();
  delay(3000);
}

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

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