examples: add Lely CANopen master example

add Lely CANopen master example

Signed-off-by: raiden00pl <raiden00@railab.me>
This commit is contained in:
raiden00pl 2026-07-05 09:52:09 +02:00 committed by Alan C. Assis
parent caa7d7faa5
commit 962d90bb6e
10 changed files with 1702 additions and 0 deletions

View file

@ -0,0 +1,53 @@
# ##############################################################################
# apps/examples/lely_master/CMakeLists.txt
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more contributor
# license agreements. See the NOTICE file distributed with this work for
# additional information regarding copyright ownership. The ASF licenses this
# file to you under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
#
# ##############################################################################
if(CONFIG_EXAMPLES_LELYMASTER)
set(LELY_INCDIR ${NUTTX_APPS_DIR}/canutils/lely-canopen/lely-core/include)
set(SRCS master_main.c sdev_master.c)
if(CONFIG_EXAMPLES_LELYMASTER_CHARDEV)
list(APPEND SRCS candev_char.c)
endif()
if(CONFIG_EXAMPLES_LELYMASTER_SOCKET)
list(APPEND SRCS candev_sock.c)
endif()
nuttx_add_application(
NAME
comaster
PRIORITY
${CONFIG_EXAMPLES_LELYMASTER_PRIORITY}
STACKSIZE
${CONFIG_EXAMPLES_LELYMASTER_STACKSIZE}
MODULE
${CONFIG_EXAMPLES_LELYMASTER}
COMPILE_FLAGS
-Wno-undef
INCLUDE_DIRECTORIES
${LELY_INCDIR}
SRCS
${SRCS})
endif()

View file

@ -0,0 +1,50 @@
#
# For a description of the syntax of this configuration file,
# see the file kconfig-language.txt in the NuttX tools repository.
#
config EXAMPLES_LELYMASTER
tristate "Lely CANopen master example"
default n
depends on CANUTILS_LELYCANOPEN && CANUTILS_LELYCANOPEN_SDEV
depends on CANUTILS_LELYCANOPEN_TIME && CANUTILS_LELYCANOPEN_OBJUPLOAD
depends on CANUTILS_LELYCANOPEN_MASTER
---help---
Enable the Lely CANopen master (comaster) example application.
if EXAMPLES_LELYMASTER
choice
prompt "CAN backend"
default EXAMPLES_LELYMASTER_CHARDEV
config EXAMPLES_LELYMASTER_CHARDEV
bool "CAN character driver"
depends on CAN
config EXAMPLES_LELYMASTER_SOCKET
bool "SocketCAN"
depends on NET_CAN
select NETUTILS_NETLIB
endchoice
config EXAMPLES_LELYMASTER_DEVPATH
string "CAN character device path"
default "/dev/can0"
depends on EXAMPLES_LELYMASTER_CHARDEV
config EXAMPLES_LELYMASTER_INTF
string "SocketCAN interface"
default "can0"
depends on EXAMPLES_LELYMASTER_SOCKET
config EXAMPLES_LELYMASTER_PRIORITY
int "Task priority"
default 100
config EXAMPLES_LELYMASTER_STACKSIZE
int "Stack size"
default DEFAULT_TASK_STACKSIZE
endif

View file

@ -0,0 +1,25 @@
############################################################################
# apps/examples/lely_master/Make.defs
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
ifneq ($(CONFIG_EXAMPLES_LELYMASTER),)
CONFIGURED_APPS += $(APPDIR)/examples/lely_master
endif

View file

@ -0,0 +1,47 @@
############################################################################
# apps/examples/lely_master/Makefile
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
include $(APPDIR)/Make.defs
PROGNAME = comaster
PRIORITY = $(CONFIG_EXAMPLES_LELYMASTER_PRIORITY)
STACKSIZE = $(CONFIG_EXAMPLES_LELYMASTER_STACKSIZE)
MODULE = $(CONFIG_EXAMPLES_LELYMASTER)
CFLAGS += -DHAVE_CONFIG_H=1
# Lely headers use bare "#if __cplusplus"; silence -Wundef like the library
CFLAGS += -Wno-undef
CSRCS = sdev_master.c
ifeq ($(CONFIG_EXAMPLES_LELYMASTER_CHARDEV),y)
CSRCS += candev_char.c
endif
ifeq ($(CONFIG_EXAMPLES_LELYMASTER_SOCKET),y)
CSRCS += candev_sock.c
endif
MAINSRC = master_main.c
include $(APPDIR)/Application.mk

