ECE-4180 Final Project

Introduction

The AFE4404 is an optical analog front-end developed by Texas Instruments for recording photoplethysmography data. Photoplethysmography is used for blood volume pulse sensing in multiple application areas such as heart-rate monitoring (in fitness monitoring devices like the Fitbit and Apple Watch) and blood oxygen saturation estimation (pulse oximeters).

Some of the features of the AFE4404 are listed below:

  • High dynamic range (100 dB)
  • Programmable LED current up to 100 mA
  • 3 LEDs
  • Programmable gain
  • I2C interface
  • Ambient subtraction at ADC output http://www.ti.com/ods/images/SBAS689D/frontpage_sbas689.gif

The part can be purchased from Digikey. However, the IC comes in a DSBGA package, which makes it inconvenient for breadboard prototyping. Therefore, I placed an order for the board through Proto Advantage in order for them to place the device on a PCB.

The device can be hooked up to the MBED as shown in the figure below. /media/uploads/dotunhunter/afe4404.png

INP and INM should be connected to the cathode and anode of the photodiode, respectively, while the anode and cathode of the LED should be connected to Tx_SUP and TX_i (where i is 1, 2, or 3), respectively as shown in the schematic above (although I only used channel 2 for my project). Note that the current design requires three power supplies in order to independently turn on the power supplies in the manner specified in the datasheet. Future versions will explore the possibility of implementing a design with single power supply.

The AFE4404 uses an I2C interface for communication. Pull-up resistors are required because internal pull-ups are not provided by the front-end device. The I2C address for the device is the 7-bit representation of 0x58. To read from any register, the register address must be sent, and the REG_READ bit in register 0x00 of the AFE must be set to 1 (for all registers other than the ADC output registers). A repeated start condition (after the register address is sent) is required for reading data from the AFE4404's registers. To write to registers, the REG_READ bit must be set to 0 after sending the registers address. Note that 24-bit data are sent or received during each communication.

Project Demo

The demo code below shows how to instantiate a new AFE_4404 object . First, it creates a thread for initializing and monitoring the HTTP request handler for communicating with the mbed via RPC. The RPC protocol is used to update the AFE's register settings from the GUI (freeing up the virtual COM port for sending PPG data).

Aside

Note that mbed's C# library for making RPC calls to the mbed didn't work, so I had to implement a simple protocol for making RPC calls with HTTP. Because of this limitation, I only setup the GUI to program the current, feedback capacitance, and feedback gain for led2. Future versions will make programming of the AFE's registers via the GUI more robust. Furthermore, I used the ethernet on the mbed for the HTTP requests and setup a network bridge connection between it and my PC. Instructions for connecting mbed's ethernet are provided in the image below and those for setting up a network bridge for Windows 10 can be found in the link below. /media/uploads/dotunhunter/ethernet.png

http://www.windowscentral.com/how-set-and-manage-network-bridge-connection-windows-10

The main code initializes the AFE with its powerUpSequence method, which initializes the ports, the power supplies, and the AFE4404's registers with values very similar to the typical settings provided in the datasheet, and the clock output. Finally, the getData interrupt service routine (ISR) is associated with the rising edge of the AFE4404's data ready (drdy) pin, and interrupts from that pin are enabled.

The getData ISR disables interrupts on the drdy pin and then reads data from LED2. A two's complement sign extension is performed in order to convert the 24-bit ADC output codes to 32-bit integers. The ISR then sets the dataAvailable variable to true to alert the main code that data is ready for it to send to the PC over the serial port.

It is important to note that while the AFE_4404 constructor requires an argument specifying the MBED's clock output pin to the AFE4404, the current version of the code is hard-coded to output the clock on pin21. This is because the maximum frequency that can be output via the pwn pins (with the mbed APIs) is less than the 4MHz clock required by the AFE. Therefore, I had to set the clock output with the mbed's registers. Future versions of the library will address this issue.

Video

The video below shows the AFE4404 sending LED2 output codes via the serial port.

Import libraryAFE4404

Code for Interfacing with Texas Instruments' AFE4404 IC for photoplethysmography applications

Import programece4180_project

Implementation of AFE4404 in mbed and C#

AFE4404 header

#ifndef AFE_4404_H
#define AFE_4404_H

#include "mbed.h"

#define LOWER_BYTE_MASK     0x000000FF
#define SIGN_EXT            0xFF000000
#define SIGN_MASK           0x00800000
#define BITS_PER_BYTE       8
#define NUM_REGISTERS       38

class AFE_4404 {
    
    public:
    
        AFE_4404(PinName rxSupplyEn, PinName txSupplyEn, PinName resetz, 
            PinName powerEn, PinName drdy, PinName clk, PinName sda, PinName scl);
        
        void initPorts(void);
        void initPowerSupply(void);
        void initRegisters(void);
        void initClock(void);
        void powerUpSequence(void);
        
        void inline enableWriteMode(void) {
            _writeBuffer[0] = 0x00;    // AFE register address 0x00
            // write 0 to REG_READ bit in register 0x00 to enable readout 
            // of write registers
            _writeBuffer[1] = 0x00;    
            _writeBuffer[2] = 0x00;
            _writeBuffer[3] = 0x00;
            _i2c.write(_address, _writeBuffer, 4);
        }
        
