Способность разбираться в люксовых часах и машинах – это признак неспособности их купить...

Меню навигации для мобильных

АЦП в AVR

Автор Shaman, 06 Июль, 2022, 23:25

« предыдущая - следующая »

Shaman

Поясните пожалуйста как правильно понимать ниже приведенный текст.
• Bit 6 – ADSC: ADC Start Conversion
In Single Conversion mode, write this bit to one to start each conversion. In Free Running mode,
write this bit to one to start the first conversion. The first conversion after ADSC has been written
after the ADC has been enabled, or if ADSC is written at the same time as the ADC is enabled,
will take 25 ADC clock cycles instead of the normal 13. This first conversion performs initializa-
tion of the ADC.
ADSC will read as one as long as a conversion is in progress. When the conversion is complete,
it returns to zero. Writing zero to this bit has no effect.

Как его понимаю я:

Данный бит запускает работу АЦП в выбраном режиме. И если режим одиночный после выполнения преобразования данный бит встаёт в 0 и АЦП выключается, а в режиме постоянного измерение выключить его уже невозможно судя по этому предложению: "Writing zero to this bit has no effect". Я прав или нет?

Slabovik

Ну, тут дословно если перевести, получается:
в режиме "Одиночное преобразование" необходимо устанавливать (записывать) бит в состояние "1" для запуска каждого преобразования.
в режиме "Постоянное преобразование" (Free running mode) установка бита в "1" запускает первое преобразование (после чего АЦП уже работает непрерывно цикл за циклом, Slabovik). Первое преобразование после запуска происходит за 25 тактов АЦП (не путать с тактами ядра процессора, Slabovik), все последующие происходят за 13 тактов.
Если работа АЦП уже предварительно разрешена, первое преобразование запускается по факту записи "1" в ADCS, либо это можно сделать одновременно с разрешением работы АЦП.

Регистр ADCS можно считывать в любое время работы АЦП. Пока АЦП не закончит цикл, из ADCS считывается "1". Когда АЦП закончит преобразование, он установит значение регистра ADCS в "0" (что означает, что данные в регистрах АЦП достоверны, т.е. готовы для считывания, Slabovik). Запись "0" в этот регистр со стороны программы, когда АЦП уже работает, не производит никаких действий (независимо от режима, но может привести к заблуждению о готовности данных а регистрах данных АЦП, Slabovik)

Т.е. в режиме одиночного преобразования нужно в регистр каждый раз забрасывать "1" для запуска каждого следующего АЦП (это удобно, когда нет нужды, чтобы АЦП молотил постоянно, он всё-таки электричество потребляет), в "0" он становится сам самостоятельно, когда АЦП закончит вычисления и АЦП при этом останавливается, ожидая следующей записи "1" в ADCS. Принудительная запись 0 в ADCS, как указано, никаких действий не произведёт - АЦП свой цикл всё-равно закончит и данные в регистры для считывания положит. Но по появлению "0" в ADCS программа следит за актуальностью данных в регистрах данных АЦП.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Благодарю.
Т.е. получается что в режиме постоянных измерений после каждого цикла это бит становится 0, а потом снова 1 ?
А для чего т огда вот это бит:
↓ спойлер ↓
• 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. Alter-
natively, 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.
[свернуть]

Он тоже взводится по окончанию вычислений и сообщает, что данные в регистрах валидные. А сбрасываетя либо вручную, либо по выполнению прерывания.






Slabovik

Покурим?  ;)
Вот что пишут от этом бите, когда Single Mode
Цитата: 24.3 Single Conversion modeA single conversion is started by disabling the power reduction ADC bit, PRADC, (in Section 10.10 "Minimizing Power Consumption" on page 37) by writing a logical zero to it and writing a logical one to the ADC start conversion bit, ADSC. This bit stays high as long as the conversion is in progress and will be cleared by hardware when the conversion is completed.
То же самое в режиме AutoTriggering (это когда преобразование запускается либо от внешнего сигнала либо от таймера)
ЦитатаADSC can also be used to determine if a conversion is in progress. The ADSC bit will be read as one during a conversion, independently of how the conversion was started.
Ну т.е. этот бит читается как "1" тогда и только тогда, когда АЦП производит преобразование, ну т.е. работает.
Предполагаю, что в режиме Free Running этот бит всегда будет читаться как "1". Поищем в документации. И тут же ниже видны картинки

Для одиночного преобразования
Single_conversion_ADC_timecycle.gif

Для непрерывного преобразования.
Free_running_ADC_timecycle.gif

Ну вот, становится видна разница.
Получается, что ADSC в режиме FreeRunning не получится использовать как индикатор готовности данных. Однако да, для этого есть специальный регистр ADIF
ЦитатаAfter the conversion is complete (ADIF is high), the conversion result can be found in the ADC result registers (ADCL, ADCH).
Но тут есть нюансы, ибо если используются прерывания для чтения из АЦП, то
Цитата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.
Из сказанного выходит, что в режиме прерываний нет нужды специально следить за ADIF, т.к. при входе в прерывание он автоматически сбрасывается и задача программы просто быстренько прочитать данные из регистров, положив их куда надо.

