Часто лицемерие у нас называют правилами приличия.
Фридрих Ницше

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

АЦП в AVR

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

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

Slabovik

Есть ощущение, что работа никак не идёт. Да?
Я смотрел алгоритм с разных сторон, но некоторые куски мне не понятны. Возможно "деревья мешают", а может там действительно какой-то косяк. Проблема в том, что мне видится неправильной такая разница в прохождении кругов ада циклов, тем более, что они получаются достаточно длинные. Поэтому я снова хотел бы начать с самого-самого начала - организации работы программы таким образом, чтобы обеспечить многозадачность (а здесь она имеется даже не скрытая) хотя бы в самом простом виде. И начинать всегда надо от крупной структуры, которая и будет формировать "скелет" алгоритма. Наполнить же его можно по мере проработки мелочей.
Если делать наоборот, то обычно получается так, что реализованная без учёта "встраивания в скелет" функциональность не даст его реализовать.

Для задачи "фонарик" могу предложить рассмотреть вот такой базовый алгоритм
Алго_1.png
Он не подробный, но это форма для роста, которая поможет разобраться в том, как сделать многозадачность.
Что здесь имеем? Имеем здесь большой круг программы, всё время сканирующий "а не случилось ли какое событие". Если событие случилось, то производится вызов соответствующих процедур для их обработки. Для организации строгой периодичности используется "тормозок" в виде перевода процессора в режим Stop (Idle), в котором он находится до поступления первого же прерывания, которое произойдёт. Периоды задаёт таймер (какой настроите - тот и будет). Они не должны быть слишком маленькими, но и не должны быть слишком большими. Обычно период выбирается соответственно наиболее часто исполняющемуся событию. Это может быть, например, период обновления индикатора, период замера какого-то параметра и т.п. Более редкие функции вызываются не каждый раз в цикле, а, например, либо по требованию, либо организуется пропуск N циклов.
В принципе, можно вообще без Idle - программа просто не будет останавливаться, дожидаясь события (именно так работали системы, в которых не было прерываний - они постоянно в цикле опрашивали аппаратуру на предмет нужных событий), но это слегка увеличивает энергопотребление процессора (тем больше, чем быстрее процессор).
В процедурах прерываний действий производится не много - только самые необходимые. Ибо прерывание - это суть событие, о котором надо "дать знать" самой программе. В прерывании можно считывать-записывать данные (например UART или АЦП), помещая их в очередь-буфер для дальнейшей их обработки основной программой, и обязательно информируем основную программу о событиях установкой соответствующих флагов (которые организованы при помощи обычных переменных в ОЗУ).
Алго_2.png
Дальше нужно определиться с тем, какие функции и какие события вообще в системе будут. Начать с основных. Обычно это вывод на индикатор, обработка кнопок, измерение чего-либо. Требующие наиболее чёткой периодичности должны быть первыми после Idle. Потому что каждая их процедур по цепочке в процессе выполнения имеет не строго одинаковую трудоёмкость, и чем дальше от Idle - тем больше будет "дрожать" периодичность их запуска. В случае с фонариком наиболее периодичной является наверное процедура обновления показаний на дисплее (матричном фонаре). А вот наиболее быстрой реакции будет требовать процедура взаимодействия с UART. Данные с регистра надо снимать достаточно быстро, пока не пришёл следующий байт. Поэтому это действие лучше всего организовать через промежуточный буфер приёма-передачи. Процедура в основном круге будет взаимодействовать с этим буфером на уровне "сообщений", а данные в/из портов UART в/из буфера можно перемещать в прерывании, потому что это короткие действия.

Функции на первом этапе достаточно определить только основные. Дополнительные можно "навесить", если это будет нужно, просто встроив их в цепочку опросов после Idle.

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

Slabovik

Случайно наткнулся на лёгкий ликбез о том, что такое вообще конечные автоматы. habr.com (https://habr.com/ru/company/timeweb/blog/717628/)
Надеюсь, что идея понятна, тем более, что она простая. Ну, а простые вещи можно соединять друг с другом - получатся сложные :)
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

За ликбез благодарю, изучаю.
Поработал с кодом и убрал делеи
теперь цикл main выглядит вот так.

