LinkWireless: deprecating asyncACKTimerId. With last changes, it's not needed and actually burns more cycles

This commit is contained in:
Rodrigo Alfonso
2024-08-27 06:05:05 -03:00
parent 57a818d5d0
commit 44453da09f
11 changed files with 43 additions and 221 deletions

View File

@@ -199,7 +199,6 @@ Name | Type | Default | Description
`timeout` | **u32** | `10` | Maximum number of *frames* without receiving data from other player before resetting the connection.
`interval` | **u16** | `50` | Number of *1024-cycle ticks* (61.04μs) between transfers *(50 = 3.052ms)*. It's the interval of Timer #`sendTimerId`. Lower values will transfer faster but also consume more CPU. You can use `Link::perFrame(...)` to convert from *packets per frame* to *interval values*.
`sendTimerId` | **u8** *(0~3)* | `3` | GBA Timer to use for sending.
`asyncACKTimerId` | **s8** *(0~3 or -1)* | `-1` | This GBA timer is used to asynchronously acknowledge the adapter transfers. It is disabled by default, with the acknowledgment procedure performed during SERIAL interrupts.
You can update these values at any time without creating a new instance:
- Call `deactivate()`.

View File

@@ -48,8 +48,7 @@ LinkUniversal* linkUniversal =
.maxPlayers = 2,
.timeout = LINK_WIRELESS_DEFAULT_TIMEOUT,
.interval = LINK_WIRELESS_DEFAULT_INTERVAL,
.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID,
.asyncACKTimerId = 0},
.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID},
__qran_seed);
LinkUniversal* linkConnection = linkUniversal;
#endif
@@ -76,8 +75,6 @@ void init() {
interrupt_enable(INTR_SERIAL);
interrupt_set_handler(INTR_TIMER3, LINK_UNIVERSAL_ISR_TIMER);
interrupt_enable(INTR_TIMER3);
interrupt_set_handler(INTR_TIMER0, LINK_UNIVERSAL_ISR_ACK_TIMER);
interrupt_enable(INTR_TIMER0);
#endif
}

View File

