/* * Copyright (c) 2014, Analog Devices, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \author Dragos Bogdan , Ian Martin */ /******************************************************************************/ /***************************** Include Files **********************************/ /******************************************************************************/ #include #include "rl78.h" #include "Communication.h" /* Communication definitions */ #ifndef NOP #define NOP asm ("nop") #endif /* Enable interrupts: */ #ifndef EI #ifdef __GNUC__ #define EI asm ("ei"); #else #define EI __enable_interrupt(); #endif #endif #undef BIT #define BIT(n) (1 << (n)) #define CLK_SCALER (0x4) #define SCALED_CLK (f_CLK / (1 << CLK_SCALER)) #define BITBANG_SPI 1 char IICA0_Flag; /******************************************************************************/ /************************ Functions Definitions *******************************/ /******************************************************************************/ /***************************************************************************//** * @brief I2C interrupt service routine. * * @return None. *******************************************************************************/ /*__interrupt */ static void IICA0_Interrupt(void) { IICA0_Flag = 1; } /***************************************************************************//** * @brief Initializes the SPI communication peripheral. * * @param lsbFirst - Transfer format (0 or 1). * Example: 0x0 - MSB first. * 0x1 - LSB first. * @param clockFreq - SPI clock frequency (Hz). * Example: 1000 - SPI clock frequency is 1 kHz. * @param clockPol - SPI clock polarity (0 or 1). * Example: 0x0 - Idle state for clock is a low level; active * state is a high level; * 0x1 - Idle state for clock is a high level; active * state is a low level. * @param clockEdg - SPI clock edge (0 or 1). * Example: 0x0 - Serial output data changes on transition * from idle clock state to active clock state; * 0x1 - Serial output data changes on transition * from active clock state to idle clock state. * * @return status - Result of the initialization procedure. * Example: 0 - if initialization was successful; * -1 - if initialization was unsuccessful. *******************************************************************************/ char SPI_Init(enum CSI_Bus bus, char lsbFirst, long clockFreq, char clockPol, char clockEdg) { #if BITBANG_SPI PIOR5 = 1; /* Move SPI/I2C/UART functions from Port 0 pins 2-4 to Port 8. */ /* Configure SCLK as an output. */ PM0 &= ~BIT(4); POM0 &= ~BIT(4); /* Configure MOSI as an output: */ PM0 &= ~BIT(2); POM0 &= ~BIT(2); PMC0 &= ~BIT(2); /* Configure MISO as an input: */ PM0 |= BIT(3); PMC0 &= ~BIT(3); #else char sdrValue = 0; char delay = 0; uint16_t scr; uint8_t shift; PIOR5 = 0; /* Keep SPI functions on Port 0 pins 2-4. */ /* Enable input clock supply. */ if(bus <= CSI11) { SAU0EN = 1; } else { SAU1EN = 1; } /* After setting the SAUmEN bit to 1, be sure to set serial clock select register m (SPSm) after 4 or more fCLK clocks have elapsed. */ NOP; NOP; NOP; NOP; /* Select the fCLK as input clock. */ if(bus <= CSI11) { SPS0 = (CLK_SCALER << 4) | CLK_SCALER; /* TODO: kludge */ } else { SPS1 = (CLK_SCALER << 4) | CLK_SCALER; /* TODO: kludge */ } /* Select the CSI operation mode. */ switch(bus) { case CSI00: SMR00 = 0x0020; break; case CSI01: SMR01 = 0x0020; break; case CSI10: SMR02 = 0x0020; break; case CSI11: SMR03 = 0x0020; break; case CSI20: SMR10 = 0x0020; break; case CSI21: SMR11 = 0x0020; break; case CSI30: SMR12 = 0x0020; break; case CSI31: SMR13 = 0x0020; break; } clockPol = 1 - clockPol; scr = (clockEdg << 13) | (clockPol << 12) | 0xC000 | /* Operation mode: Transmission/reception. */ 0x0007; /* 8-bit data length. */ switch(bus) { case CSI00: SCR00 = scr; break; case CSI01: SCR01 = scr; break; case CSI10: SCR02 = scr; break; case CSI11: SCR03 = scr; break; case CSI20: SCR10 = scr; break; case CSI21: SCR11 = scr; break; case CSI30: SCR12 = scr; break; case CSI31: SCR13 = scr; break; } /* clockFreq = mckFreq / (sdrValue * 2 + 2) */ sdrValue = SCALED_CLK / (2 * clockFreq) - 1; sdrValue <<= 9; switch(bus) { case CSI00: SDR00 = sdrValue; break; case CSI01: SDR01 = sdrValue; break; case CSI10: SDR02 = sdrValue; break; case CSI11: SDR03 = sdrValue; break; case CSI20: SDR10 = sdrValue; break; case CSI21: SDR11 = sdrValue; break; case CSI30: SDR12 = sdrValue; break; case CSI31: SDR13 = sdrValue; break; } /* Set the clock and data initial level. */ clockPol = 1 - clockPol; shift = bus & 0x3; if(bus <= CSI11) { SO0 &= ~(0x0101 << shift); SO0 |= ((clockPol << 8) | clockPol) << shift; } else { SO1 &= ~(0x0101 << shift); SO1 |= ((clockPol << 8) | clockPol) << shift; } /* Enable output for serial communication operation. */ switch(bus) { case CSI00: SOE0 |= BIT(0); break; case CSI01: SOE0 |= BIT(1); break; case CSI10: SOE0 |= BIT(2); break; case CSI11: SOE0 |= BIT(3); break; case CSI20: SOE1 |= BIT(0); break; case CSI21: SOE1 |= BIT(1); break; case CSI30: SOE1 |= BIT(2); break; case CSI31: SOE1 |= BIT(3); break; } switch(bus) { case CSI00: /* SO00 output: */ P1 |= BIT(2); PM1 &= ~BIT(2); /* SI00 input: */ PM1 |= BIT(1); /* SCK00N output: */ P1 |= BIT(0); PM1 &= ~BIT(0); break; case CSI01: /* SO01 output: */ P7 |= BIT(3); PM7 &= ~BIT(3); /* SI01 input: */ PM7 |= BIT(4); /* SCK01 output: */ P7 |= BIT(5); PM7 &= ~BIT(5); break; case CSI10: PMC0 &= ~BIT(2); /* Disable analog input on SO10. */ /* SO10 output: */ P0 |= BIT(2); PM0 &= ~BIT(2); /* SI10 input: */ PM0 |= BIT(3); /* SCK10N output: */ P0 |= BIT(4); PM0 &= ~BIT(4); break; case CSI11: /* SO11 output: */ P5 |= BIT(1); PM5 &= ~BIT(1); /* SI11 input: */ PM5 |= BIT(0); /* SCK11 output: */ P3 |= BIT(0); PM3 &= ~BIT(0); break; case CSI20: /* SO20 output: */ P1 |= BIT(3); PM1 &= ~BIT(3); /* SI20 input: */ PM1 |= BIT(4); /* SCK20 output: */ P1 |= BIT(5); PM1 &= ~BIT(5); break; case CSI21: /* SO21 output: */ P7 |= BIT(2); PM7 &= ~BIT(2); /* SI21 input: */ PM7 |= BIT(1); /* SCK21 output: */ P7 |= BIT(0); PM7 &= ~BIT(0); break; case CSI30: /* TODO: not supported */ break; case CSI31: /* TODO: not supported */ break; } /* Wait for the changes to take place. */ for(delay = 0; delay < 50; delay++) { NOP; } /* Set the SEmn bit to 1 and enter the communication wait status */ switch(bus) { case CSI00: SS0 = BIT(0); break; case CSI01: SS0 = BIT(1); break; case CSI10: SS0 = BIT(2); break; case CSI11: SS0 = BIT(3); break; case CSI20: SS1 = BIT(0); break; case CSI21: SS1 = BIT(1); break; case CSI30: SS1 = BIT(2); break; case CSI31: SS1 = BIT(3); break; } /* Sanity check: */ if(bus == CSI10) { /* MOSI: */ PIOR5 = 0; PMC02 = 0; PM02 = 0; P02 = 1; /* MISO: */ PIOR5 = 0; PMC03 = 0; PM03 = 1; /* SCLK: */ PIOR5 = 0; PM04 = 0; P04 = 1; } #endif return 0; } /***************************************************************************//** * @brief Writes data to SPI. * * @param slaveDeviceId - The ID of the selected slave device. * @param data - Data represents the write buffer. * @param bytesNumber - Number of bytes to write. * * @return Number of written bytes. *******************************************************************************/ #if 0 char SPI_Write(enum CSI_Bus bus, char slaveDeviceId, unsigned char *data, char bytesNumber) { char byte = 0; unsigned char read = 0; unsigned short originalSCR = 0; unsigned short originalSO1 = 0; volatile uint8_t *sio; volatile uint16_t *ssr; switch(bus) { default: case CSI00: sio = &SIO00; ssr = &SSR00; break; case CSI01: sio = &SIO01; ssr = &SSR01; break; case CSI10: sio = &SIO10; ssr = &SSR02; break; case CSI11: sio = &SIO11; ssr = &SSR03; break; case CSI20: sio = &SIO20; ssr = &SSR10; break; case CSI21: sio = &SIO21; ssr = &SSR11; break; case CSI30: sio = &SIO30; ssr = &SSR12; break; case CSI31: sio = &SIO31; ssr = &SSR13; break; } for(byte = 0; byte < bytesNumber; byte++) { *sio = data[byte]; NOP; while(*ssr & 0x0040) ; read = *sio; } return bytesNumber; } #endif #if BITBANG_SPI #define sclk_low() (P0 &= ~BIT(4)) #define sclk_high() (P0 |= BIT(4)) #define mosi_low() (P0 &= ~BIT(2)) #define mosi_high() (P0 |= BIT(2)) #define read_miso() (P0bits.bit3) static unsigned char spi_byte_exchange(unsigned char tx) { unsigned char rx = 0, n = 0; sclk_low(); for(n = 0; n < 8; n++) { if(tx & 0x80) { mosi_high(); } else { mosi_low(); } /* The slave samples MOSI at the rising-edge of SCLK. */ sclk_high(); rx <<= 1; rx |= read_miso(); tx <<= 1; /* The slave changes the value of MISO at the falling-edge of SCLK. */ sclk_low(); } return rx; } #endif /***************************************************************************//** * @brief Reads data from SPI. * * @param slaveDeviceId - The ID of the selected slave device. * @param data - Data represents the write buffer as an input parameter * and the read buffer as an output parameter. * @param bytesNumber - Number of bytes to read. * * @return Number of read bytes. *******************************************************************************/ char SPI_Read(enum CSI_Bus bus, char slaveDeviceId, unsigned char *data, char bytesNumber) { #if BITBANG_SPI unsigned char n = 0; for(n = 0; n < bytesNumber; n++) { data[n] = spi_byte_exchange(data[n]); } #else char byte = 0; unsigned short originalSCR = 0; unsigned short originalSO1 = 0; volatile uint8_t *sio; volatile uint16_t *ssr; char dummy; switch(bus) { default: case CSI00: sio = &SIO00; ssr = &SSR00; break; case CSI01: sio = &SIO01; ssr = &SSR01; break; case CSI10: sio = &SIO10; ssr = &SSR02; break; case CSI11: sio = &SIO11; ssr = &SSR03; break; case CSI20: sio = &SIO20; ssr = &SSR10; break; case CSI21: sio = &SIO21; ssr = &SSR11; break; case CSI30: sio = &SIO30; ssr = &SSR12; break; case CSI31: sio = &SIO31; ssr = &SSR13; break; } /* Flush the receive buffer: */ while(*ssr & 0x0020) dummy = *sio; (void)dummy; for(byte = 0; byte < bytesNumber; byte++) { *sio = data[byte]; NOP; while(*ssr & 0x0040) ; data[byte] = *sio; } #endif return bytesNumber; } /***************************************************************************//** * @brief Initializes the I2C communication peripheral. * * @param clockFreq - I2C clock frequency (Hz). * Example: 100000 - SPI clock frequency is 100 kHz. * @return status - Result of the initialization procedure. * Example: 0 - if initialization was successful; * -1 - if initialization was unsuccessful. *******************************************************************************/ char I2C_Init(long clockFreq) { long fckFreq = 32000000; unsigned char wlValue = 0; unsigned char whValue = 0; (void)IICA0_Interrupt; /* Prevent an unused-function warning. */ /* Enable interrupts */ EI; /* Enable input clock supply. */ IICA0EN = 1; /* Set the fast mode plus operation. */ SMC0 = 1; /* Set transfer rate. */ wlValue = (unsigned char)((0.5 * fckFreq) / clockFreq); whValue = (unsigned char)(wlValue - (fckFreq / (10 * clockFreq))); IICWL0 = wlValue; IICWH0 = whValue; STCEN0 = 1; /* After operation is enabled, enable generation of a start */ /* condition without detecting a stop condition. */ WTIM0 = 1; /* Interrupt request is generated at the ninth clock’s */ /* falling edge. */ /* Enable I2C operation. */ IICE0 = 1; /* Configure SCLA0 and SDAA0 pins as digital output. */ P6 &= ~0x03; PM6 &= ~0x03; return 0; } /***************************************************************************//** * @brief Writes data to a slave device. * * @param slaveAddress - Adress of the slave device. * @param dataBuffer - Pointer to a buffer storing the transmission data. * @param bytesNumber - Number of bytes to write. * @param stopBit - Stop condition control. * Example: 0 - A stop condition will not be sent; * 1 - A stop condition will be sent. * * @return status - Number of read bytes or 0xFF if the slave address was * not acknowledged by the device. *******************************************************************************/ char I2C_Write(char slaveAddress, unsigned char *dataBuffer, char bytesNumber, char stopBit) { char byte = 0; char status = 0; IICAMK0 = 1; /* Interrupt servicing disabled. */ STT0 = 1; /* Generate a start condition. */ IICAMK0 = 0; /* Interrupt servicing enabled. */ /* Send the first byte. */ IICA0_Flag = 0; IICA0 = (slaveAddress << 1); while(IICA0_Flag == 0) ; if(ACKD0) { /* Acknowledge was detected. */ for(byte = 0; byte < bytesNumber; byte++) { IICA0_Flag = 0; IICA0 = *dataBuffer; while(IICA0_Flag == 0) ; dataBuffer++; } status = bytesNumber; } else { /* Acknowledge was not detected. */ status = 0xFF; } if(stopBit) { SPT0 = 1; /* Generate a stop condition. */ while(IICBSY0) ; /* Wait until the I2C bus status flag is cleared. */ } return status; } /***************************************************************************//** * @brief Reads data from a slave device. * * @param slaveAddress - Adress of the slave device. * @param dataBuffer - Pointer to a buffer that will store the received data. * @param bytesNumber - Number of bytes to read. * @param stopBit - Stop condition control. * Example: 0 - A stop condition will not be sent; * 1 - A stop condition will be sent. * * @return status - Number of read bytes or 0xFF if the slave address was * not acknowledged by the device. *******************************************************************************/ char I2C_Read(char slaveAddress, unsigned char *dataBuffer, char bytesNumber, char stopBit) { char byte = 0; char status = 0; IICAMK0 = 1; /* Interrupt servicing disabled. */ STT0 = 1; /* Generate a start condition. */ IICAMK0 = 0; /* Interrupt servicing enabled. */ /* Send the first byte. */ IICA0_Flag = 0; IICA0 = (slaveAddress << 1) + 1; while(IICA0_Flag == 0) ; if(ACKD0) { /* Acknowledge was detected. */ ACKE0 = 1; /* Enable acknowledgment. */ for(byte = 0; byte < bytesNumber; byte++) { if(byte == (bytesNumber - 1)) { ACKE0 = 0U; /* Disable acknowledgment. */ } WREL0 = 1U; /* Cancel wait. */ IICA0_Flag = 0; while(IICA0_Flag == 0) ; *dataBuffer = IICA0; dataBuffer++; } status = bytesNumber; } else { /* Acknowledge was not detected. */ status = 0xFF; } if(stopBit) { SPT0 = 1; /* Generate a stop condition. */ while(IICBSY0) ; /* Wait until the I2C bus status flag is cleared. */ } return status; }