Страница 1 из 1

Тахометр на ATTiny261

Добавлено: Чт ноя 14, 2013 10:26:05
baron_P
Хочется сделать сабж для измерения оборотов двигателя посредством энкодера. С энкодера два сигнала - импульсы, сдвинутые друг относительно друга на пол-периода.
Идея примерно такая.
Заводим один канал на вход внешнего прерывания INT0. Второй - на соседний порт (просто вход). В прерывании добавляем единицу к счетчику импульсов и смотрим, что на входе второго сигнала. Если 0 - направление вращения одно, 1 - другое.
Берем 10-битный таймер/счетчик Т1 (Т0 занят динамической индикацией). Используем прерывание по переполнению Т1. В прерывании записываем значение того, что насчиталось в счетчике импульсов и обнуляем счетчик импульсов.
Зная период, за который происходит переполнение Т1, и количество импульсов на оборот энкодера, считаем обороты в минуту.
Код:
Спойлер

Код: Выделить всё

#include <avr/io.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#define F_CPU 8000000UL
#define T0_START 220 //настройка частоты динамической индикации

//инициализация переменных и констант
volatile int clk_pulse;  //счетчик импульсов энкодера
volatile int clk_pulse_ms; //количество импульсов за опр. время
int rpm; //обороты в минуту
volatile unsigned char data[5]; //разряды
const unsigned char number[12] = //символы
{
  0x3f, //0
  0x06, //1
  0x5b, //2
  0x4f, //3   
  0x66, //4
  0x6d, //5 
  0x7d, //6
  0x07, //7   
  0x7f, //8
  0x6f, //9
  0x40, //-
  0x00  //
};

//функция преобразования числа
void CONVERT_num(int value)
{
  int row_local;

  //разбиваем число на разряды
  if ((value >> 15) == 0) data[4] = 11;
  else data[4] = 10;
  row_local = abs(value);
  data[0] = row_local % 10;
  row_local = row_local / 10;
  data[1] = row_local % 10;
  row_local = row_local / 10;
  data[2] = row_local % 10;
  data[3] = row_local / 10;
}

//вектор прерывания по переполнению Т0
ISR(TIMER0_OVF_vect)
{ 
  static unsigned char count; //номер разряда
  
  TCNT0L = T0_START;
  
  PORTB |= 0x1f; //гасим все
    
  //индикация с 0-го разряда
  PORTA = number[data[count]];
  PORTB &= ~(1 << count);	
  count++;
  if (count == 5) count = 0;
}

//вектор внешнего прерывания 0
ISR(INT0_vect)
{
  clk_pulse = abs(clk_pulse) + 1; //счетчик импульсов

  //определение направления вращения
  if ((PINB & 0x20) == 0x20) clk_pulse = clk_pulse * (-1);
}

//вектор прерывания по переполнению Т1
ISR(TIMER1_OVF_vect)
{
  clk_pulse_ms = clk_pulse; //импульсов время счета Т1
  clk_pulse = 0;
}

void main(void)
{ 
  //инициализация портов
  DDRA = 0x7f; //сегменты индикации
  PORTA = 0x00;
  DDRB = 0x1f; //разряды индикации и входа энкодера
  PORTB = 0xff;

  //инициализация таймера 0
  TCCR0B |= (1 << CS02); //предделитель 256
  TIMSK |= (1 << TOIE0); //разрешение прерывания по переполнению Т0
  TCNT0L = T0_START;
  
  //иннициализация внешнего прерывания 0
  MCUCR |= ((1 << ISC01) | (1 << ISC00)); //по переднему фронту
  GIMSK |= (1 << INT0); //разрешение прерывания INT0
  
  //инициализация таймера 1
  TCCR1B |= ((1 << CS13) | (1<<CS12) | (1 << CS11) | (1 << CS10)); //предделитель Т1
  TIMSK |= (1 << TOIE1); //разрешение прерывания по переполнению Т1
  
  sei();  //разрешение прерываний

  while (1)
  {
	rpm = clk_pulse_ms;  //вычисление об/мин
	CONVERT_num(rpm); //преобразование для вывода на индикацию
  }
}
Схема модельки:
СпойлерИзображение
Вопрос к последнему пункту. Как рассчитать обороты?
Моя логика.