Загадку, что надо делать, если прерывание не используется, а режим Free Running попробуешь сам разгадать? Хотя сразу скажу, что такой режим используется не часто, потому что в программном режиме всё-таки удобнее одиночное преобразование, где алгоритм прост: запустил - подождал - прочитал - пока вычисляю АЦП стоит, с если стало надо - снова запустил.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Цитата: Slabovik от 07 Июль, 2022, 20:04Загадку, что надо делать, если прерывание не используется, а режим Free Running попробуешь сам разгадать?

Если правильно понимаю из даташита и вот этого примера, если прерывание не используется нужно вручную обнулять ADIF после того как программа прочитала данные из регистров.

Я всегда плохо читал эти  диаграммы. И вот здесь не понятно. В даташите написано, что сброшенное состояние ADIF это 1 т.е. как только он принимает значение 0 можно читать. А на рисунках в One conversion он 0, в Next conversion 1, а по моему пониманию 0 должен быть между ними, а во время, 1.
Поясните, пожалуйста, где я тут не правильно читаю?

Slabovik

Цитата: Shaman от 13 Июль, 2022, 14:46В даташите написано, что сброшенное состояние ADIF это 1 т.е. как только он принимает значение 0 можно читать.
Это в каком месте?

зы: читать диаграммы по-любому намного лучше, т.к. они не требуют траты 30+ минут на просмотр видео только ради того, чтобы в этом видео найти словесное описание их же...
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

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. Alter-natively, 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.

Этот бит устанавливается, когда преобразование АЦП завершается и регистры данных обновляются. Прерывание завершения преобразования АЦП выполняется, если установлены бит ADIE и бит I в SREG. ADIF сбрасывается аппаратно при выполнении соответствующего вектора обработки прерывания. В качестве альтернативы ADIF очищается путем записи логической единицы во флаг. Имейте в виду, что при выполнении чтения-модификации-записи на ADCSRA ожидающее прерывание может быть отключено. Это также применяется, если SBI и CBI используются инструкции.


Slabovik

Погоди-погоди.... Ты только что утверждал, что в даташите написано, что
Цитата: Slabovik от 14 Июль, 2022, 10:03сброшенное состояние ADIF это 1
а из даташита цитируешь, что "для сброса ADIF запишите в него "1". Разве это одно и то же? Это совершенно разные вещи. Первое - это состояние. Второе - действие для изменения состояния.

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

Хотя, лучшей практикой было бы конечно взять да попробовать, мало ли чего...

зы: подозреваю, что есть некоторое заблуждение относительно того, что в реальности из регистра вовсе не обязано читаться то, что туда было записано. Ибо регистр записи и регистр чтения - это разные устройства.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

А вот тут я перестаю что-то понимать. Для меня это одно и тоже. Если написано что для сброса состояния нужно записать 1 значит в регистре это бит выставится в 1. А значит сброшенное состояние 1. И вот этот автор говорит то же самое.

Цитата: Slabovik от 14 Июль, 2022, 15:13зы: подозреваю, что есть некоторое заблуждение относительно того, что в реальности из регистра вовсе не обязано читаться то, что туда было записано. Ибо регистр записи и регистр чтения - это разные устройства.

Возможно, но это то регистр состояния если его разнести на 2 разных адреса то как сам контроллер будет понимать как он настроен если писаться будет по одному адресу. читаться из другого.

А проверить в живую пока не получается, код не работает в протеусе. Даже напрямую скопированный из видео выше.

Видео длинное, поэтому вот код
#define F_CPU 1000000UL
#include <avr/io.h>
int main(void)
{
    DDRB |= (1<<2) | (1<<1) | (1<<0);
    PORTB &= ~((1<<2) | (1<<1) | (1<<0));
    DDRC &= ~(1<<1);

    //Настраиваем АЦП
    ADCSRA |= (1<<ADEN);        //разрешаем работу АЦП,
    ADCSRA |= (1<<ADFR);       //разрешаем работу АЦП с тригера
    ADCSRA &= ~(1<<ADPS2);       
    ADCSRA |= (1<<ADPS1) | (1<<ADPS0); //частота дискретизации 125 кГц
    ADMUX |= (1<<REFS1) | (1<<REFS0); //используем внутреннее опорное напряжение
    ADMUX |= (1<<ADLAR);              //правостороннее выравнивание
    ADMUX &= ~((1<<MUX3) | (1<<MUX2) | (1<<MUX1)); //выставляем в качестве входа АЦП порт PC0
    ADMUX |= (1<<MUX0);
    ADCSRA |= (1<<ADSC);       //запускаем АЦП

    while (1)
    {
         if (ADCSRA & (1<<4))
             {
                if (ADC >= 600)     //в место 600 нужно подставить необходимое заначение для опорного напряжения 1,1 V
                {
                        PORTB |= (1<<0);
                        PORTB &= ~(1<<1);
                        PORTB &= ~(1<<2);
                }
                if (ADC >= 560 && ADC < 600)     //в место 600 нужно подставить необходимое заначение для опорного напряжения 1,1 V
                {
                        PORTB &= ~(1<<0);
                        PORTB |= (1<<1);
                        PORTB &= ~(1<<2);
                }
                if (ADC < 560)     //в место 600 нужно подставить необходимое заначение для опорного напряжения 1,1 V
                {
                        PORTB &= ~(1<<0);
                        PORTB &= ~(1<<1);
                        PORTB |= (1<<2);
                }
                
                ADCSRA |= (1<<4);
                
             }
    }
}
[свернуть]

