canbus: Rework CAN command protocol
Rework the micro-controller command protocol so that it supports direct communication with the serialqueue.c code. Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
parent
041692828c
commit
6cb419a90a
|
@ -0,0 +1,65 @@
|
||||||
|
This document describes the protocol Klipper uses to communicate over
|
||||||
|
[CAN bus](https://en.wikipedia.org/wiki/CAN_bus).
|
||||||
|
|
||||||
|
# Micro-controller id assignment
|
||||||
|
|
||||||
|
Klipper uses only CAN 2.0A standard size CAN bus packets, which are
|
||||||
|
limited to 8 data bytes and an 11-bit CAN bus identifier. In order to
|
||||||
|
support efficient communication, each micro-controller is assigned at
|
||||||
|
run-time a unique 1-byte CAN bus nodeid (`canbus_nodeid`) for general
|
||||||
|
Klipper command and response traffic. Klipper command messages going
|
||||||
|
from host to micro-controller use the CAN bus id of `canbus_nodeid *
|
||||||
|
2 + 256`, while Klipper response messages from micro-controller to
|
||||||
|
host use `canbus_nodeid * 2 + 256 + 1`.
|
||||||
|
|
||||||
|
Each micro-controller has a factory assigned unique chip identifier
|
||||||
|
that is used during id assignment. This identifier can exceed the
|
||||||
|
length of one CAN packet, so a hash function is used to generate a
|
||||||
|
unique 6-byte id (`canbus_uuid`) from the factory id.
|
||||||
|
|
||||||
|
# Admin messages
|
||||||
|
|
||||||
|
Admin messages are used for id assignment. Admin messages sent from
|
||||||
|
host to micro-controller use the CAN bus id `0x3f0` and messages sent
|
||||||
|
from micro-controller to host use the CAN bus id `0x3f1`. All
|
||||||
|
micro-controllers listen to messages on id `0x3f0`; that id can be
|
||||||
|
thought of as a "broadcast address".
|
||||||
|
|
||||||
|
## CMD_QUERY_UNASSIGNED message
|
||||||
|
|
||||||
|
This command queries all micro-controllers that have not yet been
|
||||||
|
assigned a `canbus_nodeid`. Unassigned micro-controllers will respond
|
||||||
|
with a RESP_NEED_NODEID response message.
|
||||||
|
|
||||||
|
The CMD_QUERY_UNASSIGNED message format is:
|
||||||
|
`<1-byte message_id = 0x00>`
|
||||||
|
|
||||||
|
## CMD_SET_NODEID message
|
||||||
|
|
||||||
|
This command assigns a `canbus_nodeid` to the micro-controller with a
|
||||||
|
given `canbus_uuid`.
|
||||||
|
|
||||||
|
The CMD_SET_NODEID message format is:
|
||||||
|
`<1-byte message_id = 0x01><6-byte canbus_uuid><1-byte canbus_nodeid>`
|
||||||
|
|
||||||
|
## RESP_NEED_NODEID message
|
||||||
|
|
||||||
|
The RESP_NEED_NODEID message format is:
|
||||||
|
`<1-byte message_id = 0x20><6-byte canbus_uuid>`
|
||||||
|
|
||||||
|
# Data Packets
|
||||||
|
|
||||||
|
A micro-controller that has been assigned a nodeid via the
|
||||||
|
CMD_SET_NODEID command can send and receive data packets.
|
||||||
|
|
||||||
|
The packet data in messages using the node's receive CAN bus id
|
||||||
|
(`canbus_nodeid * 2 + 256`) are simply appended to a buffer, and when
|
||||||
|
a complete [mcu protocol message](Protocol.md) is found its contents
|
||||||
|
are parsed and processed. The data is treated as a byte stream - there
|
||||||
|
is no requirement for the start of a Klipper message block to align
|
||||||
|
with the start of a CAN bus packet.
|
||||||
|
|
||||||
|
Similarly, mcu protocol message responses are sent from
|
||||||
|
micro-controller to host by copying the message data into one or more
|
||||||
|
packets with the node's transmit CAN bus id (`canbus_nodeid * 2 +
|
||||||
|
256 + 1`).
|
|
@ -60,6 +60,8 @@ communication with the Klipper developers.
|
||||||
control API.
|
control API.
|
||||||
- [MCU commands](MCU_Commands.md): A description of low-level commands
|
- [MCU commands](MCU_Commands.md): A description of low-level commands
|
||||||
implemented in the micro-controller software.
|
implemented in the micro-controller software.
|
||||||
|
- [CAN bus protocol](CANBUS_protocol.md): Klipper CAN bus message
|
||||||
|
format.
|
||||||
- [Debugging](Debugging.md): Information on how to test and debug
|
- [Debugging](Debugging.md): Information on how to test and debug
|
||||||
Klipper.
|
Klipper.
|
||||||
- [Benchmarks](Benchmarks.md): Information on the Klipper benchmark
|
- [Benchmarks](Benchmarks.md): Information on the Klipper benchmark
|
||||||
|
|
|
@ -82,80 +82,88 @@ console_sendf(const struct command_encoder *ce, va_list args)
|
||||||
|
|
||||||
|
|
||||||
/****************************************************************
|
/****************************************************************
|
||||||
* CAN command handling
|
* CAN "admin" command handling
|
||||||
****************************************************************/
|
****************************************************************/
|
||||||
|
|
||||||
static uint8_t receive_buf[192], receive_pos;
|
// Available commands and responses
|
||||||
DECL_CONSTANT("RECEIVE_WINDOW", ARRAY_SIZE(receive_buf));
|
#define CANBUS_CMD_QUERY_UNASSIGNED 0x00
|
||||||
|
#define CANBUS_CMD_SET_NODEID 0x01
|
||||||
|
#define CANBUS_RESP_NEED_NODEID 0x20
|
||||||
|
|
||||||
static void
|
// Helper to verify a UUID in a command matches this chip's UUID
|
||||||
can_process_data(uint32_t id, uint32_t len, uint8_t *data)
|
static int
|
||||||
|
can_check_uuid(uint32_t id, uint32_t len, uint8_t *data)
|
||||||
{
|
{
|
||||||
int rpos = receive_pos;
|
return len >= 7 && memcmp(&data[1], canbus_uuid, sizeof(canbus_uuid)) == 0;
|
||||||
if (len > sizeof(receive_buf) - rpos)
|
|
||||||
len = sizeof(receive_buf) - rpos;
|
|
||||||
memcpy(&receive_buf[rpos], data, len);
|
|
||||||
receive_pos = rpos + len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to retry sending until successful
|
// Helpers to encode/decode a CAN identifier to a 1-byte "nodeid"
|
||||||
static void
|
static int
|
||||||
canbus_send_blocking(uint32_t id, uint32_t len, uint8_t *data)
|
can_get_nodeid(void)
|
||||||
{
|
{
|
||||||
|
if (!canbus_assigned_id)
|
||||||
|
return 0;
|
||||||
|
return (canbus_assigned_id - 0x100) >> 1;
|
||||||
|
}
|
||||||
|
static uint32_t
|
||||||
|
can_decode_nodeid(int nodeid)
|
||||||
|
{
|
||||||
|
return (nodeid << 1) + 0x100;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
can_process_query_unassigned(uint32_t id, uint32_t len, uint8_t *data)
|
||||||
|
{
|
||||||
|
if (canbus_assigned_id)
|
||||||
|
return;
|
||||||
|
uint8_t send[8];
|
||||||
|
send[0] = CANBUS_RESP_NEED_NODEID;
|
||||||
|
memcpy(&send[1], canbus_uuid, sizeof(canbus_uuid));
|
||||||
|
// Send with retry
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int ret = canbus_send(id, len, data);
|
int ret = canbus_send(CANBUS_ID_ADMIN_RESP, 7, send);
|
||||||
if (ret >= 0)
|
if (ret >= 0)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
can_process_ping(uint32_t id, uint32_t len, uint8_t *data)
|
can_id_conflict(void)
|
||||||
{
|
{
|
||||||
canbus_send_blocking(canbus_assigned_id + 1, 0, NULL);
|
canbus_assigned_id = 0;
|
||||||
|
canbus_set_filter(canbus_assigned_id);
|
||||||
|
shutdown("Another CAN node assigned this ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
can_process_reset(uint32_t id, uint32_t len, uint8_t *data)
|
can_process_set_nodeid(uint32_t id, uint32_t len, uint8_t *data)
|
||||||
{
|
{
|
||||||
uint32_t reset_id = data[0] | (data[1] << 8);
|
if (len < 8)
|
||||||
if (reset_id == canbus_assigned_id)
|
|
||||||
canbus_reboot();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
can_process_uuid(uint32_t id, uint32_t len, uint8_t *data)
|
|
||||||
{
|
|
||||||
if (canbus_assigned_id)
|
|
||||||
return;
|
return;
|
||||||
canbus_send_blocking(CANBUS_ID_UUID_RESP, sizeof(canbus_uuid), canbus_uuid);
|
uint32_t newid = can_decode_nodeid(data[7]);
|
||||||
}
|
if (can_check_uuid(id, len, data)) {
|
||||||
|
if (newid != canbus_assigned_id) {
|
||||||
static void
|
canbus_assigned_id = newid;
|
||||||
can_process_set_id(uint32_t id, uint32_t len, uint8_t *data)
|
|
||||||
{
|
|
||||||
// compare my UUID with packet to check if this packet mine
|
|
||||||
if (memcmp(&data[2], canbus_uuid, sizeof(canbus_uuid)) == 0) {
|
|
||||||
canbus_assigned_id = data[0] | (data[1] << 8);
|
|
||||||
canbus_set_filter(canbus_assigned_id);
|
canbus_set_filter(canbus_assigned_id);
|
||||||
}
|
}
|
||||||
|
} else if (newid == canbus_assigned_id) {
|
||||||
|
can_id_conflict();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle an "admin" command
|
||||||
static void
|
static void
|
||||||
can_process(uint32_t id, uint32_t len, uint8_t *data)
|
can_process(uint32_t id, uint32_t len, uint8_t *data)
|
||||||
{
|
{
|
||||||
if (id == canbus_assigned_id) {
|
if (!len)
|
||||||
if (len)
|
return;
|
||||||
can_process_data(id, len, data);
|
switch (data[0]) {
|
||||||
else
|
case CANBUS_CMD_QUERY_UNASSIGNED:
|
||||||
can_process_ping(id, len, data);
|
can_process_query_unassigned(id, len, data);
|
||||||
} else if (id == CANBUS_ID_UUID) {
|
break;
|
||||||
if (len)
|
case CANBUS_CMD_SET_NODEID:
|
||||||
can_process_reset(id, len, data);
|
can_process_set_nodeid(id, len, data);
|
||||||
else
|
break;
|
||||||
can_process_uuid(id, len, data);
|
|
||||||
} else if (id==CANBUS_ID_SET) {
|
|
||||||
can_process_set_id(id, len, data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,6 +180,19 @@ canbus_notify_rx(void)
|
||||||
sched_wake_task(&canbus_rx_wake);
|
sched_wake_task(&canbus_rx_wake);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint8_t receive_buf[192], receive_pos;
|
||||||
|
DECL_CONSTANT("RECEIVE_WINDOW", ARRAY_SIZE(receive_buf));
|
||||||
|
|
||||||
|
static void
|
||||||
|
can_process_data(uint32_t id, uint32_t len, uint8_t *data)
|
||||||
|
{
|
||||||
|
int rpos = receive_pos;
|
||||||
|
if (len > sizeof(receive_buf) - rpos)
|
||||||
|
len = sizeof(receive_buf) - rpos;
|
||||||
|
memcpy(&receive_buf[rpos], data, len);
|
||||||
|
receive_pos = rpos + len;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
canbus_rx_task(void)
|
canbus_rx_task(void)
|
||||||
{
|
{
|
||||||
|
@ -185,6 +206,11 @@ canbus_rx_task(void)
|
||||||
int ret = canbus_read(&id, data);
|
int ret = canbus_read(&id, data);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
break;
|
break;
|
||||||
|
if (id && id == canbus_assigned_id)
|
||||||
|
can_process_data(id, ret, data);
|
||||||
|
if (id && id == canbus_assigned_id + 1)
|
||||||
|
can_id_conflict();
|
||||||
|
else if (id == CANBUS_ID_ADMIN)
|
||||||
can_process(id, ret, data);
|
can_process(id, ret, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,14 +235,19 @@ DECL_TASK(canbus_rx_task);
|
||||||
* Setup and shutdown
|
* Setup and shutdown
|
||||||
****************************************************************/
|
****************************************************************/
|
||||||
|
|
||||||
|
void
|
||||||
|
command_get_canbus_id(uint32_t *args)
|
||||||
|
{
|
||||||
|
sendf("canbus_id canbus_uuid=%.*s canbus_nodeid=%u"
|
||||||
|
, sizeof(canbus_uuid), canbus_uuid, can_get_nodeid());
|
||||||
|
}
|
||||||
|
DECL_COMMAND_FLAGS(command_get_canbus_id, HF_IN_SHUTDOWN, "get_canbus_id");
|
||||||
|
|
||||||
void
|
void
|
||||||
canbus_set_uuid(void *uuid)
|
canbus_set_uuid(void *uuid)
|
||||||
{
|
{
|
||||||
memcpy(canbus_uuid, uuid, sizeof(canbus_uuid));
|
memcpy(canbus_uuid, uuid, sizeof(canbus_uuid));
|
||||||
canbus_notify_rx();
|
canbus_notify_rx();
|
||||||
|
|
||||||
// Send initial message
|
|
||||||
can_process_uuid(0, 0, NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -3,16 +3,14 @@
|
||||||
|
|
||||||
#include <stdint.h> // uint32_t
|
#include <stdint.h> // uint32_t
|
||||||
|
|
||||||
#define CANBUS_ID_UUID 0x321
|
#define CANBUS_ID_ADMIN 0x3f0
|
||||||
#define CANBUS_ID_SET 0x322
|
#define CANBUS_ID_ADMIN_RESP 0x3f1
|
||||||
#define CANBUS_ID_UUID_RESP 0x323
|
|
||||||
#define CANBUS_UUID_LEN 6
|
#define CANBUS_UUID_LEN 6
|
||||||
|
|
||||||
// callbacks provided by board specific code
|
// callbacks provided by board specific code
|
||||||
int canbus_read(uint32_t *id, uint8_t *data);
|
int canbus_read(uint32_t *id, uint8_t *data);
|
||||||
int canbus_send(uint32_t id, uint32_t len, uint8_t *data);
|
int canbus_send(uint32_t id, uint32_t len, uint8_t *data);
|
||||||
void canbus_set_filter(uint32_t id);
|
void canbus_set_filter(uint32_t id);
|
||||||
void canbus_reboot(void);
|
|
||||||
|
|
||||||
// canbus.c
|
// canbus.c
|
||||||
void canbus_notify_tx(void);
|
void canbus_notify_tx(void);
|
||||||
|
|
|
@ -159,43 +159,35 @@ canbus_send(uint32_t id, uint32_t len, uint8_t *data)
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CAN_FILTER_NUMBER 0
|
|
||||||
|
|
||||||
// Setup the receive packet filter
|
// Setup the receive packet filter
|
||||||
void
|
void
|
||||||
canbus_set_filter(uint32_t id)
|
canbus_set_filter(uint32_t id)
|
||||||
{
|
{
|
||||||
uint32_t filternbrbitpos = 1 << CAN_FILTER_NUMBER;
|
|
||||||
|
|
||||||
/* Select the start slave bank */
|
/* Select the start slave bank */
|
||||||
SOC_CAN->FMR |= CAN_FMR_FINIT;
|
SOC_CAN->FMR |= CAN_FMR_FINIT;
|
||||||
/* Initialisation mode for the filter */
|
/* Initialisation mode for the filter */
|
||||||
SOC_CAN->FA1R = 0;
|
SOC_CAN->FA1R = 0;
|
||||||
|
|
||||||
uint32_t idadmin = CANBUS_ID_UUID;
|
uint32_t mask = CAN_RI0R_STID | CAN_TI0R_IDE | CAN_TI0R_RTR;
|
||||||
SOC_CAN->sFilterRegister[CAN_FILTER_NUMBER].FR1 = idadmin << (5 + 16);
|
SOC_CAN->sFilterRegister[0].FR1 = CANBUS_ID_ADMIN << CAN_RI0R_STID_Pos;
|
||||||
SOC_CAN->sFilterRegister[CAN_FILTER_NUMBER].FR2 = id << (5 + 16);
|
SOC_CAN->sFilterRegister[0].FR2 = mask;
|
||||||
|
SOC_CAN->sFilterRegister[1].FR1 = (id + 1) << CAN_RI0R_STID_Pos;
|
||||||
|
SOC_CAN->sFilterRegister[1].FR2 = mask;
|
||||||
|
SOC_CAN->sFilterRegister[2].FR1 = id << CAN_RI0R_STID_Pos;
|
||||||
|
SOC_CAN->sFilterRegister[2].FR2 = mask;
|
||||||
|
|
||||||
/* Identifier list mode for the filter */
|
|
||||||
SOC_CAN->FM1R = filternbrbitpos;
|
|
||||||
/* 32-bit scale for the filter */
|
/* 32-bit scale for the filter */
|
||||||
SOC_CAN->FS1R = filternbrbitpos;
|
SOC_CAN->FS1R = (1<<0) | (1<<1) | (1<<2);
|
||||||
|
|
||||||
/* FIFO 0 assigned for the filter */
|
/* FIFO 0 assigned for the filter */
|
||||||
SOC_CAN->FFA1R = 0;
|
SOC_CAN->FFA1R = 0;
|
||||||
|
|
||||||
/* Filter activation */
|
/* Filter activation */
|
||||||
SOC_CAN->FA1R = filternbrbitpos;
|
SOC_CAN->FA1R = (1<<0) | (id ? (1<<1) | (1<<2) : 0);
|
||||||
/* Leave the initialisation mode for the filter */
|
/* Leave the initialisation mode for the filter */
|
||||||
SOC_CAN->FMR &= ~CAN_FMR_FINIT;
|
SOC_CAN->FMR &= ~CAN_FMR_FINIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
canbus_reboot(void)
|
|
||||||
{
|
|
||||||
NVIC_SystemReset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function handles CAN global interrupts
|
// This function handles CAN global interrupts
|
||||||
void
|
void
|
||||||
CAN_IRQHandler(void)
|
CAN_IRQHandler(void)
|
||||||
|
@ -292,7 +284,7 @@ can_init(void)
|
||||||
;
|
;
|
||||||
|
|
||||||
/*##-2- Configure the CAN Filter #######################################*/
|
/*##-2- Configure the CAN Filter #######################################*/
|
||||||
canbus_set_filter(CANBUS_ID_SET);
|
canbus_set_filter(0);
|
||||||
|
|
||||||
/*##-3- Configure Interrupts #################################*/
|
/*##-3- Configure Interrupts #################################*/
|
||||||
armcm_enable_irq(CAN_IRQHandler, CAN_RX0_IRQn, 0);
|
armcm_enable_irq(CAN_IRQHandler, CAN_RX0_IRQn, 0);
|
||||||
|
|
Loading…
Reference in New Issue