Берем частоту МК f_cpu = 8000000 Гц
Предделитель Т1 k_t1 = 16384
Количество тиков Т1 n_t1 = 1024

Период одного тика Т1 t_1_t1 = (1 / f_cpu) * k_t1 = 2.048 мс
Время до переполнения Т1 t_t1 = t_1_t1 * n_t1 = 2.097 с

Период импульса энкодера t_en = 2 мс (примерно при 80 оборотах и 360 импульсах на оборот)

Количество импульсов за время до переполнения n_p = t_t1 / t_en = 1049

А у меня на индикаторе только 6-8. В вышеприведенном коде я просто вывожу счет импульсов за время до переполнения Т1 без всяких преобразований. Вывод на дисплей верный - проверял выводом констант. Счет импульсов в прерывании по входу верный - пихул туда инверсию порта А - частота на выходах А совпадает с поступающей на вход. В свойствах МК в протеусе фьюзы CKSEL 0010 - встроенный генератор на 8 МГц.
Где ошибка?

Re: Тахометр на ATTiny261

Добавлено: Чт ноя 14, 2013 10:48:06
Аlex
Проблема, скорее всего, в типах данных. У Вас в формулах фигурируют выражения, требующие вычислений с вещественными числами. Обратите на это внимание.
По измерению оборотов. Немного не тот выбрали подход. Нужно измерять период следования импульсов от датчика, заполняя их частотой дискретизации, равной частоте тактирования таймера. Т.е. по импульсу запускаете таймер (без всяких предделителей, пусть шпарит на высокой частоте - разрешение будет выше), а по следующему импульсу читаете его и перезапускаете заного. В итоге имеете период, измеренный в тиках таймера.
А дальше всё просто. Берём формулу расчёта частоты из периода (f=1/t) и вычисляем обороты. Где 1 - это частота тактирования таймера, t - период в тиках таймера.
Для об/мин формула будет - (1*60/имп на оборот)/t

Re: Тахометр на ATTiny261

Добавлено: Чт ноя 14, 2013 11:08:03
baron_P
Спасибо.
Все преобразования, которые я использую - сложение, умножение на -1 и модуль (abs). Все то страшно-дробное, что я привел - это мой расчет работы таймера на бумажке, в прогамме в явном виде не используется.
Попробую ваш метод, кажется более простым.

Re: Тахометр на ATTiny261

Добавлено: Чт ноя 14, 2013 11:12:50
Аlex
Не забудьте расширить таймер, сделав обработчик от его переполнения, т.к. его разрешения при маленьких оборотах может не хватить.

Re: Тахометр на ATTiny261

Добавлено: Чт янв 09, 2014 14:55:52
baron_P
Немного некропостинга.

Сколько не ковырялся, так и не понял, как формируется частота на счетчиках ATTiny261. Чтоб разобраться, понимаю ли я хоть что-нить, собрал генератор импульсов на ATTiny13A.
Схема:
СпойлерИзображение
Прошивка:
Спойлер#include <avr/io.h>
#include <avr/interrupt.h>
//Частота 1,2 МГц c делителем на 8 для ATTiny13A

volatile unsigned char f_ref; //заданная частота

void main(void)
{
//Инициализация АЦП
ADMUX = 0b00100010; //выравнивание по л.к., источник PB4
ADCSRA = 0b10101111; //вкл., разр. прер., преддел. 128
ADCSRB = 0x00;

//Инициализация Таймер/Счетчика0
TCCR0A = 0b01000000; //работает вывод OC0A
TCCR0B = 0b000000011; //пределитель 64
TIMSK0 = 0b00000100; //прерыв. по совпадению OC0A разрешено

//Инициализация портов
DDRB = 0b00000001; //PB0 - выход
PORTB = 0b01101110; //PB0 в "0"

//Разрешение прерываний
sei();

//Запуск преобразования АЦП
ADCSRA |= 1 << 6;

//Рабочий цикл программы
while(1)
{
OCR0A = f_ref; //задание в регистр сравнения А
}
}

