Friday, November 23, 2018

Unboxing Particle's Mesh Network IoT Series (Boron and Xenon)

In this video we unbox Particle's new IoT Mesh Network series (Argon, Boron, Xenon). We take a look both the hardware and the software that allows you to easily create a cloud connected mesh network. Link to product page: https://www.particle.io/mesh




//******************Code from Video*****************************************
// -----------------------------------
// Controlling LEDs over the Internet
// -----------------------------------

// First, let's create our "shorthand" for the pins
// Same as in the Blink an LED example:
// led1 is D0, led2 is D7

int led1 = D0;
int led2 = D7;

// Last time, we only needed to declare pins in the setup function.
// This time, we are also going to register our Particle function

void setup()
{

   // Here's the pin configuration, same as last time
   pinMode(led1, OUTPUT);
   pinMode(led2, OUTPUT);

   // We are also going to declare a Particle.function so that we can turn the LED on and off from the cloud.
   Particle.function("led",ledToggle);
   // This is saying that when we ask the cloud for the function "led", it will employ the function ledToggle() from this app.

   // For good measure, let's also make sure both LEDs are off when we start:
   digitalWrite(led1, LOW);
   digitalWrite(led2, LOW);

}


// Last time, we wanted to continously blink the LED on and off
// Since we're waiting for input through the cloud this time,
// we don't actually need to put anything in the loop

void loop()
{
   // Nothing to do here
}

// We're going to have a super cool function now that gets called when a matching API request is sent
// This is the ledToggle function we registered to the "led" Particle.function earlier.


int ledToggle(String command) {
    /* Particle.functions always take a string as an argument and return an integer.
    Since we can pass a string, it means that we can give the program commands on how the function should be used.
    In this case, telling the function "on" will turn the LED on and telling it "off" will turn the LED off.
    Then, the function returns a value to us to let us know what happened.
    In this case, it will return 1 for the LEDs turning on, 0 for the LEDs turning off,
    and -1 if we received a totally bogus command that didn't do anything to the LEDs.
    */

    if (command=="on") {
        digitalWrite(led1,HIGH);
        digitalWrite(led2,HIGH);
        Particle.publish("LED State", "ON");
        return 1;
    }
    else if (command=="off") {
        digitalWrite(led1,LOW);
        digitalWrite(led2,LOW);
        Particle.publish("LED State", "OFF");
        return 0;
    }
    else {
        return -1;
    }
}

Friday, November 9, 2018

Designing a Thermocouple Temperature Measurement Circuit Part 2

In this series we look at how to design a Thermocouple temperature measurement circuit. In part 2 we look at a real world example of a Thermocouple J Type circuit design and discuss some of the common sources of error and how to avoid them.





Wednesday, October 10, 2018

Designing a Thermocouple Temperature Measurement Circuit Part 1

In this series we look at how to design a Thermocouple temperature measurement circuit. In part 1 we look at Thermocouple theory, pros and cons versus other temperature measurement techniques, and an overview of a measurement hardware circuit as well as calculations done in software.




Wednesday, September 26, 2018

Easy Way to Create a Wireless Sensor Network

In this video we look at an easy way with not very much code to setup a wireless network using the nRF24L01 Transceiver and Arduino.



//***************************Master or Receiver code*****************
/*This code was used for a video tutorial on the ForceTronics YouTube Channel
 * This code is free and open for anybody to use and modify at your own risk
*/

#include <SPI.h> //Call SPI library so you can communicate with the nRF24L01+
#include <nRF24L01.h> //nRF2401 libarary found at https://github.com/tmrh20/RF24/
#include <RF24.h> //nRF2401 libarary found at https://github.com/tmrh20/RF24/

const uint8_t pinCE = 9; //This pin is used to set the nRF24 to standby (0) or active mode (1)
const uint8_t pinCSN = 10; //This pin is used for SPI comm chip select
RF24 wirelessSPI(pinCE, pinCSN); // Declare object from nRF24 library (Create your wireless SPI) 
const uint64_t rAddress = 0xB00B1E50C3LL;  //Create pipe address for the network and notice I spelled boobies because I am mature, the "LL" is for LongLong type
const uint8_t rFChan = 89; //Set channel frequency default (chan 84 is 2.484GHz to 2.489GHz)

//Create a structure to hold fake sensor data and channel data
struct PayLoad {
  uint8_t chan;
  uint8_t sensor;
};

PayLoad payload; //create struct object

void setup() {
  wirelessSPI.begin();  //Start the nRF24 module
  wirelessSPI.setChannel(rFChan); //set communication frequency channel
  wirelessSPI.openReadingPipe(1,rAddress);  //This is receiver or master so we need to be ready to read data from transmitters
  wirelessSPI.startListening();    // Start listening for messages
  Serial.begin(115200);  //serial port to display received data
  Serial.println("Network master is online...");
}

void loop() {
  if(wirelessSPI.available()){ //Check if recieved data
     wirelessSPI.read(&payload, sizeof(payload)); //read packet of data and store it in struct object
     Serial.print("Received data packet from node: ");
     Serial.println(payload.chan); //print node number or channel
     Serial.print("Node sensor value is: ");
     Serial.println(payload.sensor); //print node's sensor value
     Serial.println(); 
  }
}

//***************************Node or Transmitter code*****************
/*This code was used for a video tutorial on the ForceTronics YouTube Channel
 * This code is free and open for anybody to use and modify at your own risk
*/

