Arduino Software, Part 2

Welcome to the jungle. The structure and features of the Arduino sketch for the active line array are not easy to follow, but the task of this section is to illustrate the logic and make that software more intelligible. But first, let’s zoom out to understand what this code needs to accomplish.

The Arduino code needs to:

  • Initialize the MCU and all other hardware
  • Set up the Human-Computer Interface (HCI)
  • Respond to inputs from users

Those basic functions are depicted in the following block diagram:

The sketch for the current line array project is at this link. This code is still evolving, as there are still some features that will be added or will be changed after more testing and measurement. Also, there will be two versions of the sketch: one for the left speaker and one for the right. The version for the left speaker receives the SPDIF audio and transmits it to the right, along with command data. It was too convoluted to make a single sketch that could “know” whether it was the left or right speaker by reading a switch, so it was decided to use two versions of the sketch. And, as noted before, you are on your own with this code, as there is no support other than the explanations on this page.

In the discussions below, we have left out the “software initialization” code such as the many #define and #include statements, variable declarations, library instantiations, and so forth. All of that code is important but is a at lower level of detail than what we can address in this article.

Hardware Initialization

At power up, there are a number of hardware components that need to be initialized. There are some ports on the MCU that need to be initialized, and the Bluetooth radio needs to be configured. These initialization routines are in the CPU_init.ino file.

There is another file for initializing and monitoring the power amps: SSM3582.ino. Currently, this code only initializes the power amps, but eventually there will be more routines for monitoring the amps and displaying their status on an OLED display on the motherboard.

Set up HCI

The function “HCI_init()” is called as part of the Arduino setup. This function creates an in-memory model of the possible states of the human-computer interface as detailed in the next sub-section. There are also routines that provide non-volatile storage of the HCI state using the Arduino Preferences library, and these routines are described in the subsequent sub-section.

A bit of history of how this code evolved is a good way to start the description of this code. The displays to control the earlier DSP chips, such as the TAS3004, STA326, and others were made with just navigation switches and very crude 2-line LCD modules. The navigation switches allowed you to cursor through a menu of options. These early designs allowed selecting different pages of options, and the MCU needed to keep track of which page was selected and what data needed to be displayed on each page. For these designs, it was necessary to keep the display “dumb”, which meant that the menu selections were saved in the MCU, and the MCU responded to the menu selections by telling the display what characters to put on the screen. At power-on, the MCU would restore all of the menu selections, and restore the display to the same page and cursor position as before powering down.

For later designs we started using somewhat more intelligent displays such as the Nextion series, which used touchscreens and “hotspots” that the user could select using code running on the display. This made the HCI much nicer looking and easier to navigate, but the design still kept most of the display “intelligence” in the Arduino MCU code. As the HCI design stated to transition to cell phone apps to control the DSP, the screens got much nicer looking and easier to navigate, but the basic design of the “dumb display” remained. This “dumb display” paradigm means the HCI has no ability to evaluate messages, such as translating units or doing display formatting. The display doesn’t need to know anything about the DSP or MCU design–it just needs to allow the user to select from a menu of options that are defined by the MCU. That selection results in sending a character string to the MCU that identifies the menu item and whether the value for that item should be increased, decreased or set to a specific value. The MCU then sends back a string of characters to the display that tells the cell phone what data to display on the screen for that menu item. The character strings that define the menu item are documented on the Serial Commands page.

HCI Init

This type of HCI is implemented using a data structure of menu items (or “fields”, as they are called in the Arduino line array code). This data structure includes the current value for that menu item, the default value, the total number of options for that menu item, the length of the response string and a list of responses for that menu item. For example, the structure for the “Tweeter Delay” menu item looks like this:

  HCI_state[Delay_T].default_value = 0; 
  HCI_state[Delay_T].value = HCI_state[Delay_T].default_value;
  HCI_state[Delay_T].value_table_pointer = &delay_values[0][0];
  HCI_state[Delay_T].number_of_values = delay_values_qty;
  HCI_state[Delay_T].value_length = delay_values_len;

