CodeVision AVR в вопросах и ответах

Обсуждаем контроллеры компании Atmel.
Ответить
Друг Кота
Аватара пользователя
Сообщения: 3832
Зарегистрирован: Сб сен 10, 2011 17:46:25

Сообщение oleg110592 »

ADIF - флаг прерывания АЦП. Этот бит устанавливается в 1 когда АЦП завершено преобразование и в регистрах ADCL и ADCH находятся актуальные данные. Этот флаг устанавливается даже в том случае, если прерывания запрещены. Это необходимо для случая программного опроса АЦП. Если используются прерывания, то флаг сбрасывается автоматически. Если используется программный опрос, то флаг может быть сброшен записью лог.1 в этот бит.
ADIE - Если в этом бите установлена единица, и прерывания разрешены глобально, то при окончании преобразования будет выполнен переход по вектору прерывания от АЦП.
upd: чуть опоздал с ответом
Реклама
Друг Кота
Аватара пользователя
Сообщения: 4905
Зарегистрирован: Чт апр 11, 2013 11:19:59
Откуда: Минск

Сообщение WiseLord »

Да, был неправ в этом плане
• Bit 4 – ADIF: ADC Interrupt Flag
This bit is set when an ADC conversion completes and the Data Registers are updated. The ADC Conversion Complete Interrupt is executed if the ADIE bit and the I-bit in SREG are set. ADIF is cleared by hardware when executing the corresponding interrupt Handling Vector. Alternatively, ADIF is cleared by writing a logical one to the flag. Beware that if doing a Read-Modify-Write on ADCSRA, a pending interrupt can be disabled. This also applies if the SBI and CBI instructions are used.
Странный подход, но не нам судить.

Но, на мой взгляд, лучше забыть про этот бит, и факт окончания измерений определять по ADSC, ушедшему в ноль.
Контактная информация:
Реклама
Друг Кота
Аватара пользователя
Сообщения: 3832
Зарегистрирован: Сб сен 10, 2011 17:46:25

Сообщение oleg110592 »

имхо лучше следовать рекомендациям производителя:
Application Note Atmel AVR126
Изображение
для STM8 тоже применял подобный подход для формирования микросекундных задержек на таймере - удобно следить за флагом прерываний без разрешения самих прерываний:

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

void delay_us(uint8_t us)
{
    TIM4->SR1 &= ~TIM4_SR1_UIF; 
    TIM4->CNTR = 256-us; //
    TIM4->CR1 |= TIM4_CR1_CEN; // Разрешаем счет.
    while((TIM4->SR1 & TIM4_SR1_UIF)==0);
} 
тут, правда, флаг как положено сбрасывается нулем
Вымогатель припоя
Аватара пользователя
Сообщения: 601
Зарегистрирован: Пт фев 13, 2009 20:58:13
Откуда: Донецк

Сообщение DataLife »

Ребят, помогите, пожалуйста... Который день не могу найти ошибку... Кто не в теме - это просто вольтметр... Идея построения кода взята тут: ТЫЦ
Спойлер#include <mega8.h>
#include <delay.h>

//Объявляем переменные

//------------------0-----1-----2-----3-----4-----5-----6-----7-----8------9----dp
char SEGMENTE[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x80};

unsigned int adc_data[4];
unsigned int ADC_volt;


/** ФУНКЦИЯ ПРЕОБРАЗОВАНИЯ **/

void data_convert(unsigned int value)
{
unsigned char tmp; // переменная для временного хранения данных преобразования

tmp = value / 1000;
adc_data[0] = SEGMENTE[tmp];
tmp = value % 1000 / 100;
adc_data[1] = SEGMENTE[tmp];
tmp = value % 100 / 10;
adc_data[2] = SEGMENTE[tmp];
tmp = value % 10;
adc_data[3] = SEGMENTE[tmp];
}

