SPI or I2C to UART Bridge
Dependents: SC16IS750_Test mbed_SC16IS750 Xadow_SC16IS750_Test Xadow_MPU9150AHRS
SC16IS750.cpp
- Committer:
- wim
- Date:
- 2014-02-09
- Revision:
- 1:0440152c5387
- Parent:
- 0:d64854a60f95
- Child:
- 2:76cb93b511f2
File content as of revision 1:0440152c5387:
/* SC16IS750 interface * 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, * 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 * Constructor for this Abstract Class is protected * Supports both SPI and I2C interfaces through derived classes * * @code * * @endcode */ //SC16IS750::SC16IS750() : Serial(NC, NC) { //Fout ??? SC16IS750::SC16IS750() { // Dont call _init() here since the SPI or I2C port have not yet been configured... //_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_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 } /** 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 }; /** 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) { 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. * * @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::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 // 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 // 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) {}; #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 * @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() { return (this->readRegister(LSR) & 0x01); } /** Determine how many characters are 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 how much space available for writing characters. * @return int character space available to write */ int SC16IS750::writableCount() { /* * Get the available space for writing characters. * * 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 } /** * 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; } return this->readRegister(RHR); } /** * 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; } 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) wait_us(10); // (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 /** 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) ; } /** 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 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) { _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 registerAddress The address of the Register (enum RegisterName) * @return char The 8bit value read from the register */ char SC16IS750_SPI::readRegister(RegisterName 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 // /** 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); // 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 registerAddress 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 registerAddress 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 //