@@ -43,21 +43,20 @@ int main() {
u32 maxPlayers = (initialKeys & KEY_B) ? 2 : LINK_UNIVERSAL_MAX_PLAYERS;
// (1) Create a LinkUniversal instance
linkUniversal = new LinkUniversal(
protocol, "LinkUNI",
(LinkUniversal::CableOptions){
.baudRate = LinkCable::BAUD_RATE_1,
.timeout = LINK_CABLE_DEFAULT_TIMEOUT,
.interval = LINK_CABLE_DEFAULT_INTERVAL,
.sendTimerId = LINK_CABLE_DEFAULT_SEND_TIMER_ID},
(LinkUniversal::WirelessOptions){
.retransmission = true,
.maxPlayers = maxPlayers,
.timeout = LINK_WIRELESS_DEFAULT_TIMEOUT,
.interval = LINK_WIRELESS_DEFAULT_INTERVAL,
.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID,
.asyncACKTimerId = LINK_WIRELESS_DEFAULT_ASYNC_ACK_TIMER_ID},
__qran_seed);
linkUniversal =
new LinkUniversal(protocol, "LinkUNI",
(LinkUniversal::CableOptions){
.baudRate = LinkCable::BAUD_RATE_1,
.timeout = LINK_CABLE_DEFAULT_TIMEOUT,
.interval = LINK_CABLE_DEFAULT_INTERVAL,
.sendTimerId = LINK_CABLE_DEFAULT_SEND_TIMER_ID},
(LinkUniversal::WirelessOptions){
.retransmission = true,
.maxPlayers = maxPlayers,
.timeout = LINK_WIRELESS_DEFAULT_TIMEOUT,
.interval = LINK_WIRELESS_DEFAULT_INTERVAL,
.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID},
__qran_seed);
// (2) Add the required interrupt service routines
interrupt_init();

View File

@@ -34,7 +34,7 @@ void hang();
LinkWireless::Error lastError;
LinkWireless* linkWireless = nullptr;
bool forwarding, retransmission, asyncACK;
bool forwarding, retransmission;
u32 maxPlayers;
void init() {
@@ -69,20 +69,17 @@ start:
"Press A to start\n\n"
"hold LEFT on start:\n -> disable forwarding\n\n"
"hold UP on start:\n -> disable retransmission\n\n"
"hold B on start:\n -> set 2 players\n\n"
"hold START on start:\n -> async ACK");
"hold B on start:\n -> set 2 players");
waitFor(KEY_A);
u16 initialKeys = ~REG_KEYS & KEY_ANY;
forwarding = !(initialKeys & KEY_LEFT);
retransmission = !(initialKeys & KEY_UP);
maxPlayers = (initialKeys & KEY_B) ? 2 : LINK_WIRELESS_MAX_PLAYERS;
asyncACK = initialKeys & KEY_START;
// (1) Create a LinkWireless instance
linkWireless = new LinkWireless(
forwarding, retransmission, maxPlayers, LINK_WIRELESS_DEFAULT_TIMEOUT,
LINK_WIRELESS_DEFAULT_INTERVAL, LINK_WIRELESS_DEFAULT_SEND_TIMER_ID,
asyncACK ? 0 : -1);
LINK_WIRELESS_DEFAULT_INTERVAL, LINK_WIRELESS_DEFAULT_SEND_TIMER_ID);
// linkWireless->debug = [](std::string str) { log(str); };
// (2) Add the required interrupt service routines
@@ -94,10 +91,6 @@ start:
interrupt_set_handler(INTR_TIMER3, LINK_WIRELESS_ISR_TIMER);
interrupt_enable(INTR_TIMER3);
// (only required when using async ACK)
interrupt_set_handler(INTR_TIMER0, LINK_WIRELESS_ISR_ACK_TIMER);
interrupt_enable(INTR_TIMER0);
// (3) Initialize the library
linkWireless->activate();
@@ -114,8 +107,7 @@ start:
"(SELECT = cancel)\n (START = activate)\n\n-> forwarding: " +
(forwarding ? "ON" : "OFF") +
"\n-> retransmission: " + (retransmission ? "ON" : "OFF") +
"\n-> max players: " + std::to_string(maxPlayers) +
"\n-> async ACK: " + (asyncACK ? "ON" : "OFF"));
"\n-> max players: " + std::to_string(maxPlayers));
// SELECT = back
if (keys & KEY_SELECT) {
@@ -431,22 +423,16 @@ void messageLoop() {
output += "\n_onVBlank: " + std::to_string(linkWireless->lastVBlankTime);
output += "\n_onSerial: " + std::to_string(linkWireless->lastSerialTime);
output += "\n_onTimer: " + std::to_string(linkWireless->lastTimerTime);
if (asyncACK)
output += " | " + std::to_string(linkWireless->lastACKTimerTime);
output +=
"\n_serialIRQs: " + std::to_string(linkWireless->lastFrameSerialIRQs);
output +=
"\n_timerIRQs: " + std::to_string(linkWireless->lastFrameTimerIRQs);
if (asyncACK)
output += " | " + std::to_string(linkWireless->lastFrameACKTimerIRQs);
output +=
"\n_ms: " +
std::to_string(linkWireless->toMs(
linkWireless->lastVBlankTime +
linkWireless->lastSerialTime * linkWireless->lastFrameSerialIRQs +
linkWireless->lastTimerTime * linkWireless->lastFrameTimerIRQs +
linkWireless->lastACKTimerTime *
linkWireless->lastFrameACKTimerIRQs));
linkWireless->lastTimerTime * linkWireless->lastFrameTimerIRQs));
#else
if (lostPackets > 0) {
output += "\n\n_lostPackets: " + std::to_string(lostPackets) + "\n";

View File

@@ -12,8 +12,6 @@
// irq_add(II_VBLANK, LINK_UNIVERSAL_ISR_VBLANK);
// irq_add(II_SERIAL, LINK_UNIVERSAL_ISR_SERIAL);
// irq_add(II_TIMER3, LINK_UNIVERSAL_ISR_TIMER);
// irq_add(II_TIMER2, LINK_UNIVERSAL_ISR_ACK_TIMER); // (*)
// // optional, for `LinkWireless::asyncACKTimerId` -----^
// - 3) Initialize the library with:
// linkUniversal->activate();
// - 4) Sync:
@@ -120,7 +118,6 @@ class LinkUniversal {
u32 timeout;
u16 interval;
u8 sendTimerId;
s8 asyncACKTimerId;
};
/**
@@ -147,8 +144,7 @@ class LinkUniversal {
true, LINK_UNIVERSAL_MAX_PLAYERS,
LINK_WIRELESS_DEFAULT_TIMEOUT,
LINK_WIRELESS_DEFAULT_INTERVAL,
LINK_WIRELESS_DEFAULT_SEND_TIMER_ID,
LINK_WIRELESS_DEFAULT_ASYNC_ACK_TIMER_ID},
LINK_WIRELESS_DEFAULT_SEND_TIMER_ID},
int randomSeed = 123) {
this->linkCable =
new LinkCable(cableOptions.baudRate, cableOptions.timeout,
@@ -157,7 +153,7 @@ class LinkUniversal {
wirelessOptions.retransmission, true,
Link::_min(wirelessOptions.maxPlayers, LINK_UNIVERSAL_MAX_PLAYERS),
wirelessOptions.timeout, wirelessOptions.interval,
wirelessOptions.sendTimerId, wirelessOptions.asyncACKTimerId);
wirelessOptions.sendTimerId);
this->config.protocol = protocol;
this->config.gameName = gameName;
@@ -440,15 +436,6 @@ class LinkUniversal {
linkWireless->_onTimer();
}
/**
* @brief This method is called by the other TIMER interrupt handler.
* \warning This is internal API!
*/
void _onACKTimer() {
if (mode == LINK_WIRELESS)
linkWireless->_onACKTimer();
}
LinkCable* linkCable;
LinkWireless* linkWireless;
@@ -712,11 +699,4 @@ inline void LINK_UNIVERSAL_ISR_TIMER() {
linkUniversal->_onTimer();
}
/**
* @brief TIMER interrupt handler used for ACKs.
*/
inline void LINK_UNIVERSAL_ISR_ACK_TIMER() {
linkUniversal->_onACKTimer();
}
#endif // LINK_UNIVERSAL_H

View File

@@ -27,22 +27,6 @@ LINK_WIRELESS_CODE_IWRAM void LinkWireless::_onTimer() {
__onTimer();
#ifdef LINK_WIRELESS_ENABLE_NESTED_IRQ
irqEnd();
#endif
}
LINK_WIRELESS_CODE_IWRAM void LinkWireless::_onACKTimer() {
#ifdef LINK_WIRELESS_ENABLE_NESTED_IRQ
if (interrupt)
return;
interrupt = true;
LINK_WIRELESS_BARRIER;
Link::_REG_IME = 1;
#endif
__onACKTimer();
#ifdef LINK_WIRELESS_ENABLE_NESTED_IRQ
irqEnd();
#endif

View File

@@ -12,8 +12,6 @@
// irq_add(II_VBLANK, LINK_WIRELESS_ISR_VBLANK);
// irq_add(II_SERIAL, LINK_WIRELESS_ISR_SERIAL);
// irq_add(II_TIMER3, LINK_WIRELESS_ISR_TIMER);
// irq_add(II_TIMER2, LINK_WIRELESS_ISR_ACK_TIMER); // --v
// // optional, for `LinkWireless::asyncACKTimerId` -----^
// - 3) Initialize the library with:
// linkWireless->activate();
// - 4) Start a server:
@@ -161,7 +159,6 @@ static volatile char LINK_WIRELESS_VERSION[] = "LinkWireless/v7.0.0";
#define LINK_WIRELESS_DEFAULT_TIMEOUT 10
#define LINK_WIRELESS_DEFAULT_INTERVAL 50
#define LINK_WIRELESS_DEFAULT_SEND_TIMER_ID 3
#define LINK_WIRELESS_DEFAULT_ASYNC_ACK_TIMER_ID -1
#define LINK_WIRELESS_BARRIER asm volatile("" ::: "memory")
#define LINK_WIRELESS_CODE_IWRAM \
__attribute__((section(".iwram"), target("arm"), noinline))
@@ -235,13 +232,10 @@ class LinkWireless {
u32 lastVBlankTime = 0;
u32 lastSerialTime = 0;
u32 lastTimerTime = 0;
u32 lastACKTimerTime = 0;
u32 lastFrameSerialIRQs = 0;
u32 lastFrameTimerIRQs = 0;
u32 lastFrameACKTimerIRQs = 0;
u32 serialIRQCount = 0;
u32 timerIRQCount = 0;
u32 ackTimerIRQCount = 0;
#endif
enum State {
@@ -304,20 +298,15 @@ class LinkWireless {
* *(50 = 3.052ms)*. It's the interval of Timer #`sendTimerId`. Lower values
* will transfer faster but also consume more CPU.
* @param sendTimerId GBA Timer to use for sending.
* @param asyncACKTimerId This GBA timer is used to asynchronously acknowledge
* the adapter transfers. It is disabled by default, with the acknowledgment
* procedure performed during SERIAL interrupts.
* \warning You can use `Link::perFrame(...)` to convert from *packets per
* frame* to *interval values*.
*/
explicit LinkWireless(
bool forwarding = true,
bool retransmission = true,
u8 maxPlayers = LINK_WIRELESS_MAX_PLAYERS,
u32 timeout = LINK_WIRELESS_DEFAULT_TIMEOUT,
u16 interval = LINK_WIRELESS_DEFAULT_INTERVAL,
u8 sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID,
s8 asyncACKTimerId = LINK_WIRELESS_DEFAULT_ASYNC_ACK_TIMER_ID) {
explicit LinkWireless(bool forwarding = true,
bool retransmission = true,
u8 maxPlayers = LINK_WIRELESS_MAX_PLAYERS,
u32 timeout = LINK_WIRELESS_DEFAULT_TIMEOUT,
u16 interval = LINK_WIRELESS_DEFAULT_INTERVAL,
u8 sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID) {
#ifdef LINK_WIRELESS_TWO_PLAYERS_ONLY
maxPlayers = 2;
#endif
@@ -328,7 +317,6 @@ class LinkWireless {
this->config.timeout = timeout;
this->config.interval = interval;
this->config.sendTimerId = sendTimerId;
this->config.asyncACKTimerId = asyncACKTimerId;
}
/**
@@ -861,21 +849,17 @@ class LinkWireless {
lastVBlankTime = profileStop();
lastFrameSerialIRQs = serialIRQCount;
lastFrameTimerIRQs = timerIRQCount;
lastFrameACKTimerIRQs = ackTimerIRQCount;
serialIRQCount = 0;
timerIRQCount = 0;
ackTimerIRQCount = 0;
#endif
}
#ifdef LINK_WIRELESS_PUT_ISR_IN_IWRAM
void _onSerial();
void _onTimer();
void _onACKTimer();
#else
void _onSerial() { __onSerial(); }
void _onTimer() { __onTimer(); }
void _onACKTimer() { __onACKTimer(); }
#endif
/**
@@ -893,40 +877,25 @@ class LinkWireless {
linkSPI->_onSerial(true);
bool hasNewData = linkSPI->getAsyncState() == LinkSPI::AsyncState::READY;
if (!usesAsyncACK()) {
if (hasNewData) {
if (!acknowledge()) {
reset();
lastError = ACKNOWLEDGE_FAILED;
return;
}
} else
if (hasNewData) {
if (!acknowledge()) {
reset();
lastError = ACKNOWLEDGE_FAILED;
return;
}
}
} else
return;
u32 newData = linkSPI->getAsyncData();
if (!isSessionActive())
return;
if (asyncCommand.isActive) {
if (usesAsyncACK()) {
if (asyncCommand.ackStep != AsyncCommand::ACKStep::READY)
return;
if (asyncCommand.state == AsyncCommand::State::PENDING) {
updateAsyncCommand(newData);
if (hasNewData) {
linkSPI->_setSOLow();
asyncCommand.ackStep = AsyncCommand::ACKStep::WAITING_FOR_HIGH;
asyncCommand.pendingData = newData;
startACKTimer();
} else
return;
} else {
if (asyncCommand.state == AsyncCommand::State::PENDING) {
updateAsyncCommand(newData);
if (asyncCommand.state == AsyncCommand::State::COMPLETED)
processAsyncCommand();
}
if (asyncCommand.state == AsyncCommand::State::COMPLETED)
processAsyncCommand();
}
}
@@ -960,47 +929,6 @@ class LinkWireless {
#endif
}
/**
* @brief This method is called by the other TIMER interrupt handler.
* \warning This is internal API!
*/
LINK_WIRELESS_ALWAYS_INLINE void __onACKTimer() {
if (!isEnabled || !asyncCommand.isActive ||
asyncCommand.ackStep == AsyncCommand::ACKStep::READY)
return;
if (asyncCommand.ackStep == AsyncCommand::ACKStep::WAITING_FOR_HIGH) {
if (!linkSPI->_isSIHigh())
return;
linkSPI->_setSOHigh();
asyncCommand.ackStep = AsyncCommand::ACKStep::WAITING_FOR_LOW;
} else if (asyncCommand.ackStep == AsyncCommand::ACKStep::WAITING_FOR_LOW) {
if (linkSPI->_isSIHigh())
return;
#ifdef PROFILING_ENABLED
profileStart();
#endif
linkSPI->_setSOLow();
asyncCommand.ackStep = AsyncCommand::ACKStep::READY;
stopACKTimer();
if (asyncCommand.state == AsyncCommand::State::PENDING) {
updateAsyncCommand(asyncCommand.pendingData);
if (asyncCommand.state == AsyncCommand::State::COMPLETED)
processAsyncCommand();
}
#ifdef PROFILING_ENABLED
lastACKTimerTime = profileStop();
ackTimerIRQCount++;
#endif
}
}
struct Config {
bool forwarding;
bool retransmission;
@@ -1008,7 +936,6 @@ class LinkWireless {
u32 timeout;
u32 interval;
u32 sendTimerId;
s8 asyncACKTimerId;
};
/**
@@ -1088,8 +1015,6 @@ class LinkWireless {
DATA_REQUEST
};
enum ACKStep { READY, WAITING_FOR_HIGH, WAITING_FOR_LOW };
u8 type;
u32 parameters[LINK_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH];
u32 responses[LINK_WIRELESS_MAX_COMMAND_RESPONSE_LENGTH];
@@ -1098,8 +1023,6 @@ class LinkWireless {
Step step;
u32 sentParameters, totalParameters;
u32 receivedResponses, totalResponses;
u32 pendingData;
ACKStep ackStep;
bool isActive;
};
@@ -1560,17 +1483,6 @@ class LinkWireless {
nextAsyncCommandDataSize++;
}
void startACKTimer() {
Link::_REG_TM[config.asyncACKTimerId].start = -1;
Link::_REG_TM[config.asyncACKTimerId].cnt =
Link::_TM_ENABLE | Link::_TM_IRQ | BASE_FREQUENCY;
}
void stopACKTimer() {
Link::_REG_TM[config.asyncACKTimerId].cnt =
Link::_REG_TM[config.asyncACKTimerId].cnt & (~Link::_TM_ENABLE);
}
void copyName(char* target, const char* source, u32 length) {
u32 len = std::strlen(source);
@@ -1664,9 +1576,6 @@ class LinkWireless {
void stop() {
stopTimer();
if (usesAsyncACK())
stopACKTimer();
linkSPI->deactivate();
}
@@ -1808,8 +1717,6 @@ class LinkWireless {
asyncCommand.totalParameters = withData ? nextAsyncCommandDataSize : 0;
asyncCommand.receivedResponses = 0;
asyncCommand.totalResponses = 0;
asyncCommand.pendingData = 0;
asyncCommand.ackStep = AsyncCommand::ACKStep::READY;
asyncCommand.isActive = true;
u32 command = buildCommand(type, asyncCommand.totalParameters);
@@ -1937,8 +1844,6 @@ class LinkWireless {
return true;
}
bool usesAsyncACK() { return config.asyncACKTimerId > -1; }
bool cmdTimeout(u32& lines, u32& vCount) {
return timeout(CMD_TIMEOUT, lines, vCount);
}
@@ -2039,11 +1944,4 @@ inline void LINK_WIRELESS_ISR_TIMER() {
linkWireless->_onTimer();
}
/**
* @brief TIMER interrupt handler used for ACKs.
*/
inline void LINK_WIRELESS_ISR_ACK_TIMER() {
linkWireless->_onACKTimer();
}
#endif // LINK_WIRELESS_H

View File

@@ -22,7 +22,7 @@ C_LinkUniversalHandle C_LinkUniversal_create(
LinkUniversal::WirelessOptions{
wirelessOptions.retransmission, wirelessOptions.maxPlayers,
wirelessOptions.timeout, wirelessOptions.interval,
wirelessOptions.sendTimerId, wirelessOptions.asyncACKTimerId},
wirelessOptions.sendTimerId},
randomSeed);
}
@@ -125,8 +125,4 @@ void C_LinkUniversal_onSerial(C_LinkUniversalHandle handle) {
void C_LinkUniversal_onTimer(C_LinkUniversalHandle handle) {
static_cast<LinkUniversal*>(handle)->_onTimer();
}
void C_LinkUniversal_onACKTimer(C_LinkUniversalHandle handle) {
static_cast<LinkUniversal*>(handle)->_onACKTimer();
}
}

View File

@@ -44,7 +44,6 @@ typedef struct {
u32 timeout;
u16 interval;
u8 sendTimerId;
s8 asyncACKTimerId;
} C_LinkUniversal_WirelessOptions;
C_LinkUniversalHandle C_LinkUniversal_createDefault();
@@ -90,7 +89,6 @@ u32 C_LinkUniversal_getSubWaitCount(C_LinkUniversalHandle handle);
void C_LinkUniversal_onVBlank(C_LinkUniversalHandle handle);
void C_LinkUniversal_onSerial(C_LinkUniversalHandle handle);
void C_LinkUniversal_onTimer(C_LinkUniversalHandle handle);
void C_LinkUniversal_onACKTimer(C_LinkUniversalHandle handle);
extern C_LinkUniversalHandle cLinkUniversal;
@@ -106,10 +104,6 @@ inline void C_LINK_UNIVERSAL_ISR_TIMER() {
C_LinkUniversal_onTimer(cLinkUniversal);
}
inline void C_LINK_UNIVERSAL_ISR_ACK_TIMER() {
C_LinkUniversal_onACKTimer(cLinkUniversal);
}
#ifdef __cplusplus
}
#endif

View File

@@ -12,10 +12,9 @@ C_LinkWirelessHandle C_LinkWireless_create(bool forwarding,
u8 maxPlayers,
u32 timeout,
u16 interval,
u8 sendTimerId,
s8 asyncACKTimerId) {
u8 sendTimerId) {
return new LinkWireless(forwarding, retransmission, maxPlayers, timeout,
interval, sendTimerId, asyncACKTimerId);
interval, sendTimerId);
}
void C_LinkWireless_destroy(C_LinkWirelessHandle handle) {
@@ -186,8 +185,4 @@ void C_LinkWireless_onSerial(C_LinkWirelessHandle handle) {
void C_LinkWireless_onTimer(C_LinkWirelessHandle handle) {
static_cast<LinkWireless*>(handle)->_onTimer();
}
void C_LinkWireless_onACKTimer(C_LinkWirelessHandle handle) {
static_cast<LinkWireless*>(handle)->_onACKTimer();
}
}

View File

@@ -73,8 +73,7 @@ C_LinkWirelessHandle C_LinkWireless_create(bool forwarding,
u8 maxPlayers,
u32 timeout,
u16 interval,
u8 sendTimerId,
s8 asyncACKTimerId);
u8 sendTimerId);
void C_LinkWireless_destroy(C_LinkWirelessHandle handle);
bool C_LinkWireless_activate(C_LinkWirelessHandle handle);
@@ -122,7 +121,6 @@ u32 C_LinkWireless_nextPendingPacketId(C_LinkWirelessHandle handle);
void C_LinkWireless_onVBlank(C_LinkWirelessHandle handle);
void C_LinkWireless_onSerial(C_LinkWirelessHandle handle);
void C_LinkWireless_onTimer(C_LinkWirelessHandle handle);
void C_LinkWireless_onACKTimer(C_LinkWirelessHandle handle);
extern C_LinkWirelessHandle cLinkWireless;
@@ -138,10 +136,6 @@ inline void C_LINK_WIRELESS_ISR_TIMER() {
C_LinkWireless_onTimer(cLinkWireless);
}
inline void C_LINK_WIRELESS_ISR_ACK_TIMER() {
C_LinkWireless_onACKTimer(cLinkWireless);
}
#ifdef __cplusplus
}
#endif