Учебный курс. Подключение энкодера. Типовая структура простой программы. Генератор на AVR.

   Принцип действия, схема включения и исходник библиотеки для работы с инкрементным энкодером уже рассматривался мной в одной из статей. Сегодня мы поговорим о практическом применении энкодера. В качестве примера я выбрал программу генератора прямоугольного сигнала с диапазоном рабочих частот 1 – 100 Гц. Первоначальный замысел предполагал диапазон 1 - 1000 Гц, но на практике выяснилось, что перебирать тысячу значений утомительно даже с энкодером.

 

Подготовка

Создаем в пустом workspace`е новый проект
Project > Create New Project…
Тип шаблона C > main

Копируем в папку проекта файлы исходника библиотеки для работы с энкодером
encoder.h и encoder.c

Подключаем к нашему проекту файл encoder.c
Правая кнопка мышки в окне workspace и в открывшемся меню Add > Add Files…

Копируем файл bits_macros.h в папку проекта.


Подключаем заголовочные файлы

В начале файла main.c забиваем следующие строки
#include <ioavr.h>
#include <intrinsics.h>
#include "encoder.h"
#include "bits_macros.h"

Задаем настройки проекта

Project > Options

   Тип микроконтроллера
   General Options > Target > Processor Configuration > ATMega8535
   
   Разрешение использования имен битов определенных в заголовочных файлах
   General Options > System > Enable bit defenitions...

  Оптимизация кода по размеру
  C/C++ Compiler > Optimisations >Size High
 
  Тип выходного файла
  Linker > Output File  галочка Override default  и поменять расширение на hex
  Linker > Format > Other  выбрать Intel Standart

Жмем Ок. Сохраняем проект и workspace.
Теперь у нас есть пустой проект с подключенной либой и заданными настройками.

Задача

   Заставить микроконтроллер генерировать меандр с частотой от 1 до 100 Гц. Значение частоты должно задаваться с помощью энкодера. Поворот энкодера на одну позицию должен соответствовать изменению частоты генератора на 1 Гц.

Схема для нашего примера

схема подключения энкодера к микроконтроллеру
   К выводу, на котором будет генерироваться меандр, подключен светодиод, чтобы хоть как-то видеть результат работы программы. Вряд ли у многих под рукой осциллограф.

Алгоритм программы

   Прямоугольный сигнал генерируется с помощью 16 разрядного таймера Т1, который работает в режиме СТС – сброс при совпадении. Во флэш памяти микроконтроллера  храниться массив, содержащий для каждого значения требуемой частоты константу. Для доступа к элементам массива используется переменная pTimerValue. В прерываниях таймера Т1 значение константы считывается и записывается в регистр сравнения.
   Для генерации сигнала используется вывод PD5 (OC1A). У него есть альтернативные функции – он может менять свое состояние на противоположное при равенстве счетного регистра и регистра сравнения.
   Опрос энкодера выполняется в прерывании таймера Т0. Частота работы таймера ~ единицы килогерц. Режим работы – Normal (здесь не принципиально, какой режим).
   В основной программе в бесконечном цикле while микроконтроллер опрашивает буфер энкодера и в зависимости от его значения уменьшает или увеличивает переменную pTimerValue.
   В самом начале main`а располагается код инициализации периферии и необходимых переменных.

Структура программы

Для наглядности я изобразил структуру программы в виде диаграммы.

типовая структура простой микроконтроллерной программы

Это типовая структура построения простых программ. Прерывания естественно происходят в произвольном месте цикла.
  • Инициализация.
  • Бесконечный цикл (так называемый superloop), в котором происходит ожидание события, обычно в виде опроса флагов или какого-нибудь буфера.
  • Параллельная работа периферийных устройств, вызывающих прерывания. В них выполняется какой-то код (желательно короткий) и выставляются флаги.
Для простых задач такого подхода хватает за глаза. Для сложных существуют другие способы организации программ. Наберитесь терпения, скоро и до них дойдет дело.