У меня он почему-то работает как выключатель, т.е. если резистор стоит в минимуме то горит красный, стоит только повернуть сразу зелёный. И для кода из видео я естественно нарисовал схему с Атмега8 и компилил под неё. В живую собрать пока не могу, нет в наличии дросселя на 10uH, а купить не позволяет время.

Slabovik

А контроллер не понимает, контроллер - это суть набор логических элементов. И вот этот набо логических элементов работает так, что хардверный сигнал "RD/WR" выбирает, к какому набору элементов идёт обращение. И всегда (подчеркну: ВСЕГДА) это разные наборы. Их схемотехнически можно организовать так, что вход регистра, который для чтения, будет подключён к выходу регистра, который на запись, и тогда результат чтения будет идентичен записанному результату, это удобно в ряде случаев (например: память), но в общем случае это не так и по одному и тому же адресу, определяемому хардверными линиями адреса, чтение и запись производится в/из разных наборов логических ячеек, а арбитражем для этого как раз служит сигнал "RD/WR".
Следовательно вот это утверждение
ЦитатаЕсли написано что для сброса состояния нужно записать 1 значит в регистре это бит выставится в 1. А значит сброшенное состояние 1
не является верным. В даташите чётко написано, что сброшенное состояние "0", но для получения "0" при чтении записать надо "1". Это не только у Mega такой прикол, это и у Tiny аналогично, и на форумах тоже часть народ недопонимает это место. Но ещё раз скажу: лучше проверить, поскольку у меня, не часто практикующего последнее время, в голове набор приёмов для кучки разных процессоров и ядер, поэтому где-то могу и ошибиться. Но документация - наше всё, я её и толкую. Ладно, поехали дальше.

Настоятельно рекомендую вписывать в условия более явные признаки.
Вот это: if (ADCSRA & (1<<4)) хорошо бы писать хотя бы так: if ((ADCSRA & (1<<ADIF))>0) потому что компилятор бывает игнорирует такие неявно заданные условия, либо интерпретирует их не так, как видит человек (уже ведь были примеры из практики).

Далее, сразу, как только вошли в это условие (бит=1), надо сделать две вещи. Порядок не особо принципиален, т.к. это произойдёт быстро.
Первое: сбросить бит: ADCSRA=ADCSRA |(1<<ADIF)
Второе: прочитать один раз регистры ADC в двубайтную переменную, с которой дальше и работать.
Если такого чтения не сделать, программа каждый раз, когда у ней будет появляться ADC, будет читать эти регистры, а по прошествии некоторого времени они могут стать неактуальными (часики-то тикают, несмотря на то, что 13 тактов при 125 кГц - это довольно много, но это и не много, когда начинают выполняться какие-либо действия).

Да, в конфигурации обозначен запуск от триггера. А что служит триггером? ADTS не вижу, где задан... По-идее для FreeRunning надо его в 0 поставить (там три бита). Считаем, что FreeRunning стоит.

PortB используется для контрольки?

Для некоторого ускорения работы программы, IF'ы, которые проверяют прочитанные данные с ADC (пусть это будет переменная ADCresult), лучше вложить друг в друга. Будучи записанными просто последовательно, они каждый раз выполняются все. Будучи вложенными, внутренние будут выполняться только при совпадении условий. Например
if (ADCresult <= LowLevel) { Signal = Warning_LOW; }
  else { if (ADCresult >= NormLevel) { Signal = Warning_OK;}
            else { Signal = Warning_NearLOW;}
        }

Ну и здесь, записать в PortB состояние Signal (да, меня практика научила меньше порты дёргать)
Думаю, мелочи, которые я не конкретизировал, вполне понятны и реализация их не составит большого труда. :)