void ind_update (void)
{
static unsigned char count = 0;
PORTD = 0x00; // гасим все разряды
PORTD = adc_data[count]; // выводим в порт код цифры
if (count == 0) PORTB |= (1<<0);
if (count == 1) PORTB |= (1<<1); // перечисляем выводимые разряды
if (count == 2) PORTB |= (1<<2);
if (count == 3) PORTB |= (1<<3);
count++; // включаем следующий разряд
if (count == 4) count = 0;
}


void ADC_result(void)
{
unsigned int adc_tmp;

ADCSRA |= 0x40; //начинаем измерение
while((ADCSRA & 0x10)==0); //Ждём флаг окончания измерения
ADCSRA |=0x10;
adc_tmp=ADCW;
ADC_volt=adc_tmp*4.88;
}

// Обработчик прерывания по переполнению таймера2
interrupt [TIM2_OVF] void timer2_ovf_isr(void)
{
ind_update();
}

void main(void)
{
PORTB=0x00;
DDRB|=(1<<0)|(1<<1)|(1<<2)|(1<<3); // настройка на выход. 4 разряда

PORTD=0x00;
DDRD=0xFF; // настройка на выход всего порта

ADCSRA = 0x8E; //0b10001110 - предделитель на 64, прерывания разрешены, ADC включён
ADMUX = 0x40; //0b01000000 - AVCC , ACD0, ADLAR off

TIMSK |= (1 << TOIE2); // разрешение прерывания по таймеру2
TCCR2 |= (1 << CS21); // предделитель таймера2 на 8


#asm ("sei") // глобально разрешаем прерывания.
while (1)
{
ADC_result();
data_convert(ADC_volt);
delay_ms(200);
}

}
Индикатор полностью "молчит".
Когда делал на двух таймерах (один отвечает за расчёт АЦП , второй за вывод значения на индикатор) - всё работало. На основа этого кода я и стоил вышеизложенный. Настройки инициализации таймера2 и АЦП - те же...

Попробовал отключить прерывания от АЦП - индикатор ожил, но показывает галиматью... Скорее всего дело в выводе данных на индикатор, а это пересчёт в цифры => присвоение переменной из массива adc_data[n] значения из массива SEGMENTE[n] => присвоение значения adc_data[n] = PORTD...
Хотя, визуально, кажется какие-то значения проскакивают в белеберде на индикаторе... Просто горят ненужные сегменты, хотя перед выводом все гасятся (PORTD = 0x00) ... голова кругом... :facepalm:
Только те, кто предпринимают абсурдные попытки, смогут достичь невозможного.
Реклама
Эиком - электронные компоненты и радиодетали
Модератор
Аватара пользователя
Сообщения: 4614
Зарегистрирован: Чт мар 18, 2010 23:09:57
Откуда: Планета Земля

Сообщение Аlex »

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

PORTD = 0x00; // гасим все разряды
Это не гашение, а ерунда :facepalm:
Логично предположить, что этой строки нет вообще, т.к. следом за ней сразу же идёт вывод данных в порт.
Контактная информация:
Реклама
Вымогатель припоя
Аватара пользователя
Сообщения: 601
Зарегистрирован: Пт фев 13, 2009 20:58:13
Откуда: Донецк

Сообщение DataLife »