View file

@ -0,0 +1,44 @@
/****************************************************************************
* apps/examples/lely_master/candev.h
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef __APPS_EXAMPLES_LELY_MASTER_CANDEV_H
#define __APPS_EXAMPLES_LELY_MASTER_CANDEV_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <canutils/lely/config.h>
#include <lely/can/msg.h>
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
int comaster_candev_init(void);
int comaster_candev_send(FAR const struct can_msg *msg);
int comaster_candev_recv(FAR struct can_msg *msg);
#endif /* __APPS_EXAMPLES_LELY_MASTER_CANDEV_H */

View file

@ -0,0 +1,173 @@
/****************************************************************************
* apps/examples/lely_master/candev_char.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <nuttx/can/can.h>
#include "candev.h"
/****************************************************************************
* Private Data
****************************************************************************/
static int g_candev_fd;
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: comaster_candev_init
****************************************************************************/
int comaster_candev_init(void)
{
struct canioc_bittiming_s bt;
int ret;
/* Non-blocking so the main loop keeps servicing the CANopen timers */
g_candev_fd = open(CONFIG_EXAMPLES_LELYMASTER_DEVPATH,
O_RDWR | O_NONBLOCK);
if (g_candev_fd < 0)
{
ret = -errno;
printf("ERROR: open %s failed %d\n",
CONFIG_EXAMPLES_LELYMASTER_DEVPATH, errno);
return ret;
}
ret = ioctl(g_candev_fd, CANIOC_GET_BITTIMING, &bt);
if (ret < 0)
{
printf("Bit timing not available: %d\n", errno);
}
else
{
printf("Bit timing:\n");
printf(" Baud: %lu\n", (unsigned long)bt.bt_baud);
printf(" TSEG1: %u\n", bt.bt_tseg1);
printf(" TSEG2: %u\n", bt.bt_tseg2);
printf(" SJW: %u\n", bt.bt_sjw);
}
return OK;
}
/****************************************************************************
* Name: comaster_candev_send
****************************************************************************/
int comaster_candev_send(FAR const struct can_msg *msg)
{
struct can_msg_s txmsg;
ssize_t nwritten;
int i;
txmsg.cm_hdr.ch_id = msg->id;
txmsg.cm_hdr.ch_rtr = (msg->flags & CAN_FLAG_RTR) != 0;
txmsg.cm_hdr.ch_dlc = msg->len;
#ifdef CONFIG_CAN_EXTID
txmsg.cm_hdr.ch_extid = (msg->flags & CAN_FLAG_IDE) != 0;
#endif
#ifdef CONFIG_CAN_ERRORS
txmsg.cm_hdr.ch_error = 0;
#endif
txmsg.cm_hdr.ch_tcf = 0;
for (i = 0; i < msg->len; i++)
{
txmsg.cm_data[i] = msg->data[i];
}
nwritten = write(g_candev_fd, &txmsg, CAN_MSGLEN(txmsg.cm_hdr.ch_dlc));
if (nwritten != CAN_MSGLEN(txmsg.cm_hdr.ch_dlc))
{
return -1;
}
return 0;
}
/****************************************************************************
* Name: comaster_candev_recv
****************************************************************************/
int comaster_candev_recv(FAR struct can_msg *msg)
{
struct can_msg_s rxmsg;
ssize_t nread;
int i;
nread = read(g_candev_fd, &rxmsg, sizeof(struct can_msg_s));
if (nread < 0)
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
return 0;
}
return -1;
}
if (nread < CAN_MSGLEN(0))
{
return 0;
}
msg->id = rxmsg.cm_hdr.ch_id;
msg->flags = 0;
#ifdef CONFIG_CAN_EXTID
if (rxmsg.cm_hdr.ch_extid)
{
msg->flags |= CAN_FLAG_IDE;
}
#endif
if (rxmsg.cm_hdr.ch_rtr)
{
msg->flags |= CAN_FLAG_RTR;
}
msg->len = rxmsg.cm_hdr.ch_dlc;
for (i = 0; i < rxmsg.cm_hdr.ch_dlc; i++)
{
msg->data[i] = rxmsg.cm_data[i];
}
return 1;
}