        void inline enableReadMode(void) {
            _writeBuffer[0] = 0x00;    // AFE register address 0x00
            // write 1 to REG_READ bit in register 0x00 to enable writes to 
            // write registers
            _writeBuffer[1] = 0x00;    
            _writeBuffer[2] = 0x00;
            _writeBuffer[3] = 0x01;
            _i2c.write(_address, _writeBuffer, 4);           
        }  
        
        void inline disableIRQ(void) {
            _drdy.disable_irq();
        }

        void inline enableIRQ(void) {
            _drdy.enable_irq();
        }
        
        void getData(void);
        void writeData(uint8_t reg, uint32_t data);
        uint32_t readData(uint8_t reg, bool adc);
        
        
    private:
        DigitalOut  _rxSupplyEn;
        DigitalOut  _txSupplyEn;
        DigitalOut  _resetz;
        DigitalOut  _powerEn;
        
        InterruptIn _drdy;
        PwmOut      _clk;
        I2C         _i2c;
        
        int _address;
        char _writeBuffer[5];
        char _readBuffer[5];
        // temporary variable to prevent multiple allocations
        uint32_t _tempData;
};

#endif

AFE4404 Implementatopn (.cpp file)

#include "mbed.h"
#include "AFE_4404.h"


char LED = 0x2A; // LED2 on AFE4404
int32_t data;
volatile bool dataAvailable = false;


AFE_4404::AFE_4404(PinName rxSupplyEn, PinName txSupplyEn, PinName resetz, 
            PinName powerEn, PinName drdy, PinName clk, PinName sda, PinName scl):
            
            _rxSupplyEn(rxSupplyEn), _txSupplyEn(txSupplyEn), _resetz(resetz), 
            _powerEn(powerEn), _drdy(drdy), _clk(clk), _i2c(sda, scl) {
                // inputs: 
                // rxSupplyEn, txSuppyEn, and powerEn: digital outputs (active low) used to 
                // turn on the power supplies in the manner described in the datasheet
                // resetz: used to reset the AFE after power up (active low)
                // drdy:  interrupt from AFE when data is ready
                // clk:    pwm output at ~ 4.1MHz
                // sda, scl: I2C data and clock, respectively;
       
       // shift by 1 for 8-bit representation of 7-bit address
        _address = (0x58 << 1);
}
                
void AFE_4404::initPorts(void) { 
        
        // turn off power supplies
        _rxSupplyEn   = 0;
        _txSupplyEn   = 0;
        _powerEn      = 0;
        
        // resetz is active low, so leave on before power supply init
        _resetz     = 1;
        
        // set the clock output to zero before power-up sequence
        // this convoluted method was required because of the way the the PWM
        // output is set up (faster that possible with the MBED APIs)
        _clk.period(10);
        _clk.write(0);
        
        disableIRQ();
}

void AFE_4404::initPowerSupply(void) {
    
    wait_ms(100);
    
    _powerEn = 1;
    wait_ms(100);
    
    _rxSupplyEn = 1;
    wait_ms(10);
    
    _txSupplyEn = 1;
    wait_ms(20);
    
    _resetz = 0;
    wait_us(35);
    
    _resetz = 1;
    
    initClock();
    wait_ms(2);
}   

uint32_t AFE_4404::readData(uint8_t reg, bool adc = true) {
    
    if (!adc) {
        enableReadMode();
    }
    
    _writeBuffer[0] = reg;  // initialize write buffer with AFE register address
    
    // initialize read buffers to 0. probably unnecessary
    _readBuffer[0] = 0x00;
    _readBuffer[1] = 0x00;
    _readBuffer[2] = 0x00;
    
    // write the register to AFE and use repeated start mode as specified in
    // the datasheet
    _i2c.write(_address, _writeBuffer, 1, true);
    // read 3 bytes of data from register MSB first
    _i2c.read(_address, _readBuffer, 3);
    
    _tempData = 0;
    _tempData = (_readBuffer[0] << (BITS_PER_BYTE * 2)) | \
        (_readBuffer[1] << BITS_PER_BYTE) | _readBuffer[2];
    
    if (adc && (SIGN_MASK & _tempData)) {
        _tempData |= SIGN_EXT;
    }
    
    return _tempData;
    
}

void AFE_4404::writeData(uint8_t reg, uint32_t data) {
    
    enableWriteMode();
    
    _writeBuffer[0] = reg;
    
    // store the lower 3 bytes of data in _writeBuffer (MSB first)
    for (int i = 2, j = 1; i >= 0; i--, j++) {
        _writeBuffer[j] = (data >> (BITS_PER_BYTE * i)) & LOWER_BYTE_MASK;
    }
    
    // write 4 bytes
    // 1 for the register address and 3 for the lower 3 bytes of data
    _i2c.write(_address, _writeBuffer, 4);
    
}

struct Register {
    uint8_t addr;
    uint32_t val;
};