#include <SPI.h> //Call SPI library so you can communicate with the nRF24L01+
#include <nRF24L01.h> //nRF2401 libarary found at https://github.com/tmrh20/RF24/
#include <RF24.h> //nRF2401 libarary found at https://github.com/tmrh20/RF24/

const uint8_t pinCE = 9; //This pin is used to set the nRF24 to standby (0) or active mode (1)
const uint8_t pinCSN = 10; //This pin is used to tell the nRF24 whether the SPI communication is a command
RF24 wirelessSPI(pinCE, pinCSN); // Declare object from nRF24 library (Create your wireless SPI) 
const uint64_t wAddress = 0xB00B1E50C3LL;  //Create pipe address to send data, the "LL" is for LongLong type
const uint8_t rFChan = 89; //Set channel default (chan 84 is 2.484GHz to 2.489GHz)
const uint8_t rDelay = 7; //this is based on 250us increments, 0 is 250us so 7 is 2 ms
const uint8_t rNum = 5; //number of retries that will be attempted 
const uint8_t chan1 = 2; //D2 pin for node channel check
const uint8_t chan2 = 3; //D3 pin for node channel check
const uint8_t chan3 = 4; //D4 pin for node channel check

//stuct of payload to send fake sensor data and node channel
struct PayLoad {
  uint8_t chan;
  uint8_t sensor;
};

PayLoad payload; //create struct object

void setup() {
  pinMode(chan1,INPUT_PULLUP); //set channel select digital pins to input pullup
  pinMode(chan2,INPUT_PULLUP);
  pinMode(chan3,INPUT_PULLUP);
  wirelessSPI.begin();  //Start the nRF24 module
  wirelessSPI.setChannel(rFChan); 
  wirelessSPI.setRetries(rDelay,rNum); //if a transmit fails to reach receiver (no ack packet) then this sets retry attempts and delay between retries   
  wirelessSPI.openWritingPipe(wAddress); //open writing or transmit pipe
  wirelessSPI.stopListening(); //go into transmit mode
  randomSeed(analogRead(0)); //set random seed for fake sensor data
  setChannel();  //checks current channel setting for transceiver
}

void loop() {
  delay(3000); //send data every 3 seconds
  payload.sensor = random(0,255); //get made up sensor value
  if (!wirelessSPI.write(&payload, sizeof(payload))){  //send data and remember it will retry if it fails
    delay(random(5,20)); //as another back up, delay for a random amount of time and try again
    if (!wirelessSPI.write(&payload, sizeof(payload))){
      //set error flag if it fails again
    }
  }

}

//check for low digital pin to set node address
void setChannel() {
  if(!digitalRead(chan1)) payload.chan = 1;
  else if(!digitalRead(chan2)) payload.chan = 2;
  else if(!digitalRead(chan3)) payload.chan = 3;
  else payload.chan = 0;
}

Sunday, August 19, 2018

Building a UART to USB Memory Stick / Drive Bridge

In this video look at how to create a bridge that takes data from the UART port and writes it to a file on a USB thumb drive (BOMS device). For this project we will use the FT900 microcontroller from FTDI / Bridgetek.




/* Code from the video***********************************************************
 * This is code was used for a Tutorial on the ForceTronics YouTube Channel on writing UART data to a file on a USB thumb drive
 * Most of this code was leveraged from the FTDI / Bridgetek example program called "USBH Example File System" it was written for the FT900
 * family of microcontrollers from FTDI / Bridgetek. This code is free and open for any to use or modify at their own risk
 * */

//all these includes come from the example "USBH Example File System" made by FTDI
#include <stdint.h>
#include <string.h>

#include <ft900.h>
#include <ft900_usb.h>
#include <ft900_usbh_boms.h>
#include <ft900_startup_dfu.h>

// UART support for printf output
#include "tinyprintf.h"

// Support for FATFS 
#include "ff.h"
#include "diskio.h"

/* CONSTANTS ***********************************************************************/

#define EXAMPLE_FILE "FTronics.TXT"
// Change this value in order to change the size of the buffer being used
#define RINGBUFFER_SIZE (4096)

/* GLOBAL VARIABLES ****************************************************************/

// File system handle for fatfs.
FATFS fs;
// Context structure and handle for BOMS device.
USBH_BOMS_context bomsCtx;
USBH_interface_handle hOpenDisk = 0;
USBH_device_handle hRootDev;
unsigned long fPosition = 0; //This variable is used to track the position in the file where the last data was written
volatile unsigned int mTimer = 0; //This variable was used to test how long it took to write data to the file
typedef struct //struct for buffer that stores UART data before it is written to file
{
    uint8_t     data[RINGBUFFER_SIZE];
    uint16_t    wr_idx;
    uint16_t    rd_idx;
} RingBuffer_t;

// Receiver buffer
static RingBuffer_t uart0BufferIn = { {0}, 0, 0 };

