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);
}