mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-10-22 09:53:33 +08:00
422 lines
11 KiB
C++
422 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
|
|
*/
|
|
|
|
#include <uavcan_lpc11c24/can.hpp>
|
|
#include <uavcan_lpc11c24/clock.hpp>
|
|
#include <uavcan/util/templates.hpp>
|
|
#include <chip.h>
|
|
#include "internal.hpp"
|
|
|
|
/**
|
|
* The default value should be OK for any use case.
|
|
*/
|
|
#ifndef UAVCAN_LPC11C24_RX_QUEUE_LEN
|
|
# define UAVCAN_LPC11C24_RX_QUEUE_LEN 8
|
|
#endif
|
|
|
|
#if UAVCAN_LPC11C24_RX_QUEUE_LEN > 254
|
|
# error UAVCAN_LPC11C24_RX_QUEUE_LEN is too large
|
|
#endif
|
|
|
|
extern "C" void canRxCallback(uint8_t msg_obj_num);
|
|
extern "C" void canTxCallback(uint8_t msg_obj_num);
|
|
extern "C" void canErrorCallback(uint32_t error_info);
|
|
|
|
namespace uavcan_lpc11c24
|
|
{
|
|
namespace
|
|
{
|
|
/**
|
|
* Hardware message objects are allocated as follows:
|
|
* - 1 - Single TX object
|
|
* - 2..32 - RX objects
|
|
* TX priority is defined by the message object number, not by the CAN ID (chapter 16.7.3.5 of the user manual),
|
|
* hence we can't use more than one object because that would cause priority inversion on long transfers.
|
|
*/
|
|
const unsigned NumMsgObjects = 32;
|
|
|
|
/**
|
|
* Total number of CAN errors.
|
|
* Does not overflow.
|
|
*/
|
|
uint32_t error_cnt;
|
|
|
|
/**
|
|
* True if there's no pending TX frame, i.e. write is possible.
|
|
*/
|
|
bool tx_free = true;
|
|
|
|
/**
|
|
* Gets updated every time the CAN IRQ handler is being called.
|
|
*/
|
|
uint64_t last_irq_utc_timestamp = 0;
|
|
|
|
bool had_activity;
|
|
|
|
/**
|
|
* After a received message gets extracted from C_CAN, it will be stored in the RX queue until libuavcan
|
|
* reads it via select()/receive() calls.
|
|
*/
|
|
class RxQueue
|
|
{
|
|
struct Item
|
|
{
|
|
uint64_t utc_usec;
|
|
uavcan::CanFrame frame;
|
|
Item() : utc_usec(0) { }
|
|
};
|
|
|
|
Item buf_[UAVCAN_LPC11C24_RX_QUEUE_LEN];
|
|
uint32_t overflow_cnt_;
|
|
uint8_t in_;
|
|
uint8_t out_;
|
|
uint8_t len_;
|
|
|
|
public:
|
|
RxQueue()
|
|
: overflow_cnt_(0)
|
|
, in_(0)
|
|
, out_(0)
|
|
, len_(0)
|
|
{ }
|
|
|
|
void push(const uavcan::CanFrame& frame, const uint64_t& utc_usec)
|
|
{
|
|
buf_[in_].frame = frame;
|
|
buf_[in_].utc_usec = utc_usec;
|
|
in_++;
|
|
if (in_ >= UAVCAN_LPC11C24_RX_QUEUE_LEN)
|
|
{
|
|
in_ = 0;
|
|
}
|
|
len_++;
|
|
if (len_ > UAVCAN_LPC11C24_RX_QUEUE_LEN)
|
|
{
|
|
len_ = UAVCAN_LPC11C24_RX_QUEUE_LEN;
|
|
if (overflow_cnt_ < 0xFFFFFFFF)
|
|
{
|
|
overflow_cnt_++;
|
|
}
|
|
out_++;
|
|
if (out_ >= UAVCAN_LPC11C24_RX_QUEUE_LEN)
|
|
{
|
|
out_ = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void pop(uavcan::CanFrame& out_frame, uint64_t& out_utc_usec)
|
|
{
|
|
if (len_ > 0)
|
|
{
|
|
out_frame = buf_[out_].frame;
|
|
out_utc_usec = buf_[out_].utc_usec;
|
|
out_++;
|
|
if (out_ >= UAVCAN_LPC11C24_RX_QUEUE_LEN)
|
|
{
|
|
out_ = 0;
|
|
}
|
|
len_--;
|
|
}
|
|
}
|
|
|
|
unsigned getLength() const { return len_; }
|
|
|
|
uint32_t getOverflowCount() const { return overflow_cnt_; }
|
|
};
|
|
|
|
RxQueue rx_queue;
|
|
|
|
|
|
int computeBaudrate(uint32_t baud_rate, uint32_t can_api_timing_cfg[2])
|
|
{
|
|
const uint32_t pclk = Chip_Clock_GetMainClockRate();
|
|
const uint32_t clk_per_bit = pclk / baud_rate;
|
|
for (uint32_t div = 0; div <= 15; div++)
|
|
{
|
|
for (uint32_t quanta = 1; quanta <= 32; quanta++)
|
|
{
|
|
for (uint32_t segs = 3; segs <= 17; segs++)
|
|
{
|
|
if (clk_per_bit == (segs * quanta * (div + 1)))
|
|
{
|
|
segs -= 3;
|
|
const uint32_t seg1 = segs / 2;
|
|
const uint32_t seg2 = segs - seg1;
|
|
const uint32_t can_sjw = (seg1 > 3) ? 3 : seg1;
|
|
can_api_timing_cfg[0] = div;
|
|
can_api_timing_cfg[1] = ((quanta - 1) & 0x3F) |
|
|
(can_sjw & 0x03) << 6 |
|
|
(seg1 & 0x0F) << 8 |
|
|
(seg2 & 0x07) << 12;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
CanDriver CanDriver::self;
|
|
|
|
int CanDriver::init(uavcan::uint32_t baudrate)
|
|
{
|
|
CriticalSectionLocker locker;
|
|
|
|
error_cnt = 0;
|
|
tx_free = true;
|
|
last_irq_utc_timestamp = 0;
|
|
had_activity = false;
|
|
|
|
/*
|
|
* C_CAN init
|
|
*/
|
|
Chip_Clock_EnablePeriphClock(SYSCTL_CLOCK_CAN);
|
|
static uint32_t can_api_init_table[2];
|
|
if (computeBaudrate(baudrate, can_api_init_table) != 0)
|
|
{
|
|
return -1;
|
|
}
|
|
LPC_CCAN_API->init_can(can_api_init_table, true);
|
|
static CCAN_CALLBACKS_T ccan_callbacks =
|
|
{
|
|
canRxCallback,
|
|
canTxCallback,
|
|
canErrorCallback,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0
|
|
};
|
|
LPC_CCAN_API->config_calb(&ccan_callbacks);
|
|
NVIC_EnableIRQ(CAN_IRQn);
|
|
|
|
/*
|
|
* Default RX msgobj config:
|
|
* 31 - all STD
|
|
* 32 - all EXT
|
|
* RTR ignored
|
|
*/
|
|
CCAN_MSG_OBJ_T msg_obj = CCAN_MSG_OBJ_T();
|
|
msg_obj.msgobj = 31;
|
|
LPC_CCAN_API->config_rxmsgobj(&msg_obj);
|
|
msg_obj.mode_id = CAN_MSGOBJ_EXT;
|
|
msg_obj.msgobj = 32;
|
|
LPC_CCAN_API->config_rxmsgobj(&msg_obj);
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool CanDriver::hasReadyRx() const
|
|
{
|
|
CriticalSectionLocker locker;
|
|
return rx_queue.getLength() > 0;
|
|
}
|
|
|
|
bool CanDriver::hasEmptyTx() const
|
|
{
|
|
CriticalSectionLocker locker;
|
|
return tx_free;
|
|
}
|
|
|
|
bool CanDriver::hadActivity()
|
|
{
|
|
CriticalSectionLocker locker;
|
|
const bool ret = had_activity;
|
|
had_activity = false;
|
|
return ret;
|
|
}
|
|
|
|
uavcan::int16_t CanDriver::send(const uavcan::CanFrame& frame, uavcan::MonotonicTime tx_deadline,
|
|
uavcan::CanIOFlags flags)
|
|
{
|
|
if (frame.isErrorFrame() ||
|
|
frame.dlc > 8 ||
|
|
flags != 0) // Only plain IO is allowed. Loopback, TX timestamping are not supported by this driver.
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Frame conversion
|
|
*/
|
|
CCAN_MSG_OBJ_T msgobj = CCAN_MSG_OBJ_T();
|
|
msgobj.mode_id = frame.id & uavcan::CanFrame::MaskExtID;
|
|
if (frame.isExtended())
|
|
{
|
|
msgobj.mode_id |= CAN_MSGOBJ_EXT;
|
|
}
|
|
if (frame.isRemoteTransmissionRequest())
|
|
{
|
|
msgobj.mode_id |= CAN_MSGOBJ_RTR;
|
|
}
|
|
msgobj.dlc = frame.dlc;
|
|
uavcan::copy(frame.data, frame.data + frame.dlc, msgobj.data);
|
|
|
|
/*
|
|
* Transmission
|
|
*/
|
|
(void)tx_deadline; // TX timeouts are not supported by this driver yet (and hardly going to be).
|
|
|
|
CriticalSectionLocker locker;
|
|
|
|
if (tx_free)
|
|
{
|
|
tx_free = false; // Mark as pending - will be released in TX callback
|
|
msgobj.msgobj = 1;
|
|
LPC_CCAN_API->can_transmit(&msgobj);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uavcan::int16_t CanDriver::receive(uavcan::CanFrame& out_frame, uavcan::MonotonicTime& out_ts_monotonic,
|
|
uavcan::UtcTime& out_ts_utc, uavcan::CanIOFlags& out_flags)
|
|
{
|
|
out_ts_monotonic = uavcan_lpc11c24::clock::getMonotonic();
|
|
out_flags = 0; // We don't support any IO flags
|
|
|
|
CriticalSectionLocker locker;
|
|
if (rx_queue.getLength() == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
uint64_t ts_utc = 0;
|
|
rx_queue.pop(out_frame, ts_utc);
|
|
out_ts_utc = uavcan::UtcTime::fromUSec(ts_utc);
|
|
return 1;
|
|
}
|
|
|
|
uavcan::int16_t CanDriver::select(uavcan::CanSelectMasks& inout_masks, uavcan::MonotonicTime blocking_deadline)
|
|
{
|
|
const bool noblock = ((inout_masks.read == 1) && hasReadyRx()) ||
|
|
((inout_masks.write == 1) && hasEmptyTx());
|
|
|
|
if (!noblock && (clock::getMonotonic() > blocking_deadline))
|
|
{
|
|
#if defined(UAVCAN_LPC11C24_USE_WFE) && UAVCAN_LPC11C24_USE_WFE
|
|
/*
|
|
* It's not cool (literally) to burn cycles in a busyloop, and we have no OS to pass control to other
|
|
* tasks, thus solution is to halt the core until a hardware event occurs - e.g. clock timer overflow.
|
|
* Upon such event the select() call will return, even if no requested IO operations became available.
|
|
* It's OK to do that, libuavcan can handle such behavior.
|
|
*
|
|
* Note that it is not possible to precisely control the sleep duration with WFE, since we can't predict when
|
|
* the next hardware event occurs. Worst case conditions:
|
|
* - WFE gets executed right after the clock timer interrupt;
|
|
* - CAN bus is completely silent (no traffic);
|
|
* - User's application has no interrupts and generates no hardware events.
|
|
* In such scenario execution will stuck here for one period of the clock timer interrupt, even if
|
|
* blocking_deadline expires sooner.
|
|
* If the user's application requires higher timing precision, an extra dummy IRQ can be added just to
|
|
* break WFE every once in a while.
|
|
*/
|
|
__WFE();
|
|
#endif
|
|
}
|
|
|
|
inout_masks.read = hasReadyRx() ? 1 : 0;
|
|
inout_masks.write = hasEmptyTx() ? 1 : 0;
|
|
return 0; // Return value doesn't matter as long as it is non-negative
|
|
}
|
|
|
|
uavcan::int16_t CanDriver::configureFilters(const uavcan::CanFilterConfig* filter_configs,
|
|
uavcan::uint16_t num_configs)
|
|
{
|
|
(void)filter_configs;
|
|
(void)num_configs;
|
|
return -1;
|
|
}
|
|
|
|
uavcan::uint64_t CanDriver::getErrorCount() const
|
|
{
|
|
CriticalSectionLocker locker;
|
|
return uint64_t(error_cnt) + uint64_t(rx_queue.getOverflowCount());
|
|
}
|
|
|
|
uavcan::uint16_t CanDriver::getNumFilters() const
|
|
{
|
|
return NumMsgObjects - 1; // First msgobj is reserved for TX frame
|
|
}
|
|
|
|
uavcan::ICanIface* CanDriver::getIface(uavcan::uint8_t iface_index)
|
|
{
|
|
return (iface_index == 0) ? this : NULL;
|
|
}
|
|
|
|
uavcan::uint8_t CanDriver::getNumIfaces() const
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* C_CAN handlers
|
|
*/
|
|
extern "C"
|
|
{
|
|
|
|
void canRxCallback(uint8_t msg_obj_num)
|
|
{
|
|
CCAN_MSG_OBJ_T msg_obj = CCAN_MSG_OBJ_T();
|
|
msg_obj.msgobj = msg_obj_num;
|
|
LPC_CCAN_API->can_receive(&msg_obj);
|
|
|
|
uavcan::CanFrame frame;
|
|
|
|
// CAN ID, EXT or not
|
|
if (msg_obj.mode_id & CAN_MSGOBJ_EXT)
|
|
{
|
|
frame.id = msg_obj.mode_id & uavcan::CanFrame::MaskExtID;
|
|
frame.id |= uavcan::CanFrame::FlagEFF;
|
|
}
|
|
else
|
|
{
|
|
frame.id = msg_obj.mode_id & uavcan::CanFrame::MaskStdID;
|
|
}
|
|
|
|
// RTR
|
|
if (msg_obj.mode_id & CAN_MSGOBJ_RTR)
|
|
{
|
|
frame.id |= uavcan::CanFrame::FlagRTR;
|
|
}
|
|
|
|
// Payload
|
|
frame.dlc = msg_obj.dlc;
|
|
uavcan::copy(msg_obj.data, msg_obj.data + msg_obj.dlc, frame.data);
|
|
|
|
uavcan_lpc11c24::rx_queue.push(frame, uavcan_lpc11c24::last_irq_utc_timestamp);
|
|
uavcan_lpc11c24::had_activity = true;
|
|
}
|
|
|
|
void canTxCallback(uint8_t msg_obj_num)
|
|
{
|
|
(void)msg_obj_num;
|
|
uavcan_lpc11c24::tx_free = true;
|
|
uavcan_lpc11c24::had_activity = true;
|
|
}
|
|
|
|
void canErrorCallback(uint32_t error_info)
|
|
{
|
|
(void)error_info;
|
|
if (uavcan_lpc11c24::error_cnt < 0xFFFFFFFF)
|
|
{
|
|
uavcan_lpc11c24::error_cnt++;
|
|
}
|
|
}
|
|
|
|
void CAN_IRQHandler();
|
|
|
|
void CAN_IRQHandler()
|
|
{
|
|
uavcan_lpc11c24::last_irq_utc_timestamp = uavcan_lpc11c24::clock::getUtcUSecFromCanInterrupt();
|
|
LPC_CCAN_API->isr();
|
|
}
|
|
|
|
}
|