FETbox Serial Control

Convenience functions for serial control of the PlateFlo FETbox

1. Key Features

  • Simple, high-level hardware control through USB serial communication

  • Hit-and-hold capability for power-efficient solenoid operation

  • Full Arduino output pin control, including PWM functionality

  • Full Arduino digital and analog pin reading functionality

  • Asynchronous serial I/O for non-blocking operation via the serial_io module

  • Logging via the base Python logging module

2. Quick Start

Installation

The plateflo package is hosted on PyPI and can be installed using pip:

pip install plateflo

or

python -m pip install plateflo

More detailed instructions on installing Python modules can be found here.

Setup

Instantiate a FETbox object:

>>> from plateflo import fetbox
>>>
>>> # Connects to a FETbox on serial port 'COM3'
>>> my_fetbox = FETbox(port = "COM3")

This creates a FETbox object and opens a serial connection with the device.

Note

Device must be connected when the FETbox object is instantiated.

You can automatically instantiate all connected FETboxes with auto_connect_fetbox():

>>> # returns a dictionary of FETbox devices, keyed by their IDs
>>> my_fetboxes = auto_connect_fetbox()

>>> # run a motor on MOSFET output channel 1 of device ID 1
>>> my_fetboxes[1].enable_chan(1)

Tip

When exiting from your program, call my_fetbox.kill() to close the serial port and end all FETbox-associated threads running in the background.

Alternatively, discover connected devices using the scan_for_fetbox() function,

>>> # Scan systems serial ports for FETbox(es)
>>> fetboxes = scan_for_fetbox()

>>> # One device found:
>>> print(fetboxes)
>>> [{'port':'COM3', 'id':0}, {'port':'COM4', 'id':1}]
>>>
>>> # Multiple devices found:
>>> print(fetboxes)
>>> [{'port':'COM3', 'id':0}, {'port':'COM4', 'id':1}]

>>> # No devices found:
>>> print(fetboxes)
>>> []

then instantiate using the result:

>>> my_fetbox = FETbox(port = fetboxes[0]['port'])

3. Usage

MOSFET Output Channel Control

There are four built-in methods for control of the FETbox’s five MOSFET output channels:

Enable:

enable_chan(chan)

Disable:

disable_chan(chan)

PWM:

pwm_chan(chan, pwm)

Hit-and-Hold:

hit_hold_chan(chan, duty)

enable_chan() and disable_chan() simply set the specified channel’s (chan, 1-5) output either high (+12 V) or low (0 V).

pwm_chan() sets a PWM output on the specified channel(chan, 1-5). This can be used to effectively set the channel’s output voltage between 0 V (pwm=0) and +12 V (pwm=255).

hit_hold_chan() was implemented with solenoid control in mind. Full +12 V is output on the specified channel (chan) briefly, then reduced to the specified PWM duty cycle (duty=0.0-1.0). This reduces power consumption and heat generated when operating solenoid valves.

Technical Note

The PWM carrier wave frequencies differ between output channels:

Channels

Arduino Pins

PWM Frequency (default)

1, 4, 5

D3, D9, D10, D11

31372.55 (490.20) Hz

2, 3

D5, D6

62500.00 (976.56) Hz

These have been increased from the Arduino defaults, so as to move out of the audible range (you/your labmates are welcome).

Arduino Pin Control

All of the Arduino Nano’s microcontroller pins are broken out on the FETbox PCB, along with 40 unconnected solder pads and power for development. This allows the end user to connect additional inputs/outputs to customize the FETbox their application.

The fetbox module includes basic functionality for serial control of these additional pins.

See the official Arduino website for more details about digital and analog pins:

Setting Output Pins

Digital Write:

digital_write(pin, val)

PWM:

analog_write(pin, pwm)

Digital pins (D0-D13) and analog input pins (A0-A5 [*]) can be both be set to output simple LOW (0V) or HIGH (+5V) signals.

>>> # set D7 output to HIGH
>>> my_fetbox.digital_write(7, 1)
>>> # pin D7 now reads +5V

The Arduino Nano is only capable of ‘analog’ (PWM) output on pins D3, D5, D6, D9, D10, and D11 - of which, the first five are connected to MOSFET output channels. All of these can still be controlled with the analog_write() method, however, only D11 is completely unused. PWM values are 8-bit (0-255).