View file

@ -0,0 +1,197 @@
/****************************************************************************
* apps/examples/lely_master/candev_sock.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/socket.h>
#include <nuttx/can.h>
#include "netutils/netlib.h"
#include "candev.h"
/****************************************************************************
* Private Data
****************************************************************************/
static int g_candev_sock;
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: comaster_candev_init
****************************************************************************/
int comaster_candev_init(void)
{
struct sockaddr_can addr;
struct ifreq ifr;
int on = 1;
int ret;
g_candev_sock = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (g_candev_sock < 0)
{
ret = -errno;
printf("ERROR: socket failed %d\n", errno);
return ret;
}
strlcpy(ifr.ifr_name, CONFIG_EXAMPLES_LELYMASTER_INTF, IFNAMSIZ);
/* SocketCAN silently drops traffic while the interface is down, so bring
* it up here. This lets the example run without a separate "ifup" step.
*/
if (netlib_ifup(ifr.ifr_name) < 0)
{
ret = -errno;
printf("ERROR: netlib_ifup %s failed %d\n", ifr.ifr_name, errno);
goto errout;
}
ifr.ifr_ifindex = if_nametoindex(ifr.ifr_name);
if (ifr.ifr_ifindex == 0)
{
ret = -errno;
printf("ERROR: if_nametoindex %s failed %d\n", ifr.ifr_name, errno);
goto errout;
}
setsockopt(g_candev_sock, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS,
&on, sizeof(on));
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(g_candev_sock, (FAR struct sockaddr *)&addr, sizeof(addr)) < 0)
{
ret = -errno;
printf("ERROR: bind failed %d\n", errno);
goto errout;
}
ret = fcntl(g_candev_sock, F_SETFL, O_NONBLOCK);
if (ret < 0)
{
ret = -errno;
goto errout;
}
return OK;
errout:
close(g_candev_sock);
g_candev_sock = -1;
return ret;
}
/****************************************************************************
* Name: comaster_candev_send
****************************************************************************/
int comaster_candev_send(FAR const struct can_msg *msg)
{
struct can_frame frame;
ssize_t nwritten;
memset(&frame, 0, sizeof(frame));
frame.can_id = msg->id;
if (msg->flags & CAN_FLAG_IDE)
{
frame.can_id |= CAN_EFF_FLAG;
}
if (msg->flags & CAN_FLAG_RTR)
{
frame.can_id |= CAN_RTR_FLAG;
}
frame.can_dlc = msg->len;
memcpy(frame.data, msg->data, msg->len);
nwritten = write(g_candev_sock, &frame, sizeof(frame));
if (nwritten != sizeof(frame))
{
return -1;
}
return 0;
}
/****************************************************************************
* Name: comaster_candev_recv
****************************************************************************/
int comaster_candev_recv(FAR struct can_msg *msg)
{
struct can_frame frame;
ssize_t nread;
nread = read(g_candev_sock, &frame, sizeof(frame));
if (nread < 0)
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
return 0;
}
return -1;
}
if (nread < sizeof(frame))
{
return 0;
}
msg->id = frame.can_id & CAN_EFF_MASK;
msg->flags = 0;
if (frame.can_id & CAN_EFF_FLAG)
{
msg->flags |= CAN_FLAG_IDE;
}
if (frame.can_id & CAN_RTR_FLAG)
{
msg->flags |= CAN_FLAG_RTR;
}
msg->len = frame.can_dlc;
memcpy(msg->data, frame.data, frame.can_dlc);
return 1;
}