void AFE_4404::initRegisters(void) {
    
    unsigned char i;
    struct Register reg[NUM_REGISTERS];
    reg[0].addr = 0x01; reg[0].val = 0x000050;
    reg[1].addr = 0x02; reg[1].val = 0x00018F;
    reg[2].addr = 0x03; reg[2].val = 0x000320;
    reg[3].addr = 0x04; reg[3].val = 0x0004AF;
    reg[4].addr = 0x05; reg[4].val = 0x0001E0;
    reg[5].addr = 0x06; reg[5].val = 0x00031F;
    reg[6].addr = 0x07; reg[6].val = 0x000370;
    reg[7].addr = 0x08; reg[7].val = 0x0004AF;
    reg[8].addr = 0x09; reg[8].val = 0x000000;
    reg[9].addr = 0x0A; reg[9].val = 0x00018F;
    reg[10].addr = 0x0B; reg[10].val = 0x0004FF;
    reg[11].addr = 0x0C; reg[11].val = 0x00063E;
    reg[12].addr = 0x0D; reg[12].val = 0x000198;
    reg[13].addr = 0x0E; reg[13].val = 0x0005BB;
    reg[14].addr = 0x0F; reg[14].val = 0x0005C4;
    reg[15].addr = 0x10; reg[15].val = 0x0009E7;
    reg[16].addr = 0x11; reg[16].val = 0x0009F0;
    reg[17].addr = 0x12; reg[17].val = 0x000E13;
    reg[18].addr = 0x13; reg[18].val = 0x000E1C;
    reg[19].addr = 0x14; reg[19].val = 0x00123F;
    reg[20].addr = 0x15; reg[20].val = 0x000191;
    reg[21].addr = 0x16; reg[21].val = 0x000197;
    reg[22].addr = 0x17; reg[22].val = 0x0005BD;
    reg[23].addr = 0x18; reg[23].val = 0x0005C3;
    reg[24].addr = 0x19; reg[24].val = 0x0009E9;
    reg[25].addr = 0x1A; reg[25].val = 0x0009EF;
    reg[26].addr = 0x1B; reg[26].val = 0x000E15;
    reg[27].addr = 0x1C; reg[27].val = 0x000E1B;
    reg[28].addr = 0x1D; reg[28].val = 0x009C3F;
    reg[29].addr = 0x1E; reg[29].val = 0x000103;
    reg[30].addr = 0x20; reg[30].val = 0x008003;
    reg[31].addr = 0x21; reg[31].val = 0x000003;
    reg[32].addr = 0x22; reg[32].val = 0x000400;
    reg[33].addr = 0x23; reg[33].val = 0x000000;
    reg[34].addr = 0x32; reg[34].val = 0x00155F;
    reg[35].addr = 0x33; reg[35].val = 0x00991F;
    reg[36].addr = 0x36; reg[36].val = 0x000190;
    reg[37].addr = 0x37; reg[37].val = 0x00031F;

    for (i = 0; i < NUM_REGISTERS; i++) 
      writeData(reg[i].addr, reg[i].val);
    
}

void AFE_4404::initClock(void) {
    
    LPC_PWM1->TCR = (1 << 1);               // Reset counter, disable PWM
    LPC_SC->PCLKSEL0 &= ~(0x3 << 12);  
    LPC_SC->PCLKSEL0 |= (1 << 12);          // Set peripheral clock divider to /1, i.e. system clock
    LPC_PWM1->MR0 = 22;                     // Match Register 0 is shared period counter for all PWM1
    LPC_PWM1->MR6 = 11;                      // Pin 21 is PWM output 6, so Match Register 6
    LPC_PWM1->LER |= 1;                     // Start updating at next period start
    LPC_PWM1->TCR = (1 << 0) || (1 << 3);   // Enable counter and PWM
}

void AFE_4404::powerUpSequence(void) {
    
    initPorts();
    initPowerSupply();
    initRegisters();
    initClock();
    _drdy.rise(this, &AFE_4404::getData);
    enableIRQ();
    
}

void AFE_4404::getData(void) {
    
    disableIRQ();  
    data = static_cast<int32_t> (readData(LED, true));
    dataAvailable = true;
    enableIRQ();
}

Demo Code

main demo code

#include "mbed.h"
#include "EthernetInterface.h"
#include "HTTPServer.h"
#include "mbed_rpc.h"
#include "RPCFunction.h"
#include "AFE_4404.h"

EthernetInterface eth;  
HTTPServer svr;

int baudRate = 115200;
Serial pc(USBTX, USBRX); 

extern int32_t data;
extern bool dataAvailable;
// p22: rxSupplyEn, p8: txSupplyEn, p6: resetz, p7: powerEn,
// p5: drdy, p21: clk, p9: sda, p10: scl   
AFE_4404 afe(p22, p8, p6, p7, p5, p21, p9, p10);

Thread rpcThread;
//automatically lock rpcBusy when it's been used
volatile bool rpcBusy = true;
void afeRpc(Arguments *input, Reply *output);
RPCFunction afeRpcFunction(&afeRpc, "afe");

void afeRpc(Arguments *input, Reply *output){
    
    int32_t registerAddress;
    int32_t registerData;
    int32_t suspend; // prevents main from writing to data if it's 1 
   
    // prevent main loop from writing data to serial
    rpcBusy = true;
    Thread::wait(100);
    
    registerAddress = input->getArg<int32_t>();
    registerData = input->getArg<int32_t>();
    suspend = input->getArg<int32_t>();
    
    // write data to afe register
    afe.writeData(
        static_cast<uint8_t>(registerAddress), 
        static_cast<uint32_t>(registerData));
    
    output->putData(registerAddress);
    output->putData(registerData);
    
    // suspend serial writes in main loop. functionality isn't currently
    // being used, so rpcBusy is always false in current C# GUI version
    rpcBusy = (suspend == 1) ? true : false;
}