int main (void)
{
        TCCR1A = 0b10000011;        //устанавливаем настройки таймера
        TCCR1B = 0b00001000;        //устанавливаем настройки таймера 
        DDRC = 0b00111111;          //устанавливаем необходимые пины порта C на вывод
        DDRB = 0b00000110;          //устанавливаем необходимые пины порта B на вывод
        DDRD = 0b00111100;          //устанавливаем необходимые пины порта D на вывод
        PORTC |= (1<<1) | (1<<2);   //устанавливаем на пинах порта C 1
        PORTB &= ~(1<<1) | ~(1<<2); //устанавливаем на пинах порта B 0
        PORTD &= ~(1<<2) | ~(1<<3) | ~(1<<5); //устанавливаем на пинах порта D 0
       
        USART_Init(MYUBRR);
        uint8_t time_run_fire = 0;  //счетчик для установки скорости бегущего огня
        uint8_t time_flach = 0;     //счетчик для установки скорости моргания поворотником
        uint8_t time_blink = 0;     //счетчик для установки скорости моргалки
        uint8_t count_flach = 0;    //количества морганий поворотника
        uint8_t leter_buf_1 = 11;   //RGB
        uint8_t leter_buf_2 = 11;   // Синий справа-Красный слева  
        uint8_t leter_buf_3 = 11;   // Зелёный
        uint8_t leter_buf_4 = 11;   // Красный справа-Синий слева
        uint8_t z = 0;      //включение и выключение инверсии буфера экрана, 0 справа
        uint8_t w;          //выбор порта для пропуска  PWM на выход     
        
        _pwm_pass;
        _clock_high;                //для предотвращения ложного срабатывания
        _latch_up;                  //поднимаем защелку
        _flash_G_low;               //выключаем светодиоды
        _flash_RGB_low;             //выключаем светодиоды
        _pwm_close(2<<1);           //выключаем светодиоды
        sei();                      //разрешаем глобальные прерывания
        readmi (11,11,11,11,0);     //гасим экран

        while (1)
        {      PORTB |= (1<<2);    //Для дебага удалить из окончательной версии
               
                
           
            
             //включение поворотников
             if (((status[0] & (7<<0)) == 1) || ((status[0] & (7<<0)) == 2) || ((status[0] & (7<<0)) == 5) || ((status[0] & (7<<0)) == 6))
             {
                
                //вкючение правого поворотника
                if  ((status[0] & (1<<RIGHT)) == 1)
                {
                        //status[3] |=(1<<RTFG);
                        
                        if (((status[0] & (1<<STOP)) == 0))
                        {
                           leter_buf_2 = 11; 
                           leter_buf_1 = 11; 
                           z = 0;
                           w = 1;  
                        }
                        else //со стопом
                        {
                           leter_buf_2 = 9; 
                           leter_buf_1 = 0;
                           z = 0;
                           w = 1; 
                        }
                }
                //вкючение левого поворотника
                else if ((status[0] & (1<<LEFT)) == 2)
                {
                        //status[3] |=(1<<LTFG);
                        
                        if (((status[0] & (1<<STOP)) == 0)) 
                        {
                           leter_buf_2 = 11; 
                           leter_buf_1 = 11;
                           z = 1;
                           w = 2;  
                        }
                        else //со стопом
                        {
                           leter_buf_2 = 9; 
                           leter_buf_1 = 0;
                           z = 1;
                           w = 2; 
                        }
                }
                
                _scvagnost;
                _pwm_on;
                _pwm_open(1<<w);   //устанавливаем на пине w порта C 0, тем самым пропуская PWM на выход
                switch (status[1])  //бегущий огонь
                {
                   case 1:
                   {
                           leter_buf_3 = 0;
                           leter_buf_4 = 0;
                           if (time_run_fire == _turn_signal_spd)
                           {
                                status[1] = 2;
                                time_run_fire = 0;
                           }
                           break;
                   }
                   case 2:
                   {
                           leter_buf_3 = 1;
                           leter_buf_4 = 1;
                           if (time_run_fire == _turn_signal_spd)
                           {
                                status[1] = 3;
                                time_run_fire = 0;
                           }
                           break;
                   };
                   case 3:
                   {
                           leter_buf_3 = 2;
                           leter_buf_4 = 2;
                           if (time_run_fire == _turn_signal_spd)
                           {
                                status[1] = 4;
                                time_run_fire = 0;
                           }
                           break;
                   };
                   case 4:
                   {
                           leter_buf_3 = 3;
                           leter_buf_4 = 3;
                           if (time_run_fire == _turn_signal_spd)
                           {
                                status[1] = 5;
                                time_run_fire = 0;
                           }
                           break;
                   };
                   case 5:
                   {
                           leter_buf_3 = 4;
                           leter_buf_4 = 4;
                           if (time_run_fire == _turn_signal_spd)
                           {
                                status[1] = 6;
                                time_run_fire = 0;
                           }
                           break;
                   };
                   case 6:
                   {
                           leter_buf_3 = 5;
                           leter_buf_4 = 5;
                           if (time_run_fire == _turn_signal_spd)
                           {
                                status[1] = 7;
                                time_run_fire = 0;
                           }
                           break;
                   };
                   case 7:
                   {
                           leter_buf_3 = 6;
                           leter_buf_4 = 6;
                           if (time_run_fire == _turn_signal_spd)
                           {
                                status[1] = 8;
                                time_run_fire = 0;
                           }
                           break;
                   };
                   case 8:
                   {
                           leter_buf_3 = 7;
                           leter_buf_4 = 7;
                           if (time_run_fire == _turn_signal_spd)
                           {
                                status[1] = 9;
                                time_run_fire = 0;
                           }
                           break;
                   };
                   case 9:
                   {
                           leter_buf_3 = 8;
                           leter_buf_4 = 8;
                           if (time_flach == _turn_signal_fspd)
                           {
                                status[1] = 10;
                                count_flach ++;
                                if (count_flach == _quntity_flach)
                                {
                                    status[1] = 1;
                                    count_flach = 0;
                                }
                           }
                           break;
                   };
                   case 10:
                   {
                           leter_buf_3 = 11;
                           leter_buf_4 = 11;
                           
                           if (time_flach == _turn_signal_fspd)
                           {
                                status[1] = 9;
                                count_flach ++;
                                if (count_flach == _quntity_flach)
                                {
                                    status[1] = 1;
                                    count_flach = 0;
                                }
                           }
                           break;
                   };
                   case 20:
                   {
                           status[1] = 1;
                           status [0] &= ~(1<<TRCE);
                           break;
                   };
                }

                }

                //включение стопов
             else if ((status[0] & (7<<0)) == 4)
             {
                    leter_buf_1 = 3;
                    leter_buf_2 = 9;
                    leter_buf_3 = 11;
                    leter_buf_4 = 10;
                    z = 0;


                //     readmi (3, 9, 11, 10, z);
             }
                //включение моргалки
             else if ((status[0] & (15<<0)) == 8)
             {
                    if (status[2] == 1)
                    {
                            leter_buf_1 = 3;
                            leter_buf_2 = 9;
                            leter_buf_3 = 11;
                            leter_buf_4 = 10;
                            z = 0;
                        //     readmi (3, 9, 11, 10, z);
                            if (time_blink == _blink_spd)
                            {
                                status[2] = 2;
                                time_blink = 0;
                            }
                            
                    }
                    else if (status[2] == 2)
                    {
                            leter_buf_1 = 1;
                            leter_buf_2 = 5;
                            leter_buf_3 = 11;
                            leter_buf_4 = 5;
                            z = 2;
                        //     readmi (1, 5, 11, 5, 2);
                            if (time_blink == _blink_spd)
                            {
                                status[2] = 1;
                                time_blink = 0;
                            }
                    }
                    
             }
             else if ((status[0] & (1<<OFF)) != 0)
             {
                     leter_buf_2 = 11;
                     leter_buf_1 = 11;
                     leter_buf_3 = 11;
                     leter_buf_4 = 11;
                     z = 0;
                //      readmi (11,11,11,11,0);
                     status[0] &= ~(1<<OFF);
             }
             
                //обработка принятой инфы, если взведён бит приёма
             if ((status[3] & (1<<RXEND)) != 0)  
             {
                     receive ();
                     res_stat (20);
             } 
            
            readmi (leter_buf_1, leter_buf_2, leter_buf_3, leter_buf_4, z);
        //     record;
            _pwm_close(1<<w);     //устанавливаем на пине w порта C 1
            _pwm_off;
            time_run_fire ++;
            time_flach ++;   
            time_blink ++;   
              
            
            if (time_run_fire >= 255)
            {
                time_run_fire = 0;
            }
            if (time_flach >= 255)
            {
                time_flach = 0;
            }
            if (time_blink >= 255)
            {
                time_blink = 0;
            }
            if (time_blink >= 255)
            {
                time_blink = 0;
            }
            PORTB &=  ~(1<<2);    //для дебага удалить из окончательной версии
            
        }
        
}