Аlex, ставить какую-либо задержку? Попробовал сделать задержку аж в 10 us - толку нет...
Да и работало оно, когда программа была по-другому скомпонована... Косяк где-то в другом ... :(
Только те, кто предпринимают абсурдные попытки, смогут достичь невозможного.
Реклама
Опытный кот
Аватара пользователя
Сообщения: 721
Зарегистрирован: Ср июн 11, 2014 09:43:13
Откуда: США

Сообщение Pink-Pank »

После того, как отобразили цифру - делайте задержку и выключайте порт В. А так у Вас после первого прогона сразу все цифры горят.
Либо заменяйте модификацию значения порта на обычное присваивание.
Последний раз редактировалось Pink-Pank Пн авг 04, 2014 19:09:19, всего редактировалось 1 раз.
Fucking static initialization order fiasco
Контактная информация:
Друг Кота
Аватара пользователя
Сообщения: 4905
Зарегистрирован: Чт апр 11, 2013 11:19:59
Откуда: Минск

Сообщение WiseLord »

ОК, в PORTD вы закинули код цифры. А нужную цифру - не выбрали:
DataLife писал(а):

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

if (count == 0) PORTB |= (1<<0);
if (count == 1) PORTB |= (1<<1); // перечисляем выводимые разряды
if (count == 2) PORTB |= (1<<2);
if (count == 3) PORTB |= (1<<3);
После прохождения count через 0..3 на порту B так и останутся единицы на всех разрядах.
Надо перед этим кодом добавить что-то вроде PORTB &= ~((1<<0) | (1<<1) | (1<<2) | (1<<3)), (погасить все цифры) а уже потом выбирать нужный разряд в зависимости от значения count.
Контактная информация:
Модератор
Аватара пользователя
Сообщения: 4614
Зарегистрирован: Чт мар 18, 2010 23:09:57
Откуда: Планета Земля

Сообщение Аlex »

DataLife писал(а):Аlex, ставить какую-либо задержку? Попробовал сделать задержку аж в 10 us - толку нет...
Причём тут задержка ?
Вот, для примера, код :

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

PORTD=0;
PORTD=1;
Скажите, какой смысл в первой строчке ?
Контактная информация:
ARV
Ум, честь и совесть. И скромность.
Аватара пользователя
Сообщения: 18675
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск

Сообщение ARV »

Аlex писал(а):

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

PORTD=0;
PORTD=1;
Скажите, какой смысл в первой строчке ?
строб?! :idea:
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется...

Мой уютный бложик... заходите!
Контактная информация:
Модератор
Аватара пользователя
Сообщения: 4614
Зарегистрирован: Чт мар 18, 2010 23:09:57
Откуда: Планета Земля

Сообщение Аlex »

ARV писал(а):строб?!
Я, почему то, именно этот ответ и именно от Вас ожидал :)))
Контактная информация:
ARV
Ум, честь и совесть. И скромность.
Аватара пользователя
Сообщения: 18675
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск

Сообщение ARV »

Аlex писал(а):Я, почему то, именно этот ответ и именно от Вас ожидал :)))
почему, почему... умный человек потому что :))) ну и с юмором, похоже
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется...

Мой уютный бложик... заходите!
Контактная информация:
Вымогатель припоя
Аватара пользователя
Сообщения: 601
Зарегистрирован: Пт фев 13, 2009 20:58:13
Откуда: Донецк

Сообщение DataLife »

Ребята, памятник вам! :beer: Работает!
Честно говоря, стыдно ... Забыл гасить разряд перед выводом нового. :facepalm:
Правда теперь на высоких частотах работы таймера2 (250 кГц и выше) видны сегменты, которые вообще не должны гореть! То есть, благодаря задержке в сборе значений АЦП, картинка на индикаторе практически статична (например выводится значение напряжения питания МК: 4,75В), но горят и сегменты, которые "и рядом не стояли с этими цифрами"
Спойлер

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

#include <mega8.h>
#include <delay.h>

//Объявляем переменные

//------------------0-----1-----2-----3-----4-----5-----6-----7-----8------9----dp
char SEGMENTE[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x80};

char adc_data[4];
volatile unsigned int ADC_volt;
                                       
    
/** ФУНКЦИЯ ПРЕОБРАЗОВАНИЯ **/
    
void data_convert(unsigned int value)
    {
     unsigned char tmp;   // переменная для временного хранения данных преобразования
     
     tmp = value / 1000;
     adc_data[0] = SEGMENTE[tmp];
     tmp = value % 1000 / 100;
     adc_data[1] = SEGMENTE[tmp];
     tmp = value % 100 / 10;
     adc_data[2] = SEGMENTE[tmp];
     tmp = value % 10;
     adc_data[3] = SEGMENTE[tmp];
    }

