Технический форум по робототехнике.
HarryStar » 22 июл 2011, 11:56
Есть задача. Анализировать длительность сервоимпульсов.
Как обычно импульсы поступают с частотой 50 Гц, длиной от ну скажем 1000 мкс до 2000 мкс.
Делал эту задачу на АТмега32-16 Мгц на внешних прерываниях - все работает как часы, проблем нет. Точность измерения очень высокая (256 положений отрабатывает без проблем)
Возникла необходимость перенести все на АТмегу168 (там можно делать внешнее прерывание сразу по всему порту)
И тут возникли проблемы с недостаточностью быстродействия. Кварц тоже 16 Мгц, делитель 1(плюс еще во фьюзах отключил).
CVAVR Программа примерно такая:
- Код: Выделить всё • Развернуть
void main(void)
{
// Crystal Oscillator division factor: 1
#pragma optsize-
CLKPR=0x80;
CLKPR=0x00;
#ifdef _OPTIMIZE_SIZE_
#pragma optsize+
#endif
PORTD=0xFF; DDRD=0x00; // In/P
// Timer/Counter 1 initialization
// Clock value: 2000,000 kHz
TCCR1A=0x00; TCCR1B=0x02;
TCNT1H=0x00; TCNT1L=0x00;
ICR1H=0x00; ICR1L=0x00;
OCR1AH=0x00; OCR1AL=0x00;
OCR1BH=0x00; OCR1BL=0x00;
// External Interrupt(s) initialization
// Interrupt on any change on pins PCINT16-23: On
EICRA=0x00; EIMSK=0x00;
PCICR=0x04; PCMSK2=0xFF; PCIFR=0x04;
TIMSK0=0x00; TIMSK1=0x00; TIMSK2=0x00;
#asm("sei")
while (1) continue;
}
interrupt [PC_INT2] void pin_change_isr2(void)
{
unsigned int t;
t = (((unsigned int)TCNT1H)<<8) + (unsigned int)TCNT1L;
if(PIND & 0b00100000)
{
channel_3_start = t; // Старт импульса на канале 3
return;
}
if(PIND & 0b00010000)
{
channel_3_end = t; // Старт импульса на канале 4 и одновременно Стоп импульса на канале 3
return;
}
}
Проблема в том, что точность при таком измерении упала ниже плинтуса. Показания скачут в пределах 100/256, Т.е. можно более менее отследить короткий импульс 1000-1500 мкс, средний 1250-1750 и длинный 1500-2000. Т.е. вместо 256 градаций получается 3-5.
Есть большое подозрение, что хоть кварц выставлен на 16Мгц, Проц тактируется от 2Мгц или того меньше, хотя все вроде выставил как надо. Фьюзы сейчас у меня ВСЕ в 1 выставлены.
Длительность определяется как (channel_3_end - channel_3_start)/2 = длительность в мкс
В чем может быть проблема?
avr123.nm.ru » 22 июл 2011, 12:00
Наверно во фьюзах и дело. Выведите тестовой прогой меандр на ножку замерьте звуковой картой его частоту и узнайте реальную частоту вашего МК.
HarryStar » 22 июл 2011, 12:10
Попробовал только что не с прерыванием на весь порт, а с обычным INT1.
Тоже самое
- Код: Выделить всё • Развернуть
interrupt [EXT_INT1] void ext_int1_isr(void)
{
unsigned int t;
t = (((unsigned int)TCNT1H)<<8) + (unsigned int)TCNT1L;
if(PIND & 0b00001000)
{
channel_start[3] = t; // Старт импульса
return;
}
else
{
channel_end[3] = t; // Стоп импульса
return;
}
}
Вот такой обработчик INT1 дает теже неточные данные. В точности такой же на АТмега32 на INT1 работал как часы.
dccharacter » 22 июл 2011, 12:16
А зачем на ИНТ-ах это делать? CCP ворде самое оно???
HarryStar » 22 июл 2011, 12:39
К сожалению не знаком с данным термином и в поиске сразу не нашлось. Это что?
Всегда думал, что для анализа длительности внешних сигналов подходят только внешние прерывания.
Если вы про внешнее прерывание на портах
PCINT0-7
PCINT8-14
PCINT16-23
То я так и делаю в первом посте. Мне как раз и надо 8 каналов анализировать. Но для начала надо разобраться с одним. Реализацию через INT1 я привел просто для примера.
dccharacter » 22 июл 2011, 12:57
Capture Compare PWM
HarryStar » 22 июл 2011, 15:22
Ясно. Но это мне к сожалению не подходит, т.к. каналов очень много, а таймеров у МК мало.
Добавлено спустя 57 минут 52 секунды:
Частично разобрался. Дело было в порядке чтения байтов из таймера. Я читал сначала старший, потом младший, надо было наоборот. Точнее у меня стояло просто (TCNT1H<<8) + TCNT1L и такая запись давала неправильный результат. Т.е. получалось, что старший байт от одного измерения, младший от следующего(или вообще мусор).
Но это ладно, это мне понятно, сам дурак.
Дальше вообще чудеса.
Есть у меня такой кусок кода для преобразования времени задержки из 1000 - 2000 мкс в 0-255:
исходные: в channel длительность импульса примерно от 975 до 2000 мкс
unsigned int t1;
unsigned char c;
t1 = (channel-975)/4;
c = (unsigned char)t1;
Все работает как часы. Измерение очень точные.
Но я решил ввести граничные условия для страховки.
t1 = channel;
if(t1<975) t1=975;
if(t1>1995) t1=1995;
t1 = (t1-975)/4;
c = (unsigned char)t1;
Чтоб не дай бог не вылезло за один байт на границах.
В результате полный хаос. Значения прыгают плюс минус 50 примерно. Жирную строчку если закомментировать - все ОК.
Добавлено спустя 5 минут 56 секунд:
Даже не +- 50, они скачут от текущего нормального до 255. Т.е. выделенное жирным условие иногда срабатывает когда t1 меньше 1995. И получаются скачки.
=DeaD= » 22 июл 2011, 15:27
Чудеса какие-то, давай полную программу
dccharacter » 22 июл 2011, 16:59
У тебя, похоже, channel - это знаковая переменная
Добавлено спустя 6 минут 56 секунд:
кстати, сузь границы, сделай 976 и 1994
HarryStar » 22 июл 2011, 20:12
Да нет, не знаковая. Похоже глюк компилятора, другого не могу предположить.
Нижнюю границу изменить не проблема, а вот верхняя неуправляемая
Вот полный рабочий код на данный момент. Если раскоменнтировать строку, выделенную жирным то все становится плохо.
- Код: Выделить всё • Развернуть
#include <mega168.h>
#include <delay.h>
#include "..\HS-LIB\TWI_DIHALT.c"
void TWI_Error(); // Реакция на ошибки передачи. Обычно ничего делать не надо.
void SlaveOutControl(); // Реакция на запрос байта. Передается значение текущего радиоканала.
void SlaveInControl(); // Реакция на прием байтов. Установка номера радиоканала.
unsigned char current_channel;
unsigned int channel_start[9];
unsigned int channel_end[9];
unsigned int channel[9];
void main(void)
{
unsigned int t1;
PORTB=0x00; DDRB=0x03; // 0-1 Out/0 2-7 In/T
PORTC=0x00; DDRC=0x00; // In/T
PORTD=0xFF; DDRD=0x00; // In/P
// Timer/Counter 1 initialization
// Clock source: System Clock
// TCCR1A=0x00; TCCR1B=0x02; // 2000,000 kHz
TCCR1A=0x00; TCCR1B=0x01; // 16000,000 kHz
TCNT1H=0x00; TCNT1L=0x00;
ICR1H=0x00; ICR1L=0x00;
OCR1AH=0x00; OCR1AL=0x00;
OCR1BH=0x00; OCR1BL=0x00;
// External Interrupt(s) initialization
// Interrupt on any change on pins PCINT16-23: On
EICRA=0x00; EIMSK=0x00; PCICR=0x04; PCMSK2=0xFF; PCIFR=0x04;
TIMSK0=0x00; TIMSK1=0x00; TIMSK2=0x00;
#asm("sei")
Init_i2c(); // Запускаем и конфигурируем i2c
Init_Slave_i2c(0b11111001, &SlaveOutControl, &SlaveInControl);
ErrorOutFunc = &TWI_Error;
while(1)
{
t1 = channel[current_channel];
if(t1<975) t1=975;
[b]//if(t1>1995) t1=1995;[/b]
t1 = (t1-975)/4;
i2c_OutBuff [0] = (unsigned char)t1;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Отправка байта-статуса. Действий не требуется, только освобождение шины.
void SlaveOutControl()
{
i2c_Do &= i2c_Free; // Осовобождаем шину
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Обработка ошибок. Действий не требуется.
void TWI_Error()
{
i2c_Do &= i2c_Free; // Осовобождаем шину
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Получение команд. Установка текущего номера радио-канала.
void SlaveInControl()
{
unsigned int t1;
i2c_Do &= i2c_Free; // Осовобождаем шину
if(i2c_InBuff[0]<10)
{
current_channel = i2c_InBuff[0]; // Текущий канал
t1 = channel[current_channel];
if(t1<975) t1=975;
//if(t1>1995) t1=1995;
t1 = (t1-975)/4;
i2c_OutBuff [0] = (unsigned char)t1;
}
}
// Pin change 16-23 interrupt service routine
interrupt [PC_INT2] void pin_change_isr2(void)
{
unsigned int t, tH, tL;
tL = TCNT1L;
tH = TCNT1H;
t = (tH<<4) + (tL>>4);
if(PIND & 0b10000000)
{
channel_start[1] = t; // Старт импульса
return;
}
if(PIND & 0b01000000)
{
channel_start[2] = t; // Старт импульса
channel_end[1] = t; // Стоп импульса
channel[1] = channel_end[1] - channel_start[1];
return;
}
if(PIND & 0b00100000)
{
channel_start[3] = t; // Старт импульса
channel_end[2] = t; // Стоп импульса
channel[2] = channel_end[2] - channel_start[2];
return;
}
if(PIND & 0b00010000)
{
channel_start[4] = t; // Старт импульса
channel_end[3] = t; // Стоп импульса
channel[3] = channel_end[3] - channel_start[3];
return;
}
if(PIND & 0b00001000)
{
channel_start[5] = t; // Старт импульса
channel_end[4] = t; // Стоп импульса
channel[4] = channel_end[4] - channel_start[4];
return;
}
if(PIND & 0b00000100)
{
channel_start[6] = t; // Старт импульса
channel_end[5] = t; // Стоп импульса
channel[5] = channel_end[5] - channel_start[5];
return;
}
if(PIND & 0b00000010)
{
channel_start[7] = t; // Старт импульса
channel_end[6] = t; // Стоп импульса
channel[6] = channel_end[6] - channel_start[6];
return;
}
if(PIND & 0b00000001)
{
channel_start[8] = t; // Старт импульса
channel_end[7] = t; // Стоп импульса
channel[7] = channel_end[7] - channel_start[7];
return;
}
if(PIND == 0)
{
channel_end[8] = t; // Стоп импульса
channel[8] = channel_end[8] - channel_start[8];
}
}
Добавлено спустя 2 минуты 9 секунд:Жирным не выделяется внутри code - Нужная строка, обрамленная тэгами [b] почти в конце mainа
=DeaD= » 22 июл 2011, 20:18
А если t1 сделать знаковой?
Michael_K » 22 июл 2011, 21:48
Прога написана в стиле школьника.
Заложено много потенциальных глюков.
Я не знаю, что за компилятор, но...
1. выучите слово volatile (это нужно здесь)
2. научитесь пользоваться критическими секциями (это нужно здесь)
3. я бы поточнее ставил условия внутри прерывания (это вам виднее - зависит от задачи)
Общая рекомендация:
Отлаживаться нужно с простого... Или выделить минимальный неработающий кусок. Но в случае как у вас - наличия множества неявных связей это бессмысленно. Нужно сначала избавиться от них.
З.Ы.
Я фигею с советов. Dead, вы серьезно подталкиваете форумчан к методу тыка? Не удивляйтесь потом, что электростанции разваливаются.
=DeaD= » 22 июл 2011, 21:57
Для софта электростанций есть специальные подходы к верификации кода
и они заключаются совсем не в запоминании ключевых слов типа volatile
Добавлено спустя 3 минуты 32 секунды:Michael_K писал(а):1. выучите слово volatile (это нужно здесь)
2. научитесь пользоваться критическими секциями (это нужно здесь)
3. я бы поточнее ставил условия внутри прерывания (это вам виднее - зависит от задачи)
Сдаётся мне это никак не повлияет на проблему с исключительно локальной переменной t1, на которой странным образом обрабатывается условие. Но конечно попробовать можно.
Michael_K » 22 июл 2011, 22:00
Может быть и не повлияет, но отлаживать программу с заложенными неоднозначностями нельзя. Вообще никак. Она сегодня работает, а завтра нет.
HarryStar » 22 июл 2011, 22:43
Michael_K писал(а):1. выучите слово volatile (это нужно здесь)
2. научитесь пользоваться критическими секциями (это нужно здесь)
3. я бы поточнее ставил условия внутри прерывания (это вам виднее - зависит от задачи)
Программистом я работал очень давно (более 10 лет назад), с тех пор это для меня только хобби. Поэтому стиля придерживаюсь того, который мне нравится. Не думаю, что он похож на стиль школьника, но возможно с ваших высот они и не отличаются.
По сути: 1 знаю прекрасно, не ставил, потому что оптимизация отключена и проблем с глобальными переменными, изменяющимися в нескольких местах никогда не было.
2 - не понял, что вы имеете в виду
3 - в прерывании условия сделаны четко, основаны на осциллограммах работы приемника, с которого обрабатываются сигналы.
Не вижу в программе ни одного неоднозначного места, если подскажете, с удовольствием прокомментирую или приму к сведению.
Единственное, что я нашел - while в майне можно вообще убрать, поставив на его место while(1);
Хотя смысл в нем есть, и он ничему не мешает.