Проекты на АТMega8.
Простые проекты для изучения одного из самых простых и массовых микроконтроллеров.
Для работы вам понадобится как минимум CodeVision AVR2.04.4a и Proteus 7.2 SP6. Если желаете поработать с "железом", вам понадобится и программатор. Советую вот такой - легко собирается, хорошо работает. Единственная проблема с первоначальной прошивкой ATMega8, это можно сделать либо через порт LPT - 5-provodov-avr, либо купить "зашитый" микроконтроллер, я брал здесь.
Светофор №1.
Простейший светофор на одно направление не требует для создания больших усилий и навыков. Схема собирается за несколько минут. Резисторы подбираются исходя из используемых светодиодов (номинал 470 Ом указан для "советских" АЛ307), следует заметить, что на схеме вывод AVCC подключен к питанию, если его не подключить на реальном микроконтроллере независимо от использования встроенного АЦП (аналого-цифрового преобразователя), то можно вывести микросхему из строя (справедливо для любых контроллеров серии Mega)!!!
Исходный код настолько простой, что я его представлю прямо здесь. Этот код для Си я нашёл в интернете и немного подправил, однако, любой программист скажет вам, что этот код является бестолковым (далее я опишу, как именно требуется писать такие программы), но зато - простым, наглядным и рабочим.
/*****************************************************
Chip type : ATmega8Program type : Application
AVR Core Clock frequency: 1.000000 MHz
Memory model : Small
Data Stack size : 256
*****************************************************/
#include <mega8.h>
#include <delay.h>void main(void)
{
// Port B initialization
PORTB=0x00;
DDRB=0x00;
// Port C initialization
PORTC=0x00;
DDRC=0x00;
// Port D initialization
PORTD=0x00;
DDRD=0x07;
// External Interrupt(s) initialization
// INT0: Off
// INT1: Off
MCUCR=0x00;
// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
ACSR=0x80;
SFIOR=0x00;
while (1) {
PORTD.2=1; //зеленый загорается
delay_ms(8000); //горит 8 секунд
PORTD.2=0; //Выключаем зеленый
delay_ms(500); //Ждем пол секунды
PORTD.2=1; //Включаем зеленый
delay_ms(500); //Ждем полсекунды
PORTD.2=0; //Выключаем зеленый
delay_ms(500); //Ждем полсекунды
PORTD.2=1; //Включаем зеленый
/*delay_ms(500);//Ждем полсекунды
PORTD.2=0; //Выключаем зеленый
delay_ms(500); //Ждем полсекунды
PORTD.2=1; //Включаем зеленый
delay_ms(500); //Ждем полсекунды*/
PORTD.1=1; //Включаем жёлтый
PORTD.2=0; //Выключаем зеленый
delay_ms(3000); //Ждем 3 секунды
PORTD.0=1; //Включаем красный
PORTD.1=0; //выключаем жёлтый
delay_ms(9000); //на 9 секунд
PORTD.1=1; //включаем жёлтый к красному
delay_ms(3000); //ждем 3 секунды
PORTD.0=0; //Выключаем красный
PORTD.1=0; //и жёлтый
};
}
Код можно ещё несколько сократить, если учесть, что по умолчанию практически все порты и регистры ATMega8 инициализированы в 0. Теперь для начинающих некоторые пояснения:
#include <mega8.h>
#include <delay.h>
Это так называемые заголовочные файлы, в них написаны процедуры, которые мы применим из других файлов. Например, процедура delay_ms(X), которая определяет задержку в миллисекундах, была написана другим человеком и содержится в файле delay.с (на самом деле всё может быть не совсем так, или совсем не так, - процедуры может содержать объектный файл типа *.obj, *.asm), а правила её применения описаны в файле delay.h, который мы подключаем.
DDRB=0x00;
Зарезервированные имена, обозначающие регистры порта контроллера (его ножки сгруппированные байтом - по 8, каждый бит регистра порта соответствует своей ножке) и регистры направления обмена порта (0-вход, 1-выход)
Например, запись DDRB.5=1 означает что ножка PB5 контроллера настроена на выход, и при записи PORTB.5=0 или =1, на данной ножке получим 0 (0...0.8 Вольта) или 1 (4...5 Вольт). Разумеется, можно и сразу целиком присвоить значение порту: PORTB=0xFF, т.е. все ножки порта PB равны 1.
while(1) {expression};
представляет собой бесконечный цикл, так как 1=TRUE.
Светофор №2.
Чтобы получить светофор на два направления, надо модифицировать схему светофора №1:
и код светофора №1:
/*****************************************************
Chip type : ATmega8
Program type : Application
AVR Core Clock frequency: 1.000000 MHz
Memory model : Small
External RAM size : 0
Data Stack size : 256
*****************************************************/
#include <mega8.h>
#include <delay.h>
void main(void)
{
// Port B initialization
PORTB=0x00;
DDRB=0x00;
// Port C initialization
PORTC=0x00;
DDRC=0x00;
// Port D initialization
PORTD=0x00;
DDRD=0b00111111;
ACSR=0x80;
SFIOR=0x00;
while (1)
{
PORTD.2=1; //зеленый1 загорается Z1
PORTD.3=1; //красный2 загорается K2
delay_ms(8000); //зеленый1 горит 8 секунд
PORTD.2=0; //выключаем зеленый1 ZM1
delay_ms(500); //ждем пол секунды
PORTD.2=1; //включаем зеленый1
delay_ms(500); //ждем полсекунды
PORTD.2=0; //выключаем зеленый1
delay_ms(500); //ждем полсекунды
PORTD.2=1; //включаем зеленый1
delay_ms(500); //ждем полсекунды
PORTD.1=1; //включаем оранжевый1 J1 K2+J2
PORTD.4=1; //включаем оранжевый2
PORTD.2=0; //выключаем зеленый1
delay_ms(2000); //ждем 2 секунды
PORTD.0=1; //включаем красный1 K1
PORTD.5=1; //включаем зеленый2 Z2
PORTD.1=0; //выключаем оранжевый1
PORTD.4=0; //выключаем оранжевый2
PORTD.3=0; //выключаем красный2
delay_ms(8000); //ждём 8 секунд
PORTD.5=0; //выключаем зеленый2 ZM2
delay_ms(500); //ждем полсекунды
PORTD.5=1; //включаем зеленый2
delay_ms(500); //ждем полсекунды
PORTD.5=0; //выключаем зеленый2
delay_ms(500); //ждем полсекунды
PORTD.5=1; //включаем зеленый2
delay_ms(500); //ждем полсекунды
PORTD.1=1; //включаем оранжевый1 к красному1 K1+J1
PORTD.4=1; //включаем оранжевый2 J2
PORTD.5=0; //выключаем зеленый2
delay_ms(2000); //ждем 2 секунды
PORTD.0=0; //Выключаем красный1
PORTD.1=0; //и оранжевый1
PORTD.4=0; //и оранжевый2
};
}
комментарии по коду:
DDRD=0b00111111; //0b(b7)(b6)(b5)(b4)(b3)(b2)(b1)(b0)
справа битовое(двоичное) представление числа в котором слева - самый старший разряд, его же можно было записать в десятичном ( DDRD=63;) или в шестнадцатиричном виде ( DDRD=0x3F;).
void main(void)
main главная процедура, с неё начинается выполнение программы, void означает буквально ничего (нулевой тип), вместо void можно подставить (signed или unsigned) int, long; float, double и так далее (и даже указатель на другую переменную, о чём будет рассказано позже). Причём замена void на какой-либо тип в первой позиции обозначает что данная конструкция - функция, а не процедура, и может возвращать значение, т.о. она должна быть дополнена внутри словом return.
int fun(void) {
int a=10;
return(a);
}
все изменения производились согласно диаграмме работы:
Диаграмма работы светофора на два направления с мигающим зелёным сигналом. Данная диаграмма взята также с просторов интернета, и по-моему: миганий зелёного сигнала должно быть больше.
Светофор №3.
В примерах №1 и №2 было указано на несколько "прямолобое" применение кода, которое позволительно для небольших программ, однако не может быть применено из-за расточительности ресурсами в больших приложениях.
Для упрощения и сокращения кода в технологических применениях программисты обычно используют автоматы. Автомат является упрощённой и достаточной моделью процесса. Например, всю работу светофора можно разбить на некоторое конечное число состояний, которые будут повторятся с некоторым периодом. Для представления автомата потребуется программный счётчик состояний и дешифратор состояний. Светофор №3, который состоит из транспортного светофора на 4 направления, 4-х пешеходных светофоров, 1-го железнодорожного для переезда, 1-го мигающего пешеходного, было бы проблематично создать без автомата.
Код без обработки пешеходных и ж/д светофоров приведен ниже. Несложно понять что процедуры типа
void SetRedT1(void) //Транспортный светофор1 включить красный D3
{
ptc|=0x01;
}
устанавливают либо стирают отдельные биты.
В главном цикле значение счётчика i постоянно увеличивается пока не достигнет 48.
i++;
При этом счётчик обнуляется.
if (i==48) {i=0;}; // 24c=0.5*48
В цикле постоянно происходит сравнение значения счётчика с константами, и производится работа с портами. Хоть данный код и более совершенный, чем предыдущие два, но он также более подходит для наглядности чем для работы. Профессионалы чаще всего не используют наборы отдельных операций сравнения, а пользуются операторами switch-case, или вовсе записывают заранее рассчитанные данные в массивы - пользуются готовой таблицей:
flash unsigned char sw[N]={
//G1Y1R1G2Y2R2 XX
0b10000100, //1 Х периодов горят Зелёный1 и Красный2
0b10000100, //2
...
0b00000100, // Х периодов Зелёный1 мигает,
0b10000100, //
...
0b01001100, //K-1 Зелёный1 гаснет, загораются Жёлтый1 и Жёлтый2
0b00110000, //K Загорается Красный1, загорается Зелёный1, гаснут Жёлтый2 и Красный2
...
0b01101000 //N
};
while (1)
{
i++;
if (i==48) {i=0;}; // 24c=0.5*48 - цикл
PORTC=(sw[i]>>x) | mask;
PORTD=(sw[i]>>x) | mask;
delay_ms(500);
};
}
Разумеется, выражения (sw[i]>>x) | mask описаны условно, всё зависит от положения информации о сигналах в константе sw.
/*****************************************************
Chip type : ATmega8
Program type : Application
AVR Core Clock frequency: 1.000000 MHz
Memory model : Small
External RAM size : 0
Data Stack size : 256
*****************************************************/
#include <mega8.h>
#include <delay.h>
unsigned char ptb=0;
unsigned char ptc=0;
unsigned char ptd=0;
void SetRedT1() //Транспортный светофор1 включить красный D3
{
ptc|=0x01;
}
void ResetRedT1() //Транспортный светофор1 выключить красный D3
{
ptc&=~0x01;
}
void SetYelT1() //Транспортный светофор1 включить жёлтый D2
{
ptc|=0x02;
}
void ResetYelT1() //Транспортный светофор1 выключить жёлтый D2
{
ptc&=~0x02;
}
void SetGrnT1() //Транспортный светофор1 включить зелёный D1
{
ptc|=0x04;
}
void ResetGrnT1() //Транспортный светофор1 выключить зелёный D1
{
ptc&=~0x04;
}
void SetRedT2() //Транспортный светофор2 включить красный D6
{
ptc|=0x08;
}
void ResetRedT2() //Транспортный светофор2 выключить красный D6
{
ptc&=~0x08;
}
void SetYelT2() //Транспортный светофор2 включить жёлтый D5
{
ptc|=0x10;
}
void ResetYelT2() //Транспортный светофор2 выключить жёлтый D5
{
ptc&=~0x10;
}
void SetGrnT2() //Транспортный светофор2 включить зелёный D4
{
ptc|=0x20;
}
void ResetGrnT2() //Транспортный светофор2 выключить зелёный D4
{
ptc&=~0x20;
}
void SetRedT3() //Транспортный светофор3 включить красный D9
{
ptd|=0x01;
}
void ResetRedT3() //Транспортный светофор3 выключить красный D9
{
ptd&=~0x01;
}
void SetYelT3() //Транспортный светофор3 включить жёлтый D8
{
ptd|=0x02;
}
void ResetYelT3() //Транспортный светофор3 выключить жёлтый D8
{
ptd&=~0x02;
}
void SetGrnT3() //Транспортный светофор3 включить зелёный D7
{
ptd|=0x04;
}
void ResetGrnT3() //Транспортный светофор3 выключить зелёный D7
{
ptd&=~0x04;
}
void SetRedT4() //Транспортный светофор2 включить красный D12
{
ptd|=0x08;
}
void ResetRedT4() //Транспортный светофор2 выключить красный D12
{
ptd&=~0x08;
}
void SetYelT4() //Транспортный светофор2 включить жёлтый D11
{
ptd|=0x10;
}
void ResetYelT4() //Транспортный светофор2 выключить жёлтый D11
{
ptd&=~0x10;
}
void SetGrnT4() //Транспортный светофор2 включить зелёный D10
{
ptd|=0x20;
}
void ResetGrnT4() //Транспортный светофор2 выключить зелёный D10
{
ptd&=~0x20;
}
// Declare your global variables here
void main(void)
{
unsigned char i=0;
PORTB=0x00;
DDRB=0xFF;
PORTC=0x00;
DDRC=0xFF;
PORTD=0x00;
DDRD=0xFF;
ACSR=0x80;
while (1)
{
if (i==0) {ResetYelT1();ResetRedT1();SetGrnT1();
ResetYelT2();SetRedT2();
ResetYelT3();ResetRedT3();SetGrnT3();
ResetYelT4();SetRedT4();
}; //0s
if (i==16) {ResetGrnT1();
ResetGrnT3();
}; //8s
if (i==17) {SetGrnT1();
SetGrnT3();
};
if (i==18) {ResetGrnT1();
ResetGrnT3();
};
if (i==19) {SetGrnT1();
SetGrnT3();
};
if (i==20) {ResetGrnT1();SetYelT1();
SetYelT2();
ResetGrnT3();SetYelT3();
SetYelT4();
}; //10s
if (i==24) {ResetYelT1();SetRedT1();
ResetYelT2();ResetRedT2();SetGrnT2();
ResetYelT3();SetRedT3();
ResetYelT4();ResetRedT4();SetGrnT4();
}; //12s
if (i==40) {ResetGrnT2();
ResetGrnT4();
}; //20s
if (i==41) {SetGrnT2();
SetGrnT4();
};
if (i==42) {ResetGrnT2();
ResetGrnT4();
}; //21s
if (i==43) {SetGrnT2();
SetGrnT4();
};
if (i==44) {SetYelT1();
ResetGrnT2();SetYelT2();
SetYelT3();
ResetGrnT4();SetYelT4();
}; //22s
i++;
if (i==48) {i=0;}; // 24c=0.5*48 - цикл
//PORTB=ptb;
PORTC=ptc;
PORTD=ptd;
delay_ms(500);
};
}
Столбчатый индикатор.
Если в предыдущих примерах использовались цифровые ресурсы микроконтроллера, то данный пример использует аналоговые - АЦП. Аналого-цифровой преобразователь предназначен для дискретных преобразований аналогового сигнала в цифровой код с определённой частотой дискретизации. Для начинающих скажу лишь, что максимальная скорость оцифровки ограничена.
Столбчатый индикатор отображает входное напряжение в виде столбца светящихся сегментов (или просто отдельных излучателей), что очень полезно для всяких индикаторов, примерами могут являться столбчатые индикаторы уровня записи (ИУЗ).
Код программы приведен ниже:
/*****************************************************
Chip type : ATmega8
Program type : Application
AVR Core Clock frequency: 4.000000 MHz
Memory model : Small
External RAM size : 0
Data Stack size : 256
*****************************************************/
#include <mega8.h>
#include <delay.h>
#define ADC_VREF_TYPE 0x40 //5V Vcc + C 4.7uF AREF
//PD0 LED1
//PD1 LED2
//PD2 LED3
//PD3 LED4
//PD4 LED5
//PD5 LED6
//PD6 LED7
//PD7 LED8
//PB0 LED9
//PB1 LED10
//PB2 LED11
//PB3 LED12
//PB4 LED13
//PB5 LED14
//PC4 LED15
//PC5 LED16
const
unsigned char PD[17]=
{
0b00000000, //0
0b00000001, //1
0b00000011, //2
0b00000111, //3
0b00001111, //4
0b00011111, //5
0b00111111, //6
0b01111111, //7
0b11111111, //8
//_____________
0b11111111, //9
0b11111111, //10
0b11111111, //11
0b11111111, //12
0b11111111, //13
0b11111111, //14
0b11111111, //15
0b11111111 //16
};
unsigned char PB[17]=
{
0b00000000, //0
0b00000000, //1
0b00000000, //2
0b00000000, //3
0b00000000, //4
0b00000000, //5
0b00000000, //6
0b00000000, //7
0b00000000, //8
//_____________
0b00000001, //9
0b00000011, //10
0b00000111, //11
0b00001111, //12
0b00011111, //13
0b00111111, //14
//_____________
0b00111111, //15
0b00111111 //16
};
// Read the AD conversion result
unsigned int read_adc(unsigned char adc_input)
{
ADMUX=adc_input | (ADC_VREF_TYPE & 0xff);
// Delay needed for the stabilization of the ADC input voltage
delay_us(10);
// Start the AD conversion
ADCSRA|=0x40;
// Wait for the AD conversion to complete
while ((ADCSRA & 0x10)==0);
ADCSRA|=0x10;
return ADCW;
}
void ADC_init()
{
// ADC initialization
// ADC Clock frequency: 125.000 kHz
// ADC Voltage Reference: Vcc, cap. on AREF
ADMUX=ADC_VREF_TYPE & 0xff;
ADCSRA=0x85;
}
void Decode(int signal)
{
PORTD=PD[signal];
PORTB=PB[signal];
if (signal==15) {PORTC |= 0b00010000;PORTC &= 0b11011111;}
else if (signal==16) {PORTC |= 0b00110000;}
else {PORTC &= 0b11001111;}
}
void SetupIO ()
{
// Input/Output Ports initialization
PORTB=0b00000000;
DDRB= 0b00111111;
PORTC=0b00001100;
DDRC= 0b00110000; //PC0,PC1 = ADC0,ADC1, PC2,PC3 setup pins with PUP
PORTD=0x00;
DDRD=0xFF;
}
void main(void)
{
// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
ACSR=0x80;
SFIOR=0x00;
SetupIO();
ADC_init();
while (1)
{
Decode(read_adc(0)*0.049*0.32);
delay_ms(100);
};
}
Практически все приёмы, используемые в данном коде, были рассмотрены выше. Единственно хотелось бы отметить расширенную конструкцию с оператором if:
if () {} //если (условие соблюдается) то { }
else if () {} //иначе если (условие соблюдается) то { }
else {} //иначе { }
и выражение read_adc(0)*0.049*0.32
дело в том, что АЦП выдаёт свои значения по шкале 0-1023 (2^10), что соответствует шкале 0-5 вольт (т.к. опорное напряжение 5 вольт), а нам нужно получить сначала вольты, а потом перевести в количество сегментов. 1024*0.049*0.32=16 т.е. вся шкала АЦП, это 16 сегментов.
Число 0.049 взято кратным числу 5 вольт /1024 отсчёта =0.0048828 (~10 отсчётов АЦП), а потом домножено на 0.32 экспериментальным методом, чтобы получить зажигание последнего сегмента при нужном входном напряжении.
Последовательный порт.
А теперь предлагаю окунуться в мир интерфейсов. Самый простой и широко используемый до недавних пор последовательный протокол RS232, хоть и считается устаревшим, но всё ещё входит в состав выпускаемых современных микроконтроллеров - и не в количестве одного, как правило теперь их не менее 4-х. Также, многие устройства, всё еще используют данный интерфейс (например, некоторые экраны VFD NoritakeItron, медленные устройства для производств и т.п.). Данный пример позволяет оценить, насколько легко и просто настроить и использовать RS232 (USART) в микроконтроллерах.
Ниже приведен код, который осуществляет передачу символа 0.
/*****************************************************
Chip type : ATmega8
Program type : Application
AVR Core Clock frequency: 1.000000 MHz
Memory model : Small
External RAM size : 0
Data Stack size : 256
*****************************************************/
#include <mega8.h>
// Standard Input/Output functions
//#include <stdio.h>
#define BAUDRATE 57600
#define BaudValue ((11059200UL/(BAUDRATE*16UL))-1)
void USART_Transmit(unsigned char data)
{
//{while ( !(UCSRA & (1<<UDRE)) ); //Ожидание опустошения буфера приема
UDR = data;
while( !(UCSRA & (1<<UDRE)) );
//UDR = data; //Начало передачи данных
}
void USART_init(void)
{
UBRRH = BaudValue >> 8;
UBRRL = BaudValue & 0xFF;
//
UCSRB=(1<<RXEN)|(1<<TXEN);
DDRD=0x02;
UCSRC = ( 1<<URSEL ) | ( 3<<UCSZ0 );
//UCSRA = (1<<U2X_Value);
}
void main(void)
{
ACSR=0x80;
SFIOR=0x00;
USART_init();
while (1) {USART_Transmit(0x30);};
}
Учтите, что Proteus не поддерживает эмуляцию UCSRC (установка параметров связи порта), поэтому установите частоту генератора 3500000, а в терминале поставьте следующие параметры:
Ниже приведен код, который осуществляет передачу символа 0.
/*****************************************************
Chip type : ATmega8
Program type : Application
AVR Core Clock frequency: 1.000000 MHz
Memory model : Small
External RAM size : 0
Data Stack size : 256
*****************************************************/
#include <mega8.h>
// Standard Input/Output functions
//#include <stdio.h>
#define BAUDRATE 57600
#define BaudValue ((11059200UL/(BAUDRATE*16UL))-1)
void USART_Transmit(unsigned char data)
{
//{while ( !(UCSRA & (1<<UDRE)) ); //Ожидание опустошения буфера приема
UDR = data;
while( !(UCSRA & (1<<UDRE)) );
//UDR = data; //Начало передачи данных
}
void USART_init(void)
{
UBRRH = BaudValue >> 8;
UBRRL = BaudValue & 0xFF;
//
UCSRB=(1<<RXEN)|(1<<TXEN);
DDRD=0x02;
UCSRC = ( 1<<URSEL ) | ( 3<<UCSZ0 );
//UCSRA = (1<<U2X_Value);
}
void main(void)
{
ACSR=0x80;
SFIOR=0x00;
USART_init();
while (1) {USART_Transmit(0x30);};
}
Учтите, что Proteus не поддерживает эмуляцию UCSRC (установка параметров связи порта), поэтому установите частоту генератора 3500000, а в терминале поставьте следующие параметры:
Комментариев нет:
Отправить комментарий