Motor Control over BLE with nRF51822 and TB6612FNG



Introduction


In this project, we will control two motors over BLE using the Nordic nRF51-DK. To do this, we will use a motor driver based on the Toshiba TB6612FNG chip. We will use GPIOs and PWM to communicate with this board.


Background


Before you read further, you might want to look at some of my previous articles on nRF51822 programming, since we’re going to use the same development setup here.



Communicating with TB6612FNG


For this project, I used a Pololu TB6612FNG board. A similar board is available from Sparkfun also. You can see the connections for the Pololu board below:


 


The pins PWMA and PWMB control the speeds of the two DC motors. (The speed being proportional to the duty cycle of the PWM signal.) The pins (AIN1, AIN2) and (BIN1, BIN2) control the direction of rotation of the motors. The STBY “standby” pin has to be high for any of the controls to work. The VCC pin is the supply for the board logic, and has to be in the range of 2.7 to 5.5 V. The VMOT pin is the motor power supply, which has to be in the range of 4.5 to 13.5 V. (AO1, AO2) and (BO1, BO2) are output supplies to the two motors.


The speed and direction of the motors can be controlled in various ways, as shown in an excerpt from the Toshiba TB6612FNG data sheet below:


 


Connections


Here is how the nRF51-DK is hooked up to the TB6612FNG in this project:


nRF51-DK TB6612FNG
P0.01 PWMA
P0.02 AIN1
P0.03 AIN2
P0.04 PWMB
P0.05 BIN1
P0.06 BIN2
P0.07 STBY
GND GND

In my case, the motors are rated at around 4.5 V. I supply VMOT via a 11.2 V LiPO battery regulated down to 5V using a 7805 regulator IC.


Motor Control


The PWM module is initialized in the code as follows:


   // Create the instance "PWM1" using TIMER1.
   APP_PWM_INSTANCE(PWM1,1);   
   //...

   // 2-channel PWM
   app_pwm_config_t pwm1_cfg =
      APP_PWM_DEFAULT_CONFIG_2CH(1000L, PWMA, PWMB);

    pwm1_cfg.pin_polarity[0] = APP_PWM_POLARITY_ACTIVE_HIGH;
    pwm1_cfg.pin_polarity[1] = APP_PWM_POLARITY_ACTIVE_HIGH;

    /* Initialize and enable PWM. */
    err_code = app_pwm_init(&PWM1,&pwm1_cfg,pwm_ready_callback);
    APP_ERROR_CHECK(err_code);
    app_pwm_enable(&PWM1);

In the code above, we set up PWM1 instance with a frequency of 1000 Hz on the PWMA and PWMB pins.


To control the motors, we use the Nordic nRFToolboxApp. The app has a configurable keypad which send strings to the nRF51-DK over the Nordic UART Service when the buttons are pressed. We check for these strings and take appropriate action in the code as follows:


// Function for handling the data from the Nordic UART Service.
static void nus_data_handler(ble_nus_t * p_nus, uint8_t * p_data,
                             uint16_t length)
{
  if (strstr((char*)(p_data), RECORD)) {
  }
  else if (strstr((char*)(p_data), SHUFFLE)) {
    forward = !forward;
    set_dir(forward);
  }
  else if (strstr((char*)(p_data), STOP)) {
    stop_motors();
  }
  else if (strstr((char*)(p_data), PLAY)) {
    start_motors();
  }
  else if (strstr((char*)(p_data), FORWARD)) {
    turn_right = true;
  }
  else if (strstr((char*)(p_data), REWIND)) {
    turn_left = true;
  }
}

Here is how you start and stop the motors, and set the speed of rotation:


/* stop_motors: bring motors to a stop */
void stop_motors()
{
  nrf_gpio_pin_clear(STBY);
}

/* start_motors: start motors */
void start_motors()
{
  nrf_gpio_pin_set(STBY);
}

/* set_speed: set speed for both motors */
void set_speed(uint8_t speed)
{
  curr_speed = speed;
  // set speed
  while (app_pwm_channel_duty_set(&PWM1, 0, speed) == NRF_ERROR_BUSY);
  while (app_pwm_channel_duty_set(&PWM1, 1, speed) == NRF_ERROR_BUSY);      
}

As you can see above, setting STBY to LOW will stop the motors. Setting the duty cycle will effect the speed of the motors proportionately. Here’s how you set the direction of rotation of the motors:


/* direction: change motor direction */
void set_dir(bool forward)
{
  if(forward) {
    // set direction A
    nrf_gpio_pin_set(AIN1);
    nrf_gpio_pin_clear(AIN2);
    // set direction B
    nrf_gpio_pin_set(BIN1);
    nrf_gpio_pin_clear(BIN2);
  }
  else {
     // set direction A
    nrf_gpio_pin_clear(AIN1);
    nrf_gpio_pin_set(AIN2);
    // set direction B
    nrf_gpio_pin_clear(BIN1);
    nrf_gpio_pin_set(BIN2);
  }
}

Flipping the input pin states switches the direction of motors above.


In my case, the motors are fitted to a 2WD robot chassis with a caster wheel. So to make the wheels turn, I can just reduce the speed of one of the wheels, as follows:


/* turn: turn left/right */
void turn(bool left)
{
  // store current speed
  uint8_t tmp = curr_speed;

  if (left) {
    while (app_pwm_channel_duty_set(&PWM1, 0, 10) == NRF_ERROR_BUSY);
    while (app_pwm_channel_duty_set(&PWM1, 1, 25) == NRF_ERROR_BUSY);
  }
  else {
    while (app_pwm_channel_duty_set(&PWM1, 0, 25) == NRF_ERROR_BUSY);
    while (app_pwm_channel_duty_set(&PWM1, 1, 10) == NRF_ERROR_BUSY);
  }

  // turn for x secs
  nrf_delay_ms(1000);

  // restore speed
  set_speed(tmp);
}

In the above code, I reduce the speed, make the turn by just rotating one of the wheels, wait for a bit, and then restore speed. This has the action of changing the direction of movement towards left or right.


Calling turn() above directly from the NUS data handler messes up the PWM signal. (The Nordic implentation of nRF51 PWM has several known bugs.) So I side-stepped this problem by just setting a flag and executing the turn in the main loop:


    // loop
    while(1) {

      if(turn_left) {
        turn(true);
        turn_left = false;
      }
      else if(turn_right) {
        turn(false);
        turn_right = false;
      }

      //
      nrf_delay_ms(50);
    }

You can refer to the source code link below to see how all these pieces fit together.


In Action


See the BLE motor control in action below:


 


Downloads


You can get the complete source code for this project here:


https://github.com/electronut/nRF51-TB6612FNG-test


References


  1. nRF51 Series Reference Manual Version 3.0.
  2. Toshiba TB6612FNG data sheet.