воскресенье, 12 февраля 2017 г.

Снижаем скорость

Эта статья про ШИМ (PWM). Именно с помощью широтно-импульсной модуляции я решил замедлить своего робота.


Чтобы использовать ШИМ, необходимо немного изменить подключение драйвера двигателей к ардуино.

arduino uno --- motor driver
D8 --- ENB
D9 --- IN4
D10 --- IN3
D11 --- IN2
D12 --- IN1
D13 --- ENA

Скетч

////////////////////////
//
// Arduino UNO
//
////////////////////////
//
// Sketch: Robot
//

// driver --- arduino
// ENA --- D13
// IN1 --- D12
// IN2 --- D11
// IN3 --- D10
// IN4 --- D9
// ENB --- D8

#include <util/delay.h>

#define DRIVER_DDR DDRB
#define DRIVER_PORT PORTB

#define ENA 5
#define IN1 4
#define IN2 3
#define IN3 2
#define IN4 1
#define ENB 0

volatile unsigned char count = 0;
volatile unsigned char width = 22;

void driver_init( void );
void tc2_init( void );
void stop( void );
void forward( void );
void backward( void );
void right( void );
void left( void );

void setup()
{
    driver_init();
    tc2_init();
    _delay_ms(4000);
}

void loop()
{
    forward();
    _delay_ms(4000);
    stop();
    _delay_ms(1000);

    backward();
    _delay_ms(4000);
    stop();
    _delay_ms(1000);

    right();
    _delay_ms(4000);
    stop();
    _delay_ms(1000);

    left();
    _delay_ms(4000);
    stop();
    _delay_ms(1000);
}

void driver_init()
{
    DRIVER_DDR |= ((1<<ENA)|(1<<IN1)|(1<<IN2)|(1<<IN3)|(1<<IN4)|(1<<ENB));
    DRIVER_PORT &= ~((1<<ENA)|(1<<IN1)|(1<<IN2)|(1<<IN3)|(1<<IN4)|(1<<ENB));
}

void tc2_init()
{
    // TC2 Counter Value Register
    TCNT2 = 0;
    // TC2 Control Register A
    // COM2A1 COM2A0 COM2B1 COM2B0 - - WGM21 WGM20
    TCCR2A = 0;
    // TC2 Control Register B
    // FOC2A FOC2B - - WGM22 CS22 CS21 CS20
    TCCR2B = (1<<CS20);
    // TC2 Interrupt Mask Register
    // - - - - - OCIE2B OCIE2A TOIE2
    TIMSK2 = (1<<TOIE2);
    // TC2 Interrupt Flag Register
    // - - - - - OCF2B OCF2A TOV2
    TIFR2 = (1<<TOV2);
}

void stop()
{
    DRIVER_PORT &= ~((1<<IN1)|(1<<IN2)|(1<<IN3)|(1<<IN4));
}

void forward()
{
    DRIVER_PORT &= ~((1<<IN1)|(1<<IN2)|(1<<IN3)|(1<<IN4));
    DRIVER_PORT |= ((0<<IN1)|(1<<IN2)|(0<<IN3)|(1<<IN4));
}

void backward()
{
    DRIVER_PORT &= ~((1<<IN1)|(1<<IN2)|(1<<IN3)|(1<<IN4));
    DRIVER_PORT |= ((1<<IN1)|(0<<IN2)|(1<<IN3)|(0<<IN4));
}

void right()
{
    DRIVER_PORT &= ~((1<<IN1)|(1<<IN2)|(1<<IN3)|(1<<IN4));
    DRIVER_PORT |= ((1<<IN1)|(0<<IN2)|(0<<IN3)|(1<<IN4));
}

void left()
{
    DRIVER_PORT &= ~((1<<IN1)|(1<<IN2)|(1<<IN3)|(1<<IN4));
    DRIVER_PORT |= ((0<<IN1)|(1<<IN2)|(1<<IN3)|(0<<IN4));
}

ISR(TIMER2_OVF_vect)
{
    if ( count == 0 ) DRIVER_PORT |= ((1<<ENA)|(1<<ENB));
    if ( count == width ) DRIVER_PORT &= ~((1<<ENA)|(1<<ENB));

    count += 1;
    if ( count == 40 ) count = 0;
}

//
// End
//
////////////////////////

Приведенный скетч будет работать только в ардуино с микроконтроллером ATmega328 или ATmega168.

В программе ШИМ реализован с помощью таймера/счетчика 2. Это восьмибитный таймер счетчик.

Работа с таймером/счетчиком 2 осуществляется через специальные регистры. Изменение значений этих регистров, точнее, изменение битов этих регистров, влияет на работу таймера/счетчика 2. Содержимое регистров можно читать (присваивать значение регистра какой либо переменной).

