Comment on page
Arduino Code Structure
Three main libraries are used throughout the Arduino files to simplify the code structure and usability.
The Arduino code structure can be thought of in 3 sections: pre-setup, setup, and loop. The fluidics script is slightly different and will be explained separately, but it has the same basic structure.
This is everything before the
setupfunction. This is where we import header files and setup any global variables/constants.
Variables indicating the pins for controlling the 4-bit multiplexer for selecting which Smart Sleeve to communicate with,
SIG_PIN. S0-3 are for selecting the channel, and
SIG_PINis output of the mux. We use the ADC on the SAMD21 on pin 0.
inputStringholds incoming serial data from the RPi.
stringCompleteboolean stating if the string is "complete", meaning that a "!" character has been detected in the incoming serial data.
end_markis what the SAMD21 uses to denote the end of it's messages going out to the RPi - it is not what the SAMD21 is looking for as the end of an incoming message.
Serial communication variables notating what strings are used as commas, and the
addressvariable which is the identifier of the SAMD21 - ie, the SAMD21 will only act on data addressed to what is in this variable.
We initialize an
inwhich handles all serial input.
Three very important variables if PID is being used are
Input- these three
doublearrays hold what value the PID should be trying to hit, what value is sent out to the actuation element (like a resistive heater), and what the current value being read from the ADC of that element is, respectively. These will be talked about more later.
After these variables are initialized, 16 PID objects are initialized (one for each Smart Sleeve) and put into an array.
The setup code runs one time when the system is turned on.
In this block we generally initialize the various pins of the SAMD21 for INPUT or OUTPUT and set pins to be digital LOW or HIGH. We also initialize the PWM if applicable and turn on the
Serialcommunications modules on the microcontroller. If PID is being used, we also set those for the proper range and modes.
This is the section of the script that is run continuously (on loop) over and over. The general logic is to read from the serial line, see if there are any messages to act on, read data from the Smart Sleeves if applicable, then update any elements that are being controlled for each Smart Sleeve.
If a message on serial is found, the script will save those values, send a message back to the RPi, and continue looping until it receives an ACK from the RPi addressed to it to act on the previously sent values. See the server code structure for more information on serial messages between the RPi and SAMD21s.
TODO: normalize the naming of functions.
This function reads from
Serial1and constructs a string in
inputStringcontaining the data. When it detects the end of the message ("!") it will return. This string is then checked by the
into determine if the message is formatted appropriately and if the SAMD21 should act on it (ie addressed to it.)
This function sends out data via serial to the RPi.
It sets pin 12 to a digital HIGH first, which tells the RS-485 that it will be communicating out, then formats the output string containing all the data from the Smart Sleeves.
Then it will delay for 100 ms in order to allow enough time for pin 12 to actually flip and for the RS-485 chip to react before outputting serial data over
Serial1(the default Tx pin). Lastly it will flip pin 12 back to LOW, indicating it is done using the Serial bus.
This function updates the
Setpointvariable to the values from commands sent from the Rpi.
This function reads data from the Smart Sleeves through the multiplexer. It also handles any averaging for that data and updates the PID based on the readings.
It first creates an array for holding data from the vials, then calls a helper function
readMuxpassing the vial it wishes to get data from. Then it calculates the average for each vial, and sets the
Inputfor each accordingly. It will then call the
Computefunction of the PIDs with the updated data and make a call to the
TlcPWM chip to set the new value as decided by the PID controller.
Gets data from the multiplexer for a given channel and returns the value that is read off of the ADC.
First, it loops through each control pin (S0-3) and sets them appropriately to select the desired channel. Then, it reads off of the
SIG_PINand returns that value.
The fluidics script follows the same basic structure as above, but has some special code for handling peristaltic pumps and IPP millifluidics.
In the pre-setup, there is an
speedsetwhich contains two values for PWM OFF and ON, which are 4095 and 0 respectively by default.
The pump class is an object for keeping track of a single peristaltic pump. They get initialized with the address they are controlling, and have local variables keeping track of whether or not it is current running, how long it needs to pump, and chemostat periodicity information.
This function checks whether the pump needs to be turned on or off by checking the timing based on milliseconds since it began pumping. If it needs to turn off it sets the Tlc for it's given address and updates local variables.
If it is off and is in chemostat mode (
pumpInterval> 0), it checks wheter it is time to turn on and sets the Tlc appropriately.
Turns on a pump based on a passed command.
pumpIntervalvariables, then sets the Tlc accordingly.
IPPs pump by sequentially activating pneumatic valves, where the flow rate of the pump is correlated to the frequency at which the valves are actuated. Each IPP object keeps track of the 3 solenoids it needs to actuate to pump, as well as whether or not the pump is currently running.
Example of commands to use these:
pumpi,<Hz>|<Pump number>|<IPP index>,...
To run 2 IPPs on address (0, 1, 2) and (3, 4, 5) after the normal 32 influx/efflux pumps:
Then acknowledge like normal.
To turn off IPPs, a command with 0 as Hz to the desired pump number. It can be in any address slot. You do not need to have an entry for each individual solenoid channel - just specificy the high level IPP pump index (the second value in the set of three between the pipes).
The initialization simply sets addresses of the solenoids.
This function is overloaded (ie two function stubs). If a
longholdTime is passed, it will change the frequency of the IPP without having to turn it off completely and reset it, useful for very slow pumps. If the pump is off, it will also turn it on. Otherwise, it will check the timing and state of the solenoids and update them accordingly.
For an IPP, sets up a given address to be solenoid 1, 2, or 3 of an IPP. The valves do not necessarily need to be sequential addresses, although it is normal to set things up this way (see example above).
In the setup, the pump objects are initialized. There are two potential layouts on the PCB, one where each address is just sequential, and another where the pump PCB has the pumps layed out a little differently and as such the address for Vial2 is not address 2. The initialization code should be commented out or modified for the given physical setup of the pump arrays.
In the loop, the way the serial commands are parsed is a little bit different from normal as chemostats and IPP commands use the pipe "|" character to denote more information for a given address.
As an example, in the command:
Addresses 0 and 16 are set as chemostats. The time to pump is the first number, and the pumping interval is the second. So address 0 will run for 2 seconds every 4 seconds, and address 16 will run for 3 seconds every 4 seconds.
Addresses 1 and 17 are set to run for 10 seconds and 15 seconds one time, respectively.
Tlc.update()function is only called a single time at the end of the loop! Do not call this function multiple times, it will cause things to not work.