From d6cf87291cbfa838814547acefbd5d0cd0b212f3 Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 17 Mar 2022 14:16:47 +0100 Subject: [PATCH] intermediate: fork of esp32 nmea2000 --- lib/nmea2000esp32/ESP32_CAN_def.h | 291 ++++++++++++++++++++++ lib/nmea2000esp32/NMEA2000_esp32.cpp | 348 +++++++++++++++++++++++++++ lib/nmea2000esp32/NMEA2000_esp32.h | 98 ++++++++ platformio.ini | 2 +- src/main.cpp | 24 +- 5 files changed, 758 insertions(+), 5 deletions(-) create mode 100644 lib/nmea2000esp32/ESP32_CAN_def.h create mode 100644 lib/nmea2000esp32/NMEA2000_esp32.cpp create mode 100644 lib/nmea2000esp32/NMEA2000_esp32.h diff --git a/lib/nmea2000esp32/ESP32_CAN_def.h b/lib/nmea2000esp32/ESP32_CAN_def.h new file mode 100644 index 0000000..98eede4 --- /dev/null +++ b/lib/nmea2000esp32/ESP32_CAN_def.h @@ -0,0 +1,291 @@ +/** + * @section License + * + * The MIT License (MIT) + * + * Copyright (c) 2017, Thomas Barth, barth-dev.de + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __DRIVERS_CAN_REGDEF_H_ +#define __DRIVERS_CAN_REGDEF_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + CAN_SPEED_100KBPS=100, /**< \brief CAN Node runs at 100kBit/s. */ + CAN_SPEED_125KBPS=125, /**< \brief CAN Node runs at 125kBit/s. */ + CAN_SPEED_250KBPS=250, /**< \brief CAN Node runs at 250kBit/s. */ + CAN_SPEED_500KBPS=500, /**< \brief CAN Node runs at 500kBit/s. */ + CAN_SPEED_800KBPS=800, /**< \brief CAN Node runs at 800kBit/s. */ + CAN_SPEED_1000KBPS=1000 /**< \brief CAN Node runs at 1000kBit/s. */ +}CAN_speed_t; + +/** + * \brief CAN frame type (standard/extended) + */ +typedef enum { + CAN_frame_std=0, /**< Standard frame, using 11 bit identifer. */ + CAN_frame_ext=1 /**< Extended frame, using 29 bit identifer. */ +}CAN_frame_format_t; + +/** + * \brief CAN RTR + */ +typedef enum { + CAN_no_RTR=0, /**< No RTR frame. */ + CAN_RTR=1 /**< RTR frame. */ +}CAN_RTR_t; + +/** \brief Frame information record type */ +typedef union{uint32_t U; /**< \brief Unsigned access */ + struct { + uint8_t DLC:4; /**< \brief [3:0] DLC, Data length container */ + unsigned int unknown_2:2; /**< \brief \internal unknown */ + CAN_RTR_t RTR:1; /**< \brief [6:6] RTR, Remote Transmission Request */ + CAN_frame_format_t FF:1; /**< \brief [7:7] Frame Format, see# CAN_frame_format_t*/ + unsigned int reserved_24:24; /**< \brief \internal Reserved */ + } B; +} CAN_FIR_t; + +/** \brief Start address of CAN registers */ +#define MODULE_CAN ((volatile CAN_Module_t *)0x3ff6b000) + +/** \brief Get standard message ID */ +#define _CAN_GET_STD_ID (((uint32_t)MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.STD.ID[0] << 3) | \ + (MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.STD.ID[1] >> 5)) + +/** \brief Get extended message ID */ +#define _CAN_GET_EXT_ID (((uint32_t)MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[0] << 21) | \ + (MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[1] << 13) | \ + (MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[2] << 5) | \ + (MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[3] >> 3 )) + +/** \brief Set standard message ID */ +#define _CAN_SET_STD_ID(x) MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.STD.ID[0] = ((x) >> 3); \ + MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.STD.ID[1] = ((x) << 5); + +/** \brief Set extended message ID */ +#define _CAN_SET_EXT_ID(x) MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[0] = ((x) >> 21); \ + MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[1] = ((x) >> 13); \ + MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[2] = ((x) >> 5); \ + MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[3] = ((x) << 3); \ + +/** \brief Interrupt status register */ +typedef enum { + __CAN_IRQ_RX= BIT(0), /**< \brief RX Interrupt */ + __CAN_IRQ_TX= BIT(1), /**< \brief TX Interrupt */ + __CAN_IRQ_ERR= BIT(2), /**< \brief Error Interrupt */ + __CAN_IRQ_DATA_OVERRUN= BIT(3), /**< \brief Date Overrun Interrupt */ + __CAN_IRQ_WAKEUP= BIT(4), /**< \brief Wakeup Interrupt */ + __CAN_IRQ_ERR_PASSIVE= BIT(5), /**< \brief Passive Error Interrupt */ + __CAN_IRQ_ARB_LOST= BIT(6), /**< \brief Arbitration lost interrupt */ + __CAN_IRQ_BUS_ERR= BIT(7), /**< \brief Bus error Interrupt */ +}__CAN_IRQ_t; + + +/** \brief OCMODE options. */ +typedef enum { + __CAN_OC_BOM= 0b00, /**< \brief bi-phase output mode */ + __CAN_OC_TOM= 0b01, /**< \brief test output mode */ + __CAN_OC_NOM= 0b10, /**< \brief normal output mode */ + __CAN_OC_COM= 0b11, /**< \brief clock output mode */ +}__CAN_OCMODE_t; + + +/** + * CAN controller (SJA1000). + */ +typedef struct { + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int RM:1; /**< \brief MOD.0 Reset Mode */ + unsigned int LOM:1; /**< \brief MOD.1 Listen Only Mode */ + unsigned int STM:1; /**< \brief MOD.2 Self Test Mode */ + unsigned int AFM:1; /**< \brief MOD.3 Acceptance Filter Mode */ + unsigned int SM:1; /**< \brief MOD.4 Sleep Mode */ + unsigned int reserved_27:27; /**< \brief \internal Reserved */ + } B; + } MOD; + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int TR:1; /**< \brief CMR.0 Transmission Request */ + unsigned int AT:1; /**< \brief CMR.1 Abort Transmission */ + unsigned int RRB:1; /**< \brief CMR.2 Release Receive Buffer */ + unsigned int CDO:1; /**< \brief CMR.3 Clear Data Overrun */ + unsigned int GTS:1; /**< \brief CMR.4 Go To Sleep */ + unsigned int reserved_27:27; /**< \brief \internal Reserved */ + } B; + } CMR; + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int RBS:1; /**< \brief SR.0 Receive Buffer Status */ + unsigned int DOS:1; /**< \brief SR.1 Data Overrun Status */ + unsigned int TBS:1; /**< \brief SR.2 Transmit Buffer Status */ + unsigned int TCS:1; /**< \brief SR.3 Transmission Complete Status */ + unsigned int RS:1; /**< \brief SR.4 Receive Status */ + unsigned int TS:1; /**< \brief SR.5 Transmit Status */ + unsigned int ES:1; /**< \brief SR.6 Error Status */ + unsigned int BS:1; /**< \brief SR.7 Bus Status */ + unsigned int reserved_24:24; /**< \brief \internal Reserved */ + } B; + } SR; + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int RI:1; /**< \brief IR.0 Receive Interrupt */ + unsigned int TI:1; /**< \brief IR.1 Transmit Interrupt */ + unsigned int EI:1; /**< \brief IR.2 Error Interrupt */ + unsigned int DOI:1; /**< \brief IR.3 Data Overrun Interrupt */ + unsigned int WUI:1; /**< \brief IR.4 Wake-Up Interrupt */ + unsigned int EPI:1; /**< \brief IR.5 Error Passive Interrupt */ + unsigned int ALI:1; /**< \brief IR.6 Arbitration Lost Interrupt */ + unsigned int BEI:1; /**< \brief IR.7 Bus Error Interrupt */ + unsigned int reserved_24:24; /**< \brief \internal Reserved */ + } B; + } IR; + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int RIE:1; /**< \brief IER.0 Receive Interrupt Enable */ + unsigned int TIE:1; /**< \brief IER.1 Transmit Interrupt Enable */ + unsigned int EIE:1; /**< \brief IER.2 Error Interrupt Enable */ + unsigned int DOIE:1; /**< \brief IER.3 Data Overrun Interrupt Enable */ + unsigned int WUIE:1; /**< \brief IER.4 Wake-Up Interrupt Enable */ + unsigned int EPIE:1; /**< \brief IER.5 Error Passive Interrupt Enable */ + unsigned int ALIE:1; /**< \brief IER.6 Arbitration Lost Interrupt Enable */ + unsigned int BEIE:1; /**< \brief IER.7 Bus Error Interrupt Enable */ + unsigned int reserved_24:24; /**< \brief \internal Reserved */ + } B; + } IER; + uint32_t RESERVED0; + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int BRP:6; /**< \brief BTR0[5:0] Baud Rate Prescaler */ + unsigned int SJW:2; /**< \brief BTR0[7:6] Synchronization Jump Width*/ + unsigned int reserved_24:24; /**< \brief \internal Reserved */ + } B; + } BTR0; + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int TSEG1:4; /**< \brief BTR1[3:0] Timing Segment 1 */ + unsigned int TSEG2:3; /**< \brief BTR1[6:4] Timing Segment 2*/ + unsigned int SAM:1; /**< \brief BTR1.7 Sampling*/ + unsigned int reserved_24:24; /**< \brief \internal Reserved */ + } B; + } BTR1; + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int OCMODE:2; /**< \brief OCR[1:0] Output Control Mode, see # */ + unsigned int OCPOL0:1; /**< \brief OCR.2 Output Control Polarity 0 */ + unsigned int OCTN0:1; /**< \brief OCR.3 Output Control Transistor N0 */ + unsigned int OCTP0:1; /**< \brief OCR.4 Output Control Transistor P0 */ + unsigned int OCPOL1:1; /**< \brief OCR.5 Output Control Polarity 1 */ + unsigned int OCTN1:1; /**< \brief OCR.6 Output Control Transistor N1 */ + unsigned int OCTP1:1; /**< \brief OCR.7 Output Control Transistor P1 */ + unsigned int reserved_24:24; /**< \brief \internal Reserved */ + } B; + } OCR; + uint32_t RESERVED1[2]; + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int ALC:8; /**< \brief ALC[7:0] Arbitration Lost Capture */ + unsigned int reserved_24:24; /**< \brief \internal Reserved */ + } B; + } ALC; + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int ECC:8; /**< \brief ECC[7:0] Error Code Capture */ + unsigned int reserved_24:24; /**< \brief \internal Reserved */ + } B; + } ECC; + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int EWLR:8; /**< \brief EWLR[7:0] Error Warning Limit */ + unsigned int reserved_24:24; /**< \brief \internal Reserved */ + } B; + } EWLR; + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int RXERR:8; /**< \brief RXERR[7:0] Receive Error Counter */ + unsigned int reserved_24:24; /**< \brief \internal Reserved */ + } B; + } RXERR; + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int TXERR:8; /**< \brief TXERR[7:0] Transmit Error Counter */ + unsigned int reserved_24:24; /**< \brief \internal Reserved */ + } B; + } TXERR; + + union { + struct { + uint32_t CODE[4]; /**< \brief Acceptance Message ID */ + uint32_t MASK[4]; /**< \brief Acceptance Mask */ + uint32_t RESERVED2[5]; + } ACC; /**< \brief Acceptance filtering */ + struct { + CAN_FIR_t FIR; /**< \brief Frame information record */ + union{ + struct { + uint32_t ID[2]; /**< \brief Standard frame message-ID*/ + uint32_t data[8]; /**< \brief Standard frame payload */ + uint32_t reserved[2]; + } STD; /**< \brief Standard frame format */ + struct { + uint32_t ID[4]; /**< \brief Extended frame message-ID*/ + uint32_t data[8]; /**< \brief Extended frame payload */ + } EXT; /**< \brief Extended frame format */ + }TX_RX; /**< \brief RX/TX interface */ + }FCTRL; /**< \brief Function control regs */ + } MBX_CTRL; /**< \brief Mailbox control */ + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int RMC:8; /**< \brief RMC[7:0] RX Message Counter */ + unsigned int reserved_24:24; /**< \brief \internal Reserved Enable */ + } B; + } RMC; + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int RBSA:8; /**< \brief RBSA[7:0] RX Buffer Start Address */ + unsigned int reserved_24:24; /**< \brief \internal Reserved Enable */ + } B; + } RBSA; + union{uint32_t U; /**< \brief Unsigned access */ + struct { + unsigned int COD:3; /**< \brief CDR[2:0] CLKOUT frequency selector based of fOSC*/ + unsigned int COFF:1; /**< \brief CDR.3 CLKOUT off*/ + unsigned int reserved_1:1; /**< \brief \internal Reserved */ + unsigned int RXINTEN:1; /**< \brief CDR.5 This bit allows the TX1 output to be used as a dedicated receive interrupt output*/ + unsigned int CBP:1; /**< \brief CDR.6 allows to bypass the CAN input comparator and is only possible in reset mode.*/ + unsigned int CAN_M:1; /**< \brief CDR.7 If CDR.7 is at logic 0 the CAN controller operates in BasicCAN mode. If set to logic 1 the CAN controller operates in PeliCAN mode. Write access is only possible in reset mode*/ + unsigned int reserved_24:24; /**< \brief \internal Reserved */ + } B; + } CDR; + uint32_t IRAM[2]; +}CAN_Module_t; + +#ifdef __cplusplus +} +#endif + +#endif /* __DRIVERS_CAN_REGDEF_H_ */ diff --git a/lib/nmea2000esp32/NMEA2000_esp32.cpp b/lib/nmea2000esp32/NMEA2000_esp32.cpp new file mode 100644 index 0000000..3e4d390 --- /dev/null +++ b/lib/nmea2000esp32/NMEA2000_esp32.cpp @@ -0,0 +1,348 @@ +/* +NMEA2000_esp32.cpp + +Copyright (c) 2015-2020 Timo Lappalainen, Kave Oy, www.kave.fi + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Inherited NMEA2000 object for ESP32 modules. See also NMEA2000 library. + +Thanks to Thomas Barth, barth-dev.de, who has written ESP32 CAN code. To avoid extra +libraries, I implemented his code directly to the NMEA2000_esp32 to avoid extra +can.h library, which may cause even naming problem. +*/ + +#include "driver/periph_ctrl.h" + +#include "soc/dport_reg.h" +#include "NMEA2000_esp32.h" + +bool tNMEA2000_esp32::CanInUse=false; +tNMEA2000_esp32 *pNMEA2000_esp32=0; + +void ESP32Can1Interrupt(void *); + +#define ECDEBUG(fmt,args...) if(debugStream){debugStream->printf(fmt, ## args);} + +//***************************************************************************** +tNMEA2000_esp32::tNMEA2000_esp32(gpio_num_t _TxPin, + gpio_num_t _RxPin, + Print *dbg) : + tNMEA2000(), IsOpen(false), + speed(CAN_SPEED_250KBPS), TxPin(_TxPin), RxPin(_RxPin), + RxQueue(NULL), TxQueue(NULL) { + debugStream=dbg; +} + +//***************************************************************************** +bool tNMEA2000_esp32::CANSendFrame(unsigned long id, unsigned char len, const unsigned char *buf, bool /*wait_sent*/) { + if ( uxQueueSpacesAvailable(TxQueue)==0 ) return false; // can not send to queue + + tCANFrame frame; + frame.id=id; + frame.len=len>8?8:len; + memcpy(frame.buf,buf,len); + ECDEBUG("CanSendFrame Error TX %d\n",MODULE_CAN->TXERR.U); + ECDEBUG("CanSendFrame Error Overrun %d\n",errOverrun); + ECDEBUG("CanSendFrame Error Arbitration %d\n",errArb); + ECDEBUG("CanSendFrame Error Bus %d\n",errBus); + ECDEBUG("CanSendFrame Error Recovery %d\n",errRecovery); + xQueueSendToBack(TxQueue,&frame,0); // Add frame to queue + if ( MODULE_CAN->SR.B.TBS==0 ) { + ECDEBUG("CanSendFrame: wait for ISR to send %d\n",frame.id); + return true; // Currently sending, ISR takes care of sending + } + + if ( MODULE_CAN->SR.B.TBS==1 ) { // Check again and restart send, if is not going on + xQueueReceive(TxQueue,&frame,0); + ECDEBUG("CanSendFrame: send direct %d\n",frame.id); + CAN_send_frame(frame); + } + + return true; +} + +//***************************************************************************** +void tNMEA2000_esp32::InitCANFrameBuffers() { + if (MaxCANReceiveFrames<10 ) MaxCANReceiveFrames=50; // ESP32 has plenty of RAM + if (MaxCANSendFrames<10 ) MaxCANSendFrames=40; + uint16_t CANGlobalBufSize=MaxCANSendFrames-4; + MaxCANSendFrames=4; // we do not need much libary internal buffer since driver has them. + RxQueue=xQueueCreate(MaxCANReceiveFrames,sizeof(tCANFrame)); + TxQueue=xQueueCreate(CANGlobalBufSize,sizeof(tCANFrame)); + + tNMEA2000::InitCANFrameBuffers(); // call main initialization +} + +//***************************************************************************** +bool tNMEA2000_esp32::CANOpen() { + if (IsOpen) return true; + + if (CanInUse) return false; // currently prevent accidental second instance. Maybe possible in future. + + pNMEA2000_esp32=this; + IsOpen=true; + CAN_init(); + + CanInUse=IsOpen; + + return IsOpen; +} + +//***************************************************************************** +bool tNMEA2000_esp32::CANGetFrame(unsigned long &id, unsigned char &len, unsigned char *buf) { + bool HasFrame=false; + tCANFrame frame; + + //receive next CAN frame from queue + if ( xQueueReceive(RxQueue,&frame, 0)==pdTRUE ) { + HasFrame=true; + id=frame.id; + len=frame.len; + memcpy(buf,frame.buf,frame.len); + } + + return HasFrame; +} + +//***************************************************************************** +void tNMEA2000_esp32::CAN_init() { + + //Time quantum + double __tq; + + + // A soft reset of the ESP32 leaves it's CAN controller in an undefined state so a reset is needed. + // Reset CAN controller to same state as it would be in after a power down reset. + periph_module_reset(PERIPH_CAN_MODULE); + + + //enable module + DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_CAN_CLK_EN); + DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_CAN_RST); + + //configure RX pin + gpio_set_direction(RxPin,GPIO_MODE_INPUT); + gpio_matrix_in(RxPin,CAN_RX_IDX,0); + gpio_pad_select_gpio(RxPin); + + //set to PELICAN mode + MODULE_CAN->CDR.B.CAN_M=0x1; + + //synchronization jump width is the same for all baud rates + MODULE_CAN->BTR0.B.SJW =0x1; + + //TSEG2 is the same for all baud rates + MODULE_CAN->BTR1.B.TSEG2 =0x1; + + //select time quantum and set TSEG1 + switch (speed) { + case CAN_SPEED_1000KBPS: + MODULE_CAN->BTR1.B.TSEG1 =0x4; + __tq = 0.125; + break; + + case CAN_SPEED_800KBPS: + MODULE_CAN->BTR1.B.TSEG1 =0x6; + __tq = 0.125; + break; + default: + MODULE_CAN->BTR1.B.TSEG1 =0xc; + __tq = ((float)1000/speed) / 16; + } + + //set baud rate prescaler + MODULE_CAN->BTR0.B.BRP=(uint8_t)round((((APB_CLK_FREQ * __tq) / 2) - 1)/1000000)-1; + + /* Set sampling + * 1 -> triple; the bus is sampled three times; recommended for low/medium speed buses (class A and B) where filtering spikes on the bus line is beneficial + * 0 -> single; the bus is sampled once; recommended for high speed buses (SAE class C)*/ + MODULE_CAN->BTR1.B.SAM =0x1; + + //enable all interrupts + MODULE_CAN->IER.U = 0xef; // bit 0x10 contains Baud Rate Prescaler Divider (BRP_DIV) bit + + //no acceptance filtering, as we want to fetch all messages + MODULE_CAN->MBX_CTRL.ACC.CODE[0] = 0; + MODULE_CAN->MBX_CTRL.ACC.CODE[1] = 0; + MODULE_CAN->MBX_CTRL.ACC.CODE[2] = 0; + MODULE_CAN->MBX_CTRL.ACC.CODE[3] = 0; + MODULE_CAN->MBX_CTRL.ACC.MASK[0] = 0xff; + MODULE_CAN->MBX_CTRL.ACC.MASK[1] = 0xff; + MODULE_CAN->MBX_CTRL.ACC.MASK[2] = 0xff; + MODULE_CAN->MBX_CTRL.ACC.MASK[3] = 0xff; + + //set to normal mode + MODULE_CAN->OCR.B.OCMODE=__CAN_OC_NOM; + + //clear error counters + MODULE_CAN->TXERR.U = 0; + MODULE_CAN->RXERR.U = 0; + (void)MODULE_CAN->ECC; + + //clear interrupt flags + (void)MODULE_CAN->IR.U; + + //install CAN ISR + esp_intr_alloc(ETS_CAN_INTR_SOURCE,0,ESP32Can1Interrupt,NULL,NULL); + + //configure TX pin + // We do late configure, since some initialization above caused CAN Tx flash + // shortly causing one error frame on startup. By setting CAN pin here + // it works right. + gpio_set_direction(TxPin,GPIO_MODE_OUTPUT); + gpio_matrix_out(TxPin,CAN_TX_IDX,0,0); + gpio_pad_select_gpio(TxPin); + + //Showtime. Release Reset Mode. + MODULE_CAN->MOD.B.RM = 0; +} + +//***************************************************************************** +void tNMEA2000_esp32::CAN_read_frame() { + tCANFrame frame; + CAN_FIR_t FIR; + + //get FIR + FIR.U=MODULE_CAN->MBX_CTRL.FCTRL.FIR.U; + frame.len=FIR.B.DLC>8?8:FIR.B.DLC; + + // Handle only extended frames + if (FIR.B.FF==CAN_frame_ext) { //extended frame + //Get Message ID + frame.id = _CAN_GET_EXT_ID; + + //deep copy data bytes + for( size_t i=0; iMBX_CTRL.FCTRL.TX_RX.EXT.data[i]; + } + + //send frame to input queue + xQueueSendToBackFromISR(RxQueue,&frame,0); + } + + //Let the hardware know the frame has been read. + MODULE_CAN->CMR.B.RRB=1; +} + +//***************************************************************************** +void tNMEA2000_esp32::CAN_send_frame(tCANFrame &frame) { + CAN_FIR_t FIR; + + FIR.U=0; + FIR.B.DLC=frame.len>8?8:frame.len; + FIR.B.FF=CAN_frame_ext; + + //copy frame information record + MODULE_CAN->MBX_CTRL.FCTRL.FIR.U=FIR.U; + + //Write message ID + _CAN_SET_EXT_ID(frame.id); + + // Copy the frame data to the hardware + for ( size_t i=0; iMBX_CTRL.FCTRL.TX_RX.EXT.data[i]=frame.buf[i]; + } + + // Transmit frame + MODULE_CAN->CMR.B.TR=1; +} +static int tx_error_count=0; +#define CAN_MAX_TX_RETRY 12 + +void tNMEA2000_esp32::CAN_bus_off_recovery(){ + MODULE_CAN->MOD.B.RM = 1; + MODULE_CAN->TXERR.U = 127; + MODULE_CAN->RXERR.U = 0; + MODULE_CAN->MOD.B.RM = 0; +} + +//***************************************************************************** +void tNMEA2000_esp32::InterruptHandler() { + //Interrupt flag buffer + uint32_t interrupt; + + // Read interrupt status and clear flags + interrupt = (MODULE_CAN->IR.U & 0xff); + + // Handle TX complete interrupt + if ((interrupt & __CAN_IRQ_TX) != 0) { + tCANFrame frame; + if ( (xQueueReceiveFromISR(TxQueue,&frame,NULL)==pdTRUE) ) { + CAN_send_frame(frame); + } + } + + // Handle RX frame available interrupt + if ((interrupt & __CAN_IRQ_RX) != 0) { + CAN_read_frame(); + } + + // Handle error interrupts. + if ((interrupt & (__CAN_IRQ_ERR //0x4 + + | __CAN_IRQ_WAKEUP //0x10 + | __CAN_IRQ_ERR_PASSIVE //0x20 + + + )) != 0) { + /*handler*/ + + } + //https://www.esp32.com/viewtopic.php?t=5010 + // Handle error interrupts. + if (interrupt & __CAN_IRQ_DATA_OVERRUN ) { //0x08 + errOverrun++; + MODULE_CAN->CMR.B.CDO=1; + (void)MODULE_CAN->SR.U; // read SR after write to CMR to settle register changes + } + if (interrupt & __CAN_IRQ_ARB_LOST ) { //0x40 + errArb++; + (void)MODULE_CAN->ALC.U; // must be read to re-enable interrupt + tx_error_count++; + } + if (interrupt & __CAN_IRQ_BUS_ERR ) { //0x80 + errBus++; + (void)MODULE_CAN->ECC.U; // must be read to re-enable interrupt + tx_error_count+=2; + } + if (tx_error_count>=2*CAN_MAX_TX_RETRY || MODULE_CAN->SR.B.ES) { + MODULE_CAN->CMR.B.AT=1; // abort transmission + (void)MODULE_CAN->SR.U; // read SR after write to CMR to settle register changes + tx_error_count=0; + if (MODULE_CAN->SR.B.ES) { + errRecovery++; + CAN_bus_off_recovery(); + } + return; + } + //should we really recover here? + if (MODULE_CAN->SR.B.BS){ + MODULE_CAN->CMR.B.AT=1; // abort transmission + (void)MODULE_CAN->SR.U; // read SR after write to CMR to settle register changes + tx_error_count=0; + errRecovery++; + CAN_bus_off_recovery(); + } + +} + +//***************************************************************************** +void ESP32Can1Interrupt(void *) { + pNMEA2000_esp32->InterruptHandler(); +} diff --git a/lib/nmea2000esp32/NMEA2000_esp32.h b/lib/nmea2000esp32/NMEA2000_esp32.h new file mode 100644 index 0000000..d1f67c4 --- /dev/null +++ b/lib/nmea2000esp32/NMEA2000_esp32.h @@ -0,0 +1,98 @@ +/* +NMEA2000_esp32.h + +Copyright (c) 2015-2020 Timo Lappalainen, Kave Oy, www.kave.fi + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Inherited NMEA2000 object for ESP32 modules. See also NMEA2000 library. + +Thanks to Thomas Barth, barth-dev.de, who has written ESP32 CAN code. To avoid extra +libraries, I implemented his code directly to the NMEA2000_esp32 to avoid extra +can.h library, which may cause even naming problem. + +The library sets as default CAN Tx pin to GPIO 16 and CAN Rx pint to GPIO 4. If you +want to use other pins (I have not tested can any pins be used), add defines e.g. +#define ESP32_CAN_TX_PIN GPIO_NUM_34 +#define ESP32_CAN_RX_PIN GPIO_NUM_35 +before including NMEA2000_esp32.h or NMEA2000_CAN.h +*/ + +#ifndef _NMEA2000_ESP32_H_ +#define _NMEA2000_ESP32_H_ + +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "driver/gpio.h" +#include "NMEA2000.h" +#include "N2kMsg.h" +#include "ESP32_CAN_def.h" + +#ifndef ESP32_CAN_TX_PIN +#define ESP32_CAN_TX_PIN GPIO_NUM_16 +#endif +#ifndef ESP32_CAN_RX_PIN +#define ESP32_CAN_RX_PIN GPIO_NUM_4 +#endif + +class tNMEA2000_esp32 : public tNMEA2000 +{ +private: + bool IsOpen; + static bool CanInUse; + +protected: + struct tCANFrame { + uint32_t id; // can identifier + uint8_t len; // length of data + uint8_t buf[8]; + }; + +protected: + CAN_speed_t speed; + gpio_num_t TxPin; + gpio_num_t RxPin; + QueueHandle_t RxQueue; + QueueHandle_t TxQueue; + Print *debugStream; + int errOverrun=0; + int errArb=0; + int errBus=0; + int errRecovery=0; + +protected: + void CAN_read_frame(); // Read frame to queue within interrupt + void CAN_send_frame(tCANFrame &frame); // Send frame + void CAN_init(); + void CAN_bus_off_recovery(); //recover from bus off + +protected: + bool CANSendFrame(unsigned long id, unsigned char len, const unsigned char *buf, bool wait_sent=true); + bool CANOpen(); + bool CANGetFrame(unsigned long &id, unsigned char &len, unsigned char *buf); + virtual void InitCANFrameBuffers(); + +public: + tNMEA2000_esp32(gpio_num_t _TxPin=ESP32_CAN_TX_PIN, + gpio_num_t _RxPin=ESP32_CAN_RX_PIN, + Print *debugStream=NULL); + + void InterruptHandler(); +}; + +#endif diff --git a/platformio.ini b/platformio.ini index cd07950..80594ca 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,7 +21,7 @@ platform = espressif32 framework = arduino lib_deps = ttlappalainen/NMEA2000-library @ ^4.17.2 - ttlappalainen/NMEA2000_esp32 @ ^1.0.3 + #ttlappalainen/NMEA2000_esp32 @ ^1.0.3 ttlappalainen/NMEA0183 @ ^1.7.1 bblanchon/ArduinoJson@^6.18.5 ottowinter/ESPAsyncWebServer-esphome@^2.0.1 diff --git a/src/main.cpp b/src/main.cpp index 1c6b3d3..becdce6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,6 +14,7 @@ #include "GwAppInfo.h" // #define GW_MESSAGE_DEBUG_ENABLED //#define FALLBACK_SERIAL +//#define CAN_ESP_DEBUG const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting #include #include "GwApi.h" @@ -27,7 +28,6 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting #define N2K_CERTIFICATION_LEVEL 0xff #endif -#include // This will automatically choose right CAN library and create suitable NMEA2000 object #include #include #include @@ -66,6 +66,17 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting #include "GwChannel.h" #include "GwChannelList.h" +#include // forked from https://github.com/ttlappalainen/NMEA2000_esp32 +#ifdef FALLBACK_SERIAL + #ifdef CAN_ESP_DEBUG + #define CDBS &Serial + #else + #define CDBS NULL + #endif + tNMEA2000 &NMEA2000=*(new tNMEA2000_esp32(ESP32_CAN_TX_PIN,ESP32_CAN_RX_PIN,CDBS)); +#else + tNMEA2000 &NMEA2000=*(new tNMEA2000_esp32()); +#endif #define MAX_NMEA2000_MESSAGE_SEASMART_SIZE 500 @@ -212,7 +223,12 @@ void handleN2kMessage(const tN2kMsg &n2kMsg,int sourceId, bool isConverted=false } if (sourceId != N2K_CHANNEL_ID && sendOutN2k){ countNMEA2KOut.add(n2kMsg.PGN); - NMEA2000.SendMsg(n2kMsg); + if (NMEA2000.SendMsg(n2kMsg)){ + countNMEA2KOut.add(n2kMsg.PGN); + } + else{ + countNMEA2KOut.addFail(n2kMsg.PGN); + } } }; @@ -622,8 +638,8 @@ void setup() { #ifdef FALLBACK_SERIAL fallbackSerial=true; //falling back to old style serial for logging - Serial.begin(baud); - Serial.printf("fallback serial enabled, error was %d\n",st); + Serial.begin(115200); + Serial.printf("fallback serial enabled\n"); logger.prefix="FALLBACK:"; #endif userCodeHandler.startInitTasks(MIN_USER_TASK);