SPI or I2C to UART Bridge

Dependents:   SC16IS750_Test mbed_SC16IS750 Xadow_SC16IS750_Test Xadow_MPU9150AHRS

SC16IS750.cpp

Committer:
wim
Date:
2014-01-22
Revision:
0:d64854a60f95
Child:
1:0440152c5387

File content as of revision 0:d64854a60f95:

/* SC16IS750 interface 
 *   /////////////////////v1.0 Tedd OKANO, 18 Jul 2012, I2C I/F only, MIT License
 *   v1.1 WH, Nov 2013, Added SPI I/F and more methods, MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include    "mbed.h"
#include    "SC16IS750.h"


/** Abstract class SC16IS750 for converter between either SPI or I2C and a Serial port
  *
  * Supports both SPI and I2C interfaces through derived classes
  *
  * @code
  *
  * @endcode
  */
//SC16IS750::SC16IS750() : Serial(NC, NC) {   //Fout ???
SC16IS750::SC16IS750() {  
  init();  // initialise UART registers
}


/** Set baudrate of the serial port.    
  *  @param  baud integer baudrate (4800, 9600 etc)
  *  @return none
  */
void SC16IS750::baud(int baudrate) {
  unsigned long divisor = BAUD_RATE_DIVISOR(baudrate);
  char lcr_tmp;
  
  _config.baudrate = baudrate;               // Save baudrate

  lcr_tmp = this->readRegister(LCR);                            // Read current LCR register
  this->writeRegister(LCR, lcr_tmp | LCR_DIV_ENA);              // Enable Divisor registers
  this->writeRegister(DLL, ( divisor       & 0xFF));            //   write divisor LSB
  this->writeRegister(DLH, ((divisor >> 8) & 0xFF));            //   write divisor MSB
  this->writeRegister(LCR, lcr_tmp);                            // Restore LCR register, activate regular RBR, THR and IER registers  
}


/** Set the transmission format used by the serial port.   
  *   @param bits      The number of bits in a word (5-8; default = 8)
  *   @param parity    The parity used (Serial::None, Serial::Odd, Serial::Even, Serial::Forced1, Serial::Forced0; default = Serial::None)
  *   @param stop_bits The number of stop bits (1 or 2; default = 1) 
  */
void SC16IS750::format(int bits, Serial::Parity parity, int stop_bits) {
  char lcr_tmp = 0x00;
  
  switch (bits) {
    case 5:  lcr_tmp |= LCR_BITS5;
             break;
    case 6:  lcr_tmp |= LCR_BITS6;
             break;
    case 7:  lcr_tmp |= LCR_BITS7;
             break;
    case 8:  lcr_tmp |= LCR_BITS8;
             break;
    default: lcr_tmp |= LCR_BITS8;     
  }

  switch (parity) {
    case Serial::None:    lcr_tmp |= LCR_NONE;
                          break;
    case Serial::Odd:     lcr_tmp |= LCR_ODD;
                          break;
    case Serial::Even:    lcr_tmp |= LCR_EVEN;
                          break;
    case Serial::Forced1: lcr_tmp |= LCR_FORCED1;
                          break;
    case Serial::Forced0: lcr_tmp |= LCR_FORCED0;
                          break;                      
    default:              lcr_tmp |= LCR_NONE;     
  }

  switch (stop_bits) {
    case 1:  lcr_tmp |= LCR_BITS1;
             break;
    case 2:  lcr_tmp |= LCR_BITS2;
             break;
    default: lcr_tmp |= LCR_BITS1;     
  }

  _config.dataformat = lcr_tmp;      // Save dataformat   

  this->writeRegister(LCR, lcr_tmp); // Set LCR register, activate regular RBR, THR and IER registers  

};


/**
  * Initialise the UART.
  *
  * If initialisation fails this method does not return.
  */