//Прерывание по окончанию преобразования
ISR (ADC_vect)
{
f_ref = ADCH;
if (f_ref == 0) f_ref++;
}

//Прервание по совпадению ОС0А
ISR (TIM0_COMPA_vect)
{
TCNT0 = 0;
}
Задание частоты (точнее периода) берется с АЦП. Записывается в регистр сравнения, и по совпадению с таймер/счетчиком Т0 генерируется инверсия на соотв. выводе. Частота расчетная, в модельке протеуса и реальная на макетке совпадают. Т.е. принцип я понял.

Теперь беру такую схему на ATTiny261:
СпойлерИзображение
Прошивка:
Спойлер#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 8000000UL

//инициализация переменных и констант
int rpm; //обороты в минуту
volatile unsigned char data[5]; //разряды
const unsigned char number[12] = //символы
{
0x3f, //0
0x06, //1
0x5b, //2
0x4f, //3
0x66, //4
0x6d, //5
0x7d, //6
0x07, //7
0x7f, //8
0x6f, //9
0x40, //-
0x00 //
};

//функция преобразования числа
void CONVERT_num(int value)
{
int row_local;

//разбиваем число на разряды
if ((value >> 15) == 0)
{
data[4] = 11;
row_local = value;
}
else
{
data[4] = 10;
row_local = value * (-1);
}
data[0] = row_local % 10;
row_local = row_local / 10;
data[1] = row_local % 10;
row_local = row_local / 10;
data[2] = row_local % 10;
data[3] = row_local / 10;
}

//вектор прерывания по переполнению Т0
ISR(TIMER0_OVF_vect)
{
static unsigned char count; //номер разряда

PORTB |= 0x1f; //гасим все

//индикация с 0-го разряда
PORTA = ~PORTA;//number[data[count]];
PORTB &= ~(1 << count);
count++;
if (count == 5) count = 0;
}


void main(void)
{
//инициализация портов
DDRA = 0x7f; //сегменты индикации
PORTA = 0x00;
DDRB = 0b01111111; //разряды индикации
PORTB = 0b01111111;

//инициализация таймера 0
TCCR0B |= (1 << CS02); //предделитель 256
TIMSK |= (1 << TOIE0); //разрешение прерывания по переполнению Т0


sei(); //разрешение прерываний

while (1)
{
rpm = -8888; //вычисление об/мин
CONVERT_num(rpm); //преобразование для вывода на индикацию
}
}
Эта программа в оригинале выводит на индикатор переменную rpm. Я выкинул лишнее, теперь она по переполнению T0 в соотв. прерывании инвертирует PORTA. Т0 в режиме 8-битного счетчика. Частота МК 8 МГц. Предделитель счетчика 256. Т.е. частота на выводах PORTA должна быть:
f = f_cpu / (2 * k * T0) = 8 000 000 / (2 * 256 * 256) = 61,035 Гц
А в протеусе получаю 62,5 Гц. Вроде тоже все ок.

Теперь такая схема (сигнал снимаю со свободной лапки PA7):
СпойлерИзображение
Прошивка:
Спойлер#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 8000000UL

//инициализация переменных и констант
int rpm; //обороты в минуту
volatile unsigned char data[5]; //разряды
const unsigned char number[12] = //символы
{
0x3f, //0
0x06, //1
0x5b, //2
0x4f, //3
0x66, //4
0x6d, //5
0x7d, //6
0x07, //7
0x7f, //8
0x6f, //9
0x40, //-
0x00 //
};

