ПОРТ-МАНИПУЛАЦИЯ НА ATmega328P - ДОМ ЗА ROCKY

Abstract: This page presents techniques for programming of microcontrollers with ATmega328P processor through port manipulation, considering its pros and possible cons. A prototype to a smart electronic system with a compilation of electronic components is proposed. A principal illustration of the system for representing its idea is synthesized. A table is given, describing the components to their respective pins, each with its own role. The source code of the system is provided.

Keywords: Arduino, ATmega328P, microcontrollers, port manipulation.

Въведение

Порт-манипулацията представлява по-бързо манипулиране на входно-изходните пинове на микронконтролера чрез програмиране на ниско ниво на портовите регистри. За нуждите на настоящата публикация е ползвана микроконтролерна развойна платка Arduino Uno Rev3 с ATmega328P AVR микроконтролер. Програмирането чрез така наречената порт-манипулация притежава както своите предимства, така и своеобразни рискове, от които сме се абстрахирали за целите на нашата публикация.
Рискове на порт-манипулацията:

Както обаче гласят 2 от общо 19-те принципа за програмиране из „кодекса“ на програмния език Python, наречен Zen of Python: “Не оставяйте грешките незабелязани...“, “... освен ако не сте ги скрили умишлено”, ако се предвидят всички заплахи на този вид програмиране и се заобиколят професионално, това би довело до използване на предимствата на порт-манипулацията (The Zen of Python, 2020): ATmega328P е високопроизводителен нискомощен AVR 8-битов микроконтролер, разполагащ с 3 порта (B, C, D), контролирани от три вида регистри (DDR, PORT, PIN) (ATmega328P, 2020). Той е напълно достатъчен за изпълнение на целите на публикацията, а именно създаване на интелигентна електронна система за обслужване на домашен любимец (в нашия случай куче - Rocky).

Интелигентна електронна система Дом за Rocky

За създаването на интелигентна електронна система, обслужваща например домашен любимец е представена идеологична принципна илюстрация на системата на фиг.1.

Rossi RM
Фиг.1. Принципна илюстрация на системата

Идеята за проекта е следната: чрез фоторезистора да се управлява осветление евентуално в помещение, обитавано от домашен любимец. Чрез температурен сензор да се следи температурата в помещението и да се управлява моторче за вентилация (в нашия случай за охлаждане). Скоростта на вентилация се настройва от превключвател с 4 степени. Чрез сензор за движение се следи присъствието на Роки, като при отсъствие повече от зададеното време от стопанина се задейства аларма чрез звънец в случая, която се деактивира от засечено движение или чрез бутон за RESET на цялата система на място от стопанина. Съществува и отделен бутон за моментно контролно осветление в помещението.
За нуждите на една такава система са подбрани примерни електронни компоненти, които може да бъдат заменени със свои аналози като марка и модели. В Таблица 1 са представени основните компоненти, свързващи се към съответния си пин, както и ролята на всеки пин (Input = Вход, Output = Изход).

Таблица 1. Свързване на компонентите към съответните пинове с определена роля

I/O ПИН № КОМПОНЕНТ
Input DDD2 2 Бутон №2 (PUSH)
Output DDD4 4 Зелен светодиод (LED)
Output DDD5 5 Жълт светодиод (LED)
Input DDD7 7 Сензор за движение (PIR)
Input DDB0 8 Превключвател (DIP Switch)
Output DDB1 9 Звънец (Buzzer)
Input DDB2 10 Превключвател (DIP Switch)
Output DDB3 11 Вентилатор 5V (Fan)
Output DDB5 13 Червен светодиод (LED)
Input RESET Бутон №1 (PUSH)
Input ADMUX0 A0 Температурен сензор (TMP36)
Input ADMUX1 A1 Фоторезистор

Освен изброените в Таблица 1 компоненти, в системата се използват и резистори: 4 бр. 330 Ω, 1 бр. 2.2 kΩ, 1 бр. 1 kΩ, 1 бр. 10 kΩ; диод: 1 бр. 1N4004; транзистор: 1 бр. N-Channel MOSFET и кондензатори: 2 бр. 100 nF. На фиг. 2 е изобразена прототипната принципна схема на свързване на компонентите към микроконтролера Uno.

