diff --git a/src/BaseCAN.cpp b/src/BaseCAN.cpp index 8dc68c6..27c9010 100644 --- a/src/BaseCAN.cpp +++ b/src/BaseCAN.cpp @@ -58,7 +58,7 @@ CanTiming BaseCAN::solveCanTiming(uint32_t clockFreq, uint32_t bitrate, uint8_t } timing.prescaler = clockFreq / (bitrate * timeQuanta); - timing.sjw = 3; + timing.sjw = 1; timing.tseg1 = uint32_t(0.875 * timeQuanta) - 1; float samplePoint = (1.0 + timing.tseg1) / timeQuanta; @@ -107,9 +107,9 @@ void BaseCAN::failAndBlink(CanErrorType errorType) for (uint8_t i = 0; i < errorType; i++) { digitalWrite(LED_BUILTIN, HIGH); - delay(200); + delay(100); digitalWrite(LED_BUILTIN, LOW); - delay(200); + delay(100); } delay(1000); } diff --git a/src/BaseCAN.h b/src/BaseCAN.h index 2764699..3739989 100644 --- a/src/BaseCAN.h +++ b/src/BaseCAN.h @@ -28,6 +28,7 @@ enum CanMode enum CanErrorType { + CAN_ERROR_INIT_TIMEOUT = 0x10U, CAN_ERROR_CLOCK = 0x03U, CAN_ERROR_TIMING = 0x04U, CAN_ERROR_BITRATE_TOO_HIGH = 0x05U, diff --git a/src/SimpleCAN.h b/src/SimpleCAN.h index 8347323..4c1d996 100644 --- a/src/SimpleCAN.h +++ b/src/SimpleCAN.h @@ -4,6 +4,8 @@ #include "stm/can/CAN.h" #elif defined(HAL_FDCAN_MODULE_ENABLED) #include "stm/fdcan/CAN.h" +#elif defined(ARDUINO_ARCH_GD32) +#include "gd/can2b/CAN.h" #else #error "No CAN module is enabled, expecting a define for ARDUINO_ARCH_ESP32 | HAL_CAN_MODULE_ENABLED | HAL_FDCAN_MODULE_ENABLED" #endif \ No newline at end of file diff --git a/src/gd/can2b/CAN.cpp b/src/gd/can2b/CAN.cpp new file mode 100644 index 0000000..ab2ced1 --- /dev/null +++ b/src/gd/can2b/CAN.cpp @@ -0,0 +1,293 @@ +#if ARDUINO_ARCH_GD32 + +#include "./CAN.h" + +extern "C" void CAN0_RX0_IRQHandler(void); +extern "C" void CAN1_RX0_IRQHandler(void); + +void (*GD_CAN::callbackFunction_)() = nullptr; +can_receive_message_struct rxHeader_ = {}; +can_trasnmit_message_struct txHeader_ = {}; +uint32_t GD_CAN::hcan_ = 0; + +uint16_t GD_CAN::pinRX_; +uint16_t GD_CAN::pinTX_; +uint16_t GD_CAN::pinSHDN_; + +uint16_t can_prescaler; +int can_bitrate; +uint8_t can_tseg1; +uint8_t can_tseg2; +uint8_t can_sjw; + +GD_CAN::GD_CAN(uint16_t pinRX, uint16_t pinTX, uint16_t pinSHDN) : filter_(CanFilter(FilterType::ACCEPT_ALL)), started_(false) +{ + hcan_ = CAN0; + pinRX_ = pinRX; + pinTX_ = pinTX; + pinSHDN_ = pinSHDN; + + if (hcan_ == CAN0) + { + // rcu_apb1_clock_config(RCU_APB1_CKAHB_DIV2); + + // rcu_periph_clock_enable(RCU_CAN0); + // rcu_periph_clock_enable(RCU_GPIOB); + + // gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_8); + // gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); + + // pin_function((PinName)GD_CAN::pinRX_, pinmap_function((PinName)GD_CAN::pinRX_, PinMap_CAN_RD)); + // pin_function((PinName)GD_CAN::pinTX_, pinmap_function((PinName)GD_CAN::pinTX_, PinMap_CAN_TD)); + + // // Clear bits 13 and 14 first + // AFIO_PCF0 &= ~(AFIO_PCF0_CAN0_REMAP_BIT_13 | AFIO_PCF0_CAN0_REMAP_BIT_14); + + // // Set bit 14 only (for '10' binary configuration) + // AFIO->PCF0 |= AFIO_PCF0_CAN0_REMAP_BIT_14; + + // NVIC_SetPriority(USBD_LP_CAN0_RX0_IRQn, 0); + // nvic_irq_enable(USBD_LP_CAN0_RX0_IRQn, 3, 0); + } + else + { +#ifdef GD32F30X_CL + rcu_periph_clock_enable(RCU_CAN1); + nvic_irq_enable(CAN1_RX0_IRQn, 0, 0); +#endif + } + + if (pinSHDN != NC) + { + pinMode(pinSHDN, OUTPUT); + } + + mode = CAN_NORMAL; +} + +bool GD_CAN::begin(int bitrate) +{ + if (bitrate > 1000000) + { + failAndBlink(CAN_ERROR_BITRATE_TOO_HIGH); + } + + can_parameter_struct can_parameter; + + can_deinit(hcan_); + + rcu_apb1_clock_config(RCU_APB1_CKAHB_DIV2); + + rcu_periph_clock_enable(RCU_CAN0); + rcu_periph_clock_enable(RCU_GPIOB); + rcu_periph_clock_enable(RCU_AF); + + gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_8); + gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); + gpio_pin_remap_config(GPIO_CAN_PARTIAL_REMAP, ENABLE); + + uint32_t clockFreq = rcu_clock_freq_get(CK_APB1); + + CanTiming timing = solveCanTiming(clockFreq, bitrate); + + // nvic_irq_enable(USBD_LP_CAN0_RX0_IRQn, 3, 0); + can_struct_para_init(CAN_INIT_STRUCT, &can_parameter); + + // can_parameter.resync_jump_width=CAN_BT_SJW_1TQ; + can_parameter.time_segment_1 = CAN_BT_BS1_14TQ; + can_parameter.time_segment_2 = CAN_BT_BS2_1TQ; + can_parameter.prescaler = 15; + + can_parameter.resync_jump_width = (uint8_t)timing.sjw - 1; + can_parameter.time_segment_1 = (uint8_t)(timing.tseg1 - 1); + can_parameter.time_segment_2 = (uint8_t)(timing.tseg2 - 1); + can_parameter.prescaler = (uint16_t)timing.prescaler; + // working_mode seems badly named, but it's the only way to set loopback mode + can_parameter.working_mode = mode == CAN_LOOPBACK ? CAN_LOOPBACK_MODE : CAN_NORMAL_MODE; + + can_bitrate = bitrate; + + logStatus('i', + can_init(hcan_, &can_parameter)); + + if (pinSHDN_ != NC) + { + digitalWrite(pinSHDN_, LOW); + } + + started_ = true; + applyFilter(); + // todo: sort out return type + return true; +} + +void GD_CAN::end() +{ + if (pinSHDN_ != NC) + { + digitalWrite(pinSHDN_, HIGH); + } + can_deinit(hcan_); + started_ = false; +} + +void GD_CAN::filter(CanFilter filter) +{ + filter_ = filter; +} + +void GD_CAN::applyFilter() +{ + + // Unimplemented: the ability to filter on IDE or RTR bits (personally not that useful?!) + + static uint32_t filterIdHigh = 0xffff; + static uint32_t filterIdLow = 0xffff; // <-- all digits must match + static uint32_t filterMaskHigh = 0xffff; + static uint32_t filterMaskLow = 0xffff; + uint8_t filterIndex = 0; + + if (filter_.getType() == FilterType::MASK_STANDARD) + { + filterIdHigh = filter_.getIdentifier() << 5; // make room for IDE, RTR bits (+ 3 unused) + filterMaskHigh = filter_.getMask() << 5; + filterIdLow = 0x0000; + filterMaskLow = 0x0000; + } + else if (filter_.getType() == FilterType::MASK_EXTENDED) + { + filterIdLow = (filter_.getIdentifier() & 0x0000ffff) << 3; // make room for IDE, RTR bit (+ 1 unused bit) + filterIdHigh = filter_.getIdentifier() >> 16; + filterMaskLow = (filter_.getMask() & 0x0000ffff) << 3; + filterMaskHigh = filter_.getMask() >> 16; + } + else if (filter_.getType() == FilterType::ACCEPT_ALL) + { + filterIdLow = 0x0000; + filterIdHigh = 0x0000; //<- no digits have to match + filterMaskLow = 0x0000; + filterMaskHigh = 0x0000; + } + +#ifdef CAN_DEBUG + _Serial->println("###### FILTER ######"); + _Serial->print("filterType: "); + _Serial->print(filter_.getType()); + _Serial->print(" (identifier: "); + _Serial->print(filter_.getIdentifier(), HEX); + _Serial->print(", mask: "); + _Serial->print(filter_.getMask(), HEX); + _Serial->println(")"); + _Serial->print("registers (filterIdLow: "); + _Serial->print(filterIdLow, HEX); + _Serial->print(", filterIdHigh: "); + _Serial->print(filterIdHigh, HEX); + _Serial->print(", filterMaskLow: "); + _Serial->print(filterMaskLow, HEX); + _Serial->print(", filterMaskHigh: "); + _Serial->print(filterMaskHigh, HEX); + _Serial->println(")"); +#endif + + can_filter_parameter_struct can_filter; + + can_struct_para_init(CAN_FILTER_STRUCT, &can_filter); + + can_filter.filter_number = (hcan_ == CAN0) ? 0 : 15; + can_filter.filter_mode = CAN_FILTERMODE_MASK; + can_filter.filter_bits = CAN_FILTERBITS_32BIT; + can_filter.filter_list_high = filterIdHigh; + can_filter.filter_list_low = filterIdLow; + can_filter.filter_mask_high = filterMaskHigh; + can_filter.filter_mask_low = filterMaskLow; + can_filter.filter_fifo_number = filterIndex; + can_filter.filter_enable = ENABLE; + // logStatus('f', + can_filter_init(&can_filter); + + // todo: support interrupt approach (alternative to polling) + // can_interrupt_enable(hcan_, CAN_INT_RFNE0 | CAN_INT_TME); +} + +int GD_CAN::write(CanMsg const &txMsg) +{ +#ifdef CAN_DEBUG + _Serial->print("tx: "); + txMsg.printTo(*_Serial); + _Serial->println(); + +#endif + + txHeader_ = { + .tx_sfid = txMsg.isExtendedId() ? 0 : txMsg.getStandardId(), + .tx_efid = txMsg.isExtendedId() ? txMsg.getExtendedId() : 0, + .tx_ff = txMsg.isExtendedId() ? (uint8_t)CAN_FF_EXTENDED : (uint8_t)CAN_FF_STANDARD, + .tx_ft = txMsg.isRTR() ? (uint8_t)CAN_FT_REMOTE : (uint8_t)CAN_FT_DATA, + .tx_dlen = txMsg.data_length, + }; + + if (txMsg.data_length > 0) + { + memcpy(txHeader_.tx_data, txMsg.data, txMsg.data_length); + } + + return logStatus('t', + can_message_transmit(hcan_, &txHeader_)); +} + +CanMsg GD_CAN::read() +{ + + memset(&rxHeader_, 0, sizeof(rxHeader_)); // <-zero before reusing rxHeader_ + + can_message_receive(hcan_, CAN_FIFO0, &rxHeader_); + can_error_enum err = can_error_get(hcan_); + CanMsg const rxMsg( + (rxHeader_.rx_ff == CAN_FF_EXTENDED) ? CanExtendedId(rxHeader_.rx_efid, rxHeader_.rx_ft == CAN_FT_REMOTE) + : CanStandardId(rxHeader_.rx_sfid, rxHeader_.rx_ft == CAN_FT_REMOTE), + rxHeader_.rx_dlen, + rxHeader_.rx_data); + +#ifdef CAN_DEBUG + _Serial->print("rx: "); + rxMsg.printTo(*_Serial); + _Serial->println(); +#endif + return rxMsg; +} + +size_t GD_CAN::available() +{ + return can_receive_message_length_get(hcan_, CAN_FIFO0); +} + +void CAN0_RX0_IRQHandler(void) +{ + can_receive_message_struct receive_message; + memset(&receive_message, 0, sizeof(receive_message)); + can_message_receive(CAN0, CAN_FIFO0, &receive_message); +} + +CanStatus GD_CAN::logStatus(char op, uint32_t status) +{ +#ifdef CAN_DEBUG + if (status != 0x0) + { + _Serial->print("ERROR ("); + _Serial->print(op); + _Serial->print(") "); + _Serial->print(status); + } +#endif + return status == 0x0 ? CAN_OK : CAN_ERROR; +} + +#if CAN_HOWMANY > 0 +GD_CAN CAN(PIN_CAN0_RX, PIN_CAN0_TX, PIN_CAN0_SHDN); +#endif + +// #if CAN_HOWMANY > 1 +// GD_CAN CAN1(PIN_CAN1_RX, PIN_CAN1_TX, PIN_CAN1_SHDN); +// #endif + +#endif \ No newline at end of file diff --git a/src/gd/can2b/CAN.h b/src/gd/can2b/CAN.h new file mode 100644 index 0000000..b7e6d7b --- /dev/null +++ b/src/gd/can2b/CAN.h @@ -0,0 +1,53 @@ +#pragma once + +#if ARDUINO_ARCH_GD32 + +#include "Arduino.h" +#include "BaseCAN.h" +#include "gd32f30x.h" +#include "gd32f30x_can.h" +#include "gd32f30x_gpio.h" + +class GD_CAN : public BaseCAN +{ + +public: + GD_CAN(uint16_t pinRX, uint16_t pinTX, uint16_t pinSHDN = NC); + + bool begin(int can_bitrate) override; + void end() override; + + void filter(CanFilter filter) override; + + // CanStatus subscribe(void (*_messageReceiveCallback)() = nullptr); + // CanStatus unsubscribe(); + + int write(CanMsg const &msg) override; + CanMsg read() override; + size_t available() override; + + static uint32_t hcan_; + static void (*callbackFunction_)(); + + static uint16_t pinRX_; + static uint16_t pinTX_; + static uint16_t pinSHDN_; + +private: + bool started_ = false; + void applyFilter(); // filter is applied after begin() is called + CanFilter filter_; + can_receive_message_struct rxHeader_; + can_trasnmit_message_struct txHeader_; + CanStatus logStatus(char op, uint32_t status); +}; + +#if CAN_HOWMANY > 0 +extern GD_CAN CAN; +#endif + +// #if CAN_HOWMANY > 1 +// extern GD_CAN CAN1; +// #endif + +#endif \ No newline at end of file diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..3f34880 --- /dev/null +++ b/test/README.md @@ -0,0 +1,15 @@ +## Unit tests - on device + +The tests in `test\embedded` run 'on device'. + +To run these tests follow: +```sh +mkdir SimpleCAN_test +cd SimpleCAN_test +mkdir lib +cd lib +git clone git clone git@github.com:owennewo/SimpleCAN.git +cd .. +cp lib/SimpleCAN/examples/platformio.ini . +``` +then edit `default_envs` in `./platformio.ini` to choose the target you wish to test