//функция преобразования числа
void CONVERT_num(int value)
{
int row_local;

//разбиваем число на разряды
if ((value >> 15) == 0)
{
data[4] = 11;
row_local = value;
}
else
{
data[4] = 10;
row_local = value * (-1);
}
data[0] = row_local % 10;
row_local = row_local / 10;
data[1] = row_local % 10;
row_local = row_local / 10;
data[2] = row_local % 10;
data[3] = row_local / 10;
}

//вектор прерывания по переполнению Т0
ISR(TIMER0_OVF_vect)
{
static unsigned char count; //номер разряда

PORTB |= 0x1f; //гасим все

//индикация с 0-го разряда
//PORTA = number[data[count]];
PORTB &= ~(1 << count);
count++;
if (count == 5) count = 0;
}

ISR(TIMER1_OVF_vect)
{
PORTA = ~PORTA;
}

void main(void)
{
//инициализация портов
DDRA = 0xff; //сегменты индикации
PORTA = 0x00;
DDRB = 0b00111111; //разряды индикации, PB6 вход
PORTB = 0b00111111;

//инициализация таймера Т0
TCCR0B |= (1 << CS02); //предделитель 256
TIMSK |= (1 << TOIE0); //разрешение прерывания по переполнению Т0

//инициализация таймера Т1
TCCR1B |= (1 << CS13) | (1 << CS12) | (1 << CS11) | (1 << CS10); //предделитель 16384
TIMSK |= (1 << TOIE1); //разрешение прерывания по переполнению Т1

sei(); //разрешение прерываний

while (1)
{
rpm = -8888; //вычисление об/мин
CONVERT_num(rpm); //преобразование для вывода на индикацию
}
}
Частота МК 8 МГц. Включен 10-битный таймер Т1 (1024 тика) с предделителем 16384. Инверсию PORTA перетащил из прерывания по Т0 в прерывание по Т1. Частота на PA7 должна быть:
f = f_cpu / (2 * k * T1) = 8 000 000 / (2 * 16384 * 1024) = 0,24 Гц
А в протеусе выдает 243,9 Гц. Т.е. как будто счетчик считает 1 тик вместо 1024.
Для k = 4096 то же самое соотношение: 0,954 и 990 Гц соответственно.
На этом пока тупик, не соображу что не так.

Re: Тахометр на ATTiny261

Добавлено: Чт янв 09, 2014 15:10:02
pcb
Битик CKDIV?

Re: Тахометр на ATTiny261

Добавлено: Чт янв 09, 2014 15:49:02
baron_P
Он вроде делит на 8, а не на 1024. И влияет на оба счетчика, а в последних двух примерах биты конфигурации не менялись. Тут что-то не так то ли с конфигурацией Т1, то ли с моим пониманием логики его работы.

Выкинул все лишне, оставил два счетчика, каждый из которых в прерывании инвертирует свой порт:
Спойлер#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 8000000UL

//инициализация переменных и констант
int rpm;

//вектор прерывания по переполнению Т0
ISR(TIMER0_OVF_vect)
{
PORTB = ~PORTB;
}

//вектор прерывания по переполнению Т1
ISR(TIMER1_OVF_vect)
{
PORTA = ~PORTA;
}

void main(void)
{
//инициализация портов
DDRA = 0xff;
PORTA = 0x00;
DDRB = 0xff;
PORTB = 0x00;

//инициализация таймера Т0
TCCR0B |= (1 << CS02) | (1 << CS00); //предделитель 1024
TIMSK |= (1 << TOIE0); //разрешение прерывания по переполнению Т0

//инициализация таймера Т1
TCCR1B |= (1 << CS13) | (1 << CS12) | (1 << CS11) | (1 << CS10); //предделитель 16384
TIMSK |= (1 << TOIE1); //разрешение прерывания по переполнению Т1

sei(); //разрешение прерываний

while (1)
{
rpm = -8888;
}
}
Частота по Т0 в теории и протеусе совпадают. По Т1 - в 1000 раз отличаются. Совсем ничего не понимаю.
Попробую на ATMega8 тахометр сделать, на нее документации пообширнее. Может хоть там до таких затыков не дойдет.