Если будет интерес к таймеру/счетчику 2, напишу про все его регистры и их биты. А пока ограничусь кратким описанием регистров и битов значимых для данного проекта.

Чтобы понять как работает показанный код достаточно разобраться в регистрах:
1) TCNT2 - TC2 Counter Value Register (счетный регистр таймера/счетчика 2);
2) TCCR2A - TC2 Control Register A (первый управляющий регистр таймера/ счетчика 2);
3) TCCR2B - TC2 Control Register B (второй управляющий регистр таймера/ счетчика 2);
4) TIMSK2 - TC2 Interrupt Mask Register (регистр управления прерываниями таймера/счетчика 2);
5) TIFR2 - TC2 Interrupt Flag Register (регистр флагов прерываний таймера/счетчика 2).

Полагаю не будет лишним указать названия всех битов этих регистров.

TCCR2A: COM2A1, COM2A0, COM2B1, COM2B0, -, -, WGM21, WGM20.
TCCR2B: FOC2A, FOC2B, -, -, WGM22, CS22, CS21, CS20.
TIMSK2: -, -, -, -, -, OCIE2B, OCIE2A, TOIE2.
TIFR2: -, -, -, -, -, OCF2B, OCF2A, TOV2.

Биты отмеченные как "-" не используются. Их значение ни на что не влияет.

В регистре TCNT2 записано то, что насчитал таймер/счетчик 2. Это число от 0 до 255.

Через регистры TCCR2A и TCCR2B осуществляется управление, настройка таймера/счетчика 2.

Биты CS22, CS21, CS20 регистра TCCR2B отвечают за тактовый сигнал таймера/счетчика 2. Если все эти биты сброшены (в битах записаны нули), то таймер/счетчик 2 остановлен (отсутствует тактовый сигнал). Если установлен только бит CS20 (в этом бите записана единица, а в остальных битах записаны нули), то таймер/счетчик 2 работает на основной частоте контроллера (для arduino uno это 16 MHz). Этот режим тактирования таймера/счетчика 2 и используется в программе.

В таком режиме значение счетного регистра таймера/счетчика 2 увеличивается на 1 за один такт микроконтроллера. Периодически происходит переполнение счетного регистра. Подробнее об этом процессе можно почитать на http://justforduino.blogspot.ru/2013/12/blog-post.html

Регистр TIMSK2 отвечает за прерывания таймера/счетчика 2.

Бит TOIE2 регистра TIMSK2 разрешает или запрещает прерывание по переполнению таймера/счетчика 2. Если бит сброшен (ноль), то прерывание запрещено. А если бит установлен (единица), то прерывание разрешено.

Регистр TIFR2 отведен для флагов прерываний. Значения битов этого регистра меняются самим контроллером. При возникновении события прерывания, устанавливается соответствующий прерыванию флаг (в соответствующий бит записывается единица). При вызове обработчика прерывания, соответствующий прерыванию флаг сбрасывается (в соответствующий бит записывается ноль). Флаг прерывания можно сбросить программно. Для этого нужно записать единицу в бит соответствующий прерыванию.

В программе используется прерывание по переполнению таймера/счетчика 2. Под флаг этого прерывания отведен бит TOV2 регистра TIFR2.

Обрабатывается прерывание по переполнению таймера/счетчика 2 в ISR(TIMER2_OVF_vect).

Обработчик прерывания по переполнению таймера/счетчика 2 вызывается через

(1.0 / 16000000.0) * 256.0 * 1000.0 = 0.016 ms

Для формирования одного периода ШИМ необходимо вызвать обработчик 40 раз

(1.0 / 16000000.0) * 256.0 * 1000.0 * 40.0 = 0.64 ms

Таким образом частота ШИМ получается

1.0 / 0.64 = 1.5625 KHz

В результате работы программы на цифровых пинах 8 и 13 будет сигнал показанный на представленной ниже картинке


На клеммах электродвигателя ток меняется приблизительно так, как показано на картинке ниже


Это связано с индуктивностью обмоток электродвигателя. И это накладывает ограничения на частоту ШИМ. Если частота будет слишком высокая, то ток в обмотках не будет достигать максимально возможного значения. В итоге сильно снизится момент двигателя.

От значения переменной width зависит заполнение ШИМ. Этой переменной можно присвоить значение от 0 до 40. Опытным путем была установлена нецелесообразность присвоения значения менее 22. Дело в том, что снижение заполнения приводит к снижению момента на валу коллекторного двигателя постоянного тока. Проще говоря, платформа перестает ехать.

Нужно учитывать ещё один нюанс. Двигатели получают ток непосредственно от аккумулятора. А аккумулятор в процессе работы постепенно разряжается. Напряжение и ток снижаются. Момент на валу также снижается.

Итог. Полученный результат меня вполне устраивает.

К сожалению видео снять проблематично. Снег, холод. Может позже.