/* LOCAL FUNCTIONS / INLINES *******************************************************/
DSTATUS disk_initialize(BYTE pdrv);
DSTATUS disk_status(BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
DWORD get_fattime(void);

/** @name tfp_putc
 *  @details Machine dependent putc function for tfp_printf (tinyprintf) library.
 *  @param p Parameters (machine dependent)
 *  @param c The character to write
 */
void tfp_putc(void* p, char c)
{
    uart_write((ft900_uart_regs_t*)p, (uint8_t)c);
}

void ISR_timer(void) //this interrupt fires every ten milliseconds to handle USB functions
{
    if (timer_is_interrupted(timer_select_a))
    {
    // Call USB timer handler to action transaction and hub timers.
    USBH_timer();
    mTimer++;
    }
}

/* FatFS Functions ******************/

/** Initialise a drive
 *  @param pdrv Physical Drive number
 *  @return Disk Status */
DSTATUS disk_initialize(BYTE pdrv)
{
DSTATUS stat = 0;
int8_t status;

    status = USBH_BOMS_init(hOpenDisk, 0, &bomsCtx);

    if (status != USBH_BOMS_OK)
    {
    tfp_printf("BOMS device could not be initialised: %d\r\n", status);
stat = STA_NOINIT;
hOpenDisk = 0;
    }

return stat;
}

/** Disk Status
 *  @param pdrv Physical Drive number
 *  @return Disk Status */
DSTATUS disk_status(BYTE pdrv)
{
DSTATUS stat = 0;
    
if (0 == hOpenDisk)
    {
stat |= STA_NOINIT;
    }
        
//TODO: perform a GetSense SCSI command to make sure it's there
    if (USBH_BOMS_status(&bomsCtx) != USBH_BOMS_OK)
    {
        stat |= STA_NODISK;
    }

return stat;
}

/** Read sector(s) from disk
 *  @param pdrv Physical Drive number
 *  @param buff Data buffer to store into
 *  @param sector The logical sector address
 *  @param count The number of sectors to read
 *  @return Disk Status */
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count)
{
DRESULT res = RES_OK;

if (USBH_BOMS_read(&bomsCtx, sector, USBH_BOMS_BLOCK_SIZE * count, buff) != USBH_BOMS_OK)
    {
res = RES_ERROR;
    }

return res;
}

#if _USE_WRITE
/** Write sector(s) to the disk
 *  @param pdrv Physical Drive number
 *  @param buff Data buffer to write to the disk
 *  @param sector The logical sector address
 *  @param count The number of sectors to write
 *  @return Disk Status */
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count)
{
DRESULT res = RES_OK;

if (USBH_BOMS_write(&bomsCtx, sector, USBH_BOMS_BLOCK_SIZE * count, (uint8_t *)buff) != USBH_BOMS_OK)
{
res = RES_ERROR;
    }

return res;
}
#endif

#if _USE_IOCTL
/** Disk IO Control
 *  @param pdrv Physical Drive Number
 *  @param cmd Control Code
 *  @param buff Buffer to send/receive control data 
 *  @return Disk Status */
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff)
{
DRESULT res = RES_OK;

    /* Not Supported */

return res;
}
#endif

#if _FS_READONLY == 0
/** Get the current time
 *  @return The time in the following format:
 *          bit[31:25] = Year from 1980 (0..127),
 *          bit[24:21] = Month (1..12),
 *          bit[20:16] = Day of the Month (1..31),
 *          bit[15:11] = Hour (0..23),
 *          bit[10:5]  = Minute (0..59),
 *          bit[4..0]  = Second / 2 (0..29) */
DWORD get_fattime(void)
{
    return 0; /* Invalid timestamp */
}
#endif

/**
 See how much data is available in the UART0 ring buffer

 @return The number of bytes available
 */
uint16_t uart0Available(void)
{
    int16_t diff = uart0BufferIn.wr_idx - uart0BufferIn.rd_idx;

    if (diff < 0)
    {
        diff += RINGBUFFER_SIZE;
    }

    return (uint16_t)diff;
}

/**
 Receive a number of bytes from UART0

 @return The number of bytes read from UART0
 */
uint16_t uart0Rx(uint8_t *data, uint16_t len)
{
    uint16_t avail = 0;
    uint16_t copied = 0;

    avail = uart0Available();

    /* Copy in as much data as we can ...
       This can be either the maximum size of the buffer being given
       or the maximum number of bytes available in the Serial Port
       buffer */
    while(len-- && avail--)
    {
        *data = uart0BufferIn.data[uart0BufferIn.rd_idx];
        data++;

        /* Increment the pointer and wrap around */
        uart0BufferIn.rd_idx++;
        if (uart0BufferIn.rd_idx == RINGBUFFER_SIZE) uart0BufferIn.rd_idx = 0;

        copied++;
    }

    /* Report back how many bytes have been copied into the buffer...*/
    return copied;
}



/**
 The Interrupt which handles asynchronous transmission and reception
 of data into the ring buffer
 */
void uart0ISR()
{
    static uint8_t c;

    /* Receive interrupt... */
    if (uart_is_interrupted(UART0, uart_interrupt_rx))
    {
        /* Read a byte into the Ring Buffer... */
        uart_read(UART0, &c);

        uart0BufferIn.data[uart0BufferIn.wr_idx] = c;

        /* Increment the pointer and wrap around */
        uart0BufferIn.wr_idx++;
        if (uart0BufferIn.wr_idx == RINGBUFFER_SIZE) uart0BufferIn.wr_idx = 0;

        /* Check to see if we have hit the back of the buffer... */
        if (uart0BufferIn.wr_idx == uart0BufferIn.rd_idx)
        {
            /* Increment the pointer and wrap around */
            uart0BufferIn.rd_idx++;
            if (uart0BufferIn.rd_idx == RINGBUFFER_SIZE) uart0BufferIn.rd_idx = 0;
        }
    }
}

//this function mounts file system that the file is written to
int8_t mountFileSystem(USBH_interface_handle hBOMS)
{
    // Initialise FatFS
    hOpenDisk = hBOMS; //this gets USB "interface" handle
    //Would like to make this a one time action
    if (f_mount(&fs, "", 0) != FR_OK) //mounts file system, needed to open file I am assuming
    {
    tfp_printf("Unable to mount File System\r\n");
    return -1;
    }

    tfp_printf("\r\n\r\n"); delayms(1000);

    return 0;
}

