SPI or I2C to UART Bridge

Dependents:   SC16IS750_Test mbed_SC16IS750 Xadow_SC16IS750_Test Xadow_MPU9150AHRS

Revision:
1:0440152c5387
Parent:
0:d64854a60f95
Child:
2:76cb93b511f2
diff -r d64854a60f95 -r 0440152c5387 SC16IS750.cpp
--- a/SC16IS750.cpp	Wed Jan 22 16:39:37 2014 +0000
+++ b/SC16IS750.cpp	Sun Feb 09 14:58:06 2014 +0000
@@ -1,6 +1,5 @@
 /* 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
+ *   v0.1 WH, Nov 2013, Ported to mbed, Sparkfun Libs used as example. Added I2C and SPI I/F and more methods
  *
  * 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,
@@ -22,7 +21,7 @@
 
 
 /** Abstract class SC16IS750 for converter between either SPI or I2C and a Serial port
-  *
+  * Constructor for this Abstract Class is protected  
   * Supports both SPI and I2C interfaces through derived classes
   *
   * @code
@@ -31,7 +30,8 @@
   */
 //SC16IS750::SC16IS750() : Serial(NC, NC) {   //Fout ???
 SC16IS750::SC16IS750() {  
-  init();  // initialise UART registers
+// Dont call _init() here since the SPI or I2C port have not yet been configured...
+  //_init();  // initialise UART registers
 }
 
 
@@ -46,10 +46,11 @@
   _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(LCR, lcr_tmp | LCR_ENABLE_DIV);           // 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  
+  
 }
 
 
@@ -101,54 +102,164 @@
 
 };
 