Расчет констант для таймера Т1

   Рассчитаем значение константы для частоты 1 Гц. Подобный расчет я уже приводил, но будет не лишним его вспомнить
  Тактовая частота микроконтроллера 16 МГц (смотрите схему). Коэффициент предделителя таймера - 256. Он позволяет получить прерывания с любой частотой из нашего диапазона.
   Период одного тика таймера будет равен 1/(16 МГц/ 256) = 16 мкс
На выводе PD5 нам нужно получить сигнал частотой 1 Гц. Вывод меняет свое состояние на каждое прерывание таймера и значит, частота прерываний должна быть в 2 раза больше. Для нашего случая - 2 Гц.
   Сколько тиков таймера уложится в 2 Герца? (1/2 Гц)/16 мкс = 31250
   Это и есть искомая константа.
   Остальные значения рассчитываются аналогично. Я для этого обычно использую Exel.   

Полученные значения мы помещаем в массив

__flash unsigned int timerValue[] =
{

};

сохраняем его в отдельном файле – timer_value.h и подлючаем его к файлу main.c

  #include "timer_value.h"

Да, еще нужно добавить парочку констант в этот файл

#define MAX_TIM_VALUE 99
#define MIN_TIM_VALUE 0


Убедимся, что правильно рассчитали константы для таймера. Запустим его. Код программы будет такой.

//программирование AVR  на Си
//пример использования энкодера
//ChipEnable.ru     17.10.09

#include <ioavr.h>
#include <intrinsics.h>
#include "encoder.h"
#include "bits_macros.h"
#include "timer_value.h"

//индекс для доступа к элементам массива
volatile unsigned char pTimerValue = 0;

int main(void)
{
//инициализация таймера Т1
TCNT1 = 0;
TCCR1A = (0<<COM1A1)|(1<<COM1A0)|(0<<WGM11)|(0<<WGM10);
TCCR1B = (0<<WGM13)|(1<<WGM12)|(1<<CS12)|(0<<CS11)|(0<<CS10);
OCR1A = timerValue[pTimerValue];

//настройка вывода PD5 на выход
SetBit(PORTD, PD5);
SetBit(DDRD, PD5);

//ничего не делаем в бесконечном цикле
while(1);
return 0;
}

Думаю, пояснения требует только кусок инициализации таймера.

Обнуление счетного регистра
TCNT1 = 0;

Инициализация конфигурационных регистров таймера Т1.
TCCR1A = (0<<COM1A1)|(1<<COM1A0)|(0<<WGM11)|(0<<WGM10);
TCCR1B = (0<<WGM13)|(1<<WGM12)|(1<<CS12)|(0<<CS11)|(0<<CS10);

Где биты WGM13, WGM12, WGM11, WGM10 задают режим работы таймера – СТС,
CS12, CS11, CS10 – определяют коэффициент предделителя таймера –256,
COM1A1, COM1A0 – определяют поведение вывода PD5(OC1F) – в данном случае по сигналу таймера он будет менять свое состояние на противоположное

Инициализация регистра совпадения начальным значением.
OCR1A = timerValue[pTimerValue];

Компилируем программу и грузим в микроконтроллер. Светодиод должен моргать с частотой 1 Гц.
В программе нет никаких прерываний. Нет никаких манипуляций с выводом PD5. Однако светодиод моргает!

Программа

  Теперь нужно “прикрутить” к этой программе энкодер. Зададим настройки в хедер файле encoder.h – порт и выводы, к которым подключен энкодер, значения констант.

#define PORT_Enc   PORTA     
#define PIN_Enc      PINA
#define DDR_Enc     DDRA
#define Pin1_Enc     2
#define Pin2_Enc     1

#define RIGHT_SPIN 0x01
#define LEFT_SPIN 0xff

 Хедер содержит прототипы трех функций. Вспомним их назначение.