В результате минимальная длительность цикла стала 1380us а Δ сотавила 176us (скрины ниже), что ничтожно мало и теперь я могу использовать длительность цикла как счётчик и можно наконец то начинать писаать код слежения за батарейкой.

Длительность цикла
RigolDS2.png

Δ длительности
RigolDS1.png

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

Slabovik

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

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

Shaman

Вернулся к теме после перерыва ужаснулся тому, что написал и стал переписывать снова. И опять наткнулся на место в даташите которое не до конца понимаю.
Вот блок-схема AЦП:
Выделение_046.png 

Меня интересует настройка входного мультиплексора. Для начала дам вводную:
  • Есть регистр ADMUX младшими четырьмя битами которого я выставляю с какого входа мультиплексора я буду читать.
  • Начиная с 16 меги появился регистр DIDR0 которым,
    если я правильно перевёл,
    • Bit 5:0 – ADC5D..ADC0D: ADC5..0 Digital Input Disable
    When this bit is written logic one, the digital input buffer on the corresponding ADC pin is dis-
    abled. The corresponding PIN Register bit will always read as zero when this bit is set. When an
    analog signal is applied to the ADC5..0 pin and the digital input from this pin is not needed, this
    bit should be written logic one to reduce power consumption in the digital input buffer.
    [свернуть]
    отключаются неиспользуемые выходы мультиплексора и из регистра PIN, при отключенном входе мультиплексора, всегда будет считываться 0, независимо от его состояния.