void ind_update (void)
    {
     static unsigned char count = 0;
     PORTD = 0;                          // гасим все сегменты
     PORTB &= ~((1<<0) | (1<<1) | (1<<2) | (1<<3));   // гасим все разряды

     PORTD = adc_data[count];           // выводим в порт код цифры
     if ((count == 0)&&(!(PORTD == 0x3F))) PORTB |= (1<<0);             // перечисляем выводимые разряды, не выводим "ноль" в первом разряде
     if (count == 1) PORTB |= (1<<1);   
     if (count == 2) PORTB |= (1<<2);
     if (count == 3) PORTB |= (1<<3);
     count++;                           // включаем следующий разряд
     if (count == 4) count = 0;
     
      
    }


void ADC_result(void)
{
        unsigned int adc_tmp;

        ADCSRA |= 0x40; //начинаем измерение
        while((ADCSRA & 0x10)==0); //Ждём флаг окончания измерения
        ADCSRA |=0x10;
        adc_tmp=ADCW;
        ADC_volt=(float)adc_tmp*4645/10000;     // не задумываемся над вычислениями
}

// Обработчик прерывания по переполнению таймера2
interrupt [TIM2_OVF] void timer2_ovf_isr(void)
{
  ind_update();           
}

void main(void)
{
 PORTB=0x00;
 DDRB|=(1<<0)|(1<<1)|(1<<2)|(1<<3); // настройка на выход. 4 разряда

 PORTD=0x00;
 DDRD=0xFF;                         // настройка на выход всего порта
 
 ADCSRA = 0x86; //0b10000110 - предделитель на 64, прерывания запрещены, ADC включён
 ADMUX = 0x40; //0b01000000 - AVCC , ACD0, ADLAR off

 TIMSK |= (1 << TOIE2); // разрешение прерывания по таймеру2
 TCCR2 |= (1 << CS20)|(1 << CS21);  // предделитель таймера2 на 32  (250 MHz)

 
#asm ("sei") // глобально разрешаем прерывания.
while (1)
      {
      ADC_result(); 
      data_convert(ADC_volt);
      delay_ms(500);
      } 

}
Аlex, прошу простить мою неграмотность в языке...
Что же, смею предположить, что PORTD=1; и PORTD=0x00; и PORTD=0b00000000; мне казалось, что одно и тоже в разных системах (десятичная, шестнадцатиричная, бинарная) ... Раз стал вопрос, то я ошибаюсь, скорее всего ...
Только те, кто предпринимают абсурдные попытки, смогут достичь невозможного.
Модератор
Аватара пользователя
Сообщения: 4614
Зарегистрирован: Чт мар 18, 2010 23:09:57
Откуда: Планета Земля

Сообщение Аlex »

DataLife писал(а):Раз стал вопрос, то я ошибаюсь, скорее всего ...
Вопрос встал не об этом.
Команда PORTD=0x00; - бессмысленна, т.к. сразу же после неё идёт запись значения в порт. Соответственно, это не назовёшь гашением.
DataLife писал(а):на высоких частотах работы таймера2 (250 кГц и выше)
Не многовато ли для индикации ? :roll:
Контактная информация:
Друг Кота
Аватара пользователя
Сообщения: 4905
Зарегистрирован: Чт апр 11, 2013 11:19:59
Откуда: Минск

Сообщение WiseLord »

Видимо, инерционность светодиодов сказывается. Либо прерывания настолько часто идут, что не успевает обработчик выполниться.
Разбираться лень, но навскидку тут около 20 тактов процессора (не считая вызов подпрограммы из обработчика прерывания) так что при 250кГц развёртке это вполне может быть, особенно если частота тактовая ниже 8МГц. Так что лучше, как минимум, содержимое ind_update лучше прямо в обработчик поместить.

Попробуйте задержку после отключения разрядов добавить. Не знаю, как в CodeVision NOP реализуется, но что-то вроде:
Спойлер

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