These variables are initialized at power-on by using the values in the HCI.h file that define the menu parameters:

  const byte delay_values_qty = 12;
  const byte delay_values_len = 4;
  const char delay_values[delay_values_qty][delay_values_len + 1] 
     PROGMEM = {{"0.0 "}, {"0.28"}, {"0.56"}, {"0.84"}, ("1.13"},
                {"1.41"}, {"1.69"}, {"2.25"}, {"2.81"}, {"3.66"},
                {"4.78"}, {"5.91"} 

In this example, there are 12 options that the user can select for the tweeter delay. When the user selects one of these delay values, the display device needs to send the character string “X00__”. The X is the delay command, and the “00” selects the tweeter delay (see the list of commands on the Serial Command page). The characters following the X00 tell the MCU whether to increment the value, decrement it, keep it the same, or jump to a specific value. For example, the string “X0003″ would tell the MCU to use the 4th value of delay for the tweeters, which would result in a delay of 0.84”.

Save/Restore HCI State

The value for each HCI menu item is stored in the Arduino non-volatile memory using the Preferences library. The Preferences library is the preferred method for saving state information for the ESP32, and it requires saving key-value pairs. The code that saves and restores the state is in the Save_Load_ESP.ino file.

This code is verbose because each variable needs to be assigned a unique string and a default value for the key, but this code is easy enough to follow that no additional comments are necessary. However, there are two issues worth noting about this code. First, the programmer needs to remember to update this file if new commands are added. And second, the ESP32 does not have dedicated EEPROM for non-volatile storage and instead uses Flash memory. Flash memory has a limited number of write/erase cycles before becoming unreliable, so it is wise to avoid constant updates. That is why the cell phone app described elsewhere only issues a save command when the HCI pages are changed.

Respond to User Inputs

User commands can be received from a number of different sources, as described in the next subsection. Once the commands are received, we need to decode them, update the HCI state, and then execute them.

Receive Commands

The main path for receiving commands in the active line array is via Bluetooth, using a UART/Serial Port Emulation over the BLE protocol. There are many online tutorials on how to get BLE clients such as a cell phone to communicate with an Arduino server, and the Arduino code for this sketch was developed by following those examples. Those examples will walk you through the details that have been left out of this write-up.

The Bluetooth BLE library code supports a callback that we can use to receive data from the BLE-enable device. We merely need to set a flag in the callback to signal that new data has been received, and then we can pass along that received data for decoding. This callback is similar to how data is received using the Android Serial Monitor, and in fact, we can send the same commands from the Monitor or receive them from the Bluetooth device, and the commands will be handled in the same way.

The MCU can also use the IRremote library to receive commands assigned to remote control keys. For example, we can assign a button to Volume Up or Volume Down, and the HCI.ino software will decode the command as though it had come from a Bluetooth device. And the MCU can also send and receive data using User Bits in the SPDIF stream. Therefore, one speaker can be set up as a master for the Bluetooth controller, and it will echo the commands to the slave on the same SPDIF fiber optic line that transmits the audio. That feature was described in this post.

Yet another way to send commands to the MCU is using the Arduino Cloud libraries that are based on Matter or the “Internet of Things” (IoT) protocol. For this input, there are unique callbacks for each variable, so the decoding step described in the next subsection is not required. However, the execution and HCI state update are handled in the same way as all of the other inputs just mentioned.

Decode Commands

The logic for the command decoder is in the SCI_BLE.ino file. This software has been rewritten several times, and yet, it is still difficult to follow and is one of those routines that someday you still needs to be refactored. It takes those commands that are listed on the Serial Command page and maps it to an enumerated menu item called field_ID that is defined in the file “enum_struct.h”. The routine also composes the response for each command that tells the external controller what information to display to the user.

Execute Commands

The commands are executed in the sketch file called “Update HW_from_HCI.ino”. At one time this code was much more complicated, but in its current form it simply calls the routines in the ADAU1466_cmd file. As noted earlier, the ADAU1466_cmd.cpp and ADAU1466_cmd.h files currently appear in the sketch along with the other code described on this page, but they will eventually be put in the library directory.

Update the State

The HCI state, which includes all the current values for each menu item, is updated by the previous_value(), next_value() and current_value() routines that are in the HCI.ino file. These routines use the information in the HCI_state structure to walk through the list of options for each menu item and provide the “rollover” to return to the start of the list after going past the end. The state isn’t automatically written to non-volatile memory for the reasons cited in the “Save/Restore HCI State” subsection. Saving the state to non-volatile memory only occurs when a Save command is received from the external controller.