//This function creates the file that we are storing on the USB drive
//If the file already exists it will be deleted and a new one is created
int8_t createFile(USBH_interface_handle hBOMS) {
// Initialise FatFS
    hOpenDisk = hBOMS; //this gets USB "interface" handle
    FRESULT res; //file data type, that is an enumeration with states of the file interactions
    FIL f; //another file data type enum with file conditions
    // Check to see if the example file is there.
    //This code checks to see if file already exists
    res = f_stat(EXAMPLE_FILE, NULL);
    if (FR_OK == res)
    {
        tfp_printf("File " EXAMPLE_FILE " already exists. Deleting\r\n");
        if (FR_OK != f_unlink(EXAMPLE_FILE))
        {
        tfp_printf("Problem deleting " EXAMPLE_FILE "\r\n");
        return 0;
        }
    }

    res = f_open(&f, EXAMPLE_FILE, FA_CREATE_NEW);

    if (FR_OK != res)
    {
    tfp_printf("Problem creating file " EXAMPLE_FILE "\r\n");
    return 0;
    }

    tfp_printf( "Closing " EXAMPLE_FILE "\r\n");
        if (FR_OK != f_close(&f)) //This is where the file is closed after writing
        {
        tfp_printf("Error closing " EXAMPLE_FILE "\r\n");
        }

tfp_printf("\r\n\r\n"); delayms(1000);
return 1;
}

//funciton used to write data to the file. the data pointer is for the data you want to write
//this bCount variable is for how much data
int8_t writeDataToFile(USBH_interface_handle hBOMS, uint8_t* data, uint16_t bCount)
{
    FRESULT res; //file data type, that is an enumeration with states of the file interactions
    FIL f; //another file data type enum with file conditions
    UINT towrite, written;

    // Initialise FatFS
    hOpenDisk = hBOMS; //this gets USB "interface" handle which I am guessing is information on the USB device we are working with

    // Write some data to the USB memory stick, this is where we open file to write to
   // tfp_printf( "Opening " EXAMPLE_FILE " for writing\r\n");
    res = f_open(&f, EXAMPLE_FILE, FA_WRITE | FA_OPEN_EXISTING);

    if (FR_OK != res)
    {
    tfp_printf("Problem opening file " EXAMPLE_FILE "\r\n");
    }

    f_lseek (&f, fPosition); //change writing position of the file???

    towrite = bCount;
    written = 0;
    //this is where we write the data to the file. It seems if you mess up setting up USB stuff this is where we fail
    while(towrite)
    {
        f_write(&f, data, towrite, &written);

        towrite -= written;
        data += written;

        tfp_printf("Wrote %d bytes\r\n", written);
    }

    fPosition = fPosition + written;

    tfp_printf( "Closing " EXAMPLE_FILE "\r\n");
    if (FR_OK != f_close(&f)) //This is where the file is closed after writing
    {
    tfp_printf("Error closing " EXAMPLE_FILE "\r\n");
    }

    return 0;
}

//This funciton will read data from file. It is not used in this example and was left in for debugging purposes
int8_t readFromFile(USBH_interface_handle hBOMS)
{
    FIL f; //another file data type enum with file conditions
    UINT read;
    uint8_t* buffer[512]; //buffer for reading file on USB BOMS

    // Initialise FatFS
    hOpenDisk = hBOMS; //this gets USB "interface" handle which I am guessing is information on the USB device we are working with

    //this is where the / a file is open and read
    tfp_printf( "Opening " EXAMPLE_FILE " for reading\r\n\r\n");
    if (FR_OK != f_open(&f, EXAMPLE_FILE, FA_READ))
    {
    tfp_printf("Error opening " EXAMPLE_FILE " for reading\r\n");
    }

    do
    {
        f_read(&f, buffer, 128, &read);
        uart_writen(UART0, (uint8_t *)buffer, read);
    }
    while(read == 128);


    tfp_printf( "\r\n" "Closing " EXAMPLE_FILE "\r\n");
    f_close(&f);


    tfp_printf("\r\n\r\n");

    return 0;
}