void rpcThreadRoutine() {
    
    printf("Setting up...\n");
    eth.init();
    
    // wait for a while because the initialization doesn't work without the delay
    // maybe the initialization is done asynchronously??
    wait(10.0);
    
    int ethErr = eth.connect();
    
    if(ethErr < 0) 
        printf("Error %d in setup.\n", ethErr);
  
    svr.addHandler<HTTPRpcRequestHandler>("/rpc");
    //attach server to port 80
    svr.start(80, &eth);
    
    // gives enough time to pause terminal application to obtain IP address
    Thread::wait(2000);  
    printf("Listening...\n");
    
    rpcBusy = false;
    
    //Listen indefinitely
    while(true) {
        svr.poll();
        Thread::wait(1000);
    }    
}

int main() {
    
    pc.baud(baudRate);
    
    rpcThread.start(rpcThreadRoutine);   
    // spin until RPC is setup
    while(rpcBusy) {}
    
    afe.powerUpSequence();
    
    while(true){
        if(dataAvailable && !rpcBusy) {
            pc.printf("%d ", data);  
            dataAvailable = false;
        } 
    }
}

C# GUI Code All Implemented by Me

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.IO.Ports;
using System.Net;
using System.Diagnostics;


namespace AFE4404
{
    public partial class Form1 : Form
    {
        // parts of the data may be incomplete numbers, i.e., there's no delimiter after it
        // use partialNumber to store the incomplete number and concatenate with next incomplete
        // (but delimited) number read from serial port
        private string partialNumber = "";
        // stores the number of available data (after accounting for incomplete numbers)
        private int validLength;

        // is split to obtain data
        private string dataString;
        // stores valid data obtained from dataString
        private string[] dataArray;

        private SerialPort serial;
        // serial data is read with interrupt driven I/O, so waitSerialData is used to
        // signal the background thread to read the data (background thread blocks until data
        // is available
        private AutoResetEvent waitSerialData;

        // background thread that reads and plots data on GUI
        private Thread serialThread;

        private int end = 0;
        // actual data is 22 bits (with 2's complement sign extension) representing
        // range [-1, 1]
        private double codeScale = Math.Pow(2, 21);

        // array that's used to store data to plot
        private double[] chartData = new double[400];
        // adds raw data to list so that it can be exported as .csv
        private List<string> chartDataList = new List<string>();
        // preallocate comma used for storing chartDataList in .csv
        byte comma = (byte)',';

        // min and max of data to plot
        private double min;
        private double max;

        // add's padding to the min and max (only currently works for strictly positive
        // data. will make modifications to account for negative (and 0) data later
        private double axisScaling = 0.02;

        // update axis after number of chart updates (keeps the plot axis from being jerky)
        private int axisUpdateDelay = 0;

        private string comPort = "COM4";
        private int baudRate = 115200;

        private string mbedHostName = "http://128.61.33.139";
        private string mbedRpcFunction = "afe";
        // do not modify. I was supposed to use it for a different unimplemented functionality
        string url; // full url for rpc get request will be formed durin runtime
        int suspendMbedMain = 0; 

        public Form1()
        {
            InitializeComponent();
            baudRateComboBox.Text = baudRate.ToString();
            comPortComboBox.Text = comPort.ToString();
            chart1.ChartAreas[0].AxisY.LabelStyle.Format = "{0:0.000}";
            led2CurrentNumericUpDown.DecimalPlaces = 1; // display led currents to 1 decimal place
        }

        private void Start_Click(object sender, EventArgs e)
        {
            
            if (serialThread != null && serialThread.IsAlive)
            {
                // end serial data connection if it already exists
                // change color/enable controls 
                serialThread.Abort();

                Start.Text = "Start";
                Start.BackColor = Color.YellowGreen;

                Save.Enabled = true;
                Save.BackColor = Color.DarkGray;

                comPortComboBox.Enabled = true;
                comPortComboBox.BackColor = Color.DarkGray;

                baudRateComboBox.Enabled = true;
                baudRateComboBox.BackColor = Color.DarkGray;

            }
            else
            {
                // create a new thread for processing serial data in the background
                serialThread = new Thread(() => serialThreadRoutine());
                serialThread.Start();


                // disable controls and also update text and colors of controls
                Start.Text = "Stop";
                Start.BackColor = Color.Red;

                Save.Enabled = false; // cannot save data in list if chart is updating
                Save.BackColor = Color.LightGray;

                comPortComboBox.Enabled = false;
                comPortComboBox.BackColor = Color.LightGray;

                baudRateComboBox.Enabled = false;
                baudRateComboBox.BackColor = Color.LightGray;
            }
        }

        public void serialThreadRoutine()
        {
            // create new event to alert thread that data is available
            waitSerialData = new AutoResetEvent(false);
            try
            {
                setupSerialPort();
                serial.Open();
                serial.DiscardInBuffer();
                partialNumber = "";

                while (true)
                {
                    // wait for condition signal and process serial data once it receives 
                    // a signal that serial data is available
                    waitSerialData.WaitOne();
                    Invoke((MethodInvoker)delegate { process(); });
                }
            }
            finally
            {
                serial.Close();
            }
        }

