blob: 0d4ff5f17561190e24d1cd25638820ac9d994042 [file] [log] [blame]
#include "HAL/Digital.hpp"
#include "HAL/Port.h"
#include "HAL/HAL.hpp"
#include "ChipObject.h"
#include "HAL/cpp/Resource.hpp"
#include "HAL/cpp/priority_mutex.h"
#include "FRC_NetworkCommunication/LoadOut.h"
#include <stdio.h>
#include <math.h>
#include <mutex>
#include "i2clib/i2c-lib.h"
#include "spilib/spi-lib.h"
static_assert(sizeof(uint32_t) <= sizeof(void *), "This file shoves uint32_ts into pointers.");
static const uint32_t kExpectedLoopTiming = 40;
static const uint32_t kDigitalPins = 26;
static const uint32_t kPwmPins = 20;
static const uint32_t kRelayPins = 8;
static const uint32_t kNumHeaders = 10; // Number of non-MXP pins
/**
* kDefaultPwmPeriod is in ms
*
* - 20ms periods (50 Hz) are the "safest" setting in that this works for all devices
* - 20ms periods seem to be desirable for Vex Motors
* - 20ms periods are the specified period for HS-322HD servos, but work reliably down
* to 10.0 ms; starting at about 8.5ms, the servo sometimes hums and get hot;
* by 5.0ms the hum is nearly continuous
* - 10ms periods work well for Victor 884
* - 5ms periods allows higher update rates for Luminary Micro Jaguar speed controllers.
* Due to the shipping firmware on the Jaguar, we can't run the update period less
* than 5.05 ms.
*
* kDefaultPwmPeriod is the 1x period (5.05 ms). In hardware, the period scaling is implemented as an
* output squelch to get longer periods for old devices.
*/
static const float kDefaultPwmPeriod = 5.05;
/**
* kDefaultPwmCenter is the PWM range center in ms
*/
static const float kDefaultPwmCenter = 1.5;
/**
* kDefaultPWMStepsDown is the number of PWM steps below the centerpoint
*/
static const int32_t kDefaultPwmStepsDown = 1000;
static const int32_t kPwmDisabled = 0;
struct DigitalPort {
Port port;
uint32_t PWMGeneratorID;
};
// XXX: Set these back to static once we figure out the memory clobbering issue
// Create a mutex to protect changes to the digital output values
priority_recursive_mutex digitalDIOMutex;
// Create a mutex to protect changes to the relay values
priority_recursive_mutex digitalRelayMutex;
// Create a mutex to protect changes to the DO PWM config
priority_recursive_mutex digitalPwmMutex;
priority_recursive_mutex digitalI2COnBoardMutex;
priority_recursive_mutex digitalI2CMXPMutex;
tDIO* digitalSystem = NULL;
tRelay* relaySystem = NULL;
tPWM* pwmSystem = NULL;
hal::Resource *DIOChannels = NULL;
hal::Resource *DO_PWMGenerators = NULL;
hal::Resource *PWMChannels = NULL;
bool digitalSystemsInitialized = false;
uint8_t i2COnboardObjCount = 0;
uint8_t i2CMXPObjCount = 0;
uint8_t i2COnBoardHandle = 0;
uint8_t i2CMXPHandle = 0;
int32_t m_spiCS0Handle = 0;
int32_t m_spiCS1Handle = 0;
int32_t m_spiCS2Handle = 0;
int32_t m_spiCS3Handle = 0;
int32_t m_spiMXPHandle = 0;
priority_recursive_mutex spiOnboardSemaphore;
priority_recursive_mutex spiMXPSemaphore;
tSPI *spiSystem;
struct SPIAccumulator {
void* notifier = nullptr;
uint32_t triggerTime;
uint32_t period;
int64_t value = 0;
uint32_t count = 0;
int32_t last_value = 0;
int32_t center = 0;
int32_t deadband = 0;
uint8_t cmd[4]; // command to send (up to 4 bytes)
uint32_t valid_mask;
uint32_t valid_value;
int32_t data_max; // one more than max data value
int32_t data_msb_mask; // data field MSB mask (for signed)
uint8_t data_shift; // data field shift right amount, in bits
uint8_t xfer_size; // SPI transfer size, in bytes (up to 4)
uint8_t port;
bool is_signed; // is data field signed?
bool big_endian; // is response big endian?
};
SPIAccumulator* spiAccumulators[5] = {nullptr, nullptr, nullptr, nullptr, nullptr};
/**
* Initialize the digital system.
*/
void initializeDigital(int32_t *status) {
if (digitalSystemsInitialized) return;
hal::Resource::CreateResourceObject(&DIOChannels, tDIO::kNumSystems * kDigitalPins);
hal::Resource::CreateResourceObject(&DO_PWMGenerators, tDIO::kNumPWMDutyCycleAElements + tDIO::kNumPWMDutyCycleBElements);
hal::Resource::CreateResourceObject(&PWMChannels, tPWM::kNumSystems * kPwmPins);
digitalSystem = tDIO::create(status);
// Relay Setup
relaySystem = tRelay::create(status);
// Turn off all relay outputs.
relaySystem->writeValue_Forward(0, status);
relaySystem->writeValue_Reverse(0, status);
// PWM Setup
pwmSystem = tPWM::create(status);
// Make sure that the 9403 IONode has had a chance to initialize before continuing.
while(pwmSystem->readLoopTiming(status) == 0) delayTicks(1);
if (pwmSystem->readLoopTiming(status) != kExpectedLoopTiming) {
// TODO: char err[128];
// TODO: sprintf(err, "DIO LoopTiming: %d, expecting: %lu\n", digitalModules[port->module-1]->readLoopTiming(status), kExpectedLoopTiming);
*status = LOOP_TIMING_ERROR; // NOTE: Doesn't display the error
}
//Calculate the length, in ms, of one DIO loop
double loopTime = pwmSystem->readLoopTiming(status)/(kSystemClockTicksPerMicrosecond*1e3);
pwmSystem->writeConfig_Period((uint16_t) (kDefaultPwmPeriod/loopTime + .5), status);
uint16_t minHigh = (uint16_t) ((kDefaultPwmCenter-kDefaultPwmStepsDown*loopTime)/loopTime + .5);
pwmSystem->writeConfig_MinHigh(minHigh, status);
// printf("MinHigh: %d\n", minHigh);
// Ensure that PWM output values are set to OFF
for (uint32_t pwm_index = 0; pwm_index < kPwmPins; pwm_index++) {
// Initialize port structure
DigitalPort digital_port;
digital_port.port.pin = pwm_index;
setPWM(&digital_port, kPwmDisabled, status);
setPWMPeriodScale(&digital_port, 3, status); // Set all to 4x by default.
}
digitalSystemsInitialized = true;
}
/**
* Create a new instance of a digital port.
*/
void* initializeDigitalPort(void* port_pointer, int32_t *status) {
initializeDigital(status);
Port* port = (Port*) port_pointer;
// Initialize port structure
DigitalPort* digital_port = new DigitalPort();
digital_port->port = *port;
return digital_port;
}
void freeDigitalPort(void* digital_port_pointer) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
delete port;
}
bool checkPWMChannel(void* digital_port_pointer) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
return port->port.pin < kPwmPins;
}
bool checkRelayChannel(void* digital_port_pointer) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
return port->port.pin < kRelayPins;
}
/**
* Check a port to make sure that it is not NULL and is a valid PWM port.
*
* Sets the status to contain the appropriate error.
*
* @return true if the port passed validation.
*/
static bool verifyPWMChannel(DigitalPort *port, int32_t *status) {
if (port == NULL) {
*status = NULL_PARAMETER;
return false;
} else if (!checkPWMChannel(port)) {
*status = PARAMETER_OUT_OF_RANGE;
return false;
} else {
return true;
}
}
/**
* Check a port to make sure that it is not NULL and is a valid Relay port.
*
* Sets the status to contain the appropriate error.
*
* @return true if the port passed validation.
*/
static bool verifyRelayChannel(DigitalPort *port, int32_t *status) {
if (port == NULL) {
*status = NULL_PARAMETER;
return false;
} else if (!checkRelayChannel(port)) {
*status = PARAMETER_OUT_OF_RANGE;
return false;
} else {
return true;
}
}
/**
* Map DIO pin numbers from their physical number (10 to 26) to their position
* in the bit field.
*/
uint32_t remapMXPChannel(uint32_t pin) {
return pin - 10;
}
uint32_t remapMXPPWMChannel(uint32_t pin) {
if(pin < 14) {
return pin - 10; //first block of 4 pwms (MXP 0-3)
} else {
return pin - 6; //block of PWMs after SPI
}
}
/**
* Set a PWM channel to the desired value. The values range from 0 to 255 and the period is controlled
* by the PWM Period and MinHigh registers.
*
* @param channel The PWM channel to set.
* @param value The PWM value to set.
*/
void setPWM(void* digital_port_pointer, unsigned short value, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
if (!verifyPWMChannel(port, status)) { return; }
if(port->port.pin < tPWM::kNumHdrRegisters) {
pwmSystem->writeHdr(port->port.pin, value, status);
} else {
pwmSystem->writeMXP(port->port.pin - tPWM::kNumHdrRegisters, value, status);
}
}
/**
* Get a value from a PWM channel. The values range from 0 to 255.
*
* @param channel The PWM channel to read from.
* @return The raw PWM value.
*/
unsigned short getPWM(void* digital_port_pointer, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
if (!verifyPWMChannel(port, status)) { return 0; }
if(port->port.pin < tPWM::kNumHdrRegisters) {
return pwmSystem->readHdr(port->port.pin, status);
} else {
return pwmSystem->readMXP(port->port.pin - tPWM::kNumHdrRegisters, status);
}
}
void latchPWMZero(void* digital_port_pointer, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
if (!verifyPWMChannel(port, status)) { return; }
pwmSystem->writeZeroLatch(port->port.pin, true, status);
pwmSystem->writeZeroLatch(port->port.pin, false, status);
}
/**
* Set how how often the PWM signal is squelched, thus scaling the period.
*
* @param channel The PWM channel to configure.
* @param squelchMask The 2-bit mask of outputs to squelch.
*/
void setPWMPeriodScale(void* digital_port_pointer, uint32_t squelchMask, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
if (!verifyPWMChannel(port, status)) { return; }
if(port->port.pin < tPWM::kNumPeriodScaleHdrElements) {
pwmSystem->writePeriodScaleHdr(port->port.pin, squelchMask, status);
} else {
pwmSystem->writePeriodScaleMXP(port->port.pin - tPWM::kNumPeriodScaleHdrElements, squelchMask, status);
}
}
/**
* Allocate a DO PWM Generator.
* Allocate PWM generators so that they are not accidentally reused.
*
* @return PWM Generator refnum
*/
void* allocatePWM(int32_t *status) {
return (void*)DO_PWMGenerators->Allocate("DO_PWM");
}
/**
* Free the resource associated with a DO PWM generator.
*
* @param pwmGenerator The pwmGen to free that was allocated with AllocateDO_PWM()
*/
void freePWM(void* pwmGenerator, int32_t *status) {
uint32_t id = (uint32_t) pwmGenerator;
if (id == ~0ul) return;
DO_PWMGenerators->Free(id);
}
/**
* Change the frequency of the DO PWM generator.
*
* The valid range is from 0.6 Hz to 19 kHz. The frequency resolution is logarithmic.
*
* @param rate The frequency to output all digital output PWM signals.
*/
void setPWMRate(double rate, int32_t *status) {
// Currently rounding in the log rate domain... heavy weight toward picking a higher freq.
// TODO: Round in the linear rate domain.
uint8_t pwmPeriodPower = (uint8_t)(log(1.0 / (pwmSystem->readLoopTiming(status) * 0.25E-6 * rate))/log(2.0) + 0.5);
digitalSystem->writePWMPeriodPower(pwmPeriodPower, status);
}
/**
* Configure the duty-cycle of the PWM generator
*
* @param pwmGenerator The generator index reserved by AllocateDO_PWM()
* @param dutyCycle The percent duty cycle to output [0..1].
*/
void setPWMDutyCycle(void* pwmGenerator, double dutyCycle, int32_t *status) {
uint32_t id = (uint32_t) pwmGenerator;
if (id == ~0ul) return;
if (dutyCycle > 1.0) dutyCycle = 1.0;
if (dutyCycle < 0.0) dutyCycle = 0.0;
float rawDutyCycle = 256.0 * dutyCycle;
if (rawDutyCycle > 255.5) rawDutyCycle = 255.5;
{
std::lock_guard<priority_recursive_mutex> sync(digitalPwmMutex);
uint8_t pwmPeriodPower = digitalSystem->readPWMPeriodPower(status);
if (pwmPeriodPower < 4) {
// The resolution of the duty cycle drops close to the highest frequencies.
rawDutyCycle = rawDutyCycle / pow(2.0, 4 - pwmPeriodPower);
}
if(id < 4)
digitalSystem->writePWMDutyCycleA(id, (uint8_t)rawDutyCycle, status);
else
digitalSystem->writePWMDutyCycleB(id - 4, (uint8_t)rawDutyCycle, status);
}
}
/**
* Configure which DO channel the PWM signal is output on
*
* @param pwmGenerator The generator index reserved by AllocateDO_PWM()
* @param channel The Digital Output channel to output on
*/
void setPWMOutputChannel(void* pwmGenerator, uint32_t pin, int32_t *status) {
uint32_t id = (uint32_t) pwmGenerator;
if (id > 5) return;
digitalSystem->writePWMOutputSelect(id, pin, status);
}
/**
* Set the state of a relay.
* Set the state of a relay output to be forward. Relays have two outputs and each is
* independently set to 0v or 12v.
*/
void setRelayForward(void* digital_port_pointer, bool on, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
if (!verifyRelayChannel(port, status)) {
return;
}
{
std::lock_guard<priority_recursive_mutex> sync(digitalRelayMutex);
uint8_t forwardRelays = relaySystem->readValue_Forward(status);
if (on)
forwardRelays |= 1 << port->port.pin;
else
forwardRelays &= ~(1 << port->port.pin);
relaySystem->writeValue_Forward(forwardRelays, status);
}
}
/**
* Set the state of a relay.
* Set the state of a relay output to be reverse. Relays have two outputs and each is
* independently set to 0v or 12v.
*/
void setRelayReverse(void* digital_port_pointer, bool on, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
if (!verifyRelayChannel(port, status)) {
return;
}
{
std::lock_guard<priority_recursive_mutex> sync(digitalRelayMutex);
uint8_t reverseRelays = relaySystem->readValue_Reverse(status);
if (on)
reverseRelays |= 1 << port->port.pin;
else
reverseRelays &= ~(1 << port->port.pin);
relaySystem->writeValue_Reverse(reverseRelays, status);
}
}
/**
* Get the current state of the forward relay channel
*/
bool getRelayForward(void* digital_port_pointer, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
if (!verifyRelayChannel(port, status)) { return false; }
uint8_t forwardRelays = relaySystem->readValue_Forward(status);
return (forwardRelays & (1 << port->port.pin)) != 0;
}
/**
* Get the current state of the reverse relay channel
*/
bool getRelayReverse(void* digital_port_pointer, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
if (!verifyRelayChannel(port, status)) { return false; }
uint8_t reverseRelays = relaySystem->readValue_Reverse(status);
return (reverseRelays & (1 << port->port.pin)) != 0;
}
/**
* Allocate Digital I/O channels.
* Allocate channels so that they are not accidently reused. Also the direction is set at the
* time of the allocation.
*
* @param channel The Digital I/O channel
* @param input If true open as input; if false open as output
* @return Was successfully allocated
*/
bool allocateDIO(void* digital_port_pointer, bool input, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
char buf[64];
snprintf(buf, 64, "DIO %d", port->port.pin);
if (DIOChannels->Allocate(port->port.pin, buf) == ~0ul) {
*status = RESOURCE_IS_ALLOCATED;
return false;
}
{
std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex);
tDIO::tOutputEnable outputEnable = digitalSystem->readOutputEnable(status);
if(port->port.pin < kNumHeaders) {
uint32_t bitToSet = 1 << port->port.pin;
if (input) {
outputEnable.Headers = outputEnable.Headers & (~bitToSet); // clear the bit for read
} else {
outputEnable.Headers = outputEnable.Headers | bitToSet; // set the bit for write
}
} else {
uint32_t bitToSet = 1 << remapMXPChannel(port->port.pin);
// Disable special functions on this pin
short specialFunctions = digitalSystem->readEnableMXPSpecialFunction(status);
digitalSystem->writeEnableMXPSpecialFunction(specialFunctions & ~bitToSet, status);
if (input) {
outputEnable.MXP = outputEnable.MXP & (~bitToSet); // clear the bit for read
} else {
outputEnable.MXP = outputEnable.MXP | bitToSet; // set the bit for write
}
}
digitalSystem->writeOutputEnable(outputEnable, status);
}
return true;
}
bool allocatePWMChannel(void* digital_port_pointer, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
if (!verifyPWMChannel(port, status)) { return false; }
char buf[64];
snprintf(buf, 64, "PWM %d", port->port.pin);
if (PWMChannels->Allocate(port->port.pin, buf) == ~0ul) {
*status = RESOURCE_IS_ALLOCATED;
return false;
}
if (port->port.pin > tPWM::kNumHdrRegisters-1) {
snprintf(buf, 64, "PWM %d and DIO %d", port->port.pin, remapMXPPWMChannel(port->port.pin) + 10);
if (DIOChannels->Allocate(remapMXPPWMChannel(port->port.pin) + 10, buf) == ~0ul) return false;
uint32_t bitToSet = 1 << remapMXPPWMChannel(port->port.pin);
short specialFunctions = digitalSystem->readEnableMXPSpecialFunction(status);
digitalSystem->writeEnableMXPSpecialFunction(specialFunctions | bitToSet, status);
}
return true;
}
void freePWMChannel(void* digital_port_pointer, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
if (!port) return;
if (!verifyPWMChannel(port, status)) { return; }
PWMChannels->Free(port->port.pin);
if(port->port.pin > tPWM::kNumHdrRegisters-1) {
DIOChannels->Free(remapMXPPWMChannel(port->port.pin) + 10);
uint32_t bitToUnset = 1 << remapMXPPWMChannel(port->port.pin);
short specialFunctions = digitalSystem->readEnableMXPSpecialFunction(status);
digitalSystem->writeEnableMXPSpecialFunction(specialFunctions & ~bitToUnset, status);
}
}
/**
* Free the resource associated with a digital I/O channel.
*
* @param channel The Digital I/O channel to free
*/
void freeDIO(void* digital_port_pointer, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
if (!port) return;
DIOChannels->Free(port->port.pin);
}
/**
* Write a digital I/O bit to the FPGA.
* Set a single value on a digital I/O channel.
*
* @param channel The Digital I/O channel
* @param value The state to set the digital channel (if it is configured as an output)
*/
void setDIO(void* digital_port_pointer, short value, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
if (value != 0 && value != 1) {
if (value != 0)
value = 1;
}
{
std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex);
tDIO::tDO currentDIO = digitalSystem->readDO(status);
if(port->port.pin < kNumHeaders) {
if(value == 0) {
currentDIO.Headers = currentDIO.Headers & ~(1 << port->port.pin);
} else if (value == 1) {
currentDIO.Headers = currentDIO.Headers | (1 << port->port.pin);
}
} else {
if(value == 0) {
currentDIO.MXP = currentDIO.MXP & ~(1 << remapMXPChannel(port->port.pin));
} else if (value == 1) {
currentDIO.MXP = currentDIO.MXP | (1 << remapMXPChannel(port->port.pin));
}
uint32_t bitToSet = 1 << remapMXPChannel(port->port.pin);
short specialFunctions = digitalSystem->readEnableMXPSpecialFunction(status);
digitalSystem->writeEnableMXPSpecialFunction(specialFunctions & ~bitToSet, status);
}
digitalSystem->writeDO(currentDIO, status);
}
}
/**
* Read a digital I/O bit from the FPGA.
* Get a single value from a digital I/O channel.
*
* @param channel The digital I/O channel
* @return The state of the specified channel
*/
bool getDIO(void* digital_port_pointer, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
tDIO::tDI currentDIO = digitalSystem->readDI(status);
//Shift 00000001 over channel-1 places.
//AND it against the currentDIO
//if it == 0, then return false
//else return true
if(port->port.pin < kNumHeaders) {
return ((currentDIO.Headers >> port->port.pin) & 1) != 0;
} else {
// Disable special functions
uint32_t bitToSet = 1 << remapMXPChannel(port->port.pin);
short specialFunctions = digitalSystem->readEnableMXPSpecialFunction(status);
digitalSystem->writeEnableMXPSpecialFunction(specialFunctions & ~bitToSet, status);
return ((currentDIO.MXP >> remapMXPChannel(port->port.pin)) & 1) != 0;
}
}
/**
* Read the direction of a the Digital I/O lines
* A 1 bit means output and a 0 bit means input.
*
* @param channel The digital I/O channel
* @return The direction of the specified channel
*/
bool getDIODirection(void* digital_port_pointer, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
tDIO::tOutputEnable currentOutputEnable = digitalSystem->readOutputEnable(status);
//Shift 00000001 over port->port.pin-1 places.
//AND it against the currentOutputEnable
//if it == 0, then return false
//else return true
if(port->port.pin < kNumHeaders) {
return ((currentOutputEnable.Headers >> port->port.pin) & 1) != 0;
} else {
return ((currentOutputEnable.MXP >> remapMXPChannel(port->port.pin)) & 1) != 0;
}
}
/**
* Generate a single pulse.
* Write a pulse to the specified digital output channel. There can only be a single pulse going at any time.
*
* @param channel The Digital Output channel that the pulse should be output on
* @param pulseLength The active length of the pulse (in seconds)
*/
void pulse(void* digital_port_pointer, double pulseLength, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
tDIO::tPulse pulse;
if(port->port.pin < kNumHeaders) {
pulse.Headers = 1 << port->port.pin;
} else {
pulse.MXP = 1 << remapMXPChannel(port->port.pin);
}
digitalSystem->writePulseLength((uint8_t)(1.0e9 * pulseLength / (pwmSystem->readLoopTiming(status) * 25)), status);
digitalSystem->writePulse(pulse, status);
}
/**
* Check a DIO line to see if it is currently generating a pulse.
*
* @return A pulse is in progress
*/
bool isPulsing(void* digital_port_pointer, int32_t *status) {
DigitalPort* port = (DigitalPort*) digital_port_pointer;
tDIO::tPulse pulseRegister = digitalSystem->readPulse(status);
if(port->port.pin < kNumHeaders) {
return (pulseRegister.Headers & (1 << port->port.pin)) != 0;
} else {
return (pulseRegister.MXP & (1 << remapMXPChannel(port->port.pin))) != 0;
}
}
/**
* Check if any DIO line is currently generating a pulse.
*
* @return A pulse on some line is in progress
*/
bool isAnyPulsing(int32_t *status) {
tDIO::tPulse pulseRegister = digitalSystem->readPulse(status);
return pulseRegister.Headers != 0 && pulseRegister.MXP != 0;
}
/**
* Write the filter index from the FPGA.
* Set the filter index used to filter out short pulses.
*
* @param digital_port_pointer The digital I/O channel
* @param filter_index The filter index. Must be in the range 0 - 3,
* where 0 means "none" and 1 - 3 means filter # filter_index - 1.
*/
void setFilterSelect(void* digital_port_pointer, int filter_index,
int32_t* status) {
DigitalPort* port = (DigitalPort*)digital_port_pointer;
std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex);
if (port->port.pin < kNumHeaders) {
digitalSystem->writeFilterSelectHdr(port->port.pin, filter_index, status);
}
else {
digitalSystem->writeFilterSelectMXP(remapMXPChannel(port->port.pin),
filter_index, status);
}
}
/**
* Read the filter index from the FPGA.
* Get the filter index used to filter out short pulses.
*
* @param digital_port_pointer The digital I/O channel
* @return filter_index The filter index. Must be in the range 0 - 3,
* where 0 means "none" and 1 - 3 means filter # filter_index - 1.
*/
int getFilterSelect(void* digital_port_pointer, int32_t* status) {
DigitalPort* port = (DigitalPort*)digital_port_pointer;
std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex);
if (port->port.pin < kNumHeaders) {
return digitalSystem->readFilterSelectHdr(port->port.pin, status);
}
else {
return digitalSystem->readFilterSelectMXP(remapMXPChannel(port->port.pin),
status);
}
}
/**
* Set the filter period for the specified filter index.
*
* Set the filter period in FPGA cycles. Even though there are 2 different
* filter index domains (MXP vs HDR), ignore that distinction for now since it
* compilicates the interface. That can be changed later.
*
* @param filter_index The filter index, 0 - 2.
* @param value The number of cycles that the signal must not transition to be
* counted as a transition.
*/
void setFilterPeriod(int filter_index, uint32_t value, int32_t* status) {
std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex);
digitalSystem->writeFilterPeriodHdr(filter_index, value, status);
if (*status == 0) {
digitalSystem->writeFilterPeriodMXP(filter_index, value, status);
}
}
/**
* Get the filter period for the specified filter index.
*
* Get the filter period in FPGA cycles. Even though there are 2 different
* filter index domains (MXP vs HDR), ignore that distinction for now since it
* compilicates the interface. Set status to NiFpga_Status_SoftwareFault if the
* filter values miss-match.
*
* @param filter_index The filter index, 0 - 2.
* @param value The number of cycles that the signal must not transition to be
* counted as a transition.
*/
uint32_t getFilterPeriod(int filter_index, int32_t* status) {
uint32_t hdr_period = 0;
uint32_t mxp_period = 0;
{
std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex);
hdr_period = digitalSystem->readFilterPeriodHdr(filter_index, status);
if (*status == 0) {
mxp_period = digitalSystem->readFilterPeriodMXP(filter_index, status);
}
}
if (hdr_period != mxp_period) {
*status = NiFpga_Status_SoftwareFault;
return -1;
}
return hdr_period;
}
struct counter_t {
tCounter* counter;
uint32_t index;
};
typedef struct counter_t Counter;
static hal::Resource *counters = NULL;
void* initializeCounter(Mode mode, uint32_t *index, int32_t *status) {
hal::Resource::CreateResourceObject(&counters, tCounter::kNumSystems);
*index = counters->Allocate("Counter");
if (*index == ~0ul) {
*status = NO_AVAILABLE_RESOURCES;
return NULL;
}
Counter* counter = new Counter();
counter->counter = tCounter::create(*index, status);
counter->counter->writeConfig_Mode(mode, status);
counter->counter->writeTimerConfig_AverageSize(1, status);
counter->index = *index;
return counter;
}
void freeCounter(void* counter_pointer, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
if (!counter) return;
delete counter->counter;
counters->Free(counter->index);
}
void setCounterAverageSize(void* counter_pointer, int32_t size, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
counter->counter->writeTimerConfig_AverageSize(size, status);
}
/**
* remap the digital source pin and set the module.
* If it's an analog trigger, determine the module from the high order routing channel
* else do normal digital input remapping based on pin number (MXP)
*/
void remapDigitalSource(bool analogTrigger, uint32_t &pin, uint8_t &module) {
if (analogTrigger) {
module = pin >> 4;
} else {
if(pin >= kNumHeaders) {
pin = remapMXPChannel(pin);
module = 1;
} else {
module = 0;
}
}
}
/**
* Set the source object that causes the counter to count up.
* Set the up counting DigitalSource.
*/
void setCounterUpSource(void* counter_pointer, uint32_t pin, bool analogTrigger, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
uint8_t module;
remapDigitalSource(analogTrigger, pin, module);
counter->counter->writeConfig_UpSource_Module(module, status);
counter->counter->writeConfig_UpSource_Channel(pin, status);
counter->counter->writeConfig_UpSource_AnalogTrigger(analogTrigger, status);
if(counter->counter->readConfig_Mode(status) == kTwoPulse ||
counter->counter->readConfig_Mode(status) == kExternalDirection) {
setCounterUpSourceEdge(counter_pointer, true, false, status);
}
counter->counter->strobeReset(status);
}
/**
* Set the edge sensitivity on an up counting source.
* Set the up source to either detect rising edges or falling edges.
*/
void setCounterUpSourceEdge(void* counter_pointer, bool risingEdge, bool fallingEdge, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
counter->counter->writeConfig_UpRisingEdge(risingEdge, status);
counter->counter->writeConfig_UpFallingEdge(fallingEdge, status);
}
/**
* Disable the up counting source to the counter.
*/
void clearCounterUpSource(void* counter_pointer, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
counter->counter->writeConfig_UpFallingEdge(false, status);
counter->counter->writeConfig_UpRisingEdge(false, status);
// Index 0 of digital is always 0.
counter->counter->writeConfig_UpSource_Channel(0, status);
counter->counter->writeConfig_UpSource_AnalogTrigger(false, status);
}
/**
* Set the source object that causes the counter to count down.
* Set the down counting DigitalSource.
*/
void setCounterDownSource(void* counter_pointer, uint32_t pin, bool analogTrigger, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
unsigned char mode = counter->counter->readConfig_Mode(status);
if (mode != kTwoPulse && mode != kExternalDirection) {
// TODO: wpi_setWPIErrorWithContext(ParameterOutOfRange, "Counter only supports DownSource in TwoPulse and ExternalDirection modes.");
*status = PARAMETER_OUT_OF_RANGE;
return;
}
uint8_t module;
remapDigitalSource(analogTrigger, pin, module);
counter->counter->writeConfig_DownSource_Module(module, status);
counter->counter->writeConfig_DownSource_Channel(pin, status);
counter->counter->writeConfig_DownSource_AnalogTrigger(analogTrigger, status);
setCounterDownSourceEdge(counter_pointer, true, false, status);
counter->counter->strobeReset(status);
}
/**
* Set the edge sensitivity on a down counting source.
* Set the down source to either detect rising edges or falling edges.
*/
void setCounterDownSourceEdge(void* counter_pointer, bool risingEdge, bool fallingEdge, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
counter->counter->writeConfig_DownRisingEdge(risingEdge, status);
counter->counter->writeConfig_DownFallingEdge(fallingEdge, status);
}
/**
* Disable the down counting source to the counter.
*/
void clearCounterDownSource(void* counter_pointer, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
counter->counter->writeConfig_DownFallingEdge(false, status);
counter->counter->writeConfig_DownRisingEdge(false, status);
// Index 0 of digital is always 0.
counter->counter->writeConfig_DownSource_Channel(0, status);
counter->counter->writeConfig_DownSource_AnalogTrigger(false, status);
}
/**
* Set standard up / down counting mode on this counter.
* Up and down counts are sourced independently from two inputs.
*/
void setCounterUpDownMode(void* counter_pointer, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
counter->counter->writeConfig_Mode(kTwoPulse, status);
}
/**
* Set external direction mode on this counter.
* Counts are sourced on the Up counter input.
* The Down counter input represents the direction to count.
*/
void setCounterExternalDirectionMode(void* counter_pointer, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
counter->counter->writeConfig_Mode(kExternalDirection, status);
}
/**
* Set Semi-period mode on this counter.
* Counts up on both rising and falling edges.
*/
void setCounterSemiPeriodMode(void* counter_pointer, bool highSemiPeriod, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
counter->counter->writeConfig_Mode(kSemiperiod, status);
counter->counter->writeConfig_UpRisingEdge(highSemiPeriod, status);
setCounterUpdateWhenEmpty(counter_pointer, false, status);
}
/**
* Configure the counter to count in up or down based on the length of the input pulse.
* This mode is most useful for direction sensitive gear tooth sensors.
* @param threshold The pulse length beyond which the counter counts the opposite direction. Units are seconds.
*/
void setCounterPulseLengthMode(void* counter_pointer, double threshold, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
counter->counter->writeConfig_Mode(kPulseLength, status);
counter->counter->writeConfig_PulseLengthThreshold((uint32_t)(threshold * 1.0e6) * kSystemClockTicksPerMicrosecond, status);
}
/**
* Get the Samples to Average which specifies the number of samples of the timer to
* average when calculating the period. Perform averaging to account for
* mechanical imperfections or as oversampling to increase resolution.
* @return SamplesToAverage The number of samples being averaged (from 1 to 127)
*/
int32_t getCounterSamplesToAverage(void* counter_pointer, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
return counter->counter->readTimerConfig_AverageSize(status);
}
/**
* Set the Samples to Average which specifies the number of samples of the timer to
* average when calculating the period. Perform averaging to account for
* mechanical imperfections or as oversampling to increase resolution.
* @param samplesToAverage The number of samples to average from 1 to 127.
*/
void setCounterSamplesToAverage(void* counter_pointer, int samplesToAverage, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
if (samplesToAverage < 1 || samplesToAverage > 127) {
*status = PARAMETER_OUT_OF_RANGE;
}
counter->counter->writeTimerConfig_AverageSize(samplesToAverage, status);
}
/**
* Reset the Counter to zero.
* Set the counter value to zero. This doesn't effect the running state of the counter, just sets
* the current value to zero.
*/
void resetCounter(void* counter_pointer, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
counter->counter->strobeReset(status);
}
/**
* Read the current counter value.
* Read the value at this instant. It may still be running, so it reflects the current value. Next
* time it is read, it might have a different value.
*/
int32_t getCounter(void* counter_pointer, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
int32_t value = counter->counter->readOutput_Value(status);
return value;
}
/*
* Get the Period of the most recent count.
* Returns the time interval of the most recent count. This can be used for velocity calculations
* to determine shaft speed.
* @returns The period of the last two pulses in units of seconds.
*/
double getCounterPeriod(void* counter_pointer, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
tCounter::tTimerOutput output = counter->counter->readTimerOutput(status);
double period;
if (output.Stalled) {
// Return infinity
double zero = 0.0;
period = 1.0 / zero;
} else {
// output.Period is a fixed point number that counts by 2 (24 bits, 25 integer bits)
period = (double)(output.Period << 1) / (double)output.Count;
}
return period * 2.5e-8; // result * timebase (currently 40ns)
}
/**
* Set the maximum period where the device is still considered "moving".
* Sets the maximum period where the device is considered moving. This value is used to determine
* the "stopped" state of the counter using the GetStopped method.
* @param maxPeriod The maximum period where the counted device is considered moving in
* seconds.
*/
void setCounterMaxPeriod(void* counter_pointer, double maxPeriod, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
counter->counter->writeTimerConfig_StallPeriod((uint32_t)(maxPeriod * 4.0e8), status);
}
/**
* Select whether you want to continue updating the event timer output when there are no samples captured.
* The output of the event timer has a buffer of periods that are averaged and posted to
* a register on the FPGA. When the timer detects that the event source has stopped
* (based on the MaxPeriod) the buffer of samples to be averaged is emptied. If you
* enable the update when empty, you will be notified of the stopped source and the event
* time will report 0 samples. If you disable update when empty, the most recent average
* will remain on the output until a new sample is acquired. You will never see 0 samples
* output (except when there have been no events since an FPGA reset) and you will likely not
* see the stopped bit become true (since it is updated at the end of an average and there are
* no samples to average).
*/
void setCounterUpdateWhenEmpty(void* counter_pointer, bool enabled, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
counter->counter->writeTimerConfig_UpdateWhenEmpty(enabled, status);
}
/**
* Determine if the clock is stopped.
* Determine if the clocked input is stopped based on the MaxPeriod value set using the
* SetMaxPeriod method. If the clock exceeds the MaxPeriod, then the device (and counter) are
* assumed to be stopped and it returns true.
* @return Returns true if the most recent counter period exceeds the MaxPeriod value set by
* SetMaxPeriod.
*/
bool getCounterStopped(void* counter_pointer, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
return counter->counter->readTimerOutput_Stalled(status);
}
/**
* The last direction the counter value changed.
* @return The last direction the counter value changed.
*/
bool getCounterDirection(void* counter_pointer, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
bool value = counter->counter->readOutput_Direction(status);
return value;
}
/**
* Set the Counter to return reversed sensing on the direction.
* This allows counters to change the direction they are counting in the case of 1X and 2X
* quadrature encoding only. Any other counter mode isn't supported.
* @param reverseDirection true if the value counted should be negated.
*/
void setCounterReverseDirection(void* counter_pointer, bool reverseDirection, int32_t *status) {
Counter* counter = (Counter*) counter_pointer;
if (counter->counter->readConfig_Mode(status) == kExternalDirection) {
if (reverseDirection)
setCounterDownSourceEdge(counter_pointer, true, true, status);
else
setCounterDownSourceEdge(counter_pointer, false, true, status);
}
}
struct encoder_t {
tEncoder* encoder;
uint32_t index;
};
typedef struct encoder_t Encoder;
static const double DECODING_SCALING_FACTOR = 0.25;
static hal::Resource *quadEncoders = NULL;
void* initializeEncoder(uint8_t port_a_module, uint32_t port_a_pin, bool port_a_analog_trigger,
uint8_t port_b_module, uint32_t port_b_pin, bool port_b_analog_trigger,
bool reverseDirection, int32_t *index, int32_t *status) {
// Initialize encoder structure
Encoder* encoder = new Encoder();
remapDigitalSource(port_a_analog_trigger, port_a_pin, port_a_module);
remapDigitalSource(port_b_analog_trigger, port_b_pin, port_b_module);
hal::Resource::CreateResourceObject(&quadEncoders, tEncoder::kNumSystems);
encoder->index = quadEncoders->Allocate("4X Encoder");
*index = encoder->index;
// TODO: if (index == ~0ul) { CloneError(quadEncoders); return; }
encoder->encoder = tEncoder::create(encoder->index, status);
encoder->encoder->writeConfig_ASource_Module(port_a_module, status);
encoder->encoder->writeConfig_ASource_Channel(port_a_pin, status);
encoder->encoder->writeConfig_ASource_AnalogTrigger(port_a_analog_trigger, status);
encoder->encoder->writeConfig_BSource_Module(port_b_module, status);
encoder->encoder->writeConfig_BSource_Channel(port_b_pin, status);
encoder->encoder->writeConfig_BSource_AnalogTrigger(port_b_analog_trigger, status);
encoder->encoder->strobeReset(status);
encoder->encoder->writeConfig_Reverse(reverseDirection, status);
encoder->encoder->writeTimerConfig_AverageSize(4, status);
return encoder;
}
void freeEncoder(void* encoder_pointer, int32_t *status) {
Encoder* encoder = (Encoder*) encoder_pointer;
if (!encoder) return;
quadEncoders->Free(encoder->index);
delete encoder->encoder;
}
/**
* Reset the Encoder distance to zero.
* Resets the current count to zero on the encoder.
*/
void resetEncoder(void* encoder_pointer, int32_t *status) {
Encoder* encoder = (Encoder*) encoder_pointer;
encoder->encoder->strobeReset(status);
}
/**
* Gets the raw value from the encoder.
* The raw value is the actual count unscaled by the 1x, 2x, or 4x scale
* factor.
* @return Current raw count from the encoder
*/
int32_t getEncoder(void* encoder_pointer, int32_t *status) {
Encoder* encoder = (Encoder*) encoder_pointer;
return encoder->encoder->readOutput_Value(status);
}
/**
* Returns the period of the most recent pulse.
* Returns the period of the most recent Encoder pulse in seconds.
* This method compenstates for the decoding type.
*
* @deprecated Use GetRate() in favor of this method. This returns unscaled periods and GetRate() scales using value from SetDistancePerPulse().
*
* @return Period in seconds of the most recent pulse.
*/
double getEncoderPeriod(void* encoder_pointer, int32_t *status) {
Encoder* encoder = (Encoder*) encoder_pointer;
tEncoder::tTimerOutput output = encoder->encoder->readTimerOutput(status);
double value;
if (output.Stalled) {
// Return infinity
double zero = 0.0;
value = 1.0 / zero;
} else {
// output.Period is a fixed point number that counts by 2 (24 bits, 25 integer bits)
value = (double)(output.Period << 1) / (double)output.Count;
}
double measuredPeriod = value * 2.5e-8;
return measuredPeriod / DECODING_SCALING_FACTOR;
}
/**
* Sets the maximum period for stopped detection.
* Sets the value that represents the maximum period of the Encoder before it will assume
* that the attached device is stopped. This timeout allows users to determine if the wheels or
* other shaft has stopped rotating.
* This method compensates for the decoding type.
*
* @deprecated Use SetMinRate() in favor of this method. This takes unscaled periods and SetMinRate() scales using value from SetDistancePerPulse().
*
* @param maxPeriod The maximum time between rising and falling edges before the FPGA will
* report the device stopped. This is expressed in seconds.
*/
void setEncoderMaxPeriod(void* encoder_pointer, double maxPeriod, int32_t *status) {
Encoder* encoder = (Encoder*) encoder_pointer;
encoder->encoder->writeTimerConfig_StallPeriod((uint32_t)(maxPeriod * 4.0e8 * DECODING_SCALING_FACTOR), status);
}
/**
* Determine if the encoder is stopped.
* Using the MaxPeriod value, a boolean is returned that is true if the encoder is considered
* stopped and false if it is still moving. A stopped encoder is one where the most recent pulse
* width exceeds the MaxPeriod.
* @return True if the encoder is considered stopped.
*/
bool getEncoderStopped(void* encoder_pointer, int32_t *status) {
Encoder* encoder = (Encoder*) encoder_pointer;
return encoder->encoder->readTimerOutput_Stalled(status) != 0;
}
/**
* The last direction the encoder value changed.
* @return The last direction the encoder value changed.
*/
bool getEncoderDirection(void* encoder_pointer, int32_t *status) {
Encoder* encoder = (Encoder*) encoder_pointer;
return encoder->encoder->readOutput_Direction(status);
}
/**
* Set the direction sensing for this encoder.
* This sets the direction sensing on the encoder so that it could count in the correct
* software direction regardless of the mounting.
* @param reverseDirection true if the encoder direction should be reversed
*/
void setEncoderReverseDirection(void* encoder_pointer, bool reverseDirection, int32_t *status) {
Encoder* encoder = (Encoder*) encoder_pointer;
encoder->encoder->writeConfig_Reverse(reverseDirection, status);
}
/**
* Set the Samples to Average which specifies the number of samples of the timer to
* average when calculating the period. Perform averaging to account for
* mechanical imperfections or as oversampling to increase resolution.
* @param samplesToAverage The number of samples to average from 1 to 127.
*/
void setEncoderSamplesToAverage(void* encoder_pointer, uint32_t samplesToAverage, int32_t *status) {
Encoder* encoder = (Encoder*) encoder_pointer;
if (samplesToAverage < 1 || samplesToAverage > 127) {
*status = PARAMETER_OUT_OF_RANGE;
}
encoder->encoder->writeTimerConfig_AverageSize(samplesToAverage, status);
}
/**
* Get the Samples to Average which specifies the number of samples of the timer to
* average when calculating the period. Perform averaging to account for
* mechanical imperfections or as oversampling to increase resolution.
* @return SamplesToAverage The number of samples being averaged (from 1 to 127)
*/
uint32_t getEncoderSamplesToAverage(void* encoder_pointer, int32_t *status) {
Encoder* encoder = (Encoder*) encoder_pointer;
return encoder->encoder->readTimerConfig_AverageSize(status);
}
/**
* Set an index source for an encoder, which is an input that resets the
* encoder's count.
*/
void setEncoderIndexSource(void *encoder_pointer, uint32_t pin, bool analogTrigger, bool activeHigh,
bool edgeSensitive, int32_t *status) {
Encoder* encoder = (Encoder*) encoder_pointer;
encoder->encoder->writeConfig_IndexSource_Channel((unsigned char)pin, status);
encoder->encoder->writeConfig_IndexSource_Module((unsigned char)0, status);
encoder->encoder->writeConfig_IndexSource_AnalogTrigger(analogTrigger, status);
encoder->encoder->writeConfig_IndexActiveHigh(activeHigh, status);
encoder->encoder->writeConfig_IndexEdgeSensitive(edgeSensitive, status);
}
/**
* Get the loop timing of the PWM system
*
* @return The loop time
*/
uint16_t getLoopTiming(int32_t *status) {
return pwmSystem->readLoopTiming(status);
}
/*
* Initialize the spi port. Opens the port if necessary and saves the handle.
* If opening the MXP port, also sets up the pin functions appropriately
* @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
*/
void spiInitialize(uint8_t port, int32_t *status) {
if(spiSystem == NULL)
spiSystem = tSPI::create(status);
if(spiGetHandle(port) !=0 ) return;
switch(port){
case 0:
spiSetHandle(0, spilib_open("/dev/spidev0.0"));
break;
case 1:
spiSetHandle(1, spilib_open("/dev/spidev0.1"));
break;
case 2:
spiSetHandle(2, spilib_open("/dev/spidev0.2"));
break;
case 3:
spiSetHandle(3, spilib_open("/dev/spidev0.3"));
break;
case 4:
initializeDigital(status);
if(!allocateDIO(getPort(14), false, status)){printf("Failed to allocate DIO 14\n"); return;}
if(!allocateDIO(getPort(15), false, status)) {printf("Failed to allocate DIO 15\n"); return;}
if(!allocateDIO(getPort(16), true, status)) {printf("Failed to allocate DIO 16\n"); return;}
if(!allocateDIO(getPort(17), false, status)) {printf("Failed to allocate DIO 17\n"); return;}
digitalSystem->writeEnableMXPSpecialFunction(digitalSystem->readEnableMXPSpecialFunction(status)|0x00F0, status);
spiSetHandle(4, spilib_open("/dev/spidev1.0"));
break;
default:
break;
}
return;
}
/**
* Generic transaction.
*
* This is a lower-level interface to the spi hardware giving you more control over each transaction.
*
* @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
* @param dataToSend Buffer of data to send as part of the transaction.
* @param dataReceived Buffer to read data into.
* @param size Number of bytes to transfer. [0..7]
* @return Number of bytes transferred, -1 for error
*/
int32_t spiTransaction(uint8_t port, uint8_t *dataToSend, uint8_t *dataReceived, uint8_t size)
{
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
return spilib_writeread(spiGetHandle(port), (const char*) dataToSend, (char*) dataReceived, (int32_t) size);
}
/**
* Execute a write transaction with the device.
*
* Write to a device and wait until the transaction is complete.
*
* @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
* @param datToSend The data to write to the register on the device.
* @param sendSize The number of bytes to be written
* @return The number of bytes written. -1 for an error
*/
int32_t spiWrite(uint8_t port, uint8_t* dataToSend, uint8_t sendSize)
{
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
return spilib_write(spiGetHandle(port), (const char*) dataToSend, (int32_t) sendSize);
}
/**
* Execute a read from the device.
*
* This methdod does not write any data out to the device
* Most spi devices will require a register address to be written before
* they begin returning data
*
* @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
* @param buffer A pointer to the array of bytes to store the data read from the device.
* @param count The number of bytes to read in the transaction. [1..7]
* @return Number of bytes read. -1 for error.
*/
int32_t spiRead(uint8_t port, uint8_t *buffer, uint8_t count)
{
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
return spilib_read(spiGetHandle(port), (char*) buffer, (int32_t) count);
}
/**
* Close the SPI port
*
* @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
*/
void spiClose(uint8_t port) {
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
if (spiAccumulators[port]) {
int32_t status = 0;
spiFreeAccumulator(port, &status);
}
spilib_close(spiGetHandle(port));
spiSetHandle(port, 0);
return;
}
/**
* Set the clock speed for the SPI bus.
*
* @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
* @param speed The speed in Hz (0-1MHz)
*/
void spiSetSpeed(uint8_t port, uint32_t speed) {
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
spilib_setspeed(spiGetHandle(port), speed);
}
/**
* Set the SPI options
*
* @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
* @param msb_first True to write the MSB first, False for LSB first
* @param sample_on_trailing True to sample on the trailing edge, False to sample on the leading edge
* @param clk_idle_high True to set the clock to active low, False to set the clock active high
*/
void spiSetOpts(uint8_t port, int msb_first, int sample_on_trailing, int clk_idle_high) {
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
spilib_setopts(spiGetHandle(port), msb_first, sample_on_trailing, clk_idle_high);
}
/**
* Set the CS Active high for a SPI port
*
* @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
*/
void spiSetChipSelectActiveHigh(uint8_t port, int32_t *status){
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
if(port < 4)
{
spiSystem->writeChipSelectActiveHigh_Hdr(spiSystem->readChipSelectActiveHigh_Hdr(status) | (1<<port), status);
}
else
{
spiSystem->writeChipSelectActiveHigh_MXP(1, status);
}
}
/**
* Set the CS Active low for a SPI port
*
* @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
*/
void spiSetChipSelectActiveLow(uint8_t port, int32_t *status){
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
if(port < 4)
{
spiSystem->writeChipSelectActiveHigh_Hdr(spiSystem->readChipSelectActiveHigh_Hdr(status) & ~(1<<port), status);
}
else
{
spiSystem->writeChipSelectActiveHigh_MXP(0, status);
}
}
/**
* Get the stored handle for a SPI port
*
* @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
* @return The stored handle for the SPI port. 0 represents no stored handle.
*/
int32_t spiGetHandle(uint8_t port){
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
switch(port){
case 0:
return m_spiCS0Handle;
case 1:
return m_spiCS1Handle;
case 2:
return m_spiCS2Handle;
case 3:
return m_spiCS3Handle;
case 4:
return m_spiMXPHandle;
default:
return 0;
}
}
/**
* Set the stored handle for a SPI port
*
* @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP.
* @param handle The value of the handle for the port.
*/
void spiSetHandle(uint8_t port, int32_t handle){
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
switch(port){
case 0:
m_spiCS0Handle = handle;
break;
case 1:
m_spiCS1Handle = handle;
break;
case 2:
m_spiCS2Handle = handle;
break;
case 3:
m_spiCS3Handle = handle;
break;
case 4:
m_spiMXPHandle = handle;
break;
default:
break;
}
}
/**
* Get the semaphore for a SPI port
*
* @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
* @return The semaphore for the SPI port.
*/
priority_recursive_mutex& spiGetSemaphore(uint8_t port) {
if(port < 4)
return spiOnboardSemaphore;
else
return spiMXPSemaphore;
}
static void spiAccumulatorProcess(uint32_t currentTime, void *param) {
SPIAccumulator* accum = (SPIAccumulator*)param;
// perform SPI transaction
uint8_t resp_b[4];
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(accum->port));
spilib_writeread(spiGetHandle(accum->port), (const char*) accum->cmd, (char*) resp_b, (int32_t) accum->xfer_size);
// convert from bytes
uint32_t resp = 0;
if (accum->big_endian) {
for (int i=0; i < accum->xfer_size; ++i) {
resp <<= 8;
resp |= resp_b[i] & 0xff;
}
} else {
for (int i = accum->xfer_size - 1; i >= 0; --i) {
resp <<= 8;
resp |= resp_b[i] & 0xff;
}
}
// process response
if ((resp & accum->valid_mask) == accum->valid_value) {
// valid sensor data; extract data field
int32_t data = (int32_t)(resp >> accum->data_shift);
data &= accum->data_max - 1;
// 2s complement conversion if signed MSB is set
if (accum->is_signed && (data & accum->data_msb_mask) != 0)
data -= accum->data_max;
// center offset
data -= accum->center;
// only accumulate if outside deadband
if (data < -accum->deadband || data > accum->deadband)
accum->value += data;
++accum->count;
accum->last_value = data;
} else {
// no data from the sensor; just clear the last value
accum->last_value = 0;
}
// reschedule timer
accum->triggerTime += accum->period;
// handle timer slip
if (accum->triggerTime < currentTime)
accum->triggerTime = currentTime + accum->period;
int32_t status = 0;
updateNotifierAlarm(accum->notifier, accum->triggerTime, &status);
}
/**
* Initialize a SPI accumulator.
*
* @param port SPI port
* @param period Time between reads, in us
* @param cmd SPI command to send to request data
* @param xfer_size SPI transfer size, in bytes
* @param valid_mask Mask to apply to received data for validity checking
* @param valid_data After valid_mask is applied, required matching value for
* validity checking
* @param data_shift Bit shift to apply to received data to get actual data
* value
* @param data_size Size (in bits) of data field
* @param is_signed Is data field signed?
* @param big_endian Is device big endian?
*/
void spiInitAccumulator(uint8_t port, uint32_t period, uint32_t cmd,
uint8_t xfer_size, uint32_t valid_mask, uint32_t valid_value,
uint8_t data_shift, uint8_t data_size, bool is_signed,
bool big_endian, int32_t *status) {
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
if (port > 4) return;
if (!spiAccumulators[port])
spiAccumulators[port] = new SPIAccumulator();
SPIAccumulator* accum = spiAccumulators[port];
if (big_endian) {
for (int i = xfer_size - 1; i >= 0; --i) {
accum->cmd[i] = cmd & 0xff;
cmd >>= 8;
}
} else {
accum->cmd[0] = cmd & 0xff; cmd >>= 8;
accum->cmd[1] = cmd & 0xff; cmd >>= 8;
accum->cmd[2] = cmd & 0xff; cmd >>= 8;
accum->cmd[3] = cmd & 0xff;
}
accum->period = period;
accum->xfer_size = xfer_size;
accum->valid_mask = valid_mask;
accum->valid_value = valid_value;
accum->data_shift = data_shift;
accum->data_max = (1 << data_size);
accum->data_msb_mask = (1 << (data_size - 1));
accum->is_signed = is_signed;
accum->big_endian = big_endian;
if (!accum->notifier) {
accum->notifier = initializeNotifier(spiAccumulatorProcess, accum, status);
accum->triggerTime = getFPGATime(status) + period;
if (*status != 0) return;
updateNotifierAlarm(accum->notifier, accum->triggerTime, status);
}
}
/**
* Frees a SPI accumulator.
*/
void spiFreeAccumulator(uint8_t port, int32_t *status) {
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
SPIAccumulator* accum = spiAccumulators[port];
if (!accum) {
*status = NULL_PARAMETER;
return;
}
cleanNotifier(accum->notifier, status);
delete accum;
spiAccumulators[port] = nullptr;
}
/**
* Resets the accumulator to zero.
*/
void spiResetAccumulator(uint8_t port, int32_t *status) {
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
SPIAccumulator* accum = spiAccumulators[port];
if (!accum) {
*status = NULL_PARAMETER;
return;
}
accum->value = 0;
accum->count = 0;
accum->last_value = 0;
}
/**
* Set the center value of the accumulator.
*
* The center value is subtracted from each value before it is added to the accumulator. This
* is used for the center value of devices like gyros and accelerometers to make integration work
* and to take the device offset into account when integrating.
*/
void spiSetAccumulatorCenter(uint8_t port, int32_t center, int32_t *status) {
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
SPIAccumulator* accum = spiAccumulators[port];
if (!accum) {
*status = NULL_PARAMETER;
return;
}
accum->center = center;
}
/**
* Set the accumulator's deadband.
*/
void spiSetAccumulatorDeadband(uint8_t port, int32_t deadband, int32_t *status) {
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
SPIAccumulator* accum = spiAccumulators[port];
if (!accum) {
*status = NULL_PARAMETER;
return;
}
accum->deadband = deadband;
}
/**
* Read the last value read by the accumulator engine.
*/
int32_t spiGetAccumulatorLastValue(uint8_t port, int32_t *status) {
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
SPIAccumulator* accum = spiAccumulators[port];
if (!accum) {
*status = NULL_PARAMETER;
return 0;
}
return accum->last_value;
}
/**
* Read the accumulated value.
*
* @return The 64-bit value accumulated since the last Reset().
*/
int64_t spiGetAccumulatorValue(uint8_t port, int32_t *status) {
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
SPIAccumulator* accum = spiAccumulators[port];
if (!accum) {
*status = NULL_PARAMETER;
return 0;
}
return accum->value;
}
/**
* Read the number of accumulated values.
*
* Read the count of the accumulated values since the accumulator was last Reset().
*
* @return The number of times samples from the channel were accumulated.
*/
uint32_t spiGetAccumulatorCount(uint8_t port, int32_t *status) {
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
SPIAccumulator* accum = spiAccumulators[port];
if (!accum) {
*status = NULL_PARAMETER;
return 0;
}
return accum->count;
}
/**
* Read the average of the accumulated value.
*
* @return The accumulated average value (value / count).
*/
double spiGetAccumulatorAverage(uint8_t port, int32_t *status) {
int64_t value;
uint32_t count;
spiGetAccumulatorOutput(port, &value, &count, status);
if (count == 0) return 0.0;
return ((double)value) / count;
}
/**
* Read the accumulated value and the number of accumulated values atomically.
*
* This function reads the value and count atomically.
* This can be used for averaging.
*
* @param value Pointer to the 64-bit accumulated output.
* @param count Pointer to the number of accumulation cycles.
*/
void spiGetAccumulatorOutput(uint8_t port, int64_t *value, uint32_t *count,
int32_t *status) {
std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port));
SPIAccumulator* accum = spiAccumulators[port];
if (!accum) {
*status = NULL_PARAMETER;
*value = 0;
*count = 0;
return;
}
*value = accum->value;
*count = accum->count;
}
/*
* Initialize the I2C port. Opens the port if necessary and saves the handle.
* If opening the MXP port, also sets up the pin functions appropriately
* @param port The port to open, 0 for the on-board, 1 for the MXP.
*/
void i2CInitialize(uint8_t port, int32_t *status) {
initializeDigital(status);
if(port > 1)
{
//Set port out of range error here
return;
}
priority_recursive_mutex &lock = port == 0 ? digitalI2COnBoardMutex : digitalI2CMXPMutex;
{
std::lock_guard<priority_recursive_mutex> sync(lock);
if(port == 0) {
i2COnboardObjCount++;
if (i2COnBoardHandle > 0) return;
i2COnBoardHandle = i2clib_open("/dev/i2c-2");
} else if(port == 1) {
i2CMXPObjCount++;
if (i2CMXPHandle > 0) return;
if(!allocateDIO(getPort(24), false, status)) return;
if(!allocateDIO(getPort(25), false, status)) return;
digitalSystem->writeEnableMXPSpecialFunction(digitalSystem->readEnableMXPSpecialFunction(status)|0xC000, status);
i2CMXPHandle = i2clib_open("/dev/i2c-1");
}
return;
}
}
/**
* Generic transaction.
*
* This is a lower-level interface to the I2C hardware giving you more control over each transaction.
*
* @param dataToSend Buffer of data to send as part of the transaction.
* @param sendSize Number of bytes to send as part of the transaction.
* @param dataReceived Buffer to read data into.
* @param receiveSize Number of bytes to read from the device.
* @return Transfer Aborted... false for success, true for aborted.
*/
int32_t i2CTransaction(uint8_t port, uint8_t deviceAddress, uint8_t *dataToSend, uint8_t sendSize, uint8_t *dataReceived, uint8_t receiveSize)
{
if(port > 1) {
//Set port out of range error here
return -1;
}
int32_t handle = port == 0 ? i2COnBoardHandle:i2CMXPHandle;
priority_recursive_mutex &lock = port == 0 ? digitalI2COnBoardMutex : digitalI2CMXPMutex;
{
std::lock_guard<priority_recursive_mutex> sync(lock);
return i2clib_writeread(handle, deviceAddress, (const char*) dataToSend, (int32_t) sendSize, (char*) dataReceived, (int32_t) receiveSize);
}
}
/**
* Execute a write transaction with the device.
*
* Write a single byte to a register on a device and wait until the
* transaction is complete.
*
* @param registerAddress The address of the register on the device to be written.
* @param data The byte to write to the register on the device.
* @return Transfer Aborted... false for success, true for aborted.
*/
int32_t i2CWrite(uint8_t port, uint8_t deviceAddress, uint8_t* dataToSend, uint8_t sendSize)
{
if(port > 1) {
//Set port out of range error here
return -1;
}
int32_t handle = port == 0 ? i2COnBoardHandle:i2CMXPHandle;
priority_recursive_mutex &lock = port == 0 ? digitalI2COnBoardMutex : digitalI2CMXPMutex;
{
std::lock_guard<priority_recursive_mutex> sync(lock);
return i2clib_write(handle, deviceAddress, (const char*) dataToSend, (int32_t) sendSize);
}
}
/**
* Execute a read transaction with the device.
*
* Read bytes from a device.
* Most I2C devices will auto-increment the register pointer internally allowing
* you to read consecutive registers on a device in a single transaction.
*
* @param registerAddress The register to read first in the transaction.
* @param count The number of bytes to read in the transaction.
* @param buffer A pointer to the array of bytes to store the data read from the device.
* @return Transfer Aborted... false for success, true for aborted.
*/
int32_t i2CRead(uint8_t port, uint8_t deviceAddress, uint8_t *buffer, uint8_t count)
{
if(port > 1) {
//Set port out of range error here
return -1;
}
int32_t handle = port == 0 ? i2COnBoardHandle:i2CMXPHandle;
priority_recursive_mutex &lock = port == 0 ? digitalI2COnBoardMutex : digitalI2CMXPMutex;
{
std::lock_guard<priority_recursive_mutex> sync(lock);
return i2clib_read(handle, deviceAddress, (char*) buffer, (int32_t) count);
}
}
void i2CClose(uint8_t port) {
if(port > 1) {
//Set port out of range error here
return;
}
priority_recursive_mutex &lock = port == 0 ? digitalI2COnBoardMutex : digitalI2CMXPMutex;
{
std::lock_guard<priority_recursive_mutex> sync(lock);
if((port == 0 ? i2COnboardObjCount--:i2CMXPObjCount--) == 0) {
int32_t handle = port == 0 ? i2COnBoardHandle:i2CMXPHandle;
i2clib_close(handle);
}
}
return;
}