+/** Generate a break condition on the serial line
+  */
+void SC16IS750::send_break() {
+  // Wait for 1.5 frames before clearing the break condition
+  // This will have different effects on our platforms, but should
+  // ensure that we keep the break active for at least one frame.
+  // We consider a full frame (1 start bit + 8 data bits bits + 
+  // 1 parity bit + 2 stop bits = 12 bits) for computation.
+  // One bit time (in us) = 1000000/_baud
+  // Twelve bits: 12000000/baud delay
+  // 1.5 frames: 18000000/baud delay
+  set_break(true);
+  wait_us(18000000/_config.baudrate);
+  set_break(false);    
+};
+    
+/** Set a break condition on the serial line
+  *  @param enable  break condition
+  */
+void SC16IS750::set_break(bool enable) {
 
-/**
-  * Initialise the UART.
+  if (enable) {
+    _config.dataformat |= LCR_BRK_ENA;      // Save dataformat         
+  }
+  else {
+    _config.dataformat &= ~LCR_BRK_ENA;     // Save dataformat           
+  }
+
+  this->writeRegister(LCR, _config.dataformat); // Set LCR register
+}
+
+/** Set the flow control type on the serial port
+  *  Added for compatibility with Serial Class.
+  *  SC16IS750 supports only Flow, Pins can not be selected.
   *
-  * If initialisation fails this method does not return.
+  *  @param type the flow control type (Disabled, RTS, CTS, RTSCTS)     
+  *  @param flow1 the first flow control pin (RTS for RTS or RTSCTS, CTS for CTS)
+  *  @param flow2 the second flow control pin (CTS for RTSCTS)
   */
-void SC16IS750::init() {
+void SC16IS750::set_flow_control(Flow type, PinName flow1, PinName flow2) {
+  char lcr_tmp; 
+  char efr_tmp = 0x00;
+  
+  // We need to enable flow control to prevent overflow of buffers and
+  // lose data when used with fast devices like the WiFly.
+
+  switch (type) {
+     case Disabled : 
+                     break;
+     case RTS:       efr_tmp = EFR_ENABLE_RTS;
+                     break;     
+     case CTS:       efr_tmp = EFR_ENABLE_CTS;                     
+                     break;     
+     case RTSCTS:    efr_tmp = EFR_ENABLE_RTS | EFR_ENABLE_CTS;
+                     break;
+     default:             ;    
+
+  }
+
+  //Save flowcontrol state
+  //enable enhanced functions  
+  _config.flowctrl = efr_tmp | EFR_ENABLE_ENHANCED_FUNCTIONS,
+
+  lcr_tmp = this->readRegister(LCR);                       // save LRC register
+  this->writeRegister(LCR, LCR_ENABLE_ENHANCED_FUNCTIONS); // write magic number 0xBF to enable access to EFR register
+  this->writeRegister(EFR, _config.flowctrl);              // set flow and enable enhanced functions
+  this->writeRegister(LCR, lcr_tmp);                       // restore LCR register
+}  
+ 
+
+/** Initialise internal registers
+  * Should be in protection section. Public for testing purposes
+  * If initialisation fails this method does not return.    
+  *   @param none
+  *   @return none 
+  */
+void SC16IS750::_init() {
 
   // Initialise SC16IS750
 
+  // Software reset, assuming there is no access to the HW Reset pin
+  swReset();
+  
   // Set default baudrate and save in _config
+  // LCR, DLL/DLH  
   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
+  // LCR, EFR
+  set_flow_control(); 
   
   // Set default dataformat and save in _config
+  // LCR 
   format();  
 
+  // Set default break condition and save in _config
+  // LCR   
+  //set_break();
+  
   // 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   
+  // FCR
+  _config.fifoenable = true;  
+  _config.fifoformat = FCR_RX_IRQ_NONE | FCR_ENA_FIFO_64;
+  flush();
 
   // 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) {
-    };
+    while(1) {};
 #else    
     printf("Failed to initialise UART bridge\r\n");    
+#endif    
+  }
+  else {
+    printf("Initialised UART bridge!\r\n");      
+  }
+
+}
+
+/**
+  * Flush the UART FIFOs while maintaining current FIFO mode.
+  *   @param  none
+  *   @return none
+  */
+void SC16IS750::flush() {
+
+  // reset TXFIFO, reset RXFIFO, non FIFO mode  
+  this->writeRegister(FCR, FCR_TXFIFO_RST | FCR_RXFIFO_RST);
+  
+  if (_config.fifoenable)
+    // enable FIFO mode and set FIFO control values  
+    this->writeRegister(FCR, _config.fifoformat | FCR_ENABLE_FIFO);
+  else
+    // disable FIFO mode and set FIFO control values  
+    this->writeRegister(FCR, _config.fifoformat);
+  
+#if(0)
+//original
+ /*
+   * 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();
   }
 #endif
 
 }
 
 
+
 /**
   * Check that UART is connected and operational.
   *  @param  none
@@ -169,18 +280,10 @@
   *   @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.
+/** Determine how many characters are available to read.
   *   @return int Characters available to read
   */
 int SC16IS750::readableCount() {
@@ -201,12 +304,12 @@
   return (this->writableCount() > 0);  // Check datasheet for faster version
 }
 
-/** Determine if how many characters available to write.
-  *   @return int Characters available to write
+/** Determine how much space available for writing characters.
+  *   @return int character space available to write
   */
 int SC16IS750::writableCount() {
   /*
-   * Get the number of chars (characters) available for reading.
+   * Get the available space for writing characters.
    *
    * This is data that's already stored in the transmit
    * buffer (which holds 64 chars).
@@ -217,15 +320,13 @@
 }
 
 
-
-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()'.
-   */
+/**
+  * Read char from UART Bridge.
+  * Acts in the same manner as 'Serial.read()'.  
+  *   @param none    
+  *   @return char read or -1 if no data available. 
+  */ 
+int SC16IS750::getc() {
 
   if (!readable()) {
     return -1;
@@ -234,16 +335,19 @@
   return this->readRegister(RHR);
 }
 
-
-void SC16IS750::putc(char value) {
-  /*
-   * Write char to UART.
-   */
-
+/**
+  * Write char to UART Bridge. Blocking when no free space in FIFO
+  *   @param value char to be written    
+  *   @return value written  
+  */ 
+int SC16IS750::putc(int value) {
   while (this->readRegister(TXLVL) == 0) {
     // Wait for space in TX buffer
+    wait_us(10);
   };
   this->writeRegister(THR, value);
+  
+  return value;
 }
 
 
@@ -254,6 +358,7 @@
   write((const uint8_t *) str, strlen(str));  
   while (this->readRegister(TXLVL) < 64) {
     // Wait for empty TX buffer (slow)
+    wait_us(10);    
     // (But apparently still not slow enough to ensure delivery.)
   };
 }
@@ -279,89 +384,108 @@
 }
 #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();
-  }
-}
-
-
+/** Set direction of I/O port pins.
+  * This method is specific to the SPI-I2C UART and not found on the 16750
+  *   @param  bits Bitpattern for I/O (1=output, 0=input)
+  *   @return none
+  */
 void SC16IS750::ioSetDirection(unsigned char bits) {
   this->writeRegister(IODIR, bits);
 }
 