        public void setupSerialPort()
        {
            serial = new SerialPort(comPort);
            serial.BaudRate = baudRate;
            serial.Parity = Parity.None;
            serial.StopBits = StopBits.One;
            serial.DataBits = 8;
            serial.Handshake = Handshake.None;
            serial.DataReceived += dataReceivedHandler;
        }

        public void dataReceivedHandler(object sender,
            SerialDataReceivedEventArgs e)
        {
            // all interrupt service routine for serial does is alert background thread that
            // serial data is available
            waitSerialData.Set();
        }


        public void process()
        {
            
            if (serial.IsOpen)
            {
                dataString = serial.ReadExisting();
                // concatenate partially formed number from previous serial data to new data
                // received from serial port
                dataString = (partialNumber == "") ? dataString : partialNumber + dataString;

                // break up serial data into array. space is used as delimiter
                dataArray = dataString.Split((char[])null,
                    StringSplitOptions.RemoveEmptyEntries);

                // this condition may lead to occasional incorrect samples. I still need to debug to verify
                if (dataArray.Length > 0)
                {
                    // store last number if it wasn't delimited
                    partialNumber = (dataString.Last() == ' ') ? "" : dataArray.Last();
                    // don't plot/store the last number if it wasn't delimited
                    validLength = (partialNumber == "") ? dataArray.Length : dataArray.Length - 1;


                    // the following code segment is inefficient and will be improved later to remove
                    // the redundant array copy. this can be done by directly writing to chartDataList
                    // and updating the chart directly with chartDataList
                    // ************************************************************************************
                    end = chartData.Length - validLength;

                    Array.Copy(chartData, validLength, chartData, 0, end);

                    for (int i = 0; i < validLength; i++)
                    {
                        chartData[end++] = Int32.Parse(dataArray[i]) / codeScale;
                        chartDataList.Add(dataArray[i]);
                    }

                    chart1.Series["Series1"].Points.Clear();

                    min = 10e7;
                    max = 0;

                    for (int i = 0; i < chartData.Length; i++)
                    {
                        chart1.Series["Series1"].Points.AddY(chartData[i]);
                        min = (chartData[i] < min) ? chartData[i] : min;
                        max = (chartData[i] > max) ? chartData[i] : max;
                    }
                    // ************************************************************************************

                    // update axis range only after N number of chart updates
                    if (axisUpdateDelay > 0)
                    {
                        axisUpdateDelay--;
                    }
                    else
                    {
                        // change the axis range (include padding)
                        chart1.ChartAreas[0].AxisY.Minimum = min - axisScaling * min;
                        chart1.ChartAreas[0].AxisY.Maximum = max + axisScaling * max;
                        // reset number of char updates before axis rescaling
                        axisUpdateDelay = 100;
                    }
                }
            }

        }

        private void Save_Click(object sender, EventArgs e)
        {

            // save to .csv file
            // code found online

            Stream myStream = null;
            SaveFileDialog saveFileDialog1 = new SaveFileDialog();

            // set initial save directory to two directories above the current one
            saveFileDialog1.InitialDirectory =
                Path.GetFullPath(".." + "..");
            saveFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
            saveFileDialog1.FilterIndex = 2;
            saveFileDialog1.RestoreDirectory = true;

            if (saveFileDialog1.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    if ((myStream = saveFileDialog1.OpenFile()) != null)
                    {
                        using (myStream)
                        {
                            foreach (string value in chartDataList)
                            {
                                byte[] data = new UTF8Encoding(true).GetBytes(value);
                                myStream.Write(data, 0, data.Length);
                                myStream.Write(new byte[] { comma }, 0, 1);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error: Could not write file to disk:\nOriginal error: " + ex.Message);
                }
            }
        }

        private void refreshComButton_Click(object sender, EventArgs e)
        {
            foreach (string str in SerialPort.GetPortNames())
            {
                if ( !comPortComboBox.Items.Contains(str) )
                    comPortComboBox.Items.Add(str);
            }
        }

        private void comPortComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            Object selectedItem = comPortComboBox.SelectedItem;
            comPort = selectedItem.ToString();
        }

        private void baudRateComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            Object selectedItem = baudRateComboBox.SelectedItem;
            baudRate = int.Parse(selectedItem.ToString());
        }

        private void updateRegistersButton_Click(object sender, EventArgs e)
        {
            // be sure to select current, gain, and capacitance if you're updating the registers
            // because the current implementation sets default values for all of them. 
            // I don't have time to make it more configurable right now, but I'll do so later

            // AFE register for modifying led2 current
            int led2RegAddress = 0x22; 
            // AFE register for modifying gain and feedback capacitance of led2 channel
            int gainRegAddress = 0x21;
            double led2Value = Convert.ToDouble(led2CurrentNumericUpDown.Value);
            // add epsilon to led2 value because of floating point issues
            int led2Index = (int) ((led2Value + 0.01) / 0.8);

            int ledRegValue = 0;
            // 3 bit led2 current control start at bit 6
            ledRegValue |= (led2Index << 6); 

            int gainsRegValue = 0;

            if (tiaCapComboBox != null)
                gainsRegValue |= (tiaCapComboBox.SelectedIndex << 3);

            if (tiaGainComboBox.SelectedItem != null)
                gainsRegValue |= tiaGainComboBox.SelectedIndex;

            // make RPC call to change led2 current
            url = getURL(led2RegAddress, ledRegValue);
            Invoke((MethodInvoker)delegate { makeHttpRPCRequest(url); });

            // make RPC call to modify led2 gain and capacitance
            url = getURL(gainRegAddress, gainsRegValue);
            Invoke((MethodInvoker)delegate { makeHttpRPCRequest(url); });
        }

