Подготовка
Создаем в пустом 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.