Firmware for Arduino Nano (ATmega328P) to control a dual L298N motor driver with optional quadrature encoders, using a framed protocol over I2C, SPI, or Serial.
All configuration lives in include/config.h.
Select exactly one bus:
#define BUS_USE_I2C 1
#define BUS_USE_SPI 0
#define BUS_USE_SERIAL 0
#define I2C_SLAVE_ADDRESS 0x69
#define SPI_MODE_CONFIG 0
#define SPI_CLOCK_HZ 4000000UL
#define SERIAL_BAUD 115200UL
Notes:
- I2C and Serial use the settings above directly.
- SPI runs in slave mode; the master sets the clock.
SPI_CLOCK_HZis informational and used by the host configuration.
#define PROTOCOL_SOF 0xAA
#define PROTOCOL_MAX_DATA 24
PROTOCOL_MAX_DATA limits the payload size inside a frame (opcode + payload). Increase only if needed.
#define PIN_ENA 5
#define PIN_ENB 6
#define PIN_IN1 7
#define PIN_IN2 8
#define PIN_IN3 4
#define PIN_IN4 9
These map to the L298N pins: ENA/PWMA, ENB/PWMB, IN1-4.
#define ENABLE_ENCODERS 0
#define ENC1_A_PIN 2
#define ENC1_B_PIN 3
#define ENC2_A_PIN A0
#define ENC2_B_PIN A1
Set ENABLE_ENCODERS to 1 to enable full quadrature decoding via pin-change interrupts.
All buses use the same framed protocol:
SOF | LEN | OPCODE | PAYLOAD... | CRC
SOF: start byte,0xAALEN: number of bytes starting atOPCODEand ending at the last payload byteCRC: CRC-8 overLENand all bytes fromOPCODEthrough payload
CRC-8 uses polynomial 0x07, initial value 0x42.
Responses use the same format. The response opcode is OPCODE | 0x80 and the first response payload byte is a status code:
0x69OK0x01BAD_LEN0x02BAD_OPCODE
Request payload:
uint8 motorId, int16 speed
Signed values map to direction. Range is -255..255. Magnitude maps to PWM 0..255.
Response payload:
status
Request payload: none.
Response payload:
status, int32 countA, int32 countB
Counts are little-endian.
Request payload:
uint8 motorId, int32 count
Use motorId 0 or 1. Use count = 0 to reset.
Response payload:
status
Request payload: none.
Response payload:
status, uint8 mask
Bit mapping:
- bit0 IN1
- bit1 IN2
- bit2 IN3
- bit3 IN4
- bit4 ENA
- bit5 ENB
Request payload: none.
Response payload:
status, uint8 major, uint8 minor, uint8 patch
All hex values are bytes in wire order.
AA 04 01 01 1A 00 27
AA 04 01 01 00 00 F2
AA 01 02 1B
AA 06 03 01 00 00 00 00 D5
- SPI slave responds on subsequent clocks. The host should send a request frame, then perform a second SPI transfer to read the response frame.
- For I2C, the response is served on the next master read after a complete request frame is received.
An alternative firmware entry point exists at src/main_encoder_self_test.cpp.
This build is dedicated to encoder validation and does not run the framed protocol handler.
Behavior:
- Tests both motors in both directions.
- Uses a short full-power kick before the configured test speed.
- Samples encoder counts every 100 ms.
- Stops motor, computes delta, and prints
PASSorFAILto Serial. - Repeats continuously.
Build/upload this firmware using the dedicated environment:
platformio run -e nanoatmega328_encoder_test --target upload
Open a serial monitor at 115200 baud to watch results:
platformio device monitor -p /dev/ttyUSB0 -b 115200