mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-05 03:21:42 +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 class InterruptJNI extends JNIWrapper {
|
||||||
public static final int HalInvalidHandle = 0;
|
public static final int HalInvalidHandle = 0;
|
||||||
|
|
||||||
public interface InterruptJNIHandlerFunction {
|
public static native int initializeInterrupts();
|
||||||
void apply(int interruptAssertedMask, Object param);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static native int initializeInterrupts(boolean watcher);
|
|
||||||
|
|
||||||
public static native void cleanInterrupts(int interruptHandle);
|
public static native void cleanInterrupts(int interruptHandle);
|
||||||
|
|
||||||
public static native int waitForInterrupt(
|
public static native int waitForInterrupt(
|
||||||
int interruptHandle, double timeout, boolean ignorePrevious);
|
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 readInterruptRisingTimestamp(int interruptHandle);
|
||||||
|
|
||||||
public static native long readInterruptFallingTimestamp(int interruptHandle);
|
public static native long readInterruptFallingTimestamp(int interruptHandle);
|
||||||
@@ -29,9 +21,6 @@ public class InterruptJNI extends JNIWrapper {
|
|||||||
public static native void requestInterrupts(
|
public static native void requestInterrupts(
|
||||||
int interruptHandle, int digitalSourceHandle, int analogTriggerType);
|
int interruptHandle, int digitalSourceHandle, int analogTriggerType);
|
||||||
|
|
||||||
public static native void attachInterruptHandler(
|
|
||||||
int interruptHandle, InterruptJNIHandlerFunction handler, Object param);
|
|
||||||
|
|
||||||
public static native void setInterruptUpSourceEdge(
|
public static native void setInterruptUpSourceEdge(
|
||||||
int interruptHandle, boolean risingEdge, boolean fallingEdge);
|
int interruptHandle, boolean risingEdge, boolean fallingEdge);
|
||||||
|
|
||||||
|
|||||||
@@ -21,65 +21,14 @@
|
|||||||
using namespace hal;
|
using namespace hal;
|
||||||
|
|
||||||
namespace {
|
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 {
|
struct Interrupt {
|
||||||
std::unique_ptr<tInterrupt> anInterrupt;
|
std::unique_ptr<tInterrupt> anInterrupt;
|
||||||
std::unique_ptr<tInterruptManager> manager;
|
std::unique_ptr<tInterruptManager> manager;
|
||||||
std::unique_ptr<InterruptThreadOwner> threadOwner = nullptr;
|
|
||||||
void* param = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
static void threadedInterruptHandler(uint32_t mask, void* param) {
|
|
||||||
static_cast<InterruptThreadOwner*>(param)->Notify(mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
static LimitedHandleResource<HAL_InterruptHandle, Interrupt, kNumInterrupts,
|
static LimitedHandleResource<HAL_InterruptHandle, Interrupt, kNumInterrupts,
|
||||||
HAL_HandleEnum::Interrupt>* interruptHandles;
|
HAL_HandleEnum::Interrupt>* interruptHandles;
|
||||||
|
|
||||||
@@ -94,8 +43,7 @@ void InitializeInterrupts() {
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
|
HAL_InterruptHandle HAL_InitializeInterrupts(int32_t* status) {
|
||||||
int32_t* status) {
|
|
||||||
hal::init::CheckInit();
|
hal::init::CheckInit();
|
||||||
HAL_InterruptHandle handle = interruptHandles->Allocate();
|
HAL_InterruptHandle handle = interruptHandles->Allocate();
|
||||||
if (handle == HAL_kInvalidHandle) {
|
if (handle == HAL_kInvalidHandle) {
|
||||||
@@ -108,24 +56,16 @@ HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
|
|||||||
anInterrupt->anInterrupt.reset(tInterrupt::create(interruptIndex, status));
|
anInterrupt->anInterrupt.reset(tInterrupt::create(interruptIndex, status));
|
||||||
anInterrupt->anInterrupt->writeConfig_WaitForAck(false, status);
|
anInterrupt->anInterrupt->writeConfig_WaitForAck(false, status);
|
||||||
anInterrupt->manager = std::make_unique<tInterruptManager>(
|
anInterrupt->manager = std::make_unique<tInterruptManager>(
|
||||||
(1u << interruptIndex) | (1u << (interruptIndex + 8u)), watcher, status);
|
(1u << interruptIndex) | (1u << (interruptIndex + 8u)), true, status);
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle,
|
void HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle) {
|
||||||
int32_t* status) {
|
|
||||||
auto anInterrupt = interruptHandles->Get(interruptHandle);
|
auto anInterrupt = interruptHandles->Get(interruptHandle);
|
||||||
interruptHandles->Free(interruptHandle);
|
interruptHandles->Free(interruptHandle);
|
||||||
if (anInterrupt == nullptr) {
|
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,
|
int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
|
||||||
@@ -150,31 +90,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
|
|||||||
return result;
|
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,
|
int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||||
int32_t* status) {
|
int32_t* status) {
|
||||||
auto anInterrupt = interruptHandles->Get(interruptHandle);
|
auto anInterrupt = interruptHandles->Get(interruptHandle);
|
||||||
@@ -223,40 +138,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,
|
|||||||
anInterrupt->anInterrupt->writeConfig_Source_Module(routingModule, status);
|
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,
|
void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle,
|
||||||
HAL_Bool risingEdge, HAL_Bool fallingEdge,
|
HAL_Bool risingEdge, HAL_Bool fallingEdge,
|
||||||
int32_t* status) {
|
int32_t* status) {
|
||||||
|
|||||||
@@ -17,126 +17,19 @@
|
|||||||
|
|
||||||
using namespace hal;
|
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" {
|
extern "C" {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Class: edu_wpi_first_hal_InterruptJNI
|
* Class: edu_wpi_first_hal_InterruptJNI
|
||||||
* Method: initializeInterrupts
|
* Method: initializeInterrupts
|
||||||
* Signature: (Z)I
|
* Signature: ()I
|
||||||
*/
|
*/
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
Java_edu_wpi_first_hal_InterruptJNI_initializeInterrupts
|
Java_edu_wpi_first_hal_InterruptJNI_initializeInterrupts
|
||||||
(JNIEnv* env, jclass, jboolean watcher)
|
(JNIEnv* env, jclass)
|
||||||
{
|
{
|
||||||
int32_t status = 0;
|
int32_t status = 0;
|
||||||
HAL_InterruptHandle interrupt = HAL_InitializeInterrupts(watcher, &status);
|
HAL_InterruptHandle interrupt = HAL_InitializeInterrupts(&status);
|
||||||
|
|
||||||
CheckStatusForceThrow(env, status);
|
CheckStatusForceThrow(env, status);
|
||||||
return (jint)interrupt;
|
return (jint)interrupt;
|
||||||
@@ -151,14 +44,7 @@ JNIEXPORT void JNICALL
|
|||||||
Java_edu_wpi_first_hal_InterruptJNI_cleanInterrupts
|
Java_edu_wpi_first_hal_InterruptJNI_cleanInterrupts
|
||||||
(JNIEnv* env, jclass, jint interruptHandle)
|
(JNIEnv* env, jclass, jint interruptHandle)
|
||||||
{
|
{
|
||||||
int32_t status = 0;
|
HAL_CleanInterrupts((HAL_InterruptHandle)interruptHandle);
|
||||||
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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -179,36 +65,6 @@ Java_edu_wpi_first_hal_InterruptJNI_waitForInterrupt
|
|||||||
return result;
|
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
|
* Class: edu_wpi_first_hal_InterruptJNI
|
||||||
* Method: readInterruptRisingTimestamp
|
* Method: readInterruptRisingTimestamp
|
||||||
@@ -261,37 +117,6 @@ Java_edu_wpi_first_hal_InterruptJNI_requestInterrupts
|
|||||||
CheckStatus(env, status);
|
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
|
* Class: edu_wpi_first_hal_InterruptJNI
|
||||||
* Method: setInterruptUpSourceEdge
|
* Method: setInterruptUpSourceEdge
|
||||||
|
|||||||
@@ -19,25 +19,20 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef void (*HAL_InterruptHandlerFunction)(uint32_t interruptAssertedMask,
|
|
||||||
void* param);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes an interrupt.
|
* Initializes an interrupt.
|
||||||
*
|
*
|
||||||
* @param watcher true for synchronous interrupts, false for asynchronous
|
* @param watcher true for synchronous interrupts, false for asynchronous
|
||||||
* @return the created interrupt handle
|
* @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.
|
* Frees an interrupt.
|
||||||
*
|
*
|
||||||
* @param interruptHandle the interrupt handle
|
* @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.
|
* 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,
|
double timeout, HAL_Bool ignorePrevious,
|
||||||
int32_t* status);
|
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.
|
* Returns the timestamp for the rising interrupt that occurred most recently.
|
||||||
*
|
*
|
||||||
@@ -110,34 +86,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,
|
|||||||
HAL_AnalogTriggerType analogTriggerType,
|
HAL_AnalogTriggerType analogTriggerType,
|
||||||
int32_t* status);
|
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.
|
* Sets the edges to trigger the interrupt on.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -42,16 +42,12 @@ struct Interrupt {
|
|||||||
HAL_Handle portHandle;
|
HAL_Handle portHandle;
|
||||||
uint8_t index;
|
uint8_t index;
|
||||||
HAL_AnalogTriggerType trigType;
|
HAL_AnalogTriggerType trigType;
|
||||||
bool watcher;
|
|
||||||
int64_t risingTimestamp;
|
int64_t risingTimestamp;
|
||||||
int64_t fallingTimestamp;
|
int64_t fallingTimestamp;
|
||||||
bool previousState;
|
bool previousState;
|
||||||
bool fireOnUp;
|
bool fireOnUp;
|
||||||
bool fireOnDown;
|
bool fireOnDown;
|
||||||
int32_t callbackId;
|
int32_t callbackId;
|
||||||
|
|
||||||
void* callbackParam;
|
|
||||||
HAL_InterruptHandlerFunction callbackFunction;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SynchronousWaitData {
|
struct SynchronousWaitData {
|
||||||
@@ -83,8 +79,7 @@ void InitializeInterrupts() {
|
|||||||
} // namespace hal::init
|
} // namespace hal::init
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
|
HAL_InterruptHandle HAL_InitializeInterrupts(int32_t* status) {
|
||||||
int32_t* status) {
|
|
||||||
hal::init::CheckInit();
|
hal::init::CheckInit();
|
||||||
HAL_InterruptHandle handle = interruptHandles->Allocate();
|
HAL_InterruptHandle handle = interruptHandles->Allocate();
|
||||||
if (handle == HAL_kInvalidHandle) {
|
if (handle == HAL_kInvalidHandle) {
|
||||||
@@ -100,19 +95,10 @@ HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
|
|||||||
anInterrupt->index = getHandleIndex(handle);
|
anInterrupt->index = getHandleIndex(handle);
|
||||||
anInterrupt->callbackId = -1;
|
anInterrupt->callbackId = -1;
|
||||||
|
|
||||||
anInterrupt->watcher = watcher;
|
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle,
|
void HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle) {
|
||||||
int32_t* status) {
|
|
||||||
HAL_DisableInterrupts(interruptHandle, status);
|
|
||||||
auto anInterrupt = interruptHandles->Get(interruptHandle);
|
|
||||||
interruptHandles->Free(interruptHandle);
|
interruptHandles->Free(interruptHandle);
|
||||||
if (anInterrupt == nullptr) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return anInterrupt->callbackParam;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ProcessInterruptDigitalSynchronous(const char* name, void* param,
|
static void ProcessInterruptDigitalSynchronous(const char* name, void* param,
|
||||||
@@ -352,12 +338,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
|
|||||||
return WaitResult::Timeout;
|
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) {
|
if (interrupt->isAnalog) {
|
||||||
return WaitForInterruptAnalog(interruptHandle, interrupt.get(), timeout,
|
return WaitForInterruptAnalog(interruptHandle, interrupt.get(), timeout,
|
||||||
ignorePrevious);
|
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,
|
int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
|
||||||
int32_t* status) {
|
int32_t* status) {
|
||||||
auto interrupt = interruptHandles->Get(interruptHandle);
|
auto interrupt = interruptHandles->Get(interruptHandle);
|
||||||
@@ -602,24 +392,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,
|
|||||||
interrupt->trigType = analogTriggerType;
|
interrupt->trigType = analogTriggerType;
|
||||||
interrupt->portHandle = digitalSourceHandle;
|
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,
|
void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle,
|
||||||
HAL_Bool risingEdge, HAL_Bool fallingEdge,
|
HAL_Bool risingEdge, HAL_Bool fallingEdge,
|
||||||
@@ -636,6 +408,19 @@ void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle,
|
|||||||
|
|
||||||
void HAL_ReleaseWaitingInterrupt(HAL_InterruptHandle interruptHandle,
|
void HAL_ReleaseWaitingInterrupt(HAL_InterruptHandle interruptHandle,
|
||||||
int32_t* status) {
|
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"
|
} // 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
|
* 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.
|
* currently running an uninterruptible command or child.
|
||||||
*
|
*
|
||||||
* @return whether or not this {@link CommandGroup} is interruptible.
|
* @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
|
* 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
|
* 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
|
* @param command the {@link Command} to add
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include <hal/AnalogTrigger.h>
|
||||||
#include <hal/FRCUsageReporting.h>
|
#include <hal/FRCUsageReporting.h>
|
||||||
#include <wpi/NullDeleter.h>
|
#include <wpi/NullDeleter.h>
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
|
|
||||||
#include "frc/AnalogTriggerOutput.h"
|
#include "frc/AnalogTriggerOutput.h"
|
||||||
|
|
||||||
|
#include <hal/AnalogTrigger.h>
|
||||||
#include <hal/FRCUsageReporting.h>
|
#include <hal/FRCUsageReporting.h>
|
||||||
|
|
||||||
#include "frc/AnalogTrigger.h"
|
#include "frc/AnalogTrigger.h"
|
||||||
|
#include "frc/AnalogTriggerType.h"
|
||||||
#include "frc/Errors.h"
|
#include "frc/Errors.h"
|
||||||
|
|
||||||
using namespace frc;
|
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 <hal/Types.h>
|
||||||
|
|
||||||
#include "frc/InterruptableSensorBase.h"
|
#include "frc/AnalogTriggerType.h"
|
||||||
|
|
||||||
namespace frc {
|
namespace frc {
|
||||||
|
|
||||||
@@ -19,14 +19,14 @@ namespace frc {
|
|||||||
* constructed and freed when finished for the source. The source can either be
|
* constructed and freed when finished for the source. The source can either be
|
||||||
* a digital input or analog trigger but not both.
|
* a digital input or analog trigger but not both.
|
||||||
*/
|
*/
|
||||||
class DigitalSource : public InterruptableSensorBase {
|
class DigitalSource {
|
||||||
public:
|
public:
|
||||||
DigitalSource() = default;
|
DigitalSource() = default;
|
||||||
DigitalSource(DigitalSource&&) = default;
|
DigitalSource(DigitalSource&&) = default;
|
||||||
DigitalSource& operator=(DigitalSource&&) = default;
|
DigitalSource& operator=(DigitalSource&&) = default;
|
||||||
|
|
||||||
HAL_Handle GetPortHandleForRouting() const override = 0;
|
virtual HAL_Handle GetPortHandleForRouting() const = 0;
|
||||||
AnalogTriggerType GetAnalogTriggerTypeForRouting() const override = 0;
|
virtual AnalogTriggerType GetAnalogTriggerTypeForRouting() const = 0;
|
||||||
virtual bool IsAnalogTrigger() const = 0;
|
virtual bool IsAnalogTrigger() const = 0;
|
||||||
virtual int GetChannel() 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/AnalogInput.h"
|
||||||
#include "frc/AnalogOutput.h"
|
#include "frc/AnalogOutput.h"
|
||||||
#include "frc/AnalogTrigger.h"
|
#include "frc/AnalogTrigger.h"
|
||||||
|
#include "frc/AsynchronousInterrupt.h"
|
||||||
#include "frc/Counter.h"
|
#include "frc/Counter.h"
|
||||||
#include "frc/Timer.h"
|
#include "frc/Timer.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
@@ -91,10 +92,6 @@ TEST(AnalogLoopTest, AnalogTriggerCounterWorks) {
|
|||||||
<< "Analog trigger counter did not count 50 ticks";
|
<< "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) {
|
TEST(AnalogLoopTest, AsynchronusInterruptWorks) {
|
||||||
frc::AnalogInput input{TestBench::kFakeAnalogOutputChannel};
|
frc::AnalogInput input{TestBench::kFakeAnalogOutputChannel};
|
||||||
frc::AnalogOutput output{TestBench::kAnalogOutputChannel};
|
frc::AnalogOutput output{TestBench::kAnalogOutputChannel};
|
||||||
@@ -106,16 +103,19 @@ TEST(AnalogLoopTest, AsynchronusInterruptWorks) {
|
|||||||
// Given an interrupt handler that sets an int32_t to 12345
|
// Given an interrupt handler that sets an int32_t to 12345
|
||||||
std::shared_ptr<frc::AnalogTriggerOutput> triggerOutput =
|
std::shared_ptr<frc::AnalogTriggerOutput> triggerOutput =
|
||||||
trigger.CreateOutput(frc::AnalogTriggerType::kState);
|
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
|
// If the analog output moves from below to above the window
|
||||||
output.SetVoltage(0.0);
|
output.SetVoltage(0.0);
|
||||||
frc::Wait(kDelayTime);
|
frc::Wait(kDelayTime);
|
||||||
output.SetVoltage(5.0);
|
output.SetVoltage(5.0);
|
||||||
triggerOutput->CancelInterrupts();
|
|
||||||
|
|
||||||
// Then the int32_t should be 12345
|
// Then the int32_t should be 12345
|
||||||
frc::Wait(kDelayTime);
|
frc::Wait(kDelayTime);
|
||||||
|
interrupt.Disable();
|
||||||
|
|
||||||
EXPECT_EQ(12345, param) << "The interrupt did not run.";
|
EXPECT_EQ(12345, param) << "The interrupt did not run.";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,9 @@
|
|||||||
#include <units/time.h>
|
#include <units/time.h>
|
||||||
|
|
||||||
#include "TestBench.h"
|
#include "TestBench.h"
|
||||||
|
#include "frc/AsynchronousInterrupt.h"
|
||||||
#include "frc/Counter.h"
|
#include "frc/Counter.h"
|
||||||
#include "frc/InterruptableSensorBase.h"
|
#include "frc/SynchronousInterrupt.h"
|
||||||
#include "frc/Timer.h"
|
#include "frc/Timer.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
@@ -71,10 +72,10 @@ TEST_F(DIOLoopTest, DIOPWM) {
|
|||||||
m_output.EnablePWM(0.0);
|
m_output.EnablePWM(0.0);
|
||||||
frc::Wait(0.5_s);
|
frc::Wait(0.5_s);
|
||||||
m_output.UpdateDutyCycle(0.5);
|
m_output.UpdateDutyCycle(0.5);
|
||||||
m_input.RequestInterrupts();
|
frc::SynchronousInterrupt interrupt{m_output};
|
||||||
m_input.SetUpSourceEdge(false, true);
|
interrupt.SetInterruptEdges(false, true);
|
||||||
frc::InterruptableSensorBase::WaitResult result =
|
frc::SynchronousInterrupt::WaitResult result =
|
||||||
m_input.WaitForInterrupt(3_s, true);
|
interrupt.WaitForInterrupt(3_s, true);
|
||||||
|
|
||||||
frc::Wait(0.5_s);
|
frc::Wait(0.5_s);
|
||||||
bool firstCycle = m_input.Get();
|
bool firstCycle = m_input.Get();
|
||||||
@@ -96,7 +97,7 @@ TEST_F(DIOLoopTest, DIOPWM) {
|
|||||||
frc::Wait(0.5_s);
|
frc::Wait(0.5_s);
|
||||||
bool secondAfterStop = m_input.Get();
|
bool secondAfterStop = m_input.Get();
|
||||||
|
|
||||||
EXPECT_EQ(frc::InterruptableSensorBase::WaitResult::kFallingEdge, result)
|
EXPECT_EQ(frc::SynchronousInterrupt::WaitResult::kFallingEdge, result)
|
||||||
<< "WaitForInterrupt was not falling.";
|
<< "WaitForInterrupt was not falling.";
|
||||||
|
|
||||||
EXPECT_FALSE(firstCycle) << "Input not low after first delay";
|
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.";
|
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) {
|
TEST_F(DIOLoopTest, AsynchronousInterruptWorks) {
|
||||||
|
Reset();
|
||||||
|
|
||||||
int32_t param = 0;
|
int32_t param = 0;
|
||||||
|
|
||||||
// Given an interrupt handler that sets an int32_t to 12345
|
frc::AsynchronousInterrupt interrupt(m_input,
|
||||||
m_input.RequestInterrupts(InterruptHandler, ¶m);
|
[&](auto a, auto b) { param = 12345; });
|
||||||
m_input.EnableInterrupts();
|
|
||||||
|
|
||||||
|
interrupt.Enable();
|
||||||
// If the voltage rises
|
// If the voltage rises
|
||||||
m_output.Set(false);
|
m_output.Set(false);
|
||||||
m_output.Set(true);
|
m_output.Set(true);
|
||||||
m_input.CancelInterrupts();
|
|
||||||
|
|
||||||
// Then the int32_t should be 12345
|
// Then the int32_t should be 12345
|
||||||
frc::Wait(kDelayTime);
|
frc::Wait(kDelayTime);
|
||||||
|
|
||||||
|
interrupt.Disable();
|
||||||
|
|
||||||
EXPECT_EQ(12345, param) << "The interrupt did not run.";
|
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) {
|
TEST_F(DIOLoopTest, SynchronousInterruptWorks) {
|
||||||
// Given a synchronous interrupt
|
Reset();
|
||||||
m_input.RequestInterrupts();
|
|
||||||
|
|
||||||
// If we have another thread trigger the interrupt in a few seconds
|
// Given a synchronous interrupt
|
||||||
pthread_t interruptTriggererLoop;
|
frc::SynchronousInterrupt interrupt(m_input);
|
||||||
pthread_create(&interruptTriggererLoop, nullptr, InterruptTriggerer,
|
|
||||||
&m_output);
|
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
|
// Then this thread should pause and resume after that number of seconds
|
||||||
frc::Timer timer;
|
frc::Timer timer;
|
||||||
timer.Start();
|
timer.Start();
|
||||||
m_input.WaitForInterrupt(kSynchronousInterruptTime + 1_s);
|
interrupt.WaitForInterrupt(kSynchronousInterruptTime + 1_s);
|
||||||
EXPECT_NEAR_UNITS(kSynchronousInterruptTime, timer.Get(),
|
auto time = timer.Get().to<double>();
|
||||||
kSynchronousInterruptTimeTolerance);
|
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() {
|
public void close() {
|
||||||
super.close();
|
super.close();
|
||||||
SendableRegistry.remove(this);
|
SendableRegistry.remove(this);
|
||||||
if (m_interrupt != 0) {
|
|
||||||
cancelInterrupts();
|
|
||||||
}
|
|
||||||
DIOJNI.freeDIOPort(m_handle);
|
DIOJNI.freeDIOPort(m_handle);
|
||||||
m_handle = 0;
|
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
|
* 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.
|
* 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 boolean isAnalogTrigger();
|
||||||
|
|
||||||
public abstract int getChannel();
|
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.greaterThan;
|
||||||
import static org.hamcrest.Matchers.lessThan;
|
import static org.hamcrest.Matchers.lessThan;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertSame;
|
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
import edu.wpi.first.wpilibj.test.AbstractComsSetup;
|
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
|
* 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 {
|
public abstract class AbstractInterruptTest extends AbstractComsSetup {
|
||||||
private InterruptableSensorBase m_interruptable = null;
|
private DigitalSource m_source = null;
|
||||||
|
|
||||||
private InterruptableSensorBase getInterruptable() {
|
private DigitalSource getSource() {
|
||||||
if (m_interruptable == null) {
|
if (m_source == null) {
|
||||||
m_interruptable = giveInterruptableSensorBase();
|
m_source = giveSource();
|
||||||
}
|
}
|
||||||
return m_interruptable;
|
return m_source;
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void interruptTeardown() {
|
public void interruptTeardown() {
|
||||||
if (m_interruptable != null) {
|
if (m_source != null) {
|
||||||
freeInterruptableSensorBase();
|
freeSource();
|
||||||
m_interruptable = null;
|
m_source = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Give the interruptable sensor base that interrupts can be attached to. */
|
/** Give the sensor source that interrupts can be attached to. */
|
||||||
abstract InterruptableSensorBase giveInterruptableSensorBase();
|
abstract DigitalSource giveSource();
|
||||||
|
|
||||||
/**
|
/** Cleans up the sensor source. This is only called if {@link #giveSource()} is called. */
|
||||||
* Cleans up the interruptable sensor base. This is only called if {@link
|
abstract void freeSource();
|
||||||
* #giveInterruptableSensorBase()} is called.
|
|
||||||
*/
|
|
||||||
abstract void freeInterruptableSensorBase();
|
|
||||||
|
|
||||||
/** Perform whatever action is required to set the interrupt high. */
|
/** Perform whatever action is required to set the interrupt high. */
|
||||||
abstract void setInterruptHigh();
|
abstract void setInterruptHigh();
|
||||||
@@ -55,116 +51,78 @@ public abstract class AbstractInterruptTest extends AbstractComsSetup {
|
|||||||
/** Perform whatever action is required to set the interrupt low. */
|
/** Perform whatever action is required to set the interrupt low. */
|
||||||
abstract void setInterruptLow();
|
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)
|
@Test(timeout = 1000)
|
||||||
public void testSingleInterruptsTriggering() throws Exception {
|
public void testSingleInterruptsTriggering() throws Exception {
|
||||||
// Given
|
// Given
|
||||||
final InterruptCounter counter = new InterruptCounter();
|
// final InterruptCounter counter = new InterruptCounter();
|
||||||
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter);
|
// TestInterruptHandlerFunction function = new
|
||||||
|
// TestInterruptHandlerFunction(counter);
|
||||||
|
|
||||||
// When
|
AtomicBoolean hasFired = new AtomicBoolean(false);
|
||||||
getInterruptable().requestInterrupts(function);
|
AtomicInteger counter = new AtomicInteger(0);
|
||||||
getInterruptable().enableInterrupts();
|
AtomicLong interruptFireTime = new AtomicLong();
|
||||||
|
|
||||||
setInterruptLow();
|
try (AsynchronousInterrupt interrupt =
|
||||||
Timer.delay(0.01);
|
new AsynchronousInterrupt(
|
||||||
// Note: Utility.getFPGATime() is used because double values can turn over
|
getSource(),
|
||||||
// after the robot has been running for a long time
|
(a, b) -> {
|
||||||
final long interruptTriggerTime = RobotController.getFPGATime();
|
interruptFireTime.set(RobotController.getFPGATime());
|
||||||
setInterruptHigh();
|
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
|
assertEquals("The interrupt did not fire the expected number of times", 1, counter.get());
|
||||||
while (!function.m_interruptComplete.get()) {
|
|
||||||
Timer.delay(0.005);
|
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)
|
@Test(timeout = 2000)
|
||||||
public void testMultipleInterruptsTriggering() {
|
public void testMultipleInterruptsTriggering() {
|
||||||
// Given
|
|
||||||
final InterruptCounter counter = new InterruptCounter();
|
|
||||||
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter);
|
|
||||||
|
|
||||||
// When
|
AtomicBoolean hasFired = new AtomicBoolean(false);
|
||||||
getInterruptable().requestInterrupts(function);
|
AtomicInteger counter = new AtomicInteger(0);
|
||||||
getInterruptable().enableInterrupts();
|
|
||||||
|
|
||||||
final int fireCount = 50;
|
try (AsynchronousInterrupt interrupt =
|
||||||
for (int i = 0; i < fireCount; i++) {
|
new AsynchronousInterrupt(
|
||||||
setInterruptLow();
|
getSource(),
|
||||||
setInterruptHigh();
|
(a, b) -> {
|
||||||
// Wait for the interrupt to complete before moving on
|
hasFired.set(true);
|
||||||
while (!function.m_interruptComplete.getAndSet(false)) {
|
counter.incrementAndGet();
|
||||||
Timer.delay(0.005);
|
})) {
|
||||||
|
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. */
|
/** The timeout length for this test in seconds. */
|
||||||
@@ -172,131 +130,128 @@ public abstract class AbstractInterruptTest extends AbstractComsSetup {
|
|||||||
|
|
||||||
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
||||||
public void testSynchronousInterruptsTriggering() {
|
public void testSynchronousInterruptsTriggering() {
|
||||||
// Given
|
|
||||||
getInterruptable().requestInterrupts();
|
|
||||||
|
|
||||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
|
||||||
final Runnable runnable =
|
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||||
() -> {
|
final Runnable runnable =
|
||||||
Timer.delay(synchronousDelay);
|
() -> {
|
||||||
setInterruptLow();
|
Timer.delay(synchronousDelay);
|
||||||
setInterruptHigh();
|
setInterruptLow();
|
||||||
};
|
setInterruptHigh();
|
||||||
|
};
|
||||||
|
|
||||||
// When
|
// When
|
||||||
|
|
||||||
// Note: the long time value is used because doubles can flip if the robot
|
// Note: the long time value is used because doubles can flip if the robot
|
||||||
// is left running for long enough
|
// is left running for long enough
|
||||||
final long startTimeStamp = RobotController.getFPGATime();
|
final long startTimeStamp = RobotController.getFPGATime();
|
||||||
new Thread(runnable).start();
|
new Thread(runnable).start();
|
||||||
// Delay for twice as long as the timeout so the test should fail first
|
// Delay for twice as long as the timeout so the test should fail first
|
||||||
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
|
interrupt.waitForInterrupt(synchronousTimeout * 2);
|
||||||
final long stopTimeStamp = RobotController.getFPGATime();
|
final long stopTimeStamp = RobotController.getFPGATime();
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
// The test will not have timed out and:
|
// The test will not have timed out and:
|
||||||
final double interruptRunTime = (stopTimeStamp - startTimeStamp) * 1e-6;
|
final double interruptRunTime = (stopTimeStamp - startTimeStamp) * 1e-6;
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"The interrupt did not run for the expected amount of time (units in seconds)",
|
"The interrupt did not run for the expected amount of time (units in seconds)",
|
||||||
synchronousDelay,
|
synchronousDelay,
|
||||||
interruptRunTime,
|
interruptRunTime,
|
||||||
0.1);
|
0.1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
||||||
public void testSynchronousInterruptsWaitResultTimeout() {
|
public void testSynchronousInterruptsWaitResultTimeout() {
|
||||||
// Given
|
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
|
||||||
getInterruptable().requestInterrupts();
|
SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout / 2);
|
||||||
|
|
||||||
// Don't fire interrupt. Expect it to timeout.
|
assertEquals(
|
||||||
InterruptableSensorBase.WaitResult result =
|
"The interrupt did not time out correctly.",
|
||||||
getInterruptable().waitForInterrupt(synchronousTimeout / 2);
|
result,
|
||||||
|
SynchronousInterrupt.WaitResult.kTimeout);
|
||||||
assertEquals(
|
}
|
||||||
"The interrupt did not time out correctly.",
|
|
||||||
result,
|
|
||||||
InterruptableSensorBase.WaitResult.kTimeout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
||||||
public void testSynchronousInterruptsWaitResultRisingEdge() {
|
public void testSynchronousInterruptsWaitResultRisingEdge() {
|
||||||
// Given
|
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
|
||||||
getInterruptable().requestInterrupts();
|
|
||||||
|
|
||||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||||
final Runnable runnable =
|
final Runnable runnable =
|
||||||
() -> {
|
() -> {
|
||||||
Timer.delay(synchronousDelay);
|
Timer.delay(synchronousDelay);
|
||||||
setInterruptLow();
|
setInterruptLow();
|
||||||
setInterruptHigh();
|
setInterruptHigh();
|
||||||
};
|
};
|
||||||
|
|
||||||
new Thread(runnable).start();
|
new Thread(runnable).start();
|
||||||
// Delay for twice as long as the timeout so the test should fail first
|
// Delay for twice as long as the timeout so the test should fail first
|
||||||
InterruptableSensorBase.WaitResult result =
|
SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout * 2);
|
||||||
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
|
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"The interrupt did not fire on the rising edge.",
|
"The interrupt did not fire on the rising edge.",
|
||||||
result,
|
result,
|
||||||
InterruptableSensorBase.WaitResult.kRisingEdge);
|
SynchronousInterrupt.WaitResult.kRisingEdge);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
||||||
public void testSynchronousInterruptsWaitResultFallingEdge() {
|
public void testSynchronousInterruptsWaitResultFallingEdge() {
|
||||||
// Given
|
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
|
||||||
getInterruptable().requestInterrupts();
|
// Given
|
||||||
getInterruptable().setUpSourceEdge(false, true);
|
interrupt.setInterruptEdges(false, true);
|
||||||
|
|
||||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||||
final Runnable runnable =
|
final Runnable runnable =
|
||||||
() -> {
|
() -> {
|
||||||
Timer.delay(synchronousDelay);
|
Timer.delay(synchronousDelay);
|
||||||
setInterruptHigh();
|
setInterruptHigh();
|
||||||
setInterruptLow();
|
setInterruptLow();
|
||||||
};
|
};
|
||||||
|
|
||||||
new Thread(runnable).start();
|
new Thread(runnable).start();
|
||||||
// Delay for twice as long as the timeout so the test should fail first
|
// Delay for twice as long as the timeout so the test should fail first
|
||||||
InterruptableSensorBase.WaitResult result =
|
SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout * 2);
|
||||||
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
|
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"The interrupt did not fire on the falling edge.",
|
"The interrupt did not fire on the falling edge.",
|
||||||
result,
|
result,
|
||||||
InterruptableSensorBase.WaitResult.kFallingEdge);
|
SynchronousInterrupt.WaitResult.kFallingEdge);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 4000)
|
@Test(timeout = 4000)
|
||||||
public void testDisableStopsInterruptFiring() {
|
public void testDisableStopsInterruptFiring() {
|
||||||
final InterruptCounter counter = new InterruptCounter();
|
AtomicBoolean interruptComplete = new AtomicBoolean(false);
|
||||||
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter);
|
AtomicInteger counter = new AtomicInteger(0);
|
||||||
|
try (AsynchronousInterrupt interrupt =
|
||||||
// When
|
new AsynchronousInterrupt(
|
||||||
getInterruptable().requestInterrupts(function);
|
getSource(),
|
||||||
getInterruptable().enableInterrupts();
|
(a, b) -> {
|
||||||
|
interruptComplete.set(true);
|
||||||
final int fireCount = 50;
|
counter.incrementAndGet();
|
||||||
for (int i = 0; i < fireCount; i++) {
|
})) {
|
||||||
setInterruptLow();
|
interrupt.enable();
|
||||||
setInterruptHigh();
|
final int fireCount = 50;
|
||||||
// Wait for the interrupt to complete before moving on
|
for (int i = 0; i < fireCount; i++) {
|
||||||
while (!function.m_interruptComplete.getAndSet(false)) {
|
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);
|
Timer.delay(0.005);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
getInterruptable().disableInterrupts();
|
|
||||||
// TestBench.out().println("Finished disabling the robot");
|
|
||||||
|
|
||||||
for (int i = 0; i < fireCount; i++) {
|
assertEquals(
|
||||||
setInterruptLow();
|
"The interrupt did not fire the expected number of times", fireCount, counter.get());
|
||||||
setInterruptHigh();
|
|
||||||
// Just wait because the interrupt should not fire
|
|
||||||
Timer.delay(0.005);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see
|
* @see
|
||||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#giveInterruptableSensorBase()
|
* edu.wpi.first.wpilibj.AbstractInterruptTest#giveSource()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
InterruptableSensorBase giveInterruptableSensorBase() {
|
DigitalSource giveSource() {
|
||||||
m_interruptTrigger = new AnalogTrigger(analogIO.getInput());
|
m_interruptTrigger = new AnalogTrigger(analogIO.getInput());
|
||||||
m_interruptTrigger.setLimitsVoltage(2.0, 3.0);
|
m_interruptTrigger.setLimitsVoltage(2.0, 3.0);
|
||||||
m_interruptTriggerOutput =
|
m_interruptTriggerOutput =
|
||||||
@@ -154,11 +154,10 @@ public class AnalogCrossConnectTest extends AbstractInterruptTest {
|
|||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see
|
* @see
|
||||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#freeInterruptableSensorBase()
|
* edu.wpi.first.wpilibj.AbstractInterruptTest#freeSource()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
void freeInterruptableSensorBase() {
|
void freeSource() {
|
||||||
m_interruptTriggerOutput.cancelInterrupts();
|
|
||||||
m_interruptTriggerOutput.close();
|
m_interruptTriggerOutput.close();
|
||||||
m_interruptTriggerOutput = null;
|
m_interruptTriggerOutput = null;
|
||||||
m_interruptTrigger.close();
|
m_interruptTrigger.close();
|
||||||
|
|||||||
@@ -101,10 +101,11 @@ public class DIOCrossConnectTest extends AbstractInterruptTest {
|
|||||||
dio.getOutput().enablePWM(0.0);
|
dio.getOutput().enablePWM(0.0);
|
||||||
Timer.delay(0.5);
|
Timer.delay(0.5);
|
||||||
dio.getOutput().updateDutyCycle(0.5);
|
dio.getOutput().updateDutyCycle(0.5);
|
||||||
dio.getInput().requestInterrupts();
|
try (var interruptHandler = new SynchronousInterrupt(dio.getInput())) {
|
||||||
dio.getInput().setUpSourceEdge(false, true);
|
interruptHandler.setInterruptEdges(false, true);
|
||||||
// TODO: Add return value from WaitForInterrupt
|
// TODO: Add return value from WaitForInterrupt
|
||||||
dio.getInput().waitForInterrupt(3.0, true);
|
interruptHandler.waitForInterrupt(3.0, true);
|
||||||
|
}
|
||||||
Timer.delay(0.5);
|
Timer.delay(0.5);
|
||||||
final boolean firstCycle = dio.getInput().get();
|
final boolean firstCycle = dio.getInput().get();
|
||||||
Timer.delay(0.5);
|
Timer.delay(0.5);
|
||||||
@@ -140,10 +141,10 @@ public class DIOCrossConnectTest extends AbstractInterruptTest {
|
|||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see
|
* @see
|
||||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#giveInterruptableSensorBase()
|
* edu.wpi.first.wpilibj.AbstractInterruptTest#giveSource()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
InterruptableSensorBase giveInterruptableSensorBase() {
|
DigitalSource giveSource() {
|
||||||
return dio.getInput();
|
return dio.getInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,10 +152,10 @@ public class DIOCrossConnectTest extends AbstractInterruptTest {
|
|||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see
|
* @see
|
||||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#freeInterruptableSensorBase()
|
* edu.wpi.first.wpilibj.AbstractInterruptTest#freeSource()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
void freeInterruptableSensorBase() {
|
void freeSource() {
|
||||||
// Handled in the fixture
|
// Handled in the fixture
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,11 +61,6 @@ public class DIOCrossConnectFixture implements ITestFixture {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean reset() {
|
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);
|
m_output.set(false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user