И, да... Надо помнить, что резистор - это очень-очень шумный элемент. Поэтому после резистора надо бы сигнал пофильтровать. Между средним выводом и ногой входа АЦП воткнуть, скажем, 10 кОм, а между входом АЦП и землёй какой-нибудь конденсатор на микрофарад (с SMD керамикой нынче нет проблем, их как грязи). И ещё... Дроссель в питании секции АЦП на этапе макета - элемент вовсе не обязательный. без него всё будет работать не хуже. На этапе реализации можно уже и поставить.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Ёкарный бабай  :)  Благодарю за потраченное на ответ время.
Помимо понимания он породил ещё больше вопросов. Попробую разложить комментарии и новые вопросы по степени важности относительно изначального поста.

1.
Цитата: Slabovik от 14 Июль, 2022, 18:21В даташите чётко написано, что сброшенное состояние "0", но для получения "0" при чтении записать надо "1".

Если не сложно киньте, пожалуйста, выдержки из мануала. Я пока не нашел.

2.
Цитата: Slabovik от 14 Июль, 2022, 18:21Вот это: if (ADCSRA & (1<<4))

Я тоже склоняюсь к тому, что компилятор gcc может это неверно интерпретировать в примере сработало скорее всего потому, что использовалась среда разработки для атмеги.

3.
Цитата: Slabovik от 14 Июль, 2022, 18:21ADTS не вижу, где задан...
Пример писался для Атмеги8, в ней нет регистра ADCSRB а соответственно и бита ADTS. А также 5 бит в регистре ADCSRА называется ADFR вместо ADATE и просто включает режим FreeRunning. Комментарий остался от кода для 328-й, забыл исправить  :)

4.
Цитата: Slabovik от 14 Июль, 2022, 18:21И всегда (подчеркну: ВСЕГДА) это разные наборы.
Ок, очень ценная информация. А сигнал RD/WR чисто хардверный или может управляться тем кто пишет программу? Если нет, то тогда интересно зачем сделана такая реализация?

5.
Цитата: Slabovik от 14 Июль, 2022, 18:21PortB используется для контрольки?
В основной программе для отладки, потом удалю, а в примере из видео для вывода индикации.

6.
Цитата: Slabovik от 14 Июль, 2022, 18:21Для некоторого ускорения работы программы,
Да, благодарю, я потом так и делаю просто в СИ я новичок и так мне проще запустить незнакомый код и отследить где он не работает и уж потом упрощать.

Я знаю что контроллер не понимает. Просто так короче писать чем: "С помощью какого набора логических элементов и связей между ними происходит определение как, когда и в какой регистр записывать?"  :)

Slabovik

Цитата: Shaman от 15 Июль, 2022, 17:27выдержки из мануала. Я пока не нашел.
Как это не нашёл, если мне же эту фразу и цитировал?  :o Описание флага ADIF...  :-\
Цитата: Shaman от 15 Июль, 2022, 17:27А сигнал RD/WR чисто хардверный или может управляться тем кто пишет программу?
Это сигнал арбитража шины, и он хардверный (и не единственный). Но косвенно программист на него влияет, например, когда пишет ReadPort или ReadRAM (команды чисто условные). Формируется же непосредственно шинным контроллером, который у ATmega спрятан где-то внутри, как и сама шина. Вообще, хорошо бы поизучать построение вычислительных систем, начиная с i8080 или с той же Моторолы (правда, у нас она мало распространена, зато i8080 как грязи), там шинный контроллер - устройство частично внешнее (частично всё-таки в чипе CPU, который и задаёт правила его работы), но зато сама шина полностью открыта для фантазий конструктора. Другое дело, что по скорости та же Mega кроет его как... но это другой вопрос.

Код для Mega8 в общем-то такой же, как для других "мег", но да, есть некоторые отличия в управляющих регистрах.

Тут, как говорится, твой ход - реализовать изменения в тестовой программе и посмотреть, как оно отзовётся. Ну не расчехлять же мне свой макетатор...  ::)

зы: Кстати, вот здесь интересная ситуация разбирается. Может пригодится...

Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Цитата: Shaman от 14 Июль, 2022, 14:38This 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. Alter-natively, 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.

Этот бит устанавливается, когда преобразование АЦП завершается и регистры данных обновляются. Прерывание завершения преобразования АЦП выполняется, если установлены бит ADIE и бит I в SREG. ADIF сбрасывается аппаратно при выполнении соответствующего вектора обработки прерывания. В качестве альтернативы ADIF очищается путем записи логической единицы во флаг. Имейте в виду, что при выполнении чтения-модификации-записи на ADCSRA ожидающее прерывание может быть отключено. Это также применяется, если SBI и CBI используются инструкции.

Господи, да где!?  :o З десь сказано, что он очищается записью 1, но где написано, что очищенное состояние это 0

Slabovik

Просто хотя бы совмести первые восемь слов с картинкой, которую я уже показывал. Даташит - это всё-таки цельный документ, а не просто обрывки фраз...  :-\  Плюсом надо помнить правила логики, что если об инверсии флага нигде не упоминается, следовательно прочитанное состояние "1" означает "Set", а "0" ~ "Clear". Об это говорит любой фрагмент текста, где описывается его работа, и также диаграммы.

