Skip to content

Latest commit

 

History

History
97 lines (78 loc) · 6.28 KB

handshaking.md

File metadata and controls

97 lines (78 loc) · 6.28 KB

Handshaking and auto-detection

Stacksynth modules are designed to be joined together to make a larger keyboard. Messages are exchanged between modules using a CAN bus. Typically, one module will have a receiver role and synthesise the notes that are played any module. The other modules transmit notes. Each module needs to be set up to generate notes from a different octave.

The role and octave can be defined by compiling different versions of the code for each module. You can also allow them to be set with the user interface. However, a neater solution is to implement a handshaking system so that modules will automatically set up their roles and octaves on startup, depending on their position in the row of modules.

Handshake signals

The inter-module connectors include a pair of east/west handshake signals in addition to the CAN bus.

Each module can generate a handshake signal on its west and east connectors. It can also detect a signal generated by a neighbouring module.

Sending a handshake signal

There are not enough microcontroller pins to control the handshake outputs directly, so they are latched in a D flip-flop (DFF). For example, the west output looks like this:

The state of the DFF is set with OUT_PIN, which is an output from the microcontroller that controls several functions. The value is latched on the rising edge of R5 (R6 for the east output), which is one of the row driver signals for the key scanning matrix. Therefore, whenever the key scanning matrix addresses row 5, the west handshake output is updated. You must set OUT_PIN to the correct value every time row 5 is accessed. Normally, you would do that in the key scanning routine, for example:

for (i=0; i<7; i++) {
  selectRow(i);                     //Set row address
  digitalWrite(OUT_PIN,outBits[i]); //Set value to latch in DFF
  digitalWrite(REN_PIN,1);          //Enable selected row
  delayMicroseconds(3);             //Wait for column inputs to stabilise
  keyArray[i] = readCols();         //Read column inputs
  digitalWrite(REN_PIN,0);          //Disable selected row
}

Here, outBits is an array of bools that contains the values to latch in the DFF for each row. The DFFs have the following functions:

Row DFF function
0 Unimplemented
1 Unimplemented
2 Unimplemented
3 OLED PSU Enable
4 OLED nReset
5 Handshake output west
6 Handshake output east

The OLED controls should be left high after startup.

Receiving a handshake signal

The handshake inputs are connected to a FET that's placed in one node of the key scan matrix. For example:

A high input causes the FET to turn on, which, when the row is selected (driven low), pulls the column input low and is read as 0. The resistor ensures that the FET is off if nothing is connected to the input - this is read as 1.

Warning The FET acts to invert the logic signal. So if one module sets a handshaking output high (on), the adjacent module will read a logic 0, and vice versa

You read the handshake inputs just like a physical switch and the code example above already includes both handshake inputs in keyArray. The positions of the inputs in the matrix are:

HS Input Row Column
West 5 3
East 6 3

Auto detection

For a two or three module keyboard, you can determine the location of each module just by switching on all the handshake outputs and reading the inputs. The modules at each end will detect just one input from adjacent modules, while the centre module will detect two. You may need to broadcast a CAN message from the centre module to announce that it is a three module keyboard, not two.

Four or more modules is more complex because there are multiple centre modules. On startup, you can do something like this:

  1. Get a unique ID for the module. You can use the microcontroller's built-in, 96-bit ID, which is accessed through the HAL functions HAL_GetUIDw0(), HAL_GetUIDw1() and HAL_GetUIDw2(). It consists of a few different fields, some binary, some ASCII. You may want to generate a hash to get a smaller data type.
  2. At startup, set both handshake outputs (west and east) on
  3. Wait long enough for all the other modules to start and switch on their handshake outputs
  4. Read the handshake inputs. Is the west input off (logic 1)? If so:
    • This is the most westerely module (position 0)
    • If the east input is also off, it is the only module, so end handshaking here
    • Broadcast a handshaking message on the CAN bus that starts with a predefined symbol for handshaking and contains this module's ID and position (0)
    • Set the east handshake signal off
  5. If the module is not the most westerly, it must wait for handshaking messages on the CAN bus and for the west handshake input to change:
    • Record the ID and position of each module that sends a message
    • Has the west handshake input has changed to off? If so:
      • This module's position is one greater than the position of the previous message
      • Broadcast a new handshaking message and set the east handshake output off
      • If this module is the most easterly, send another CAN message to indicate handshaking complete

Once handshaking is complete, each module will contain a data structure listing the ID and position of every module. This can be used to set up the role and octave number of each module.

You may wish to consider live plugging and unplugging of modules as well as detecting a static configuration. One way you could do this is to hold all the handshake outputs on during normal operation. If any module detects a neighbour connecting or disconnecting, it can broadcast a CAN message that triggers a new auto-detection sequence.