void ENC_InitEncoder(void) настраивает выводы микроконтроллера, к которым подключен энкодер на вход. Эту функцию нужно вызвать в начале main`а.

void ENC_PollEncoder(void) – однократно опрашивает энкодер, анализирует текущее и предыдущее состояния и записывает в буфер соответствующие константы (RIGHT_SPIN и LEFT_SPIN). Эта функция будет сидеть в прерывании таймера Т0.

unsigned char ENC_GetStateEncoder(void) – возвращает содержимое буфера энкодера. Если поворот на одну позицию не был зафиксирован – функция вернет 0, если поворот был зафиксирован функция вернет значение соответствующей константы. При этом значение буфера очистится. Эта функция будет вызываться в основном программе – в цикле while.

Дополняем нашу программу. Можете попробовать сделать это самостоятельно.

//программирование AVR  на Си
//пример использования энкодера
//ChipEnable.ru     17.10.09


#include <ioavr.h>
#include <intrinsics.h>
#include "encoder.h"
#include "bits_macros.h"
#include "timer_value.h"

#define TCNT0_const 253
#define TCCR0_const 5

volatile unsigned char pTimerValue = 0;

int main(void)
{
ENC_InitEncoder();

//инициализация таймера т0
TCNT0 = TCNT0_const;
TCCR0 = TCCR0_const;

//инициализация таймера т1
TCNT1 = 0;
TCCR1A = (0<<COM1A1)|(1<<COM1A0)|(0<<WGM11)|(0<<WGM10);
TCCR1B = (0<<WGM13)|(1<<WGM12)|(1<<CS12)|(0<<CS11)|(0<<CS10);
OCR1A = timerValue[pTimerValue];

//разрешение прерываний от таймеров
//т0 - по переполнению, т1 - по совпадению

TIMSK = (1<<TOIE0)|(1<<OCIE1A);

//настраиваем PD5 на выход
SetBit(PORTD, PD5);
SetBit(DDRD, PD5);

__enable_interrupt();
while(1){
  //считываем содержимое буфера энкодера
  //после считывания он очищается

  unsigned char stateEnc = ENC_GetStateEncoder();

  //если не пустой
  if (stateEnc != 0){
       //определяем направление вращения и изменяем переменную timerValue
       if (stateEnc == RIGHT_SPIN){
         if (pTimerValue == MAX_TIM_VALUE) pTimerValue = MIN_TIM_VALUE;
         else pTimerValue++;  
       }
      if (stateEnc == LEFT_SPIN) {
          if (pTimerValue == MIN_TIM_VALUE) pTimerValue = MAX_TIM_VALUE;
          else pTimerValue--;  
       }
  }
}  
return 0;
}


//опрос энкодера
#pragma vector=TIMER0_OVF_vect
__interrupt void timer0_ovf_my(void)
{
  TCNT0 = TCNT0_const;
  ENC_PollEncoder();
}


#pragma vector=TIMER1_COMPA_vect
__interrupt void timer1_compa_my(void)
{
  //обновляем значение регистра стравнения
  OCR1A = timerValue[pTimerValue];
}

Вроде все должно быть понятно.
Кусок кода, в котором изменяется значение pTimerValue, можно было бы написать еще так:

if (stateEnc != 0) {
    pTimerValue = pTimerValue + stateEnc;  
    if (pTimerValue == (MAX_TIM_VALUE + 1)) pTimerValue = MIN_TIM_VALUE;
    else if (pTimerValue == (MIN_TIM_VALUE - 1)) pTimerValue = MAX_TIM_VALUE;
}

При вращении энкодера вправо pTimerValue складывается с 1, то есть инкрементируется.
При вращении энкодера влево pTimerValue складывается с 0хff, что равносильно вычитанию 1. Одна и та же операция, а результат прямо противоположный.
(Двоичные числа можно вычитать складывая. Для этого одно из чисел нужно представить в дополнительном коде – вычесть из него 1 и проинвертировать все разряды. 0xff – это единица в дополнительном коде)

Файлы

Энкодер. Генератор на AVR – проект для IARa.
Энкодер. Генератор на AVR – проект для WINAVR.

У вас недостаточно прав для комментирования.