Arduino - Rossi RM
Фиг.2. Прототипна схема на свързване на системата

Програмният код на системата, базиращ се на принципа на порт-манипулация изглежда по следния начин:

          
          // ***** Authors: @Rossi RM & Krassimir Kolev***** //
          // Инициализация на променливи
          bool state = LOW; // Променлива за състоянието на PIR сензора
          float voltage0 = 0, voltage1 = 0, degreeC, lightSensor, temperature = 0, adcl0 = 0, adch0 = 0, adcl1 = 0, adch1 = 0; // Променливи за АЦП
          int counter = 0; // Времеброяч

          void initInt0() { // Инициализация на външно прекъсване през пин 2
           EICRA = 0; // Ниското ниво на INT0 генерира заявка за прекъсване
           EIMSK |= 1; // Активиране на външното прекъсване на пин 2 - INT0
          }

          // Инициализация на Таймер 1 за прекъсване през 1 секунда
          void initTimer1() {
           TCCR1A = 0; // Зануляване на регистъра, привеждане в нормално състояние
           TCCR1B = 0; // Зануляване на регистъра за делителя
           TCNT1 = 0; // Инициализаия стойността на брояча като 0
           OCR1A = 15624; // = (16*10^6)MHz / (1 Hz * 1024 (делител)) - 1 (< 65536) // Задаване стойност за сравняване на регистъра с честота 1 Hz на инкрементация
           TCCR1B |= (1 << WGM12); // Активиране на CTC режим - изчистване на таймера при съвпадение
           TCCR1B |= (1 << CS12) | (1 << CS10); // Задаване на битове CS12 и CS10 за делител 1024 от основния такт на контролера
           TIMSK1 |= (1 << OCIE1A); // Разрешаване на прекъсване при съвпадение на стойностите след сравняване
          }

          // Инициализация на АЦП
          void initADC() {
           ADMUX &= ~(1<<5); // Зануляване на бита ADLAR = 0, който влияе върху представянето на резултата от АЦП и преобразуване в регистъра за данни (бит ADLAR)
           ADMUX |= 1<<6; // Избор на референтно напрежение = 5V от платката за АЦП (бит REFS0)
           ADMUX &= ~(1<<0); // Избран аналогово канал. Ползване на ADC0, пин A0 (бит MUX0)
           ADCSRA |= (1<<2) | (1<<1) | (1<<0) ; // Задаване на делител 128 между системната тактова честота и собствения такт на АЦП (битове ADPS2:0)
           ADCSRA |= (1<<7) | (1<<3) ; // Активиране на прекъсване и на модула на АЦП (битове ADEN и ADIE)
          }

          // Инициализация на пиновете от портовете
          void initPORTS() {
           DDRD |= 1 << DDD4; // Пин 4 изход
           DDRD |= 1 << DDD5; // Пин 5 изход
           DDRB |= 1 << DDB1; // Пин 9 изход
           DDRB |= 1 << DDB3; // Пин 11 изход
           DDRB |= 1 << DDB5; // Пин 13 изход
           PORTB = 0x05; // Активиране на pull-up (издърпващите) резистори на пинове 8 и 10, за осигуряване на стбаилно състояние на сигнала
          }

          void setup() {
           initPORTS(); // Извикване на функцията за портовете
           initTimer1(); // Извикване на функцията за Таймер 1
           initADC(); // Извикване на функцията за АЦП
           ADCSRA |= 1<<6; // Стартиране на АЦП (бит ADSC)
           initInt0(); // Извикване на функцията за външно прекъсване от бутон на пин 2

           TCCR2A = 0x81; // Сравняване на изходния режим за съвпадение A за изчистване на OC2A при сравнение и избор на режим 1 за PWM, phase correct (битове COM2A1 и WGM20)
           TCCR2B = 0x01; // Избран делител 1 (бит CS20), без делене

          //Serial.begin(9600); // Задаване на скорост за предаване последователно на данни в битове за секунда при комуникация с компютъра
          }

          ISR(INT0_vect) { // Генерирано външно прекъсване от бутона към пин 2 при ниско ниво
           PORTD |= 1 << PORTD4; // Светва зелената светлинна индикация
          }

          ISR(ADC_vect) { // Прекъсване от АЦП
           if (~ADMUX & (1<<0)) { // Проверка DALI BIT 0 = 0
           adcl0=ADCL; // Прочитане и записване на стойността от младшия регистър
           adch0=ADCH; // Прочитане и записване на стойността от старшия регистър
           }
           if (ADMUX & (1<<0)) { // Поверка дали бит 0 != 0
           adcl1=ADCL; // Прочитане и записване на стойността от младшия регистър
           adch1=ADCH; // Прочитане и записване на стойността от старшия регистър
           }
           ADMUX = ADMUX ^ B00000001; // Превключване на каналите на АЦП, мултиплексиране
           ADCSRA |= 1 <<6; // Стартиране на АЦП отново (бит ADSC)
          }

          ISR(TIMER1_COMPA_vect){ // Прекъсване от Таймер 1
           counter++; // Инкрементиране на стойността на времеброяча
          }

          void loop() {
           // Код за температурния сензор и моторчето
           voltage0 = (256*adch0+adcl0)*(5.0/1024.0);
           degreeC = (voltage0-0.5)*100.0;
           //Serial.println(degreeC); // Отпечатване на екрана стойността от температурния сензор

           if(degreeC >= 10.0) {
             // Включи моторче
             //PORTB |= 1 << PORTB3; // код за 3V DC моторче
             switch (PINB & 5) { // Прочитане на стойността от пинове 8 и 10 с взаимно изключване
             case 0: OCR2A = 77; //30% Задаване на стойност в изходния сравняващ регистър А за генериране на изходен сигнал към пин 11
             break;
             case 1: OCR2A = 127; //50%
             break;
             case 4: OCR2A = 178; //70%
             break;
             case 5: OCR2A = 229; //90%
             break;
             }
           }
           else if (degreeC < 10.0) {
             // Изключи моторче
             //PORTB &= ~(1 << PORTB3); // код за 3V DC моторче
             OCR2A = 0;
           }

           // Код на фоторезистора и светлинната му индикация
           voltage1 = (256*adch1+adcl1)*(5.0/1024.0);
           lightSensor = voltage1;
           //Serial.println(lightSensor); // Отпечатване на екрана стойността от фоторезистора
           if(lightSensor < 0.2) { // Проверка за стойността на фоторезистора
           PORTD |= 1 << PORTD5; // Жълта светлинна индикация
           }
           else if(lightSensor >= 1.0) {
           PORTD &= ~(1 << PORTD5); // Липса на жълта светлинна индикация
           }

           // Код на сензора за движение PIR и светлинната му индикация
           if (PIND & (1 << PIND7)) { // Ако има движение
             if (state == LOW) {
                PORTB |= 1 << PORTB5; // Червена светлинна индикация
                PORTB &= ~(1 << PORTB1); // Спиране на звънеца
                counter = 0; // Зануляване на времеброяча
                state = HIGH;
                //Serial.println("Motion detected!"); // Отпечатване на екрана при регистриране на движение
              }
           }
           else if (~PIND & (1 << PIND7)) { // Ако няма движение
             PORTB &= ~(1 << PORTB5); // Липса на червена светлинна индикация

             if (state == HIGH){
               //Serial.println("Motion stopped!"); // Отпечатване на екрана при липса на движение
               state = LOW;
             }
           }

           // Изминало време след последното регистрирано движение от PIR сензора
           if (counter >= 7) { // При стойност равна на посочената
             PORTB |= 1 << PORTB1; // Пусни звънеца
           }

           PORTD &= ~(1 << PORTD4); // Липса на зелена светлинна индикация през цялото време
           // *** Само хардуерно реализирано без код - при натискане на бутон 1 изпълни RESET на системата Arduino ***
          }
        
      
+359 884 843 741