File syn_canopen.c¶
File List > proto > syn_canopen.c
Go to the documentation of this file
#if __has_include("syn_config.h")
#include "syn_config.h"
#endif
#if !defined(SYN_USE_CANOPEN) || SYN_USE_CANOPEN
#include "syntropic/proto/syn_canopen.h"
#include "syntropic/util/syn_pack.h"
#include <string.h>
static void canopen_queue_tx(SYN_CANOpenNode *node, uint32_t cob_id, const uint8_t *data,
uint8_t len)
{
if (node == NULL) {
return;
}
if (len > sizeof(node->tx_data)) {
len = (uint8_t)sizeof(node->tx_data);
}
node->tx_cob_id = cob_id;
if (data != NULL && len > 0) {
(void)memcpy(node->tx_data, data, len);
}
node->tx_len = len;
node->tx_pending = true;
}
static const SYN_CANOpenODEntry *canopen_find_od(const SYN_CANOpenNode *node, uint16_t index,
uint8_t subindex)
{
if (node == NULL || node->od_table == NULL) {
return NULL;
}
for (size_t i = 0; i < node->od_count; i++) {
if (node->od_table[i].index == index && node->od_table[i].subindex == subindex) {
return &node->od_table[i];
}
}
return NULL;
}
static void canopen_send_sdo_abort(SYN_CANOpenNode *node, uint16_t index, uint8_t subindex,
uint32_t abort_code)
{
uint8_t payload[8];
size_t pos = 0;
syn_pack_u8(payload, &pos, 0x80U);
syn_pack_u16_le(payload, &pos, index);
syn_pack_u8(payload, &pos, subindex);
syn_pack_u32_le(payload, &pos, abort_code);
canopen_queue_tx(node, 0x580U + node->node_id, payload, 8);
}
SYN_Status syn_canopen_init(SYN_CANOpenNode *node, const SYN_CANOpenNodeConfig *cfg,
const SYN_CANOpenODEntry *od_table, size_t od_count)
{
if (node == NULL || cfg == NULL || od_table == NULL || od_count == 0) {
return SYN_INVALID_PARAM;
}
if (cfg->node_id < 1U || cfg->node_id > 127U) {
return SYN_INVALID_PARAM;
}
(void)memset(node, 0, sizeof(*node));
node->node_id = cfg->node_id;
node->cfg = *cfg;
node->od_table = od_table;
node->od_count = od_count;
node->nmt_state = SYN_CANOPEN_NMT_STATE_PREOP;
/* Queue Bootup Message: COB-ID = 0x700 + NodeID, payload = 0x00 */
uint8_t boot_payload[1] = {0x00U};
canopen_queue_tx(node, 0x700U + node->node_id, boot_payload, 1);
return SYN_OK;
}
SYN_Status syn_canopen_od_read(SYN_CANOpenNode *node, uint16_t index, uint8_t subindex, void *buf,
size_t buf_size, size_t *out_len)
{
if (node == NULL || buf == NULL || out_len == NULL) {
return SYN_INVALID_PARAM;
}
const SYN_CANOpenODEntry *entry = canopen_find_od(node, index, subindex);
if (entry == NULL) {
return SYN_ERROR;
}
if (entry->access == SYN_CANOPEN_ACCESS_WO) {
return SYN_ERROR;
}
if (buf_size < entry->size) {
return SYN_INVALID_PARAM;
}
(void)memcpy(buf, entry->data_ptr, entry->size);
*out_len = entry->size;
return SYN_OK;
}
SYN_Status syn_canopen_od_write(SYN_CANOpenNode *node, uint16_t index, uint8_t subindex,
const void *data, size_t len)
{
if (node == NULL || data == NULL) {
return SYN_INVALID_PARAM;
}
const SYN_CANOpenODEntry *entry = canopen_find_od(node, index, subindex);
if (entry == NULL) {
return SYN_ERROR;
}
if (entry->access == SYN_CANOPEN_ACCESS_RO) {
return SYN_ERROR;
}
if (len != entry->size) {
return SYN_INVALID_PARAM;
}
(void)memcpy(entry->data_ptr, data, len);
return SYN_OK;
}
SYN_Status syn_canopen_process_rx(SYN_CANOpenNode *node, uint32_t cob_id, const uint8_t *data,
uint8_t len)
{
if (node == NULL || data == NULL || len == 0) {
return SYN_INVALID_PARAM;
}
/* 1. NMT Control Message: COB-ID = 0x000 */
if (cob_id == 0x000U && len >= 2) {
uint8_t target_node = data[1];
if (target_node == 0U || target_node == node->node_id) {
uint8_t cmd = data[0];
switch (cmd) {
case SYN_CANOPEN_NMT_CMD_START:
node->nmt_state = SYN_CANOPEN_NMT_STATE_OPERATIONAL;
break;
case SYN_CANOPEN_NMT_CMD_STOP:
node->nmt_state = SYN_CANOPEN_NMT_STATE_STOPPED;
break;
case SYN_CANOPEN_NMT_CMD_PREOP:
node->nmt_state = SYN_CANOPEN_NMT_STATE_PREOP;
break;
case SYN_CANOPEN_NMT_CMD_RESET_NODE:
case SYN_CANOPEN_NMT_CMD_RESET_COMM:
node->nmt_state = SYN_CANOPEN_NMT_STATE_PREOP;
{
uint8_t boot_payload[1] = {0x00U};
canopen_queue_tx(node, 0x700U + node->node_id, boot_payload, 1);
}
break;
default:
break;
}
}
return SYN_OK;
}
/* 2. SDO Request Server: COB-ID = 0x600 + NodeID */
if (cob_id == (0x600U + node->node_id) && len == 8) {
size_t sdo_pos = 0;
uint8_t cmd = syn_unpack_u8(data, &sdo_pos);
uint16_t index = syn_unpack_u16_le(data, &sdo_pos);
uint8_t subindex = syn_unpack_u8(data, &sdo_pos);
/* SDO Download (Write Request) */
if ((cmd & 0xE0U) == 0x20U) {
const SYN_CANOpenODEntry *entry = canopen_find_od(node, index, subindex);
if (entry == NULL) {
const SYN_CANOpenODEntry *entry_idx = NULL;
for (size_t i = 0; i < node->od_count; i++) {
if (node->od_table[i].index == index) {
entry_idx = &node->od_table[i];
break;
}
}
if (entry_idx == NULL) {
canopen_send_sdo_abort(node, index, subindex, SYN_CANOPEN_SDO_ABORT_NOT_EXIST);
} else {
canopen_send_sdo_abort(node, index, subindex,
SYN_CANOPEN_SDO_ABORT_SUBINDEX_NOT_EXIST);
}
} else if (entry->access == SYN_CANOPEN_ACCESS_RO) {
canopen_send_sdo_abort(node, index, subindex, SYN_CANOPEN_SDO_ABORT_READ_ONLY);
} else if ((cmd & 0x02U) != 0) { /* Expedited download */
uint8_t n = (cmd >> 2) & 0x03U;
size_t write_len = (size_t)(4 - n);
if (write_len != entry->size) {
canopen_send_sdo_abort(node, index, subindex,
SYN_CANOPEN_SDO_ABORT_TYPE_MISMATCH);
} else {
(void)syn_canopen_od_write(node, index, subindex, &data[4], write_len);
uint8_t resp[8] = {0x60U, data[1], data[2], data[3], 0, 0, 0, 0};
canopen_queue_tx(node, 0x580U + node->node_id, resp, 8);
}
} else { /* Segmented download initiate */
node->sdo_session.state = SYN_CANOPEN_SDO_SEG_DOWNLOAD;
node->sdo_session.index = index;
node->sdo_session.subindex = subindex;
node->sdo_session.toggle = 0;
node->sdo_session.transferred_bytes = 0;
node->sdo_session.total_bytes =
(size_t)(data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24));
uint8_t resp[8] = {0x60U, data[1], data[2], data[3], 0, 0, 0, 0};
canopen_queue_tx(node, 0x580U + node->node_id, resp, 8);
}
return SYN_OK;
}
/* SDO Download Segment Request */
if ((cmd & 0xE0U) == 0x00U && node->sdo_session.state == SYN_CANOPEN_SDO_SEG_DOWNLOAD) {
uint8_t t = (cmd >> 4) & 0x01U;
if (t != node->sdo_session.toggle) {
canopen_send_sdo_abort(node, node->sdo_session.index, node->sdo_session.subindex,
SYN_CANOPEN_SDO_ABORT_TOGGLE_BIT);
node->sdo_session.state = SYN_CANOPEN_SDO_IDLE;
return SYN_OK;
}
const SYN_CANOpenODEntry *entry =
canopen_find_od(node, node->sdo_session.index, node->sdo_session.subindex);
uint8_t n = (cmd >> 1) & 0x07U;
size_t seg_len = 7U - n;
if (entry != NULL && (node->sdo_session.transferred_bytes + seg_len) <= entry->size) {
(void)memcpy((uint8_t *)entry->data_ptr + node->sdo_session.transferred_bytes,
&data[1], seg_len);
node->sdo_session.transferred_bytes += seg_len;
uint8_t resp[8] = {(uint8_t)((t << 4) | 0x20U), 0, 0, 0, 0, 0, 0, 0};
canopen_queue_tx(node, 0x580U + node->node_id, resp, 8);
node->sdo_session.toggle ^= 1U;
if ((cmd & 0x01U) != 0) { /* Last segment */
node->sdo_session.state = SYN_CANOPEN_SDO_IDLE;
}
} else {
canopen_send_sdo_abort(node, node->sdo_session.index, node->sdo_session.subindex,
SYN_CANOPEN_SDO_ABORT_TYPE_MISMATCH);
node->sdo_session.state = SYN_CANOPEN_SDO_IDLE;
}
return SYN_OK;
}
/* SDO Upload (Read Request) */
if ((cmd & 0xE0U) == 0x40U) {
const SYN_CANOpenODEntry *entry = canopen_find_od(node, index, subindex);
if (entry == NULL) {
const SYN_CANOpenODEntry *entry_idx = NULL;
for (size_t i = 0; i < node->od_count; i++) {
if (node->od_table[i].index == index) {
entry_idx = &node->od_table[i];
break;
}
}
if (entry_idx == NULL) {
canopen_send_sdo_abort(node, index, subindex, SYN_CANOPEN_SDO_ABORT_NOT_EXIST);
} else {
canopen_send_sdo_abort(node, index, subindex,
SYN_CANOPEN_SDO_ABORT_SUBINDEX_NOT_EXIST);
}
} else if (entry->access == SYN_CANOPEN_ACCESS_WO) {
canopen_send_sdo_abort(node, index, subindex, SYN_CANOPEN_SDO_ABORT_WRITE_ONLY);
} else if (entry->size <= 4) {
uint8_t read_buf[4] = {0};
size_t read_len = 0;
if (syn_canopen_od_read(node, index, subindex, read_buf, sizeof(read_buf),
&read_len) == SYN_OK &&
read_len <= 4) {
uint8_t n = (uint8_t)(4 - read_len);
uint8_t resp[8];
resp[0] = 0x43U | (uint8_t)(n << 2);
resp[1] = data[1];
resp[2] = data[2];
resp[3] = data[3];
(void)memcpy(&resp[4], read_buf, read_len);
if (read_len < 4) {
(void)memset(&resp[4 + read_len], 0, 4 - read_len);
}
canopen_queue_tx(node, 0x580U + node->node_id, resp, 8);
} else {
canopen_send_sdo_abort(node, index, subindex,
SYN_CANOPEN_SDO_ABORT_TYPE_MISMATCH);
}
} else { /* Segmented upload initiate */
node->sdo_session.state = SYN_CANOPEN_SDO_SEG_UPLOAD;
node->sdo_session.index = index;
node->sdo_session.subindex = subindex;
node->sdo_session.toggle = 0;
node->sdo_session.total_bytes = entry->size;
node->sdo_session.transferred_bytes = 0;
uint8_t resp[8] = {0x41U,
data[1],
data[2],
data[3],
(uint8_t)(entry->size & 0xFFU),
(uint8_t)((entry->size >> 8) & 0xFFU),
(uint8_t)((entry->size >> 16) & 0xFFU),
(uint8_t)((entry->size >> 24) & 0xFFU)};
canopen_queue_tx(node, 0x580U + node->node_id, resp, 8);
}
return SYN_OK;
}
/* SDO Upload Segment Request */
if ((cmd & 0xE0U) == 0x60U && node->sdo_session.state == SYN_CANOPEN_SDO_SEG_UPLOAD) {
uint8_t t = (cmd >> 4) & 0x01U;
if (t != node->sdo_session.toggle) {
canopen_send_sdo_abort(node, node->sdo_session.index, node->sdo_session.subindex,
SYN_CANOPEN_SDO_ABORT_TOGGLE_BIT);
node->sdo_session.state = SYN_CANOPEN_SDO_IDLE;
return SYN_OK;
}
const SYN_CANOpenODEntry *entry =
canopen_find_od(node, node->sdo_session.index, node->sdo_session.subindex);
if (entry != NULL) {
size_t rem = entry->size - node->sdo_session.transferred_bytes;
size_t seg_len = (rem > 7U) ? 7U : rem;
uint8_t n = (uint8_t)(7U - seg_len);
uint8_t c = (rem <= 7U) ? 1U : 0U;
uint8_t resp[8] = {0};
resp[0] = (uint8_t)((t << 4) | (n << 1) | c);
(void)memcpy(&resp[1],
(const uint8_t *)entry->data_ptr + node->sdo_session.transferred_bytes,
seg_len);
canopen_queue_tx(node, 0x580U + node->node_id, resp, 8);
node->sdo_session.transferred_bytes += seg_len;
node->sdo_session.toggle ^= 1U;
if (c != 0) {
node->sdo_session.state = SYN_CANOPEN_SDO_IDLE;
}
} else {
canopen_send_sdo_abort(node, node->sdo_session.index, node->sdo_session.subindex,
SYN_CANOPEN_SDO_ABORT_NOT_EXIST);
node->sdo_session.state = SYN_CANOPEN_SDO_IDLE;
}
return SYN_OK;
}
}
/* 3. RPDO Processing (Operational State Only) */
if (node->nmt_state == SYN_CANOPEN_NMT_STATE_OPERATIONAL) {
for (int i = 0; i < 4; i++) {
if (node->cfg.rpdo[i].enabled && node->cfg.rpdo[i].cob_id == cob_id) {
(void)syn_canopen_od_write(node, node->cfg.rpdo[i].od_index,
node->cfg.rpdo[i].od_subindex, data, len);
return SYN_OK;
}
}
}
return SYN_OK;
}
SYN_Status syn_canopen_update(SYN_CANOpenNode *node, uint32_t dt_ms)
{
if (node == NULL) {
return SYN_INVALID_PARAM;
}
/* Periodic Heartbeat Producer */
if (node->cfg.heartbeat_ms > 0) {
node->heartbeat_timer_ms += (uint16_t)dt_ms;
if (node->heartbeat_timer_ms >= node->cfg.heartbeat_ms) {
node->heartbeat_timer_ms = 0;
uint8_t hb_payload[1];
hb_payload[0] = node->nmt_state;
canopen_queue_tx(node, 0x700U + node->node_id, hb_payload, 1);
}
}
/* Periodic TPDO Transmission (Operational State Only) */
if (node->nmt_state == SYN_CANOPEN_NMT_STATE_OPERATIONAL) {
for (int i = 0; i < 4; i++) {
if (node->cfg.tpdo[i].enabled && !node->tx_pending) {
uint8_t tpdo_buf[8] = {0};
size_t tpdo_len = 0;
if (syn_canopen_od_read(node, node->cfg.tpdo[i].od_index,
node->cfg.tpdo[i].od_subindex, tpdo_buf, sizeof(tpdo_buf),
&tpdo_len) == SYN_OK) {
canopen_queue_tx(node, node->cfg.tpdo[i].cob_id, tpdo_buf, (uint8_t)tpdo_len);
}
}
}
}
return SYN_OK;
}
SYN_Status syn_canopen_send_emcy(SYN_CANOpenNode *node, uint16_t err_code, uint8_t err_reg)
{
if (node == NULL) {
return SYN_INVALID_PARAM;
}
uint8_t payload[8] = {0};
payload[0] = (uint8_t)(err_code & 0xFFU);
payload[1] = (uint8_t)((err_code >> 8) & 0xFFU);
payload[2] = err_reg;
canopen_queue_tx(node, 0x080U + node->node_id, payload, 8);
return SYN_OK;
}
bool syn_canopen_get_tx(SYN_CANOpenNode *node, uint32_t *out_cob_id, uint8_t *out_data,
uint8_t *out_len)
{
if (node == NULL || !node->tx_pending) {
return false;
}
if (out_cob_id != NULL) {
*out_cob_id = node->tx_cob_id;
}
if (out_data != NULL) {
(void)memcpy(out_data, node->tx_data, node->tx_len);
}
if (out_len != NULL) {
*out_len = node->tx_len;
}
node->tx_pending = false;
return true;
}
SYN_Status syn_canopen_tpdo_trigger(SYN_CANOpenNode *node, uint8_t pdo_num)
{
if (node == NULL || pdo_num < 1 || pdo_num > 4) {
return SYN_INVALID_PARAM;
}
if (node->nmt_state != SYN_CANOPEN_NMT_STATE_OPERATIONAL) {
return SYN_ERROR; /* TPDOs only transmitted in Operational state */
}
SYN_CANOpenPDOMap *map = &node->cfg.tpdo[pdo_num - 1];
if (!map->enabled || map->cob_id == 0) {
return SYN_ERROR; /* Unmapped */
}
uint8_t payload[8];
size_t len = 0;
SYN_Status st =
syn_canopen_od_read(node, map->od_index, map->od_subindex, payload, sizeof(payload), &len);
if (st == SYN_OK && len > 0) {
canopen_queue_tx(node, map->cob_id, payload, (uint8_t)len);
return SYN_OK;
}
return SYN_ERROR;
}
#endif /* SYN_USE_CANOPEN */