зы: можно было уже с полдюжины раз попробовать на макете ( я же знаю, он у тебя есть), чем впустую перепихиваться по поводу "а вот там этого не написано"...
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Всё, ушел в долгое переваривание полученной информации и отпуск во время которого расстояние от меня до макетки буде около 3000 км поэтому проверить в железе пока не вариант.  :)
Резюмирую то что понял.
1. 1 регистр всегда равно 2 независимых ячейки памяти на чтение и запись (если есть где инфа на русском зачем так сделано буду признателен если поделитесь)

2. Непонятно по какой причине запись 1 в ячейку/регистр? (даже не знаю как это назвать) записи приводит к установке 0 в ячейке чтения. Опять же непонятно как и зачем так сделано. Если знаете, расскажите, пожалуйста.

А тем кто так пишет документацию, желаю самим же её и читать до конца дней.  :)

Slabovik

1. Это логическое следствие схемотехники на транзисторах (и лампах тоже). И сделано оно так не `зачем', а 'потому что'
2. По причине, что так организовано схемотехнически. А за логическим объяснением смысла - это лучше к производителю.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Slabovik

Ну что, никак?
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman


Где смайлик рука-лицо?

И да, вот такое выражение работает if (ADCSRA & (1<<4))

Shaman

Поскольку АЦП удалось запустить продолжаю ковыряние, и прежде чем 20 раз переписывать код решил расписать желаемую логику работы.

Поскольку АЦП будет следить за разрядом батареи запускать его в непрерывный режим расточительство. Поэтому запуск будет раз в 10 мин. Результат вычисления будет сложен в какой-нибудь регистр. Далее, в общем цикле организуется слежение за этим регистром и если значение падает ниже или поднимается выше определённого, вызывается функция проверки на ложное срабатывание.
Функция должна работать так:
1. Переключает время срабатывания АЦП c 10 минут на 1.
2. Делает 3 замера и сравнивает полученные значения.
3. Если все 3 замера ниже порога отключения отключает питание или не включает. Если хоть один выше, возвращает время срабатывание АЦП назад и обнуляет свои замеры и оставляет питание или включает.

Поскольку АЦП не будет выдавать одинаковые значения в качества порога срабатывания должен быть выбран диапазон значений.

Вопрос: допустима ли такая логика работы? Если есть вариант поинтереснее прошу рассказать

P.S. Еще возник вопрос по поводу таймера по которому включается АЦП если использовать встроенные то они слишком быстрые. Даже с максимальным делителем 16 битный таймер посчитает максиму секунду на частоте 1 мГц.
Можно как-то ещё замедлить вычисления таймера. без организации внешнего тактирования?
Потому что очень хочется использовать встроенную функцию АЦП, срабатывание по переполнению таймера.

Slabovik

Нехилый cеанс самобичевания :) Продолжаем наши передачи  ;D
Я так понял, что во всём был виноват бит установки выравнивания?
А если серьёзно, что там с записью единицы в порт?
По таймеру... В принципе, если таймеров не хватает, то можно опрашивать АЦП в основном цикле программы, она ведь всё-равно циклична (насколько я понимаю, это всё связано с тем же самым проектом-фонариком?). Но на самом деле пофиг, в каком процессе будет вызываться подпрограмма опроса АЦП.
Значит, делаем так. Постулаты:
подпрограмму запускаем каждый раз (каждое переполнение таймера либо каждый программный цикл).
Подпрограмма имеет пару своих, но глобальных переменных, в котором она хранит своё состояние. Первая - счётцик циклов. Вторая - значение, при котором счётчик считается переполненным (либо удобнее наоборот - начальное значение счётчика, а сам счётчик при этом на вычитание).
Подпрограмма при вызове проверяет значение счётчика, увеличивает его на 1 (вычитает 1), проверяет на совпадение с максимальным значением (нулём).
Если макс значение не достигнуто, то выход из подпрограммы.
Если достигнуто - запускаем АЦП.
По результатам измерений можно скорректировать вторую переменную-значение, чтобы начать измерять либо чаще, либо реже.

зы: запускать АЦП в процедуре прерывания не рекомендую т.к. замер отнимает довольно значительное время.
Если использовать таймер с прерыванием, то в прерывании лучше всего считать количество этих прерываний (первая и вторая переменные) и по результатам выставлять флажок на запуск измерения АЦП, который можно делать уже в программном цикле, когда основной программе делать уже особо нечего и до следующего тика таймера есть время.

А порогу срабатывания лучше всего придать свойства триггера с гистерезисом. Например, при движении измерений вниз, считаем, что LOW наступает при 340 единиц АЦП. При каком-то измерении получили 339 - поставили флажок "LOW". А вот при движении вверх, чтобы 'LOW' заменить на 'MIDDLE', нужно, чтобы было не 340 единиц, а 345. Т.е. вот эта разница 5 - это гистерезис.


измеренофлаг состояния
348Middle
344Middle
338LOW
341LOW
343LOW
347Middle
352Middle

Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Благодарю, я тоже склонялся к такому решению после того как на досуге поковырялся в исходниках Arduino IDE и там функции millis и micros тоже реализованы через программный таймер.
millis и micros
unsigned long millis(void);
unsigned long micros(void);
void delay(unsigned long ms);
void delayMicroseconds(unsigned int us);
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout);
unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout);

void shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val);
uint8_t shiftIn(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder);

void attachInterrupt(uint8_t interruptNum, void (*userFunc)(void), int mode);
void detachInterrupt(uint8_t interruptNum);

void setup(void);
void loop(void);

// Get the bit location within the hardware port of the given virtual pin.
// This comes from the pins_*.c file for the active board configuration.

#define analogInPinToBit(P) (P)

// On the ATmega1280, the addresses of some of the port registers are
// greater than 255, so we can't store them in uint8_t's.
extern const uint16_t PROGMEM port_to_mode_PGM[];
extern const uint16_t PROGMEM port_to_input_PGM[];
extern const uint16_t PROGMEM port_to_output_PGM[];

extern const uint8_t PROGMEM digital_pin_to_port_PGM[];
// extern const uint8_t PROGMEM digital_pin_to_bit_PGM[];
extern const uint8_t PROGMEM digital_pin_to_bit_mask_PGM[];
extern const uint8_t PROGMEM digital_pin_to_timer_PGM[];

// Get the bit location within the hardware port of the given virtual pin.
// This comes from the pins_*.c file for the active board configuration.
// 
// These perform slightly better as macros compared to inline functions
//
#define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) )
#define digitalPinToBitMask(P) ( pgm_read_byte( digital_pin_to_bit_mask_PGM + (P) ) )
#define digitalPinToTimer(P) ( pgm_read_byte( digital_pin_to_timer_PGM + (P) ) )
#define analogInPinToBit(P) (P)
#define portOutputRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_output_PGM + (P))) )
#define portInputRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_input_PGM + (P))) )
#define portModeRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_mode_PGM + (P))) )

#define NOT_A_PIN 0
#define NOT_A_PORT 0

#define NOT_AN_INTERRUPT -1

#ifdef ARDUINO_MAIN
#define PA 1
#define PB 2
#define PC 3
#define PD 4
#define PE 5
#define PF 6
#define PG 7
#define PH 8
#define PJ 10
#define PK 11
#define PL 12
#endif

#define NOT_ON_TIMER 0
#define TIMER0A 1
#define TIMER0B 2
#define TIMER1A 3
#define TIMER1B 4
#define TIMER1C 5
#define TIMER2  6
#define TIMER2A 7
#define TIMER2B 8

#define TIMER3A 9
#define TIMER3B 10
#define TIMER3C 11
#define TIMER4A 12
#define TIMER4B 13
#define TIMER4C 14
#define TIMER4D 15
#define TIMER5A 16
#define TIMER5B 17
#define TIMER5C 18

#ifdef __cplusplus
} // extern "C"
#endif
  
[свернуть]

Буду использовать подпрограмму, вызываемую каждую итерацию главного цикла, но как тогда посчитать время если работа цикла не равномерна? Понятно, что совершенно без разницы сработает АЦП через 10 минут или через 12. Но как изначально (не эмпирически узнать эту величину). Та же мmillis привязан к переполнению таймера и просто увеличивается по прерыванию, но вызывать прерывание каждую долю секунды не много ли?

Slabovik

А зачем? К таймеру надо привязать сами циклы, а свободное время проц пусть вообще спит (стоит в HLT). Тогда эти циклы будут чётко ритмичны. Но тут надо уже самостоятельно так просчитать, чтобы все текущие дела проца были бы возможны к выполнению за время одного цикла (это конечно не обязательное условие в принципе, но если не успевать, придётся городить арбитаж задач, а это громоздко и лень).

зы: 10 минут это как-то многовато вообще-то. Раз в минуту-две, а по достижении Middle где-нибудь раз секунд в 20-30. Хотя это после получения практических результатов можно будет и откорректировать.

По поводу "как узнать, сколько выполняется процедура", когда программа длинная и не asm (но и в asm тоже), очень удобно использовать такой способ. Встал осциллографом на ножку-две и наблюдаешь-считаешь.

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

Shaman

Цитата: Slabovik от 18 Окт., 2022, 17:25А зачем? К таймеру надо привязать сами циклы, а свободное время проц пусть вообще спит (стоит в HLT). Тогда эти циклы будут чётко ритмичны. Но тут надо уже самостоятельно так просчитать, чтобы все текущие дела проца были бы возможны к выполнению за время одного цикла (это конечно не обязательное условие в принципе, но если не успевать, придётся городить арбитаж задач, а это громоздко и лень).


Вот это я не понял вообще. ))
К таймеру надо привязать сами циклы - это как?
а свободное время проц пусть вообще спит (стоит в HLT) - это что? HLT не гуглиться.
Но тут надо уже самостоятельно так просчитать, чтобы все текущие дела проца были бы возможны к выполнению за время одного цикла - работу проца можно принудительно остановить не выполнив задачу ?

Посмотрел длительность основного цикла, результат не радует.

169.png 323.png


1-я картинка это когда программа ничего не делает.
2-я когда пошел эффект
Ширина импульса = длительности цикла.

Длительность увеличивается в 1000 раз, а значит никакая привязка реального времени к длительности цикла невозможна. И кроме как реализовывать millis из ардуино я решения не вижу. Но тогда получается раз в секунду процессор будет останавливаться по прерыванию, не много ли? И я еще не разбирался с приоритетом прерываний, что если одно придет во время другого? Еще остаётся вариант с внешним генератором который будет тикать допустим раз в секунду или медленнее, но это увеличит энергопотребление и размер схемы.



Slabovik

#23
HLT, он же Halt, он же Sleep, он же Wait... Если перед этой командой запретить (замаскировать) все входящие прерывания DI (Disable Interrupts) проц на ней застынет в вечном сне (правда, для таких случаев придумали немаскируемые прерывания, но не в любой системе они есть). Очень просто
  • DI
  • HALT
У AVR команда называется Sleep и конфигурируется (см. раздел 10 описания ATmega: Power Management and Sleep Modes).

Раз в секунду? Это как-то слишком редко.
Какое в системе самое краткое периодическое событие, которое нужно выполнять чётко по времени? Мне помнится, это была отрисовка эффектов. Но ведь смена кадров - это не такое уж и частое явление.

По осциллограммам самый медленный круг (цикл) получается примерно за 1,5мс. Не так уж и плохо - за секунду ~650 кругов. А то, что в зависимости от условий длительной кругов не одинаковая - это вообще мелочь.

Хорошо бы на этом этапе осмыслить и нарисовать блок-диаграмму, что там вообще получается.

Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Да это тот самый фонарик просто код пока вынес отдельно дабы не запороть уже написанное. ))
За подсказку с блоксхемой благодарю очень помогла.
У меня получились две схемы
Общая:
Блок схема.png

И процедуры измерения:
схема процедуры dimension.png 

На их основе набросал вот такой код (пока не проверял свободное время кончилось)
#define F_CPU 1000000UL
#include <avr/io.h>
#include <avr/interrupt.h>

//управление таймерами
#define _timer_on TCCR0B |= (5<<CS00)      //включаем таймер
#define _timer_off TCCR0B &= ~(7<<CS00)    //выключаем таймер

//выбор порта управления питанием
#define _power_on PORTD |= (1<<5)         //Включаем основное питание
#define _power_off PORTD &= ~(1<<5)       //Выключаем основное питание

//имена бит в массиве состояния
#define RXEND 3  //приём завершен
#define STOP 2
#define LEFT 1
#define RIGHT 0
#define BLINK 3
#define RTFG 0
#define LTFG 0
#define TRCE 7
#define OFF 6
#define BATPG 4 //инверсный 

//Значения порогов гистерезиса
#define _pow_lay_up 500         //значения от балды
#define _pow_lay_down 400
//Значения периодичности срабатывания таймера
#define _threshold_max 480
#define _threshold_min 90

#define SIZE_STATUS 4                     //размер буфера статуса

//Массив состояния
unsigned char status [SIZE_STATUS] =
{
      0b00000000,   /*Состояние эффекта начиная с младшего бита по порядку:
                        0 Правый поворот, имя бита RIGHT
                        1 Левый поворот, имя бита LEFT
                        2 Стоп, имя бита STOP
                        3 Моргалка, имя бита BLINK
                        7 TRCE Позволяет изменить состояние сигнала поворота (взводится при изменении состояния сигнала стоп и поворота) */
      1,            // Шаг эффекта поворот
      1,            // Шаг Эффекта моргалка
      0b00000000,   /* Флаги :
                        0 Правый поворот вкл (нужен для сброса шага эффекта после выключения)
                        1 Левый поворот вкл
                        3 Пакет принят RXEND
                        4 Бит проверки напряжения BATPG*/
};
//Массив проверки напряжения
unsigned int adc_compare [3];
int8_t num_adc_compare = 0;
uint16_t timer = 0;                       //переменная таймера
uint16_t threshold = _threshold_max;                 //порог срабатывания

void dimension ();                        //прототип функции измерения напряжения батареи

//Обработка прерывания по вектору TIMER0_OVF
ISR (TIMER0_OVF_vect)
{
      TIFR0 &= ~(1<<TOV0);
      timer++;
}
int main(void)
{
   DDRB |= (1<<3) | (1<<2) | (1<<1);
    PORTB &= ~((1<<3) | (1<<2) | (1<<1));
    DDRC &= ~(1<<1);
    DDRD = 0b11111100;


    TCCR0A = 0b00000000;        //устанавливаем настройки таймера
    TCCR0B = 0b00001000;        //устанавливаем настройки таймера

    //Настраиваем АЦП
    ADCSRA |= (1<<ADEN);        //разрешаем работу АЦП,
    //ADCSRA |= (1<<ADATE);       //разрешаем работу АЦП с тригера
    //ADCSRB |= (1<<ADTS2);       //в качестве тригера выбрано прерывание по переполнению таймера 0
    ADCSRA &= ~(1<<ADPS0);       
    ADCSRA |= (1<<ADPS1) | (1<<ADPS2); //частота дискретизации 125 кГц
    ADMUX |= (1<<REFS1) | (1<<REFS0); //используем внутреннее опорное напряжение
    ADMUX &= ~(1<<ADLAR);              //правостороннее выравнивание
    ADMUX &= ~(1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (1<<MUX0); //выставляем в качестве входа АЦП порт PC0
    //DIDR0 |= (1<<ADC5D) | (1<<ADC4D) | (1<<ADC3D) | (1<<ADC2D) | (1<<ADC1D); //отключаем ненужные порты мультиплексора
    //ADCSRA |= (1<<ADSC);       //запускаем АЦП
    sei();                      //разрешаем глобальные прерывания
    
    while (1)
    {
        if ((status[3] & (1<<BATPG)) == 1)
        {
                PORTD |= (1<<6);
                PORTD &= ~(1<<7);
                dimension ();
        }
        else
        {
                _timer_on;        
                PORTD |= (1<<7);
                PORTD &= ~(1<<6);
                
                if (timer >= threshold)
                {
                        _timer_off;        //останавливаем таймер
                        dimension ();

                }
        }
        
            
    }
}
void dimension ()
{
        if ((ADCSRA & (1<<ADIF)) != 0)                                   //если взеден флаг окончания измерения
        {
                if ((status[3] & (1<<BATPG)) != 0)                       //если бит напряжения 1
                {
                       if (adc_compare [num_adc_compare] >= _pow_lay_up) //если значение в массиве больше или равно верхнему порогу
                       {
                                status[3] &= ~(1<<BATPG);                //бит напряжения 0
                                ADCSRA &= ~(1<<ADIF);                    //сброс флага
                                _timer_on;
                                _power_on;       
                       }
                       else
                       {
                                ADCSRA &= ~(1<<ADIF);
                       } 
                }
                else                                                      //если бит напряжения 0
                {
                        if (num_adc_compare < 2)                          
                        {
                                adc_compare [num_adc_compare] = ADC;
                                if (adc_compare [num_adc_compare] <= _pow_lay_down) //если значение в массиве меньше или равно нижнему порогу
                                {
                                        threshold = _threshold_min;                 //уменьшаем интервал измерения.
                                        
                                }
                                num_adc_compare++;
                                ADCSRA &= ~(1<<ADIF);
                                _timer_on;
                        }
                        if (num_adc_compare = 2)     // проверяе все ли значения массива dc_compare меньше либо равны _pow_lay_down
                        {
                                int8_t a = 0;
                                for (char i=0; i<=2; i++)
                                {
                                        if (adc_compare [i] <= _pow_lay_down)         
                                        {
                                                a++;
                                        }
                                }
                                if (a == 2)           //если да, выключаем питание
                                {
                                        _power_off;
                                        status[3] |= (1<<BATPG);
                                }
                                else                  //если нет, запускаем таймер заново
                                {
                                        _timer_on;
                                }
                                threshold = _threshold_max;
                                ADCSRA &= ~(1<<ADIF);
                                num_adc_compare = 0;
                        }
                }
        }       
        else 
        {
                ADCSRA |= (1<<ADSC);       //запускаем АЦП
        }
}

На неделе займусь проверкой и тестами.
Из того что необходимо проверить в первую очередь:
1. Правильно ли включен режим одиночного измерения у АЦП? В даташите не нашел явного указания на его включение только косвенно предположил, что если не выставлять бит работы с тригера это и будет он.
2. Подумать над логикой работы может, есть вариант поизящнее?

Еще хотел при измерении делать SLEEP, но не нашел способа как оставить включенным и таймер ШИМ и АЦП, а также не понял сохраняются ли значения на портах. Если не сохраняются, то у меня и фонарь погаснет, а если сохраняются, но с выключенным ШИМ, я рискую получить вспышку красного света если измерение затянется.

Если есть предложения пока я не добрался до проверки, не обижусь если выскажете.