blob: 0f0bf80455311191b7566849c8701d826b2b25ab [file] [log] [blame]
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2008. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in $(WIND_BASE)/WPILib. */
/*----------------------------------------------------------------------------*/
#include "DigitalModule.h"
#include "I2C.h"
#include "PWM.h"
#include "Resource.h"
#include "Synchronized.h"
#include "WPIErrors.h"
#include <math.h>
#include <taskLib.h>
static Resource *DIOChannels = NULL;
static Resource *DO_PWMGenerators[tDIO::kNumSystems] = {NULL};
/**
* Get an instance of an Digital Module.
* Singleton digital module creation where a module is allocated on the first use
* and the same module is returned on subsequent uses.
*
* @param moduleNumber The digital module to get (1 or 2).
*/
DigitalModule* DigitalModule::GetInstance(UINT8 moduleNumber)
{
if (CheckDigitalModule(moduleNumber))
{
return (DigitalModule *)GetModule(nLoadOut::kModuleType_Digital, moduleNumber);
}
// If this wasn't caught before now, make sure we say what's wrong before we crash
char buf[64];
snprintf(buf, 64, "Digital Module %d", moduleNumber);
wpi_setGlobalWPIErrorWithContext(ModuleIndexOutOfRange, buf);
return NULL;
}
/**
* Create a new instance of an digital module.
* Create an instance of the digital module object. Initialize all the parameters
* to reasonable values on start.
* Setting a global value on an digital module can be done only once unless subsequent
* values are set the previously set value.
* Digital modules are a singleton, so the constructor is never called outside of this class.
*
* @param moduleNumber The digital module to create (1 or 2).
*/
DigitalModule::DigitalModule(UINT8 moduleNumber)
: Module(nLoadOut::kModuleType_Digital, moduleNumber)
, m_fpgaDIO (NULL)
{
Resource::CreateResourceObject(&DIOChannels, tDIO::kNumSystems * kDigitalChannels);
Resource::CreateResourceObject(&DO_PWMGenerators[m_moduleNumber - 1], tDIO::kNumDO_PWMDutyCycleElements);
tRioStatusCode localStatus = NiFpga_Status_Success;
m_fpgaDIO = tDIO::create(m_moduleNumber - 1, &localStatus);
wpi_setError(localStatus);
// Make sure that the 9403 IONode has had a chance to initialize before continuing.
while(m_fpgaDIO->readLoopTiming(&localStatus) == 0) taskDelay(1);
if (m_fpgaDIO->readLoopTiming(&localStatus) != kExpectedLoopTiming)
{
char err[128];
sprintf(err, "DIO LoopTiming: %d, expecting: %d\n", m_fpgaDIO->readLoopTiming(&localStatus), kExpectedLoopTiming);
wpi_setWPIErrorWithContext(LoopTimingError, err);
}
m_fpgaDIO->writePWMConfig_Period(PWM::kDefaultPwmPeriod, &localStatus);
m_fpgaDIO->writePWMConfig_MinHigh(PWM::kDefaultMinPwmHigh, &localStatus);
// Ensure that PWM output values are set to OFF
for (UINT32 pwm_index = 1; pwm_index <= kPwmChannels; pwm_index++)
{
SetPWM(pwm_index, PWM::kPwmDisabled);
SetPWMPeriodScale(pwm_index, 3); // Set all to 4x by default.
}
// Turn off all relay outputs.
m_fpgaDIO->writeSlowValue_RelayFwd(0, &localStatus);
m_fpgaDIO->writeSlowValue_RelayRev(0, &localStatus);
wpi_setError(localStatus);
// Create a semaphore to protect changes to the digital output values
m_digitalSemaphore = semMCreate(SEM_Q_PRIORITY | SEM_DELETE_SAFE | SEM_INVERSION_SAFE);
// Create a semaphore to protect changes to the relay values
m_relaySemaphore = semMCreate(SEM_Q_PRIORITY | SEM_DELETE_SAFE | SEM_INVERSION_SAFE);
// Create a semaphore to protect changes to the DO PWM config
m_doPwmSemaphore = semMCreate(SEM_Q_PRIORITY | SEM_DELETE_SAFE | SEM_INVERSION_SAFE);
AddToSingletonList();
}
DigitalModule::~DigitalModule()
{
semDelete(m_doPwmSemaphore);
m_doPwmSemaphore = NULL;
semDelete(m_relaySemaphore);
m_relaySemaphore = NULL;
semDelete(m_digitalSemaphore);
m_digitalSemaphore = NULL;
delete m_fpgaDIO;
}
/**
* 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 DigitalModule::SetPWM(UINT32 channel, UINT8 value)
{
CheckPWMChannel(channel);
tRioStatusCode localStatus = NiFpga_Status_Success;
m_fpgaDIO->writePWMValue(channel - 1, value, &localStatus);
wpi_setError(localStatus);
}
/**
* 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.
*/
UINT8 DigitalModule::GetPWM(UINT32 channel)
{
CheckPWMChannel(channel);
tRioStatusCode localStatus = NiFpga_Status_Success;
return m_fpgaDIO->readPWMValue(channel - 1, &localStatus);
wpi_setError(localStatus);
}
/**
* 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 DigitalModule::SetPWMPeriodScale(UINT32 channel, UINT32 squelchMask)
{
CheckPWMChannel(channel);
tRioStatusCode localStatus = NiFpga_Status_Success;
m_fpgaDIO->writePWMPeriodScale(channel - 1, squelchMask, &localStatus);
wpi_setError(localStatus);
}
/**
* 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 DigitalModule::SetRelayForward(UINT32 channel, bool on)
{
tRioStatusCode localStatus = NiFpga_Status_Success;
CheckRelayChannel(channel);
{
Synchronized sync(m_relaySemaphore);
UINT8 forwardRelays = m_fpgaDIO->readSlowValue_RelayFwd(&localStatus);
if (on)
forwardRelays |= 1 << (channel - 1);
else
forwardRelays &= ~(1 << (channel - 1));
m_fpgaDIO->writeSlowValue_RelayFwd(forwardRelays, &localStatus);
}
wpi_setError(localStatus);
}
/**
* 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 DigitalModule::SetRelayReverse(UINT32 channel, bool on)
{
tRioStatusCode localStatus = NiFpga_Status_Success;
CheckRelayChannel(channel);
{
Synchronized sync(m_relaySemaphore);
UINT8 reverseRelays = m_fpgaDIO->readSlowValue_RelayRev(&localStatus);
if (on)
reverseRelays |= 1 << (channel - 1);
else
reverseRelays &= ~(1 << (channel - 1));
m_fpgaDIO->writeSlowValue_RelayRev(reverseRelays, &localStatus);
}
wpi_setError(localStatus);
}
/**
* Get the current state of the forward relay channel
*/
bool DigitalModule::GetRelayForward(UINT32 channel)
{
tRioStatusCode localStatus = NiFpga_Status_Success;
UINT8 forwardRelays = m_fpgaDIO->readSlowValue_RelayFwd(&localStatus);
wpi_setError(localStatus);
return (forwardRelays & (1 << (channel - 1))) != 0;
}
/**
* Get the current state of all of the forward relay channels on this module.
*/
UINT8 DigitalModule::GetRelayForward()
{
tRioStatusCode localStatus = NiFpga_Status_Success;
UINT8 forwardRelays = m_fpgaDIO->readSlowValue_RelayFwd(&localStatus);
wpi_setError(localStatus);
return forwardRelays;
}
/**
* Get the current state of the reverse relay channel
*/
bool DigitalModule::GetRelayReverse(UINT32 channel)
{
tRioStatusCode localStatus = NiFpga_Status_Success;
UINT8 reverseRelays = m_fpgaDIO->readSlowValue_RelayRev(&localStatus);
wpi_setError(localStatus);
return (reverseRelays & (1 << (channel - 1))) != 0;
}
/**
* Get the current state of all of the reverse relay channels on this module.
*/
UINT8 DigitalModule::GetRelayReverse()
{
tRioStatusCode localStatus = NiFpga_Status_Success;
UINT8 reverseRelays = m_fpgaDIO->readSlowValue_RelayRev(&localStatus);
wpi_setError(localStatus);
return reverseRelays;
}
/**
* 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 DigitalModule::AllocateDIO(UINT32 channel, bool input)
{
char buf[64];
snprintf(buf, 64, "DIO %d (Module %d)", channel, m_moduleNumber);
if (DIOChannels->Allocate(kDigitalChannels * (m_moduleNumber - 1) + channel - 1, buf) == ~0ul) return false;
tRioStatusCode localStatus = NiFpga_Status_Success;
{
Synchronized sync(m_digitalSemaphore);
UINT32 bitToSet = 1 << (RemapDigitalChannel(channel - 1));
UINT32 outputEnable = m_fpgaDIO->readOutputEnable(&localStatus);
UINT32 outputEnableValue;
if (input)
{
outputEnableValue = outputEnable & (~bitToSet); // clear the bit for read
}
else
{
outputEnableValue = outputEnable | bitToSet; // set the bit for write
}
m_fpgaDIO->writeOutputEnable(outputEnableValue, &localStatus);
}
wpi_setError(localStatus);
return true;
}
/**
* Free the resource associated with a digital I/O channel.
*
* @param channel The Digital I/O channel to free
*/
void DigitalModule::FreeDIO(UINT32 channel)
{
DIOChannels->Free(kDigitalChannels * (m_moduleNumber - 1) + channel - 1);
}
/**
* 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 DigitalModule::SetDIO(UINT32 channel, short value)
{
if (value != 0 && value != 1)
{
wpi_setWPIError(NonBinaryDigitalValue);
if (value != 0)
value = 1;
}
tRioStatusCode localStatus = NiFpga_Status_Success;
{
Synchronized sync(m_digitalSemaphore);
UINT16 currentDIO = m_fpgaDIO->readDO(&localStatus);
if(value == 0)
{
currentDIO = currentDIO & ~(1 << RemapDigitalChannel(channel - 1));
}
else if (value == 1)
{
currentDIO = currentDIO | (1 << RemapDigitalChannel(channel - 1));
}
m_fpgaDIO->writeDO(currentDIO, &localStatus);
}
wpi_setError(localStatus);
}
/**
* 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 DigitalModule::GetDIO(UINT32 channel)
{
tRioStatusCode localStatus = NiFpga_Status_Success;
UINT32 currentDIO = m_fpgaDIO->readDI(&localStatus);
wpi_setError(localStatus);
//Shift 00000001 over channel-1 places.
//AND it against the currentDIO
//if it == 0, then return false
//else return true
return ((currentDIO >> RemapDigitalChannel(channel - 1)) & 1) != 0;
}
/**
* Read the state of all the Digital I/O lines from the FPGA
* These are not remapped to logical order. They are still in hardware order.
*/
UINT16 DigitalModule::GetDIO()
{
tRioStatusCode localStatus = NiFpga_Status_Success;
UINT32 currentDIO = m_fpgaDIO->readDI(&localStatus);
wpi_setError(localStatus);
return currentDIO;
}
/**
* 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 DigitalModule::GetDIODirection(UINT32 channel)
{
tRioStatusCode localStatus = NiFpga_Status_Success;
UINT32 currentOutputEnable = m_fpgaDIO->readOutputEnable(&localStatus);
wpi_setError(localStatus);
//Shift 00000001 over channel-1 places.
//AND it against the currentOutputEnable
//if it == 0, then return false
//else return true
return ((currentOutputEnable >> RemapDigitalChannel(channel - 1)) & 1) != 0;
}
/**
* Read the direction of all the Digital I/O lines from the FPGA
* A 1 bit means output and a 0 bit means input.
* These are not remapped to logical order. They are still in hardware order.
*/
UINT16 DigitalModule::GetDIODirection()
{
tRioStatusCode localStatus = NiFpga_Status_Success;
UINT32 currentOutputEnable = m_fpgaDIO->readOutputEnable(&localStatus);
wpi_setError(localStatus);
return currentOutputEnable;
}
/**
* 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 DigitalModule::Pulse(UINT32 channel, float pulseLength)
{
UINT16 mask = 1 << RemapDigitalChannel(channel - 1);
tRioStatusCode localStatus = NiFpga_Status_Success;
m_fpgaDIO->writePulseLength((UINT8)(1.0e9 * pulseLength / (m_fpgaDIO->readLoopTiming(&localStatus) * 25)), &localStatus);
m_fpgaDIO->writePulse(mask, &localStatus);
wpi_setError(localStatus);
}
/**
* Check a DIO line to see if it is currently generating a pulse.
*
* @return A pulse is in progress
*/
bool DigitalModule::IsPulsing(UINT32 channel)
{
UINT16 mask = 1 << RemapDigitalChannel(channel - 1);
tRioStatusCode localStatus = NiFpga_Status_Success;
UINT16 pulseRegister = m_fpgaDIO->readPulse(&localStatus);
wpi_setError(localStatus);
return (pulseRegister & mask) != 0;
}
/**
* Check if any DIO line is currently generating a pulse.
*
* @return A pulse on some line is in progress
*/
bool DigitalModule::IsPulsing()
{
tRioStatusCode localStatus = NiFpga_Status_Success;
UINT16 pulseRegister = m_fpgaDIO->readPulse(&localStatus);
wpi_setError(localStatus);
return pulseRegister != 0;
}
/**
* Allocate a DO PWM Generator.
* Allocate PWM generators so that they are not accidently reused.
*
* @return PWM Generator refnum
*/
UINT32 DigitalModule::AllocateDO_PWM()
{
char buf[64];
snprintf(buf, 64, "DO_PWM (Module: %d)", m_moduleNumber);
return DO_PWMGenerators[(m_moduleNumber - 1)]->Allocate(buf);
}
/**
* Free the resource associated with a DO PWM generator.
*
* @param pwmGenerator The pwmGen to free that was allocated with AllocateDO_PWM()
*/
void DigitalModule::FreeDO_PWM(UINT32 pwmGenerator)
{
if (pwmGenerator == ~0ul) return;
DO_PWMGenerators[(m_moduleNumber - 1)]->Free(pwmGenerator);
}
/**
* 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 on this module.
*/
void DigitalModule::SetDO_PWMRate(float rate)
{
// Currently rounding in the log rate domain... heavy weight toward picking a higher freq.
// TODO: Round in the linear rate domain.
tRioStatusCode localStatus = NiFpga_Status_Success;
UINT8 pwmPeriodPower = (UINT8)(log(1.0 / (m_fpgaDIO->readLoopTiming(&localStatus) * 0.25E-6 * rate))/log(2.0) + 0.5);
m_fpgaDIO->writeDO_PWMConfig_PeriodPower(pwmPeriodPower, &localStatus);
wpi_setError(localStatus);
}
/**
* Configure which DO channel the PWM siganl is output on
*
* @param pwmGenerator The generator index reserved by AllocateDO_PWM()
* @param channel The Digital Output channel to output on
*/
void DigitalModule::SetDO_PWMOutputChannel(UINT32 pwmGenerator, UINT32 channel)
{
if (pwmGenerator == ~0ul) return;
tRioStatusCode localStatus = NiFpga_Status_Success;
switch(pwmGenerator)
{
case 0:
m_fpgaDIO->writeDO_PWMConfig_OutputSelect_0(RemapDigitalChannel(channel - 1), &localStatus);
break;
case 1:
m_fpgaDIO->writeDO_PWMConfig_OutputSelect_1(RemapDigitalChannel(channel - 1), &localStatus);
break;
case 2:
m_fpgaDIO->writeDO_PWMConfig_OutputSelect_2(RemapDigitalChannel(channel - 1), &localStatus);
break;
case 3:
m_fpgaDIO->writeDO_PWMConfig_OutputSelect_3(RemapDigitalChannel(channel - 1), &localStatus);
break;
}
wpi_setError(localStatus);
}
/**
* 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 DigitalModule::SetDO_PWMDutyCycle(UINT32 pwmGenerator, float dutyCycle)
{
if (pwmGenerator == ~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;
tRioStatusCode localStatus = NiFpga_Status_Success;
{
Synchronized sync(m_doPwmSemaphore);
UINT8 pwmPeriodPower = m_fpgaDIO->readDO_PWMConfig_PeriodPower(&localStatus);
if (pwmPeriodPower < 4)
{
// The resolution of the duty cycle drops close to the highest frequencies.
rawDutyCycle = rawDutyCycle / pow(2.0, 4 - pwmPeriodPower);
}
m_fpgaDIO->writeDO_PWMDutyCycle(pwmGenerator, (UINT8)rawDutyCycle, &localStatus);
}
wpi_setError(localStatus);
}
/**
* Return a pointer to an I2C object for this digital module
* The caller is responsible for deleting the pointer.
*
* @param address The address of the device on the I2C bus
* @return A pointer to an I2C object to talk to the device at address
*/
I2C* DigitalModule::GetI2C(UINT32 address)
{
return new I2C(this, address);
}