//this function does all the setup work for the connected USB drive
USBH_interface_handle doAllUSBStuff(){
uint8_t status;
USBH_STATE connect;
USBH_interface_handle hInterface;
uint8_t usbClass;
uint8_t usbSubclass;
uint8_t usbProtocol;

//First USB action, initialize. There is no function for this so is it part of the USBH library
//This is part of FT900 USB library, notes from doc:Performs a software reset and initialises the USB hardware
USBH_initialise(NULL);

//Second step: Determine if a hub port has a downstream connection.
//Select a hub and a port to query. For the root hub the handle will be NULL and the port zero
//checks if there are multiple ports, and sets if there is a connection
USBH_get_connect_state(USBH_ROOT_HUB_HANDLE, USBH_ROOT_HUB_PORT, &connect);
if (connect == USBH_STATE_NOTCONNECTED)
{
tfp_printf("\r\nPlease plug in a USB Device\r\n");

// You only enter this loop if there is no USB device detected. basically loops until you plug one in
do //looks like this is only called if the first port call fails
{
//Third step: To be continuously called by the user application. Checks for asynchronous transfer completions
//and root hub events. When a root hub connection is detected then the enumeration routine is called automatically.
//There is no requirement to call USBH_enumerate if USBH_process is called periodically.
status = USBH_process();
USBH_get_connect_state(USBH_ROOT_HUB_HANDLE, USBH_ROOT_HUB_PORT, &connect);
} while (connect == USBH_STATE_NOTCONNECTED);
}
tfp_printf("\r\nUSB Device Detected\r\n");

do{ //this loop continues until USB BOMS device is enumerated
status = USBH_process(); //third step, see description above
//USBH_process combined with get connected state does the enumeration
USBH_get_connect_state(USBH_ROOT_HUB_HANDLE, USBH_ROOT_HUB_PORT, &connect);
} while (connect != USBH_STATE_ENUMERATED);

tfp_printf("USB Device Enumerated\r\n");

// Get the first device (device on root hub)
//Step four: Get device list. Get the first child device of a device. The function will return a handle to a device if there are one
//or more child devices. For devices on the root hub the handle is set to USBH_ROOT_HUB_HANDLE.If there are no interfaces then a NULL is returned.
status = USBH_get_device_list(USBH_ROOT_HUB_HANDLE, &hRootDev);

if (status != USBH_OK)
{
// Report the error code.
tfp_printf("%d\r\n", status);
}
else //this is where hub_scan_for_boms function was orginally called in USB test function
{
    //this is a repeated function call (step 4), why do that? probably just to get status data in this function rather than
    //passing it from previous function
    status = USBH_get_interface_list(hRootDev, &hInterface);

    //step 5: Get interface class, subclass and protocol. Get the class information of an interface.
    //This function basically gets the USB characteristics of the attached device
    if (USBH_interface_get_class_info(hInterface, &usbClass, &usbSubclass, &usbProtocol) == USBH_OK)
       {
    if ((usbClass == USB_CLASS_MASS_STORAGE) && //this if statement is checking all the parameters of the previous function
           (usbSubclass == USB_SUBCLASS_MASS_STORAGE_SCSI) &&
            (usbProtocol == USB_PROTOCOL_MASS_STORAGE_BOMS))
          {
          tfp_printf("BOMS device found at level %d\r\n", 1);
          //at this point we have established the connected USB device and we are moving to the file interactions
          //fs_testing(hInterface,"this better print to a mofo text file");
          mountFileSystem(hInterface);
          }
    else {
    tfp_printf("Problem creating and writing to file");
    }
       }
    else {
        tfp_printf("Problem getting USB information");
     }

}

return hInterface;
}

void setup()
{
    /* Check for a USB device connection and initiate a DFU firmware download or
       upload operation. This will timeout and return control here if no host PC
       program contacts the device's DFU interace. USB device mode is disabled
       before returning.
    */
    STARTUP_DFU();

    /* Enable the UART Device... */
        sys_enable(sys_device_uart0);
        /* Make GPIO48 function as UART0_TXD and GPIO49 function as UART0_RXD... */
        gpio_function(48, pad_uart0_txd); /* UART0 TXD */
        gpio_function(49, pad_uart0_rxd); /* UART0 RXD */
        uart_open(UART0,                    /* Device */
                  1,                        /* Prescaler = 1 */
                  UART_DIVIDER_115200_BAUD,  /* Divider = 1302 */
                  uart_data_bits_8,         /* No. Data Bits */
                  uart_parity_none,         /* Parity */
                  uart_stop_bits_1);        /* No. Stop Bits */

        // Enable tfp_printf() functionality...
        init_printf(UART0, tfp_putc);

        sys_enable(sys_device_timer_wdt);

        interrupt_attach(interrupt_timers, (int8_t)interrupt_timers, ISR_timer);

        // Timer A = 1ms
        timer_prescaler(1000);
        timer_init(timer_select_a, 1000, timer_direction_down, timer_prescaler_select_on, timer_mode_continuous);
        timer_enable_interrupt(timer_select_a);
        timer_start(timer_select_a);

        uart_disable_interrupt(UART0, uart_interrupt_tx);
        // Enable the UART to fire interrupts when receiving data...
        uart_enable_interrupt(UART0, uart_interrupt_rx);
        // Attach the interrupt so it can be called...
        interrupt_attach(interrupt_uart0, (uint8_t) interrupt_uart0, uart0ISR);
        // Enable interrupts to be fired...
        uart_enable_interrupts_globally(UART0);
        interrupt_enable_globally();

        hOpenDisk = doAllUSBStuff();
        createFile(hOpenDisk);

}

int main(int argc, char *argv[])
{
setup(); //run code to setup USB and UART communication
fPosition = 0; //reset the file position

while(1) { //loop forever
mTimer = 0; //reset 10 msec timer variable
if (uart0Available()) {
uint8_t buffer[RINGBUFFER_SIZE] = {0};
uint16_t read_bytes = uart0Rx(buffer, RINGBUFFER_SIZE);
writeDataToFile(hOpenDisk, buffer, read_bytes);
tfp_printf("timer value is %d\r\n", mTimer*10);
}

  }

    interrupt_detach(interrupt_timers);
    interrupt_disable_globally();
    sys_disable(sys_device_timer_wdt);
}



Sunday, July 15, 2018

Speeding up the ADC on Arduino SAMD21 Boards (Zero, Mkr, etc) Part 2

In this video we look at how to get higher ADC speeds out of Arduino boards that are based off of the SAMD21 microcontroller. In part 2 we discuss memory limitations and we leverage an Adafruit library to do an FFT on the ADC data.


Link to details on PCBWay Maker Contest: ttps://www.pcbway.com/project/PCB_DESIGN_CONTEST.aspx