        private string getURL(int regAddress, int regValue)
        {
            return string.Format(
                mbedHostName + "/rpc/" + mbedRpcFunction + "/run%20{0}%20{1}%20{2}",
                regAddress, regValue, suspendMbedMain);
        }

        private void makeHttpRPCRequest(string url)
        {
            // code found online for making HTTP get requests
            string html = string.Empty;

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.AutomaticDecompression = DecompressionMethods.GZip;

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            using (Stream stream = response.GetResponseStream())
            using (StreamReader reader = new StreamReader(stream))
            {
                html = reader.ReadToEnd();
            }
        }
    }
}

C# Code Mostly Auto-generated by VisualStudio Enterprise 2015 (with a few modifications by me)

namespace AFE4404
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea3 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
            System.Windows.Forms.DataVisualization.Charting.Legend legend3 = new System.Windows.Forms.DataVisualization.Charting.Legend();
            System.Windows.Forms.DataVisualization.Charting.Series series3 = new System.Windows.Forms.DataVisualization.Charting.Series();
            this.tabControl1 = new System.Windows.Forms.TabControl();
            this.tabPage1 = new System.Windows.Forms.TabPage();
            this.Start = new System.Windows.Forms.Button();
            this.chart1 = new System.Windows.Forms.DataVisualization.Charting.Chart();
            this.tabPage2 = new System.Windows.Forms.TabPage();
            this.label2 = new System.Windows.Forms.Label();
            this.label1 = new System.Windows.Forms.Label();
            this.baudRateComboBox = new System.Windows.Forms.ComboBox();
            this.Save = new System.Windows.Forms.Button();
            this.comPortComboBox = new System.Windows.Forms.ComboBox();
            this.tabPage3 = new System.Windows.Forms.TabPage();
            this.led2CurrentLabel = new System.Windows.Forms.Label();
            this.led2CurrentNumericUpDown = new System.Windows.Forms.NumericUpDown();
            this.updateRegistersButton = new System.Windows.Forms.Button();
            this.tiaGainComboBox = new System.Windows.Forms.ComboBox();
            this.tiaGainLabel = new System.Windows.Forms.Label();
            this.tiaCapComboBox = new System.Windows.Forms.ComboBox();
            this.tiaCapLabel = new System.Windows.Forms.Label();
            this.tabControl1.SuspendLayout();
            this.tabPage1.SuspendLayout();
            ((System.ComponentModel.ISupportInitialize)(this.chart1)).BeginInit();
            this.tabPage2.SuspendLayout();
            this.tabPage3.SuspendLayout();
            ((System.ComponentModel.ISupportInitialize)(this.led2CurrentNumericUpDown)).BeginInit();
            this.SuspendLayout();
            // 
            // tabControl1
            // 
            this.tabControl1.Appearance = System.Windows.Forms.TabAppearance.Buttons;
            this.tabControl1.Controls.Add(this.tabPage1);
            this.tabControl1.Controls.Add(this.tabPage2);
            this.tabControl1.Controls.Add(this.tabPage3);
            this.tabControl1.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.tabControl1.Location = new System.Drawing.Point(2, 37);
            this.tabControl1.Margin = new System.Windows.Forms.Padding(2);
            this.tabControl1.Name = "tabControl1";
            this.tabControl1.SelectedIndex = 0;
            this.tabControl1.Size = new System.Drawing.Size(736, 600);
            this.tabControl1.TabIndex = 0;
            // 
            // tabPage1
            // 
            this.tabPage1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
            this.tabPage1.Controls.Add(this.Start);
            this.tabPage1.Controls.Add(this.chart1);
            this.tabPage1.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.tabPage1.ForeColor = System.Drawing.Color.Blue;
            this.tabPage1.Location = new System.Drawing.Point(4, 32);
            this.tabPage1.Margin = new System.Windows.Forms.Padding(2);
            this.tabPage1.Name = "tabPage1";
            this.tabPage1.Padding = new System.Windows.Forms.Padding(2);
            this.tabPage1.Size = new System.Drawing.Size(728, 564);
            this.tabPage1.TabIndex = 0;
            this.tabPage1.Text = "Chart";
            this.tabPage1.UseVisualStyleBackColor = true;
            // 
            // Start
            // 
            this.Start.BackColor = System.Drawing.Color.YellowGreen;
            this.Start.ForeColor = System.Drawing.Color.Black;
            this.Start.Location = new System.Drawing.Point(638, 241);
            this.Start.Margin = new System.Windows.Forms.Padding(2);
            this.Start.Name = "Start";
            this.Start.Size = new System.Drawing.Size(85, 58);
            this.Start.TabIndex = 1;
            this.Start.Text = "Start";
            this.Start.UseVisualStyleBackColor = false;
            this.Start.Click += new System.EventHandler(this.Start_Click);
            // 
            // chart1
            // 
            chartArea3.Name = "ChartArea1";
            this.chart1.ChartAreas.Add(chartArea3);
            legend3.Enabled = false;
            legend3.Name = "Legend1";
            this.chart1.Legends.Add(legend3);
            this.chart1.Location = new System.Drawing.Point(4, 9);
            this.chart1.Margin = new System.Windows.Forms.Padding(2);
            this.chart1.Name = "chart1";
            series3.ChartArea = "ChartArea1";
            series3.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Spline;
            series3.Legend = "Legend1";
            series3.Name = "Series1";
            this.chart1.Series.Add(series3);
            this.chart1.Size = new System.Drawing.Size(635, 559);
            this.chart1.TabIndex = 0;
            this.chart1.Text = "chart1";
            // 
            // tabPage2
            // 
            this.tabPage2.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
            this.tabPage2.Controls.Add(this.label2);
            this.tabPage2.Controls.Add(this.label1);
            this.tabPage2.Controls.Add(this.baudRateComboBox);
            this.tabPage2.Controls.Add(this.Save);
            this.tabPage2.Controls.Add(this.comPortComboBox);
            this.tabPage2.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.tabPage2.ForeColor = System.Drawing.SystemColors.Desktop;
            this.tabPage2.Location = new System.Drawing.Point(4, 32);
            this.tabPage2.Margin = new System.Windows.Forms.Padding(2);
            this.tabPage2.Name = "tabPage2";
            this.tabPage2.Padding = new System.Windows.Forms.Padding(2);
            this.tabPage2.Size = new System.Drawing.Size(728, 564);
            this.tabPage2.TabIndex = 1;
            this.tabPage2.Text = "Settings";
            this.tabPage2.UseVisualStyleBackColor = true;
            // 
            // label2
            // 
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(2, 133);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(86, 20);
            this.label2.TabIndex = 4;
            this.label2.Text = "Baud Rate";
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.ForeColor = System.Drawing.Color.Black;
            this.label1.Location = new System.Drawing.Point(5, 18);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(153, 20);
            this.label1.TabIndex = 3;
            this.label1.Text = "Available COM Ports";
            // 
            // baudRateComboBox
            // 
            this.baudRateComboBox.FormattingEnabled = true;
            this.baudRateComboBox.Items.AddRange(new object[] {
            9600,
            115200});
            this.baudRateComboBox.Location = new System.Drawing.Point(4, 159);
            this.baudRateComboBox.Margin = new System.Windows.Forms.Padding(2);
            this.baudRateComboBox.Name = "baudRateComboBox";
            this.baudRateComboBox.Size = new System.Drawing.Size(134, 28);
            this.baudRateComboBox.TabIndex = 2;
            this.baudRateComboBox.SelectedIndexChanged += new System.EventHandler(this.baudRateComboBox_SelectedIndexChanged);
            // 
            // Save
            // 
            this.Save.Location = new System.Drawing.Point(4, 267);
            this.Save.Margin = new System.Windows.Forms.Padding(2);
            this.Save.Name = "Save";
            this.Save.Size = new System.Drawing.Size(134, 27);
            this.Save.TabIndex = 1;
            this.Save.Text = "Save Data";
            this.Save.UseVisualStyleBackColor = true;
            this.Save.Click += new System.EventHandler(this.Save_Click);
            // 
            // comPortComboBox
            // 
            this.comPortComboBox.FormattingEnabled = true;
            this.comPortComboBox.Location = new System.Drawing.Point(6, 46);
            this.comPortComboBox.Margin = new System.Windows.Forms.Padding(2);
            this.comPortComboBox.Name = "comPortComboBox";
            this.comPortComboBox.Size = new System.Drawing.Size(136, 28);
            this.comPortComboBox.Sorted = true;
            this.comPortComboBox.TabIndex = 0;
            this.comPortComboBox.DropDown += new System.EventHandler(this.refreshComButton_Click);
            this.comPortComboBox.SelectedIndexChanged += new System.EventHandler(this.comPortComboBox_SelectedIndexChanged);
            // 
            // tabPage3
            // 
            this.tabPage3.Controls.Add(this.tiaCapLabel);
            this.tabPage3.Controls.Add(this.tiaCapComboBox);
            this.tabPage3.Controls.Add(this.tiaGainLabel);
            this.tabPage3.Controls.Add(this.tiaGainComboBox);
            this.tabPage3.Controls.Add(this.led2CurrentLabel);
            this.tabPage3.Controls.Add(this.led2CurrentNumericUpDown);
            this.tabPage3.Controls.Add(this.updateRegistersButton);
            this.tabPage3.Location = new System.Drawing.Point(4, 32);
            this.tabPage3.Name = "tabPage3";
            this.tabPage3.Padding = new System.Windows.Forms.Padding(3);
            this.tabPage3.Size = new System.Drawing.Size(728, 564);
            this.tabPage3.TabIndex = 2;
            this.tabPage3.Text = "AFE Registers";
            this.tabPage3.UseVisualStyleBackColor = true;
            // 
            // led2CurrentLabel
            // 
            this.led2CurrentLabel.AutoSize = true;
            this.led2CurrentLabel.Location = new System.Drawing.Point(4, 54);
            this.led2CurrentLabel.Name = "led2CurrentLabel";
            this.led2CurrentLabel.Size = new System.Drawing.Size(145, 20);
            this.led2CurrentLabel.TabIndex = 3;
            this.led2CurrentLabel.Text = "LED2 Current (mA)";
            // 
            // led2CurrentNumericUpDown
            // 
            this.led2CurrentNumericUpDown.Increment = new decimal(new int[] {
            8,
            0,
            0,
            65536});
            this.led2CurrentNumericUpDown.Location = new System.Drawing.Point(15, 79);
            this.led2CurrentNumericUpDown.Maximum = new decimal(new int[] {
            50,
            0,
            0,
            0});
            this.led2CurrentNumericUpDown.Name = "led2CurrentNumericUpDown";
            this.led2CurrentNumericUpDown.Size = new System.Drawing.Size(120, 26);
            this.led2CurrentNumericUpDown.TabIndex = 2;
            // 
            // updateRegistersButton
            // 
            this.updateRegistersButton.Location = new System.Drawing.Point(284, 144);
            this.updateRegistersButton.Name = "updateRegistersButton";
            this.updateRegistersButton.Size = new System.Drawing.Size(151, 30);
            this.updateRegistersButton.TabIndex = 1;
            this.updateRegistersButton.Text = "Update Registers";
            this.updateRegistersButton.UseVisualStyleBackColor = true;
            this.updateRegistersButton.Click += new System.EventHandler(this.updateRegistersButton_Click);
            // 
            // tiaGainComboBox
            // 
            this.tiaGainComboBox.FormattingEnabled = true;
            this.tiaGainComboBox.Items.AddRange(new object[] {
            "500 K",
            "250 K",
            "100 K",
            "50 K",
            "25 K",
            "10 K",
            "1 M",
            "2 M"});
            this.tiaGainComboBox.Location = new System.Drawing.Point(298, 79);
            this.tiaGainComboBox.Name = "tiaGainComboBox";
            this.tiaGainComboBox.Size = new System.Drawing.Size(121, 28);
            this.tiaGainComboBox.TabIndex = 4;
            // 
            // tiaGainLabel
            // 
            this.tiaGainLabel.AutoSize = true;
            this.tiaGainLabel.Location = new System.Drawing.Point(321, 56);
            this.tiaGainLabel.Name = "tiaGainLabel";
            this.tiaGainLabel.Size = new System.Drawing.Size(72, 20);
            this.tiaGainLabel.TabIndex = 5;
            this.tiaGainLabel.Text = "TIA Gain";
            // 
            // tiaCapComboBox
            // 
            this.tiaCapComboBox.FormattingEnabled = true;
            this.tiaCapComboBox.Items.AddRange(new object[] {
            "5 pF",
            "2.5 pF",
            "10 pF",
            "7.5 pF",
            "20 pF",
            "17.5 pF",
            "25 pF",
            "22.5 pF"});
            this.tiaCapComboBox.Location = new System.Drawing.Point(588, 79);
            this.tiaCapComboBox.Name = "tiaCapComboBox";
            this.tiaCapComboBox.Size = new System.Drawing.Size(121, 28);
            this.tiaCapComboBox.TabIndex = 1;
            // 
            // tiaCapLabel
            // 
            this.tiaCapLabel.AutoSize = true;
            this.tiaCapLabel.Location = new System.Drawing.Point(582, 54);
            this.tiaCapLabel.Name = "tiaCapLabel";
            this.tiaCapLabel.Size = new System.Drawing.Size(127, 20);
            this.tiaCapLabel.TabIndex = 6;
            this.tiaCapLabel.Text = "TIA Capacitance";
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(741, 648);
            this.Controls.Add(this.tabControl1);
            this.ForeColor = System.Drawing.SystemColors.Desktop;
            this.Margin = new System.Windows.Forms.Padding(2);
            this.Name = "Form1";
            this.Text = "AFE4404";
            this.tabControl1.ResumeLayout(false);
            this.tabPage1.ResumeLayout(false);
            ((System.ComponentModel.ISupportInitialize)(this.chart1)).EndInit();
            this.tabPage2.ResumeLayout(false);
            this.tabPage2.PerformLayout();
            this.tabPage3.ResumeLayout(false);
            this.tabPage3.PerformLayout();
            ((System.ComponentModel.ISupportInitialize)(this.led2CurrentNumericUpDown)).EndInit();
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.TabControl tabControl1;
        private System.Windows.Forms.TabPage tabPage1;
        private System.Windows.Forms.TabPage tabPage2;
        private System.Windows.Forms.DataVisualization.Charting.Chart chart1;
        private System.Windows.Forms.ComboBox comPortComboBox;
        private System.Windows.Forms.Button Start;
        private System.Windows.Forms.Button Save;
        private System.Windows.Forms.ComboBox baudRateComboBox;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.TabPage tabPage3;
        private System.Windows.Forms.NumericUpDown led2CurrentNumericUpDown;
        private System.Windows.Forms.Button updateRegistersButton;
        private System.Windows.Forms.Label led2CurrentLabel;
        private System.Windows.Forms.Label tiaGainLabel;
        private System.Windows.Forms.ComboBox tiaGainComboBox;
        private System.Windows.Forms.Label tiaCapLabel;
        private System.Windows.Forms.ComboBox tiaCapComboBox;
    }
}


Please log in to post comments.