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

No comments: