1
0
Fork 0

Linux socketCAN driver, fix CANsend().

There was a problem with block transfer, when transmitting sequence of
data. If CANsend() failed (socket buffer full), message was dropped.
Now dropped message is marked with bufferFull flag and then it is
re-transmitted later by the driver, as is in the original behaviour of
CANopenNode. Same as commit acd719e27 in v2.0.
This commit is contained in:
Janez 2021-02-08 17:01:55 +01:00
parent 3ee8725036
commit 88558203a0
10 changed files with 118 additions and 15 deletions

View file

@ -847,7 +847,7 @@ CO_SDO_return_t CO_SDOclientDownload(CO_SDOclient_t *SDO_C,
SDO_C->state = CO_SDO_ST_ABORT;
}
/* Timeout timers *********************************************************/
/* Timeout timers and transmit bufferFull flag ****************************/
if (ret == CO_SDO_RT_waitingResponse) {
if (SDO_C->timeoutTimer < SDO_C->SDOtimeoutTime_us) {
SDO_C->timeoutTimer += timeDifference_us;
@ -1547,7 +1547,7 @@ CO_SDO_return_t CO_SDOclientUpload(CO_SDOclient_t *SDO_C,
SDO_C->state = CO_SDO_ST_ABORT;
}
/* Timeout timers *********************************************************/
/* Timeout timers and transmit bufferFull flag ****************************/
if (ret == CO_SDO_RT_waitingResponse) {
if (SDO_C->timeoutTimer < SDO_C->SDOtimeoutTime_us) {
SDO_C->timeoutTimer += timeDifference_us;

View file

@ -1142,7 +1142,7 @@ CO_SDO_return_t CO_SDOserver_process(CO_SDOserver_t *SDO,
CO_FLAG_CLEAR(SDO->CANrxNew);
} /* if (isNew) */
/* Timeout timers *********************************************************/
/* Timeout timers and transmit bufferFull flag ****************************/
#if (CO_CONFIG_SDO_SRV) & CO_CONFIG_SDO_SRV_SEGMENTED
if (ret == CO_SDO_RT_waitingResponse) {
if (SDO->timeoutTimer < SDO->SDOtimeoutTime_us) {

View file

@ -1261,7 +1261,7 @@ CO_NMT_reset_cmd_t CO_process(CO_t *co,
uint32_t timeDifference_us,
uint32_t *timerNext_us)
{
(void) enableGateway; (void) timerNext_us; /* may be unused */
(void) enableGateway; /* may be unused */
CO_NMT_reset_cmd_t reset = CO_RESET_NOT;
CO_NMT_internalState_t NMTstate = CO_NMT_getInternalState(co->NMT);

View file

@ -47,15 +47,16 @@ SOURCES = \
OBJS = $(SOURCES:%.c=%.o)
CC ?= gcc
OPT =
OPT += -DCO_SINGLE_THREAD
OPT += -g
#OPT += -O2
OPT += -DCO_SINGLE_THREAD
#OPT += -DCO_CONFIG_DEBUG=0xFFFF
#OPT += -Wextra -Wshadow -pedantic -fanalyzer
#OPT += -DCO_USE_GLOBALS
#OPT += -DCO_MULTIPLE_OD
CFLAGS = -Wall $(OPT) $(INCLUDE_DIRS)
LDFLAGS =
#LDFLAGS += -g
LDFLAGS += -g
#LDFLAGS += -pthread
#Options can be also passed via make: 'make OPT="-g" LDFLAGS="-pthread"'

View file

@ -17,6 +17,9 @@ Dictionary and are accessible from both: C code and from CANopen network.
CANopenNode homepage is https://github.com/CANopenNode/CANopenNode
This is renewed version of CANopenNode with new Object Dictionary implementation.
For older versions see branches `v1.3-master` or `v2.0-master`.
Characteristics
---------------

View file

@ -180,7 +180,7 @@ Another interesting tool is [CANopen for Python](https://github.com/christiansan
Examples here worked in virtual CAN interface, for simplicity. Virtual CAN runs inside Linux kernel only, it does not have much practical usability. If one has real CAN network configuration, then above examples are suitable also for this network, if Linux machine is connected to it and CAN interface is properly configured. When connecting your devices to real CAN network, make sure, you have at least two devices communicating, connected with ground and pair of wires, terminated with two 120ohm resistors, correct baudrate, etc.
Accessing real CANopen devices is the same as described above for virtual CAN interface. Some tested USB to CAN interfaces, which are native in Linux kernel are:
- Simple serial [USBtin](http://www.fischl.de/usbtin/) - Start with: `sudo slcand -f -o -c -s8 /dev/ttyACM0 can0; sudo ip link set up can0`
- Simple serial [USBtin](http://www.fischl.de/usbtin/) - Start with (-s5=250kbps): `sudo slcand -f -o -c -s5 /dev/ttyACM0 can0; sudo ip link set up can0`
- [EMS CPC-USB](https://www.ems-wuensche.com/?post_type=product&p=746) or [PCAN-USB FD](http://www.peak-system.com/PCAN-USB-FD.365.0.html?&L=1) - Start with: `sudo ip link set up can0 type can bitrate 250000`
- You can get the idea of other supported CAN interfaces in [Linux kernel source](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/drivers/net/can) (Kconfig files).
- Raspberry PI or similar has CAN capes available.

View file

@ -225,6 +225,8 @@ CO_ReturnError_t CO_CANmodule_init(
CANmodule->txSize = txSize;
CANmodule->CANerrorStatus = 0;
CANmodule->CANnormal = false;
CANmodule->CANtxCount = 0;
#if CO_DRIVER_MULTI_INTERFACE > 0
for (i = 0; i < CO_CAN_MSG_SFF_MAX_COB_ID; i++) {
CANmodule->rxIdentToIndex[i] = CO_INVALID_COB_ID;
@ -572,6 +574,7 @@ CO_ReturnError_t CO_CANtxBuffer_setInterface(
}
#endif /* CO_DRIVER_MULTI_INTERFACE */
#if CO_DRIVER_MULTI_INTERFACE > 0
/* send CAN message ***********************************************************/
static CO_ReturnError_t CO_CANCheckSendInterface(
@ -702,6 +705,68 @@ CO_ReturnError_t CO_CANCheckSend(CO_CANmodule_t *CANmodule, CO_CANtx_t *buffer)
return err;
}
#warning CO_CANsend() is outdated for CO_DRIVER_MULTI_INTERFACE > 0
#endif /* CO_DRIVER_MULTI_INTERFACE > 0 */
#if CO_DRIVER_MULTI_INTERFACE == 0
/* Change handling of tx buffer full in CO_CANsend(). Use CO_CANtx_t->bufferFull
* flag. Re-transmit undelivered message inside CO_CANmodule_process(). */
CO_ReturnError_t CO_CANsend(CO_CANmodule_t *CANmodule, CO_CANtx_t *buffer)
{
CO_ReturnError_t err = CO_ERROR_NO;
if (CANmodule==NULL || buffer==NULL || CANmodule->CANinterfaceCount==0) {
return CO_ERROR_ILLEGAL_ARGUMENT;
}
CO_CANinterface_t *interface = &CANmodule->CANinterfaces[0];
if (interface == NULL || interface->fd < 0) {
return CO_ERROR_ILLEGAL_ARGUMENT;
}
/* Verify overflow */
if(buffer->bufferFull){
#if CO_DRIVER_ERROR_REPORTING > 0
interface->errorhandler.CANerrorStatus |= CO_CAN_ERRTX_OVERFLOW;
#endif
log_printf(LOG_ERR, DBG_CAN_TX_FAILED, buffer->ident, interface->ifName);
err = CO_ERROR_TX_OVERFLOW;
}
errno = 0;
ssize_t n = send(interface->fd, buffer, CAN_MTU, MSG_DONTWAIT);
if (errno == 0 && n == CAN_MTU) {
/* success */
if (buffer->bufferFull) {
buffer->bufferFull = false;
CANmodule->CANtxCount--;
}
}
else if (errno == EINTR || errno == EAGAIN || errno == ENOBUFS) {
/* Send failed, message will be re-sent by CO_CANmodule_process() */
if (!buffer->bufferFull) {
buffer->bufferFull = true;
CANmodule->CANtxCount++;
}
err = CO_ERROR_TX_BUSY;
}
else {
/* Unknown error */
log_printf(LOG_DEBUG, DBG_ERRNO, "send()");
#if CO_DRIVER_ERROR_REPORTING > 0
interface->errorhandler.CANerrorStatus |= CO_CAN_ERRTX_OVERFLOW;
#endif
err = CO_ERROR_SYSCALL;
}
return err;
}
#endif /* CO_DRIVER_MULTI_INTERFACE == 0 */
/******************************************************************************/
void CO_CANclearPendingSyncPDOs(CO_CANmodule_t *CANmodule)
@ -714,18 +779,41 @@ void CO_CANclearPendingSyncPDOs(CO_CANmodule_t *CANmodule)
/******************************************************************************/
void CO_CANmodule_process(CO_CANmodule_t *CANmodule)
{
if (CANmodule == NULL || CANmodule->CANinterfaceCount == 0) return;
#if CO_DRIVER_ERROR_REPORTING > 0
/* socketCAN doesn't support microcontroller-like error counters. If an
* error has occured, a special can message is created by the driver and
* received by the application like a regular message.
* Therefore, error counter evaluation is included in rx function.
* Here we just copy evaluated CANerrorStatus from the first CAN interface. */
#if CO_DRIVER_ERROR_REPORTING > 0
if (CANmodule->CANinterfaceCount > 0) {
CANmodule->CANerrorStatus =
CANmodule->CANinterfaces[0].errorhandler.CANerrorStatus;
}
CANmodule->CANerrorStatus =
CANmodule->CANinterfaces[0].errorhandler.CANerrorStatus;
#endif
#if CO_DRIVER_MULTI_INTERFACE == 0
/* recall CO_CANsend(), if message was unsent before */
if (CANmodule->CANtxCount > 0) {
bool_t found = false;
for (uint16_t i = 0; i < CANmodule->txSize; i++) {
CO_CANtx_t *buffer = &CANmodule->txArray[i];
if (buffer->bufferFull) {
buffer->bufferFull = false;
CANmodule->CANtxCount--;
CO_CANsend(CANmodule, buffer);
found = true;
break;
}
}
if (!found) {
CANmodule->CANtxCount = 0;
}
}
#endif /* CO_DRIVER_MULTI_INTERFACE == 0 */
}

View file

@ -306,7 +306,7 @@ typedef struct {
uint8_t DLC;
uint8_t padding[3]; /* ensure alignment */
uint8_t data[8];
volatile bool_t bufferFull; /* not used */
volatile bool_t bufferFull;
volatile bool_t syncFlag; /* info about transmit message */
int can_ifindex; /* CAN Interface index to use */
} CO_CANtx_t;
@ -346,6 +346,7 @@ typedef struct {
uint16_t txSize;
uint16_t CANerrorStatus;
volatile bool_t CANnormal;
volatile uint16_t CANtxCount;
int epoll_fd; /* File descriptor for epoll, which waits for
CAN receive event */
#if CO_DRIVER_MULTI_INTERFACE > 0 || defined CO_DOXYGEN

View file

@ -50,6 +50,11 @@
#endif
#endif /* (CO_CONFIG_GTW) & CO_CONFIG_GTW_ASCII */
/* delay for recall CANsend(), if CAN TX buffer is full */
#ifndef CANSEND_DELAY_US
#define CANSEND_DELAY_US 100
#endif
/* EPOLL **********************************************************************/
/* Helper function - get monotonic clock time in microseconds */
@ -297,6 +302,11 @@ void CO_epoll_processMain(CO_epoll_t *ep,
enableGateway,
ep->timeDifference_us,
&ep->timerNext_us);
/* If there are unsent CAN messages, call CO_CANmodule_process() earlier */
if (co->CANmodule->CANtxCount > 0 && ep->timerNext_us > CANSEND_DELAY_US) {
ep->timerNext_us = CANSEND_DELAY_US;
}
}

View file

@ -41,7 +41,7 @@ extern "C" {
#define CAN_ERROR_FILTER_FAILED "(%s) Setting CAN Interface \"%s\" error filter failed", __func__
#define CAN_FILTER_FAILED "(%s) Setting CAN Interface \"%s\" message filter failed", __func__
#define CAN_NAMETOINDEX "CAN Interface \"%s\" -> Index %d"
#define CAN_SOCKET_BUF_SIZE "CAN Interface \"%s\" Buffer set to %d messages (%d Bytes)"
#define CAN_SOCKET_BUF_SIZE "CAN Interface \"%s\" RX buffer set to %d messages (%d Bytes)"
#define CAN_RX_SOCKET_QUEUE_OVERFLOW "CAN Interface \"%s\" has lost %d messages"
#define CAN_BUSOFF "CAN Interface \"%s\" changed to \"Bus Off\". Switching to Listen Only mode..."
#define CAN_NOACK "CAN Interface \"%s\" no \"ACK\" received. Switching to Listen Only mode..."
@ -60,7 +60,7 @@ extern "C" {
#define DBG_GENERAL "(%s) Error: %s%d", __func__
#define DBG_ERRNO "(%s) OS error \"%s\" in %s", __func__, strerror(errno)
#define DBG_CO_DEBUG "(%s) CO_DEBUG: %s", __func__
#define DBG_CAN_TX_FAILED "(%s) Transmitting CAN msg OID 0x%08x failed(%s)", __func__
#define DBG_CAN_TX_FAILED "(%s) Transmitting CAN msg OID 0x%03x failed(%s)", __func__
#define DBG_CAN_RX_PARAM_FAILED "(%s) Setting CAN rx buffer failed (%s)", __func__
#define DBG_CAN_RX_FAILED "(%s) Receiving CAN msg failed (%s)", __func__
#define DBG_CAN_ERROR_GENERAL "(%s) Socket error msg ID: 0x%08x, Data[0..7]: 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x (%s)", __func__