//*******************Arduino Code from Video*********************************
/*This code is from a tutorial on the ForceTronics YouTube Channel that talks about speeding up the sample rate on Arduino boards 
 * that use the SAMD21 microcontroller like the Arduino Zero or MKR series. This code is free and clear for other to use and modify 
 * at their own risk. 
 */
#include "Adafruit_ZeroFFT.h" //adafruit library for FFT
#include <SPI.h>
#include <SD.h>

const long sRate = 300000; //sample rate of ADC
const int16_t dSize = 1024; //used to set number of samples
const byte chipSelect = 38; //used for SPI chip select pin
const byte gClk = 3; //used to define which generic clock we will use for ADC
const byte intPri = 0; //used to set interrupt priority for ADC
const int cDiv = 1; //divide factor for generic clock
const float period = 3.3334; //period of 300k sample rate
String wFile = "ADC_DATA"; //used as file name to store wind and GPS data
volatile int16_t aDCVal[dSize]; //array to hold ADC samples
volatile int count = 0; //tracks how many samples we have collected
bool done = false; //tracks when done writing data to SD card

void setup() {
  portSetup(); //setup the ports or pin to make ADC measurement
  genericClockSetup(gClk,cDiv); //setup generic clock and routed it to ADC
  aDCSetup(); //this function set up registers for ADC, input argument sets ADC reference
  setUpInterrupt(intPri); //sets up interrupt for ADC and argument assigns priority
  aDCSWTrigger(); //trigger ADC to start free run mode
}

void loop() {
  
  if(count==(dSize-1) and !done) { //if done reading and they have not been written to SD card yet
    removeDCOffset(aDCVal, dSize, 8); //this function removes DC offset if you are measuring an AC signal
    int16_t fTTVal[dSize]; //array to hold FFT samples
    for(int j=0; j<dSize; j++) fTTVal[j] = aDCVal[j]; //copy one array to another array
    ZeroFFT(fTTVal,dSize); //calculate FFT and store into array
    SD.begin(chipSelect); //start SD card library
    File myFile = SD.open((wFile + ".csv"), FILE_WRITE); //open file to write data to CSV file
    if (myFile) {
      float sTime = 0;
      for (int y = 0; y < dSize; y++) { //write each reading to CSV 
        myFile.print(String(FFT_BIN(y, sRate, dSize))+",");
        myFile.print(String((fTTVal[y]))+",");
        myFile.print(sTime,5); //write each reading to SD card as string
        myFile.print(",");
        myFile.println(String(aDCVal[y])+","); //write each reading to SD card as string
        sTime = sTime + period; //update signal period info
      }
    }
    myFile.close(); //close file
    done = true; //we are done 
  }
}

//function for configuring ports or pins, note that this will not use the same pin numbering scheme as Arduino
void portSetup() {
  // Input pin for ADC Arduino A0/PA02
  REG_PORT_DIRCLR1 = PORT_PA02;

  // Enable multiplexing on PA02_AIN0 PA03/ADC_VREFA
  PORT->Group[0].PINCFG[2].bit.PMUXEN = 1;
  PORT->Group[0].PINCFG[3].bit.PMUXEN = 1;
  PORT->Group[0].PMUX[1].reg = PORT_PMUX_PMUXE_B | PORT_PMUX_PMUXO_B;
}

//this function sets up the generic clock that will be used for the ADC unit
//by default it uses the 48M system clock, input arguments set divide factor for generic clock and choose which generic clock
//Note unless you understand how the clock system works use clock 3. clocks 5 and up can brick the microcontroller based on how Arduino configures things
void genericClockSetup(int clk, int dFactor) {
  // Enable the APBC clock for the ADC
  REG_PM_APBCMASK |= PM_APBCMASK_ADC;
  
  //This allows you to setup a div factor for the selected clock certain clocks allow certain division factors: Generic clock generators 3 - 8 8 division factor bits - DIV[7:0]
  GCLK->GENDIV.reg |= GCLK_GENDIV_ID(clk)| GCLK_GENDIV_DIV(dFactor);
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);  

  //configure the generator of the generic clock with 48MHz clock
  GCLK->GENCTRL.reg |= GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(clk); // GCLK_GENCTRL_DIVSEL don't need this, it makes divide based on power of two
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
  
  //enable clock, set gen clock number, and ID to where the clock goes (30 is ADC)
  GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(clk) | GCLK_CLKCTRL_ID(30);
  while (GCLK->STATUS.bit.SYNCBUSY);
}

