Skyward boardcore
Loading...
Searching...
No Matches
Xbee.cpp
Go to the documentation of this file.
1/* Copyright (c) 2021 Skyward Experimental Rocketry
2 * Author: Luca Erbetta
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#include "Xbee.h"
24
26#include <kernel/scheduler/scheduler.h>
27#include <miosix.h>
28#include <utils/Debug.h>
29
30#include <algorithm>
31
32using miosix::FastMutex;
33using miosix::Lock;
34using miosix::Unlock;
35using std::min;
36
37namespace Boardcore
38{
39
40namespace Xbee
41{
42
44 long long txTimeout)
45 : Xbee(bus, {}, cs, attn, rst, txTimeout)
46{
47 spiXbee.config.clockDivider = SPI::ClockDivider::DIV_128;
48}
49
51 GpioType attn, GpioType rst, long long txTimeout)
52 : spiXbee(bus, cs, config), attn(attn), rst(rst), txTimeout(txTimeout)
53{
54 reset();
55}
56
58
59bool Xbee::send(uint8_t* pkt, size_t packetLength)
60{
61 if (packetLength > MAX_PACKET_PAYLOAD_LENGTH || packetLength == 0)
62 {
63 LOG_ERR(logger, "Invalid packet length (0< {} <= {})", packetLength,
64 MAX_PACKET_PAYLOAD_LENGTH);
65 return false;
66 }
67 long long startTick = Kernel::getOldTick();
68
69 TXRequestFrame txReq;
70 uint8_t txFrameId = buildTXRequestFrame(txReq, pkt, packetLength);
71 bool success = false;
72 bool statusTimeout = true;
73 {
74 Lock<FastMutex> l(mutexXbeeCommunication);
75 writeFrame(txReq);
76
77 // Wait for a TX Status frame
78 long long timeoutTick = Kernel::getOldTick() + txTimeout;
79
80 while (waitForFrame(FTYPE_TX_STATUS, FRAME_POLL_INTERVAL, timeoutTick))
81 {
82 TXStatusFrame* f = parsingApiFrame.toFrameType<TXStatusFrame>();
83
84 if (f->getFrameID() == txFrameId)
85 {
86 success = f->getDeliveryStatus() == DELS_SUCCESS;
87 statusTimeout = false;
88 break;
89 }
90 else
91 {
92 LOG_ERR(logger, "Wrong txStatus ID");
93 }
94 }
95
96 // Ak for total transmitted byte count for logging purposes
97 // sendATCommandInternal("BC");
98 }
99
100 // If there is more data to receive, wake the receive() thread
101 if (attn.value() == 0)
102 wakeReceiver();
103
104 if (statusTimeout)
105 {
106 ++status.txTimeoutCount;
107 LOG_ERR(logger, "TX_STATUS timeout");
108 }
109 timeToSendStats.add(Kernel::getOldTick() - startTick);
110
111 StackLogger::getInstance().updateStack(THID_XBEE);
112
113 return success;
114}
115
116ssize_t Xbee::receive(uint8_t* buf, size_t bufMaxSize)
117{
118 while (true)
119 {
120 // We have data in the buffer pending to be returned
121 if (!rxFramesBuffer.isEmpty() || currRxPayloadPointer >= 0)
122 {
123 return fillReceiveBuf(buf, bufMaxSize);
124 }
125
126 // No data in the buffer, but the xbee has data to return via SPI
127 else if (attn.value() == 0)
128 {
129 Lock<FastMutex> l(mutexXbeeCommunication);
130 if (readRXFrame())
131 {
132 sendATCommandInternal("DB"); // Query last packet RSSI
133 sendATCommandInternal("ER"); // Query receive error count
134 }
135 }
136 // No data available at all: sleep until we have something to do
137 else
138 {
139 {
140 miosix::FastInterruptDisableLock dLock;
141 receiveThread = miosix::Thread::getCurrentThread();
142
143 while (receiveThread != 0) // Avoid spurious wakeups
144 {
145 receiveThread->IRQwait();
146 {
147 miosix::FastInterruptEnableLock eLock(dLock);
148 miosix::Thread::yield();
149 }
150 }
151 }
152
153 // Forcefully return without receiving anything
154 if (forceRcvReturn)
155 {
156 forceRcvReturn = false;
157 return -1;
158 }
159 }
160 }
161}
162
164{
165 status.timestamp = Kernel::getOldTick();
166 status.timeToSendStats = timeToSendStats.getStats();
167 return status;
168}
169
171{
172 Lock<FastMutex> l(mutexXbeeCommunication);
173 {
174 miosix::FastInterruptDisableLock dLock();
175 rst.mode(miosix::Mode::OPEN_DRAIN);
176 }
177 rst.low();
178 miosix::delayUs(50);
179 rst.high();
180
181 // When the xbee is ready, we should assert SSEL to tell it to use
182 // SPI, and it should provide us with a modem status frame
183 long long timeout = Kernel::getOldTick() + 1000;
184 do
185 {
186 // Assert SSEL on every iteration as we don't exactly know when the
187 // xbee will be ready.
188 {
189 SPIAcquireLock acq(spiXbee);
190 spiXbee.cs.low();
191 miosix::delayUs(10);
192 spiXbee.cs.high();
193 }
194
195 miosix::delayUs(50);
196
197 if (attn.value() == 0 && readOneFrame() == ParseResult::SUCCESS)
198 {
199 handleFrame(parsingApiFrame);
200
201 if (parsingApiFrame.frameType == FTYPE_MODEM_STATUS)
202 break;
203 }
204 miosix::Thread::sleep(5);
205 } while (Kernel::getOldTick() < timeout);
206}
207
208void Xbee::wakeReceiver(bool forceReturn)
209{
210 forceRcvReturn = forceReturn;
211 miosix::FastInterruptDisableLock dLock;
212
213 if (receiveThread)
214 {
215 receiveThread->IRQwakeup();
216 receiveThread = 0;
217 }
218}
219
221{
222 if (receiveThread)
223 {
224 receiveThread->IRQwakeup();
225 if (receiveThread->IRQgetPriority() >
226 miosix::Thread::IRQgetCurrentThread()->IRQgetPriority())
227 {
228 miosix::Scheduler::IRQfindNextThread();
229 }
230
231 receiveThread = 0;
232 }
233}
234
235size_t Xbee::fillReceiveBuf(uint8_t* buf, size_t bufMaxSize)
236{
237 if (!rxFramesBuffer.isEmpty() && currRxPayloadPointer == -1)
238 {
239 Lock<FastMutex> l(mutexRxFrames);
240 currRxFrame = rxFramesBuffer.pop();
241 currRxPayloadPointer = 0;
242 }
243
244 if (currRxPayloadPointer >= 0)
245 {
246 size_t lenRemainingData =
247 currRxFrame.getRXDataLength() - currRxPayloadPointer;
248
249 // The buffer may be smaller than the data we need to return
250 size_t lenToCopy = min(bufMaxSize, lenRemainingData);
251
252 memcpy(buf, currRxFrame.getRXDataPointer() + currRxPayloadPointer,
253 lenToCopy);
254
255 currRxPayloadPointer += lenToCopy;
256
257 if (currRxPayloadPointer == currRxFrame.getRXDataLength())
258 {
259 // We've emptied the current frame
260 currRxPayloadPointer = -1;
261 }
262
263 return lenToCopy;
264 }
265
266 return 0;
267}
268
269ParseResult Xbee::readOneFrame()
270{
271 SPIAcquireLock acq(spiXbee);
272 SPISelectLock sel(spiXbee);
273
274 ParseResult result = ParseResult::IDLE;
275 do
276 {
277 result = parser.parse(spiXbee.bus.read(), &parsingApiFrame);
278 } while (attn.value() == 0 && result == ParseResult::PARSING);
279
280 return result;
281}
282
283bool Xbee::readRXFrame()
284{
285 while (attn.value() == 0)
286 {
287 if (readOneFrame() == ParseResult::SUCCESS)
288 {
289 handleFrame(parsingApiFrame);
290
291 if (parsingApiFrame.frameType == FTYPE_RX_PACKET_FRAME)
292 return true;
293 }
294 }
295 return false;
296}
297
298void Xbee::writeFrame(APIFrame& frame)
299{
300 frame.timestamp = Kernel::getOldTick(); // Only for logging purposes
301
302 // Serialize the frame
303 uint8_t txBuf[MAX_API_FRAME_SIZE];
304 size_t txBufSize = frame.toBytes(txBuf);
305
306 {
307 SPITransaction spi(spiXbee);
308 spi.transfer(txBuf, txBufSize);
309 }
310
311 // Pass the frame we just sent to the listener
312 if (frameListener)
313 frameListener(frame);
314
315 // Full duplex spi transfer: we may have received valid data while we
316 // were sending the frame.
317 for (unsigned int i = 0; i < txBufSize; i++)
318 {
319 ParseResult res = parser.parse(txBuf[i], &parsingApiFrame);
320 if (res == ParseResult::SUCCESS)
321 handleFrame(parsingApiFrame);
322 }
323}
324
325bool Xbee::waitForFrame(uint8_t frameType, unsigned int pollInterval,
326 long long timeoutTick)
327{
328 do
329 {
330 if (attn.value() == 0)
331 {
332 if (readOneFrame() == ParseResult::SUCCESS)
333 {
334 handleFrame(parsingApiFrame);
335
336 if (parsingApiFrame.frameType == frameType)
337 return true;
338 }
339 }
340 else
341 {
342 miosix::Thread::sleep(pollInterval);
343 }
344 } while (Kernel::getOldTick() < timeoutTick);
345
346 return false;
347}
348
349uint8_t Xbee::buildTXRequestFrame(TXRequestFrame& txReq, uint8_t* pkt,
350 size_t packetLength)
351{
352 memcpy(txReq.getRFDataPointer(), pkt, packetLength);
353 txReq.setRFDataLength(packetLength);
354
355 uint8_t txFrameId = getNewFrameID();
356
357 txReq.setFrameID(txFrameId);
358
359 txReq.setDestAddress(ADDRESS_BROADCAST);
360 txReq.setBroadcastRadius(0); // 0 = max hops, but we don't really care as
361 // it does not apply to point-multipoint mode
362 // Point-multipoint mode, disable ack, disable route discovery
363 txReq.setTransmitOptions(TO_DM_POINT_MULTIPOINT | TO_DISABLE_ACK |
365 txReq.calcChecksum();
366
367 return txFrameId;
368}
369
370void Xbee::sendATCommand(const char* cmd, uint8_t* params, size_t paramsLen)
371{
372 Lock<FastMutex> l(mutexXbeeCommunication);
373
374 sendATCommandInternal(0, cmd, params, paramsLen);
375}
376
377bool Xbee::sendATCommand(const char* cmd, ATCommandResponseFrame* response,
378 uint8_t* params, size_t paramsLen,
379 unsigned int timeout)
380{
381 Lock<FastMutex> l(mutexXbeeCommunication);
382
383 uint8_t txFrameId = sendATCommandInternal(cmd, params, paramsLen);
384
385 bool success = false;
386
387 long long timeoutTick = Kernel::getOldTick() + timeout;
388
389 while (waitForFrame(FTYPE_AT_COMMAND_RESPONSE, FRAME_POLL_INTERVAL,
390 timeoutTick))
391 {
393 parsingApiFrame.toFrameType<ATCommandResponseFrame>();
394
395 if (f->getFrameID() == txFrameId &&
396 strncmp(cmd, f->getATCommand(), 2) == 0)
397 {
398 memcpy(response, f, sizeof(ATCommandResponseFrame));
399 success = true;
400 break;
401 }
402 }
403
404 return success;
405}
406
407uint8_t Xbee::sendATCommandInternal(const char* cmd, uint8_t* params,
408 size_t paramsLen)
409{
410 return sendATCommandInternal(getNewFrameID(), cmd, params, paramsLen);
411}
412
413uint8_t Xbee::sendATCommandInternal(uint8_t txFrameId, const char* cmd,
414 uint8_t* params, size_t paramsLen)
415{
416 // Build the AT command
417 ATCommandFrame at;
418 at.setATCommand(cmd);
419 at.setFrameID(txFrameId);
420 if (paramsLen > 0)
421 {
422 if (paramsLen > MAX_AT_COMMAND_PARAMS_LENGTH)
423 {
424 LOG_ERR(logger, "AT Command payload too large, it was truncated");
425 paramsLen = MAX_AT_COMMAND_PARAMS_LENGTH;
426 }
427 at.setParameterSize(paramsLen);
428 memcpy(at.getCommandDataPointer(), params, paramsLen);
429 }
430
431 at.calcChecksum();
432
433 // Send it
434 writeFrame(at);
435
436 return txFrameId;
437}
438
439void Xbee::handleFrame(APIFrame& frame)
440{
441 // Set the timestamp to the frame
442 frame.timestamp = Kernel::getOldTick();
443
444 switch (frame.frameType)
445 {
447 {
448 RXPacketFrame* pf = frame.toFrameType<RXPacketFrame>();
449 {
450 Lock<FastMutex> l(mutexRxFrames);
451
452 if (rxFramesBuffer.isFull())
453 ++status.rxDroppedBuffers;
454
455 rxFramesBuffer.put(*pf);
456
457 if (rxFramesBuffer.count() > status.frameBufMaxLength)
458 status.frameBufMaxLength = rxFramesBuffer.count();
459 }
460 wakeReceiver();
461 break;
462 }
464 {
465 ModemStatusFrame* ms = frame.toFrameType<ModemStatusFrame>();
466
467 switch (ms->getStatus())
468 {
470 LOG_DEBUG(logger, "Modem status: Hardware reset");
471 break;
473 LOG_DEBUG(logger, "Modem status: Watchdog timer reset");
474 break;
475 }
476 break;
477 }
478 case FTYPE_TX_STATUS:
479 {
480 TXStatusFrame* ts = frame.toFrameType<TXStatusFrame>();
481 status.lastTxStatus = ts->getDeliveryStatus();
482
483 if (status.lastTxStatus != DELS_SUCCESS)
484 status.lastTxStatusError = status.lastTxStatus;
485
486 switch (status.lastTxStatus)
487 {
488 case DELS_SUCCESS:
489 break;
490 default:
491 LOG_ERR(
492 logger, "TX Status Error: {} (retries: {}, RD: {})",
493 status.lastTxStatus, ts->getTransmitRetryCount(),
494 ts->getDiscoveryStatus() == 2 ? "Enabled" : "Disabled");
495 break;
496 }
497 break;
498 }
499 default:
500 break;
501 }
502
503 // Pass the frame to the listener
504 if (frameListener)
505 frameListener(frame);
506}
507
509{
510 frameListener = listener;
511}
512
513uint8_t Xbee::getNewFrameID()
514{
515 uint8_t txFrameId = frameIdCounter++;
516
517 // Any value != 0 implies that we DO want a tx status response
518 if (frameIdCounter == 0)
519 frameIdCounter = 1;
520
521 return txFrameId;
522}
523
524} // namespace Xbee
525
526} // namespace Boardcore
#define LOG_ERR(logger,...)
#define LOG_DEBUG(logger,...)
miosix::GpioPin GpioType
RAII Interface for SPI bus acquisition.
Interface for low level access of a SPI bus as a master.
virtual uint8_t read()=0
Reads 8 bits from the bus.
static StackLogger & getInstance()
Definition Singleton.h:52
StatsResult getStats() const
Return statistics of the elements added so far.
Definition Stats.cpp:71
void add(float data)
Definition Stats.cpp:47
ParseResult parse(uint8_t byte, APIFrame *frame)
Parses a single byte. When this function returns ParseResult:SUCESS, frame contains a valid APIFrame.
void wakeReceiver(bool forceReturn=false)
Wakes the receive function without needing an interrupt.
Definition Xbee.cpp:208
void setOnFrameReceivedListener(OnFrameReceivedListener listener)
Set the frame received listener, called each time a new APIFrame is received from the device.
Definition Xbee.cpp:508
ssize_t receive(uint8_t *buf, size_t bufferMaxSize) override
Waits until a new packet is received.
Definition Xbee.cpp:116
Xbee(SPIBusInterface &bus, GpioType cs, GpioType attn, GpioType rst, long long txTimeout=DEFAULT_TX_TIMEOUT)
Constructs a new instance of the Xbee driver.
Definition Xbee.cpp:43
void handleATTNInterrupt()
Signals the receive() function that there is new data available. Call this from the ATTN pin interrup...
Definition Xbee.cpp:220
std::function< void(APIFrame &frame)> OnFrameReceivedListener
Definition Xbee.h:61
void reset()
Hardware resets the Xbee.
Definition Xbee.cpp:170
void sendATCommand(const char *cmd, uint8_t *params=nullptr, size_t paramsLength=0)
Sends an AT Command to the Xbee (see datasheet) without waiting for a response.
Definition Xbee.cpp:370
XbeeStatus getStatus()
Definition Xbee.cpp:163
bool send(uint8_t *pkt, size_t packetLength) override
Sends a packet. The function blocks until the packet is sent to the peripheral, but does not wait for...
Definition Xbee.cpp:59
PrintLogger l
Definition CanDriver.cpp:41
long long getOldTick()
Get the current time in milliseconds.
Definition KernelTime.h:43
@ FTYPE_AT_COMMAND_RESPONSE
Definition APIFrames.h:72
APIFrameParser::ParseResult ParseResult
Definition Xbee.h:50
This file includes all the types the logdecoder script will decode.
SPI Bus configuration for a specific slave.
GpioType cs
Chip select pin.
SPIBusInterface & bus
Bus on which the slave is connected.
FrameType * toFrameType()
Definition APIFrames.h:184
uint16_t getRXDataLength() const
Definition APIFrames.h:438
uint8_t getDeliveryStatus() const
Definition APIFrames.h:393
unsigned int frameBufMaxLength
Definition XbeeStatus.h:51
unsigned int rxDroppedBuffers
Definition XbeeStatus.h:49