mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[wpilib] Remove InterruptableSensorBase and replace with interrupt classes (#2410)
This commit is contained in:
@@ -7,21 +7,13 @@ package edu.wpi.first.hal;
|
||||
public class InterruptJNI extends JNIWrapper {
|
||||
public static final int HalInvalidHandle = 0;
|
||||
|
||||
public interface InterruptJNIHandlerFunction {
|
||||
void apply(int interruptAssertedMask, Object param);
|
||||
}
|
||||
|
||||
public static native int initializeInterrupts(boolean watcher);
|
||||
public static native int initializeInterrupts();
|
||||
|
||||
public static native void cleanInterrupts(int interruptHandle);
|
||||
|
||||
public static native int waitForInterrupt(
|
||||
int interruptHandle, double timeout, boolean ignorePrevious);
|
||||
|
||||
public static native void enableInterrupts(int interruptHandle);
|
||||
|
||||
public static native void disableInterrupts(int interruptHandle);
|
||||
|
||||
public static native long readInterruptRisingTimestamp(int interruptHandle);
|
||||
|
||||
public static native long readInterruptFallingTimestamp(int interruptHandle);
|
||||
@@ -29,9 +21,6 @@ public class InterruptJNI extends JNIWrapper {
|
||||
public static native void requestInterrupts(
|
||||
int interruptHandle, int digitalSourceHandle, int analogTriggerType);
|
||||
|
||||
public static native void attachInterruptHandler(
|
||||
int interruptHandle, InterruptJNIHandlerFunction handler, Object param);
|
||||
|
||||
public static native void setInterruptUpSourceEdge(
|
||||
int interruptHandle, boolean risingEdge, boolean fallingEdge);
|
||||
|
||||
|
||||
@@ -21,65 +21,14 @@
|
||||
using namespace hal;
|
||||
|
||||
namespace {
|
||||
// Safe thread to allow callbacks to run on their own thread
|
||||
class InterruptThread : public wpi::SafeThread {
|
||||
public:
|
||||
void Main() override {
|
||||
std::unique_lock lock(m_mutex);
|
||||
while (m_active) {
|
||||
m_cond.wait(lock, [&] { return !m_active || m_notify; });
|
||||
if (!m_active) {
|
||||
break;
|
||||
}
|
||||
m_notify = false;
|
||||
HAL_InterruptHandlerFunction handler = m_handler;
|
||||
uint32_t mask = m_mask;
|
||||
void* param = m_param;
|
||||
lock.unlock(); // don't hold mutex during callback execution
|
||||
handler(mask, param);
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
bool m_notify = false;
|
||||
HAL_InterruptHandlerFunction m_handler;
|
||||
void* m_param;
|
||||
uint32_t m_mask;
|
||||
};
|
||||
|
||||
class InterruptThreadOwner : public wpi::SafeThreadOwner<InterruptThread> {
|
||||
public:
|
||||
void SetFunc(HAL_InterruptHandlerFunction handler, void* param) {
|
||||
auto thr = GetThread();
|
||||
if (!thr)
|
||||
return;
|
||||
thr->m_handler = handler;
|
||||
thr->m_param = param;
|
||||
}
|
||||
|
||||
void Notify(uint32_t mask) {
|
||||
auto thr = GetThread();
|
||||
if (!thr)
|
||||
return;
|
||||
thr->m_mask = mask;
|
||||
thr->m_notify = true;
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
};
|
||||
|
||||
struct Interrupt {
|
||||
std::unique_ptr<tInterrupt> anInterrupt;
|
||||
std::unique_ptr<tInterruptManager> manager;
|
||||
std::unique_ptr<InterruptThreadOwner> threadOwner = nullptr;
|
||||
void* param = nullptr;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
static void threadedInterruptHandler(uint32_t mask, void* param) {
|
||||
static_cast<InterruptThreadOwner*>(param)->Notify(mask);
|
||||
}
|
||||
|
||||
static LimitedHandleResource<HAL_InterruptHandle, Interrupt, kNumInterrupts,
|
||||
HAL_HandleEnum::Interrupt>* interruptHandles;
|
||||
|
||||
@@ -94,8 +43,7 @@ void InitializeInterrupts() {
|
||||
|
||||
extern "C" {
|
||||
|
||||
HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
|
||||
int32_t* status) {
|
||||
HAL_InterruptHandle HAL_InitializeInterrupts(int32_t* status) {
|
||||
hal::init::CheckInit();
|
||||
HAL_InterruptHandle handle = interruptHandles->Allocate();
|
||||
if (handle == HAL_kInvalidHandle) {
|
||||
@@ -108,24 +56,16 @@ HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
|
||||
anInterrupt->anInterrupt.reset(tInterrupt::create(interruptIndex, status));
|
||||
anInterrupt->anInterrupt->writeConfig_WaitForAck(false, status);
|
||||
anInterrupt->manager = std::make_unique<tInterruptManager>(
|
||||
(1u << interruptIndex) | (1u << (interruptIndex + 8u)), watcher, status);
|
||||
(1u << interruptIndex) | (1u << (interruptIndex + 8u)), true, status);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
void HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle) {
|
||||
auto anInterrupt = interruptHandles->Get(interruptHandle);
|
||||
interruptHandles->Free(interruptHandle);
|
||||
if (anInterrupt == nullptr) {
|
||||
return nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
if (anInterrupt->manager->isEnabled(status)) {
|
||||
anInterrupt->manager->disable(status);
|
||||
}
|
||||
|
||||
void* param = anInterrupt->param;
|
||||
return param;
|
||||
}
|
||||
|
||||
int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
|
||||
@@ -150,31 +90,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
|
||||
return result;
|
||||
}
|
||||
|
||||
void HAL_EnableInterrupts(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
auto anInterrupt = interruptHandles->Get(interruptHandle);
|
||||
if (anInterrupt == nullptr) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!anInterrupt->manager->isEnabled(status)) {
|
||||
anInterrupt->manager->enable(status);
|
||||
}
|
||||
}
|
||||
|
||||
void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
auto anInterrupt = interruptHandles->Get(interruptHandle);
|
||||
if (anInterrupt == nullptr) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return;
|
||||
}
|
||||
if (anInterrupt->manager->isEnabled(status)) {
|
||||
anInterrupt->manager->disable(status);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
auto anInterrupt = interruptHandles->Get(interruptHandle);
|
||||
@@ -223,40 +138,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,
|
||||
anInterrupt->anInterrupt->writeConfig_Source_Module(routingModule, status);
|
||||
}
|
||||
|
||||
void HAL_AttachInterruptHandler(HAL_InterruptHandle interruptHandle,
|
||||
HAL_InterruptHandlerFunction handler,
|
||||
void* param, int32_t* status) {
|
||||
auto anInterrupt = interruptHandles->Get(interruptHandle);
|
||||
if (anInterrupt == nullptr) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return;
|
||||
}
|
||||
anInterrupt->manager->registerHandler(handler, param, status);
|
||||
anInterrupt->param = param;
|
||||
}
|
||||
|
||||
void HAL_AttachInterruptHandlerThreaded(HAL_InterruptHandle interrupt_handle,
|
||||
HAL_InterruptHandlerFunction handler,
|
||||
void* param, int32_t* status) {
|
||||
auto anInterrupt = interruptHandles->Get(interrupt_handle);
|
||||
if (anInterrupt == nullptr) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
anInterrupt->threadOwner = std::make_unique<InterruptThreadOwner>();
|
||||
anInterrupt->threadOwner->Start();
|
||||
anInterrupt->threadOwner->SetFunc(handler, param);
|
||||
|
||||
HAL_AttachInterruptHandler(interrupt_handle, threadedInterruptHandler,
|
||||
anInterrupt->threadOwner.get(), status);
|
||||
|
||||
if (*status != 0) {
|
||||
anInterrupt->threadOwner = nullptr;
|
||||
}
|
||||
anInterrupt->param = param;
|
||||
}
|
||||
|
||||
void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle,
|
||||
HAL_Bool risingEdge, HAL_Bool fallingEdge,
|
||||
int32_t* status) {
|
||||
|
||||
@@ -17,126 +17,19 @@
|
||||
|
||||
using namespace hal;
|
||||
|
||||
// Thread where callbacks are actually performed.
|
||||
//
|
||||
// JNI's AttachCurrentThread() creates a Java Thread object on every
|
||||
// invocation, which is both time inefficient and causes issues with Eclipse
|
||||
// (which tries to keep a thread list up-to-date and thus gets swamped).
|
||||
//
|
||||
// Instead, this class attaches just once. When a hardware notification
|
||||
// occurs, a condition variable wakes up this thread and this thread actually
|
||||
// makes the call into Java.
|
||||
//
|
||||
// We don't want to use a FIFO here. If the user code takes too long to
|
||||
// process, we will just ignore the redundant wakeup.
|
||||
class InterruptThreadJNI : public wpi::SafeThread {
|
||||
public:
|
||||
void Main() override;
|
||||
|
||||
bool m_notify = false;
|
||||
uint32_t m_mask = 0;
|
||||
jobject m_func = nullptr;
|
||||
jmethodID m_mid;
|
||||
jobject m_param = nullptr;
|
||||
};
|
||||
|
||||
class InterruptJNI : public wpi::SafeThreadOwner<InterruptThreadJNI> {
|
||||
public:
|
||||
void SetFunc(JNIEnv* env, jobject func, jmethodID mid, jobject param);
|
||||
|
||||
void Notify(uint32_t mask) {
|
||||
auto thr = GetThread();
|
||||
if (!thr) {
|
||||
return;
|
||||
}
|
||||
thr->m_notify = true;
|
||||
thr->m_mask = mask;
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
};
|
||||
|
||||
void InterruptJNI::SetFunc(JNIEnv* env, jobject func, jmethodID mid,
|
||||
jobject param) {
|
||||
auto thr = GetThread();
|
||||
if (!thr) {
|
||||
return;
|
||||
}
|
||||
// free global references
|
||||
if (thr->m_func) {
|
||||
env->DeleteGlobalRef(thr->m_func);
|
||||
}
|
||||
if (thr->m_param) {
|
||||
env->DeleteGlobalRef(thr->m_param);
|
||||
}
|
||||
// create global references
|
||||
thr->m_func = env->NewGlobalRef(func);
|
||||
thr->m_param = param ? env->NewGlobalRef(param) : nullptr;
|
||||
thr->m_mid = mid;
|
||||
}
|
||||
|
||||
void InterruptThreadJNI::Main() {
|
||||
JNIEnv* env;
|
||||
JavaVMAttachArgs args;
|
||||
args.version = JNI_VERSION_1_2;
|
||||
args.name = const_cast<char*>("Interrupt");
|
||||
args.group = nullptr;
|
||||
jint rs = GetJVM()->AttachCurrentThreadAsDaemon(
|
||||
reinterpret_cast<void**>(&env), &args);
|
||||
if (rs != JNI_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock lock(m_mutex);
|
||||
while (m_active) {
|
||||
m_cond.wait(lock, [&] { return !m_active || m_notify; });
|
||||
if (!m_active) {
|
||||
break;
|
||||
}
|
||||
m_notify = false;
|
||||
if (!m_func) {
|
||||
continue;
|
||||
}
|
||||
jobject func = m_func;
|
||||
jmethodID mid = m_mid;
|
||||
uint32_t mask = m_mask;
|
||||
jobject param = m_param;
|
||||
lock.unlock(); // don't hold mutex during callback execution
|
||||
env->CallVoidMethod(func, mid, static_cast<jint>(mask), param);
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
// free global references
|
||||
if (m_func) {
|
||||
env->DeleteGlobalRef(m_func);
|
||||
}
|
||||
if (m_param) {
|
||||
env->DeleteGlobalRef(m_param);
|
||||
}
|
||||
|
||||
GetJVM()->DetachCurrentThread();
|
||||
}
|
||||
|
||||
void interruptHandler(uint32_t mask, void* param) {
|
||||
static_cast<InterruptJNI*>(param)->Notify(mask);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_InterruptJNI
|
||||
* Method: initializeInterrupts
|
||||
* Signature: (Z)I
|
||||
* Signature: ()I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_InterruptJNI_initializeInterrupts
|
||||
(JNIEnv* env, jclass, jboolean watcher)
|
||||
(JNIEnv* env, jclass)
|
||||
{
|
||||
int32_t status = 0;
|
||||
HAL_InterruptHandle interrupt = HAL_InitializeInterrupts(watcher, &status);
|
||||
HAL_InterruptHandle interrupt = HAL_InitializeInterrupts(&status);
|
||||
|
||||
CheckStatusForceThrow(env, status);
|
||||
return (jint)interrupt;
|
||||
@@ -151,14 +44,7 @@ JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_InterruptJNI_cleanInterrupts
|
||||
(JNIEnv* env, jclass, jint interruptHandle)
|
||||
{
|
||||
int32_t status = 0;
|
||||
auto param =
|
||||
HAL_CleanInterrupts((HAL_InterruptHandle)interruptHandle, &status);
|
||||
if (param) {
|
||||
delete static_cast<InterruptJNI*>(param);
|
||||
}
|
||||
|
||||
// ignore status, as an invalid handle just needs to be ignored.
|
||||
HAL_CleanInterrupts((HAL_InterruptHandle)interruptHandle);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -179,36 +65,6 @@ Java_edu_wpi_first_hal_InterruptJNI_waitForInterrupt
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_InterruptJNI
|
||||
* Method: enableInterrupts
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_InterruptJNI_enableInterrupts
|
||||
(JNIEnv* env, jclass, jint interruptHandle)
|
||||
{
|
||||
int32_t status = 0;
|
||||
HAL_EnableInterrupts((HAL_InterruptHandle)interruptHandle, &status);
|
||||
|
||||
CheckStatus(env, status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_InterruptJNI
|
||||
* Method: disableInterrupts
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_InterruptJNI_disableInterrupts
|
||||
(JNIEnv* env, jclass, jint interruptHandle)
|
||||
{
|
||||
int32_t status = 0;
|
||||
HAL_DisableInterrupts((HAL_InterruptHandle)interruptHandle, &status);
|
||||
|
||||
CheckStatus(env, status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_InterruptJNI
|
||||
* Method: readInterruptRisingTimestamp
|
||||
@@ -261,37 +117,6 @@ Java_edu_wpi_first_hal_InterruptJNI_requestInterrupts
|
||||
CheckStatus(env, status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_InterruptJNI
|
||||
* Method: attachInterruptHandler
|
||||
* Signature: (ILjava/lang/Object;Ljava/lang/Object;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_InterruptJNI_attachInterruptHandler
|
||||
(JNIEnv* env, jclass, jint interruptHandle, jobject handler, jobject param)
|
||||
{
|
||||
jclass cls = env->GetObjectClass(handler);
|
||||
if (cls == nullptr) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
jmethodID mid = env->GetMethodID(cls, "apply", "(ILjava/lang/Object;)V");
|
||||
if (mid == nullptr) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
InterruptJNI* intr = new InterruptJNI;
|
||||
intr->Start();
|
||||
intr->SetFunc(env, handler, mid, param);
|
||||
|
||||
int32_t status = 0;
|
||||
HAL_AttachInterruptHandler((HAL_InterruptHandle)interruptHandle,
|
||||
interruptHandler, intr, &status);
|
||||
|
||||
CheckStatus(env, status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_InterruptJNI
|
||||
* Method: setInterruptUpSourceEdge
|
||||
|
||||
@@ -19,25 +19,20 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void (*HAL_InterruptHandlerFunction)(uint32_t interruptAssertedMask,
|
||||
void* param);
|
||||
|
||||
/**
|
||||
* Initializes an interrupt.
|
||||
*
|
||||
* @param watcher true for synchronous interrupts, false for asynchronous
|
||||
* @return the created interrupt handle
|
||||
*/
|
||||
HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher, int32_t* status);
|
||||
HAL_InterruptHandle HAL_InitializeInterrupts(int32_t* status);
|
||||
|
||||
/**
|
||||
* Frees an interrupt.
|
||||
*
|
||||
* @param interruptHandle the interrupt handle
|
||||
* @return the param passed to the interrupt, or nullptr if one
|
||||
* wasn't passed.
|
||||
*/
|
||||
void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle, int32_t* status);
|
||||
void HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle);
|
||||
|
||||
/**
|
||||
* In synchronous mode, waits for the defined interrupt to occur.
|
||||
@@ -52,25 +47,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
|
||||
double timeout, HAL_Bool ignorePrevious,
|
||||
int32_t* status);
|
||||
|
||||
/**
|
||||
* Enables interrupts to occur on this input.
|
||||
*
|
||||
* Interrupts are disabled when the RequestInterrupt call is made. This gives
|
||||
* time to do the setup of the other options before starting to field
|
||||
* interrupts.
|
||||
*
|
||||
* @param interruptHandle the interrupt handle
|
||||
*/
|
||||
void HAL_EnableInterrupts(HAL_InterruptHandle interruptHandle, int32_t* status);
|
||||
|
||||
/**
|
||||
* Disables interrupts without without deallocating structures.
|
||||
*
|
||||
* @param interruptHandle the interrupt handle
|
||||
*/
|
||||
void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status);
|
||||
|
||||
/**
|
||||
* Returns the timestamp for the rising interrupt that occurred most recently.
|
||||
*
|
||||
@@ -110,34 +86,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,
|
||||
HAL_AnalogTriggerType analogTriggerType,
|
||||
int32_t* status);
|
||||
|
||||
/**
|
||||
* Attaches an asynchronous interrupt handler to the interrupt.
|
||||
*
|
||||
* This interrupt gets called directly on the FPGA interrupt thread, so will
|
||||
* block other interrupts while running.
|
||||
*
|
||||
* @param interruptHandle the interrupt handle
|
||||
* @param handler the handler function for the interrupt to call
|
||||
* @param param a parameter to be passed to the handler
|
||||
*/
|
||||
void HAL_AttachInterruptHandler(HAL_InterruptHandle interruptHandle,
|
||||
HAL_InterruptHandlerFunction handler,
|
||||
void* param, int32_t* status);
|
||||
|
||||
/**
|
||||
* Attaches an asynchronous interrupt handler to the interrupt.
|
||||
*
|
||||
* This interrupt gets called on a thread specific to the interrupt, so will not
|
||||
* block other interrupts.
|
||||
*
|
||||
* @param interruptHandle the interrupt handle
|
||||
* @param handler the handler function for the interrupt to call
|
||||
* @param param a parameter to be passed to the handler
|
||||
*/
|
||||
void HAL_AttachInterruptHandlerThreaded(HAL_InterruptHandle interruptHandle,
|
||||
HAL_InterruptHandlerFunction handler,
|
||||
void* param, int32_t* status);
|
||||
|
||||
/**
|
||||
* Sets the edges to trigger the interrupt on.
|
||||
*
|
||||
|
||||
@@ -42,16 +42,12 @@ struct Interrupt {
|
||||
HAL_Handle portHandle;
|
||||
uint8_t index;
|
||||
HAL_AnalogTriggerType trigType;
|
||||
bool watcher;
|
||||
int64_t risingTimestamp;
|
||||
int64_t fallingTimestamp;
|
||||
bool previousState;
|
||||
bool fireOnUp;
|
||||
bool fireOnDown;
|
||||
int32_t callbackId;
|
||||
|
||||
void* callbackParam;
|
||||
HAL_InterruptHandlerFunction callbackFunction;
|
||||
};
|
||||
|
||||
struct SynchronousWaitData {
|
||||
@@ -83,8 +79,7 @@ void InitializeInterrupts() {
|
||||
} // namespace hal::init
|
||||
|
||||
extern "C" {
|
||||
HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
|
||||
int32_t* status) {
|
||||
HAL_InterruptHandle HAL_InitializeInterrupts(int32_t* status) {
|
||||
hal::init::CheckInit();
|
||||
HAL_InterruptHandle handle = interruptHandles->Allocate();
|
||||
if (handle == HAL_kInvalidHandle) {
|
||||
@@ -100,19 +95,10 @@ HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
|
||||
anInterrupt->index = getHandleIndex(handle);
|
||||
anInterrupt->callbackId = -1;
|
||||
|
||||
anInterrupt->watcher = watcher;
|
||||
|
||||
return handle;
|
||||
}
|
||||
void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
HAL_DisableInterrupts(interruptHandle, status);
|
||||
auto anInterrupt = interruptHandles->Get(interruptHandle);
|
||||
void HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle) {
|
||||
interruptHandles->Free(interruptHandle);
|
||||
if (anInterrupt == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return anInterrupt->callbackParam;
|
||||
}
|
||||
|
||||
static void ProcessInterruptDigitalSynchronous(const char* name, void* param,
|
||||
@@ -352,12 +338,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
|
||||
return WaitResult::Timeout;
|
||||
}
|
||||
|
||||
// Check to make sure we are actually an interrupt in synchronous mode
|
||||
if (!interrupt->watcher) {
|
||||
*status = NiFpga_Status_InvalidParameter;
|
||||
return WaitResult::Timeout;
|
||||
}
|
||||
|
||||
if (interrupt->isAnalog) {
|
||||
return WaitForInterruptAnalog(interruptHandle, interrupt.get(), timeout,
|
||||
ignorePrevious);
|
||||
@@ -367,196 +347,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
|
||||
}
|
||||
}
|
||||
|
||||
static void ProcessInterruptDigitalAsynchronous(const char* name, void* param,
|
||||
const struct HAL_Value* value) {
|
||||
// void* is a HAL handle
|
||||
// convert to uintptr_t first, then to handle
|
||||
uintptr_t handleTmp = reinterpret_cast<uintptr_t>(param);
|
||||
HAL_InterruptHandle handle = static_cast<HAL_InterruptHandle>(handleTmp);
|
||||
auto interrupt = interruptHandles->Get(handle);
|
||||
if (interrupt == nullptr) {
|
||||
return;
|
||||
}
|
||||
// Have a valid interrupt
|
||||
if (value->type != HAL_Type::HAL_BOOLEAN) {
|
||||
return;
|
||||
}
|
||||
bool retVal = value->data.v_boolean;
|
||||
// If no change in interrupt, return;
|
||||
if (retVal == interrupt->previousState) {
|
||||
return;
|
||||
}
|
||||
int32_t mask = 0;
|
||||
if (interrupt->previousState) {
|
||||
interrupt->previousState = retVal;
|
||||
interrupt->fallingTimestamp = hal::GetFPGATime();
|
||||
mask = 1 << (8 + interrupt->index);
|
||||
if (!interrupt->fireOnDown) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
interrupt->previousState = retVal;
|
||||
interrupt->risingTimestamp = hal::GetFPGATime();
|
||||
mask = 1 << (interrupt->index);
|
||||
if (!interrupt->fireOnUp) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// run callback
|
||||
auto callback = interrupt->callbackFunction;
|
||||
if (callback == nullptr) {
|
||||
return;
|
||||
}
|
||||
callback(mask, interrupt->callbackParam);
|
||||
}
|
||||
|
||||
static void ProcessInterruptAnalogAsynchronous(const char* name, void* param,
|
||||
const struct HAL_Value* value) {
|
||||
// void* is a HAL handle
|
||||
// convert to intptr_t first, then to handle
|
||||
uintptr_t handleTmp = reinterpret_cast<uintptr_t>(param);
|
||||
HAL_InterruptHandle handle = static_cast<HAL_InterruptHandle>(handleTmp);
|
||||
auto interrupt = interruptHandles->Get(handle);
|
||||
if (interrupt == nullptr) {
|
||||
return;
|
||||
}
|
||||
// Have a valid interrupt
|
||||
if (value->type != HAL_Type::HAL_DOUBLE) {
|
||||
return;
|
||||
}
|
||||
int32_t status = 0;
|
||||
bool retVal = GetAnalogTriggerValue(interrupt->portHandle,
|
||||
interrupt->trigType, &status);
|
||||
if (status != 0) {
|
||||
return;
|
||||
}
|
||||
// If no change in interrupt, return;
|
||||
if (retVal == interrupt->previousState) {
|
||||
return;
|
||||
}
|
||||
int mask = 0;
|
||||
if (interrupt->previousState) {
|
||||
interrupt->previousState = retVal;
|
||||
interrupt->fallingTimestamp = hal::GetFPGATime();
|
||||
if (!interrupt->fireOnDown) {
|
||||
return;
|
||||
}
|
||||
mask = 1 << (8 + interrupt->index);
|
||||
} else {
|
||||
interrupt->previousState = retVal;
|
||||
interrupt->risingTimestamp = hal::GetFPGATime();
|
||||
if (!interrupt->fireOnUp) {
|
||||
return;
|
||||
}
|
||||
mask = 1 << (interrupt->index);
|
||||
}
|
||||
|
||||
// run callback
|
||||
auto callback = interrupt->callbackFunction;
|
||||
if (callback == nullptr) {
|
||||
return;
|
||||
}
|
||||
callback(mask, interrupt->callbackParam);
|
||||
}
|
||||
|
||||
static void EnableInterruptsDigital(HAL_InterruptHandle handle,
|
||||
Interrupt* interrupt) {
|
||||
int32_t status = 0;
|
||||
int32_t digitalIndex = GetDigitalInputChannel(interrupt->portHandle, &status);
|
||||
if (status != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
interrupt->previousState = SimDIOData[digitalIndex].value;
|
||||
|
||||
int32_t uid = SimDIOData[digitalIndex].value.RegisterCallback(
|
||||
&ProcessInterruptDigitalAsynchronous,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(handle)), false);
|
||||
interrupt->callbackId = uid;
|
||||
}
|
||||
|
||||
static void EnableInterruptsAnalog(HAL_InterruptHandle handle,
|
||||
Interrupt* interrupt) {
|
||||
int32_t status = 0;
|
||||
int32_t analogIndex =
|
||||
GetAnalogTriggerInputIndex(interrupt->portHandle, &status);
|
||||
if (status != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
status = 0;
|
||||
interrupt->previousState = GetAnalogTriggerValue(
|
||||
interrupt->portHandle, interrupt->trigType, &status);
|
||||
if (status != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t uid = SimAnalogInData[analogIndex].voltage.RegisterCallback(
|
||||
&ProcessInterruptAnalogAsynchronous,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(handle)), false);
|
||||
interrupt->callbackId = uid;
|
||||
}
|
||||
|
||||
void HAL_EnableInterrupts(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
auto interrupt = interruptHandles->Get(interruptHandle);
|
||||
if (interrupt == nullptr) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have not had a callback set, error out
|
||||
if (interrupt->callbackFunction == nullptr) {
|
||||
*status = INCOMPATIBLE_STATE;
|
||||
return;
|
||||
}
|
||||
|
||||
// EnableInterrupts has already been called
|
||||
if (interrupt->callbackId >= 0) {
|
||||
// We can double enable safely.
|
||||
return;
|
||||
}
|
||||
|
||||
if (interrupt->isAnalog) {
|
||||
EnableInterruptsAnalog(interruptHandle, interrupt.get());
|
||||
} else {
|
||||
EnableInterruptsDigital(interruptHandle, interrupt.get());
|
||||
}
|
||||
}
|
||||
void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
auto interrupt = interruptHandles->Get(interruptHandle);
|
||||
if (interrupt == nullptr) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to disable if we are already disabled
|
||||
if (interrupt->callbackId < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (interrupt->isAnalog) {
|
||||
// Do analog
|
||||
int32_t status = 0;
|
||||
int32_t analogIndex =
|
||||
GetAnalogTriggerInputIndex(interrupt->portHandle, &status);
|
||||
if (status != 0) {
|
||||
return;
|
||||
}
|
||||
SimAnalogInData[analogIndex].voltage.CancelCallback(interrupt->callbackId);
|
||||
} else {
|
||||
int32_t status = 0;
|
||||
int32_t digitalIndex =
|
||||
GetDigitalInputChannel(interrupt->portHandle, &status);
|
||||
if (status != 0) {
|
||||
return;
|
||||
}
|
||||
SimDIOData[digitalIndex].value.CancelCallback(interrupt->callbackId);
|
||||
}
|
||||
interrupt->callbackId = -1;
|
||||
}
|
||||
int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
auto interrupt = interruptHandles->Get(interruptHandle);
|
||||
@@ -602,24 +392,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,
|
||||
interrupt->trigType = analogTriggerType;
|
||||
interrupt->portHandle = digitalSourceHandle;
|
||||
}
|
||||
void HAL_AttachInterruptHandler(HAL_InterruptHandle interruptHandle,
|
||||
HAL_InterruptHandlerFunction handler,
|
||||
void* param, int32_t* status) {
|
||||
auto interrupt = interruptHandles->Get(interruptHandle);
|
||||
if (interrupt == nullptr) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
interrupt->callbackFunction = handler;
|
||||
interrupt->callbackParam = param;
|
||||
}
|
||||
|
||||
void HAL_AttachInterruptHandlerThreaded(HAL_InterruptHandle interruptHandle,
|
||||
HAL_InterruptHandlerFunction handler,
|
||||
void* param, int32_t* status) {
|
||||
HAL_AttachInterruptHandler(interruptHandle, handler, param, status);
|
||||
}
|
||||
|
||||
void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle,
|
||||
HAL_Bool risingEdge, HAL_Bool fallingEdge,
|
||||
@@ -636,6 +408,19 @@ void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle,
|
||||
|
||||
void HAL_ReleaseWaitingInterrupt(HAL_InterruptHandle interruptHandle,
|
||||
int32_t* status) {
|
||||
// Requires a fairly large rewrite to get working
|
||||
auto interrupt = interruptHandles->Get(interruptHandle);
|
||||
if (interrupt == nullptr) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
synchronousInterruptHandles->ForEach(
|
||||
[interruptHandle](SynchronousWaitDataHandle handle,
|
||||
SynchronousWaitData* data) {
|
||||
if (data->interruptHandle == interruptHandle) {
|
||||
data->waitPredicate = true;
|
||||
data->waitCond.notify_all();
|
||||
}
|
||||
});
|
||||
}
|
||||
} // extern "C"
|
||||
|
||||
@@ -330,7 +330,7 @@ public class CommandGroup extends Command {
|
||||
|
||||
/**
|
||||
* Returns whether or not this group is interruptible. A command group will be uninterruptible if
|
||||
* {@link CommandGroup#setInterruptible(boolean) setInterruptable(false)} was called or if it is
|
||||
* {@link CommandGroup#setInterruptible(boolean) setInterruptible(false)} was called or if it is
|
||||
* currently running an uninterruptible command or child.
|
||||
*
|
||||
* @return whether or not this {@link CommandGroup} is interruptible.
|
||||
|
||||
@@ -125,7 +125,7 @@ public final class Scheduler implements Sendable, AutoCloseable {
|
||||
/**
|
||||
* Adds a command immediately to the {@link Scheduler}. This should only be called in the {@link
|
||||
* Scheduler#run()} loop. Any command with conflicting requirements will be removed, unless it is
|
||||
* uninterruptable. Giving <code>null</code> does nothing.
|
||||
* uninterruptible. Giving <code>null</code> does nothing.
|
||||
*
|
||||
* @param command the {@link Command} to add
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <hal/AnalogTrigger.h>
|
||||
#include <hal/FRCUsageReporting.h>
|
||||
#include <wpi/NullDeleter.h>
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
#include "frc/AnalogTriggerOutput.h"
|
||||
|
||||
#include <hal/AnalogTrigger.h>
|
||||
#include <hal/FRCUsageReporting.h>
|
||||
|
||||
#include "frc/AnalogTrigger.h"
|
||||
#include "frc/AnalogTriggerType.h"
|
||||
#include "frc/Errors.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
67
wpilibc/src/main/native/cpp/AsynchronousInterrupt.cpp
Normal file
67
wpilibc/src/main/native/cpp/AsynchronousInterrupt.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "frc/AsynchronousInterrupt.h"
|
||||
|
||||
#include <frc/DigitalSource.h>
|
||||
|
||||
using namespace frc;
|
||||
|
||||
AsynchronousInterrupt::AsynchronousInterrupt(
|
||||
DigitalSource& source, std::function<void(bool, bool)> callback)
|
||||
: m_interrupt{source}, m_callback{std::move(callback)} {}
|
||||
AsynchronousInterrupt::AsynchronousInterrupt(
|
||||
DigitalSource* source, std::function<void(bool, bool)> callback)
|
||||
: m_interrupt{source}, m_callback{std::move(callback)} {}
|
||||
AsynchronousInterrupt::AsynchronousInterrupt(
|
||||
std::shared_ptr<DigitalSource> source,
|
||||
std::function<void(bool, bool)> callback)
|
||||
: m_interrupt{source}, m_callback{std::move(callback)} {}
|
||||
|
||||
AsynchronousInterrupt::~AsynchronousInterrupt() {
|
||||
Disable();
|
||||
}
|
||||
|
||||
void AsynchronousInterrupt::ThreadMain() {
|
||||
while (m_keepRunning) {
|
||||
auto result = m_interrupt.WaitForInterrupt(10_s, false);
|
||||
if (!m_keepRunning) {
|
||||
break;
|
||||
}
|
||||
if (result == SynchronousInterrupt::WaitResult::kTimeout) {
|
||||
continue;
|
||||
}
|
||||
m_callback((result & SynchronousInterrupt::WaitResult::kRisingEdge) != 0,
|
||||
(result & SynchronousInterrupt::WaitResult::kFallingEdge) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
void AsynchronousInterrupt::Enable() {
|
||||
if (m_keepRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_keepRunning = true;
|
||||
m_thread = std::thread([this] { this->ThreadMain(); });
|
||||
}
|
||||
|
||||
void AsynchronousInterrupt::Disable() {
|
||||
m_keepRunning = false;
|
||||
m_interrupt.WakeupWaitingInterrupt();
|
||||
if (m_thread.joinable()) {
|
||||
m_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void AsynchronousInterrupt::SetInterruptEdges(bool risingEdge,
|
||||
bool fallingEdge) {
|
||||
m_interrupt.SetInterruptEdges(risingEdge, fallingEdge);
|
||||
}
|
||||
|
||||
units::second_t AsynchronousInterrupt::GetRisingTimestamp() {
|
||||
return m_interrupt.GetRisingTimestamp();
|
||||
}
|
||||
units::second_t AsynchronousInterrupt::GetFallingTimestamp() {
|
||||
return m_interrupt.GetFallingTimestamp();
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "frc/InterruptableSensorBase.h"
|
||||
|
||||
#include "frc/Errors.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
InterruptableSensorBase::~InterruptableSensorBase() {
|
||||
if (m_interrupt == HAL_kInvalidHandle) {
|
||||
return;
|
||||
}
|
||||
int32_t status = 0;
|
||||
HAL_CleanInterrupts(m_interrupt, &status);
|
||||
// Ignore status, as an invalid handle just needs to be ignored.
|
||||
}
|
||||
|
||||
void InterruptableSensorBase::RequestInterrupts(
|
||||
HAL_InterruptHandlerFunction handler, void* param) {
|
||||
FRC_Assert(m_interrupt == HAL_kInvalidHandle);
|
||||
AllocateInterrupts(false);
|
||||
|
||||
int32_t status = 0;
|
||||
HAL_RequestInterrupts(
|
||||
m_interrupt, GetPortHandleForRouting(),
|
||||
static_cast<HAL_AnalogTriggerType>(GetAnalogTriggerTypeForRouting()),
|
||||
&status);
|
||||
SetUpSourceEdge(true, false);
|
||||
HAL_AttachInterruptHandler(m_interrupt, handler, param, &status);
|
||||
FRC_CheckErrorStatus(status, "{}", "AttachInterruptHandler");
|
||||
}
|
||||
|
||||
void InterruptableSensorBase::RequestInterrupts(InterruptEventHandler handler) {
|
||||
FRC_Assert(m_interrupt == HAL_kInvalidHandle);
|
||||
AllocateInterrupts(false);
|
||||
|
||||
m_interruptHandler =
|
||||
std::make_unique<InterruptEventHandler>(std::move(handler));
|
||||
|
||||
int32_t status = 0;
|
||||
HAL_RequestInterrupts(
|
||||
m_interrupt, GetPortHandleForRouting(),
|
||||
static_cast<HAL_AnalogTriggerType>(GetAnalogTriggerTypeForRouting()),
|
||||
&status);
|
||||
SetUpSourceEdge(true, false);
|
||||
HAL_AttachInterruptHandler(
|
||||
m_interrupt,
|
||||
[](uint32_t mask, void* param) {
|
||||
auto self = reinterpret_cast<InterruptEventHandler*>(param);
|
||||
// Rising edge result is the interrupt bit set in the byte 0xFF
|
||||
// Falling edge result is the interrupt bit set in the byte 0xFF00
|
||||
// Set any bit set to be true for that edge, and AND the 2 results
|
||||
// together to match the existing enum for all interrupts
|
||||
int32_t rising = (mask & 0xFF) ? 0x1 : 0x0;
|
||||
int32_t falling = ((mask & 0xFF00) ? 0x0100 : 0x0);
|
||||
WaitResult res = static_cast<WaitResult>(falling | rising);
|
||||
(*self)(res);
|
||||
},
|
||||
m_interruptHandler.get(), &status);
|
||||
FRC_CheckErrorStatus(status, "{}", "AttachInterruptHandler");
|
||||
}
|
||||
|
||||
void InterruptableSensorBase::RequestInterrupts() {
|
||||
FRC_Assert(m_interrupt == HAL_kInvalidHandle);
|
||||
AllocateInterrupts(true);
|
||||
|
||||
int32_t status = 0;
|
||||
HAL_RequestInterrupts(
|
||||
m_interrupt, GetPortHandleForRouting(),
|
||||
static_cast<HAL_AnalogTriggerType>(GetAnalogTriggerTypeForRouting()),
|
||||
&status);
|
||||
FRC_CheckErrorStatus(status, "{}", "RequestInterrupts");
|
||||
SetUpSourceEdge(true, false);
|
||||
}
|
||||
|
||||
void InterruptableSensorBase::CancelInterrupts() {
|
||||
FRC_Assert(m_interrupt != HAL_kInvalidHandle);
|
||||
int32_t status = 0;
|
||||
HAL_CleanInterrupts(m_interrupt, &status);
|
||||
// Ignore status, as an invalid handle just needs to be ignored.
|
||||
m_interrupt = HAL_kInvalidHandle;
|
||||
m_interruptHandler = nullptr;
|
||||
}
|
||||
|
||||
InterruptableSensorBase::WaitResult InterruptableSensorBase::WaitForInterrupt(
|
||||
units::second_t timeout, bool ignorePrevious) {
|
||||
FRC_Assert(m_interrupt != HAL_kInvalidHandle);
|
||||
int32_t status = 0;
|
||||
int result;
|
||||
|
||||
result = HAL_WaitForInterrupt(m_interrupt, timeout.to<double>(),
|
||||
ignorePrevious, &status);
|
||||
FRC_CheckErrorStatus(status, "{}", "WaitForInterrupt");
|
||||
|
||||
// Rising edge result is the interrupt bit set in the byte 0xFF
|
||||
// Falling edge result is the interrupt bit set in the byte 0xFF00
|
||||
// Set any bit set to be true for that edge, and AND the 2 results
|
||||
// together to match the existing enum for all interrupts
|
||||
int32_t rising = (result & 0xFF) ? 0x1 : 0x0;
|
||||
int32_t falling = ((result & 0xFF00) ? 0x0100 : 0x0);
|
||||
return static_cast<WaitResult>(falling | rising);
|
||||
}
|
||||
|
||||
void InterruptableSensorBase::EnableInterrupts() {
|
||||
FRC_Assert(m_interrupt != HAL_kInvalidHandle);
|
||||
int32_t status = 0;
|
||||
HAL_EnableInterrupts(m_interrupt, &status);
|
||||
FRC_CheckErrorStatus(status, "{}", "EnableInterrupts");
|
||||
}
|
||||
|
||||
void InterruptableSensorBase::DisableInterrupts() {
|
||||
FRC_Assert(m_interrupt != HAL_kInvalidHandle);
|
||||
int32_t status = 0;
|
||||
HAL_DisableInterrupts(m_interrupt, &status);
|
||||
FRC_CheckErrorStatus(status, "{}", "DisableInterrupts");
|
||||
}
|
||||
|
||||
units::second_t InterruptableSensorBase::ReadRisingTimestamp() {
|
||||
FRC_Assert(m_interrupt != HAL_kInvalidHandle);
|
||||
int32_t status = 0;
|
||||
int64_t timestamp = HAL_ReadInterruptRisingTimestamp(m_interrupt, &status);
|
||||
FRC_CheckErrorStatus(status, "{}", "ReadRisingTimestamp");
|
||||
return units::microsecond_t(timestamp);
|
||||
}
|
||||
|
||||
units::second_t InterruptableSensorBase::ReadFallingTimestamp() {
|
||||
FRC_Assert(m_interrupt != HAL_kInvalidHandle);
|
||||
int32_t status = 0;
|
||||
int64_t timestamp = HAL_ReadInterruptFallingTimestamp(m_interrupt, &status);
|
||||
FRC_CheckErrorStatus(status, "{}", "ReadFallingTimestamp");
|
||||
return units::microsecond_t(timestamp);
|
||||
}
|
||||
|
||||
void InterruptableSensorBase::SetUpSourceEdge(bool risingEdge,
|
||||
bool fallingEdge) {
|
||||
if (m_interrupt == HAL_kInvalidHandle) {
|
||||
throw FRC_MakeError(
|
||||
err::NullParameter, "{}",
|
||||
"You must call RequestInterrupts before SetUpSourceEdge");
|
||||
}
|
||||
if (m_interrupt != HAL_kInvalidHandle) {
|
||||
int32_t status = 0;
|
||||
HAL_SetInterruptUpSourceEdge(m_interrupt, risingEdge, fallingEdge, &status);
|
||||
FRC_CheckErrorStatus(status, "{}", "SetUpSourceEdge");
|
||||
}
|
||||
}
|
||||
|
||||
void InterruptableSensorBase::AllocateInterrupts(bool watcher) {
|
||||
FRC_Assert(m_interrupt == HAL_kInvalidHandle);
|
||||
// Expects the calling leaf class to allocate an interrupt index.
|
||||
int32_t status = 0;
|
||||
m_interrupt = HAL_InitializeInterrupts(watcher, &status);
|
||||
FRC_CheckErrorStatus(status, "{}", "AllocateInterrupts");
|
||||
}
|
||||
105
wpilibc/src/main/native/cpp/SynchronousInterrupt.cpp
Normal file
105
wpilibc/src/main/native/cpp/SynchronousInterrupt.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "frc/SynchronousInterrupt.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <hal/Interrupts.h>
|
||||
#include <wpi/NullDeleter.h>
|
||||
|
||||
#include "frc/DigitalSource.h"
|
||||
#include "frc/Errors.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
SynchronousInterrupt::SynchronousInterrupt(DigitalSource& source)
|
||||
: m_source{&source, wpi::NullDeleter<DigitalSource>()} {
|
||||
InitSynchronousInterrupt();
|
||||
}
|
||||
SynchronousInterrupt::SynchronousInterrupt(DigitalSource* source)
|
||||
: m_source{source, wpi::NullDeleter<DigitalSource>()} {
|
||||
if (m_source == nullptr) {
|
||||
FRC_CheckErrorStatus(frc::err::NullParameter, "{}", "Source is null");
|
||||
} else {
|
||||
InitSynchronousInterrupt();
|
||||
}
|
||||
}
|
||||
SynchronousInterrupt::SynchronousInterrupt(
|
||||
std::shared_ptr<DigitalSource> source)
|
||||
: m_source{std::move(source)} {
|
||||
if (m_source == nullptr) {
|
||||
FRC_CheckErrorStatus(frc::err::NullParameter, "{}", "Source is null");
|
||||
} else {
|
||||
InitSynchronousInterrupt();
|
||||
}
|
||||
}
|
||||
|
||||
void SynchronousInterrupt::InitSynchronousInterrupt() {
|
||||
int32_t status = 0;
|
||||
m_handle = HAL_InitializeInterrupts(&status);
|
||||
FRC_CheckErrorStatus(status, "{}", "Interrupt failed to initialize");
|
||||
HAL_RequestInterrupts(m_handle, m_source->GetPortHandleForRouting(),
|
||||
static_cast<HAL_AnalogTriggerType>(
|
||||
m_source->GetAnalogTriggerTypeForRouting()),
|
||||
&status);
|
||||
FRC_CheckErrorStatus(status, "{}", "Interrupt request failed");
|
||||
HAL_SetInterruptUpSourceEdge(m_handle, true, false, &status);
|
||||
FRC_CheckErrorStatus(status, "{}", "Interrupt setting up source edge failed");
|
||||
}
|
||||
|
||||
SynchronousInterrupt::~SynchronousInterrupt() {
|
||||
HAL_CleanInterrupts(m_handle);
|
||||
}
|
||||
|
||||
inline SynchronousInterrupt::WaitResult operator|(
|
||||
SynchronousInterrupt::WaitResult lhs,
|
||||
SynchronousInterrupt::WaitResult rhs) {
|
||||
using T = std::underlying_type_t<SynchronousInterrupt::WaitResult>;
|
||||
return static_cast<SynchronousInterrupt::WaitResult>(static_cast<T>(lhs) |
|
||||
static_cast<T>(rhs));
|
||||
}
|
||||
|
||||
SynchronousInterrupt::WaitResult SynchronousInterrupt::WaitForInterrupt(
|
||||
units::second_t timeout, bool ignorePrevious) {
|
||||
int32_t status = 0;
|
||||
auto result = HAL_WaitForInterrupt(m_handle, timeout.to<double>(),
|
||||
ignorePrevious, &status);
|
||||
|
||||
auto rising =
|
||||
((result & 0xFF) != 0) ? WaitResult::kRisingEdge : WaitResult::kTimeout;
|
||||
auto falling = ((result & 0xFF00) != 0) ? WaitResult::kFallingEdge
|
||||
: WaitResult::kTimeout;
|
||||
|
||||
return rising | falling;
|
||||
}
|
||||
|
||||
void SynchronousInterrupt::SetInterruptEdges(bool risingEdge,
|
||||
bool fallingEdge) {
|
||||
int32_t status = 0;
|
||||
HAL_SetInterruptUpSourceEdge(m_handle, risingEdge, fallingEdge, &status);
|
||||
FRC_CheckErrorStatus(status, "{}", "Interrupt setting edges failed");
|
||||
}
|
||||
|
||||
void SynchronousInterrupt::WakeupWaitingInterrupt() {
|
||||
int32_t status = 0;
|
||||
HAL_ReleaseWaitingInterrupt(m_handle, &status);
|
||||
FRC_CheckErrorStatus(status, "{}", "Interrupt wakeup failed");
|
||||
}
|
||||
|
||||
units::second_t SynchronousInterrupt::GetRisingTimestamp() {
|
||||
int32_t status = 0;
|
||||
auto ts = HAL_ReadInterruptRisingTimestamp(m_handle, &status);
|
||||
FRC_CheckErrorStatus(status, "{}", "Interrupt rising timestamp failed");
|
||||
units::microsecond_t ms{static_cast<double>(ts)};
|
||||
return ms;
|
||||
}
|
||||
|
||||
units::second_t SynchronousInterrupt::GetFallingTimestamp() {
|
||||
int32_t status = 0;
|
||||
auto ts = HAL_ReadInterruptFallingTimestamp(m_handle, &status);
|
||||
FRC_CheckErrorStatus(status, "{}", "Interrupt falling timestamp failed");
|
||||
units::microsecond_t ms{static_cast<double>(ts)};
|
||||
return ms;
|
||||
}
|
||||
150
wpilibc/src/main/native/include/frc/AsynchronousInterrupt.h
Normal file
150
wpilibc/src/main/native/include/frc/AsynchronousInterrupt.h
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <frc/SynchronousInterrupt.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
namespace frc {
|
||||
/**
|
||||
* Class for handling asynchronous interrupts.
|
||||
*
|
||||
* <p> By default, interrupts will occur on rising edge. Callbacks are disabled
|
||||
* by default, and Enable() must be called before they will occur.
|
||||
*
|
||||
* <p> Both rising and falling edge can be indiciated if both a rising and
|
||||
* falling happen between callbacks.
|
||||
*
|
||||
* <p>Synchronous interrupts are handled by the SynchronousInterrupt class.
|
||||
*/
|
||||
class AsynchronousInterrupt {
|
||||
public:
|
||||
/**
|
||||
* Construct an Asynchronous Interrupt from a Digital Source.
|
||||
*
|
||||
* <p> At construction, the interrupt will trigger on the rising edge.
|
||||
*
|
||||
* <p> The first bool in the callback is rising, the 2nd is falling.
|
||||
*/
|
||||
AsynchronousInterrupt(DigitalSource& source,
|
||||
std::function<void(bool, bool)> callback);
|
||||
|
||||
/**
|
||||
* Construct an Asynchronous Interrupt from a Digital Source.
|
||||
*
|
||||
* <p> At construction, the interrupt will trigger on the rising edge.
|
||||
*
|
||||
* <p> The first bool in the callback is rising, the 2nd is falling.
|
||||
*/
|
||||
AsynchronousInterrupt(DigitalSource* source,
|
||||
std::function<void(bool, bool)> callback);
|
||||
|
||||
/**
|
||||
* Construct an Asynchronous Interrupt from a Digital Source.
|
||||
*
|
||||
* <p> At construction, the interrupt will trigger on the rising edge.
|
||||
*
|
||||
* <p> The first bool in the callback is rising, the 2nd is falling.
|
||||
*/
|
||||
AsynchronousInterrupt(std::shared_ptr<DigitalSource> source,
|
||||
std::function<void(bool, bool)> callback);
|
||||
|
||||
/**
|
||||
* Construct an Asynchronous Interrupt from a Digital Source.
|
||||
*
|
||||
* <p> At construction, the interrupt will trigger on the rising edge.
|
||||
*
|
||||
* <p> The first bool in the callback is rising, the 2nd is falling.
|
||||
*/
|
||||
template <typename Callable, typename Arg, typename... Args>
|
||||
AsynchronousInterrupt(DigitalSource& source, Callable&& f, Arg&& arg,
|
||||
Args&&... args)
|
||||
: AsynchronousInterrupt(
|
||||
source, std::bind(std::forward<Callable>(f), std::forward<Arg>(arg),
|
||||
std::forward<Args>(args)...)) {}
|
||||
|
||||
/**
|
||||
* Construct an Asynchronous Interrupt from a Digital Source.
|
||||
*
|
||||
* <p> At construction, the interrupt will trigger on the rising edge.
|
||||
*
|
||||
* <p> The first bool in the callback is rising, the 2nd is falling.
|
||||
*/
|
||||
template <typename Callable, typename Arg, typename... Args>
|
||||
AsynchronousInterrupt(DigitalSource* source, Callable&& f, Arg&& arg,
|
||||
Args&&... args)
|
||||
: AsynchronousInterrupt(
|
||||
source, std::bind(std::forward<Callable>(f), std::forward<Arg>(arg),
|
||||
std::forward<Args>(args)...)) {}
|
||||
|
||||
/**
|
||||
* Construct an Asynchronous Interrupt from a Digital Source.
|
||||
*
|
||||
* <p> At construction, the interrupt will trigger on the rising edge.
|
||||
*
|
||||
* <p> The first bool in the callback is rising, the 2nd is falling.
|
||||
*/
|
||||
template <typename Callable, typename Arg, typename... Args>
|
||||
AsynchronousInterrupt(std::shared_ptr<DigitalSource> source, Callable&& f,
|
||||
Arg&& arg, Args&&... args)
|
||||
: AsynchronousInterrupt(
|
||||
source, std::bind(std::forward<Callable>(f), std::forward<Arg>(arg),
|
||||
std::forward<Args>(args)...)) {}
|
||||
|
||||
~AsynchronousInterrupt();
|
||||
|
||||
/**
|
||||
* Enables interrupt callbacks. Before this, callbacks will not occur. Does
|
||||
* nothing if already enabled.
|
||||
*/
|
||||
void Enable();
|
||||
|
||||
/**
|
||||
* Disables interrupt callbacks. Does nothing if already disabled.
|
||||
*/
|
||||
void Disable();
|
||||
|
||||
/**
|
||||
* Set which edges to trigger the interrupt on.
|
||||
*
|
||||
* @param risingEdge Trigger on rising edge
|
||||
* @param fallingEdge Trigger on falling edge
|
||||
*/
|
||||
void SetInterruptEdges(bool risingEdge, bool fallingEdge);
|
||||
|
||||
/**
|
||||
* Get the timestamp of the last rising edge.
|
||||
*
|
||||
* <p>This function does not require the interrupt to be enabled to work.
|
||||
*
|
||||
* <p>This only works if rising edge was configured using SetInterruptEdges.
|
||||
* @return the timestamp in seconds relative to getFPGATime
|
||||
*/
|
||||
units::second_t GetRisingTimestamp();
|
||||
|
||||
/**
|
||||
* Get the timestamp of the last falling edge.
|
||||
*
|
||||
* <p>This function does not require the interrupt to be enabled to work.
|
||||
*
|
||||
* <p>This only works if falling edge was configured using SetInterruptEdges.
|
||||
* @return the timestamp in seconds relative to getFPGATime
|
||||
*/
|
||||
units::second_t GetFallingTimestamp();
|
||||
|
||||
private:
|
||||
void ThreadMain();
|
||||
|
||||
std::atomic_bool m_keepRunning{false};
|
||||
std::thread m_thread;
|
||||
SynchronousInterrupt m_interrupt;
|
||||
std::function<void(bool, bool)> m_callback;
|
||||
};
|
||||
} // namespace frc
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include <hal/Types.h>
|
||||
|
||||
#include "frc/InterruptableSensorBase.h"
|
||||
#include "frc/AnalogTriggerType.h"
|
||||
|
||||
namespace frc {
|
||||
|
||||
@@ -19,14 +19,14 @@ namespace frc {
|
||||
* constructed and freed when finished for the source. The source can either be
|
||||
* a digital input or analog trigger but not both.
|
||||
*/
|
||||
class DigitalSource : public InterruptableSensorBase {
|
||||
class DigitalSource {
|
||||
public:
|
||||
DigitalSource() = default;
|
||||
DigitalSource(DigitalSource&&) = default;
|
||||
DigitalSource& operator=(DigitalSource&&) = default;
|
||||
|
||||
HAL_Handle GetPortHandleForRouting() const override = 0;
|
||||
AnalogTriggerType GetAnalogTriggerTypeForRouting() const override = 0;
|
||||
virtual HAL_Handle GetPortHandleForRouting() const = 0;
|
||||
virtual AnalogTriggerType GetAnalogTriggerTypeForRouting() const = 0;
|
||||
virtual bool IsAnalogTrigger() const = 0;
|
||||
virtual int GetChannel() const = 0;
|
||||
};
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <hal/Interrupts.h>
|
||||
#include <units/time.h>
|
||||
|
||||
#include "frc/AnalogTriggerType.h"
|
||||
|
||||
namespace frc {
|
||||
|
||||
class InterruptableSensorBase {
|
||||
public:
|
||||
enum WaitResult {
|
||||
kTimeout = 0x0,
|
||||
kRisingEdge = 0x1,
|
||||
kFallingEdge = 0x100,
|
||||
kBoth = 0x101,
|
||||
};
|
||||
|
||||
/**
|
||||
* Handler for interrupts.
|
||||
*
|
||||
* First parameter is if rising, 2nd is if falling.
|
||||
*/
|
||||
using InterruptEventHandler = std::function<void(WaitResult)>;
|
||||
|
||||
InterruptableSensorBase() = default;
|
||||
|
||||
/**
|
||||
* Free the resources for an interrupt event.
|
||||
*/
|
||||
virtual ~InterruptableSensorBase();
|
||||
|
||||
InterruptableSensorBase(InterruptableSensorBase&&) = default;
|
||||
InterruptableSensorBase& operator=(InterruptableSensorBase&&) = default;
|
||||
|
||||
virtual HAL_Handle GetPortHandleForRouting() const = 0;
|
||||
virtual AnalogTriggerType GetAnalogTriggerTypeForRouting() const = 0;
|
||||
|
||||
/**
|
||||
* Request one of the 8 interrupts asynchronously on this digital input.
|
||||
*
|
||||
* Request interrupts in asynchronous mode where the user's interrupt handler
|
||||
* will be called when the interrupt fires. Users that want control over the
|
||||
* thread priority should use the synchronous method with their own spawned
|
||||
* thread. The default is interrupt on rising edges only.
|
||||
*/
|
||||
virtual void RequestInterrupts(HAL_InterruptHandlerFunction handler,
|
||||
void* param);
|
||||
|
||||
/**
|
||||
* Request one of the 8 interrupts asynchronously on this digital input.
|
||||
*
|
||||
* Request interrupts in asynchronous mode where the user's interrupt handler
|
||||
* will be called when the interrupt fires. Users that want control over the
|
||||
* thread priority should use the synchronous method with their own spawned
|
||||
* thread. The default is interrupt on rising edges only.
|
||||
*/
|
||||
virtual void RequestInterrupts(InterruptEventHandler handler);
|
||||
|
||||
/**
|
||||
* Request one of the 8 interrupts synchronously on this digital input.
|
||||
*
|
||||
* Request interrupts in synchronous mode where the user program will have to
|
||||
* explicitly wait for the interrupt to occur using WaitForInterrupt.
|
||||
* The default is interrupt on rising edges only.
|
||||
*/
|
||||
virtual void RequestInterrupts();
|
||||
|
||||
/**
|
||||
* Cancel interrupts on this device.
|
||||
*
|
||||
* This deallocates all the chipobject structures and disables any interrupts.
|
||||
*/
|
||||
virtual void CancelInterrupts();
|
||||
|
||||
/**
|
||||
* In synchronous mode, wait for the defined interrupt to occur.
|
||||
*
|
||||
* You should <b>NOT</b> attempt to read the sensor from another thread while
|
||||
* waiting for an interrupt. This is not threadsafe, and can cause memory
|
||||
* corruption
|
||||
*
|
||||
* @param timeout Timeout
|
||||
* @param ignorePrevious If true, ignore interrupts that happened before
|
||||
* WaitForInterrupt was called.
|
||||
* @return What interrupts fired
|
||||
*/
|
||||
virtual WaitResult WaitForInterrupt(units::second_t timeout,
|
||||
bool ignorePrevious = true);
|
||||
|
||||
/**
|
||||
* Enable interrupts to occur on this input.
|
||||
*
|
||||
* Interrupts are disabled when the RequestInterrupt call is made. This gives
|
||||
* time to do the setup of the other options before starting to field
|
||||
* interrupts.
|
||||
*/
|
||||
virtual void EnableInterrupts();
|
||||
|
||||
/**
|
||||
* Disable Interrupts without without deallocating structures.
|
||||
*/
|
||||
virtual void DisableInterrupts();
|
||||
|
||||
/**
|
||||
* Return the timestamp for the rising interrupt that occurred most recently.
|
||||
*
|
||||
* This is in the same time domain as GetClock(). The rising-edge interrupt
|
||||
* should be enabled with SetUpSourceEdge().
|
||||
*
|
||||
* @return Timestamp in seconds since boot.
|
||||
*/
|
||||
virtual units::second_t ReadRisingTimestamp();
|
||||
|
||||
/**
|
||||
* Return the timestamp for the falling interrupt that occurred most recently.
|
||||
*
|
||||
* This is in the same time domain as GetClock().
|
||||
* The falling-edge interrupt should be enabled with
|
||||
* {@link #DigitalInput.SetUpSourceEdge}
|
||||
*
|
||||
* @return Timestamp in seconds since boot.
|
||||
*/
|
||||
virtual units::second_t ReadFallingTimestamp();
|
||||
|
||||
/**
|
||||
* Set which edge to trigger interrupts on
|
||||
*
|
||||
* @param risingEdge true to interrupt on rising edge
|
||||
* @param fallingEdge true to interrupt on falling edge
|
||||
*/
|
||||
virtual void SetUpSourceEdge(bool risingEdge, bool fallingEdge);
|
||||
|
||||
protected:
|
||||
hal::Handle<HAL_InterruptHandle> m_interrupt;
|
||||
std::unique_ptr<InterruptEventHandler> m_interruptHandler{nullptr};
|
||||
|
||||
void AllocateInterrupts(bool watcher);
|
||||
};
|
||||
|
||||
} // namespace frc
|
||||
98
wpilibc/src/main/native/include/frc/SynchronousInterrupt.h
Normal file
98
wpilibc/src/main/native/include/frc/SynchronousInterrupt.h
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <hal/Types.h>
|
||||
#include <units/time.h>
|
||||
|
||||
namespace frc {
|
||||
class DigitalSource;
|
||||
|
||||
/**
|
||||
* Class for handling ssynchronous interrupts.
|
||||
*
|
||||
* <p> By default, interrupts will occur on rising edge.
|
||||
*
|
||||
* <p> Asynchronous interrupts are handled by the AsynchronousInterrupt class.
|
||||
*/
|
||||
class SynchronousInterrupt {
|
||||
public:
|
||||
enum WaitResult {
|
||||
kTimeout = 0x0,
|
||||
kRisingEdge = 0x1,
|
||||
kFallingEdge = 0x100,
|
||||
kBoth = 0x101,
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a Synchronous Interrupt from a Digital Source.
|
||||
*/
|
||||
explicit SynchronousInterrupt(DigitalSource& source);
|
||||
|
||||
/**
|
||||
* Construct a Synchronous Interrupt from a Digital Source.
|
||||
*/
|
||||
explicit SynchronousInterrupt(DigitalSource* source);
|
||||
|
||||
/**
|
||||
* Construct a Synchronous Interrupt from a Digital Source.
|
||||
*/
|
||||
explicit SynchronousInterrupt(std::shared_ptr<DigitalSource> source);
|
||||
|
||||
~SynchronousInterrupt();
|
||||
|
||||
SynchronousInterrupt(SynchronousInterrupt&&) = default;
|
||||
SynchronousInterrupt& operator=(SynchronousInterrupt&&) = default;
|
||||
|
||||
/**
|
||||
* Wait for an interrupt to occur.
|
||||
*
|
||||
* <p> Both rising and falling edge can be returned if both a rising and
|
||||
* falling happened between calls, and ignorePrevious is false.
|
||||
*
|
||||
* @param timeout The timeout to wait for. 0s or less will return immediately.
|
||||
* @param ignorePrevious True to ignore any previous interrupts, false to
|
||||
* return interrupt value if an interrupt has occured since last call.
|
||||
* @return The edge(s) that were triggered, or timeout.
|
||||
*/
|
||||
WaitResult WaitForInterrupt(units::second_t timeout,
|
||||
bool ignorePrevious = true);
|
||||
|
||||
/**
|
||||
* Set which edges cause an interrupt to occur.
|
||||
*
|
||||
* @param risingEdge true to trigger on rising edge, false otherwise.
|
||||
* @param fallingEdge true to trigger on falling edge, false otherwise
|
||||
*/
|
||||
void SetInterruptEdges(bool risingEdge, bool fallingEdge);
|
||||
|
||||
/**
|
||||
* Get the timestamp (relative to FPGA Time) of the last rising edge.
|
||||
*/
|
||||
units::second_t GetRisingTimestamp();
|
||||
|
||||
/**
|
||||
* Get the timestamp of the last falling edge.
|
||||
*
|
||||
* <p>This function does not require the interrupt to be enabled to work.
|
||||
*
|
||||
* <p>This only works if falling edge was configured using setInterruptEdges.
|
||||
* @return the timestamp in seconds relative to getFPGATime
|
||||
*/
|
||||
units::second_t GetFallingTimestamp();
|
||||
|
||||
/**
|
||||
* Wake up an existing wait call. Can be called from any thread.
|
||||
*/
|
||||
void WakeupWaitingInterrupt();
|
||||
|
||||
private:
|
||||
void InitSynchronousInterrupt();
|
||||
std::shared_ptr<DigitalSource> m_source;
|
||||
hal::Handle<HAL_InterruptHandle> m_handle;
|
||||
};
|
||||
} // namespace frc
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "frc/AnalogInput.h"
|
||||
#include "frc/AnalogOutput.h"
|
||||
#include "frc/AnalogTrigger.h"
|
||||
#include "frc/AsynchronousInterrupt.h"
|
||||
#include "frc/Counter.h"
|
||||
#include "frc/Timer.h"
|
||||
#include "gtest/gtest.h"
|
||||
@@ -91,10 +92,6 @@ TEST(AnalogLoopTest, AnalogTriggerCounterWorks) {
|
||||
<< "Analog trigger counter did not count 50 ticks";
|
||||
}
|
||||
|
||||
static void InterruptHandler(uint32_t interruptAssertedMask, void* param) {
|
||||
*reinterpret_cast<int32_t*>(param) = 12345;
|
||||
}
|
||||
|
||||
TEST(AnalogLoopTest, AsynchronusInterruptWorks) {
|
||||
frc::AnalogInput input{TestBench::kFakeAnalogOutputChannel};
|
||||
frc::AnalogOutput output{TestBench::kAnalogOutputChannel};
|
||||
@@ -106,16 +103,19 @@ TEST(AnalogLoopTest, AsynchronusInterruptWorks) {
|
||||
// Given an interrupt handler that sets an int32_t to 12345
|
||||
std::shared_ptr<frc::AnalogTriggerOutput> triggerOutput =
|
||||
trigger.CreateOutput(frc::AnalogTriggerType::kState);
|
||||
triggerOutput->RequestInterrupts(InterruptHandler, ¶m);
|
||||
triggerOutput->EnableInterrupts();
|
||||
|
||||
frc::AsynchronousInterrupt interrupt{triggerOutput,
|
||||
[&](auto a, auto b) { param = 12345; }};
|
||||
|
||||
interrupt.Enable();
|
||||
|
||||
// If the analog output moves from below to above the window
|
||||
output.SetVoltage(0.0);
|
||||
frc::Wait(kDelayTime);
|
||||
output.SetVoltage(5.0);
|
||||
triggerOutput->CancelInterrupts();
|
||||
|
||||
// Then the int32_t should be 12345
|
||||
frc::Wait(kDelayTime);
|
||||
interrupt.Disable();
|
||||
|
||||
EXPECT_EQ(12345, param) << "The interrupt did not run.";
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
#include <units/time.h>
|
||||
|
||||
#include "TestBench.h"
|
||||
#include "frc/AsynchronousInterrupt.h"
|
||||
#include "frc/Counter.h"
|
||||
#include "frc/InterruptableSensorBase.h"
|
||||
#include "frc/SynchronousInterrupt.h"
|
||||
#include "frc/Timer.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
@@ -71,10 +72,10 @@ TEST_F(DIOLoopTest, DIOPWM) {
|
||||
m_output.EnablePWM(0.0);
|
||||
frc::Wait(0.5_s);
|
||||
m_output.UpdateDutyCycle(0.5);
|
||||
m_input.RequestInterrupts();
|
||||
m_input.SetUpSourceEdge(false, true);
|
||||
frc::InterruptableSensorBase::WaitResult result =
|
||||
m_input.WaitForInterrupt(3_s, true);
|
||||
frc::SynchronousInterrupt interrupt{m_output};
|
||||
interrupt.SetInterruptEdges(false, true);
|
||||
frc::SynchronousInterrupt::WaitResult result =
|
||||
interrupt.WaitForInterrupt(3_s, true);
|
||||
|
||||
frc::Wait(0.5_s);
|
||||
bool firstCycle = m_input.Get();
|
||||
@@ -96,7 +97,7 @@ TEST_F(DIOLoopTest, DIOPWM) {
|
||||
frc::Wait(0.5_s);
|
||||
bool secondAfterStop = m_input.Get();
|
||||
|
||||
EXPECT_EQ(frc::InterruptableSensorBase::WaitResult::kFallingEdge, result)
|
||||
EXPECT_EQ(frc::SynchronousInterrupt::WaitResult::kFallingEdge, result)
|
||||
<< "WaitForInterrupt was not falling.";
|
||||
|
||||
EXPECT_FALSE(firstCycle) << "Input not low after first delay";
|
||||
@@ -132,48 +133,46 @@ TEST_F(DIOLoopTest, FakeCounter) {
|
||||
EXPECT_EQ(100, counter.Get()) << "Counter did not count up to 100.";
|
||||
}
|
||||
|
||||
static void InterruptHandler(uint32_t interruptAssertedMask, void* param) {
|
||||
*reinterpret_cast<int32_t*>(param) = 12345;
|
||||
}
|
||||
|
||||
TEST_F(DIOLoopTest, AsynchronousInterruptWorks) {
|
||||
Reset();
|
||||
|
||||
int32_t param = 0;
|
||||
|
||||
// Given an interrupt handler that sets an int32_t to 12345
|
||||
m_input.RequestInterrupts(InterruptHandler, ¶m);
|
||||
m_input.EnableInterrupts();
|
||||
frc::AsynchronousInterrupt interrupt(m_input,
|
||||
[&](auto a, auto b) { param = 12345; });
|
||||
|
||||
interrupt.Enable();
|
||||
// If the voltage rises
|
||||
m_output.Set(false);
|
||||
m_output.Set(true);
|
||||
m_input.CancelInterrupts();
|
||||
|
||||
// Then the int32_t should be 12345
|
||||
frc::Wait(kDelayTime);
|
||||
|
||||
interrupt.Disable();
|
||||
|
||||
EXPECT_EQ(12345, param) << "The interrupt did not run.";
|
||||
}
|
||||
|
||||
static void* InterruptTriggerer(void* data) {
|
||||
auto& output = *static_cast<frc::DigitalOutput*>(data);
|
||||
output.Set(false);
|
||||
frc::Wait(kSynchronousInterruptTime);
|
||||
output.Set(true);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TEST_F(DIOLoopTest, SynchronousInterruptWorks) {
|
||||
// Given a synchronous interrupt
|
||||
m_input.RequestInterrupts();
|
||||
Reset();
|
||||
|
||||
// If we have another thread trigger the interrupt in a few seconds
|
||||
pthread_t interruptTriggererLoop;
|
||||
pthread_create(&interruptTriggererLoop, nullptr, InterruptTriggerer,
|
||||
&m_output);
|
||||
// Given a synchronous interrupt
|
||||
frc::SynchronousInterrupt interrupt(m_input);
|
||||
|
||||
std::thread thr([this]() {
|
||||
m_output.Set(false);
|
||||
frc::Wait(kSynchronousInterruptTime);
|
||||
m_output.Set(true);
|
||||
});
|
||||
|
||||
// Then this thread should pause and resume after that number of seconds
|
||||
frc::Timer timer;
|
||||
timer.Start();
|
||||
m_input.WaitForInterrupt(kSynchronousInterruptTime + 1_s);
|
||||
EXPECT_NEAR_UNITS(kSynchronousInterruptTime, timer.Get(),
|
||||
kSynchronousInterruptTimeTolerance);
|
||||
interrupt.WaitForInterrupt(kSynchronousInterruptTime + 1_s);
|
||||
auto time = timer.Get().to<double>();
|
||||
if (thr.joinable())
|
||||
thr.join();
|
||||
EXPECT_NEAR(kSynchronousInterruptTime.to<double>(), time,
|
||||
kSynchronousInterruptTimeTolerance.to<double>());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.wpilibj;
|
||||
|
||||
import static edu.wpi.first.wpilibj.util.ErrorMessages.requireNonNullParam;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Class for handling asynchrounous interrupts.
|
||||
*
|
||||
* <p>By default, interrupts will occur on rising edge. Callbacks are disabled by default, and
|
||||
* Enable() must be called before they will occur.
|
||||
*
|
||||
* <p>Both rising and falling edge can be indiciated if both a rising and falling happen between
|
||||
* callbacks.
|
||||
*
|
||||
* <p>Synchronous interrupts are handled by the SynchronousInterrupt class.
|
||||
*/
|
||||
public class AsynchronousInterrupt implements AutoCloseable {
|
||||
private final BiConsumer<Boolean, Boolean> m_callback;
|
||||
private final SynchronousInterrupt m_interrupt;
|
||||
|
||||
private final AtomicBoolean m_keepRunning = new AtomicBoolean(false);
|
||||
private Thread m_thread;
|
||||
|
||||
/**
|
||||
* Construct a new asynchronous interrupt using a Digital Source.
|
||||
*
|
||||
* <p>At construction, the interrupt will trigger on the rising edge.
|
||||
*
|
||||
* <p>Callbacks will not be triggered until enable() is called.
|
||||
*
|
||||
* @param source The digital source to use.
|
||||
* @param callback The callback to call on an interrupt
|
||||
*/
|
||||
public AsynchronousInterrupt(DigitalSource source, BiConsumer<Boolean, Boolean> callback) {
|
||||
m_callback = requireNonNullParam(callback, "callback", "AsynchronousInterrupt");
|
||||
m_interrupt = new SynchronousInterrupt(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the interrupt.
|
||||
*
|
||||
* <p>This does not close the associated digital source.
|
||||
*
|
||||
* <p>This will disable the interrupt if it is enabled.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
disable();
|
||||
m_interrupt.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables interrupt callbacks. Before this, callbacks will not occur. Does nothing if already
|
||||
* enabled.
|
||||
*/
|
||||
public void enable() {
|
||||
if (m_keepRunning.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_keepRunning.set(true);
|
||||
m_thread = new Thread(this::threadMain);
|
||||
}
|
||||
|
||||
/** Disables interrupt callbacks. Does nothing if already disabled. */
|
||||
public void disable() {
|
||||
m_keepRunning.set(false);
|
||||
m_interrupt.wakeupWaitingInterrupt();
|
||||
if (m_thread != null) {
|
||||
if (m_thread.isAlive()) {
|
||||
try {
|
||||
m_thread.interrupt();
|
||||
m_thread.join();
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
m_thread = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set which edges to trigger the interrupt on.
|
||||
*
|
||||
* @param risingEdge Trigger on rising edge
|
||||
* @param fallingEdge Trigger on falling edge
|
||||
*/
|
||||
public void setInterruptEdges(boolean risingEdge, boolean fallingEdge) {
|
||||
m_interrupt.setInterruptEdges(risingEdge, fallingEdge);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp of the last rising edge.
|
||||
*
|
||||
* <p>This function does not require the interrupt to be enabled to work.
|
||||
*
|
||||
* <p>This only works if rising edge was configured using setInterruptEdges.
|
||||
*
|
||||
* @return the timestamp in seconds relative to getFPGATime
|
||||
*/
|
||||
public double getRisingTimestamp() {
|
||||
return m_interrupt.getRisingTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp of the last falling edge.
|
||||
*
|
||||
* <p>This function does not require the interrupt to be enabled to work.
|
||||
*
|
||||
* <p>This only works if falling edge was configured using setInterruptEdges.
|
||||
*
|
||||
* @return the timestamp in seconds relative to getFPGATime
|
||||
*/
|
||||
public double getFallingTimestamp() {
|
||||
return m_interrupt.getFallingTimestamp();
|
||||
}
|
||||
|
||||
private void threadMain() {
|
||||
while (m_keepRunning.get()) {
|
||||
var result = m_interrupt.waitForInterruptRaw(10, false);
|
||||
if (!m_keepRunning.get()) {
|
||||
break;
|
||||
}
|
||||
if (result == 0) {
|
||||
continue;
|
||||
}
|
||||
m_callback.accept((result & 0x1) != 0, (result & 0x100) != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,9 +40,6 @@ public class DigitalInput extends DigitalSource implements Sendable {
|
||||
public void close() {
|
||||
super.close();
|
||||
SendableRegistry.remove(this);
|
||||
if (m_interrupt != 0) {
|
||||
cancelInterrupts();
|
||||
}
|
||||
DIOJNI.freeDIOPort(m_handle);
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,25 @@ package edu.wpi.first.wpilibj;
|
||||
* just provides a channel, then a digital input will be constructed and freed when finished for the
|
||||
* source. The source can either be a digital input or analog trigger but not both.
|
||||
*/
|
||||
public abstract class DigitalSource extends InterruptableSensorBase {
|
||||
public abstract class DigitalSource implements AutoCloseable {
|
||||
public abstract boolean isAnalogTrigger();
|
||||
|
||||
public abstract int getChannel();
|
||||
|
||||
/**
|
||||
* If this is an analog trigger.
|
||||
*
|
||||
* @return true if this is an analog trigger.
|
||||
*/
|
||||
public abstract int getAnalogTriggerTypeForRouting();
|
||||
|
||||
/**
|
||||
* The channel routing number.
|
||||
*
|
||||
* @return channel routing number
|
||||
*/
|
||||
public abstract int getPortHandleForRouting();
|
||||
|
||||
@Override
|
||||
public void close() {}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.wpilibj;
|
||||
|
||||
import edu.wpi.first.hal.InterruptJNI.InterruptJNIHandlerFunction;
|
||||
|
||||
/**
|
||||
* It is recommended that you use this class in conjunction with classes from {@link
|
||||
* java.util.concurrent.atomic} as these objects are all thread safe.
|
||||
*
|
||||
* @param <T> The type of the parameter that should be returned to the the method {@link
|
||||
* #interruptFired(int, Object)}
|
||||
*/
|
||||
public abstract class InterruptHandlerFunction<T> {
|
||||
/**
|
||||
* The entry point for the interrupt. When the interrupt fires the {@link #apply(int, Object)}
|
||||
* method is called. The outer class is provided as an interface to allow the implementer to pass
|
||||
* a generic object to the interrupt fired method.
|
||||
*/
|
||||
private class Function implements InterruptJNIHandlerFunction {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void apply(int interruptAssertedMask, Object param) {
|
||||
interruptFired(interruptAssertedMask, (T) param);
|
||||
}
|
||||
}
|
||||
|
||||
final Function m_function = new Function();
|
||||
|
||||
/**
|
||||
* This method is run every time an interrupt is fired.
|
||||
*
|
||||
* @param interruptAssertedMask Interrupt Mask
|
||||
* @param param The parameter provided by overriding the {@link #overridableParameter()} method.
|
||||
*/
|
||||
public abstract void interruptFired(int interruptAssertedMask, T param);
|
||||
|
||||
/**
|
||||
* Override this method if you would like to pass a specific parameter to the {@link
|
||||
* #interruptFired(int, Object)} when it is fired by the interrupt. This method is called once
|
||||
* when {@link InterruptableSensorBase#requestInterrupts(InterruptHandlerFunction)} is run.
|
||||
*
|
||||
* @return The object that should be passed to the interrupt when it runs
|
||||
*/
|
||||
public T overridableParameter() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.wpilibj;
|
||||
|
||||
import edu.wpi.first.hal.InterruptJNI;
|
||||
import edu.wpi.first.hal.util.AllocationException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/** Base for sensors to be used with interrupts. */
|
||||
public abstract class InterruptableSensorBase implements AutoCloseable {
|
||||
@SuppressWarnings("MissingJavadocMethod")
|
||||
public enum WaitResult {
|
||||
kTimeout(0x0),
|
||||
kRisingEdge(0x1),
|
||||
kFallingEdge(0x100),
|
||||
kBoth(0x101);
|
||||
|
||||
public final int value;
|
||||
|
||||
WaitResult(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static WaitResult getValue(boolean rising, boolean falling) {
|
||||
if (rising && falling) {
|
||||
return kBoth;
|
||||
} else if (rising) {
|
||||
return kRisingEdge;
|
||||
} else if (falling) {
|
||||
return kFallingEdge;
|
||||
} else {
|
||||
return kTimeout;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The interrupt resource. */
|
||||
protected int m_interrupt = InterruptJNI.HalInvalidHandle;
|
||||
|
||||
/** Flags if the interrupt being allocated is synchronous. */
|
||||
protected boolean m_isSynchronousInterrupt;
|
||||
|
||||
/** Create a new InterrupatableSensorBase. */
|
||||
public InterruptableSensorBase() {
|
||||
m_interrupt = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (m_interrupt != 0) {
|
||||
cancelInterrupts();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is an analog trigger.
|
||||
*
|
||||
* @return true if this is an analog trigger.
|
||||
*/
|
||||
public abstract int getAnalogTriggerTypeForRouting();
|
||||
|
||||
/**
|
||||
* The channel routing number.
|
||||
*
|
||||
* @return channel routing number
|
||||
*/
|
||||
public abstract int getPortHandleForRouting();
|
||||
|
||||
/**
|
||||
* Request one of the 8 interrupts asynchronously on this digital input.
|
||||
*
|
||||
* @param handler The {@link Consumer} that will be called whenever there is an interrupt on this
|
||||
* device. Request interrupts in synchronous mode where the user program interrupt handler
|
||||
* will be called when an interrupt occurs. The default is interrupt on rising edges only.
|
||||
*/
|
||||
public void requestInterrupts(Consumer<WaitResult> handler) {
|
||||
if (m_interrupt != 0) {
|
||||
throw new AllocationException("The interrupt has already been allocated");
|
||||
}
|
||||
|
||||
allocateInterrupts(false);
|
||||
|
||||
assert m_interrupt != 0;
|
||||
|
||||
InterruptJNI.requestInterrupts(
|
||||
m_interrupt, getPortHandleForRouting(), getAnalogTriggerTypeForRouting());
|
||||
setUpSourceEdge(true, false);
|
||||
InterruptJNI.attachInterruptHandler(
|
||||
m_interrupt,
|
||||
(mask, obj) -> {
|
||||
// Rising edge result is the interrupt bit set in the byte 0xFF
|
||||
// Falling edge result is the interrupt bit set in the byte 0xFF00
|
||||
boolean rising = (mask & 0xFF) != 0;
|
||||
boolean falling = (mask & 0xFF00) != 0;
|
||||
handler.accept(WaitResult.getValue(rising, falling));
|
||||
},
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request one of the 8 interrupts asynchronously on this digital input.
|
||||
*
|
||||
* @param handler The {@link InterruptHandlerFunction} that contains the method {@link
|
||||
* InterruptHandlerFunction#interruptFired(int, Object)} that will be called whenever there is
|
||||
* an interrupt on this device. Request interrupts in synchronous mode where the user program
|
||||
* interrupt handler will be called when an interrupt occurs. The default is interrupt on
|
||||
* rising edges only.
|
||||
*/
|
||||
public void requestInterrupts(InterruptHandlerFunction<?> handler) {
|
||||
if (m_interrupt != 0) {
|
||||
throw new AllocationException("The interrupt has already been allocated");
|
||||
}
|
||||
|
||||
allocateInterrupts(false);
|
||||
|
||||
assert m_interrupt != 0;
|
||||
|
||||
InterruptJNI.requestInterrupts(
|
||||
m_interrupt, getPortHandleForRouting(), getAnalogTriggerTypeForRouting());
|
||||
setUpSourceEdge(true, false);
|
||||
InterruptJNI.attachInterruptHandler(
|
||||
m_interrupt, handler.m_function, handler.overridableParameter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Request one of the 8 interrupts synchronously on this digital input. Request interrupts in
|
||||
* synchronous mode where the user program will have to explicitly wait for the interrupt to occur
|
||||
* using {@link #waitForInterrupt}. The default is interrupt on rising edges only.
|
||||
*/
|
||||
public void requestInterrupts() {
|
||||
if (m_interrupt != 0) {
|
||||
throw new AllocationException("The interrupt has already been allocated");
|
||||
}
|
||||
|
||||
allocateInterrupts(true);
|
||||
|
||||
assert m_interrupt != 0;
|
||||
|
||||
InterruptJNI.requestInterrupts(
|
||||
m_interrupt, getPortHandleForRouting(), getAnalogTriggerTypeForRouting());
|
||||
setUpSourceEdge(true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate the interrupt.
|
||||
*
|
||||
* @param watcher true if the interrupt should be in synchronous mode where the user program will
|
||||
* have to explicitly wait for the interrupt to occur.
|
||||
*/
|
||||
protected void allocateInterrupts(boolean watcher) {
|
||||
m_isSynchronousInterrupt = watcher;
|
||||
|
||||
m_interrupt = InterruptJNI.initializeInterrupts(watcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel interrupts on this device. This deallocates all the chipobject structures and disables
|
||||
* any interrupts.
|
||||
*/
|
||||
public void cancelInterrupts() {
|
||||
if (m_interrupt == 0) {
|
||||
throw new IllegalStateException("The interrupt is not allocated.");
|
||||
}
|
||||
InterruptJNI.cleanInterrupts(m_interrupt);
|
||||
m_interrupt = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* In synchronous mode, wait for the defined interrupt to occur.
|
||||
*
|
||||
* @param timeout Timeout in seconds
|
||||
* @param ignorePrevious If true, ignore interrupts that happened before waitForInterrupt was
|
||||
* called.
|
||||
* @return Result of the wait.
|
||||
*/
|
||||
public WaitResult waitForInterrupt(double timeout, boolean ignorePrevious) {
|
||||
if (m_interrupt == 0) {
|
||||
throw new IllegalStateException("The interrupt is not allocated.");
|
||||
}
|
||||
int result = InterruptJNI.waitForInterrupt(m_interrupt, timeout, ignorePrevious);
|
||||
|
||||
// Rising edge result is the interrupt bit set in the byte 0xFF
|
||||
// Falling edge result is the interrupt bit set in the byte 0xFF00
|
||||
// Set any bit set to be true for that edge, and AND the 2 results
|
||||
// together to match the existing enum for all interrupts
|
||||
boolean rising = (result & 0xFF) != 0;
|
||||
boolean falling = (result & 0xFF00) != 0;
|
||||
return WaitResult.getValue(rising, falling);
|
||||
}
|
||||
|
||||
/**
|
||||
* In synchronous mode, wait for the defined interrupt to occur.
|
||||
*
|
||||
* @param timeout Timeout in seconds
|
||||
* @return Result of the wait.
|
||||
*/
|
||||
public WaitResult waitForInterrupt(double timeout) {
|
||||
return waitForInterrupt(timeout, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable interrupts to occur on this input. Interrupts are disabled when the RequestInterrupt
|
||||
* call is made. This gives time to do the setup of the other options before starting to field
|
||||
* interrupts.
|
||||
*/
|
||||
public void enableInterrupts() {
|
||||
if (m_interrupt == 0) {
|
||||
throw new IllegalStateException("The interrupt is not allocated.");
|
||||
}
|
||||
if (m_isSynchronousInterrupt) {
|
||||
throw new IllegalStateException("You do not need to enable synchronous interrupts");
|
||||
}
|
||||
InterruptJNI.enableInterrupts(m_interrupt);
|
||||
}
|
||||
|
||||
/** Disable Interrupts without without deallocating structures. */
|
||||
public void disableInterrupts() {
|
||||
if (m_interrupt == 0) {
|
||||
throw new IllegalStateException("The interrupt is not allocated.");
|
||||
}
|
||||
if (m_isSynchronousInterrupt) {
|
||||
throw new IllegalStateException("You can not disable synchronous interrupts");
|
||||
}
|
||||
InterruptJNI.disableInterrupts(m_interrupt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the timestamp for the rising interrupt that occurred most recently. This is in the same
|
||||
* time domain as getClock(). The rising-edge interrupt should be enabled with {@link
|
||||
* #setUpSourceEdge}.
|
||||
*
|
||||
* @return Timestamp in seconds since boot.
|
||||
*/
|
||||
public double readRisingTimestamp() {
|
||||
if (m_interrupt == 0) {
|
||||
throw new IllegalStateException("The interrupt is not allocated.");
|
||||
}
|
||||
return InterruptJNI.readInterruptRisingTimestamp(m_interrupt) * 1e-6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the timestamp for the falling interrupt that occurred most recently. This is in the same
|
||||
* time domain as getClock(). The falling-edge interrupt should be enabled with {@link
|
||||
* #setUpSourceEdge}.
|
||||
*
|
||||
* @return Timestamp in seconds since boot.
|
||||
*/
|
||||
public double readFallingTimestamp() {
|
||||
if (m_interrupt == 0) {
|
||||
throw new IllegalStateException("The interrupt is not allocated.");
|
||||
}
|
||||
return InterruptJNI.readInterruptFallingTimestamp(m_interrupt) * 1e-6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set which edge to trigger interrupts on.
|
||||
*
|
||||
* @param risingEdge true to interrupt on rising edge
|
||||
* @param fallingEdge true to interrupt on falling edge
|
||||
*/
|
||||
public void setUpSourceEdge(boolean risingEdge, boolean fallingEdge) {
|
||||
if (m_interrupt != 0) {
|
||||
InterruptJNI.setInterruptUpSourceEdge(m_interrupt, risingEdge, fallingEdge);
|
||||
} else {
|
||||
throw new IllegalArgumentException("You must call RequestInterrupts before setUpSourceEdge");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.wpilibj;
|
||||
|
||||
import static edu.wpi.first.wpilibj.util.ErrorMessages.requireNonNullParam;
|
||||
|
||||
import edu.wpi.first.hal.InterruptJNI;
|
||||
|
||||
/**
|
||||
* Class for handling sychronous interrupts.
|
||||
*
|
||||
* <p>By default, interrupts will occur on rising edge.
|
||||
*
|
||||
* <p>Asynchronous interrupts are handled by the AsynchronousInterrupt class.
|
||||
*/
|
||||
public class SynchronousInterrupt implements AutoCloseable {
|
||||
@SuppressWarnings("PMD.SingularField")
|
||||
private final DigitalSource m_source;
|
||||
|
||||
private final int m_handle;
|
||||
|
||||
@SuppressWarnings("JavadocMethod")
|
||||
public enum WaitResult {
|
||||
kTimeout(0x0),
|
||||
kRisingEdge(0x1),
|
||||
kFallingEdge(0x100),
|
||||
kBoth(0x101);
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public final int value;
|
||||
|
||||
WaitResult(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/** Create a wait result. */
|
||||
public static WaitResult getValue(boolean rising, boolean falling) {
|
||||
if (rising && falling) {
|
||||
return kBoth;
|
||||
} else if (rising) {
|
||||
return kRisingEdge;
|
||||
} else if (falling) {
|
||||
return kFallingEdge;
|
||||
} else {
|
||||
return kTimeout;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new synchronous interrupt using a DigitalSource.
|
||||
*
|
||||
* <p>At construction, the interrupt will trigger on the rising edge.
|
||||
*
|
||||
* @param source The digital source to use.
|
||||
*/
|
||||
public SynchronousInterrupt(DigitalSource source) {
|
||||
m_source = requireNonNullParam(source, "source", "SynchronousInterrupt");
|
||||
m_handle = InterruptJNI.initializeInterrupts();
|
||||
InterruptJNI.requestInterrupts(
|
||||
m_handle, m_source.getPortHandleForRouting(), m_source.getAnalogTriggerTypeForRouting());
|
||||
InterruptJNI.setInterruptUpSourceEdge(m_handle, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the interrupt.
|
||||
*
|
||||
* <p>This does not close the associated digital source.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
InterruptJNI.cleanInterrupts(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for interrupt that returns the raw result value from the hardware.
|
||||
*
|
||||
* <p>Used by AsynchronousInterrupt. Users prefer to use waitForInterrupt.
|
||||
*
|
||||
* @param timeoutSeconds The timeout in seconds. 0 or less will return immediately.
|
||||
* @param ignorePrevious True to ignore if a previous interrupt has occured, and only wait for a
|
||||
* new trigger. False will consider if an interrupt has occured since the last time the
|
||||
* interrupt was read.
|
||||
* @return The raw hardware interrupt result
|
||||
*/
|
||||
int waitForInterruptRaw(double timeoutSeconds, boolean ignorePrevious) {
|
||||
return InterruptJNI.waitForInterrupt(m_handle, timeoutSeconds, ignorePrevious);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for an interrupt.
|
||||
*
|
||||
* @param timeoutSeconds The timeout in seconds. 0 or less will return immediately.
|
||||
* @param ignorePrevious True to ignore if a previous interrupt has occured, and only wait for a
|
||||
* new trigger. False will consider if an interrupt has occured since the last time the
|
||||
* interrupt was read.
|
||||
* @return Result of which edges were triggered, or if an timeout occured.
|
||||
*/
|
||||
public WaitResult waitForInterrupt(double timeoutSeconds, boolean ignorePrevious) {
|
||||
int result = InterruptJNI.waitForInterrupt(m_handle, timeoutSeconds, ignorePrevious);
|
||||
|
||||
// Rising edge result is the interrupt bit set in the byte 0xFF
|
||||
// Falling edge result is the interrupt bit set in the byte 0xFF00
|
||||
// Set any bit set to be true for that edge, and AND the 2 results
|
||||
// together to match the existing enum for all interrupts
|
||||
boolean rising = (result & 0xFF) != 0;
|
||||
boolean falling = (result & 0xFF00) != 0;
|
||||
return WaitResult.getValue(rising, falling);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for an interrupt, ingoring any previously occuring interrupts.
|
||||
*
|
||||
* @param timeoutSeconds The timeout in seconds. 0 or less will return immediately.
|
||||
* @return Result of which edges were triggered, or if an timeout occured.
|
||||
*/
|
||||
public WaitResult waitForInterrupt(double timeoutSeconds) {
|
||||
return waitForInterrupt(timeoutSeconds, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set which edges to trigger the interrupt on.
|
||||
*
|
||||
* @param risingEdge Trigger on rising edge
|
||||
* @param fallingEdge Trigger on falling edge
|
||||
*/
|
||||
public void setInterruptEdges(boolean risingEdge, boolean fallingEdge) {
|
||||
InterruptJNI.setInterruptUpSourceEdge(m_handle, risingEdge, fallingEdge);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp of the last rising edge.
|
||||
*
|
||||
* <p>This only works if rising edge was configured using setInterruptEdges.
|
||||
*
|
||||
* @return the timestamp in seconds relative to getFPGATime
|
||||
*/
|
||||
public double getRisingTimestamp() {
|
||||
return InterruptJNI.readInterruptRisingTimestamp(m_handle) * 1e-6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp of the last falling edge.
|
||||
*
|
||||
* <p>This only works if falling edge was configured using setInterruptEdges.
|
||||
*
|
||||
* @return the timestamp in seconds relative to getFPGATime
|
||||
*/
|
||||
public double getFallingTimestamp() {
|
||||
return InterruptJNI.readInterruptFallingTimestamp(m_handle) * 1e-6;
|
||||
}
|
||||
|
||||
/** Force triggering of any waiting interrupt, which will be seen as a timeout. */
|
||||
public void wakeupWaitingInterrupt() {
|
||||
InterruptJNI.releaseWaitingInterrupt(m_handle);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import static org.hamcrest.Matchers.both;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.lessThan;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import edu.wpi.first.wpilibj.test.AbstractComsSetup;
|
||||
@@ -20,34 +19,31 @@ import org.junit.Test;
|
||||
|
||||
/**
|
||||
* This class should not be run as a test explicitly. Instead it should be extended by tests that
|
||||
* use the InterruptableSensorBase.
|
||||
* use DigitalSource.
|
||||
*/
|
||||
public abstract class AbstractInterruptTest extends AbstractComsSetup {
|
||||
private InterruptableSensorBase m_interruptable = null;
|
||||
private DigitalSource m_source = null;
|
||||
|
||||
private InterruptableSensorBase getInterruptable() {
|
||||
if (m_interruptable == null) {
|
||||
m_interruptable = giveInterruptableSensorBase();
|
||||
private DigitalSource getSource() {
|
||||
if (m_source == null) {
|
||||
m_source = giveSource();
|
||||
}
|
||||
return m_interruptable;
|
||||
return m_source;
|
||||
}
|
||||
|
||||
@After
|
||||
public void interruptTeardown() {
|
||||
if (m_interruptable != null) {
|
||||
freeInterruptableSensorBase();
|
||||
m_interruptable = null;
|
||||
if (m_source != null) {
|
||||
freeSource();
|
||||
m_source = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Give the interruptable sensor base that interrupts can be attached to. */
|
||||
abstract InterruptableSensorBase giveInterruptableSensorBase();
|
||||
/** Give the sensor source that interrupts can be attached to. */
|
||||
abstract DigitalSource giveSource();
|
||||
|
||||
/**
|
||||
* Cleans up the interruptable sensor base. This is only called if {@link
|
||||
* #giveInterruptableSensorBase()} is called.
|
||||
*/
|
||||
abstract void freeInterruptableSensorBase();
|
||||
/** Cleans up the sensor source. This is only called if {@link #giveSource()} is called. */
|
||||
abstract void freeSource();
|
||||
|
||||
/** Perform whatever action is required to set the interrupt high. */
|
||||
abstract void setInterruptHigh();
|
||||
@@ -55,116 +51,78 @@ public abstract class AbstractInterruptTest extends AbstractComsSetup {
|
||||
/** Perform whatever action is required to set the interrupt low. */
|
||||
abstract void setInterruptLow();
|
||||
|
||||
private class InterruptCounter {
|
||||
private final AtomicInteger m_count = new AtomicInteger();
|
||||
|
||||
void increment() {
|
||||
m_count.addAndGet(1);
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
return m_count.get();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestInterruptHandlerFunction extends InterruptHandlerFunction<InterruptCounter> {
|
||||
protected final AtomicBoolean m_exceptionThrown = new AtomicBoolean(false);
|
||||
/** Stores the time that the interrupt fires. */
|
||||
final AtomicLong m_interruptFireTime = new AtomicLong();
|
||||
/** Stores if the interrupt has completed at least once. */
|
||||
final AtomicBoolean m_interruptComplete = new AtomicBoolean(false);
|
||||
|
||||
protected Exception m_ex;
|
||||
final InterruptCounter m_counter;
|
||||
|
||||
TestInterruptHandlerFunction(InterruptCounter counter) {
|
||||
m_counter = counter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interruptFired(int interruptAssertedMask, InterruptCounter param) {
|
||||
m_interruptFireTime.set(RobotController.getFPGATime());
|
||||
m_counter.increment();
|
||||
try {
|
||||
// This won't cause the test to fail
|
||||
assertSame(m_counter, param);
|
||||
} catch (Exception ex) {
|
||||
// So we must throw the exception within the test
|
||||
m_exceptionThrown.set(true);
|
||||
m_ex = ex;
|
||||
}
|
||||
m_interruptComplete.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterruptCounter overridableParameter() {
|
||||
return m_counter;
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 1000)
|
||||
public void testSingleInterruptsTriggering() throws Exception {
|
||||
// Given
|
||||
final InterruptCounter counter = new InterruptCounter();
|
||||
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter);
|
||||
// final InterruptCounter counter = new InterruptCounter();
|
||||
// TestInterruptHandlerFunction function = new
|
||||
// TestInterruptHandlerFunction(counter);
|
||||
|
||||
// When
|
||||
getInterruptable().requestInterrupts(function);
|
||||
getInterruptable().enableInterrupts();
|
||||
AtomicBoolean hasFired = new AtomicBoolean(false);
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
AtomicLong interruptFireTime = new AtomicLong();
|
||||
|
||||
setInterruptLow();
|
||||
Timer.delay(0.01);
|
||||
// Note: Utility.getFPGATime() is used because double values can turn over
|
||||
// after the robot has been running for a long time
|
||||
final long interruptTriggerTime = RobotController.getFPGATime();
|
||||
setInterruptHigh();
|
||||
try (AsynchronousInterrupt interrupt =
|
||||
new AsynchronousInterrupt(
|
||||
getSource(),
|
||||
(a, b) -> {
|
||||
interruptFireTime.set(RobotController.getFPGATime());
|
||||
hasFired.set(true);
|
||||
counter.incrementAndGet();
|
||||
})) {
|
||||
interrupt.enable();
|
||||
setInterruptLow();
|
||||
Timer.delay(0.01);
|
||||
final long interruptTriggerTime = RobotController.getFPGATime();
|
||||
setInterruptHigh();
|
||||
while (!hasFired.get()) {
|
||||
Timer.delay(0.005);
|
||||
}
|
||||
|
||||
// Delay until the interrupt is complete
|
||||
while (!function.m_interruptComplete.get()) {
|
||||
Timer.delay(0.005);
|
||||
assertEquals("The interrupt did not fire the expected number of times", 1, counter.get());
|
||||
|
||||
final long range = 10000; // in microseconds
|
||||
assertThat(
|
||||
"The interrupt did not fire within the expected time period (values in milliseconds)",
|
||||
interruptFireTime.get(),
|
||||
both(greaterThan(interruptTriggerTime - range))
|
||||
.and(lessThan(interruptTriggerTime + range)));
|
||||
assertThat(
|
||||
"The readRisingTimestamp() did not return the correct value (values in seconds)",
|
||||
interrupt.getRisingTimestamp(),
|
||||
both(greaterThan((interruptTriggerTime - range) / 1e6))
|
||||
.and(lessThan((interruptTriggerTime + range) / 1e6)));
|
||||
}
|
||||
|
||||
// Then
|
||||
assertEquals("The interrupt did not fire the expected number of times", 1, counter.getCount());
|
||||
// If the test within the interrupt failed
|
||||
if (function.m_exceptionThrown.get()) {
|
||||
throw function.m_ex;
|
||||
}
|
||||
final long range = 10000; // in microseconds
|
||||
assertThat(
|
||||
"The interrupt did not fire within the expected time period (values in milliseconds)",
|
||||
function.m_interruptFireTime.get(),
|
||||
both(greaterThan(interruptTriggerTime - range))
|
||||
.and(lessThan(interruptTriggerTime + range)));
|
||||
assertThat(
|
||||
"The readRisingTimestamp() did not return the correct value (values in seconds)",
|
||||
getInterruptable().readRisingTimestamp(),
|
||||
both(greaterThan((interruptTriggerTime - range) / 1e6))
|
||||
.and(lessThan((interruptTriggerTime + range) / 1e6)));
|
||||
}
|
||||
|
||||
@Test(timeout = 2000)
|
||||
public void testMultipleInterruptsTriggering() {
|
||||
// Given
|
||||
final InterruptCounter counter = new InterruptCounter();
|
||||
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter);
|
||||
|
||||
// When
|
||||
getInterruptable().requestInterrupts(function);
|
||||
getInterruptable().enableInterrupts();
|
||||
AtomicBoolean hasFired = new AtomicBoolean(false);
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
|
||||
final int fireCount = 50;
|
||||
for (int i = 0; i < fireCount; i++) {
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
// Wait for the interrupt to complete before moving on
|
||||
while (!function.m_interruptComplete.getAndSet(false)) {
|
||||
Timer.delay(0.005);
|
||||
try (AsynchronousInterrupt interrupt =
|
||||
new AsynchronousInterrupt(
|
||||
getSource(),
|
||||
(a, b) -> {
|
||||
hasFired.set(true);
|
||||
counter.incrementAndGet();
|
||||
})) {
|
||||
interrupt.enable();
|
||||
|
||||
final int fireCount = 50;
|
||||
for (int i = 0; i < fireCount; i++) {
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
// Wait for the interrupt to complete before moving on
|
||||
while (!hasFired.getAndSet(false)) {
|
||||
Timer.delay(0.005);
|
||||
}
|
||||
}
|
||||
// Then
|
||||
assertEquals(
|
||||
"The interrupt did not fire the expected number of times", fireCount, counter.get());
|
||||
}
|
||||
// Then
|
||||
assertEquals(
|
||||
"The interrupt did not fire the expected number of times", fireCount, counter.getCount());
|
||||
}
|
||||
|
||||
/** The timeout length for this test in seconds. */
|
||||
@@ -172,131 +130,128 @@ public abstract class AbstractInterruptTest extends AbstractComsSetup {
|
||||
|
||||
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
||||
public void testSynchronousInterruptsTriggering() {
|
||||
// Given
|
||||
getInterruptable().requestInterrupts();
|
||||
|
||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||
final Runnable runnable =
|
||||
() -> {
|
||||
Timer.delay(synchronousDelay);
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
};
|
||||
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
|
||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||
final Runnable runnable =
|
||||
() -> {
|
||||
Timer.delay(synchronousDelay);
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
};
|
||||
|
||||
// When
|
||||
// When
|
||||
|
||||
// Note: the long time value is used because doubles can flip if the robot
|
||||
// is left running for long enough
|
||||
final long startTimeStamp = RobotController.getFPGATime();
|
||||
new Thread(runnable).start();
|
||||
// Delay for twice as long as the timeout so the test should fail first
|
||||
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
|
||||
final long stopTimeStamp = RobotController.getFPGATime();
|
||||
// Note: the long time value is used because doubles can flip if the robot
|
||||
// is left running for long enough
|
||||
final long startTimeStamp = RobotController.getFPGATime();
|
||||
new Thread(runnable).start();
|
||||
// Delay for twice as long as the timeout so the test should fail first
|
||||
interrupt.waitForInterrupt(synchronousTimeout * 2);
|
||||
final long stopTimeStamp = RobotController.getFPGATime();
|
||||
|
||||
// Then
|
||||
// The test will not have timed out and:
|
||||
final double interruptRunTime = (stopTimeStamp - startTimeStamp) * 1e-6;
|
||||
assertEquals(
|
||||
"The interrupt did not run for the expected amount of time (units in seconds)",
|
||||
synchronousDelay,
|
||||
interruptRunTime,
|
||||
0.1);
|
||||
// Then
|
||||
// The test will not have timed out and:
|
||||
final double interruptRunTime = (stopTimeStamp - startTimeStamp) * 1e-6;
|
||||
assertEquals(
|
||||
"The interrupt did not run for the expected amount of time (units in seconds)",
|
||||
synchronousDelay,
|
||||
interruptRunTime,
|
||||
0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
||||
public void testSynchronousInterruptsWaitResultTimeout() {
|
||||
// Given
|
||||
getInterruptable().requestInterrupts();
|
||||
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
|
||||
SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout / 2);
|
||||
|
||||
// Don't fire interrupt. Expect it to timeout.
|
||||
InterruptableSensorBase.WaitResult result =
|
||||
getInterruptable().waitForInterrupt(synchronousTimeout / 2);
|
||||
|
||||
assertEquals(
|
||||
"The interrupt did not time out correctly.",
|
||||
result,
|
||||
InterruptableSensorBase.WaitResult.kTimeout);
|
||||
assertEquals(
|
||||
"The interrupt did not time out correctly.",
|
||||
result,
|
||||
SynchronousInterrupt.WaitResult.kTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
||||
public void testSynchronousInterruptsWaitResultRisingEdge() {
|
||||
// Given
|
||||
getInterruptable().requestInterrupts();
|
||||
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
|
||||
|
||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||
final Runnable runnable =
|
||||
() -> {
|
||||
Timer.delay(synchronousDelay);
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
};
|
||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||
final Runnable runnable =
|
||||
() -> {
|
||||
Timer.delay(synchronousDelay);
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
};
|
||||
|
||||
new Thread(runnable).start();
|
||||
// Delay for twice as long as the timeout so the test should fail first
|
||||
InterruptableSensorBase.WaitResult result =
|
||||
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
|
||||
new Thread(runnable).start();
|
||||
// Delay for twice as long as the timeout so the test should fail first
|
||||
SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout * 2);
|
||||
|
||||
assertEquals(
|
||||
"The interrupt did not fire on the rising edge.",
|
||||
result,
|
||||
InterruptableSensorBase.WaitResult.kRisingEdge);
|
||||
assertEquals(
|
||||
"The interrupt did not fire on the rising edge.",
|
||||
result,
|
||||
SynchronousInterrupt.WaitResult.kRisingEdge);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
||||
public void testSynchronousInterruptsWaitResultFallingEdge() {
|
||||
// Given
|
||||
getInterruptable().requestInterrupts();
|
||||
getInterruptable().setUpSourceEdge(false, true);
|
||||
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
|
||||
// Given
|
||||
interrupt.setInterruptEdges(false, true);
|
||||
|
||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||
final Runnable runnable =
|
||||
() -> {
|
||||
Timer.delay(synchronousDelay);
|
||||
setInterruptHigh();
|
||||
setInterruptLow();
|
||||
};
|
||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||
final Runnable runnable =
|
||||
() -> {
|
||||
Timer.delay(synchronousDelay);
|
||||
setInterruptHigh();
|
||||
setInterruptLow();
|
||||
};
|
||||
|
||||
new Thread(runnable).start();
|
||||
// Delay for twice as long as the timeout so the test should fail first
|
||||
InterruptableSensorBase.WaitResult result =
|
||||
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
|
||||
new Thread(runnable).start();
|
||||
// Delay for twice as long as the timeout so the test should fail first
|
||||
SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout * 2);
|
||||
|
||||
assertEquals(
|
||||
"The interrupt did not fire on the falling edge.",
|
||||
result,
|
||||
InterruptableSensorBase.WaitResult.kFallingEdge);
|
||||
assertEquals(
|
||||
"The interrupt did not fire on the falling edge.",
|
||||
result,
|
||||
SynchronousInterrupt.WaitResult.kFallingEdge);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 4000)
|
||||
public void testDisableStopsInterruptFiring() {
|
||||
final InterruptCounter counter = new InterruptCounter();
|
||||
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter);
|
||||
|
||||
// When
|
||||
getInterruptable().requestInterrupts(function);
|
||||
getInterruptable().enableInterrupts();
|
||||
|
||||
final int fireCount = 50;
|
||||
for (int i = 0; i < fireCount; i++) {
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
// Wait for the interrupt to complete before moving on
|
||||
while (!function.m_interruptComplete.getAndSet(false)) {
|
||||
AtomicBoolean interruptComplete = new AtomicBoolean(false);
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
try (AsynchronousInterrupt interrupt =
|
||||
new AsynchronousInterrupt(
|
||||
getSource(),
|
||||
(a, b) -> {
|
||||
interruptComplete.set(true);
|
||||
counter.incrementAndGet();
|
||||
})) {
|
||||
interrupt.enable();
|
||||
final int fireCount = 50;
|
||||
for (int i = 0; i < fireCount; i++) {
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
// Wait for the interrupt to complete before moving on
|
||||
while (!interruptComplete.getAndSet(false)) {
|
||||
Timer.delay(0.005);
|
||||
}
|
||||
}
|
||||
interrupt.disable();
|
||||
for (int i = 0; i < fireCount; i++) {
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
// Just wait because the interrupt should not fire
|
||||
Timer.delay(0.005);
|
||||
}
|
||||
}
|
||||
getInterruptable().disableInterrupts();
|
||||
// TestBench.out().println("Finished disabling the robot");
|
||||
|
||||
for (int i = 0; i < fireCount; i++) {
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
// Just wait because the interrupt should not fire
|
||||
Timer.delay(0.005);
|
||||
assertEquals(
|
||||
"The interrupt did not fire the expected number of times", fireCount, counter.get());
|
||||
}
|
||||
|
||||
// Then
|
||||
assertEquals(
|
||||
"The interrupt did not fire the expected number of times", fireCount, counter.getCount());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,10 +139,10 @@ public class AnalogCrossConnectTest extends AbstractInterruptTest {
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#giveInterruptableSensorBase()
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#giveSource()
|
||||
*/
|
||||
@Override
|
||||
InterruptableSensorBase giveInterruptableSensorBase() {
|
||||
DigitalSource giveSource() {
|
||||
m_interruptTrigger = new AnalogTrigger(analogIO.getInput());
|
||||
m_interruptTrigger.setLimitsVoltage(2.0, 3.0);
|
||||
m_interruptTriggerOutput =
|
||||
@@ -154,11 +154,10 @@ public class AnalogCrossConnectTest extends AbstractInterruptTest {
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#freeInterruptableSensorBase()
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#freeSource()
|
||||
*/
|
||||
@Override
|
||||
void freeInterruptableSensorBase() {
|
||||
m_interruptTriggerOutput.cancelInterrupts();
|
||||
void freeSource() {
|
||||
m_interruptTriggerOutput.close();
|
||||
m_interruptTriggerOutput = null;
|
||||
m_interruptTrigger.close();
|
||||
|
||||
@@ -101,10 +101,11 @@ public class DIOCrossConnectTest extends AbstractInterruptTest {
|
||||
dio.getOutput().enablePWM(0.0);
|
||||
Timer.delay(0.5);
|
||||
dio.getOutput().updateDutyCycle(0.5);
|
||||
dio.getInput().requestInterrupts();
|
||||
dio.getInput().setUpSourceEdge(false, true);
|
||||
// TODO: Add return value from WaitForInterrupt
|
||||
dio.getInput().waitForInterrupt(3.0, true);
|
||||
try (var interruptHandler = new SynchronousInterrupt(dio.getInput())) {
|
||||
interruptHandler.setInterruptEdges(false, true);
|
||||
// TODO: Add return value from WaitForInterrupt
|
||||
interruptHandler.waitForInterrupt(3.0, true);
|
||||
}
|
||||
Timer.delay(0.5);
|
||||
final boolean firstCycle = dio.getInput().get();
|
||||
Timer.delay(0.5);
|
||||
@@ -140,10 +141,10 @@ public class DIOCrossConnectTest extends AbstractInterruptTest {
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#giveInterruptableSensorBase()
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#giveSource()
|
||||
*/
|
||||
@Override
|
||||
InterruptableSensorBase giveInterruptableSensorBase() {
|
||||
DigitalSource giveSource() {
|
||||
return dio.getInput();
|
||||
}
|
||||
|
||||
@@ -151,10 +152,10 @@ public class DIOCrossConnectTest extends AbstractInterruptTest {
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#freeInterruptableSensorBase()
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#freeSource()
|
||||
*/
|
||||
@Override
|
||||
void freeInterruptableSensorBase() {
|
||||
void freeSource() {
|
||||
// Handled in the fixture
|
||||
}
|
||||
|
||||
|
||||
@@ -61,11 +61,6 @@ public class DIOCrossConnectFixture implements ITestFixture {
|
||||
|
||||
@Override
|
||||
public boolean reset() {
|
||||
try {
|
||||
m_input.cancelInterrupts();
|
||||
} catch (IllegalStateException ex) {
|
||||
// This will happen if the interrupt has not been allocated for this test.
|
||||
}
|
||||
m_output.set(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user