blob: 501869ad805da91e2758e5e9ae60ee167c8fe243 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Herwig Eichler, www.conmeleon.org
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Herwig Eichler - initial API and implementation and initial documentation
*
* This code has been inspired by the work of Philippe Van Hecke <lemouchon(at).gmail.com>
*
* This class uses the linux kernel spidev device driver.
*
* A SPI device (e.g. ADC) on bus X with chip select line Y will be available as /dev/spidevX.Y
* If you don't see this device in your file system, the spidev kernel module might not be loaded
* SPI devices have a limited userspace API, supporting basic half-duplex
* read() and write() access to SPI slave devices. Using ioctl() requests,
* full duplex transfers and device I/O configuration are also available.
*
* advantage:
* - portability, because spidev is part of the linux kernel
*
* disadvantage:
* - speed, it is slower than direct hardware register access and memory mapping
* - runs in user space and is therefore not time deterministic
*******************************************************************************/
#ifndef SPI_SPIDEVICE_H_
#define SPI_SPIDEVICE_H_
#include "../util/uncopyable.h"
#include <string>
namespace CONMELEON {
//! sets the mode of the SPI bus
/*! Clock polarity (CPOL) and clock phase (CPHA) determine the edges of the clock signal on which the data is driven and sampled (latched).
* Each of the two parameters has two possible states, which allows for four possible combinations, all of which are incompatible with one another.
*
* - SPI_MODE_0 CPOL = 0, CPHA = 0, Clock idle low, data is clocked in on rising edge, output data (change) on falling edge
* - SPI_MODE_1 CPOL = 0, CPHA = 1, Clock idle low, data is clocked in on falling edge, output data (change) on rising edge
* - SPI_MODE_2 CPOL = 1, CPHA = 0, Clock idle high, data is clocked in on falling edge, output data (change) on rising edge
* - SPI_MODE_3 CPOL = 1, CPHA = 1, Clock idle high, data is clocked in on rising, edge output data (change) on falling edge
*
* Most of the devices use SPI_MODE_0 and SPI_MODE_1, but the datasheet of the device should always be checked.
* You can find a nice paper based on the Linux kernel documentation here <http://www.abcelectronique.com/forum/attachment.php?attachmentid=28879>
*/
enum ESpiMode {
SPIMODE0, /*!< clock polarity (CPOL) is low and clock phase (CPHA) is low */
SPIMODE1, /*!< clock polarity (CPOL) is low and clock phase (CPHA) is high */
SPIMODE2, /*!< clock polarity (CPOL) is high and clock phase (CPHA) is low */
SPIMODE3 /*!< clock polarity (CPOL) is high and clock phase (CPHA) is high */
};
// the following enums are not yet used, but might be necessary to allow only certain values for en_nSpeed and en_nBitsPerWord
//! sets the clock divider setting used for calculate the SPI bus clock from the internal system clock
/*! in case of the Raspberry Pi the system clock is 250MHz
*
*/
enum EClockDevider {
MHz125 = 2, /*!< clock divider 2 gives SPI clock of 125 MHz */
MHz62 = 4, /*!< clock divider 4 gives SPI clock of 62.5 MHz */
MHz31 = 8, /*!< clock divider 8 gives SPI clock of 31.2 MHz */
MHz15 = 16, /*!< clock divider 16 gives SPI clock of 15.6 MHz */
MHz8 = 32, /*!< clock divider 32 gives SPI clock of 7.8 MHz */
MHz4 = 64, /*!< clock divider 64 gives SPI clock of 3.9 MHz */
MHz2 = 128, /*!< clock divider 128 gives SPI clock of 1953 kHz */
MHz1 = 256, /*!< clock divider 256 gives SPI clock of 976 kHz */
kHz488 = 512, /*!< clock divider 512 gives SPI clock of 488 kHz */
kHz244 = 1024, /*!< clock divider 1024 gives SPI clock of 244 kHz */
kHz122 = 2048, /*!< clock divider 2048 gives SPI clock of 122 kHz */
kHz61 = 4096, /*!< clock divider 4096 gives SPI clock of 61 kHz */
kHz30 = 8192, /*!< clock divider 8192 gives SPI clock of 30.5 kHz */
kHz15 = 16384, /*!< clock divider 16384 gives SPI clock of 15.2 kHz */
Hz7629 = 32768 /*!< clock divider 32768 gives SPI clock of 7629 Hz */
};
//! sets the bits used per word for the SPI bus data transfer
enum EBitsPerWord {
standard = 8, /*!< 8 bits per word, which is the default for most applications */
lcd = 9 /*!< 9 bits per word, used for LoSSI bus mode devices like LCD displays for instance */
};
/*! \class CSpiDevice
* \brief CSpiDevice represents a device (slave) connected via SPI bus and provides basic configuration and data processing
*
* CSpiDevice needs the Linux SPI kernel driver to be loaded (e.g the spi-bcm2708 module on the Raspberry Pi)
* A SPI device (e.g. ADC) on bus X with chip select line Y will be available as /dev/spidevX.Y
* If you don't see this device in your file system, the spidev kernel driver module might not be loaded.
* SPI devices have a limited userspace API, supporting basic half-duplex
* read() and write() access to SPI slave devices. Using ioctl() requests,
* full duplex transfers and device I/O configuration are also available.
*
* disadvantage:
* - speed, it is slower than direct hardware register access and memory mapping
* - runs in user space and is therefore not time deterministic
*
* A SPI device itself normally is unique, so the CSpiDevice class is derived from CUncopyable
* to prevent the compiler from creating default copy constructor and assignment operator and to disable these functions in derived classes as well.
*/
class CSpiDevice : private CUncopyable {
public:
/*! \fn CSpiDevice(const char* sDevice)
* \brief creates a SPI device according to the \a sDevice parameter
* \param sDevice gives the device path e.g. /dev/spidev0.0
*
* The constructor sets the default parameters to m_nBitsPerWord = 8, m_enMode = SPIMODE0 and m_nSpeed to 1 MHz.
* It configures the bus via file access to /dev/spidevx.y and using ioctl() calls to the kernel driver
*/
explicit CSpiDevice(const char* sDevice);
/*! \fn CSpiDevice(const char* sDevice, unsigned int nSpeed, ESpiMode enMode)
* \brief creates a SPI device according to the \a sDevice parameter
* \param sDevice gives the device path e.g. /dev/spidev0.0
* \param nSpeed gives the bus clock frequency e.g. 1000000 for 1 MHz
* \param enMode gives the SPI bus mode setting e.g. SPIMODE0
*
* The constructor sets the default parameters to m_nBitsPerWord = 8
*/
CSpiDevice(const char* sDevice, unsigned int nSpeed, ESpiMode enMode);
/*! \fn ~CSpiDevice()
* \brief releases the current current SPI device and closing the file descriptor to /dev/spidevx.y
*
*/
~CSpiDevice();
/*! \fn bool isOpen()
* \brief returns true if the device has been opened and configured successfully, false will be returned if something went wrong
*
*/
bool isOpen() const { return m_bValid; };
/*! \fn ESpiMode getMode()
* \brief returns the currently configured SPI mode
*
*/
ESpiMode getMode() const { return m_enMode; };
/*! \fn void setMode(ESpiMode newMode)
* \brief sets the SPI mode to \a newMode
* \param newMode gives the SPI mode to set (SPIMODE0 to SPIMODE3)
*
*/
void setMode(ESpiMode newMode) { m_enMode = newMode; };
/*! \fn bool write(const unsigned char* paData, int nLength)
* \brief writes \a nLength bytes of the \a paData buffer to the device
* \param paData buffer holding the bytes to write
* \param nLength number of bytes to write to the device
*
* The function writes data e.g. the configuration register settings, which needs to be provided in paData, to the SPI device e.g. the ADC.
* The answer sent from the device is ignored.
* TRUE is returned if the operation was successful, FALSE if not.
* The caller of the function needs to make sure, that \a paData is not smaller than \a nLength.
*/
bool write(const unsigned char* paData, int nLength);
/*! \fn bool read(unsigned char* paData, int nLength)
* \brief reads \a nLength bytes of the \a paData buffer to the device
* \param paData buffer holding the bytes read from the device
* \param nLength number of bytes to read from the device
*
* The function reads data from the SPI device, e.g. an eeprom.
* TRUE is returned if the operation was successful, FALSE if not.
* The caller of the function needs to make sure, that \a paData is big enough to hold \a nLength bytes.
*/
bool read(unsigned char* paData, int nLength);
/*! \fn bool transfer(const unsigned char* paTxData, unsigned char* paRxData, unsigned int nLength)
* \brief writes and reads \a nLength bytes in a single communication cycle
* \param paTxData buffer holding the bytes to send to the device
* \param paRxData buffer holding the bytes read back from the device
* \param nLength number of bytes to read and write
*
* The function transfer() sends the bytes stored in the paTxData buffer to the SPI device and stores the answer in the paRxData buffer.
* Both buffers need to have the same \a nLength in bytes.
* This function is useful for SPI devices, which respond to a write operation of the SPI master immediately (e.g an ADC or an EEPROM)
* TRUE is returned if the operation was successful, FALSE if not.
* The caller of the function needs to make sure, that both \a paTxData and \a paRxData are big enough to hold \a nLength bytes.
*/
bool transfer(const unsigned char* paTxData, unsigned char* paRxData, unsigned int nLength);
private:
/*! \fn bool openAndConfigureBus(const char* sDevice)
* \brief does the initial setup of the SPI device bus
* \param sDevice gives the device path e.g. /dev/spidev0.0
*
* The function opens the device, sets read and write mode, speed and so on.
* If something is wrong during the setup and configuration it returns FALSE, TRUE is returned if everything is fine.
* All the member variables m_enMode, m_nBitsPerWord and m_nSpeed need to be initialized properly before this function is called.
* This is normally done by the constructors of CSpiDevice.
*/
bool openAndConfigureBus(const char* sDevice);
// TODO: make the data type of m_BitsPerWord bullet proof to allow only 8 (standard) or 9 (used by some LCD displays in LoSSI mode) bits
unsigned char m_nBitsPerWord;
// TODO: make the data type of m_Speed bullet proof, e.g. enum
unsigned int m_nSpeed;
ESpiMode m_enMode;
// file descriptor used for the SPI device
int m_nFileDescriptor;
// m_Valid is used by other member functions to check if the SPI device bus has been initialized properly
bool m_bValid;
};
}
#endif /* SPI_SPIDEVICE_H_ */