void SC16IS750::init() {

  // Initialise SC16IS750

  // Set default baudrate and save in _config
  baud();

  // Set dataflow and save in _config
  // We need to enable flow control or we overflow buffers and
  // lose data when used with the WiFly. Note that flow control 
  // needs to be enabled on the WiFly for this to work but it's
  // possible to do that with flow control enabled here but not there.
  // TODO: Make this able to be configured externally?
  _config.flowctrl = EFR_ENABLE_CTS | EFR_ENABLE_RTS | EFR_ENABLE_ENHANCED_FUNCTIONS,

  this->writeRegister(LCR, 0xBF); // access EFR register
  this->writeRegister(EFR, _config.flowctrl); // enable enhanced registers
  
  // Set default dataformat and save in _config
  format();  

  // Set default fifoformat and save in _config
  this->writeRegister(FCR, 0x06); // reset TXFIFO, reset RXFIFO, non FIFO mode
  this->writeRegister(FCR, 0x01); // enable FIFO mode   

  // The UART bridge should now be successfully initialised.


  // Test if UART bridge is present and initialised
  if(!connected()){ 
#if(0)  
    // Lock up if we fail to initialise UART bridge.
    while(1) {
    };
#else    
    printf("Failed to initialise UART bridge\r\n");    
  }
#endif

}


/**
  * Check that UART is connected and operational.
  *  @param  none
  *  @return bool true when connected, false otherwise
  */
bool SC16IS750::connected() {
  // Perform read/write test to check if UART is working
  const char TEST_CHARACTER = 'H';

  this->writeRegister(SPR, TEST_CHARACTER);

  return (this->readRegister(SPR) == TEST_CHARACTER);
}



/** Determine if there is a character available to read.
  *   @return 1 if there is a character available to read, 0 otherwise
  */
int SC16IS750::readable() {
  /**
   * Get the number of chars (characters) available for reading.
   *
   * This is data that's already arrived and stored in the receive
   * buffer (which holds 64 chars).
   * This alternative just checks if there's data but doesn't
   * return how many characters are in the buffer:
   */
  return (this->readRegister(LSR) & 0x01);
}

/** Determine if how many characters available to read.
  *   @return int Characters available to read
  */
int SC16IS750::readableCount() {
  /*
   * Get the number of chars (characters) available for reading.
   *
   * This is data that's already arrived and stored in the receive
   * buffer (which holds 64 chars).
   */

  return (this->readRegister(RXLVL));
}

/** Determine if there is space available to write a character.    
  *   @return 1 if there is a space for a character to write, 0 otherwise
  */
int SC16IS750::writable() {
  return (this->writableCount() > 0);  // Check datasheet for faster version
}

/** Determine if how many characters available to write.
  *   @return int Characters available to write
  */
int SC16IS750::writableCount() {
  /*
   * Get the number of chars (characters) available for reading.
   *
   * This is data that's already stored in the transmit
   * buffer (which holds 64 chars).
   */

  return (this->readRegister(TXLVL));
//  return (readRegister(64 - TXLVL));  //Check datasheet
}



char SC16IS750::getc() {
  /*
   * Read char from UART.
   *
   * Returns char read or or -1 if no data available.
   *
   * Acts in the same manner as 'Serial.read()'.
   */

  if (!readable()) {
    return -1;
  }

  return this->readRegister(RHR);
}


void SC16IS750::putc(char value) {
  /*
   * Write char to UART.
   */

  while (this->readRegister(TXLVL) == 0) {
    // Wait for space in TX buffer
  };
  this->writeRegister(THR, value);
}


void SC16IS750::write(const char *str) {
  /*
   * Write string to UART.
   */
  write((const uint8_t *) str, strlen(str));  
  while (this->readRegister(TXLVL) < 64) {
    // Wait for empty TX buffer (slow)
    // (But apparently still not slow enough to ensure delivery.)
  };
}

