Motor Tester using the DRV8256P Module

like a servo tester for brushed motors, battery-powered
January 15, 2022
c attiny drv8256p avr-mcu brushed-motor lipo-2s


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);
    }
}