When you are building and testing RC vehicles, having a servo tester is incredibly handy for checking basic functions and end-to-end range. I wanted an easy way to test brushed DC motors, had to place a Pololu order anyway, and decided to build something from their DRV8256P module. This is a tiny, but extremely flexible driver, able to run 1.9A continuous at up to 40V with reverse input protection and 48V without. The low end of the voltage range is 4.5V, not quite low enough to use a 1S lipo (3.7-4.2V) without some kind of charge pump.
This design fits on a piece of ElectroCookie strip board, which has a nice strip layout for DIP mounting. There are three LEDs: green for forwards, yellow for reverse, and red for the module’s fault condition. The potentiometer has a center detent and is being used as a voltage divider, which is then sampled by the ATtiny402’s ADC and turned into a pair of PWM signals. There are test points on the PWM lines, along with a 5V programming header. The ATtiny is powered by a L7805 regulator and barely uses any current, while the DRV8256 module is powered directly from the battery. Both the LDO and MCU have recommended decoupling capacitors, and I haven’t had any trouble with low power resets.
Code
The code for the ATtiny is very basic, mostly bit twiddling, but seems to work well enough. It uses TCA0
to produce
the PWM signals, using two of the comparators. ADC0
is used to sample the potentiometer, using VDD as the reference
and average 4 samples to reduce noise. Each loop of the program waits for a sample, then updates the PWM duty cycles
to drive the motor forward or backwards.
/*
* File: avr-main.c
* Author: ssube
*
* Created on January 14, 2022, 6:35 PM
*/
#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
uint8_t loop_counter = 0;
int16_t sample = 0;
void init(void) {
// setup pins
PORTMUX.CTRLC = PORTMUX_TCA00_ALTERNATE_gc;
PORTA.DIR |= PIN2_bm | PIN3_bm; // set LED pins
PORTA.DIR |= PIN1_bm | PIN7_bm; // set PWM pins
PORTA.OUT |= PIN2_bm | PIN3_bm; // start LEDs on
PORTA.OUT &= ~(PIN1_bm | PIN7_bm); // start PWMs off
// setup ADC
ADC0.CTRLA &= ~ADC_ENABLE_bm; // disable ADC
ADC0.CTRLB = ADC_SAMPNUM_ACC4_gc; // average 4 samples
ADC0.CTRLC = ADC_REFSEL_VDDREF_gc | ADC_PRESC_DIV4_gc; // set clock and voltage ref
ADC0.MUXPOS = ADC_MUXPOS_AIN6_gc; // mux to read pin 6
ADC0.CTRLA = ADC_RESSEL_8BIT_gc | ADC_ENABLE_bm; // set 8-bit resolution and enable
// setup TCA
TCA0.SINGLE.CTRLA &= ~TCA_SINGLE_ENABLE_bm; // disable TCA
TCA0.SINGLE.CTRLESET = TCA_SINGLE_CMD_RESET_gc; // hard reset timer peripheral
// setup WO0/WO3 and WO1 (which the datasheet says should be WO2)
TCA0.SINGLE.PER = 0x40; // 3.3MHz / 64 / 256 = 200Hz
TCA0.SINGLE.CMP0 = 0x00; // WO0, pin 3
TCA0.SINGLE.CMP1 = 0x00; // WO1, pin 4
// enable TCA
TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm | TCA_SINGLE_CMP1EN_bm | TCA_SINGLE_WGMODE_SINGLESLOPE_gc; // comparators 0 and 1, count up and loop
TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV256_gc | TCA_SINGLE_ENABLE_bm; // set clock and enable
}
int main(void) {
init();
// loop
while (1) {
ADC0.COMMAND = ADC_STCONV_bm; // run ADC conversion
while ((ADC0.INTFLAGS & ADC_RESRDY_bm) == 0); // wait for result
sample = ADC0.RES;
PORTA.OUT &= ~(PIN2_bm | PIN3_bm); // disable both LEDs
if (sample > 512) {
PORTA.OUT |= PIN3_bm;
TCA0.SINGLE.CMP0 = sample / 16;
TCA0.SINGLE.CMP1 = 0x00;
} else {
PORTA.OUT |= PIN2_bm;
TCA0.SINGLE.CMP0 = 0x00;
TCA0.SINGLE.CMP1 = sample / 16;
}
loop_counter += 1;
_delay_ms(50);
}
}