#if ENABLE_BULK_TRANSFERS
void SC16IS750::write(const uint8_t *buffer, size_t size) {
  /*
  
    Write buffer to UART.
 
   */
  //select();
  //transfer(THR); // TODO: Change this when we modify register addresses? (Even though it's 0x00.) 

  while(size > 16) {
    //transfer_bulk(buffer, 16); //ringbuffer?
    size -= 16;
    buffer += 16;
  }
  //transfer_bulk(buffer, size);

  //deselect();
}
#endif

void SC16IS750::flush() {
  /*
   * Flush characters from SC16IS750 receive buffer.
   */

  // Note: This may not be the most appropriate flush approach.
  //       It might be better to just flush the UART's buffer
  //       rather than the buffer of the connected device
  //       which is essentially what this does.
  while(readable() > 0) {
    getc();
  }
}


void SC16IS750::ioSetDirection(unsigned char bits) {
  this->writeRegister(IODIR, bits);
}


void SC16IS750::ioSetState(unsigned char bits) {
  this->writeRegister(IOSTATE, bits);
}



// Begin SPI Implementation
//

/** Class SC16IS750_SPI for a converter between SPI and a Serial port
  *
  */
SC16IS750_SPI::SC16IS750_SPI (SPI *spi, PinName cs) : _spi(spi), _cs(cs)  {
  _cs = 1;  // deselect
  
  _spi->format(8, 0);          
  _spi->frequency(1000000);
 
};

/** Write value to internal register.
  * Pure virtual, must be declared in derived class.   
  *   @param register_address  The address of the Register (enum RegisterName)
  *   @param data              The 8bit value to write
  *   @return none 
  */
void SC16IS750_SPI::writeRegister(RegisterName registerAddress, char data) {
  /*
   * Write <data> char to the SC16IS750 register <registerAddress>
   */

  _cs = 0; //  select;
  _spi->write(registerAddress);
  _spi->write(data);
  _cs = 1; //  deselect;
  
  
//Test only  
  DigitalOut myled2(LED_GREEN);  
  myled2 = 0; //LED On
  wait(0.2);
  myled2 = 1; //LED Off
  wait(0.6);  
}


/** Read value from internal register.
  *   @param register_address  The address of the Register (enum RegisterName)
  *   @return char             The 8bit value read from the register
  */
char SC16IS750_SPI::readRegister(RegisterName registerAddress) {
  /*
   * Read char from SC16IS750 register at <registerAddress>.
   */

  // Used in SPI read operations to flush slave's shift register
  const char SPI_DUMMY_char = 0xFF; 

  char result;

  _cs = 0; //  select;
  _spi->write(SPI_READ_MODE_FLAG | registerAddress);
  result = _spi->write(SPI_DUMMY_char);
  _cs = 1; //  deselect;

  return result;  
}

//
// End SPI Implementation


// Begin I2C Implementation
//

/** Class SC16IS750_I2C for a converter between I2C and a Serial port
  *
  */
SC16IS750_I2C::SC16IS750_I2C(I2C *i2c, uint8_t deviceAddress) : _i2c(i2c), _slaveAddress(deviceAddress) {

    _i2c->frequency(400000);

}


/** Write value to internal register.
  *   @param register_address  The address of the Register (enum RegisterName)
  *   @param data              The 8bit value to write
  *   @return none 
  */
void SC16IS750_I2C::writeRegister(RegisterName registerAddress, char data) {
  char w[2];

  w[0] = registerAddress;
  w[1] = data;

  _i2c->write( _slaveAddress, w, 2 );
}


/** Read value from internal register.
  *   @param register_address  The address of the Register (enum RegisterName)
  *   @return char             The 8bit value read from the register
  */
char SC16IS750_I2C::readRegister(RegisterName registerAddress) {
  /*
   * Read char from SC16IS750 register at <registerAddress>.
   */
   char w[1];
   char r[1];
    
   w[0] = registerAddress;
    
   _i2c->write( _slaveAddress, w, 1 );
   _i2c->read( _slaveAddress, r, 1 );

   return ( r[0] );
}


//
// End I2C Implementation