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 микроконтролер. Програмирането чрез така наречената порт-манипулация притежава както своите предимства, така и своеобразни рискове, от които сме се абстрахирали за целите на нашата публикация.
Рискове на порт-манипулацията:
Интелигентна електронна система Дом за Rocky
За създаването на интелигентна електронна система, обслужваща например домашен любимец е представена идеологична принципна илюстрация на системата на фиг.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.
Програмният код на системата, базиращ се на принципа на порт-манипулация изглежда по следния начин:
// ***** 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 ***
}