diff --git a/mcp4xxx.cpp b/mcp4xxx.cpp new file mode 100644 index 0000000..1f26956 --- /dev/null +++ b/mcp4xxx.cpp @@ -0,0 +1,119 @@ +#include "mcp4xxx.h" + +namespace icecave { +namespace arduino { + +MCP4XXX::MCP4XXX(byte select_pin, Pot pot, MemorySize memory_size, WiperConfiguration config) + : m_select_pin(select_pin) + , m_pot_address(static_cast
(pot)) + , m_max_value(memory_size + config) // Potentiometer configurations allow "memory_size + 1" values, for setting the "full-scale" wiper position. +{ + SPI.begin(); + pinMode(m_select_pin, OUTPUT); + deselect(); +} + + +word MCP4XXX::max_value(void) const +{ + return m_max_value; +} + +bool MCP4XXX::increment(void) +{ + return transfer(m_pot_address, command_increment); +} + +bool MCP4XXX::decrement(void) +{ + return transfer(m_pot_address, command_decrement); +} + +bool MCP4XXX::set(word value) +{ + return transfer(m_pot_address, command_write, min(value, m_max_value)); +} + +bool MCP4XXX::get(word& value) const +{ + return transfer(m_pot_address, command_read, data_mask_word, value); +} + +word MCP4XXX::get(void) const +{ + word result = 0xFFFF; + get(result); + return result; +} + +void MCP4XXX::select(void) const +{ + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setClockDivider(SPI_CLOCK_DIV128); + digitalWrite(m_select_pin, LOW); +} + +void MCP4XXX::deselect(void) const +{ + digitalWrite(m_select_pin, HIGH); +} + +byte MCP4XXX::build_command(Address address, Command command) const +{ + return ((address << 4) & address_mask) + | ((command << 2) & command_mask) + | cmderr_mask; +} + +word MCP4XXX::build_command(Address address, Command command, word data) const +{ + return (build_command(address, command) << 8) + | (data & data_mask_word); +} + +bool MCP4XXX::transfer(Address address, Command command) const +{ + select(); + byte result = SPI.transfer(build_command(address, command)); + deselect(); + return result & cmderr_mask; +} + +bool MCP4XXX::transfer(Address address, Command command, word data) const +{ + select(); + + word cmd = build_command(address, command, data); + bool valid = SPI.transfer(highByte(cmd)) & cmderr_mask; + + if (valid) + { + SPI.transfer(lowByte(cmd)); + } + + deselect(); + return valid; +} + +bool MCP4XXX::transfer(Address address, Command command, word data, word& result) const +{ + select(); + + word cmd = build_command(address, command, data); + byte high_byte = SPI.transfer(highByte(cmd)); + bool valid = high_byte & cmderr_mask; + + if (valid) + { + result = ((high_byte & data_mask) << 8) + | SPI.transfer(lowByte(cmd)); + } + + deselect(); + return valid; +} + +} // end namespace arduino +} // end namespace icecave + diff --git a/mcp4xxx.h b/mcp4xxx.h new file mode 100644 index 0000000..605d5f9 --- /dev/null +++ b/mcp4xxx.h @@ -0,0 +1,186 @@ +#ifndef __ICECAVE_ARDUINO_MCP4XXX +#define __ICECAVE_ARDUINO_MCP4XXX + +#include +#include + +namespace icecave { +namespace arduino { + +/** + * This class controls Microchip's MCP4XXX range of digital potentiometers. + * http://ww1.microchip.com/downloads/en/DeviceDoc/22060b.pdf + * + * The supported chips are configured as follows: + * + * MCP 4 X Y Z + * | | | + * | | +- Z = Wiper Configuration (1 = Potentiometer, 2 = Rheostat). + * | | + * | +--- Y = Memory Type (3 = 7-bit RAM, 4 = 7-bit EEPROM, 5 = 8-bit RAM, 6 = 8-bit EEPROM). + * | + * +----- X = Number of pots (1 or 2). + * + * Note that the MCP41X1 chips multiplex the SDI and SDO on a single pin. + * To use these chips with a standard SPI interface as on the Arduino you will need to + * connect the SDI/SDO pin on the pot to the Arduino's MISO pin, then bridge the MISO pin to the MOSI pin + * with a resistor (3k9 resistor seems to work well). + * + * This class has only been tested with the MCP4151. + */ +class MCP4XXX +{ + public: + enum Pot + { + pot_0 = B00, + pot_1 = B01 + }; + + enum MemorySize + { + memory_7bit = 127, + memory_8bit = 255 + }; + + enum WiperConfiguration + { + rheostat = 0, + potentiometer = 1 + }; + + /** + * select_pin - The pin to use for the SPI slave select signal, defaults to the main slave select pin on your Arduino. + * pot - For the 2-pot variants (MCP42XX), the potentiometer to control. Must be pot_0 for MCP41XX chips. + * memory_size - memory_7bit for MCP4X3X and MCP4X4X, memory_8bit for MCP4X5X and MCP4X6X. + */ + MCP4XXX(byte select_pin = SS, Pot pot = pot_0, MemorySize memory_size = memory_8bit, WiperConfiguration config = potentiometer); + + /** + * Retrieve the maximum value allowed for the wiper position. + * + * The maximum value depends on the device's memory type and wiper configuration. + * + * 7-bit devices have a maximum value of 127 for rheostats and 128 for potentiometers. + * 8-bit devices have a maximum value of 255 for rheostats and 256 for potentiometers. + * + * The higher value on potentiometers (MCP4XX1) facilitates direct connection of the wiper to the "A" terminal (also known as "full-scale"). + * Confusingly the rheostat devices (MCP4XX2) have only a wiper pin and "B" terminal pin, (not "A"). + */ + word max_value(void) const; + + /** + * Increase the wiper position by 1. + * + * Returns TRUE on success; otherwise, FALSE. + */ + bool increment(void); + + /** + * Decrease the wiper position by 1. + * + * Returns TRUE on success; otherwise, FALSE. + */ + bool decrement(void); + + /** + * Set the wiper position. + * + * value - The new wiper position (must be between 0 and max_value()). + * + * Returns TRUE on success; otherwise, FALSE. + */ + bool set(word value); + + /** + * Get the wiper position. + * + * value - Assigned the value of the current wiper position when retrieved successfully. + * + * Returns TRUE on success; otherwise, FALSE. + */ + bool get(word& value) const; + + /** + * Get the wiper position. + * + * Returns the current wiper position, a value of 0xFFFF indicates a failure. + */ + word get(void) const; + + private: + const static byte address_mask = B11110000; + const static byte command_mask = B00001100; + const static byte cmderr_mask = B00000010; + const static byte data_mask = B00000001; + const static word data_mask_word = 0x01FF; + + enum Address + { + address_pot0_wiper = B0000 + , address_pot1_wiper = B0001 + , address_tcon = B0100 + , address_status = B0101 + }; + + enum Command + { + command_write = B00 + , command_read = B11 + , command_increment = B01 + , command_decrement = B10 + }; + + /** + * Select this device for SPI communication + * + * Configures SPI for communication with MCP devices, and sends slave-select pin LOW. + */ + void select(void) const; + + /** + * Cease SPI communications with this device. + * + * Sends the slave-select pin HIGH. + */ + void deselect(void) const; + + /** + * Build an 8-bit command. + */ + byte build_command(Address address, Command command) const; + + /** + * Build a 16-bit command. + */ + word build_command(Address address, Command command, word data) const; + + /** + * Transfer an 8-bit command. + */ + bool transfer(Address address, Command command) const; + + /** + * Transfer a 16-bit command. + * + * Result is TRUE if address/command combination is valid; otherwise, FALSE. + */ + bool transfer(Address address, Command command, word data) const; + + /** + * Transfer a 16-bit command, and read the response. + * + * Result is TRUE if address/command combination is valid; otherwise, FALSE. + * + * The result argument is populated with the 9-bit response only if return value is TRUE. + */ + bool transfer(Address address, Command command, word data, word& result) const; + + byte m_select_pin; + Address m_pot_address; + word m_max_value; +}; + +} // end namespace arduino +} // end namespace icecave +#endif // __ICECAVE_ARDUINO_MCP4XXX