View file

@ -0,0 +1,358 @@
/****************************************************************************
* apps/examples/lely_master/master_main.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>
#include <unistd.h>
#include <canutils/lely/config.h>
#include <lely/co/csdo.h>
#include <lely/co/dev.h>
#include <lely/co/nmt.h>
#include <lely/co/sdev.h>
#include <lely/co/sdo.h>
#include <lely/co/time.h>
#include "sdev.h"
#include "candev.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define SLAVE_ID 0x02
#define OBJ_COUNTER 0x2100
/****************************************************************************
* Private Types
****************************************************************************/
enum master_phase_e
{
PHASE_WAIT, /* wait for the slave, then read its info via SDO */
PHASE_SDO, /* SDO request in flight */
PHASE_RUN /* network operational, streaming PDO data */
};
struct co_net_s
{
can_net_t *net;
co_dev_t *dev;
co_nmt_t *nmt;
};
/****************************************************************************
* Private Data
****************************************************************************/
static struct co_net_s g_net;
static bool g_sdo_done;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: elapsed_ms
****************************************************************************/
static long elapsed_ms(const struct timespec *a, const struct timespec *b)
{
return (a->tv_sec - b->tv_sec) * 1000 +
(a->tv_nsec - b->tv_nsec) / 1000000;
}
/****************************************************************************
* Name: on_can_send
****************************************************************************/
static int on_can_send(const struct can_msg *msg, void *data)
{
UNUSED(data);
return comaster_candev_send(msg);
}
/****************************************************************************
* Name: nmt_start_remote
****************************************************************************/
static void nmt_start_remote(co_unsigned8_t id)
{
struct can_msg msg;
/* NMT command frame: COB-ID 0x000, {command specifier, node-ID} */
memset(&msg, 0, sizeof(msg));
msg.len = 2;
msg.data[0] = CO_NMT_CS_START;
msg.data[1] = id;
comaster_candev_send(&msg);
}
/****************************************************************************
* Name: on_sdo_up
****************************************************************************/
static void on_sdo_up(co_csdo_t *sdo, co_unsigned16_t idx,
co_unsigned8_t subidx, co_unsigned32_t ac,
const void *ptr, size_t n, void *data)
{
co_unsigned32_t val;
UNUSED(sdo);
UNUSED(idx);
UNUSED(subidx);
UNUSED(data);
if (ac == 0 && n >= sizeof(co_unsigned32_t))
{
memcpy(&val, ptr, sizeof(val));
printf("SDO: slave device type (0x1000) = 0x%08x\n", (unsigned)val);
}
else
{
printf("SDO: read failed, abort code 0x%08x\n", (unsigned)ac);
}
g_sdo_done = true;
}
/****************************************************************************
* Name: on_time
****************************************************************************/
static void on_time(co_time_t *time, const struct timespec *tp, void *data)
{
UNUSED(time);
UNUSED(data);
/* Update the wall clock */
clock_settime(CLOCK_REALTIME, tp);
}
/****************************************************************************
* Name: on_nmt_cs
****************************************************************************/
static void on_nmt_cs(co_nmt_t *nmt, co_unsigned8_t cs, void *data)
{
UNUSED(data);
switch (cs)
{
case CO_NMT_CS_START:
case CO_NMT_CS_ENTER_PREOP:
{
co_time_set_ind(co_nmt_get_time(nmt), &on_time, NULL);
break;
}
case CO_NMT_CS_RESET_NODE:
{
exit(0);
break;
}
case CO_NMT_CS_STOP:
case CO_NMT_CS_RESET_COMM:
default:
{
break;
}
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: main
****************************************************************************/
int main(int argc, FAR char *argv[])
{
struct timespec now;
struct can_msg msg;
struct timespec t0;
co_unsigned32_t last = 0xffffffff;
int phase = PHASE_WAIT;
int ret;
co_csdo_t *csdo;
co_unsigned32_t v;
memset(&g_net, 0, sizeof(g_net));
g_sdo_done = false;
setvbuf(stdout, NULL, _IONBF, 0);
ret = comaster_candev_init();
if (ret < 0)
{
printf("ERROR: comaster_candev_init failed %d\n", ret);
goto errout;
}
g_net.net = can_net_create();
if (!g_net.net)
{
printf("ERROR: can_net_create failed\n");
goto errout;
}
can_net_set_send_func(g_net.net, &on_can_send, NULL);
clock_gettime(CLOCK_MONOTONIC, &now);
can_net_set_time(g_net.net, &now);
/* Create dev from struct */
g_net.dev = co_dev_create_from_sdev(&g_sdev_master);
if (!g_net.dev)
{
printf("ERROR: co_dev_create_from_sdev failed\n");
goto errout;
}
g_net.nmt = co_nmt_create(g_net.net, g_net.dev);
if (!g_net.nmt)
{
printf("ERROR: co_nmt_create failed\n");
goto errout;
}
/* Start the NMT service by resetting the node */
co_nmt_cs_ind(g_net.nmt, CO_NMT_CS_RESET_NODE);
/* Set the NMT indication function after the initial reset */
co_nmt_set_cs_ind(g_net.nmt, &on_nmt_cs, NULL);
printf("lely CANopen master running (node 0x%02x)\n", g_sdev_master.id);
clock_gettime(CLOCK_MONOTONIC, &t0);
while (1)
{
clock_gettime(CLOCK_MONOTONIC, &now);
can_net_set_time(g_net.net, &now);
while (comaster_candev_recv(&msg) > 0)
{
can_net_recv(g_net.net, &msg);
}
switch (phase)
{
/* Give the slave time to boot, then read its device type with
* a Client-SDO transfer (works while both nodes are pre-op).
*/
case PHASE_WAIT:
{
if (elapsed_ms(&now, &t0) >= 500)
{
csdo = co_nmt_get_csdo(g_net.nmt, 1);
if (csdo != NULL && co_csdo_up_req(
csdo, 0x1000, 0x00, &on_sdo_up, NULL) == 0)
{
phase = PHASE_SDO;
}
else
{
t0 = now;
}
}
break;
}
/* Once the SDO completes, bring the network to OPERATIONAL:
* the master itself and the slave (NMT start). PDO/SYNC then
* flow.
*/
case PHASE_SDO:
{
if (g_sdo_done)
{
co_nmt_cs_ind(g_net.nmt, CO_NMT_CS_START);
nmt_start_remote(SLAVE_ID);
printf("NMT: master + slave 0x%02x OPERATIONAL\n",
SLAVE_ID);
phase = PHASE_RUN;
}
break;
}
/* The slave's synchronous TPDO lands in object 0x2100 via the
* RPDO mapping; print it whenever it changes.
*/
case PHASE_RUN:
{
v = co_dev_get_val_u32(g_net.dev, OBJ_COUNTER, 0x00);
if (v != last)
{
printf("PDO rx: counter = %u\n", (unsigned)v);
last = v;
}
break;
}
}
usleep(1000);
}
errout:
if (g_net.nmt)
{
co_nmt_destroy(g_net.nmt);
}
if (g_net.dev)
{
co_dev_destroy(g_net.dev);
}
if (g_net.net)
{
can_net_destroy(g_net.net);
}
return 0;
}

View file

@ -0,0 +1,41 @@
/****************************************************************************
* apps/examples/lely_master/sdev.h
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef __APPS_EXAMPLES_LELY_SDEV_H
#define __APPS_EXAMPLES_LELY_SDEV_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <canutils/lely/config.h>
#include <lely/co/sdev.h>
/****************************************************************************
* Public data
****************************************************************************/
extern const struct co_sdev g_sdev_master;
#endif /* __APPS_EXAMPLES_LELY_SDEV_H */

View file

@ -0,0 +1,714 @@
/****************************************************************************
* apps/examples/lely_master/sdev_master.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include "sdev.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define CO_SDEV_STRING(s) s
/****************************************************************************
* Private Data
****************************************************************************/
/* Object 0x1000 sub-objects */
static const struct co_ssub g_1000_sub[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Device type"),
#endif
.subidx = 0x00,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = CO_UNSIGNED32_MIN,
#endif
.val.u32 = CO_UNSIGNED32_MIN,
.access = CO_ACCESS_RO,
.pdo_mapping = 0,
.flags = 0
}
};
/* Object 0x1001 sub-objects */
static const struct co_ssub g_1001_sub[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Error register"),
#endif
.subidx = 0x00,
.type = CO_DEFTYPE_UNSIGNED8,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u8 = CO_UNSIGNED8_MIN,
.max.u8 = CO_UNSIGNED8_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u8 = CO_UNSIGNED8_MIN,
#endif
.val.u8 = CO_UNSIGNED8_MIN,
.access = CO_ACCESS_RO,
.pdo_mapping = 0,
.flags = 0
}
};
/* Object 0x1005 sub-objects */
static const struct co_ssub g_1005_sub[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("COB-ID SYNC message"),
#endif
.subidx = 0x00,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = 0x40000080lu,
#endif
.val.u32 = 0x40000080lu,
.access = CO_ACCESS_RW,
.pdo_mapping = 0,
.flags = 0
}
};
/* Object 0x1006 sub-objects */
static const struct co_ssub g_1006_sub[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Communication cycle period"),
#endif
.subidx = 0x00,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = 0x000186a0lu,
#endif
.val.u32 = 0x000186a0lu,
.access = CO_ACCESS_RW,
.pdo_mapping = 0,
.flags = 0
}
};
/* Object 0x1012 sub-objects */
static const struct co_ssub g_1012_sub[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("COB-ID time stamp object"),
#endif
.subidx = 0x00,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = 0x80000100lu,
#endif
.val.u32 = 0x80000100lu,
.access = CO_ACCESS_RW,
.pdo_mapping = 0,
.flags = 0
}
};
/* Object 0x1017 sub-objects */
static const struct co_ssub g_1017_sub[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Producer heartbeat time"),
#endif
.subidx = 0x00,
.type = CO_DEFTYPE_UNSIGNED16,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u16 = CO_UNSIGNED16_MIN,
.max.u16 = CO_UNSIGNED16_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u16 = 0x0032,
#endif
.val.u16 = 0x0032,
.access = CO_ACCESS_RW,
.pdo_mapping = 0,
.flags = 0
}
};
/* Object 0x1018 sub-objects */
static const struct co_ssub g_1018_sub[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Highest sub-index supported"),
#endif
.subidx = 0x00,
.type = CO_DEFTYPE_UNSIGNED8,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u8 = CO_UNSIGNED8_MIN,
.max.u8 = CO_UNSIGNED8_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u8 = 0x04,
#endif
.val.u8 = 0x04,
.access = CO_ACCESS_CONST,
.pdo_mapping = 0,
.flags = 0
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Vendor-ID"),
#endif
.subidx = 0x01,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = 0x00000360lu,
#endif
.val.u32 = 0x00000360lu,
.access = CO_ACCESS_RO,
.pdo_mapping = 0,
.flags = 0
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Product code"),
#endif
.subidx = 0x02,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = CO_UNSIGNED32_MIN,
#endif
.val.u32 = CO_UNSIGNED32_MIN,
.access = CO_ACCESS_RO,
.pdo_mapping = 0,
.flags = 0
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Revision number"),
#endif
.subidx = 0x03,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = CO_UNSIGNED32_MIN,
#endif
.val.u32 = CO_UNSIGNED32_MIN,
.access = CO_ACCESS_RO,
.pdo_mapping = 0,
.flags = 0
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Serial number"),
#endif
.subidx = 0x04,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = CO_UNSIGNED32_MIN,
#endif
.val.u32 = CO_UNSIGNED32_MIN,
.access = CO_ACCESS_RO,
.pdo_mapping = 0,
.flags = 0
}
};
/* Object 0x1280 sub-objects */
static const struct co_ssub g_1280_sub[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Highest sub-index supported"),
#endif
.subidx = 0x00,
.type = CO_DEFTYPE_UNSIGNED8,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u8 = CO_UNSIGNED8_MIN,
.max.u8 = CO_UNSIGNED8_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u8 = 0x03,
#endif
.val.u8 = 0x03,
.access = CO_ACCESS_CONST,
.pdo_mapping = 0,
.flags = 0
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("COB-ID client to server"),
#endif
.subidx = 0x01,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = 0x00000602lu,
#endif
.val.u32 = 0x00000602lu,
.access = CO_ACCESS_RW,
.pdo_mapping = 0,
.flags = 0
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("COB-ID server to client"),
#endif
.subidx = 0x02,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = 0x00000582lu,
#endif
.val.u32 = 0x00000582lu,
.access = CO_ACCESS_RW,
.pdo_mapping = 0,
.flags = 0
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Node-ID of the SDO server"),
#endif
.subidx = 0x03,
.type = CO_DEFTYPE_UNSIGNED8,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u8 = CO_UNSIGNED8_MIN,
.max.u8 = CO_UNSIGNED8_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u8 = 0x02,
#endif
.val.u8 = 0x02,
.access = CO_ACCESS_RW,
.pdo_mapping = 0,
.flags = 0
}
};
/* Object 0x1400 sub-objects */
static const struct co_ssub g_1400_sub[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Highest sub-index supported"),
#endif
.subidx = 0x00,
.type = CO_DEFTYPE_UNSIGNED8,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u8 = CO_UNSIGNED8_MIN,
.max.u8 = CO_UNSIGNED8_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u8 = 0x02,
#endif
.val.u8 = 0x02,
.access = CO_ACCESS_CONST,
.pdo_mapping = 0,
.flags = 0
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("COB-ID"),
#endif
.subidx = 0x01,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = 0x00000182lu,
#endif
.val.u32 = 0x00000182lu,
.access = CO_ACCESS_RW,
.pdo_mapping = 0,
.flags = 0
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Transmission type"),
#endif
.subidx = 0x02,
.type = CO_DEFTYPE_UNSIGNED8,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u8 = CO_UNSIGNED8_MIN,
.max.u8 = CO_UNSIGNED8_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u8 = 0x01,
#endif
.val.u8 = 0x01,
.access = CO_ACCESS_RW,
.pdo_mapping = 0,
.flags = 0
}
};
/* Object 0x1600 sub-objects */
static const struct co_ssub g_1600_sub[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Number of mapped objects"),
#endif
.subidx = 0x00,
.type = CO_DEFTYPE_UNSIGNED8,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u8 = CO_UNSIGNED8_MIN,
.max.u8 = CO_UNSIGNED8_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u8 = 0x01,
#endif
.val.u8 = 0x01,
.access = CO_ACCESS_RW,
.pdo_mapping = 0,
.flags = 0
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Mapping 1"),
#endif
.subidx = 0x01,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = 0x21000020lu,
#endif
.val.u32 = 0x21000020lu,
.access = CO_ACCESS_RW,
.pdo_mapping = 0,
.flags = 0
}
};
/* Object 0x1f80 sub-objects */
static const struct co_ssub g_1f80_sub[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("NMT startup"),
#endif
.subidx = 0x00,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = CO_UNSIGNED32_MIN,
#endif
.val.u32 = 0x00000000lu,
.access = CO_ACCESS_RW,
.pdo_mapping = 0,
.flags = 0
| CO_OBJ_FLAGS_PARAMETER_VALUE
}
};
/* Object 0x2000 sub-objects */
static const struct co_ssub g_2000_sub[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Object with custom SDO download callback"),
#endif
.subidx = 0x00,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = CO_UNSIGNED32_MIN,
#endif
.val.u32 = CO_UNSIGNED32_MIN,
.access = CO_ACCESS_RW,
.pdo_mapping = 0,
.flags = 0
}
};
/* Object 0x2001 sub-objects */
static const struct co_ssub g_2001_sub[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Object with custom SDO upload callback"),
#endif
.subidx = 0x00,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = CO_UNSIGNED32_MIN,
#endif
.val.u32 = CO_UNSIGNED32_MIN,
.access = CO_ACCESS_RO,
.pdo_mapping = 0,
.flags = 0
}
};
/* Object 0x2100 sub-objects */
static const struct co_ssub g_2100_sub[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Counter"),
#endif
.subidx = 0x00,
.type = CO_DEFTYPE_UNSIGNED32,
#if !LELY_NO_CO_OBJ_LIMITS
.min.u32 = CO_UNSIGNED32_MIN,
.max.u32 = CO_UNSIGNED32_MAX,
#endif
#if !LELY_NO_CO_OBJ_DEFAULT
.def.u32 = CO_UNSIGNED32_MIN,
#endif
.val.u32 = CO_UNSIGNED32_MIN,
.access = CO_ACCESS_RW,
.pdo_mapping = 1,
.flags = 0
}
};
/* Object dictionary */
static const struct co_sobj g_g_sdev_master_objs[] =
{
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Device type"),
#endif
.idx = 0x1000,
.code = CO_OBJECT_VAR,
.nsub = 1,
.subs = g_1000_sub
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Error register"),
#endif
.idx = 0x1001,
.code = CO_OBJECT_VAR,
.nsub = 1,
.subs = g_1001_sub
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("COB-ID SYNC message"),
#endif
.idx = 0x1005,
.code = CO_OBJECT_VAR,
.nsub = 1,
.subs = g_1005_sub
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Communication cycle period"),
#endif
.idx = 0x1006,
.code = CO_OBJECT_VAR,
.nsub = 1,
.subs = g_1006_sub
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("COB-ID time stamp object"),
#endif
.idx = 0x1012,
.code = CO_OBJECT_VAR,
.nsub = 1,
.subs = g_1012_sub
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Producer heartbeat time"),
#endif
.idx = 0x1017,
.code = CO_OBJECT_VAR,
.nsub = 1,
.subs = g_1017_sub
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Identity object"),
#endif
.idx = 0x1018,
.code = CO_OBJECT_RECORD,
.nsub = 5,
.subs = g_1018_sub
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("SDO client parameter"),
#endif
.idx = 0x1280,
.code = CO_OBJECT_RECORD,
.nsub = 4,
.subs = g_1280_sub
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("RPDO communication parameter"),
#endif
.idx = 0x1400,
.code = CO_OBJECT_RECORD,
.nsub = 3,
.subs = g_1400_sub
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("RPDO mapping parameter"),
#endif
.idx = 0x1600,
.code = CO_OBJECT_RECORD,
.nsub = 2,
.subs = g_1600_sub
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("NMT startup"),
#endif
.idx = 0x1f80,
.code = CO_OBJECT_VAR,
.nsub = 1,
.subs = g_1f80_sub
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Object with custom SDO download callback"),
#endif
.idx = 0x2000,
.code = CO_OBJECT_VAR,
.nsub = 1,
.subs = g_2000_sub
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Object with custom SDO upload callback"),
#endif
.idx = 0x2001,
.code = CO_OBJECT_VAR,
.nsub = 1,
.subs = g_2001_sub
},
{
#if !LELY_NO_CO_OBJ_NAME
.name = CO_SDEV_STRING("Counter"),
#endif
.idx = 0x2100,
.code = CO_OBJECT_VAR,
.nsub = 1,
.subs = g_2100_sub
}
};
/****************************************************************************
* Public Data
****************************************************************************/
const struct co_sdev g_sdev_master =
{
.id = 0x01,
.name = NULL,
.vendor_name = CO_SDEV_STRING("Apache NuttX RTOS"),
.vendor_id = 0x00000000,
.product_name = NULL,
.product_code = 0x00000000,
.revision = 0x00000000,
.order_code = NULL,
.baud = CO_BAUD_125,
.rate = 0,
.lss = 0,
.dummy = 0x000000fe,
.nobj = 14,
.objs = g_g_sdev_master_objs
};
/****************************************************************************
* Public Functions
****************************************************************************/