diff --git a/libopencm3 b/libopencm3 index 17f159d..b0e050d 160000 --- a/libopencm3 +++ b/libopencm3 @@ -1 +1 @@ -Subproject commit 17f159dec9a548d7e1984ed9fc3fea6d4eb776cf +Subproject commit b0e050d10d12c42be031c34822117cfd3c5a0ea7 diff --git a/src/CAN/can.c b/src/CAN/can.c new file mode 100644 index 0000000..dfc2070 --- /dev/null +++ b/src/CAN/can.c @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2015, Devan Lai + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice + * appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include + +#include "config.h" +#include "can.h" + +#if CAN_RX_AVAILABLE + +#define IS_POW_OF_TWO(X) (((X) & ((X)-1)) == 0) +_Static_assert(IS_POW_OF_TWO(CAN_RX_BUFFER_SIZE), + "Unmasked circular buffer size must be a power of two"); +_Static_assert(CAN_RX_BUFFER_SIZE <= UINT8_MAX/2, + "Buffer size too big for unmasked circular buffer"); + +static volatile CAN_Message can_rx_buffer[CAN_RX_BUFFER_SIZE]; +static volatile uint8_t can_rx_head = 0; +static volatile uint8_t can_rx_tail = 0; + +bool can_rx_buffer_empty(void) { + return can_rx_head == can_rx_tail; +} + +bool can_rx_buffer_full(void) { + return (uint8_t)(can_rx_tail - can_rx_head) == CAN_RX_BUFFER_SIZE; +} + +CAN_Message* can_rx_buffer_peek(void) { + if (!can_rx_buffer_empty()) { + return (CAN_Message*)(&can_rx_buffer[(can_rx_head % CAN_RX_BUFFER_SIZE)]); + } else { + return NULL; + } +} + +void can_rx_buffer_pop(void) { + can_rx_head++; + + // Re-enable the ISR since we made space + nvic_enable_irq(CAN_NVIC_LINE); +} + +static CAN_Message* can_rx_buffer_tail(void) { + return (CAN_Message*)&can_rx_buffer[(can_rx_tail % CAN_RX_BUFFER_SIZE)]; +} + +static void can_rx_buffer_extend(void) { + can_rx_tail++; +} + +void can_rx_buffer_put(const CAN_Message* msg) { + memcpy((void*)&can_rx_buffer[(can_rx_tail % CAN_RX_BUFFER_SIZE)], (const void*)msg, sizeof(CAN_Message)); + can_rx_tail++; +} + +void can_rx_buffer_get(CAN_Message* msg) { + memcpy((void*)msg, (const void*)&can_rx_buffer[(can_rx_head % CAN_RX_BUFFER_SIZE)], sizeof(CAN_Message)); + can_rx_head++; + + // Re-enable the ISR since we made space + nvic_enable_irq(CAN_NVIC_LINE); +} + +bool can_reconfigure(uint32_t baudrate, CanMode mode) { + nvic_disable_irq(CAN_NVIC_LINE); + can_disable_irq(CAN1, CAN_IER_FMPIE0); + can_reset(CAN1); + + if (mode == MODE_RESET) { + // Just stop after resetting the CAN controller. + return true; + } + + /* Set appropriate bit timing */ + uint32_t sjw = CAN_BTR_SJW_1TQ; + uint32_t ts1; + uint32_t ts2; + uint32_t brp; + + if (baudrate == 1000000) { + brp = 3; + ts1 = CAN_BTR_TS1_11TQ; + ts2 = CAN_BTR_TS2_4TQ; + } else if (baudrate == 500000) { + brp = 6; + ts1 = CAN_BTR_TS1_11TQ; + ts2 = CAN_BTR_TS2_4TQ; + } else if (baudrate == 250000) { + brp = 12; + ts1 = CAN_BTR_TS1_11TQ; + ts2 = CAN_BTR_TS2_4TQ; + } else if (baudrate == 125000) { + brp = 24; + ts1 = CAN_BTR_TS1_11TQ; + ts2 = CAN_BTR_TS2_4TQ; + } else if (baudrate == 100000) { + brp = 30; + ts1 = CAN_BTR_TS1_11TQ; + ts2 = CAN_BTR_TS2_4TQ; + } else { + return false; + } + + bool loopback = (mode == MODE_TEST_LOCAL || mode == MODE_TEST_SILENT); + bool silent = (mode == MODE_SILENT || mode == MODE_TEST_SILENT); + + bool TTCM = false; /* TTCM: Time triggered comm mode? */ + bool ABOM = true; /* ABOM: Automatic bus-off management? */ + bool AWUM = false; /* AWUM: Automatic wakeup mode? */ + bool NART = false; /* NART: No automatic retransmission? */ + bool RFLM = true; /* RFLM: Receive FIFO locked mode? */ + bool TXFP = false; /* TXFP: Transmit FIFO priority? */ + + /* CAN cell init. */ + if (can_init(CAN1, TTCM, ABOM, AWUM, NART, RFLM, TXFP, + sjw, ts1, ts2, + brp, /* BRP+1: Baud rate prescaler */ + loopback, + silent) != 0) { + return false; + } else { + can_filter_id_mask_32bit_init(CAN1, + 0, /* Filter ID */ + 0, /* CAN ID */ + 0, /* CAN ID mask */ + 0, /* FIFO assignment (here: FIFO0) */ + true); /* Enable the filter. */ + } + + can_enable_irq(CAN1, CAN_IER_FMPIE0); + nvic_enable_irq(CAN_NVIC_LINE); + return true; +} + +bool can_setup(uint32_t baudrate, CanMode mode) { + /* Enable CAN clock */ + rcc_periph_clock_enable(RCC_CAN); + + /* Setup CANRX */ + gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO8); + gpio_set_af(GPIOB, GPIO_AF4, GPIO8); + +#if CAN_TX_AVAILABLE + gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO9); + gpio_set_af(GPIOB, GPIO_AF4, GPIO9); +#endif + + return can_reconfigure(baudrate, mode); +} + +static uint8_t can_fifo_depth(void) { + uint8_t fifo_depth = (CAN_RF0R(CAN1) & CAN_RF0R_FMP0_MASK); + // Account for one fifo entry possibly going away + if (CAN_RF0R(CAN1) & CAN_RF0R_RFOM0) { + fifo_depth = fifo_depth > 0 ? (fifo_depth - 1) : 0; + } + + return fifo_depth; +} + +bool can_read(CAN_Message* msg) { + bool success = false; + + if (can_fifo_depth() > 0) { + // Wait for the previous message to be released + while (CAN_RF0R(CAN1) & CAN_RF0R_RFOM0); + + uint8_t fmi; + bool ext, rtr; + can_receive(CAN1, 0, true, &msg->id, &ext, &rtr, &fmi, &msg->len, msg->data, NULL); + msg->format = ext ? CANExtended : CANStandard; + msg->type = rtr ? CANRemote : CANData; + success = true; + } + + return success; +} + +bool can_read_buffer(CAN_Message* msg) { + bool success = false; + if (!can_rx_buffer_empty()) { + can_rx_buffer_get(msg); + success = true; + } + + return success; +} + +bool can_write(CAN_Message* msg) { + bool ext = msg->format == CANExtended; + bool rtr = msg->type == CANRemote; + return (can_transmit(CAN1, msg->id, ext, rtr, msg->len, msg->data) != -1); +} + +void cec_can_isr(void) { + uint8_t messages_queued = 0; + uint8_t fifo_depth = can_fifo_depth(); + while (!can_rx_buffer_full() && messages_queued < fifo_depth) { + CAN_Message* msg = can_rx_buffer_tail(); + if (can_read(msg)) { + can_rx_buffer_extend(); + messages_queued++; + } else { + break; + } + } + + // If the software buffer is full, disable the ISR so that + // the main loop can drain the buffer over USB. + if (messages_queued == 0) { + nvic_disable_irq(CAN_NVIC_LINE); + } +} + + +#endif diff --git a/src/CAN/can.h b/src/CAN/can.h new file mode 100644 index 0000000..ed9e6e7 --- /dev/null +++ b/src/CAN/can.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2015, Devan Lai + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice + * appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef CAN_H +#define CAN_H + +#include "can_helper.h" + +#define CAN_RX_BUFFER_SIZE 16 + +extern bool can_setup(uint32_t baudrate, CanMode mode); +extern bool can_reconfigure(uint32_t baudrate, CanMode mode); +extern bool can_read(CAN_Message* msg); +extern bool can_read_buffer(CAN_Message* msg); + +extern bool can_write(CAN_Message* msg); + +extern bool can_rx_buffer_empty(void); +extern bool can_rx_buffer_full(void); +extern void can_rx_buffer_put(const CAN_Message* msg); +extern void can_rx_buffer_get(CAN_Message* msg); +extern CAN_Message* can_rx_buffer_peek(void); +extern void can_rx_buffer_pop(void); + +#endif diff --git a/src/CAN/can_helper.h b/src/CAN/can_helper.h new file mode 100644 index 0000000..e6e1852 --- /dev/null +++ b/src/CAN/can_helper.h @@ -0,0 +1,59 @@ +/* mbed Microcontroller Library + * Copyright (c) 2006-2013 ARM Limited + * + * Licensed 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 MBED_CAN_HELPER_H +#define MBED_CAN_HELPER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +enum CANFormat { + CANStandard = 0, + CANExtended = 1, + CANAny = 2 +}; +typedef enum CANFormat CANFormat; + +enum CANType { + CANData = 0, + CANRemote = 1 +}; +typedef enum CANType CANType; + +struct CAN_Message { + uint32_t id; // 29 bit identifier + uint8_t data[8]; // Data field + uint8_t len; // Length of data field in bytes + CANFormat format; // 0 - STANDARD, 1- EXTENDED IDENTIFIER + CANType type; // 0 - DATA FRAME, 1 - REMOTE FRAME +}; +typedef struct CAN_Message CAN_Message; + +typedef enum { + MODE_RESET, + MODE_NORMAL, + MODE_SILENT, + MODE_TEST_LOCAL, + MODE_TEST_SILENT +} CanMode; + +#ifdef __cplusplus +}; +#endif + +#endif // MBED_CAN_HELPER_H diff --git a/src/CAN/slcan.c b/src/CAN/slcan.c new file mode 100644 index 0000000..7d97fdd --- /dev/null +++ b/src/CAN/slcan.c @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2016, Devan Lai + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice + * appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "USB/vcdc.h" +#include "retarget.h" +#include "slcan.h" + +CanMode slcan_mode; +uint32_t slcan_baudrate; + +static bool parse_hex_digits(const char* input, uint8_t num_digits, uint32_t* value_out) { + bool success = true; + uint32_t value = 0; + + uint8_t i; + for (i=0; i < num_digits; i++) { + uint32_t nibble = 0; + if (input[i] >= '0' && input[i] <= '9') { + nibble = 0x0 + (input[i] - '0'); + } else if (input[i] >= 'a' && input[i] <= 'f') { + nibble = 0xA + (input[i] - 'a'); + } else if (input[i] >= 'A' && input[i] <= 'F') { + nibble = 0xA + (input[i] - 'A'); + } else { + success = false; + break; + } + uint8_t offset = 4*(num_digits-i-1); + value |= (nibble << offset); + } + + if (success) { + *value_out = value; + } + + return success; +} + +static bool parse_hex_values(const char* input, uint8_t num_values, uint8_t* values_out) { + uint8_t i; + for (i=0; i < num_values; i++) { + uint32_t value; + if (parse_hex_digits(input, 2, &value)) { + values_out[i] = (uint8_t)value; + } else { + return false; + } + input += 2; + } + + return true; +} + +static bool parse_dec_digit(const char* input, uint8_t* value_out) { + if (input[0] >= '0' && input[0] <= '9') { + *value_out = 0 + (input[0] - '0'); + return true; + } else { + return false; + } +} + +static bool slcan_process_config_command(const char* command, size_t len) { + bool success = false; + + // Validate command length + if (command[0] == 'M' || command[0] == 'm') { + if (len != 9) { + return false; + } + } else if (command[0] == 's') { + if (!((len == 5) || (len == 7))) { + return false; + } + } else if (command[0] == 'S' || command[0] == 'Z') { + if (len != 2) { + return false; + } + } else if (len != 1) { + return false; + } + + if (slcan_mode != MODE_RESET && command[0] != 'C') { + return false; + } else if (command[0] == 'C' && slcan_mode == MODE_RESET) { + return false; + } + + switch (command[0]) { + case 'S': { + switch (command[1]) { + case '3': + slcan_baudrate = 100000; + success = true; + break; + case '4': + slcan_baudrate = 125000; + success = true; + break; + case '5': + slcan_baudrate = 250000; + success = true; + break; + case '6': + slcan_baudrate = 500000; + success = true; + break; + case '8': + slcan_baudrate = 1000000; + success = true; + break; + default: + success = false; + break; + } + break; + } + case 'O': { + slcan_mode = MODE_NORMAL; + success = can_reconfigure(slcan_baudrate, slcan_mode); + break; + } + case 'L': { + slcan_mode = MODE_SILENT; + success = can_reconfigure(slcan_baudrate, slcan_mode); + break; + } + case 'l': { + slcan_mode = MODE_TEST_SILENT; + success = can_reconfigure(slcan_baudrate, slcan_mode); + break; + } + case 'C': { + slcan_mode = MODE_RESET; + success = can_reconfigure(slcan_baudrate, slcan_mode); + break; + } + // Dummy commands for compatibility + case 's': { + // TODO: implement direct BTR control + success = true; + break; + } + case 'M': + case 'm': { + // TODO: implement filtering + success = true; + break; + } + case 'Z': { + // TODO: implement timestamping + success = true; + break; + } + default: { + success = false; + break; + } + } + + return success; +} + +static bool slcan_process_transmit_command(const char* command, size_t len) { + bool success = false; + + if (slcan_mode == MODE_RESET || slcan_mode == MODE_SILENT) { + return false; + } + + bool valid_message = false; + CAN_Message msg; + if (command[0] == 't' || command[0] == 'T') { + msg.type = CANData; + msg.format = (command[0] == 't') ? CANStandard : CANExtended; + size_t id_len = msg.format == CANStandard ? 3 : 8; + if ((len >= id_len + 2) && + parse_hex_digits(&command[1], id_len, &msg.id) && + parse_dec_digit(&command[id_len + 1], &msg.len)) { + if ((len == id_len + 2 + 2*msg.len) && + (msg.len <= 8) && + parse_hex_values(&command[id_len + 2], msg.len, msg.data)) { + valid_message = true; + } + } + } else if (command[0] == 'r' || command[0] == 'R') { + msg.type = CANRemote; + msg.format = (command[0] == 'r') ? CANStandard : CANExtended; + size_t id_len = msg.format == CANStandard ? 3 : 8; + if ((len == id_len + 2) && + parse_hex_digits(&command[1], id_len, &msg.id) && + parse_dec_digit(&command[id_len + 1], &msg.len)) { + if (msg.len <= 8) { + valid_message = true; + } + } + } + + if (valid_message) { + if (command[0] == 'r' || command[0] == 't') { + vcdc_putchar('z'); + } else { + vcdc_putchar('Z'); + } + success = can_write(&msg); + } + + return success; +} + +static bool slcan_process_diagnostic_command(const char* command, size_t len) { + bool success = false; + + // Validate command length + if (command[0] == 'W') { + if (len != 5) { + return false; + } + } else { + if (len != 1) { + return false; + } + } + + switch (command[0]) { + case 'V': { + // Hardware and firmware version + success = true; + vcdc_print("V1111"); + break; + } + case 'v': { + // Firmware version + success = true; + vcdc_print("v11"); + break; + } + case 'N': { + // Serial number + success = true; + vcdc_print("C254"); + break; + } + case 'F': { + // Internal status register + success = true; + vcdc_print("F00"); + break; + } + case 'W': { + // Ignore the MCP2515 register write + success = true; + break; + } + default: { + success = false; + break; + } + } + + return success; +} + +bool slcan_exec_command(const char* command, size_t len) { + bool success = false; + + switch (command[0]) { + // Configuration commands + case 'S': + case 'O': + case 'L': + case 'l': + case 'C': + case 's': + case 'M': + case 'm': + case 'Z': { + success = slcan_process_config_command(command, len); + break; + } + // Transmission commands + case 't': + case 'T': + case 'r': + case 'R': { + success = slcan_process_transmit_command(command, len); + break; + } + // Diagnostic commands + case 'V': + case 'v': + case 'N': + case 'F': + case 'W': { + success = slcan_process_diagnostic_command(command, len); + break; + } + default: { + success = false; + break; + } + } + + return success; +} + +static size_t slcan_calc_message_length(const CAN_Message* msg) { + size_t len; + if (msg->format == CANStandard) { + len = 1 + 3 + 1 + (2 * msg->len) + 1; + } else { + len = 1 + 8 + 1 + (2 * msg->len) + 1; + } + + return len; +} + +bool slcan_output_messages(void) { + if (slcan_mode == MODE_RESET) { + return false; + } + bool read = false; + + size_t avail_buf_len = vcdc_send_buffer_space(); + while (!can_rx_buffer_empty()) { + // Examine the current message without dequeuing it + CAN_Message* msg = can_rx_buffer_peek(); + + // Stop processing messages if there's no more room + size_t msg_len = slcan_calc_message_length(msg); + if (msg_len > avail_buf_len) { + break; + } + + read = true; + if (msg->format == CANStandard) { + vcdc_print(msg->type == CANData ? "t" : "r"); + vcdc_print_hex_nibble((uint8_t)(msg->id >> 8)); + vcdc_print_hex_byte((uint8_t)(msg->id & 0xFF)); + vcdc_putchar('0' + msg->len); + } else { + vcdc_print(msg->type == CANData ? "T" : "R"); + vcdc_print_hex(msg->id); + vcdc_putchar('0' + msg->len); + } + uint8_t i = 0; + for (i=0; i < msg->len; i++) { + vcdc_print_hex_byte(msg->data[i]); + } + vcdc_putchar('\r'); + + avail_buf_len -= msg_len; + + // Release the message now that it's been processed + can_rx_buffer_pop(); + } + + return read; +} + +void slcan_app_setup(uint32_t baudrate, CanMode mode) { + slcan_mode = mode; + slcan_baudrate = baudrate; + can_setup(baudrate, mode); +} + +bool slcan_app_update(void) { + bool active = false; + + static char command_buffer[SLCAN_MAX_MESSAGE_LEN+1]; + static size_t command_len = 0; + static bool overflow = false; + + while (command_len < sizeof(command_buffer)) { + if (vcdc_recv_buffered((uint8_t*)&command_buffer[command_len], 1)) { + if (command_buffer[command_len] == '\r') { + if (overflow) { + // Reject the command and exit overflow + command_len = 0; + overflow = false; + vcdc_putchar(SLCAN_ERROR); + active = true; + } else { + // Process the command + command_buffer[command_len] = '\0'; + bool success = slcan_exec_command(command_buffer, command_len); + command_len = 0; + if (success) { + vcdc_putchar(SLCAN_OK); + } else { + vcdc_putchar(SLCAN_ERROR); + } + active = true; + } + break; + } else if (command_buffer[command_len] == '\n' && command_len == 0) { + // Ignore line-feed after carriage-return + continue; + } else if (overflow) { + // Ignore everything until the end of the command + continue; + } else { + // Add a character to the command + command_len++; + } + } else { + break; + } + } + + if (command_len >= sizeof(command_buffer)) { + overflow = true; + command_len = 0; + } + + if (slcan_output_messages()) { + active = true; + } + + + return active; +} diff --git a/src/CAN/slcan.h b/src/CAN/slcan.h new file mode 100644 index 0000000..6903bd5 --- /dev/null +++ b/src/CAN/slcan.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016, Devan Lai + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice + * appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SLCAN_H_INCLUDED +#define SLCAN_H_INCLUDED + +#include "can.h" + +#define SLCAN_MAX_MESSAGE_LEN 27 + +#define SLCAN_OK '\r' +#define SLCAN_ERROR '\a' + +extern bool slcan_output_messages(void); +extern bool slcan_exec_command(const char* command, size_t len); +extern void slcan_app_setup(uint32_t baudrate, CanMode mode); +extern bool slcan_app_update(void); + +#endif diff --git a/src/DAP42.c b/src/DAP42.c index 48b54d2..16dd542 100644 --- a/src/DAP42.c +++ b/src/DAP42.c @@ -34,6 +34,8 @@ #include "DAP/CMSIS_DAP_hal.h" #include "DFU/DFU.h" +#include "CAN/slcan.h" + #include "tick.h" #include "retarget.h" #include "console.h" @@ -119,6 +121,10 @@ int main(void) { dfu_setup(usbd_dev, &on_dfu_request); } + if (CAN_RX_AVAILABLE && VCDC_AVAILABLE) { + slcan_app_setup(500000, MODE_RESET); + } + tick_start(); /* Enable the watchdog to enable DFU recovery from bad firmware images */ @@ -133,10 +139,15 @@ int main(void) { cdc_uart_app_update(); } + if (CAN_RX_AVAILABLE && VCDC_AVAILABLE) { + slcan_app_update(); + } + if (VCDC_AVAILABLE) { vcdc_app_update(); } + // Handle DAP bool dap_active = DAP_app_update(); if (dap_active) { diff --git a/src/Makefile b/src/Makefile index 13a87c1..c33d4e8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -66,6 +66,7 @@ SRCS := $(wildcard *.c) SRCS += $(wildcard DAP/*.c) SRCS += $(wildcard USB/*.c) SRCS += $(wildcard DFU/*.c) +SRCS += $(wildcard CAN/*.c) SRCS += $(wildcard $(TARGET_COMMON_DIR)/*.c) SRCS += $(wildcard $(TARGET_COMMON_DIR)/DAP/*.c) SRCS += $(wildcard $(TARGET_COMMON_DIR)/USB/*.c) diff --git a/src/USB/cdc.c b/src/USB/cdc.c index 3bfa9b3..b5a7335 100644 --- a/src/USB/cdc.c +++ b/src/USB/cdc.c @@ -30,6 +30,9 @@ #if CDC_AVAILABLE +_Static_assert((CONSOLE_TX_BUFFER_SIZE >= USB_CDC_MAX_PACKET_SIZE), + "TX buffer too small"); + /* Descriptors */ const struct cdc_acm_functional_descriptors cdc_acm_functional_descriptors = { .header = { diff --git a/src/USB/composite_usb_conf.c b/src/USB/composite_usb_conf.c index 36738de..41383c9 100644 --- a/src/USB/composite_usb_conf.c +++ b/src/USB/composite_usb_conf.c @@ -66,7 +66,13 @@ _Static_assert((1 + NUM_OUT_ENDPOINTS <= 8), "Too many OUT endpoints for USB cor + CDC_PMA_USAGE \ + VCDC_PMA_USAGE) -_Static_assert((TOTAL_PMA_USAGE <= USB_PMA_SIZE), "USB packet memory area overallocated"); +#if CAN_RX_AVAILABLE && VCDC_AVAILABLE +#define MAX_USB_PMA_SIZE USB_PMA_SIZE_WITH_CAN +#else +#define MAX_USB_PMA_SIZE USB_PMA_SIZE +#endif + +_Static_assert((TOTAL_PMA_USAGE <= MAX_USB_PMA_SIZE), "USB packet memory area overallocated"); static const struct usb_device_descriptor dev = { .bLength = USB_DT_DEVICE_SIZE, diff --git a/src/USB/vcdc.c b/src/USB/vcdc.c index ed362aa..d31f8e4 100644 --- a/src/USB/vcdc.c +++ b/src/USB/vcdc.c @@ -65,22 +65,35 @@ static uint16_t vcdc_tx_tail = 0; static uint16_t vcdc_rx_head = 0; static uint16_t vcdc_rx_tail = 0; +_Static_assert((VCDC_RX_BUFFER_SIZE >= USB_VCDC_MAX_PACKET_SIZE), + "RX buffer too small"); + +#define IS_POW_OF_TWO(X) (((X) & ((X)-1)) == 0) +_Static_assert(IS_POW_OF_TWO(VCDC_RX_BUFFER_SIZE), + "Unmasked circular buffer size must be a power of two"); +_Static_assert(IS_POW_OF_TWO(VCDC_TX_BUFFER_SIZE), + "Unmasked circular buffer size must be a power of two"); +_Static_assert(VCDC_TX_BUFFER_SIZE <= UINT16_MAX/2, + "Buffer size too big for unmasked circular buffer"); +_Static_assert(VCDC_RX_BUFFER_SIZE <= UINT16_MAX/2, + "Buffer size too big for unmasked circular buffer"); + static bool vcdc_tx_buffer_empty(void) { return vcdc_tx_head == vcdc_tx_tail; } static bool vcdc_tx_buffer_full(void) { - return vcdc_tx_head == ((vcdc_tx_tail + 1) % VCDC_TX_BUFFER_SIZE); + return (uint16_t)(vcdc_tx_tail - vcdc_tx_head) == VCDC_TX_BUFFER_SIZE; } static void vcdc_tx_buffer_put(uint8_t data) { - vcdc_tx_buffer[vcdc_tx_tail] = data; - vcdc_tx_tail = (vcdc_tx_tail + 1) % VCDC_TX_BUFFER_SIZE; + vcdc_tx_buffer[vcdc_tx_tail % VCDC_TX_BUFFER_SIZE] = data; + vcdc_tx_tail++; } static uint8_t vcdc_tx_buffer_get(void) { - uint8_t data = vcdc_tx_buffer[vcdc_tx_head]; - vcdc_tx_head = (vcdc_tx_head + 1) % VCDC_TX_BUFFER_SIZE; + uint8_t data = vcdc_tx_buffer[vcdc_tx_head % VCDC_TX_BUFFER_SIZE]; + vcdc_tx_head++; return data; } @@ -89,17 +102,17 @@ static bool vcdc_rx_buffer_empty(void) { } static bool vcdc_rx_buffer_full(void) { - return vcdc_rx_head == ((vcdc_rx_tail + 1) % VCDC_RX_BUFFER_SIZE); + return (uint16_t)(vcdc_rx_tail - vcdc_rx_head) == VCDC_RX_BUFFER_SIZE; } static void vcdc_rx_buffer_put(uint8_t data) { - vcdc_rx_buffer[vcdc_rx_tail] = data; - vcdc_rx_tail = (vcdc_rx_tail + 1) % VCDC_RX_BUFFER_SIZE; + vcdc_rx_buffer[vcdc_rx_tail % VCDC_RX_BUFFER_SIZE] = data; + vcdc_rx_tail++; } static uint8_t vcdc_rx_buffer_get(void) { - uint8_t data = vcdc_rx_buffer[vcdc_rx_head]; - vcdc_rx_head = (vcdc_rx_head + 1) % VCDC_RX_BUFFER_SIZE; + uint8_t data = vcdc_rx_buffer[vcdc_rx_head % VCDC_RX_BUFFER_SIZE]; + vcdc_rx_head++; return data; } @@ -121,6 +134,10 @@ size_t vcdc_send_buffered(const uint8_t* data, size_t num_bytes) { return bytes_queued; } +size_t vcdc_send_buffer_space(void) { + return VCDC_TX_BUFFER_SIZE - (uint16_t)(vcdc_tx_tail - vcdc_tx_head); +} + /* User callbacks */ static GenericCallback vcdc_rx_callback = NULL; static GenericCallback vcdc_tx_callback = NULL; @@ -249,4 +266,51 @@ bool vcdc_app_update(void) { return active; } +void vcdc_putchar(const char c) { + if (!vcdc_tx_buffer_full()) { + vcdc_tx_buffer_put(c); + } +} + +void vcdc_print(const char* s) { + while (*s != '\0') { + vcdc_putchar(*s++); + } +} + +void vcdc_println(const char* s) { + while (*s != '\0') { + vcdc_putchar(*s++); + } + vcdc_putchar('\r'); + vcdc_putchar('\n'); +} + +void vcdc_print_hex_nibble(uint8_t x) { + uint8_t nibble = x & 0x0F; + char nibble_char; + if (nibble < 10) { + nibble_char = '0' + nibble; + } else { + nibble_char = 'A' + (nibble - 10); + } + vcdc_putchar(nibble_char); +} + +void vcdc_print_hex_byte(uint8_t x) { + vcdc_print_hex_nibble(x >> 4); + vcdc_print_hex_nibble(x); +} + +void vcdc_print_hex(uint32_t x) { + vcdc_print_hex_nibble((uint8_t)(x >> 28)); + vcdc_print_hex_nibble((uint8_t)(x >> 24)); + vcdc_print_hex_nibble((uint8_t)(x >> 20)); + vcdc_print_hex_nibble((uint8_t)(x >> 16)); + vcdc_print_hex_nibble((uint8_t)(x >> 12)); + vcdc_print_hex_nibble((uint8_t)(x >> 8)); + vcdc_print_hex_nibble((uint8_t)(x >> 4)); + vcdc_print_hex_nibble((uint8_t)(x >> 0)); +} + #endif diff --git a/src/USB/vcdc.h b/src/USB/vcdc.h index 1e9cb95..451ab83 100644 --- a/src/USB/vcdc.h +++ b/src/USB/vcdc.h @@ -31,4 +31,13 @@ extern bool vcdc_app_update(void); extern size_t vcdc_recv_buffered(uint8_t* data, size_t max_bytes); extern size_t vcdc_send_buffered(const uint8_t* data, size_t num_bytes); +extern size_t vcdc_send_buffer_space(void); + +extern void vcdc_putchar(const char c); +extern void vcdc_print(const char* s); +extern void vcdc_println(const char* s); +extern void vcdc_print_hex(uint32_t x); +extern void vcdc_print_hex_byte(uint8_t x); +extern void vcdc_print_hex_nibble(uint8_t x); + #endif diff --git a/src/console.c b/src/console.c index c143e1b..39c3592 100644 --- a/src/console.c +++ b/src/console.c @@ -205,7 +205,7 @@ uint8_t console_recv_blocking(void) { } void CONSOLE_USART_IRQ_NAME(void) { - if (usart_get_interrupt_source(CONSOLE_USART, USART_SR_TXE)) { + if (usart_get_flag(CONSOLE_USART, USART_SR_TXE)) { if (!console_tx_buffer_empty()) { usart_word_t buffered_byte = console_tx_buffer_get(); usart_send(CONSOLE_USART, buffered_byte); diff --git a/src/retarget.c b/src/retarget.c index b98b2d4..b8ae3e8 100644 --- a/src/retarget.c +++ b/src/retarget.c @@ -65,6 +65,22 @@ int _write(int file, char *ptr, int len) { #endif +void print_hex_nibble(uint8_t x) { + uint8_t nibble = x & 0x0F; + char nibble_char; + if (nibble < 10) { + nibble_char = '0' + nibble; + } else { + nibble_char = 'A' + (nibble - 10); + } + putchar(nibble_char); +} + +void print_hex_byte(uint8_t x) { + print_hex_nibble(x >> 4); + print_hex_nibble(x); +} + void print_hex(uint32_t x) { uint8_t i; for (i=8; i > 0; i--) { diff --git a/src/retarget.h b/src/retarget.h index 2117a48..5cd624f 100644 --- a/src/retarget.h +++ b/src/retarget.h @@ -28,6 +28,8 @@ extern void retarget(int file, uint32_t usart); extern int _write(int file, char *ptr, int len); extern void print_hex(uint32_t x); +extern void print_hex_nibble(uint8_t x); +extern void print_hex_byte(uint8_t x); extern void print(const char* s); extern void println(const char* s); diff --git a/src/stm32f042/brain3.3/config.h b/src/stm32f042/brain3.3/config.h index 4a3d8a1..fd20906 100644 --- a/src/stm32f042/brain3.3/config.h +++ b/src/stm32f042/brain3.3/config.h @@ -24,8 +24,9 @@ #define CAN_RX_AVAILABLE 1 #define CAN_TX_AVAILABLE 0 +#define CAN_NVIC_LINE NVIC_CEC_CAN_IRQ -#define VCDC_AVAILABLE 0 +#define VCDC_AVAILABLE 1 #define VCDC_TX_BUFFER_SIZE 256 #define VCDC_RX_BUFFER_SIZE 256 diff --git a/src/stm32f042/dap42/config.h b/src/stm32f042/dap42/config.h index 227e461..081d519 100644 --- a/src/stm32f042/dap42/config.h +++ b/src/stm32f042/dap42/config.h @@ -24,6 +24,7 @@ #define CAN_RX_AVAILABLE 1 #define CAN_TX_AVAILABLE 0 +#define CAN_NVIC_LINE NVIC_CEC_CAN_IRQ #define VCDC_AVAILABLE 0 #define VCDC_TX_BUFFER_SIZE 256 diff --git a/src/stm32f042/dap42k6u/config.h b/src/stm32f042/dap42k6u/config.h index 816382a..3e48d64 100644 --- a/src/stm32f042/dap42k6u/config.h +++ b/src/stm32f042/dap42k6u/config.h @@ -24,6 +24,7 @@ #define CAN_RX_AVAILABLE 1 #define CAN_TX_AVAILABLE 0 +#define CAN_NVIC_LINE NVIC_CEC_CAN_IRQ #define VCDC_AVAILABLE 0 #define VCDC_TX_BUFFER_SIZE 256 diff --git a/src/stm32f042/kitchen42/config.h b/src/stm32f042/kitchen42/config.h index 26da08a..308d50b 100644 --- a/src/stm32f042/kitchen42/config.h +++ b/src/stm32f042/kitchen42/config.h @@ -24,6 +24,7 @@ #define CAN_RX_AVAILABLE 1 #define CAN_TX_AVAILABLE 1 +#define CAN_NVIC_LINE NVIC_CEC_CAN_IRQ #define VCDC_AVAILABLE 1 #define VCDC_TX_BUFFER_SIZE 256 diff --git a/src/stm32f103/stlinkv2-1/target.c b/src/stm32f103/stlinkv2-1/target.c index 04c4a2f..f3f9dce 100644 --- a/src/stm32f103/stlinkv2-1/target.c +++ b/src/stm32f103/stlinkv2-1/target.c @@ -67,7 +67,7 @@ void gpio_setup(void) { gpio_clear(GPIOB, GPIO15); /* Enable MCO output */ - rcc_set_mco(RCC_CFGR_MCO_HSECLK); + rcc_set_mco(RCC_CFGR_MCO_HSE); gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO8); /* Setup LEDs as open-drain outputs */