/*
ADC_CTRLB_PRESCALER_DIV4_Val    0x0u  
ADC_CTRLB_PRESCALER_DIV8_Val    0x1u   
ADC_CTRLB_PRESCALER_DIV16_Val   0x2u   
ADC_CTRLB_PRESCALER_DIV32_Val   0x3u   
ADC_CTRLB_PRESCALER_DIV64_Val   0x4u   
ADC_CTRLB_PRESCALER_DIV128_Val  0x5u   
ADC_CTRLB_PRESCALER_DIV256_Val  0x6u   
ADC_CTRLB_PRESCALER_DIV512_Val  0x7u   
--> 8 bit ADC measurement takes 5 clock cycles, 10 bit ADC measurement takes 6 clock cycles
--> Using 48MHz system clock with division factor of 1
--> Using ADC division factor of 32
--> Sample rate = 48M / (5 x 32) = 300 KSPS
This function sets up the ADC, including setting resolution and ADC sample rate
*/
void aDCSetup() {
  // Select reference
  REG_ADC_REFCTRL = ADC_REFCTRL_REFSEL_INTVCC1; //set vref for ADC to VCC

  // Average control 1 sample, no right-shift
  REG_ADC_AVGCTRL |= ADC_AVGCTRL_SAMPLENUM_1;

  // Sampling time, no extra sampling half clock-cycles
  REG_ADC_SAMPCTRL = ADC_SAMPCTRL_SAMPLEN(0);
  
  // Input control and input scan
  REG_ADC_INPUTCTRL |= ADC_INPUTCTRL_GAIN_1X | ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_PIN0;
  // Wait for synchronization
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  ADC->CTRLB.reg |= ADC_CTRLB_RESSEL_8BIT | ADC_CTRLB_PRESCALER_DIV32 | ADC_CTRLB_FREERUN; //This is where you set the divide factor, note that the divide call has no effect until you change Arduino wire.c
  //Wait for synchronization
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  ADC->WINCTRL.reg = ADC_WINCTRL_WINMODE_DISABLE; // Disable window monitor mode
  while(ADC->STATUS.bit.SYNCBUSY);

  ADC->EVCTRL.reg |= ADC_EVCTRL_STARTEI; //start ADC when event occurs
  while (ADC->STATUS.bit.SYNCBUSY);

  ADC->CTRLA.reg |= ADC_CTRLA_ENABLE; //set ADC to run in standby
  while (ADC->STATUS.bit.SYNCBUSY);
}

//This function sets up an ADC interrupt that is triggered 
//when an ADC value is out of range of the window
//input argument is priority of interrupt (0 is highest priority)
void setUpInterrupt(byte priority) {
  
  ADC->INTENSET.reg |= ADC_INTENSET_RESRDY; // enable ADC ready interrupt
  while (ADC->STATUS.bit.SYNCBUSY);

  NVIC_EnableIRQ(ADC_IRQn); // enable ADC interrupts
  NVIC_SetPriority(ADC_IRQn, priority); //set priority of the interrupt
}

//software trigger to start ADC in free run
//in future could use this to set various ADC triggers
void aDCSWTrigger() {
  ADC->SWTRIG.reg |= ADC_SWTRIG_START;
}

//This ISR is called each time ADC makes a reading
void ADC_Handler() {
    if(count<1023) {
      aDCVal[count] = REG_ADC_RESULT;
      count++;
    }
    ADC->INTFLAG.reg = ADC_INTENSET_RESRDY; //Need to reset interrupt
}

//This function takes out DC offset of AC signal, it assumes that the offset brings signal to zero volts
//input arguments: array with measured points and bits of measurement
void removeDCOffset(volatile int16_t aDC[], int aSize, int bits) {
  int aSteps = pow(2,bits)/2; //get number of levels in ADC measurement and cut it in half
  for(int i=0; i<aSize; i++) {
    aDC[i] = aDC[i] - aSteps; //take out offset
  }
}

Saturday, June 30, 2018

Speeding up the ADC on Arduino SAMD21 Boards (Zero, Mkr, etc) P1

In this video we look at how to get higher ADC speeds out of Arduino boards that are based off of the SAMD21 microcontroller.



Windows file paths from video:
  • files that define register data structures: C:\Users\yourname\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.6.18\bootloaders\sofia\Bootloader_D21_Sofia_V2.1\src\ASF\sam0\utils\cmsis\samd21\include\component
  • wire.c file path: C:\Users\yourname\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.6.18\cores\arduino

//*******************Arduino example code from video*************************
/*This code is from a tutorial on the ForceTronics YouTube Channel that talks about speeding up the sample rate on Arduino boards 
 * that use the SAMD21 microcontroller like the Arduino Zero or MKR series. This code is free and clear for other to use and modify 
 * at their own risk. 
 */
#include <SPI.h>
#include <SD.h>

const int16_t dSize = 1024; //used to set number of samples
const byte chipSelect = 38; //used for SPI chip select pin
const byte gClk = 3; //used to define which generic clock we will use for ADC
const byte intPri = 0; //used to set interrupt priority for ADC
const int cDiv = 1; //divide factor for generic clock
const float period = 3.3334; //period of 300k sample rate
String wFile = "ADC_DATA"; //used as file name to store wind and GPS data
volatile int aDCVal[dSize]; //array to hold ADC samples
volatile int count = 0; //tracks how many samples we have collected
bool done = false; //tracks when done writing data to SD card

void setup() {
  portSetup(); //setup the ports or pin to make ADC measurement
  genericClockSetup(gClk,cDiv); //setup generic clock and routed it to ADC
  aDCSetup(); //this function set up registers for ADC, input argument sets ADC reference
  setUpInterrupt(intPri); //sets up interrupt for ADC and argument assigns priority
  aDCSWTrigger(); //trigger ADC to start free run mode
}

void loop() {
  
  if(count==(dSize-1) and !done) { //if done reading and they have not been written to SD card yet
    removeDCOffset(aDCVal, dSize, 8); //this function removes DC offset if you are measuring an AC signal
    SD.begin(chipSelect); //start SD card library
    File myFile = SD.open((wFile + ".csv"), FILE_WRITE); //open file to write data to CSV file
    if (myFile) {
      float sTime = 0;
      for (int y = 0; y < dSize; y++) {
        myFile.print(sTime,5); //write each reading to SD card as string
        myFile.print(",");
        myFile.println(String(aDCVal[y])+","); //write each reading to SD card as string
        sTime = sTime + period; //update signal period info
      }
    }
    myFile.close(); //close file
    done = true; //we are done 
  }
}