Вопросы:
  • Я правильно понимаю, что входной мультиплексор это независимый от порта D узел, а значит независимо от настроек регистра ADMUX я всё равно должен выставить соответствующий пин порта D на вход?
  • Зачем добавили регистр DIDR0, только лишь для экономии энергии, ведь вход жестко выбирается через ADMUX?
  • Если ответ на первый вопрос "да", то этот уже не нужен, но я всё равно спрошу. При отключении неиспользуемых входов через регистр DIDR0 порт D остаётся доступен и его можно использовать для других задач?

Slabovik

Вопрос интересный, но мне видится здесь недопонимание. Чуть внимательнее
Цитатаthe digital input buffer on the corresponding ADC pin is disabled
Т.е. когда входы (пины) используются как входы АЦП, они не имеют смысла как входы порта и эта настройка позволяет выключить (видимо, отключая питание на эту часть схемы или ещё как) цифровую логику, относящуюся к входным цепям цифровой части порта.
К аппаратуре ADC эта штука отношения не имеет, разве что подсоединена к тем же физическим линиям выводов.

Когда порт отключен (вроде это 'C', но может в вашей версии 'D') от физических линий, его логика, регистры остаются доступными для программиста. Можно спокойно записать в выходной регистр порта а затем считать оттуда записанное, использовав его как ячейку памяти. И только прочтение, как вы говорите, пина (т.е. данных с физической линии) не будет иметь смысла, т.к. он отключен.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

О, благодарю. И еще в разделе 9. Power Management and Sleep Modes
нашел вот такую штуку
An analog signal level close to VCC/2 on an input pin can cause significant current even in active mode. Digital input buffers can be disabled by writing to the Digital Input Disable Registers (DIDR1 and DIDR0). Refer to "DIDR1 – Digital Input Disable Register 1" on page 249 and "DIDR0 – Digital Input Disable Register 0" on page 266 for details.
[свернуть]

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

Slabovik

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

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

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

Slabovik

Цитата: Shaman от 16 Июль, 2022, 04:38[...]
2. Непонятно по какой причине запись 1 в ячейку/регистр? (даже не знаю как это назвать) записи приводит к установке 0 в ячейке чтения. Опять же непонятно как и зачем так сделано. Если знаете, расскажите, пожалуйста.

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

Вот, попалось ещё: у старой Моторолы точно такая же фишка
Цитата: M68HC11E Family Data SheetBits in this register indicate when certain timer system events have occurred. Coupled with the bits of TMSK2, the bits of TFLG2 allow the timer subsystem to operate in either a polled or interrupt driven system. Each bit of TFLG2 corresponds to a bit in TMSK2 in the same position.
[...]
Clear flags by writing a 1 to the corresponding bit position(s
А вообще удобно. Прочитали флаги, их же записали - они и сбросились.
Но Си-шникам этого не понять ;)
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.