├── .travis.yml ├── API.md ├── LICENSE ├── README.md ├── examples ├── CANReceiver │ └── CANReceiver.ino ├── CANReceiverCallback │ └── CANReceiverCallback.ino ├── CANSender │ └── CANSender.ino ├── CanBusMonitor │ └── CanBusMonitor.ino ├── FakeSubaruBRZ │ └── FakeSubaruBRZ.ino ├── FakeToyotaGR86 │ └── FakeToyotaGR86.ino ├── PidHistogram │ └── PidHistogram.ino ├── ReceiverStressTest │ └── ReceiverStressTest.ino └── SenderStressTest │ └── SenderStressTest.ino ├── keywords.txt ├── library.properties └── src ├── CAN.h ├── CANController.cpp ├── CANController.h ├── ESP32SJA1000.cpp ├── ESP32SJA1000.h ├── MCP2515.cpp └── MCP2515.h /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | env: 3 | global: 4 | - IDE_VERSION=1.8.5 5 | matrix: 6 | - BOARD="arduino:avr:uno" 7 | - BOARD="arduino:samd:mkrzero" 8 | - BOARD="espressif:esp32:esp32" 9 | before_install: 10 | - wget http://downloads.arduino.cc/arduino-$IDE_VERSION-linux64.tar.xz 11 | - tar xf arduino-$IDE_VERSION-linux64.tar.xz 12 | - mv arduino-$IDE_VERSION $HOME/arduino-ide 13 | - export PATH=$PATH:$HOME/arduino-ide 14 | - if [[ "$BOARD" =~ "arduino:samd:" ]]; then 15 | arduino --install-boards arduino:samd; 16 | fi 17 | - if [[ "$BOARD" =~ "espressif:esp32:" ]]; then 18 | mkdir -p $HOME/Arduino/hardware/espressif; 19 | git clone https://github.com/espressif/arduino-esp32.git $HOME/Arduino/hardware/espressif/esp32; 20 | pushd $HOME/Arduino/hardware/espressif/esp32/tools/; 21 | python get.py; 22 | popd; 23 | fi 24 | - buildExampleSketch() { arduino --verbose-build --verify --board $BOARD $PWD/examples/$1/$1.ino; } 25 | install: 26 | - mkdir -p $HOME/Arduino/libraries 27 | - ln -s $PWD $HOME/Arduino/libraries/CAN 28 | script: 29 | - buildExampleSketch CANReceiver 30 | - buildExampleSketch CANReceiverCallback 31 | - buildExampleSketch CANSender 32 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # CAN API 2 | 3 | ## Include Library 4 | 5 | ```arduino 6 | #include 7 | ``` 8 | 9 | ## Setup 10 | 11 | ### Begin 12 | 13 | Initialize the library with the specified bit rate. 14 | 15 | ```arduino 16 | CAN.begin(bitrate); 17 | ``` 18 | * `bitrate` - bit rate in bits per seconds (bps) (`1000E3`, `500E3`, `250E3`, `200E3`, `125E3`, `100E3`, `80E3`, `50E3`, `40E3`, `20E3`, `10E3`, `5E3`) 19 | 20 | Returns `1` on success, `0` on failure. 21 | 22 | ### Set pins 23 | 24 | #### MCP2515 25 | 26 | Override the default `CS` and `INT` pins used by the library. **Must** be called before `CAN.begin(...)`. 27 | 28 | ```arduino 29 | CAN.setPins(cs, irq); 30 | ``` 31 | * `cs` - new chip select pin to use, defaults to `10` 32 | * `irq` - new INT pin to use, defaults to `2`. **Must** be interrupt capable via [attachInterrupt(...)](https://www.arduino.cc/en/Reference/AttachInterrupt). 33 | 34 | This call is optional and only needs to be used if you need to change the default pins used. 35 | 36 | #### ESP32 37 | 38 | Override the default `CTX` and `CRX` pins used by the library. **Must** be called before `CAN.begin(...)`. 39 | 40 | ```arduino 41 | CAN.setPins(rx, tx); 42 | ``` 43 | * `rx` - new CRX pin to use, defaults to `4` 44 | * `tx` - new CTX pin to use, defaults to `5`. 45 | 46 | This call is optional and only needs to be used if you need to change the default pins used. 47 | 48 | ### Set SPI Frequency 49 | 50 | **MCP2515 only** 51 | 52 | Override the default SPI frequency of 10 MHz used by the library. **Must** be called before `CAN.begin(...)`. 53 | 54 | ```arduino 55 | CAN.setSPIFrequency(frequency); 56 | ``` 57 | * `frequency` - new SPI frequency to use, defaults to `10E6` 58 | 59 | This call is optional and only needs to be used if you need to change the default SPI frequency used. Some logic level converters cannot support high speeds such as 10 MHz, so a lower SPI frequency can be selected with `CAN.setSPIFrequency(frequency)`. 60 | 61 | ### Set Clock Frequency 62 | 63 | **MCP2515 only** 64 | 65 | Override the default clock source frequency that is connected to the MCP2515. **Must** be called before `CAN.begin(...)`. 66 | 67 | ```arduino 68 | CAN.setClockFrequency(clockFrequency); 69 | ``` 70 | * `clockFrequency` - new clock frequency to use (`8E6`, `16E6`) connected to MCP2515, defaults to `16 Mhz` 71 | 72 | This call is optional and only needs to be used if you need to change the clock source frequency connected to the MCP2515. Most shields have a 16 MHz clock source on board, some breakout boards have a 8 MHz source. 73 | 74 | ### End 75 | 76 | Stop the library 77 | 78 | ```arduino 79 | CAN.end() 80 | ``` 81 | 82 | ## Sending data 83 | 84 | ### Begin packet 85 | 86 | Start the sequence of sending a packet. 87 | 88 | ```arduino 89 | CAN.beginPacket(id); 90 | CAN.beginPacket(id, dlc); 91 | CAN.beginPacket(id, dlc, rtr); 92 | 93 | CAN.beginExtendedPacket(id); 94 | CAN.beginExtendedPacket(id, dlc); 95 | CAN.beginExtendedPacket(id, dlc, rtr); 96 | ``` 97 | 98 | * `id` - 11-bit id (standard packet) or 29-bit packet id (extended packet) 99 | * `dlc` - (optional) value of Data Length Code (DLC) field of packet, default is size of data written in packet 100 | * `rtr` - (optional) value of Remote Transmission Request (RTR) field of packet (`false` or `true`), defaults to `false`. RTR packets contain no data, the DLC field of the packet represents the requested length. 101 | 102 | Returns `1` on success, `0` on failure. 103 | 104 | ### Writing 105 | 106 | Write data to the packet. Each packet can contain up to 8 bytes. 107 | 108 | ```arduino 109 | CAN.write(byte); 110 | 111 | CAN.write(buffer, length); 112 | ``` 113 | * `byte` - single byte to write to packet 114 | 115 | or 116 | 117 | * `buffer` - data to write to packet 118 | * `length` - size of data to write 119 | 120 | Returns the number of bytes written. 121 | 122 | **Note:** Other Arduino `Print` API's can also be used to write data into the packet 123 | 124 | ### End packet 125 | 126 | End the sequence of sending a packet. 127 | 128 | ```arduino 129 | CAN.endPacket() 130 | ``` 131 | 132 | Returns `1` on success, `0` on failure. 133 | 134 | ## Receiving data 135 | 136 | ### Parsing packet 137 | 138 | Check if a packet has been received. 139 | 140 | ```arduino 141 | int packetSize = CAN.parsePacket(); 142 | ``` 143 | 144 | Returns the packet size in bytes or `0` if no packet was received. For RTR packets the size reflects the DLC field of the packet. 145 | 146 | ### Register callback 147 | 148 | Register a callback function for when a packet is received. 149 | 150 | ```arduino 151 | CAN.onReceive(onReceive); 152 | 153 | void onReceive(int packetSize) { 154 | // ... 155 | } 156 | ``` 157 | 158 | * `onReceive` - function to call when a packet is received. 159 | 160 | ### Packet ID 161 | 162 | ```arduino 163 | long id = CAN.packetId(); 164 | ``` 165 | 166 | Returns the id (11-bit or 29 bit) of the received packet. Standard packets have an 11-bit id, extended packets have an 29-bit id. 167 | 168 | ### Packet Extended 169 | 170 | ```arduino 171 | bool extended = CAN.packetExtended(); 172 | ``` 173 | 174 | Returns `true` if the received packet is extended, `false` otherwise. 175 | 176 | ### Packet RTR 177 | 178 | ```arduino 179 | bool rtr = CAN.packetRtr(); 180 | ``` 181 | 182 | Returns the value of the Remote Transmission Request (RTR) field of the packet `true`/`false`. RTR packets contain no data, the DLC field is the requested data length. 183 | 184 | ### Packet DLC 185 | 186 | ```arduino 187 | int DLC = CAN.packetDlc(); 188 | ``` 189 | 190 | Returns the value of the Data Length Code (DLC) field of the packet. 191 | 192 | 193 | ### Available 194 | 195 | ```arduino 196 | int availableBytes = CAN.available() 197 | ``` 198 | 199 | Returns number of bytes available for reading. 200 | 201 | ### Peeking 202 | 203 | Peek at the next byte in the packet. 204 | 205 | ```arduino 206 | int b = CAN.peek(); 207 | ``` 208 | 209 | Returns the next byte in the packet or `-1` if no bytes are available. 210 | 211 | ### Reading 212 | 213 | Read the next byte from the packet. 214 | 215 | ```arduino 216 | int b = CAN.read(); 217 | ``` 218 | 219 | Returns the next byte in the packet or `-1` if no bytes are available. 220 | 221 | **Note:** Other Arduino [`Stream` API's](https://www.arduino.cc/en/Reference/Stream) can also be used to read data from the packet 222 | 223 | ### Filtering 224 | 225 | Filter packets that meet the desired criteria. 226 | 227 | ``` 228 | CAN.filter(id); 229 | CAN.filter(id, mask); 230 | 231 | CAN.filterExtended(id); 232 | CAN.filterExtended(id, mask); 233 | ``` 234 | 235 | * `id` - 11-bit id (standard packet) or 29-bit packet id (extended packet) 236 | * `mask` - (optional) 11-bit mask (standard packet) or 29-bit mask (extended packet), defaults to `0x7ff` or `0x1fffffff` (extended) 237 | 238 | Only packets that meet the following criteria are acknowleged and received, other packets are ignored: 239 | 240 | ``` 241 | if ((packetId & mask) == id) { 242 | // acknowleged and received 243 | } else { 244 | // ignored 245 | } 246 | ``` 247 | 248 | Returns `1` on success, `0` on failure. 249 | 250 | This patched version also supports setting MCP2515 registers directly: 251 | 252 | ``` 253 | CAN.setFilterRegisters( 254 | mask0, filter0, filter1, 255 | mask1, filter2, filter3, filter4, filter5, 256 | allowRollover); 257 | ``` 258 | 259 | Please see the MCP2515 data sheet for more details on how these work. 260 | 261 | ## Other modes 262 | 263 | ### Loopback mode 264 | 265 | Put the CAN controller in loopback mode, any outgoing packets will also be received. 266 | 267 | ```arduino 268 | CAN.loopback(); 269 | ``` 270 | 271 | ### Sleep mode 272 | 273 | Put the CAN contoller in sleep mode. 274 | 275 | ```arduino 276 | CAN.sleep(); 277 | ``` 278 | 279 | Wake up the CAN contoller if it was previously in sleep mode. 280 | 281 | ```arduino 282 | CAN.wakeup(); 283 | ``` 284 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sandeep Mistry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino CAN 2 | 3 | An Arduino library for sending and receiving data using CAN bus. 4 | 5 | ## Compatible Hardware 6 | 7 | * [Microchip MCP2515](http://www.microchip.com/wwwproducts/en/en010406) based boards/shields 8 | * [Arduino MKR CAN shield](https://store.arduino.cc/arduino-mkr-can-shield) 9 | * [Espressif ESP32](http://espressif.com/en/products/hardware/esp32/overview)'s built-in [SJA1000](https://www.nxp.com/products/analog/interfaces/in-vehicle-network/can-transceiver-and-controllers/stand-alone-can-controller:SJA1000T) compatible CAN controller with an external 3.3V CAN transceiver 10 | 11 | ### Microchip MCP2515 wiring 12 | 13 | | Microchip MCP2515 | Arduino | 14 | | :---------------: | :-----: | 15 | | VCC | 5V | 16 | | GND | GND | 17 | | SCK | SCK | 18 | | SO | MISO | 19 | | SI | MOSI | 20 | | CS | 10 | 21 | | INT | 2 | 22 | 23 | 24 | `CS` and `INT` pins can be changed by using `CAN.setPins(cs, irq)`. `INT` pin is optional, it is only needed for receive callback mode. If `INT` pin is used, it **must** be interrupt capable via [`attachInterrupt(...)`](https://www.arduino.cc/en/Reference/AttachInterrupt). 25 | 26 | **NOTE**: Logic level converters must be used for boards which operate at 3.3V. 27 | 28 | ### Espressif ESP32 wiring 29 | 30 | Requires an external 3.3V CAN transceiver, such as a [TI SN65HVD230](http://www.ti.com/product/SN65HVD230). 31 | 32 | | CAN transceiver | ESP32 | 33 | | :-------------: | :---: | 34 | | 3V3 | 3V3 | 35 | | GND | GND | 36 | | CTX | 5 | 37 | | CRX | 4 | 38 | 39 | `CTX` and `CRX` pins can be changed by using `CAN.setPins(rx, tx)`. 40 | 41 | ## Installation 42 | 43 | As this is a fork of https://github.com/sandeepmistry/arduino-CAN with 44 | additional patches, you have to use git to install this version of the library: 45 | 46 | ```sh 47 | cd ~/Documents/Arduino/libraries/ # ~/Arduino/libraries on Mac OS 48 | git clone https://github.com/timurrrr/arduino-CAN CAN 49 | ``` 50 | 51 | ## API 52 | 53 | See [API.md](API.md). 54 | 55 | ## Examples 56 | 57 | See [examples](examples) folder. 58 | 59 | ## License 60 | 61 | This library is [licensed](LICENSE) under the [MIT Licence](http://en.wikipedia.org/wiki/MIT_License). 62 | -------------------------------------------------------------------------------- /examples/CANReceiver/CANReceiver.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sandeep Mistry. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | 6 | void setup() { 7 | Serial.begin(9600); 8 | while (!Serial); 9 | 10 | Serial.println("CAN Receiver"); 11 | 12 | // start the CAN bus at 500 kbps 13 | if (!CAN.begin(500E3)) { 14 | Serial.println("Starting CAN failed!"); 15 | while (1); 16 | } 17 | } 18 | 19 | void loop() { 20 | // try to parse packet 21 | int packetSize = CAN.parsePacket(); 22 | 23 | if (packetSize) { 24 | // received a packet 25 | Serial.print("Received "); 26 | 27 | if (CAN.packetExtended()) { 28 | Serial.print("extended "); 29 | } 30 | 31 | if (CAN.packetRtr()) { 32 | // Remote transmission request, packet contains no data 33 | Serial.print("RTR "); 34 | } 35 | 36 | Serial.print("packet with id 0x"); 37 | Serial.print(CAN.packetId(), HEX); 38 | 39 | if (CAN.packetRtr()) { 40 | Serial.print(" and requested length "); 41 | Serial.println(CAN.packetDlc()); 42 | } else { 43 | Serial.print(" and length "); 44 | Serial.println(packetSize); 45 | 46 | // only print packet data for non-RTR packets 47 | while (CAN.available()) { 48 | Serial.print((char)CAN.read()); 49 | } 50 | Serial.println(); 51 | } 52 | 53 | Serial.println(); 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /examples/CANReceiverCallback/CANReceiverCallback.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sandeep Mistry. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | 6 | void setup() { 7 | Serial.begin(9600); 8 | while (!Serial); 9 | 10 | Serial.println("CAN Receiver Callback"); 11 | 12 | // start the CAN bus at 500 kbps 13 | if (!CAN.begin(500E3)) { 14 | Serial.println("Starting CAN failed!"); 15 | while (1); 16 | } 17 | 18 | // register the receive callback 19 | CAN.onReceive(onReceive); 20 | } 21 | 22 | void loop() { 23 | // do nothing 24 | } 25 | 26 | void onReceive(int packetSize) { 27 | // received a packet 28 | Serial.print("Received "); 29 | 30 | if (CAN.packetExtended()) { 31 | Serial.print("extended "); 32 | } 33 | 34 | if (CAN.packetRtr()) { 35 | // Remote transmission request, packet contains no data 36 | Serial.print("RTR "); 37 | } 38 | 39 | Serial.print("packet with id 0x"); 40 | Serial.print(CAN.packetId(), HEX); 41 | 42 | if (CAN.packetRtr()) { 43 | Serial.print(" and requested length "); 44 | Serial.println(CAN.packetDlc()); 45 | } else { 46 | Serial.print(" and length "); 47 | Serial.println(packetSize); 48 | 49 | // only print packet data for non-RTR packets 50 | while (CAN.available()) { 51 | Serial.print((char)CAN.read()); 52 | } 53 | Serial.println(); 54 | } 55 | 56 | Serial.println(); 57 | } 58 | 59 | 60 | -------------------------------------------------------------------------------- /examples/CANSender/CANSender.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sandeep Mistry. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | 6 | void setup() { 7 | Serial.begin(9600); 8 | while (!Serial); 9 | 10 | Serial.println("CAN Sender"); 11 | 12 | // start the CAN bus at 500 kbps 13 | if (!CAN.begin(500E3)) { 14 | Serial.println("Starting CAN failed!"); 15 | while (1); 16 | } 17 | } 18 | 19 | void loop() { 20 | // send packet: id is 11 bits, packet can contain up to 8 bytes of data 21 | Serial.print("Sending packet ... "); 22 | 23 | CAN.beginPacket(0x12); 24 | CAN.write('h'); 25 | CAN.write('e'); 26 | CAN.write('l'); 27 | CAN.write('l'); 28 | CAN.write('o'); 29 | CAN.endPacket(); 30 | 31 | Serial.println("done"); 32 | 33 | delay(1000); 34 | 35 | // send extended packet: id is 29 bits, packet can contain up to 8 bytes of data 36 | Serial.print("Sending extended packet ... "); 37 | 38 | CAN.beginExtendedPacket(0xabcdef); 39 | CAN.write('w'); 40 | CAN.write('o'); 41 | CAN.write('r'); 42 | CAN.write('l'); 43 | CAN.write('d'); 44 | CAN.endPacket(); 45 | 46 | Serial.println("done"); 47 | 48 | delay(1000); 49 | } 50 | 51 | -------------------------------------------------------------------------------- /examples/CanBusMonitor/CanBusMonitor.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // This is a demo program that listens to messages on the CAN bus and prints them out to Serial. 4 | // 5 | // It was tested on Arduino Uno, Arduino Micro, Adafruit Feather nRF52832 and 6 | // Adafruit ItsyBitsy nRF52840 Express, and should be trivial to tweak to 7 | // support pretty much any other board with SPI. 8 | // 9 | // Connections: 10 | // MCP | BOARD 11 | // INT | Not used, can connect to Pin 9 12 | // SCK | SCK 13 | // SI | MO 14 | // SO | MI 15 | // CS | Pin 7 16 | // GND | GND 17 | // VCC | 3.3V 18 | 19 | const int CS_PIN = 7; 20 | const int IRQ_PIN = 9; 21 | const int QUARTZ_MHZ = 16; // Some MCP2515 boards have 8 MHz quartz. 22 | const int SPI_MHZ = 16; 23 | 24 | void setup() { 25 | Serial.begin(115200); 26 | 27 | uint32_t startTimeMs = millis(); 28 | while (!Serial); 29 | 30 | Serial.println("Started!"); 31 | 32 | CAN.setClockFrequency(QUARTZ_MHZ * 1E6); 33 | CAN.setSPIFrequency(SPI_MHZ * 1E6); 34 | CAN.setPins(CS_PIN, IRQ_PIN); 35 | 36 | // Subaru BRZ uses a 500k baud rate. 37 | while (!CAN.begin(500000)) { 38 | Serial.println("Failed to connect to the CAN controller!"); 39 | delay(1000); 40 | } 41 | 42 | Serial.println("CAN controller connected"); 43 | } 44 | 45 | // Forward declarations for helper functions. 46 | void handle_message(uint32_t pid); 47 | void print_report(); 48 | 49 | void loop() { 50 | int packet_size = CAN.parsePacket(); 51 | if (packet_size <= 0) { 52 | return; 53 | } 54 | 55 | if (CAN.packetRtr()) { 56 | // Ignore RTRs for now. 57 | return; 58 | } 59 | 60 | uint8_t data[8] = {0}; 61 | int data_length = 0; 62 | while (data_length < packet_size && data_length < sizeof(data)) { 63 | int byte_read = CAN.read(); 64 | if (byte_read == -1) { 65 | break; 66 | } 67 | 68 | data[data_length++] = byte_read; 69 | } 70 | 71 | uint32_t packet_id = CAN.packetId(); 72 | handle_message(packet_id, data, data_length); 73 | } 74 | 75 | void handle_message(uint32_t packet_id, uint8_t *data, int data_length) { 76 | // Optional: add something like 77 | // if (packet_id != 0x7E8) { 78 | // return; 79 | // } 80 | // to only show a subset of messages that match a certain criteria. 81 | 82 | // TODO: Add something smart to avoid spamming Serial. 83 | // For example, limit the number of messages printed over 10 seconds to 25? 84 | 85 | Serial.print("0x"); 86 | Serial.print(packet_id, HEX); 87 | Serial.print(", data:"); 88 | for (int i = 0; i < data_length; i++) { 89 | Serial.print(" "); 90 | if (data[i] < 0x10) { 91 | Serial.print("0"); // Add leading zero for readability. 92 | } 93 | Serial.print(data[i], HEX); 94 | } 95 | Serial.println(); 96 | } 97 | -------------------------------------------------------------------------------- /examples/FakeSubaruBRZ/FakeSubaruBRZ.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // This is a demo program that sends messages over the CAN bus in 4 | // a way that resembles real messages you can receive if you listen 5 | // to messages on the CAN bus of a 2013-2020 Subaru BRZ. 6 | // 7 | // DO NOT USE IT IN THE CAN NETWORK OF A REAL VEHICLE as it can cause unexpected 8 | // side effects. 9 | // 10 | // It was tested on Arduino Uno, Arduino Micro, Adafruit Feather nRF52832 and 11 | // Adafruit ItsyBitsy nRF52840 Express, and should be trivial to tweak to 12 | // support pretty much any other board with SPI. 13 | // 14 | // Connections: 15 | // MCP | BOARD 16 | // INT | Not used, can connect to Pin 9 17 | // SCK | SCK 18 | // SI | MO 19 | // SO | MI 20 | // CS | Pin 7 21 | // GND | GND 22 | // VCC | 3.3V 23 | 24 | const int CS_PIN = 7; 25 | const int IRQ_PIN = 9; 26 | const int QUARTZ_MHZ = 16; // Some MCP2515 boards have 8 MHz quartz. 27 | const int SPI_MHZ = 8; 28 | 29 | void setup() { 30 | Serial.begin(115200); 31 | 32 | uint32_t startTimeMs = millis(); 33 | while (!Serial && millis() - startTimeMs < 1000); 34 | if (!Serial) { 35 | Serial.println("Started!"); 36 | } else { 37 | // Whatever, noone's going to see anyways. 38 | } 39 | 40 | CAN.setClockFrequency(QUARTZ_MHZ * 1E6); 41 | CAN.setSPIFrequency(SPI_MHZ * 1E6); 42 | CAN.setPins(CS_PIN, IRQ_PIN); 43 | 44 | // Subaru BRZ uses a 500k baud rate. 45 | while (!CAN.begin(500000)) { 46 | Serial.println("Failed to connect to the CAN controller!"); 47 | delay(1000); 48 | } 49 | 50 | Serial.println("CAN controller connected"); 51 | } 52 | 53 | class FakeTpmsEcu { 54 | public: 55 | FakeTpmsEcu() { 56 | has_continuation_frame = false; 57 | original_frame_acked = false; 58 | } 59 | 60 | void scheduleNextFrame(uint16_t pid, uint8_t *data, uint8_t len) { 61 | if (has_continuation_frame) { 62 | Serial.print("Scheduling new frame, even though there is one pending already. Original was "); 63 | if (original_frame_acked) { 64 | Serial.println("acked."); 65 | } else { 66 | Serial.println("NOT acked."); 67 | } 68 | } 69 | has_continuation_frame = true; 70 | original_frame_acked = false; 71 | next_frame_pid = pid; 72 | memcpy(next_frame_data, data, len); 73 | next_frame_length = len; 74 | } 75 | 76 | void handleFrameAck(uint8_t separation_time_millis = 0) { 77 | if (!has_continuation_frame) { 78 | return; 79 | } 80 | 81 | original_frame_acked = true; 82 | next_frame_timestamp_millis = millis() + separation_time_millis; 83 | } 84 | 85 | void sendNextFrameIfNeeded() { 86 | if (!has_continuation_frame || !original_frame_acked) { 87 | return; 88 | } 89 | 90 | // Unsigned math magic to check if "time_diff" is "negative": 91 | unsigned long time_diff = millis() - next_frame_timestamp_millis; 92 | if (time_diff >> (8 * sizeof(time_diff) - 1)) { 93 | return; 94 | } 95 | 96 | send_data(next_frame_pid, next_frame_data, next_frame_length); 97 | has_continuation_frame = false; 98 | original_frame_acked = false; 99 | } 100 | 101 | private: 102 | bool has_continuation_frame; 103 | uint16_t next_frame_pid; 104 | uint8_t next_frame_data[8]; 105 | uint8_t next_frame_length; 106 | 107 | bool original_frame_acked; 108 | unsigned long next_frame_timestamp_millis; 109 | } fake_tpms_ecu; 110 | 111 | // Forward declaration for a helper. 112 | void try_to_receive_data(); 113 | void generate_payload(uint16_t pid, uint8_t *payload); 114 | boolean send_data(uint16_t pid, uint8_t *payload, uint8_t len); 115 | 116 | // TODO: Refactor the loop() function. It should first try to see if there's anything to send, 117 | // then if there's anything to receive. 118 | void loop() { 119 | // Many PIDs are sent 50 times per second. 120 | uint16_t num_cycles_per_second = 50; 121 | 122 | // 0x18, 0x140, 0x141 and 0x142 are intentionally duplicated in this array as they are sent 100 times 123 | // per second (double that for other PIDs) in the real car. 124 | uint16_t pids[] = { 125 | // These are sent 100 times per second: 126 | 0x18, 0x140, 0x141, 0x142, 127 | // These are sent 50 times per second: 128 | 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0x144, 0x152, 0x156, 0x280, 129 | // TODO: These are actually sent less frequently than 50 times per second: 130 | 0x282, // 16.7 times per second 131 | 0x284, // 10 times per second 132 | 0x360, // 20 times per second 133 | // These are commented out so that we don't send way too many messages: 134 | //0x361, // 20 times per second 135 | //0x370, // 20 times per second 136 | //0x372, // 10 times per second 137 | // These are sent 100 times per second: 138 | 0x18, 0x140, 0x141, 0x142, 139 | }; 140 | uint16_t num_messages_per_cycle = sizeof(pids) / sizeof(pids[0]); 141 | uint16_t num_messages_per_second = 142 | num_cycles_per_second * num_messages_per_cycle; 143 | 144 | unsigned long first_message_sent_micros = micros(); 145 | unsigned long num_messages_sent = 0; 146 | for (int i = 0; i < num_messages_per_cycle; i++, num_messages_sent++) { 147 | uint16_t pid = pids[i]; 148 | uint8_t payload[8]; 149 | generate_payload(pid, payload); 150 | 151 | if (!send_data(pid, payload, 8)) { 152 | Serial.println("Failed to send a message"); 153 | } 154 | 155 | unsigned long next_message_time_micros = 156 | first_message_sent_micros 157 | + (num_messages_sent * 1000000) / num_messages_per_second; 158 | if ((long)(micros() - next_message_time_micros) < 0) { 159 | try_to_receive_data(); 160 | fake_tpms_ecu.sendNextFrameIfNeeded(); 161 | delayMicroseconds(10); 162 | } 163 | } 164 | } 165 | 166 | boolean send_data(uint16_t id, uint8_t *payload, uint8_t len) { 167 | if (!CAN.beginPacket(id)) { 168 | Serial.println("beginPacket() failed."); 169 | return false; 170 | } 171 | 172 | CAN.write(payload, len); 173 | if (!CAN.endPacket()) { 174 | Serial.println("endPacket() failed."); 175 | return false; 176 | } 177 | 178 | return true; 179 | } 180 | 181 | void try_to_receive_data() { 182 | int packet_size = CAN.parsePacket(); 183 | if (packet_size <= 0) { 184 | return; 185 | } 186 | 187 | if (CAN.packetRtr()) { 188 | // Ignore RTRs. 189 | return; 190 | } 191 | 192 | uint32_t id = CAN.packetId(); 193 | uint8_t data[8] = {0}; 194 | int data_length = 0; 195 | while (data_length < packet_size && data_length < sizeof(data)) { 196 | int byte_read = CAN.read(); 197 | if (byte_read == -1) { 198 | break; 199 | } 200 | 201 | data[data_length++] = byte_read; 202 | } 203 | 204 | if (id == 0x7C0 && data[0] == 0x2 && data[1] == 0x21 && data[2] == 0x29) { 205 | // 0x7C0 / 0x2129 — Returns fuel level in liters x2. 206 | uint8_t response[8] = {0}; 207 | response[0] = 0x3; 208 | response[1] = 0x61; 209 | response[2] = 0x29; 210 | response[3] = 0x1C; 211 | send_data(0x7C8, response, 8); 212 | return; 213 | } 214 | 215 | if (id == 0x7DF && data[0] == 0x2 && data[1] == 0x01 && data[2] == 0x0f) { 216 | // 0x7DF / 0x010F — Returns (intake temperature in ºC + 40) 217 | uint8_t response[8] = {0}; 218 | response[0] = 0x3; 219 | response[1] = 0x41; 220 | response[2] = 0x0f; 221 | response[3] = 40 + 36; // 36 ºC 222 | send_data(0x7E8, response, 8); 223 | return; 224 | } 225 | 226 | if (id == 0x7DF && data[0] == 0x2 && data[1] == 0x01 && data[2] == 0x46) { 227 | // 0x7DF / 0x0146 — Returns (air temperature in ºC + 40) 228 | uint8_t response[8] = {0}; 229 | response[0] = 0x3; 230 | response[1] = 0x41; 231 | response[2] = 0x46; 232 | response[3] = 40 + 27; // 27 ºC 233 | send_data(0x7E8, response, 8); 234 | return; 235 | } 236 | 237 | if (id == 0x750 && data_length >= 1 && data[0] == 0x2a) { 238 | if (data_length >= 3 && data[1] == 0x02 && data[2] == 0x21) { 239 | if (data[3] == 0x30) { 240 | // TPMS pressures request. 241 | uint8_t response[8] = {0}; 242 | response[0] = 0x2a; 243 | response[1] = 0x10; // "1" means "first frame in a sequence" 244 | response[2] = 0x07; 245 | response[3] = 0x61; 246 | response[4] = 0x30; 247 | response[5] = 0xAB; // FL tire pressure 248 | response[6] = 0xAC; // FR tire pressure 249 | response[7] = 0xAD; // RR tire pressure 250 | send_data(0x758, response, 8); 251 | 252 | response[0] = 0x2a; 253 | response[1] = 0x21; // "2" means "continuation frame", "1" means "first continuation frame". 254 | response[2] = 0xAE; // RL tire pressure 255 | response[3] = 0x00; 256 | response[4] = 0x00; 257 | response[5] = 0x00; 258 | response[6] = 0x00; 259 | response[7] = 0x00; 260 | fake_tpms_ecu.scheduleNextFrame(0x758, response, 8); 261 | } else if (data[3] == 0x16) { 262 | // TPMS temperatures request. 263 | uint8_t response[8] = {0}; 264 | response[0] = 0x2a; 265 | response[1] = 0x10; // "1" means "first frame in a sequence" 266 | response[2] = 0x07; 267 | response[3] = 0x61; 268 | response[4] = 0x16; 269 | response[5] = 40 + 21; // FL tire temperature: 21ºC 270 | response[6] = 40 + 22; // FR tire temperature 271 | response[7] = 40 + 23; // RR tire temperature 272 | send_data(0x758, response, 8); 273 | 274 | response[0] = 0x2a; 275 | response[1] = 0x21; // "2" means "continuation frame", "1" means "first continuation frame". 276 | response[2] = 40 + 24; // RL tire temperature 277 | response[3] = 0x00; 278 | response[4] = 0x00; 279 | response[5] = 0x00; 280 | response[6] = 0x00; 281 | response[7] = 0x00; 282 | fake_tpms_ecu.scheduleNextFrame(0x758, response, 8); 283 | } 284 | } else if (data_length >= 3 && data[1] == 0x30 && data[2] == 0x00) { 285 | fake_tpms_ecu.handleFrameAck(data[3]); 286 | } 287 | } 288 | } 289 | 290 | void generate_payload(uint16_t pid, uint8_t *payload) { 291 | memset(payload, /* value= */ 0, /* size= */ 8); 292 | 293 | switch (pid) { 294 | case 0xD0: { 295 | // 0xD0 contains the steering wheel angle and data from motion sensors. 296 | 297 | // Pretend that the steering wheel is turned by 123 degrees to the left 298 | int16_t steering_angle_degrees = -123; 299 | int16_t value = steering_angle_degrees * 10; 300 | payload[0] = value & 0xFF; 301 | payload[1] = (value >> 8) & 0xFF; 302 | 303 | // TODO: Verify the scale for this value. The current scale is suspicious, 304 | // but matches real-world testing so far. Need to go to a skid pad to 305 | // verify for sure. 306 | int16_t rotation_clockwise_degrees_per_second = -70; 307 | int16_t rotation_clockwise_radians_per_second_x180 = 308 | (int16_t)(-3.14159 * rotation_clockwise_degrees_per_second); 309 | payload[2] = rotation_clockwise_radians_per_second_x180 & 0xFF; 310 | payload[3] = (rotation_clockwise_radians_per_second_x180 >> 8) & 0xFF; 311 | 312 | // TODO: decode what's in payload[4] and payload[5]. 313 | 314 | float lateral_acceleration_g = 0.3; 315 | // Looks to be encoded in a way that +1 increment is +0.2 m/s2. 316 | payload[6] = (int8_t)(9.80665 * lateral_acceleration_g / 0.2); 317 | 318 | float longitudinal_acceleration_g = 0.2; 319 | // Looks to be encoded in a way that +1 increment is -0.1 m/s2. 320 | // I know, it's strange that they use different scales for lat vs long. 321 | payload[7] = (int8_t)(-9.80665 * longitudinal_acceleration_g / 0.1); 322 | break; 323 | } 324 | 325 | case 0xD1: { 326 | // 0xD1 contains the speed, and the master brake cylinder pressure. 327 | uint16_t speed_m_s = 10; // 36 km/h, ~22.4 mph. 328 | // The encoding seems to be roughly radians per second x100. 329 | // The coefficient was tuned by comparing the values against an external 330 | // GPS from a session where I drove in a straight line on a highway at 331 | // constant speed on cruise control. 332 | uint16_t speed_value = (uint16_t)(speed_m_s * 63.72); 333 | payload[2] = speed_value & 0xFF; 334 | payload[3] = (speed_value >> 8) & 0xFF; 335 | 336 | // The units used for the master brake cylinder pressure are believed to 337 | // be 1/128 kPa. 338 | float brake_pressure_kPa = 1024; 339 | payload[2] = (uint8_t)(brake_pressure_kPa / 128); 340 | break; 341 | } 342 | 343 | case 0xD4: { 344 | // Wheel speed sensors / ABS sensors. 345 | uint16_t value = (uint16_t)(10 * 61); 346 | payload[0] = value & 0xFF; 347 | payload[1] = (value >> 8) & 0xFF; 348 | 349 | value = (uint16_t)(10 * 62); 350 | payload[2] = value & 0xFF; 351 | payload[3] = (value >> 8) & 0xFF; 352 | 353 | value = (uint16_t)(10 * 63); 354 | payload[4] = value & 0xFF; 355 | payload[5] = (value >> 8) & 0xFF; 356 | 357 | value = (uint16_t)(10 * 64); 358 | payload[6] = value & 0xFF; 359 | payload[7] = (value >> 8) & 0xFF; 360 | break; 361 | } 362 | 363 | case 0x140: { 364 | uint8_t accelerator_pedal_percent = 42; 365 | payload[0] = accelerator_pedal_percent * 255 / 100; 366 | 367 | // The clutch pedal has two sensors: 368 | // - 0% and >0% (used here) 369 | // - 100% and <100% (haven't found yet) 370 | // TODO: Find where data from the second sensor is. 371 | bool clutch_down = false; 372 | payload[1] = (clutch_down ? 0x80 : 0x00); 373 | 374 | // RPMs are believed to be encoded with just 14 bits. 375 | uint16_t rpm = 3456; 376 | payload[2] = rpm & 0xFF; 377 | payload[3] = (rpm >> 8) & 0x3F; 378 | break; 379 | } 380 | 381 | case 0x360: { 382 | uint8_t oil_temperature_celsius = 100; 383 | payload[2] = oil_temperature_celsius + 40; 384 | 385 | uint8_t coolant_temperature_celsius = 90; 386 | payload[3] = coolant_temperature_celsius + 40; 387 | break; 388 | } 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /examples/FakeToyotaGR86/FakeToyotaGR86.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // This is a demo program that sends messages over the CAN bus in 4 | // a way that resembles real messages you can receive if you listen 5 | // to messages on the CAN bus of a 2022 Toyota GR86. 6 | // 7 | // DO NOT USE IT IN THE CAN NETWORK OF A REAL VEHICLE as it can cause unexpected 8 | // side effects. 9 | // 10 | // It was tested on Arduino Uno, Arduino Micro, Adafruit Feather nRF52832 and 11 | // Adafruit ItsyBitsy nRF52840 Express, and should be trivial to tweak to 12 | // support pretty much any other board with SPI. 13 | // 14 | // Connections: 15 | // MCP | BOARD 16 | // INT | Not used, can connect to Pin 9 17 | // SCK | SCK 18 | // SI | MO 19 | // SO | MI 20 | // CS | Pin 7 21 | // GND | GND 22 | // VCC | 3.3V 23 | 24 | const int CS_PIN = 7; 25 | const int IRQ_PIN = 9; 26 | const int QUARTZ_MHZ = 16; // Some MCP2515 boards have 8 MHz quartz. 27 | const int SPI_MHZ = 8; 28 | 29 | void setup() { 30 | Serial.begin(115200); 31 | 32 | uint32_t startTimeMs = millis(); 33 | while (!Serial && millis() - startTimeMs < 1000); 34 | if (!Serial) { 35 | Serial.println("Started!"); 36 | } else { 37 | // Whatever, noone's going to see anyways. 38 | } 39 | 40 | CAN.setClockFrequency(QUARTZ_MHZ * 1E6); 41 | CAN.setSPIFrequency(SPI_MHZ * 1E6); 42 | CAN.setPins(CS_PIN, IRQ_PIN); 43 | 44 | // Subaru BRZ uses a 500k baud rate. 45 | while (!CAN.begin(500000)) { 46 | Serial.println("Failed to connect to the CAN controller!"); 47 | delay(1000); 48 | } 49 | 50 | Serial.println("CAN controller connected"); 51 | } 52 | 53 | class FakeTpmsEcu { 54 | public: 55 | FakeTpmsEcu() { 56 | has_continuation_frame = false; 57 | original_frame_acked = false; 58 | } 59 | 60 | void scheduleNextFrame(uint16_t pid, uint8_t *data, uint8_t len) { 61 | if (has_continuation_frame) { 62 | Serial.print("Scheduling new frame, even though there is one pending already. Original was "); 63 | if (original_frame_acked) { 64 | Serial.println("acked."); 65 | } else { 66 | Serial.println("NOT acked."); 67 | } 68 | } 69 | has_continuation_frame = true; 70 | original_frame_acked = false; 71 | next_frame_pid = pid; 72 | memcpy(next_frame_data, data, len); 73 | next_frame_length = len; 74 | } 75 | 76 | void handleFrameAck(uint8_t separation_time_millis = 0) { 77 | if (!has_continuation_frame) { 78 | return; 79 | } 80 | 81 | original_frame_acked = true; 82 | next_frame_timestamp_millis = millis() + separation_time_millis; 83 | } 84 | 85 | void sendNextFrameIfNeeded() { 86 | if (!has_continuation_frame || !original_frame_acked) { 87 | return; 88 | } 89 | 90 | // Unsigned math magic to check if "time_diff" is "negative": 91 | unsigned long time_diff = millis() - next_frame_timestamp_millis; 92 | if (time_diff >> (8 * sizeof(time_diff) - 1)) { 93 | return; 94 | } 95 | 96 | send_data(next_frame_pid, next_frame_data, next_frame_length); 97 | has_continuation_frame = false; 98 | original_frame_acked = false; 99 | } 100 | 101 | private: 102 | bool has_continuation_frame; 103 | uint16_t next_frame_pid; 104 | uint8_t next_frame_data[8]; 105 | uint8_t next_frame_length; 106 | 107 | bool original_frame_acked; 108 | unsigned long next_frame_timestamp_millis; 109 | } fake_tpms_ecu; 110 | 111 | // Forward declaration for a helper. 112 | void try_to_receive_data(); 113 | void generate_payload(uint16_t pid, uint8_t *payload); 114 | boolean send_data(uint16_t pid, uint8_t *payload, uint8_t len); 115 | 116 | // TODO: Refactor the loop() function. It should first try to see if there's anything to send, 117 | // then if there's anything to receive. 118 | void loop() { 119 | // Many PIDs are sent 50 times per second. 120 | uint16_t num_cycles_per_second = 50; 121 | 122 | // 0x40 and 0x41 are intentionally duplicated in this array as they are sent 100 times 123 | // per second (double that for other PIDs) in the real car. 124 | uint16_t pids[] = { 125 | // These are sent 100 times per second: 126 | 0x40, 0x41, 127 | // These are sent 50 times per second, part 1: 128 | 0x118, 0x138, 0x139, 0x13B, 0x13C, 129 | // These are sent 100 times per second (duplicates): 130 | 0x40, 0x41, 131 | // These are sent 50 times per second, part 2: 132 | 0x143, 0x146, 133 | // TODO: These are actually sent less frequently than 50 times per second: 134 | 0x241, // 20 times per second 135 | 0x345, // 10 times per second 136 | }; 137 | uint16_t num_messages_per_cycle = sizeof(pids) / sizeof(pids[0]); 138 | uint16_t num_messages_per_second = 139 | num_cycles_per_second * num_messages_per_cycle; 140 | 141 | unsigned long first_message_sent_micros = micros(); 142 | unsigned long num_messages_sent = 0; 143 | for (int i = 0; i < num_messages_per_cycle; i++, num_messages_sent++) { 144 | uint16_t pid = pids[i]; 145 | uint8_t payload[8]; 146 | generate_payload(pid, payload); 147 | 148 | if (!send_data(pid, payload, 8)) { 149 | Serial.println("Failed to send a message"); 150 | } 151 | 152 | unsigned long next_message_time_micros = 153 | first_message_sent_micros 154 | + (num_messages_sent * 1000000) / num_messages_per_second; 155 | if ((long)(micros() - next_message_time_micros) < 0) { 156 | try_to_receive_data(); 157 | fake_tpms_ecu.sendNextFrameIfNeeded(); 158 | delayMicroseconds(10); 159 | } 160 | } 161 | } 162 | 163 | boolean send_data(uint16_t id, uint8_t *payload, uint8_t len) { 164 | if (!CAN.beginPacket(id)) { 165 | Serial.println("beginPacket() failed."); 166 | return false; 167 | } 168 | 169 | CAN.write(payload, len); 170 | if (!CAN.endPacket()) { 171 | Serial.println("endPacket() failed."); 172 | return false; 173 | } 174 | 175 | return true; 176 | } 177 | 178 | void try_to_receive_data() { 179 | int packet_size = CAN.parsePacket(); 180 | if (packet_size <= 0) { 181 | return; 182 | } 183 | 184 | if (CAN.packetRtr()) { 185 | // Ignore RTRs. 186 | return; 187 | } 188 | 189 | uint32_t id = CAN.packetId(); 190 | uint8_t data[8] = {0}; 191 | int data_length = 0; 192 | while (data_length < packet_size && data_length < sizeof(data)) { 193 | int byte_read = CAN.read(); 194 | if (byte_read == -1) { 195 | break; 196 | } 197 | 198 | data[data_length++] = byte_read; 199 | } 200 | 201 | if (id == 0x750 && data_length >= 1 && data[0] == 0x2a) { 202 | if (data_length >= 3 && data[1] == 0x02 && data[2] == 0x21) { 203 | if (data[3] == 0x30) { 204 | // TPMS pressures request. 205 | uint8_t response[8] = {0}; 206 | response[0] = 0x2a; 207 | response[1] = 0x10; // "1" means "first frame in a sequence" 208 | response[2] = 0x07; 209 | response[3] = 0x61; 210 | response[4] = 0x30; 211 | response[5] = 0xAB; // FL tire pressure 212 | response[6] = 0xAC; // FR tire pressure 213 | response[7] = 0xAD; // RR tire pressure 214 | send_data(0x758, response, 8); 215 | 216 | response[0] = 0x2a; 217 | response[1] = 0x21; // "2" means "continuation frame", "1" means "first continuation frame". 218 | response[2] = 0xAE; // RL tire pressure 219 | response[3] = 0x00; 220 | response[4] = 0x00; 221 | response[5] = 0x00; 222 | response[6] = 0x00; 223 | response[7] = 0x00; 224 | fake_tpms_ecu.scheduleNextFrame(0x758, response, 8); 225 | } else if (data[3] == 0x16) { 226 | // TPMS temperatures request. 227 | uint8_t response[8] = {0}; 228 | response[0] = 0x2a; 229 | response[1] = 0x10; // "1" means "first frame in a sequence" 230 | response[2] = 0x07; 231 | response[3] = 0x61; 232 | response[4] = 0x16; 233 | response[5] = 40 + 21; // FL tire temperature: 21ºC 234 | response[6] = 40 + 22; // FR tire temperature 235 | response[7] = 40 + 23; // RR tire temperature 236 | send_data(0x758, response, 8); 237 | 238 | response[0] = 0x2a; 239 | response[1] = 0x21; // "2" means "continuation frame", "1" means "first continuation frame". 240 | response[2] = 40 + 24; // RL tire temperature 241 | response[3] = 0x00; 242 | response[4] = 0x00; 243 | response[5] = 0x00; 244 | response[6] = 0x00; 245 | response[7] = 0x00; 246 | fake_tpms_ecu.scheduleNextFrame(0x758, response, 8); 247 | } 248 | } else if (data_length >= 3 && data[1] == 0x30 && data[2] == 0x00) { 249 | fake_tpms_ecu.handleFrameAck(data[3]); 250 | } 251 | } 252 | } 253 | 254 | void generate_payload(uint16_t pid, uint8_t *payload) { 255 | memset(payload, /* value= */ 0, /* size= */ 8); 256 | 257 | switch (pid) { 258 | case 0x40: { 259 | uint8_t accelerator_pedal_percent = 42; 260 | payload[4] = accelerator_pedal_percent * 255 / 100; 261 | payload[5] = payload[4]; 262 | payload[6] = payload[4]; 263 | 264 | // The clutch pedal has two sensors: 265 | // - 0% and >0% (used here) 266 | // - 100% and <100% (haven't found yet) 267 | // TODO: Find where data from the second sensor is. 268 | bool clutch_down = false; 269 | payload[1] = (clutch_down ? 0x80 : 0x00); 270 | 271 | // RPMs are believed to be encoded with just 14 bits. 272 | uint16_t rpm = 3456; 273 | payload[2] = rpm & 0xFF; 274 | payload[3] = (rpm >> 8) & 0x3F; 275 | break; 276 | } 277 | 278 | 279 | case 0x138: { 280 | // Pretend that the steering wheel is turned by 123 degrees to the left 281 | int16_t steering_angle_degrees = -123; 282 | int16_t steering_value = steering_angle_degrees * 10; 283 | payload[2] = steering_value & 0xFF; 284 | payload[3] = (steering_value >> 8) & 0xFF; 285 | 286 | // TODO: Verify the scale for this value. The current scale is suspicious, 287 | // but matches real-world testing so far. Need to go to a skid pad to 288 | // verify for sure. 289 | float yaw_rate_degrees_per_second = -12.3; 290 | int16_t yaw_rate_value = (int16_t)(-yaw_rate_degrees_per_second / 0.2725); 291 | payload[4] = yaw_rate_value & 0xFF; 292 | payload[5] = (yaw_rate_value >> 8) & 0xFF; 293 | break; 294 | } 295 | 296 | case 0x139: { 297 | uint16_t speed_m_s = 10; // 36 km/h, ~22.4 mph. 298 | // The encoding seems to be roughly radians per second x100. 299 | // The coefficient was tuned by comparing the values against an external 300 | // GPS from a session where I drove in a straight line on a highway at 301 | // constant speed on cruise control. 302 | uint16_t speed_value = (uint16_t)(speed_m_s * 63.72); 303 | payload[2] = speed_value & 0xFF; 304 | payload[3] = (speed_value >> 8) & 0xFF; 305 | 306 | // The units used for the master brake cylinder pressure are believed to 307 | // be 1/128 kPa. 308 | float brake_pressure_kPa = 1024; 309 | payload[4] = 0x0C; 310 | payload[5] = (uint8_t)(brake_pressure_kPa / 128); 311 | break; 312 | } 313 | 314 | case 0x345: { 315 | uint8_t oil_temperature_celsius = 100; 316 | payload[3] = oil_temperature_celsius + 40; 317 | 318 | uint8_t coolant_temperature_celsius = 90; 319 | payload[4] = coolant_temperature_celsius + 40; 320 | break; 321 | } 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /examples/PidHistogram/PidHistogram.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // This is a demo program that listens to messages on the CAN bus and 4 | // periodically prints a list of PIDs observed, with counts how many times they 5 | // were observed. 6 | // 7 | // It was tested on Arduino Uno, Arduino Micro, Adafruit Feather nRF52832 and 8 | // Adafruit ItsyBitsy nRF52840 Express, and should be trivial to tweak to 9 | // support pretty much any other board with SPI. 10 | // 11 | // Connections: 12 | // MCP | BOARD 13 | // INT | Not used, can connect to Pin 9 14 | // SCK | SCK 15 | // SI | MO 16 | // SO | MI 17 | // CS | Pin 7 18 | // GND | GND 19 | // VCC | 3.3V 20 | 21 | const int CS_PIN = 7; 22 | const int IRQ_PIN = 9; 23 | const int QUARTZ_MHZ = 16; // Some MCP2515 boards have 8 MHz quartz. 24 | const int SPI_MHZ = 8; 25 | 26 | // Defines the maximum number of unique CAN PIDs to keep track of between 27 | // printing histrogram reports. Too many will make the output unreadable, too 28 | // few will not give you the full picture. 29 | const uint16_t MAX_NUM_CAN_PIDS = 32; 30 | 31 | // Interval in seconds between printing reports. 32 | const uint32_t REPORT_INTERVAL_SECONDS = 1; 33 | 34 | struct pid_entry { 35 | uint32_t pid; 36 | uint32_t num_received_since_last_report; 37 | }; 38 | pid_entry observed_pids[MAX_NUM_CAN_PIDS]; // Keep sorted by PID. 39 | uint16_t num_unique_observed_pids = 0; 40 | 41 | uint32_t total_received_since_last_report; 42 | uint32_t last_report_printed_ms; 43 | 44 | void setup() { 45 | Serial.begin(115200); 46 | 47 | uint32_t startTimeMs = millis(); 48 | while (!Serial); 49 | 50 | Serial.println("Started!"); 51 | 52 | CAN.setClockFrequency(QUARTZ_MHZ * 1E6); 53 | CAN.setSPIFrequency(SPI_MHZ * 1E6); 54 | CAN.setPins(CS_PIN, IRQ_PIN); 55 | 56 | // Subaru BRZ uses a 500k baud rate. 57 | while (!CAN.begin(500000)) { 58 | Serial.println("Failed to connect to the CAN controller!"); 59 | delay(1000); 60 | } 61 | 62 | Serial.println("CAN controller connected"); 63 | } 64 | 65 | // Forward declarations for helper functions. 66 | void handle_message(uint32_t pid); 67 | void print_report(); 68 | 69 | void loop() { 70 | if (millis() - last_report_printed_ms > REPORT_INTERVAL_SECONDS * 1000) { 71 | print_report(); 72 | last_report_printed_ms = millis(); 73 | } 74 | 75 | int packet_size = CAN.parsePacket(); 76 | if (packet_size <= 0) { 77 | return; 78 | } 79 | 80 | if (CAN.packetRtr()) { 81 | // Ignore RTRs for now. 82 | return; 83 | } 84 | 85 | handle_message(CAN.packetId()); 86 | } 87 | 88 | void handle_message(uint32_t pid) { 89 | total_received_since_last_report++; 90 | 91 | pid_entry *after_last = observed_pids + num_unique_observed_pids; 92 | 93 | // Use binary search to find the location where this PID should go in the 94 | // sorted 'observed_pids' list. 95 | // Unfortunately, it's not simple to use std::lower_bound() on Ardiuno AVR 96 | // boards, so I had to recall some CS classes. Hopefully, no bugs :) 97 | pid_entry *insertion_location = nullptr; 98 | { 99 | pid_entry *start_ptr = observed_pids; 100 | pid_entry *end_ptr = after_last; 101 | 102 | while (start_ptr != end_ptr) { 103 | if (start_ptr->pid >= pid) { 104 | insertion_location = start_ptr; 105 | break; 106 | } 107 | 108 | // We can now assume start_ptr->pid < pid. 109 | 110 | if (start_ptr + 1 == end_ptr) { 111 | // Narrowed the range down to one element that's smaller than pid. 112 | insertion_location = start_ptr + 1; 113 | break; 114 | } 115 | 116 | // Otherwise, keep narrowing down. This is guaranteed to make progress 117 | // as start_pid + 1 < end_ptr, and so (end_ptr - start_ptr) >= 2. 118 | pid_entry *middle = start_ptr + (end_ptr - start_ptr) / 2; 119 | if (middle->pid <= pid) { 120 | start_ptr = middle; 121 | } else { 122 | end_ptr = middle; 123 | } 124 | } 125 | 126 | // Handle the special case of an empty list. 127 | if (insertion_location == nullptr) { 128 | insertion_location = start_ptr; 129 | } 130 | } 131 | 132 | if (insertion_location < after_last && insertion_location->pid == pid) { 133 | // Found a match! 134 | insertion_location->num_received_since_last_report++; 135 | return; 136 | } else if (num_unique_observed_pids == MAX_NUM_CAN_PIDS) { 137 | // The list is already full, ignoring this PID. 138 | return; 139 | } 140 | 141 | // Move elements after insert_to_index by one element. Need to use 142 | // memmove() instead of memcpy() as src and dst overlap. 143 | memmove( 144 | /* destination= */ insertion_location + 1, 145 | /* source= */ insertion_location, 146 | /* num_bytes= */ (after_last - insertion_location) * sizeof(pid_entry)); 147 | 148 | // Finally, insert the new entry for this PID. 149 | num_unique_observed_pids++; 150 | insertion_location->pid = pid; 151 | insertion_location->num_received_since_last_report = 1; 152 | } 153 | 154 | void print_report() { 155 | if (num_unique_observed_pids == 0) { 156 | Serial.println("No messages received!"); 157 | return; 158 | } 159 | 160 | Serial.print("Received "); 161 | Serial.print(total_received_since_last_report); 162 | Serial.print(" messages ("); 163 | Serial.print(num_unique_observed_pids); 164 | Serial.println(" unique PIDs) since last report:"); 165 | 166 | for (uint16_t i = 0; i < num_unique_observed_pids; i++) { 167 | pid_entry *entry = &observed_pids[i]; 168 | Serial.print(" PID: "); 169 | Serial.print(entry->pid); 170 | Serial.print(" (0x"); 171 | Serial.print(entry->pid, HEX); 172 | Serial.print(") received "); 173 | Serial.print(entry->num_received_since_last_report); 174 | Serial.println(" times."); 175 | 176 | // Reset the count for the next report. 177 | entry->num_received_since_last_report = 0; 178 | } 179 | Serial.print("Interval between reports: "); 180 | Serial.print(REPORT_INTERVAL_SECONDS); 181 | Serial.print(" second"); 182 | if (REPORT_INTERVAL_SECONDS != 1) { 183 | Serial.print("s"); 184 | } 185 | Serial.println("."); 186 | Serial.println(""); 187 | 188 | num_unique_observed_pids = 0; 189 | total_received_since_last_report = 0; 190 | } 191 | -------------------------------------------------------------------------------- /examples/ReceiverStressTest/ReceiverStressTest.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // This is a demo program that listens to messages on the CAN bus sent by a 4 | // device using the code from the SenderStressTest example, and verifies that 5 | // correct bytes were received. 6 | // 7 | // It was tested on Arduino Uno, Arduino Micro, Adafruit Feather nRF52832 and 8 | // Adafruit ItsyBitsy nRF52840 Express, and should be trivial to tweak to 9 | // support pretty much any other board with SPI. 10 | // 11 | // Connections: 12 | // MCP | BOARD 13 | // INT | Not used, can connect to Pin 9 14 | // SCK | SCK 15 | // SI | MO 16 | // SO | MI 17 | // CS | Pin 7 18 | // GND | GND 19 | // VCC | 3.3V 20 | 21 | const int CS_PIN = 7; 22 | const int IRQ_PIN = 9; 23 | const int QUARTZ_MHZ = 16; // Some MCP2515 boards have 8 MHz quartz. 24 | const int SPI_MHZ = 10; 25 | const long BAUD_RATE = 500 * 1E3; // 500k baud rate. 26 | 27 | void setup() { 28 | Serial.begin(115200); 29 | 30 | uint32_t startTimeMs = millis(); 31 | while (!Serial && millis() - startTimeMs < 5000); 32 | if (!Serial) { 33 | Serial.println("Started!"); 34 | } else { 35 | // Whatever, noone's going to see anyways. 36 | } 37 | 38 | CAN.setClockFrequency(QUARTZ_MHZ * 1E6); 39 | CAN.setSPIFrequency(SPI_MHZ * 1E6); 40 | CAN.setPins(CS_PIN, IRQ_PIN); 41 | 42 | while (!CAN.begin(BAUD_RATE)) { 43 | Serial.println("Failed to connect to the CAN controller!"); 44 | delay(1000); 45 | } 46 | 47 | Serial.println("CAN controller connected"); 48 | } 49 | 50 | // Interval in seconds between printing reports. 51 | const uint32_t REPORT_INTERVAL_SECONDS = 1; 52 | 53 | uint32_t last_stats_ms = millis(); 54 | uint32_t num_messages_received; 55 | uint32_t num_errors; 56 | 57 | void loop() { 58 | if (millis() - last_stats_ms > REPORT_INTERVAL_SECONDS * 1000) { 59 | Serial.print("Received in "); 60 | Serial.print(REPORT_INTERVAL_SECONDS); 61 | Serial.print(" seconds:\t"); 62 | Serial.print(num_messages_received); 63 | if (num_messages_received > 0) { 64 | Serial.print(", errors:\t"); 65 | Serial.print(num_errors); 66 | Serial.print(" ("); 67 | Serial.print(100.0 * num_errors / num_messages_received); 68 | Serial.print("%)"); 69 | } 70 | Serial.println(""); 71 | 72 | last_stats_ms = millis(); 73 | num_messages_received = 0; 74 | num_errors = 0; 75 | } 76 | 77 | int packet_size = CAN.parsePacket(); 78 | if (packet_size <= 0) { 79 | return; 80 | } 81 | 82 | if (CAN.packetRtr()) { 83 | // Ignore RTRs for now. 84 | return; 85 | } 86 | 87 | num_messages_received++; 88 | 89 | uint32_t pid = CAN.packetId(); 90 | uint8_t data[8]; 91 | int data_length = 0; 92 | while (data_length < packet_size && data_length < sizeof(data)) { 93 | int byte_read = CAN.read(); 94 | if (byte_read == -1) { 95 | break; 96 | } 97 | 98 | data[data_length++] = byte_read; 99 | } 100 | 101 | uint8_t expected_payload[8] = 102 | { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, pid }; 103 | bool mismatch = data_length != 8; 104 | for (int i = 0; i < 8; i++) { 105 | if (data[i] != expected_payload[i]) { 106 | mismatch = true; 107 | } 108 | } 109 | 110 | if (mismatch) { 111 | num_errors++; 112 | Serial.print("Unexpected data received! pid = "); 113 | Serial.print(pid); 114 | Serial.print(" (0x"); 115 | Serial.print(pid, HEX); 116 | Serial.print("), data: "); 117 | for (int i = 0; i < data_length; i++) { 118 | if (i != 0) { 119 | Serial.print(" "); 120 | } 121 | Serial.print(data[i], HEX); 122 | } 123 | Serial.println(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /examples/SenderStressTest/SenderStressTest.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // This is a demo program that sends as many messages over the CAN bus as it 4 | // can, and can be used as a stress test for the hardware and a CAN receiver 5 | // device. 6 | // 7 | // DO NOT USE IT IN THE CAN NETWORK OF A REAL VEHICLE as it can cause unexpected 8 | // side effects. 9 | // 10 | // It was tested on Arduino Uno, Arduino Micro, Adafruit Feather nRF52832 and 11 | // Adafruit ItsyBitsy nRF52840 Express, and should be trivial to tweak to 12 | // support pretty much any other board with SPI. 13 | // 14 | // Connections: 15 | // MCP | BOARD 16 | // INT | Not used, can connect to Pin 9 17 | // SCK | SCK 18 | // SI | MO 19 | // SO | MI 20 | // CS | Pin 7 21 | // GND | GND 22 | // VCC | 3.3V 23 | 24 | const int CS_PIN = 7; 25 | const int IRQ_PIN = 9; 26 | const int QUARTZ_MHZ = 16; // Some MCP2515 boards have 8 MHz quartz. 27 | const int SPI_MHZ = 10; 28 | const long BAUD_RATE = 500 * 1E3; // 500k baud rate. 29 | 30 | void setup() { 31 | Serial.begin(115200); 32 | 33 | uint32_t startTimeMs = millis(); 34 | while (!Serial && millis() - startTimeMs < 5000); 35 | if (!Serial) { 36 | Serial.println("Started!"); 37 | } else { 38 | // Whatever, noone's going to see anyways. 39 | } 40 | 41 | CAN.setClockFrequency(QUARTZ_MHZ * 1E6); 42 | CAN.setSPIFrequency(SPI_MHZ * 1E6); 43 | CAN.setPins(CS_PIN, IRQ_PIN); 44 | 45 | while (!CAN.begin(BAUD_RATE)) { 46 | Serial.println("Failed to connect to the CAN controller!"); 47 | delay(1000); 48 | } 49 | 50 | Serial.println("CAN controller connected"); 51 | } 52 | 53 | // Interval in seconds between printing reports. 54 | const uint32_t REPORT_INTERVAL_SECONDS = 1; 55 | 56 | uint32_t last_stats_ms = millis(); 57 | uint32_t num_messages_sent = 0; 58 | uint32_t num_errors = 0; 59 | 60 | void loop() { 61 | for (uint16_t pid = 1; pid <= 0xff; pid++) { 62 | uint32_t current_time_ms = millis(); 63 | if (current_time_ms - last_stats_ms > REPORT_INTERVAL_SECONDS * 1000) { 64 | Serial.print("Packets sent over 1 second:\t"); 65 | Serial.print(num_messages_sent); 66 | Serial.print(", errors:\t"); 67 | Serial.println(num_errors); 68 | last_stats_ms = current_time_ms; 69 | num_messages_sent = 0; 70 | num_errors = 0; 71 | } 72 | 73 | if (!CAN.beginPacket(pid)) { 74 | Serial.println("beginPacket() failed."); 75 | num_errors++; 76 | continue; 77 | } 78 | 79 | uint8_t payload[8] = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, pid }; 80 | CAN.write(payload, 8); 81 | if (CAN.endPacket()) { 82 | num_messages_sent++; 83 | } else { 84 | Serial.println("endPacket() failed."); 85 | num_errors++; 86 | continue; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For CAN 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | CAN KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | begin KEYWORD2 16 | end KEYWORD2 17 | 18 | beginPacket KEYWORD2 19 | beginExtendedPacket KEYWORD2 20 | endPacket KEYWORD2 21 | 22 | parsePacket KEYWORD2 23 | packetId KEYWORD2 24 | packetExtended KEYWORD2 25 | packetRtr KEYWORD2 26 | packetDlc KEYWORD2 27 | 28 | write KEYWORD2 29 | 30 | available KEYWORD2 31 | read KEYWORD2 32 | peek KEYWORD2 33 | flush KEYWORD2 34 | 35 | onReceive KEYWORD2 36 | filter KEYWORD2 37 | filterExtended KEYWORD2 38 | loopback KEYWORD2 39 | sleep KEYWORD2 40 | wakeup KEYWORD2 41 | 42 | setPins KEYWORD2 43 | setSPIFrequency KEYWORD2 44 | setClockFrequency KEYWORD2 45 | dumpRegisters KEYWORD2 46 | 47 | ####################################### 48 | # Constants (LITERAL1) 49 | ####################################### 50 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=CAN 2 | version=0.3.1 3 | author=Sandeep Mistry with patches by Timur Iskhodzhanov 4 | maintainer=Timur Iskhodzhanov 5 | sentence=An Arduino library for sending and receiving data using CAN bus. 6 | paragraph=Supports Microchip MCP2515 based boards/shields and the Espressif ESP32's built-in SJA1000 compatible CAN controller. 7 | category=Communication 8 | url=https://github.com/timurrrr/arduino-CAN 9 | architectures=* 10 | includes=CAN.h 11 | -------------------------------------------------------------------------------- /src/CAN.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sandeep Mistry. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #ifndef CAN_H 5 | #define CAN_H 6 | 7 | #ifdef ARDUINO_ARCH_ESP32 8 | #include "ESP32SJA1000.h" 9 | #else 10 | #include "MCP2515.h" 11 | #endif 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/CANController.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sandeep Mistry. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include "CANController.h" 5 | 6 | CANControllerClass::CANControllerClass() : 7 | _onReceive(NULL), 8 | 9 | _packetBegun(false), 10 | _txId(-1), 11 | _txExtended(-1), 12 | _txRtr(false), 13 | _txDlc(0), 14 | _txLength(0), 15 | 16 | _rxId(-1), 17 | _rxExtended(false), 18 | _rxRtr(false), 19 | _rxDlc(0), 20 | _rxLength(0), 21 | _rxIndex(0) 22 | { 23 | // overide Stream timeout value 24 | setTimeout(0); 25 | } 26 | 27 | CANControllerClass::~CANControllerClass() 28 | { 29 | } 30 | 31 | int CANControllerClass::begin(long /*baudRate*/) 32 | { 33 | _packetBegun = false; 34 | _txId = -1; 35 | _txRtr =false; 36 | _txDlc = 0; 37 | _txLength = 0; 38 | 39 | _rxId = -1; 40 | _rxRtr = false; 41 | _rxDlc = 0; 42 | _rxLength = 0; 43 | _rxIndex = 0; 44 | 45 | return 1; 46 | } 47 | 48 | void CANControllerClass::end() 49 | { 50 | } 51 | 52 | int CANControllerClass::beginPacket(int id, int dlc, bool rtr) 53 | { 54 | if (id < 0 || id > 0x7FF) { 55 | return 0; 56 | } 57 | 58 | if (dlc > 8) { 59 | return 0; 60 | } 61 | 62 | _packetBegun = true; 63 | _txId = id; 64 | _txExtended = false; 65 | _txRtr = rtr; 66 | _txDlc = dlc; 67 | _txLength = 0; 68 | 69 | memset(_txData, 0x00, sizeof(_txData)); 70 | 71 | return 1; 72 | } 73 | 74 | int CANControllerClass::beginExtendedPacket(long id, int dlc, bool rtr) 75 | { 76 | if (id < 0 || id > 0x1FFFFFFF) { 77 | return 0; 78 | } 79 | 80 | if (dlc > 8) { 81 | return 0; 82 | } 83 | 84 | _packetBegun = true; 85 | _txId = id; 86 | _txExtended = true; 87 | _txRtr = rtr; 88 | _txDlc = dlc; 89 | _txLength = 0; 90 | 91 | memset(_txData, 0x00, sizeof(_txData)); 92 | 93 | return 1; 94 | } 95 | 96 | int CANControllerClass::endPacket() 97 | { 98 | if (!_packetBegun) { 99 | return 0; 100 | } 101 | _packetBegun = false; 102 | 103 | if (_txDlc >= 0) { 104 | _txLength = _txDlc; 105 | } 106 | 107 | return 1; 108 | } 109 | 110 | int CANControllerClass::parsePacket() 111 | { 112 | return 0; 113 | } 114 | 115 | long CANControllerClass::packetId() 116 | { 117 | return _rxId; 118 | } 119 | 120 | bool CANControllerClass::packetExtended() 121 | { 122 | return _rxExtended; 123 | } 124 | 125 | bool CANControllerClass::packetRtr() 126 | { 127 | return _rxRtr; 128 | } 129 | 130 | int CANControllerClass::packetDlc() 131 | { 132 | return _rxDlc; 133 | } 134 | 135 | size_t CANControllerClass::write(uint8_t byte) 136 | { 137 | return write(&byte, sizeof(byte)); 138 | } 139 | 140 | size_t CANControllerClass::write(const uint8_t *buffer, size_t size) 141 | { 142 | if (!_packetBegun) { 143 | return 0; 144 | } 145 | 146 | if (size > (sizeof(_txData) - _txLength)) { 147 | size = sizeof(_txData) - _txLength; 148 | } 149 | 150 | memcpy(&_txData[_txLength], buffer, size); 151 | _txLength += size; 152 | 153 | return size; 154 | } 155 | 156 | int CANControllerClass::available() 157 | { 158 | return (_rxLength - _rxIndex); 159 | } 160 | 161 | int CANControllerClass::read() 162 | { 163 | if (!available()) { 164 | return -1; 165 | } 166 | 167 | return _rxData[_rxIndex++]; 168 | } 169 | 170 | int CANControllerClass::peek() 171 | { 172 | if (!available()) { 173 | return -1; 174 | } 175 | 176 | return _rxData[_rxIndex]; 177 | } 178 | 179 | void CANControllerClass::flush() 180 | { 181 | } 182 | 183 | void CANControllerClass::onReceive(void(*callback)(int)) 184 | { 185 | _onReceive = callback; 186 | } 187 | 188 | int CANControllerClass::filter(int /*id*/, int /*mask*/) 189 | { 190 | return 0; 191 | } 192 | 193 | int CANControllerClass::filterExtended(long /*id*/, long /*mask*/) 194 | { 195 | return 0; 196 | } 197 | 198 | int CANControllerClass::observe() 199 | { 200 | return 0; 201 | } 202 | 203 | int CANControllerClass::loopback() 204 | { 205 | return 0; 206 | } 207 | 208 | int CANControllerClass::sleep() 209 | { 210 | return 0; 211 | } 212 | 213 | int CANControllerClass::wakeup() 214 | { 215 | return 0; 216 | } 217 | -------------------------------------------------------------------------------- /src/CANController.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sandeep Mistry. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #ifndef CAN_CONTROLLER_H 5 | #define CAN_CONTROLLER_H 6 | 7 | #include 8 | 9 | class CANControllerClass : public Stream { 10 | 11 | public: 12 | virtual int begin(long baudRate); 13 | virtual void end(); 14 | 15 | int beginPacket(int id, int dlc = -1, bool rtr = false); 16 | int beginExtendedPacket(long id, int dlc = -1, bool rtr = false); 17 | virtual int endPacket(); 18 | 19 | virtual int parsePacket(); 20 | long packetId(); 21 | bool packetExtended(); 22 | bool packetRtr(); 23 | int packetDlc(); 24 | 25 | // from Print 26 | virtual size_t write(uint8_t byte); 27 | virtual size_t write(const uint8_t *buffer, size_t size); 28 | 29 | // from Stream 30 | virtual int available(); 31 | virtual int read(); 32 | virtual int peek(); 33 | virtual void flush(); 34 | 35 | virtual void onReceive(void(*callback)(int)); 36 | 37 | virtual int filter(int id) { return filter(id, 0x7ff); } 38 | virtual int filter(int id, int mask); 39 | virtual int filterExtended(long id) { return filterExtended(id, 0x1fffffff); } 40 | virtual int filterExtended(long id, long mask); 41 | 42 | virtual int observe(); 43 | virtual int loopback(); 44 | virtual int sleep(); 45 | virtual int wakeup(); 46 | 47 | protected: 48 | CANControllerClass(); 49 | virtual ~CANControllerClass(); 50 | 51 | protected: 52 | void (*_onReceive)(int); 53 | 54 | bool _packetBegun; 55 | long _txId; 56 | bool _txExtended; 57 | bool _txRtr; 58 | int _txDlc; 59 | int _txLength; 60 | uint8_t _txData[8]; 61 | 62 | long _rxId; 63 | bool _rxExtended; 64 | bool _rxRtr; 65 | int _rxDlc; 66 | int _rxLength; 67 | int _rxIndex; 68 | uint8_t _rxData[8]; 69 | }; 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /src/ESP32SJA1000.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sandeep Mistry. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #ifdef ARDUINO_ARCH_ESP32 5 | 6 | #include "esp_intr.h" 7 | #include "soc/dport_reg.h" 8 | #include "driver/gpio.h" 9 | 10 | #include "ESP32SJA1000.h" 11 | 12 | #define REG_BASE 0x3ff6b000 13 | 14 | #define REG_MOD 0x00 15 | #define REG_CMR 0x01 16 | #define REG_SR 0x02 17 | #define REG_IR 0x03 18 | #define REG_IER 0x04 19 | 20 | #define REG_BTR0 0x06 21 | #define REG_BTR1 0x07 22 | #define REG_OCR 0x08 23 | 24 | #define REG_ALC 0x0b 25 | #define REG_ECC 0x0c 26 | #define REG_EWLR 0x0d 27 | #define REG_RXERR 0x0e 28 | #define REG_TXERR 0x0f 29 | #define REG_SFF 0x10 30 | #define REG_EFF 0x10 31 | #define REG_ACRn(n) (0x10 + n) 32 | #define REG_AMRn(n) (0x14 + n) 33 | 34 | #define REG_CDR 0x1F 35 | 36 | 37 | ESP32SJA1000Class::ESP32SJA1000Class() : 38 | CANControllerClass(), 39 | _rxPin(DEFAULT_CAN_RX_PIN), 40 | _txPin(DEFAULT_CAN_TX_PIN), 41 | _loopback(false), 42 | _intrHandle(NULL) 43 | { 44 | } 45 | 46 | ESP32SJA1000Class::~ESP32SJA1000Class() 47 | { 48 | } 49 | 50 | int ESP32SJA1000Class::begin(long baudRate) 51 | { 52 | CANControllerClass::begin(baudRate); 53 | 54 | _loopback = false; 55 | 56 | DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_CAN_RST); 57 | DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_CAN_CLK_EN); 58 | 59 | // RX pin 60 | gpio_set_direction(_rxPin, GPIO_MODE_INPUT); 61 | gpio_matrix_in(_rxPin, CAN_RX_IDX, 0); 62 | gpio_pad_select_gpio(_rxPin); 63 | 64 | // TX pin 65 | gpio_set_direction(_txPin, GPIO_MODE_OUTPUT); 66 | gpio_matrix_out(_txPin, CAN_TX_IDX, 0, 0); 67 | gpio_pad_select_gpio(_txPin); 68 | 69 | modifyRegister(REG_CDR, 0x80, 0x80); // pelican mode 70 | modifyRegister(REG_BTR0, 0xc0, 0x40); // SJW = 1 71 | modifyRegister(REG_BTR1, 0x70, 0x10); // TSEG2 = 1 72 | 73 | switch (baudRate) { 74 | case (long)1000E3: 75 | modifyRegister(REG_BTR1, 0x0f, 0x04); 76 | modifyRegister(REG_BTR0, 0x3f, 4); 77 | break; 78 | 79 | case (long)500E3: 80 | modifyRegister(REG_BTR1, 0x0f, 0x0c); 81 | modifyRegister(REG_BTR0, 0x3f, 4); 82 | break; 83 | 84 | case (long)250E3: 85 | modifyRegister(REG_BTR1, 0x0f, 0x0c); 86 | modifyRegister(REG_BTR0, 0x3f, 9); 87 | break; 88 | 89 | case (long)200E3: 90 | modifyRegister(REG_BTR1, 0x0f, 0x0c); 91 | modifyRegister(REG_BTR0, 0x3f, 12); 92 | break; 93 | 94 | case (long)125E3: 95 | modifyRegister(REG_BTR1, 0x0f, 0x0c); 96 | modifyRegister(REG_BTR0, 0x3f, 19); 97 | break; 98 | 99 | case (long)100E3: 100 | modifyRegister(REG_BTR1, 0x0f, 0x0c); 101 | modifyRegister(REG_BTR0, 0x3f, 24); 102 | break; 103 | 104 | case (long)80E3: 105 | modifyRegister(REG_BTR1, 0x0f, 0x0c); 106 | modifyRegister(REG_BTR0, 0x3f, 30); 107 | break; 108 | 109 | case (long)50E3: 110 | modifyRegister(REG_BTR1, 0x0f, 0x0c); 111 | modifyRegister(REG_BTR0, 0x3f, 49); 112 | break; 113 | 114 | /* 115 | Due to limitations in ESP32 hardware and/or RTOS software, baudrate can't be lower than 50kbps. 116 | See https://esp32.com/viewtopic.php?t=2142 117 | */ 118 | default: 119 | return 0; 120 | break; 121 | } 122 | 123 | modifyRegister(REG_BTR1, 0x80, 0x80); // SAM = 1 124 | writeRegister(REG_IER, 0xff); // enable all interrupts 125 | 126 | // set filter to allow anything 127 | writeRegister(REG_ACRn(0), 0x00); 128 | writeRegister(REG_ACRn(1), 0x00); 129 | writeRegister(REG_ACRn(2), 0x00); 130 | writeRegister(REG_ACRn(3), 0x00); 131 | writeRegister(REG_AMRn(0), 0xff); 132 | writeRegister(REG_AMRn(1), 0xff); 133 | writeRegister(REG_AMRn(2), 0xff); 134 | writeRegister(REG_AMRn(3), 0xff); 135 | 136 | modifyRegister(REG_OCR, 0x03, 0x02); // normal output mode 137 | // reset error counters 138 | writeRegister(REG_TXERR, 0x00); 139 | writeRegister(REG_RXERR, 0x00); 140 | 141 | // clear errors and interrupts 142 | readRegister(REG_ECC); 143 | readRegister(REG_IR); 144 | 145 | // normal mode 146 | modifyRegister(REG_MOD, 0x08, 0x08); 147 | modifyRegister(REG_MOD, 0x17, 0x00); 148 | 149 | return 1; 150 | } 151 | 152 | void ESP32SJA1000Class::end() 153 | { 154 | if (_intrHandle) { 155 | esp_intr_free(_intrHandle); 156 | _intrHandle = NULL; 157 | } 158 | 159 | DPORT_SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_CAN_RST); 160 | DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_CAN_CLK_EN); 161 | 162 | CANControllerClass::end(); 163 | } 164 | 165 | int ESP32SJA1000Class::endPacket() 166 | { 167 | if (!CANControllerClass::endPacket()) { 168 | return 0; 169 | } 170 | 171 | // wait for TX buffer to free 172 | while ((readRegister(REG_SR) & 0x04) != 0x04) { 173 | yield(); 174 | } 175 | 176 | int dataReg; 177 | 178 | if (_txExtended) { 179 | writeRegister(REG_EFF, 0x80 | (_txRtr ? 0x40 : 0x00) | (0x0f & _txLength)); 180 | writeRegister(REG_EFF + 1, _txId >> 21); 181 | writeRegister(REG_EFF + 2, _txId >> 13); 182 | writeRegister(REG_EFF + 3, _txId >> 5); 183 | writeRegister(REG_EFF + 4, _txId << 3); 184 | 185 | dataReg = REG_EFF + 5; 186 | } else { 187 | writeRegister(REG_SFF, (_txRtr ? 0x40 : 0x00) | (0x0f & _txLength)); 188 | writeRegister(REG_SFF + 1, _txId >> 3); 189 | writeRegister(REG_SFF + 2, _txId << 5); 190 | 191 | dataReg = REG_SFF + 3; 192 | } 193 | 194 | for (int i = 0; i < _txLength; i++) { 195 | writeRegister(dataReg + i, _txData[i]); 196 | } 197 | 198 | if ( _loopback) { 199 | // self reception request 200 | modifyRegister(REG_CMR, 0x1f, 0x10); 201 | } else { 202 | // transmit request 203 | modifyRegister(REG_CMR, 0x1f, 0x01); 204 | } 205 | 206 | // wait for TX complete 207 | while ((readRegister(REG_SR) & 0x08) != 0x08) { 208 | if (readRegister(REG_ECC) == 0xd9) { 209 | modifyRegister(REG_CMR, 0x1f, 0x02); // error, abort 210 | return 0; 211 | } 212 | yield(); 213 | } 214 | 215 | return 1; 216 | } 217 | 218 | int ESP32SJA1000Class::parsePacket() 219 | { 220 | if ((readRegister(REG_SR) & 0x01) != 0x01) { 221 | // no packet 222 | return 0; 223 | } 224 | 225 | _rxExtended = (readRegister(REG_SFF) & 0x80) ? true : false; 226 | _rxRtr = (readRegister(REG_SFF) & 0x40) ? true : false; 227 | _rxDlc = (readRegister(REG_SFF) & 0x0f); 228 | _rxIndex = 0; 229 | 230 | int dataReg; 231 | 232 | if (_rxExtended) { 233 | _rxId = (readRegister(REG_EFF + 1) << 21) | 234 | (readRegister(REG_EFF + 2) << 13) | 235 | (readRegister(REG_EFF + 3) << 5) | 236 | (readRegister(REG_EFF + 4) >> 3); 237 | 238 | dataReg = REG_EFF + 5; 239 | } else { 240 | _rxId = (readRegister(REG_SFF + 1) << 3) | ((readRegister(REG_SFF + 2) >> 5) & 0x07); 241 | 242 | dataReg = REG_SFF + 3; 243 | } 244 | 245 | if (_rxRtr) { 246 | _rxLength = 0; 247 | } else { 248 | _rxLength = _rxDlc; 249 | 250 | for (int i = 0; i < _rxLength; i++) { 251 | _rxData[i] = readRegister(dataReg + i); 252 | } 253 | } 254 | 255 | // release RX buffer 256 | modifyRegister(REG_CMR, 0x04, 0x04); 257 | 258 | return _rxDlc; 259 | } 260 | 261 | void ESP32SJA1000Class::onReceive(void(*callback)(int)) 262 | { 263 | CANControllerClass::onReceive(callback); 264 | 265 | if (_intrHandle) { 266 | esp_intr_free(_intrHandle); 267 | _intrHandle = NULL; 268 | } 269 | 270 | if (callback) { 271 | esp_intr_alloc(ETS_CAN_INTR_SOURCE, 0, ESP32SJA1000Class::onInterrupt, this, &_intrHandle); 272 | } 273 | } 274 | 275 | int ESP32SJA1000Class::filter(int id, int mask) 276 | { 277 | id &= 0x7ff; 278 | mask = ~(mask & 0x7ff); 279 | 280 | modifyRegister(REG_MOD, 0x17, 0x01); // reset 281 | 282 | writeRegister(REG_ACRn(0), id >> 3); 283 | writeRegister(REG_ACRn(1), id << 5); 284 | writeRegister(REG_ACRn(2), 0x00); 285 | writeRegister(REG_ACRn(3), 0x00); 286 | 287 | writeRegister(REG_AMRn(0), mask >> 3); 288 | writeRegister(REG_AMRn(1), (mask << 5) | 0x1f); 289 | writeRegister(REG_AMRn(2), 0xff); 290 | writeRegister(REG_AMRn(3), 0xff); 291 | 292 | modifyRegister(REG_MOD, 0x17, 0x00); // normal 293 | 294 | return 1; 295 | } 296 | 297 | int ESP32SJA1000Class::filterExtended(long id, long mask) 298 | { 299 | id &= 0x1FFFFFFF; 300 | mask &= ~(mask & 0x1FFFFFFF); 301 | 302 | modifyRegister(REG_MOD, 0x17, 0x01); // reset 303 | 304 | writeRegister(REG_ACRn(0), id >> 21); 305 | writeRegister(REG_ACRn(1), id >> 13); 306 | writeRegister(REG_ACRn(2), id >> 5); 307 | writeRegister(REG_ACRn(3), id << 5); 308 | 309 | writeRegister(REG_AMRn(0), mask >> 21); 310 | writeRegister(REG_AMRn(1), mask >> 13); 311 | writeRegister(REG_AMRn(2), mask >> 5); 312 | writeRegister(REG_AMRn(3), (mask << 5) | 0x1f); 313 | 314 | modifyRegister(REG_MOD, 0x17, 0x00); // normal 315 | 316 | return 1; 317 | } 318 | 319 | int ESP32SJA1000Class::observe() 320 | { 321 | modifyRegister(REG_MOD, 0x17, 0x01); // reset 322 | modifyRegister(REG_MOD, 0x17, 0x02); // observe 323 | 324 | return 1; 325 | } 326 | 327 | int ESP32SJA1000Class::loopback() 328 | { 329 | _loopback = true; 330 | 331 | modifyRegister(REG_MOD, 0x17, 0x01); // reset 332 | modifyRegister(REG_MOD, 0x17, 0x04); // self test mode 333 | 334 | return 1; 335 | } 336 | 337 | int ESP32SJA1000Class::sleep() 338 | { 339 | modifyRegister(REG_MOD, 0x1f, 0x10); 340 | 341 | return 1; 342 | } 343 | 344 | int ESP32SJA1000Class::wakeup() 345 | { 346 | modifyRegister(REG_MOD, 0x1f, 0x00); 347 | 348 | return 1; 349 | } 350 | 351 | void ESP32SJA1000Class::setPins(int rx, int tx) 352 | { 353 | _rxPin = (gpio_num_t)rx; 354 | _txPin = (gpio_num_t)tx; 355 | } 356 | 357 | void ESP32SJA1000Class::dumpRegisters(Stream& out) 358 | { 359 | for (int i = 0; i < 32; i++) { 360 | byte b = readRegister(i); 361 | 362 | out.print("0x"); 363 | if (i < 16) { 364 | out.print('0'); 365 | } 366 | out.print(i, HEX); 367 | out.print(": 0x"); 368 | if (b < 16) { 369 | out.print('0'); 370 | } 371 | out.println(b, HEX); 372 | } 373 | } 374 | 375 | void ESP32SJA1000Class::handleInterrupt() 376 | { 377 | uint8_t ir = readRegister(REG_IR); 378 | 379 | if (ir & 0x01) { 380 | // received packet, parse and call callback 381 | parsePacket(); 382 | 383 | _onReceive(available()); 384 | } 385 | } 386 | 387 | uint8_t ESP32SJA1000Class::readRegister(uint8_t address) 388 | { 389 | volatile uint32_t* reg = (volatile uint32_t*)(REG_BASE + address * 4); 390 | 391 | return *reg; 392 | } 393 | 394 | void ESP32SJA1000Class::modifyRegister(uint8_t address, uint8_t mask, uint8_t value) 395 | { 396 | volatile uint32_t* reg = (volatile uint32_t*)(REG_BASE + address * 4); 397 | 398 | *reg = (*reg & ~mask) | value; 399 | } 400 | 401 | void ESP32SJA1000Class::writeRegister(uint8_t address, uint8_t value) 402 | { 403 | volatile uint32_t* reg = (volatile uint32_t*)(REG_BASE + address * 4); 404 | 405 | *reg = value; 406 | } 407 | 408 | void ESP32SJA1000Class::onInterrupt(void* arg) 409 | { 410 | ((ESP32SJA1000Class*)arg)->handleInterrupt(); 411 | } 412 | 413 | ESP32SJA1000Class CAN; 414 | 415 | #endif 416 | -------------------------------------------------------------------------------- /src/ESP32SJA1000.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sandeep Mistry. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #ifdef ARDUINO_ARCH_ESP32 5 | 6 | #ifndef ESP32_SJA1000_H 7 | #define ESP32_SJA1000_H 8 | 9 | #include "CANController.h" 10 | 11 | #define DEFAULT_CAN_RX_PIN GPIO_NUM_4 12 | #define DEFAULT_CAN_TX_PIN GPIO_NUM_5 13 | 14 | class ESP32SJA1000Class : public CANControllerClass { 15 | 16 | public: 17 | ESP32SJA1000Class(); 18 | virtual ~ESP32SJA1000Class(); 19 | 20 | virtual int begin(long baudRate); 21 | virtual void end(); 22 | 23 | virtual int endPacket(); 24 | 25 | virtual int parsePacket(); 26 | 27 | virtual void onReceive(void(*callback)(int)); 28 | 29 | using CANControllerClass::filter; 30 | virtual int filter(int id, int mask); 31 | using CANControllerClass::filterExtended; 32 | virtual int filterExtended(long id, long mask); 33 | 34 | virtual int observe(); 35 | virtual int loopback(); 36 | virtual int sleep(); 37 | virtual int wakeup(); 38 | 39 | void setPins(int rx, int tx); 40 | 41 | void dumpRegisters(Stream& out); 42 | 43 | private: 44 | void reset(); 45 | 46 | void handleInterrupt(); 47 | 48 | uint8_t readRegister(uint8_t address); 49 | void modifyRegister(uint8_t address, uint8_t mask, uint8_t value); 50 | void writeRegister(uint8_t address, uint8_t value); 51 | 52 | static void onInterrupt(void* arg); 53 | 54 | private: 55 | gpio_num_t _rxPin; 56 | gpio_num_t _txPin; 57 | bool _loopback; 58 | intr_handle_t _intrHandle; 59 | }; 60 | 61 | extern ESP32SJA1000Class CAN; 62 | 63 | #endif 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /src/MCP2515.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sandeep Mistry. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #ifndef ARDUINO_ARCH_ESP32 5 | 6 | #include "MCP2515.h" 7 | 8 | #define REG_BFPCTRL 0x0c 9 | #define REG_TXRTSCTRL 0x0d 10 | 11 | #define REG_CANCTRL 0x0f 12 | 13 | #define REG_CNF3 0x28 14 | #define REG_CNF2 0x29 15 | #define REG_CNF1 0x2a 16 | 17 | #define REG_CANINTE 0x2b 18 | 19 | // Whenever changing the CANINTF register, use BIT MODIFY instead of WRITE. 20 | #define REG_CANINTF 0x2c 21 | 22 | #define FLAG_RXnIE(n) (0x01 << n) 23 | #define FLAG_RXnIF(n) (0x01 << n) 24 | #define FLAG_TXnIF(n) (0x04 << n) 25 | 26 | // There is a 4-register gap between RXF2EID0 and RXF3SIDH. 27 | #define REG_RXFnSIDH(n) (0x00 + ((n + (n >= 3)) * 4)) 28 | #define REG_RXFnSIDL(n) (0x01 + ((n + (n >= 3)) * 4)) 29 | #define REG_RXFnEID8(n) (0x02 + ((n + (n >= 3)) * 4)) 30 | #define REG_RXFnEID0(n) (0x03 + ((n + (n >= 3)) * 4)) 31 | 32 | #define REG_RXMnSIDH(n) (0x20 + (n * 0x04)) 33 | #define REG_RXMnSIDL(n) (0x21 + (n * 0x04)) 34 | #define REG_RXMnEID8(n) (0x22 + (n * 0x04)) 35 | #define REG_RXMnEID0(n) (0x23 + (n * 0x04)) 36 | 37 | #define REG_TXBnCTRL(n) (0x30 + (n * 0x10)) 38 | #define REG_TXBnSIDH(n) (0x31 + (n * 0x10)) 39 | #define REG_TXBnSIDL(n) (0x32 + (n * 0x10)) 40 | #define REG_TXBnEID8(n) (0x33 + (n * 0x10)) 41 | #define REG_TXBnEID0(n) (0x34 + (n * 0x10)) 42 | #define REG_TXBnDLC(n) (0x35 + (n * 0x10)) 43 | #define REG_TXBnD0(n) (0x36 + (n * 0x10)) 44 | 45 | #define REG_RXBnCTRL(n) (0x60 + (n * 0x10)) 46 | #define REG_RXBnSIDH(n) (0x61 + (n * 0x10)) 47 | #define REG_RXBnSIDL(n) (0x62 + (n * 0x10)) 48 | #define REG_RXBnEID8(n) (0x63 + (n * 0x10)) 49 | #define REG_RXBnEID0(n) (0x64 + (n * 0x10)) 50 | #define REG_RXBnDLC(n) (0x65 + (n * 0x10)) 51 | #define REG_RXBnD0(n) (0x66 + (n * 0x10)) 52 | 53 | #define FLAG_IDE 0x08 54 | #define FLAG_SRR 0x10 55 | #define FLAG_RTR 0x40 56 | #define FLAG_EXIDE 0x08 57 | #define FLAG_RXB0CTRL_BUKT 0x04 58 | 59 | #define FLAG_RXM0 0x20 60 | #define FLAG_RXM1 0x40 61 | 62 | 63 | MCP2515Class::MCP2515Class() : 64 | CANControllerClass(), 65 | _spiSettings(10E6, MSBFIRST, SPI_MODE0), 66 | _csPin(MCP2515_DEFAULT_CS_PIN), 67 | _intPin(MCP2515_DEFAULT_INT_PIN), 68 | _clockFrequency(MCP2515_DEFAULT_CLOCK_FREQUENCY) 69 | { 70 | } 71 | 72 | MCP2515Class::~MCP2515Class() 73 | { 74 | } 75 | 76 | int MCP2515Class::begin(long baudRate, bool stayInConfigurationMode) 77 | { 78 | CANControllerClass::begin(baudRate); 79 | 80 | pinMode(_csPin, OUTPUT); 81 | 82 | // start SPI 83 | SPI.begin(); 84 | 85 | reset(); 86 | 87 | if (!switchToConfigurationMode()) { 88 | return 0; 89 | } 90 | 91 | const struct { 92 | long clockFrequency; 93 | long baudRate; 94 | uint8_t cnf[3]; 95 | } CNF_MAPPER[] = { 96 | { (long)8E6, (long)1000E3, { 0x00, 0x80, 0x00 } }, 97 | { (long)8E6, (long)500E3, { 0x00, 0x90, 0x02 } }, 98 | { (long)8E6, (long)250E3, { 0x00, 0xb1, 0x05 } }, 99 | { (long)8E6, (long)200E3, { 0x00, 0xb4, 0x06 } }, 100 | { (long)8E6, (long)125E3, { 0x01, 0xb1, 0x05 } }, 101 | { (long)8E6, (long)100E3, { 0x01, 0xb4, 0x06 } }, 102 | { (long)8E6, (long)80E3, { 0x01, 0xbf, 0x07 } }, 103 | { (long)8E6, (long)50E3, { 0x03, 0xb4, 0x06 } }, 104 | { (long)8E6, (long)40E3, { 0x03, 0xbf, 0x07 } }, 105 | { (long)8E6, (long)20E3, { 0x07, 0xbf, 0x07 } }, 106 | { (long)8E6, (long)10E3, { 0x0f, 0xbf, 0x07 } }, 107 | { (long)8E6, (long)5E3, { 0x1f, 0xbf, 0x07 } }, 108 | 109 | { (long)16E6, (long)1000E3, { 0x00, 0xd0, 0x82 } }, 110 | { (long)16E6, (long)500E3, { 0x00, 0xf0, 0x86 } }, 111 | { (long)16E6, (long)250E3, { 0x41, 0xf1, 0x85 } }, 112 | { (long)16E6, (long)200E3, { 0x01, 0xfa, 0x87 } }, 113 | { (long)16E6, (long)125E3, { 0x03, 0xf0, 0x86 } }, 114 | { (long)16E6, (long)100E3, { 0x03, 0xfa, 0x87 } }, 115 | { (long)16E6, (long)80E3, { 0x03, 0xff, 0x87 } }, 116 | { (long)16E6, (long)50E3, { 0x07, 0xfa, 0x87 } }, 117 | { (long)16E6, (long)40E3, { 0x07, 0xff, 0x87 } }, 118 | { (long)16E6, (long)20E3, { 0x0f, 0xff, 0x87 } }, 119 | { (long)16E6, (long)10E3, { 0x1f, 0xff, 0x87 } }, 120 | { (long)16E6, (long)5E3, { 0x3f, 0xff, 0x87 } }, 121 | }; 122 | 123 | const uint8_t* cnf = NULL; 124 | 125 | for (unsigned int i = 0; i < (sizeof(CNF_MAPPER) / sizeof(CNF_MAPPER[0])); i++) { 126 | if (CNF_MAPPER[i].clockFrequency == _clockFrequency && CNF_MAPPER[i].baudRate == baudRate) { 127 | cnf = CNF_MAPPER[i].cnf; 128 | break; 129 | } 130 | } 131 | 132 | if (cnf == NULL) { 133 | return 0; 134 | } 135 | 136 | writeRegister(REG_CNF1, cnf[0]); 137 | writeRegister(REG_CNF2, cnf[1]); 138 | writeRegister(REG_CNF3, cnf[2]); 139 | 140 | writeRegister(REG_CANINTE, FLAG_RXnIE(1) | FLAG_RXnIE(0)); 141 | writeRegister(REG_BFPCTRL, 0x00); 142 | writeRegister(REG_TXRTSCTRL, 0x00); 143 | 144 | // A combination of RXM1 and RXM0 is "Turns mask/filters off; receives any message". 145 | writeRegister(REG_RXBnCTRL(0), FLAG_RXM1 | FLAG_RXM0); 146 | writeRegister(REG_RXBnCTRL(1), FLAG_RXM1 | FLAG_RXM0); 147 | 148 | if (!stayInConfigurationMode) { 149 | if (!switchToNormalMode()) { 150 | return 0; 151 | } 152 | } 153 | 154 | return 1; 155 | } 156 | 157 | void MCP2515Class::end() 158 | { 159 | SPI.end(); 160 | 161 | CANControllerClass::end(); 162 | } 163 | 164 | int MCP2515Class::endPacket() 165 | { 166 | if (!CANControllerClass::endPacket()) { 167 | return 0; 168 | } 169 | 170 | // Currently, we don't need to use more than one TX buffer as we always wait 171 | // until the data has been fully transmitted. For the same reason, we don't 172 | // need to check in the beginning whether there is any data in the TX buffer 173 | // pending transmission. The performance can be optimized by utilizing all 174 | // three TX buffers of the MCP2515, but this will come at extra complexity. 175 | int n = 0; 176 | 177 | // Pre-calculate values for all registers so that we can write them 178 | // sequentially via the LOAD TX BUFFER instruction. 179 | // TX BUFFER 180 | uint8_t regSIDH; 181 | uint8_t regSIDL; 182 | uint8_t regEID8; 183 | uint8_t regEID0; 184 | if (_txExtended) { 185 | regSIDH = _txId >> 21; 186 | regSIDL = 187 | (((_txId >> 18) & 0x07) << 5) | FLAG_EXIDE | ((_txId >> 16) & 0x03); 188 | regEID8 = (_txId >> 8) & 0xff; 189 | regEID0 = _txId & 0xff; 190 | } else { 191 | regSIDH = _txId >> 3; 192 | regSIDL = _txId << 5; 193 | regEID8 = 0x00; 194 | regEID0 = 0x00; 195 | } 196 | 197 | uint8_t regDLC; 198 | if (_txRtr) { 199 | regDLC = 0x40 | _txLength; 200 | } else { 201 | regDLC = _txLength; 202 | } 203 | 204 | SPI.beginTransaction(_spiSettings); 205 | digitalWrite(_csPin, LOW); 206 | // Send the LOAD TX BUFFER instruction to sequentially write registers, 207 | // starting from TXBnSIDH(n). 208 | SPI.transfer(0b01000000 | (n << 1)); 209 | SPI.transfer(regSIDH); 210 | SPI.transfer(regSIDL); 211 | SPI.transfer(regEID8); 212 | SPI.transfer(regEID0); 213 | SPI.transfer(regDLC); 214 | if (!_txRtr) { 215 | for (uint8_t i = 0; i < _txLength; i++) { 216 | SPI.transfer(_txData[i]); 217 | } 218 | } 219 | digitalWrite(_csPin, HIGH); 220 | SPI.endTransaction(); 221 | 222 | SPI.beginTransaction(_spiSettings); 223 | digitalWrite(_csPin, LOW); 224 | // Send the RTS instruction, which sets the TXREQ (TXBnCTRL[3]) bit for the 225 | // respective buffer, and clears the ABTF, MLOA and TXERR bits. 226 | SPI.transfer(0b10000000 | (1 << n)); 227 | digitalWrite(_csPin, HIGH); 228 | SPI.endTransaction(); 229 | 230 | // Wait until the transmission completes, or gets aborted. 231 | // Transmission is pending while TXREQ (TXBnCTRL[3]) bit is set. 232 | bool aborted = false; 233 | while (readRegister(REG_TXBnCTRL(n)) & 0x08) { 234 | // Read the TXERR (TXBnCTRL[4]) bit to check for errors. 235 | if (readRegister(REG_TXBnCTRL(n)) & 0x10) { 236 | // Abort on errors by setting the ABAT bit. The MCP2515 will should the 237 | // TXREQ bit shortly. We'll keep running the loop until TXREQ is cleared. 238 | modifyRegister(REG_CANCTRL, 0x10, 0x10); 239 | aborted = true; 240 | } 241 | 242 | yield(); 243 | } 244 | 245 | if (aborted) { 246 | // Reset the ABAT bit. 247 | modifyRegister(REG_CANCTRL, 0x10, 0x00); 248 | } 249 | 250 | // Clear the pending TX interrupt, if any. 251 | modifyRegister(REG_CANINTF, FLAG_TXnIF(n), 0x00); 252 | 253 | // Report failure if either of the ABTF, MLOA or TXERR bits are set. 254 | // TODO: perhaps we can reuse the last value read from this register // earlier? 255 | return (readRegister(REG_TXBnCTRL(n)) & 0x70) ? 0 : 1; 256 | } 257 | 258 | int MCP2515Class::parsePacket() 259 | { 260 | SPI.beginTransaction(_spiSettings); 261 | digitalWrite(_csPin, LOW); 262 | SPI.transfer(0xb0); // RX STATUS 263 | uint8_t rxStatus = SPI.transfer(0x00); 264 | digitalWrite(_csPin, HIGH); 265 | SPI.endTransaction(); 266 | 267 | int n; 268 | if (rxStatus & 0x40) { 269 | n = 0; 270 | } else if (rxStatus & 0x80) { 271 | n = 1; 272 | } else { 273 | _rxId = -1; 274 | _rxExtended = false; 275 | _rxRtr = false; 276 | _rxDlc = 0; 277 | _rxIndex = 0; 278 | _rxLength = 0; 279 | return 0; 280 | } 281 | 282 | SPI.beginTransaction(_spiSettings); 283 | digitalWrite(_csPin, LOW); 284 | // Send READ RX BUFFER instruction to sequentially read registers, starting 285 | // from RXBnSIDH(n). 286 | SPI.transfer(0b10010000 | (n * 0x04)); 287 | uint8_t regSIDH = SPI.transfer(0x00); 288 | uint8_t regSIDL = SPI.transfer(0x00); 289 | _rxExtended = (regSIDL & FLAG_IDE) ? true : false; 290 | 291 | // We could just skip the extended registers for standard frames, but that 292 | // would actually add more overhead, and increase complexity. 293 | uint8_t regEID8 = SPI.transfer(0x00); 294 | uint8_t regEID0 = SPI.transfer(0x00); 295 | uint8_t regDLC = SPI.transfer(0x00); 296 | uint32_t idA = (regSIDH << 3) | (regSIDL >> 5); 297 | if (_rxExtended) { 298 | uint32_t idB = 299 | ((uint32_t)(regSIDL & 0x03) << 16) 300 | | ((uint32_t)regEID8 << 8) 301 | | regEID0; 302 | 303 | _rxId = (idA << 18) | idB; 304 | _rxRtr = (regDLC & FLAG_RTR) ? true : false; 305 | } else { 306 | _rxId = idA; 307 | _rxRtr = (regSIDL & FLAG_SRR) ? true : false; 308 | } 309 | 310 | _rxDlc = regDLC & 0x0f; 311 | _rxIndex = 0; 312 | 313 | if (_rxRtr) { 314 | _rxLength = 0; 315 | } else { 316 | _rxLength = _rxDlc; 317 | 318 | // Get the data. 319 | for (uint8_t i = 0; i < _rxLength; i++) { 320 | _rxData[i] = SPI.transfer(0x00); 321 | } 322 | } 323 | 324 | // Don't need to unset the RXnIF(n) flag as this is done automatically when 325 | // setting the CS high after a READ RX BUFFER instruction. 326 | digitalWrite(_csPin, HIGH); 327 | SPI.endTransaction(); 328 | return _rxDlc; 329 | } 330 | 331 | void MCP2515Class::onReceive(void(*callback)(int)) 332 | { 333 | CANControllerClass::onReceive(callback); 334 | 335 | pinMode(_intPin, INPUT); 336 | 337 | if (callback) { 338 | SPI.usingInterrupt(digitalPinToInterrupt(_intPin)); 339 | attachInterrupt(digitalPinToInterrupt(_intPin), MCP2515Class::onInterrupt, LOW); 340 | } else { 341 | detachInterrupt(digitalPinToInterrupt(_intPin)); 342 | #ifdef SPI_HAS_NOTUSINGINTERRUPT 343 | SPI.notUsingInterrupt(digitalPinToInterrupt(_intPin)); 344 | #endif 345 | } 346 | } 347 | 348 | int MCP2515Class::filter(int id, int mask) 349 | { 350 | id &= 0x7ff; 351 | mask &= 0x7ff; 352 | 353 | // config mode 354 | writeRegister(REG_CANCTRL, 0x80); 355 | // TODO: The requested mode must be verified by reading the OPMODE[2:0] bits (CANSTAT[7:5]) 356 | if (readRegister(REG_CANCTRL) != 0x80) { 357 | return 0; 358 | } 359 | 360 | for (int n = 0; n < 2; n++) { 361 | // standard only 362 | // TODO: This doesn't look correct. According to the datasheet, the RXM0 and 363 | // RMX1 should either both be unset (in which case filters are active), or 364 | // both be unset (in which case all filters are ignored). 365 | // Either way, it's unclear why we write to the same register twice here. 366 | writeRegister(REG_RXBnCTRL(n), FLAG_RXM0); 367 | writeRegister(REG_RXBnCTRL(n), FLAG_RXM0); 368 | 369 | writeRegister(REG_RXMnSIDH(n), mask >> 3); 370 | writeRegister(REG_RXMnSIDL(n), mask << 5); 371 | writeRegister(REG_RXMnEID8(n), 0); 372 | writeRegister(REG_RXMnEID0(n), 0); 373 | } 374 | 375 | for (int n = 0; n < 6; n++) { 376 | writeRegister(REG_RXFnSIDH(n), id >> 3); 377 | writeRegister(REG_RXFnSIDL(n), id << 5); 378 | writeRegister(REG_RXFnEID8(n), 0); 379 | writeRegister(REG_RXFnEID0(n), 0); 380 | } 381 | 382 | // normal mode 383 | writeRegister(REG_CANCTRL, 0x00); 384 | // TODO: The requested mode must be verified by reading the OPMODE[2:0] bits (CANSTAT[7:5]) 385 | if (readRegister(REG_CANCTRL) != 0x00) { 386 | return 0; 387 | } 388 | 389 | return 1; 390 | } 391 | 392 | boolean MCP2515Class::setFilterRegisters( 393 | uint16_t mask0, uint16_t filter0, uint16_t filter1, 394 | uint16_t mask1, uint16_t filter2, uint16_t filter3, uint16_t filter4, uint16_t filter5, 395 | bool allowRollover) 396 | { 397 | mask0 &= 0x7ff; 398 | filter0 &= 0x7ff; 399 | filter1 &= 0x7ff; 400 | mask1 &= 0x7ff; 401 | filter2 &= 0x7ff; 402 | filter3 &= 0x7ff; 403 | filter4 &= 0x7ff; 404 | filter5 &= 0x7ff; 405 | 406 | if (!switchToConfigurationMode()) { 407 | return false; 408 | } 409 | 410 | writeRegister(REG_RXBnCTRL(0), allowRollover ? FLAG_RXB0CTRL_BUKT : 0); 411 | writeRegister(REG_RXBnCTRL(1), 0); 412 | for (int n = 0; n < 2; n++) { 413 | uint8_t mask = (n == 0) ? mask0 : mask1; 414 | writeRegister(REG_RXMnSIDH(n), mask >> 3); 415 | writeRegister(REG_RXMnSIDL(n), mask << 5); 416 | writeRegister(REG_RXMnEID8(n), 0); 417 | writeRegister(REG_RXMnEID0(n), 0); 418 | } 419 | 420 | uint8_t filter_array[6] = 421 | {filter0, filter1, filter2, filter3, filter4, filter5}; 422 | for (int n = 0; n < 6; n++) { 423 | uint8_t id = filter_array[n]; 424 | writeRegister(REG_RXFnSIDH(n), id >> 3); 425 | writeRegister(REG_RXFnSIDL(n), id << 5); 426 | writeRegister(REG_RXFnEID8(n), 0); 427 | writeRegister(REG_RXFnEID0(n), 0); 428 | } 429 | 430 | if (!switchToNormalMode()) { 431 | return false; 432 | } 433 | 434 | return true; 435 | } 436 | 437 | int MCP2515Class::filterExtended(long id, long mask) 438 | { 439 | id &= 0x1FFFFFFF; 440 | mask &= 0x1FFFFFFF; 441 | 442 | // config mode 443 | writeRegister(REG_CANCTRL, 0x80); 444 | // TODO: The requested mode must be verified by reading the OPMODE[2:0] bits (CANSTAT[7:5]) 445 | if (readRegister(REG_CANCTRL) != 0x80) { 446 | return 0; 447 | } 448 | 449 | for (int n = 0; n < 2; n++) { 450 | // extended only 451 | // TODO: This doesn't look correct. According to the datasheet, the RXM0 and 452 | // RMX1 should either both be unset (in which case filters are active), or 453 | // both be unset (in which case all filters are ignored). 454 | // Either way, it's unclear why we write to the same register twice here. 455 | writeRegister(REG_RXBnCTRL(n), FLAG_RXM1); 456 | writeRegister(REG_RXBnCTRL(n), FLAG_RXM1); 457 | 458 | writeRegister(REG_RXMnSIDH(n), mask >> 21); 459 | writeRegister(REG_RXMnSIDL(n), (((mask >> 18) & 0x03) << 5) | FLAG_EXIDE | ((mask >> 16) & 0x03)); 460 | writeRegister(REG_RXMnEID8(n), (mask >> 8) & 0xff); 461 | writeRegister(REG_RXMnEID0(n), mask & 0xff); 462 | } 463 | 464 | for (int n = 0; n < 6; n++) { 465 | writeRegister(REG_RXFnSIDH(n), id >> 21); 466 | writeRegister(REG_RXFnSIDL(n), (((id >> 18) & 0x03) << 5) | FLAG_EXIDE | ((id >> 16) & 0x03)); 467 | writeRegister(REG_RXFnEID8(n), (id >> 8) & 0xff); 468 | writeRegister(REG_RXFnEID0(n), id & 0xff); 469 | } 470 | 471 | // normal mode 472 | writeRegister(REG_CANCTRL, 0x00); 473 | // TODO: The requested mode must be verified by reading the OPMODE[2:0] bits (CANSTAT[7:5]) 474 | if (readRegister(REG_CANCTRL) != 0x00) { 475 | return 0; 476 | } 477 | 478 | return 1; 479 | } 480 | 481 | bool MCP2515Class::switchToNormalMode() { 482 | // TODO: Should we use modifyRegister(REG_CANCTRL, 0xe0, 0x00) here instead? 483 | writeRegister(REG_CANCTRL, 0x00); 484 | return (readRegister(REG_CANCTRL) & 0xe0) == 0x00; 485 | } 486 | 487 | bool MCP2515Class::switchToConfigurationMode() 488 | { 489 | // TODO: Should we use modifyRegister(REG_CANCTRL, 0xe0, 0x80) here instead? 490 | writeRegister(REG_CANCTRL, 0x80); 491 | return (readRegister(REG_CANCTRL) & 0xe0) == 0x80; 492 | } 493 | 494 | int MCP2515Class::observe() 495 | { 496 | // TODO: These should probably be 0x60, not 0x80. 497 | writeRegister(REG_CANCTRL, 0x80); 498 | // TODO: The requested mode must be verified by reading the OPMODE[2:0] bits (CANSTAT[7:5]) 499 | if (readRegister(REG_CANCTRL) != 0x80) { 500 | return 0; 501 | } 502 | 503 | return 1; 504 | } 505 | 506 | int MCP2515Class::loopback() 507 | { 508 | writeRegister(REG_CANCTRL, 0x40); 509 | // TODO: The requested mode must be verified by reading the OPMODE[2:0] bits (CANSTAT[7:5]) 510 | if (readRegister(REG_CANCTRL) != 0x40) { 511 | return 0; 512 | } 513 | 514 | return 1; 515 | } 516 | 517 | int MCP2515Class::sleep() 518 | { 519 | writeRegister(REG_CANCTRL, 0x01); 520 | // TODO: The requested mode must be verified by reading the OPMODE[2:0] bits (CANSTAT[7:5]) 521 | if (readRegister(REG_CANCTRL) != 0x01) { 522 | return 0; 523 | } 524 | 525 | return 1; 526 | } 527 | 528 | int MCP2515Class::wakeup() 529 | { 530 | writeRegister(REG_CANCTRL, 0x00); 531 | // TODO: The requested mode must be verified by reading the OPMODE[2:0] bits (CANSTAT[7:5]) 532 | if (readRegister(REG_CANCTRL) != 0x00) { 533 | return 0; 534 | } 535 | 536 | return 1; 537 | } 538 | 539 | void MCP2515Class::setPins(int cs, int irq) 540 | { 541 | _csPin = cs; 542 | _intPin = irq; 543 | } 544 | 545 | void MCP2515Class::setSPIFrequency(uint32_t frequency) 546 | { 547 | _spiSettings = SPISettings(frequency, MSBFIRST, SPI_MODE0); 548 | } 549 | 550 | void MCP2515Class::setClockFrequency(long clockFrequency) 551 | { 552 | _clockFrequency = clockFrequency; 553 | } 554 | 555 | void MCP2515Class::dumpImportantRegisters(Stream& out) { 556 | out.print("TEC: "); 557 | out.println(readRegister(0x1C), HEX); 558 | out.print("REC: "); 559 | out.println(readRegister(0x1D), HEX); 560 | out.print("CANINTE: "); 561 | out.println(readRegister(0x2B), HEX); 562 | 563 | out.print("CANINTF: "); 564 | uint8_t regCANINTF = readRegister(0x2C); 565 | out.print(regCANINTF, HEX); 566 | if (regCANINTF & 0x80) { 567 | out.print(" MERRF"); 568 | } 569 | if (regCANINTF & 0x40) { 570 | out.print(" WAKIF"); 571 | } 572 | if (regCANINTF & 0x20) { 573 | out.print(" ERRIF"); 574 | } 575 | if (regCANINTF & 0x10) { 576 | out.print(" TX2IF"); 577 | } 578 | if (regCANINTF & 0x08) { 579 | out.print(" TX1IF"); 580 | } 581 | if (regCANINTF & 0x04) { 582 | out.print(" TX0IF"); 583 | } 584 | if (regCANINTF & 0x02) { 585 | out.print(" RX1IF"); 586 | } 587 | if (regCANINTF & 0x01) { 588 | out.print(" RX0IF"); 589 | } 590 | out.println(); 591 | 592 | out.print("EFLG: "); 593 | uint8_t regEFLG = readRegister(0x2D); 594 | out.print(regEFLG, HEX); 595 | if (regEFLG & 0x80) { 596 | out.print(" RX1OVR"); 597 | } 598 | if (regEFLG & 0x40) { 599 | out.print(" RX0OVR"); 600 | } 601 | if (regEFLG & 0x20) { 602 | out.print(" TXBO"); 603 | } 604 | if (regEFLG & 0x10) { 605 | out.print(" TXEP"); 606 | } 607 | if (regEFLG & 0x08) { 608 | out.print(" RXEP"); 609 | } 610 | if (regEFLG & 0x04) { 611 | out.print(" TXWAR"); 612 | } 613 | if (regEFLG & 0x02) { 614 | out.print(" RXWAR"); 615 | } 616 | if (regEFLG & 0x01) { 617 | out.print(" EWARN"); 618 | } 619 | out.println(); 620 | } 621 | 622 | void MCP2515Class::dumpRegisters(Stream& out) 623 | { 624 | for (int i = 0; i < 128; i++) { 625 | byte b = readRegister(i); 626 | 627 | out.print("0x"); 628 | if (i < 16) { 629 | out.print('0'); 630 | } 631 | out.print(i, HEX); 632 | out.print(": 0x"); 633 | if (b < 16) { 634 | out.print('0'); 635 | } 636 | out.println(b, HEX); 637 | } 638 | } 639 | 640 | void MCP2515Class::reset() 641 | { 642 | SPI.beginTransaction(_spiSettings); 643 | digitalWrite(_csPin, LOW); 644 | SPI.transfer(0xc0); 645 | digitalWrite(_csPin, HIGH); 646 | SPI.endTransaction(); 647 | 648 | // From the data sheet: 649 | // The OST keeps the device in a Reset state for 128 OSC1 clock cycles after 650 | // the occurrence of a Power-on Reset, SPI Reset, after the assertion of the 651 | // RESET pin, and after a wake-up from Sleep mode. It should be noted that no 652 | // SPI protocol operations should be attempted until after the OST has 653 | // expired. 654 | // We sleep for 160 cycles to match the old behavior with 16 MHz quartz, and 655 | // to be on the safe side for 8 MHz devices. 656 | delayMicroseconds(ceil(160 * 1000000.0 / _clockFrequency)); 657 | } 658 | 659 | void MCP2515Class::handleInterrupt() 660 | { 661 | if (readRegister(REG_CANINTF) == 0) { 662 | return; 663 | } 664 | 665 | while (parsePacket()) { 666 | _onReceive(available()); 667 | } 668 | } 669 | 670 | uint8_t MCP2515Class::readRegister(uint8_t address) 671 | { 672 | uint8_t value; 673 | 674 | SPI.beginTransaction(_spiSettings); 675 | digitalWrite(_csPin, LOW); 676 | SPI.transfer(0x03); 677 | SPI.transfer(address); 678 | value = SPI.transfer(0x00); 679 | digitalWrite(_csPin, HIGH); 680 | SPI.endTransaction(); 681 | 682 | return value; 683 | } 684 | 685 | void MCP2515Class::modifyRegister(uint8_t address, uint8_t mask, uint8_t value) 686 | { 687 | SPI.beginTransaction(_spiSettings); 688 | digitalWrite(_csPin, LOW); 689 | SPI.transfer(0x05); 690 | SPI.transfer(address); 691 | SPI.transfer(mask); 692 | SPI.transfer(value); 693 | digitalWrite(_csPin, HIGH); 694 | SPI.endTransaction(); 695 | } 696 | 697 | void MCP2515Class::writeRegister(uint8_t address, uint8_t value) 698 | { 699 | SPI.beginTransaction(_spiSettings); 700 | digitalWrite(_csPin, LOW); 701 | SPI.transfer(0x02); 702 | SPI.transfer(address); 703 | SPI.transfer(value); 704 | digitalWrite(_csPin, HIGH); 705 | SPI.endTransaction(); 706 | } 707 | 708 | void MCP2515Class::onInterrupt() 709 | { 710 | CAN.handleInterrupt(); 711 | } 712 | 713 | MCP2515Class CAN; 714 | 715 | #endif 716 | -------------------------------------------------------------------------------- /src/MCP2515.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sandeep Mistry. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #ifndef ARDUINO_ARCH_ESP32 5 | 6 | #ifndef MCP2515_H 7 | #define MCP2515_H 8 | 9 | #include 10 | 11 | #include "CANController.h" 12 | 13 | #define MCP2515_DEFAULT_CLOCK_FREQUENCY 16e6 14 | 15 | #if defined(ARDUINO_ARCH_SAMD) && defined(PIN_SPI_MISO) && defined(PIN_SPI_MOSI) && defined(PIN_SPI_SCK) && (PIN_SPI_MISO == 10) && (PIN_SPI_MOSI == 8) && (PIN_SPI_SCK == 9) 16 | // Arduino MKR board: MKR CAN shield CS is pin 3, INT is pin 7 17 | #define MCP2515_DEFAULT_CS_PIN 3 18 | #define MCP2515_DEFAULT_INT_PIN 7 19 | #else 20 | #define MCP2515_DEFAULT_CS_PIN 10 21 | #define MCP2515_DEFAULT_INT_PIN 2 22 | #endif 23 | 24 | class MCP2515Class : public CANControllerClass { 25 | 26 | public: 27 | MCP2515Class(); 28 | virtual ~MCP2515Class(); 29 | 30 | int begin(long baudRate, bool stayInConfigurationMode); 31 | virtual int begin(long baudRate) { 32 | return begin(baudRate, /* stayInConfigurationMode= */ false); 33 | } 34 | 35 | virtual void end(); 36 | 37 | virtual int endPacket(); 38 | 39 | virtual int parsePacket(); 40 | 41 | virtual void onReceive(void(*callback)(int)); 42 | 43 | using CANControllerClass::filter; 44 | virtual int filter(int id, int mask); 45 | 46 | // mask0 is applied to filter0 and filter1 for RXB0. 47 | // mask1 is applied to filter2, filter3, filter4, filter5 for RXB1. 48 | // allowRollover controls whether messages that would otherwise overflow RXB0 49 | // should be put in RXB1 instead. 50 | // 51 | // See the MCP2515 datasheet for more info. 52 | boolean setFilterRegisters( 53 | uint16_t mask0, uint16_t filter0, uint16_t filter1, 54 | uint16_t mask1, uint16_t filter2, uint16_t filter3, uint16_t filter4, uint16_t filter5, 55 | bool allowRollover); 56 | 57 | using CANControllerClass::filterExtended; 58 | virtual int filterExtended(long id, long mask); 59 | // TODO: add setFilterRegistersExtended(). 60 | 61 | bool switchToNormalMode(); 62 | bool switchToConfigurationMode(); 63 | virtual int observe(); 64 | virtual int loopback(); 65 | virtual int sleep(); 66 | virtual int wakeup(); 67 | 68 | void setPins(int cs = MCP2515_DEFAULT_CS_PIN, int irq = MCP2515_DEFAULT_INT_PIN); 69 | void setSPIFrequency(uint32_t frequency); 70 | void setClockFrequency(long clockFrequency); 71 | 72 | void dumpImportantRegisters(Stream& out); 73 | void dumpRegisters(Stream& out); 74 | 75 | private: 76 | void reset(); 77 | 78 | void handleInterrupt(); 79 | 80 | uint8_t readRegister(uint8_t address); 81 | void modifyRegister(uint8_t address, uint8_t mask, uint8_t value); 82 | void writeRegister(uint8_t address, uint8_t value); 83 | 84 | static void onInterrupt(); 85 | 86 | private: 87 | SPISettings _spiSettings; 88 | int _csPin; 89 | int _intPin; 90 | long _clockFrequency; 91 | }; 92 | 93 | extern MCP2515Class CAN; 94 | 95 | #endif 96 | 97 | #endif 98 | --------------------------------------------------------------------------------