//function for configuring ports or pins, note that this will not use the same pin numbering scheme as Arduino
void portSetup() {
  // Input pin for ADC Arduino A0/PA02
  REG_PORT_DIRCLR1 = PORT_PA02;

  // Enable multiplexing on PA02_AIN0 PA03/ADC_VREFA
  PORT->Group[0].PINCFG[2].bit.PMUXEN = 1;
  PORT->Group[0].PINCFG[3].bit.PMUXEN = 1;
  PORT->Group[0].PMUX[1].reg = PORT_PMUX_PMUXE_B | PORT_PMUX_PMUXO_B;
}

//this function sets up the generic clock that will be used for the ADC unit
//by default it uses the 48M system clock, input arguments set divide factor for generic clock and choose which generic clock
//Note unless you understand how the clock system works use clock 3. clocks 5 and up can brick the microcontroller based on how Arduino configures things
void genericClockSetup(int clk, int dFactor) {
  // Enable the APBC clock for the ADC
  REG_PM_APBCMASK |= PM_APBCMASK_ADC;
  
  //This allows you to setup a div factor for the selected clock certain clocks allow certain division factors: Generic clock generators 3 - 8 8 division factor bits - DIV[7:0]
  GCLK->GENDIV.reg |= GCLK_GENDIV_ID(clk)| GCLK_GENDIV_DIV(dFactor);
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);  

  //configure the generator of the generic clock with 48MHz clock
  GCLK->GENCTRL.reg |= GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(clk); // GCLK_GENCTRL_DIVSEL don't need this, it makes divide based on power of two
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
  
  //enable clock, set gen clock number, and ID to where the clock goes (30 is ADC)
  GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(clk) | GCLK_CLKCTRL_ID(30);
  while (GCLK->STATUS.bit.SYNCBUSY);
}

/*
ADC_CTRLB_PRESCALER_DIV4_Val    0x0u  
ADC_CTRLB_PRESCALER_DIV8_Val    0x1u   
ADC_CTRLB_PRESCALER_DIV16_Val   0x2u   
ADC_CTRLB_PRESCALER_DIV32_Val   0x3u   
ADC_CTRLB_PRESCALER_DIV64_Val   0x4u   
ADC_CTRLB_PRESCALER_DIV128_Val  0x5u   
ADC_CTRLB_PRESCALER_DIV256_Val  0x6u   
ADC_CTRLB_PRESCALER_DIV512_Val  0x7u   
--> 8 bit ADC measurement takes 5 clock cycles, 10 bit ADC measurement takes 6 clock cycles
--> Using 48MHz system clock with division factor of 1
--> Using ADC division factor of 32
--> Sample rate = 48M / (5 x 32) = 300 KSPS
This function sets up the ADC, including setting resolution and ADC sample rate
*/
void aDCSetup() {
  // Select reference
  REG_ADC_REFCTRL = ADC_REFCTRL_REFSEL_INTVCC1; //set vref for ADC to VCC

  // Average control 1 sample, no right-shift
  REG_ADC_AVGCTRL |= ADC_AVGCTRL_SAMPLENUM_1;

  // Sampling time, no extra sampling half clock-cycles
  REG_ADC_SAMPCTRL = ADC_SAMPCTRL_SAMPLEN(0);
  
  // Input control and input scan
  REG_ADC_INPUTCTRL |= ADC_INPUTCTRL_GAIN_1X | ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_PIN0;
  // Wait for synchronization
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  ADC->CTRLB.reg |= ADC_CTRLB_RESSEL_8BIT | ADC_CTRLB_PRESCALER_DIV32 | ADC_CTRLB_FREERUN; //This is where you set the divide factor, note that the divide call has no effect until you change Arduino wire.c
  //Wait for synchronization
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  ADC->WINCTRL.reg = ADC_WINCTRL_WINMODE_DISABLE; // Disable window monitor mode
  while(ADC->STATUS.bit.SYNCBUSY);

  ADC->EVCTRL.reg |= ADC_EVCTRL_STARTEI; //start ADC when event occurs
  while (ADC->STATUS.bit.SYNCBUSY);

  ADC->CTRLA.reg |= ADC_CTRLA_ENABLE; //set ADC to run in standby
  while (ADC->STATUS.bit.SYNCBUSY);
}

//This function sets up an ADC interrupt that is triggered 
//when an ADC value is out of range of the window
//input argument is priority of interrupt (0 is highest priority)
void setUpInterrupt(byte priority) {
  
  ADC->INTENSET.reg |= ADC_INTENSET_RESRDY; // enable ADC ready interrupt
  while (ADC->STATUS.bit.SYNCBUSY);

  NVIC_EnableIRQ(ADC_IRQn); // enable ADC interrupts
  NVIC_SetPriority(ADC_IRQn, priority); //set priority of the interrupt
}

//software trigger to start ADC in free run
//in future could use this to set various ADC triggers
void aDCSWTrigger() {
  ADC->SWTRIG.reg |= ADC_SWTRIG_START;
}

//This ISR is called each time ADC makes a reading
void ADC_Handler() {
    if(count<1023) {
      aDCVal[count] = REG_ADC_RESULT;
      count++;
    }
    ADC->INTFLAG.reg = ADC_INTENSET_RESRDY; //Need to reset interrupt
}

//This function takes out DC offset of AC signal, it assumes that the offset brings signal to zero volts
//input arguments: array with measured points and bits of measurement
void removeDCOffset(volatile int aDC[], int aSize, int bits) {
  int aSteps = pow(2,bits)/2; //get number of levels in ADC measurement and cut it in half
  for(int i=0; i<aSize; i++) {
    aDC[i] = aDC[i] - aSteps; //take out offset
  }
}