{
	static unsigned char count = 0;

	PORTB &= ~((1<<0) | (1<<1) | (1<<2) | (1<<3));
	asm("nop");

	PORTD = adc_data[count];

	if (count == 0 && adc_data[0] != 0x3F) PORTB |= (1<<0);
	if (count == 1) PORTB |= (1<<1);   
	if (count == 2) PORTB |= (1<<2);
	if (count == 3) PORTB |= (1<<3);

	count = (count + 1) & 0x03;
}
Последний раз редактировалось WiseLord Пн авг 04, 2014 21:29:33, всего редактировалось 1 раз.
Контактная информация:
ARV
Ум, честь и совесть. И скромность.
Аватара пользователя
Сообщения: 18675
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск

Сообщение ARV »

WiseLord писал(а):Видимо, инерционность светодиодов сказывается.
да вы чо?! :shock:
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется...

Мой уютный бложик... заходите!
Контактная информация:
Друг Кота
Аватара пользователя
Сообщения: 4905
Зарегистрирован: Чт апр 11, 2013 11:19:59
Откуда: Минск

Сообщение WiseLord »

ARV писал(а):да вы чо?! :shock:
Думаете? Мало ли там на каких проводах это всё подключено.. Хотя частота на светодиоде 250/4 кГц кажется и не сильно большой.

P.S. Порылся в закромах - у меня при тактовой 8МГц вполне работало и при делителе 8 (на 1МГц, делим на 3 сегмента => 330кГц на цифру). Так что, наверное, я и правда слишком плохо подумал о частоте светодиодов. Правда, у меня и обработчик прерывания был попроще:
Спойлер

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

#define SEG_PORT	PORTB
#define DIG_PORT	PORTD

#define DIG_UN		(1<<PD6) /* Units */
#define DIG_TE		(1<<PD5) /* Tens */
#define DIG_HU		(1<<PD4) /* Hundreds */
#define DIGITS		(DIG_UN | DIG_TE | DIG_HU)

static const uint8_t dig[3] = {DIG_UN, DIG_TE, DIG_HU};
static volatile uint8_t ind[3] = {0, 0, 0};

ISR (TIMER0_OVF_vect)
{
	static uint8_t pos = 0;

	DIG_PORT |= DIGITS;									/* Switch off digits */
	SEG_PORT = ind[pos];								/* Set data on segments */
	DIG_PORT &= ~dig[pos];								/* Switch on current digit */

	pos++;
	if (pos > 2)
		pos = 0;

	return;
}
Последний раз редактировалось WiseLord Пн авг 04, 2014 21:40:06, всего редактировалось 5 раз.
Контактная информация:
Модератор
Аватара пользователя
Сообщения: 4614
Зарегистрирован: Чт мар 18, 2010 23:09:57
Откуда: Планета Земля

Сообщение Аlex »

Да всё понятно что у DataLife'а происходит.
Судя по коду, индикатор с общим анодом.
Кодом

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

PORTD = 0;                          // гасим все сегменты
он не то что не гасит индикаторы, а наоборот "портит всю малину". Нули на катодах, наоборот - зажигают сегменты.
Вот и происходит подсветка, пока программа выполняет

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

PORTB &= ~((1<<0) | (1<<1) | (1<<2) | (1<<3));   // гасим все разряды
.
На небольших частотах, это было бы не так заметно, а на 250Кгц и выше, даже десятые доли микросекунд будут хорошо засвечивать светодиоды.

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

ЗЫ: 2000-ное сообщение, пойду валерьянки 50 дёрну :beer: :))
Последний раз редактировалось ibiza11 Вт авг 05, 2014 10:13:11, всего редактировалось 2 раза.
Причина: -
Контактная информация:
Вымогатель припоя
Аватара пользователя
Сообщения: 601
Зарегистрирован: Пт фев 13, 2009 20:58:13
Откуда: Донецк

Сообщение DataLife »

Аlex писал(а):
DataLife писал(а):Раз стал вопрос, то я ошибаюсь, скорее всего ...
Вопрос встал не об этом.
Команда PORTD=0x00; - бессмысленна, т.к. сразу же после неё идёт запись значения в порт. Соответственно, это не назовёшь гашением.
А, теперь понял. Спасибо, буду учитывать с будущем :beer:
Аlex писал(а):
DataLife писал(а):на высоких частотах работы таймера2 (250 кГц и выше)
Не многовато ли для индикации ? :roll:
Просто "сам факт". При делителе на 256 (8МГц / 256 = 31,25 кГц) виден дребезг. На частоте 62,5 кГц нормально, но всё-равно очень тускло можно увидеть другие сегменты...