-
+/** Set bits of I/O port pins.
+  * This method is specific to the SPI-I2C UART and not found on the 16750
+  *   @param  bits Bitpattern for I/O (1= set output bit, 0 = clear output bit)
+  *   @return none
+  */
 void SC16IS750::ioSetState(unsigned char bits) {
   this->writeRegister(IOSTATE, bits);
 }
 
+/** Get bits of I/O port pins.
+  * This method is specific to the SPI-I2C UART and not found on the 16750
+  *   @param  none
+  *   @return bits Bitpattern for I/O (1= bit set, 0 = bit cleared)
+  */
+unsigned char SC16IS750::ioGetState() {
+  return this->readRegister(IOSTATE) ;
+}
 
 
-// Begin SPI Implementation
+/** Software Reset SC16IS750 device.
+  * This method is specific to the SPI-I2C UART and not found on the 16750
+  *   @param  none
+  *   @return none
+  */
+void SC16IS750::swReset() {
+  this->writeRegister(IOCTRL, IOC_SW_RST);     
+}
+
+
+//
+// End Abstract Class Implementation
 //
 
+
+
 /** Class SC16IS750_SPI for a converter between SPI and a Serial port
   *
+  * @code
+  *
+  * @endcode
+  *
   */
 SC16IS750_SPI::SC16IS750_SPI (SPI *spi, PinName cs) : _spi(spi), _cs(cs)  {
   _cs = 1;  // deselect
   
   _spi->format(8, 0);          
   _spi->frequency(1000000);
- 
+//  _spi->frequency(100000);  //test
+
+  // Dont call _init() until SPI port has been configured.
+  // That is why _init() is not called in parent Constructor 
+  _init();
+
 };
 
 /** Write value to internal register.
   * Pure virtual, must be declared in derived class.   
-  *   @param register_address  The address of the Register (enum RegisterName)
+  *   @param registerAddress   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;
-  
-  
+
+#if(0)  
 //Test only  
   DigitalOut myled2(LED_GREEN);  
   myled2 = 0; //LED On
   wait(0.2);
   myled2 = 1; //LED Off
   wait(0.6);  
+#endif  
 }
 
 
 /** Read value from internal register.
-  *   @param register_address  The address of the Register (enum RegisterName)
+  *   @param registerAddress   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; 
+  const char SPI_DUMMY_CHAR = 0xFF; 
 
   char result;
 
   _cs = 0; //  select;
   _spi->write(SPI_READ_MODE_FLAG | registerAddress);
-  result = _spi->write(SPI_DUMMY_char);
+  result = _spi->write(SPI_DUMMY_CHAR);
   _cs = 1; //  deselect;
 
   return result;  
@@ -369,23 +493,28 @@
 
 //
 // End SPI Implementation
+//
 
 
-// Begin I2C Implementation
-//
-
 /** Class SC16IS750_I2C for a converter between I2C and a Serial port
   *
+  * @code
+  *
+  * @endcode
+  *
   */
 SC16IS750_I2C::SC16IS750_I2C(I2C *i2c, uint8_t deviceAddress) : _i2c(i2c), _slaveAddress(deviceAddress) {
 
-    _i2c->frequency(400000);
+  _i2c->frequency(400000);
 
+  // Dont call _init() until I2C port has been configured.
+  // That is why _init() is not called in parent Constructor 
+  _init();
 }
 
 
 /** Write value to internal register.
-  *   @param register_address  The address of the Register (enum RegisterName)
+  *   @param registerAddress   The address of the Register (enum RegisterName)
   *   @param data              The 8bit value to write
   *   @return none 
   */
@@ -400,7 +529,7 @@
 
 
 /** Read value from internal register.
-  *   @param register_address  The address of the Register (enum RegisterName)
+  *   @param registerAddress   The address of the Register (enum RegisterName)
   *   @return char             The 8bit value read from the register
   */
 char SC16IS750_I2C::readRegister(RegisterName registerAddress) {
@@ -420,4 +549,5 @@
 
 
 //
-// End I2C Implementation
\ No newline at end of file
+// End I2C Implementation
+//
\ No newline at end of file