/* * "PE4302.cpp" * * Added to the "TinySA" program in Version 1.1 by John Price (WA2FZW): * Updated in V2.5 by M0WID * * Functions to handle the PE4302 attenuator and support for using the PCF8574 * I2C GPIO expander to drive a parallel version of the PE4302 module. */ #include "pE4302.h" /* * Create a "PCF8574" object with the default I2C address * for the basic PCF8574 (if using a PCF8574A, the default address would be * 0x38). * * Please note, we also assume that the PCF8574 is on the standard I2C bus * pins for the processor being used (21 & 22 for the ESP32; different for * Arduinos). * * The address is updated by the PCF8574 constructor. */ PCF8574 _pcf ( 0x28 ); // Create the PCF8574 object /* * The PE4302 chip can be operated in either a serial or parallel mode. * Most pre-built modules are set for parallel but by changing some jumpers * can be used in serial mode. */ /* * Serial interface constructor: * * The arguments are a pointer to the SPI class used and pin definition * for Latch Enable "LE" pin. When "LE" is HIGH the data in the serial * buffer is latched. "LE" must be LOW when the SPI bus is used for other * objects (which it is). * * The SPI object can be shared with other objects, so is declared in the main sketch * * SPIClass* vspi = new SPIClass ( VSPI ); * or SPIClass* hspi = new SPIClass ( HSPI ); * * The SPI object is initialized once in the main sketch setup, along with the * relevant pins, e.g.: * * pinMode ( V_SCLK, OUTPUT ); // SPI Clock pin * pinMode ( V_SDO, INPUT ); // SDO (MISO) pin * pinMode ( V_SDI, OUTPUT ); // SDI (MOSI) pin * * digitalWrite ( V_SCLK, LOW ); // Make SPI clock LOW * digitalWrite ( V_SDI, LOW ); // Along with MOSI * * vspi->begin ( V_SCLK, V_SDO, V_SDI ); // Start the VSPI: SCLK, MISO, MOSI */ PE4302::PE4302 ( SPIClass* spi, int le ) // Constructor for serial interface { _interface = S; // SPI (serial)interface _le_Pin = le; // Enable (LE) pin number pinMode ( _le_Pin, OUTPUT ); // Chip select pin is an output digitalWrite ( _le_Pin, LOW ); // Deselect the module _spi = spi; // Save SPI object pointer } /* * Parallel GPIO interface constructor: * arguments are the GPIO pin assignments that correspond to the "C16" to * "C0.5" pins of the chip. */ PE4302::PE4302 ( int c16, int c8, int c4, int c2, int c1, int c0 ) { _interface = P; // Parallel interface _parallel_Pins[0] = c0; // The actual GPIO pin numbers _parallel_Pins[1] = c1; // can be random as opposed _parallel_Pins[2] = c2; // to David's requirement that _parallel_Pins[3] = c4; // they had to be consecutive. _parallel_Pins[4] = c8; // The array order is LSB to MSB. _parallel_Pins[5] = c16; } /* * PCF8574 (I2C IO expander interface) constructor: * The default address set at the beginning of the module assumes that the * chip in use is a PCF8574. The PCF8574A may also be used, however its * default address is 0x38, so you might need to provide a different address * here. */ PE4302::PE4302 ( int address ) // Constructor for PCF8574 interface { _interface = PCF; // PCF8574 interface _pcf_I2C = address; // I2C bus address of the PCF8574 _pcf = PCF8574 ( address ); // Update the object with the address } /* * "PE4302_init" - Initialize the attenuator module. */ void PE4302::Init () { if ( _interface == S ) // If using the serial mode { // nothing to do! } else if ( _interface == P ) // If using the parallel mode { for ( int i = 0; i < 6; i++ ) pinMode ( _parallel_Pins[i], OUTPUT ); // All 6 pins are outputs } else if ( _interface == PCF ) // If using the PCF8574 { _pcf.begin ( _pcf_I2C, 0 ); // Initialize the chip } } /* * "SetAtten" - Set the attenuation. * * The parameter is the required attenuation in dB. * The PE4302 allows attenuation to be set in 0.5dB increments, but not here! * If the requested attenuation is out of limits the function will return "false". */ bool PE4302::SetAtten ( int8_t atten ) { bool retCode = true; // Assume good number if ( atten > PE4302_MAX ) // Maximum is defined in "My_SA.h" { atten = PE4302_MAX; retCode = false; // Indicate bad number } if ( atten < 0 ) // Can't be less than zero { atten = 0; retCode = false; // Indicate bad number } _atten = atten << 1; // Double the number and save it if ( _interface == S ) // If using the serial mode { // Serial.printf ( "SetAtten %i - clk:%i data:%i LE:%i \n", // _atten, _clock_Pin, _data_Pin, _le_Pin); uint32_t oldDivider = _spi->getClockDivider(); // run at 1MHz _spi->setFrequency(1000000); // run at 1MHz digitalWrite ( _le_Pin, LOW ); // Make the sure the attenuator is not using the shifted in data _spi->transfer ( _atten ); // Send the attenution value bit pattern digitalWrite ( _le_Pin, HIGH ); // Latch Enable pin HIGH digitalWrite ( _le_Pin, LOW ); // Then immediately LOW _spi->setClockDivider(oldDivider); // back to whatever speed it was running before } else if ( _interface == P ) // If using the parallel mode { for ( int i = 0; i < 6; i++ ) digitalWrite ( _parallel_Pins[i], _atten & ( 1 << i )); } else if ( _interface == PCF ) // If using the PCF8574 { _pcf.write8 ( _atten ); } return retCode; // Send back good/bad indication } /* * Return current attenuation value. * Bit pattern has to be divided by two to return dB */ int PE4302::GetAtten () // Send back stored attenuation { return _atten >> 1; } /* * These functions implement the stripped down PCF8574 library: */ PCF8574::PCF8574 ( const uint8_t deviceAddress ) { _address = deviceAddress; _dataOut = 0; } /* * The "begin" function is modified from the original to allow us to change the * I2C address as well as set an inital output value: */ void PCF8574::begin ( const uint8_t deviceAddress, uint8_t val ) { _address = deviceAddress; // Save address Wire.begin (); // Initialize the I2C interface PCF8574::write8 ( val ); // Output the initial value } void PCF8574::write8 ( const uint8_t value ) { _dataOut = value; // Save value to be output Wire.beginTransmission ( _address ); // Start the transmission process Wire.write ( _dataOut ); // Send the data Wire.endTransmission (); // Wasn't in original library }