Аlex, ну не напрямую же у меня мой общий катод подключён. NPN транзистор присутствует, как положено :tea:

Ниже приведу код программы, того же вольтметра, но реализация "неверная". Делал на двух таймерах: один для АЦП, другой для индикации. Таймер индикации работает по переполнению и с частотой 1 Мгц (делитель на 8 ). Дело в том, что лишних сегментов нет! Выводится только то, что должно.
Спойлер

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

#include <mega8.h>
#include <delay.h>

//наши переменные

//------------------0-----1-----2-----3-----4-----5-----6-----7-----8------9----dp
char SEGMENTE[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x80};

volatile unsigned char segcounter = 0;
volatile unsigned int adc_data = 0;
volatile unsigned int ADC_res=0;

void ADC_init(void)
    {
        ADCSRA = 0x8E; //0b10001110 - предделитель на 64, прерывания разрешены, ADC включён
        ADMUX = 0x40; //0b01000000 - AVCC , ACD0, ADLAR off
    }


// Обработчик прерывания по переполнению таймера0
interrupt [TIM1_OVF] void timer1_ovf_isr(void)
{
        ADCSRA |= 0x40; //начинаем измерение
        while((ADCSRA & 0x10)==0); //Ждём флаг окончания измерения
        ADCSRA |=0x10;
        adc_data=ADCW;
        ADC_res=adc_data*0.00488*100;

}

// Обработчик прерывания по переполнению таймера2
interrupt [TIM2_OVF] void timer2_ovf_isr(void)
{             
PORTD = 0x00; // гасим все сегменты
PORTB = (1<< segcounter); //выбираем следующий разряд
switch (segcounter)
{
case 0:
PORTD = (SEGMENTE[ADC_res % 10000 / 1000]); // Раскладываем число на разряды. Тысячи.
if (PORTD==0x3F) PORTD=0x00;                // знаю, что не правильно тут.
break;
case 1:
PORTD = (SEGMENTE[ADC_res % 1000 / 100])|(1<<7); // Сотни. Ставим точку для точности "до сотых"
break;
case 2:
PORTD = (SEGMENTE[ADC_res % 100 / 10]); // Десятки.
break;
case 3:
PORTD = (SEGMENTE[ADC_res % 10]); // Единицы.
break;
}
if ((segcounter++) > 2) segcounter = 0;
}


void main(void)
{

PORTB=0x00;
DDRB|=(1<<0)|(1<<1)|(1<<2)|(1<<3);

PORTC=0x00;
DDRC=0x00;

PORTD=0x00;
DDRD=0xFF;

TIMSK |= (1 << TOIE2)|(1<< TOIE1); // разрешение прерывания по таймеру2 и таймеру0
TCCR2 |= (1 << CS21); //предделитель таймера2 на 8
TCCR1B=0x04; // предделитель таймера1 на 1024

ADC_init();
  
#asm ("sei") // глобально разрешаем прерывания.
while (1)
      { 

      } 
   
}
Только те, кто предпринимают абсурдные попытки, смогут достичь невозможного.
Модератор
Аватара пользователя
Сообщения: 4614
Зарегистрирован: Чт мар 18, 2010 23:09:57
Откуда: Планета Земля

Сообщение Аlex »

DataLife писал(а):При делителе на 256 (8МГц / 256 = 31,25 кГц) виден дребезг
Что за дребезг ? Мерцание ?
На таких частотах индикаторы должны быть "как вкопанные". Что то у Вас не то ... :roll:
4 индикатора, 1 Кгц переключение (250 Гц на индикатор) - уже мерцание не видно даже когда трясёшь индикатор.
Контактная информация:
Ответить

Вернуться в «AVR»