>>> # set D11 to 50% PWM duty cycle
>>> my_fetbox.analog_write(11, 128)
>>> # pin D11 now outputs a +2.5V signal
>>> # set D10 to 20% PWM duty cycle
>>> my_fetbox.analog_write(10, 51)
>>> # pin D10 now outputs +1V, however MOSFET channel #5 also outputs +2.4V

Reading Input Pins

Digital Pins:

digital_read(pin)

Analog Pins:

analog_read(pin)

Arduino input pins can also be queried over the serial interface. Digital pin names are supplied as an int (e.g. 1), analogs pins names as str (e.g. “A3”).

Digital readings return 1 for a HIGH state, or 0 for a LOW state.

>>> # Read digital pin 7 (5V signal connected)
>>> reading = my_fetbox.digital_read(7)
>>> print(reading)
>>> 1

Analog readings return a 10-bit value (0-1023) which corresponds to a signal voltage approximately 0-5V.

>>> # Read analog pin 3 (3.3V signal connected)
>>> reading = my_fetbox.analog_read('A3')
>>> print(reading)
>>> 700

Digitally reading an analog pin will return the nearest state (HIGH or LOW) corresponding to the input signal.

>>> # Digital read analog pin 3 (3.3V signal connected)
>>> reading  my_fetbox.digital_read('A3')
>>> print(reading)
>>> 1

Misc. Methods

  • heartbeat() - Pings the FETbox, returns TRUE if responsive.

  • query_ID() - Retrieves the FETbox’s programmed ID.

4. Expanding Functionality

Custom Serial Commands

The plateflo.fetbox module has two methods for direct serial communication, send_cmd() and send_query(). These can both send a arbitrary command to the FETbox Arduino, however expect different responses; send_cmd() expects a simple pass/fail response, while send_query() expects an arbitrary LF-terminated string response terminated.

The FETbox firmware can be easily modified to expand the recognized commands and execute more complex code internally (e.g. reading SPI- or I2C-connected sensors) before sending an informed response string.

Note

If a custom command requires more than 200ms to execute, increase the serial timeout from the default in the pyserial backend:

>>> my_fetbox.mod_ser.ser.timeout = 1.0 # 1 second timeout

The following serial commands are already defined in fetbox.CMD:

CMDS
CMDS = {
    'get_id':       '@#\n',         # FETbox ID inquiry
    'heartbeat':    '@?\n',         # Always returns '*\r'
    'enable':       '@H%i\n',       # Enable channel i
    'disable':      '@I%i\n',       # Disable channel i
    'pwm':          '@S%i%s\n',     # PWM output channel i
    'hithold':      '@V%i%s\n',     # Hit and hold channel i
    'digread':      '@D%02i\n',     # Digital read pin i
    'digwrite':     '@E%02i%i\n',   # digitalWrite pin i
    'anaread':      '@A%02i\n',     # Analog read pin i
    'anawrite':     '@B%02i%03i\n'  # analogWrite pin i
}

Firmware Modification

FETbox serial commands have the following structure:
@<CMD><BODY>\n
|  |    |    |
|  |    |    Line feed (LF)
|  |    |
|  |    Command body, arbitrary contents
|  Command code, single ASCII character
Command start

@ is the start of command character, present at the beginning of every FETbox serial command.

The CMD character directs cmd_interpret(char* cmd) to execute user-defined code through conditional statements therein.

The BODY of the command is parsed by user code and is command-specific.

// Existing commands are defined as macros at the top of the .ino program:
#define CMD_ID        '#'   // query device ID
#define CMD_YOURCMD   '1'   // your custom command

void cmd_interpret(char* cmd) {
   /* Module ID query */
   if(cmd[0] == CMD_ID) {
      Serial.print("fetbox");
      Serial.print(ID);
      Serial.write("\n");
   }

   /* some other commands */
   else if(cmd[0] == CMD_SOMEOTHERCMD) {
      // does other stuff
      }

   /* your amazing command */
   else if(cmd[0] == CMD_YOURCMD) {
      // do something, no <BODY> parameters
      do_something();

      // or parse <BODY> for parameters, then execute a function
      int _chan = (cmd[1]-'0');  // parse channel #
      disco_time(_chan);         // execute disco on provided channel
      ack();                     // command success response
   }



   // ... etc.