A bare-metal implementation of the Controller Area Network (CAN 2.0B) protocol.
- Node 1: ESP32
- Node 2: TI TM4C123GH6PM
- Physical Layer: 2x TI SN65HVD230 (VP230) 3.3V CAN Transceivers
| Signal | TM4C123 (CAN0) |
VP230 #1 | VP230 #2 | ESP32 (TWAI) |
Notes |
|---|---|---|---|---|---|
| VCC | +3.3V |
3.3V |
3.3V |
3V3 |
Native 3.3V logic levels |
| GND | GND |
GND |
GND |
GND |
Common ground reference required across nodes |
| CANTX |
PB5 (CAN0Tx) |
CTX |
CTX |
GPIO 21 |
Routed via GPIO Matrix (ESP32) / PCTL Mux 0x8 (TM4C) |
| CANRX |
PB4 (CAN0Rx) |
CRX |
CRX |
GPIO 22 |
Routed via GPIO Matrix (ESP32) / PCTL Mux 0x8 (TM4C) |
| CAN_H | — | CANH |
CANH |
— |
|
| CAN_L | — | CANL |
CANL |
— | Differential pair |
├── ESP32/CAN/ # ESP-IDF v6.1 CMake project (TWAI direct register access)
└── TM4C123/ # Keil uVision MDK-ARM project (C_CAN bare-metal)
- Install the ESP-IDF extension in VS Code and run
ESP-IDF: Open ESP-IDF Installation Manager[cite: 3]. - Provision the toolchain via Easy Installation (installs Xtensa GCC, CMake, Ninja, and ESP-IDF v6.1 to the host environment)[cite: 3, 5].
- Reload the window (
Developer: Reload Window) and generate a blank project inESP32/CANviaESP-IDF: New Project(template-app)[cite: 4]. - Open
ESP32/CANas the workspace root so CMake andclangdresolvecompile_commands.json[cite: 4, 5]. - Set target architecture to
esp32, build the project to index register headers (soc/twai_struct.h), and flash via UART[cite: 5].
- Install the
Keil::TM4C_DFPdevice family pack via Pack Installer. - Create a new project in
TM4C123/targetingTM4C123GH6PM. - In the Run-Time Environment (RTE) manager, include only
CMSIS::COREandDevice::Startup(startup_TM4C123.sandsystem_TM4C123.c—no peripheral driver libraries). - Set the debug and flash probe to Stellaris ICDI under Target Options $\rightarrow$ Debug / Utilities.
The extra files and whatever that need to be downloaded to set up the board were stolen from Quantum Leaps - Modern Embedded Systems Programming series so god knows how to set everything up again. Good luck.
These milestones were generated by Gemini, that's my guide, and then I am making notes on everything as a reference for future use since Gemini yaps too much.
To save power, most peripherals are disabled and held in a reset state by default upon boot.
Clock: Digital logic circuits require an oscillating clock signal to step through operations and move data across registers.
Reset: The reset line forces all internal state machines, FIFOs, and registers of a specific peripheral back to their default hardware values without rebooting the entire CPU. By setting DPORT_PERIP_RST_EN_REG to 0, we are leaving this reset mode, letting it operate.
First, some mapping magic happens.
Second, we need to output enable the GPIO pins.
Use GPIO.enable_w1ts = (1 << PIN_NUM)
-
enable (Direct Read/Write): Writing to this register overwrites all 32 pins at once.
-
enable_w1ts (Write 1 to Set): Writing a 1 to any bit position turns on output enable for that corresponding GPIO pin, while writing a 0 does nothing and leaves existing pins completely untouched.
-
enable_w1tc (Write 1 to Clear): Writing a 1 to any bit position turns off the output enable for that corresponding GPIO pin.
CANTX is the output from the ESP32. CANRX is the input from the ESP32.
TWAI_RX_IDX = 94
TWAI_TX_IDX = 123
Then lastly you need to map the GPIO pin to the right index
GPIO.func_out_sel_cfg[21].func_sel = 123; // Set GPIO21 to output the CAN TX signal (index 123)
Reset Mode is a state where the controller is electrically and logically disconnected from the TWAI bus.
Firstly, we enter reset mode and also enter extended mode.
Extended Mode switches the TWAI hardware from a 1980s legacy register layout (Basic Mode / BasicCAN) to the modern CAN 2.0B register architecture.
Then, we want to configure our BAUD rate. Use TWAI_BUS_TIMING_0_REG and TWAI_BUS_TIMING_1_REG.
For 0_REG, we can configure the BAUD rate (TWAI_BAUD_PRESC) and the Synchronization Jump Width. Need to find out what exactly the jump width is later.
For 1_REG, we can configure some other things, but more interestingly, the sample method! In this project we chose triple sampling (bus_timing_1_reg.sam = 1).
And then we want to configure the acceptance filters.
Lastly, we can send a packet!!
Use tx_rx_buffer[].byte.
RX - PB4 and TX - PB5
PB4 and PB5 are wired strictly to CAN0Rx and CAN0Tx (Module 0).
Woke up the CAN0 hardware and connected it physically to pins PB4 and PB5 on the chip.
Use same values as ESP32.
To turn on a bit, use "|=", and to turn it off, use "&= ~".