Светодиодное сердце на микроконтроллере Atmega16, или программирование AVR на языке Pascal
Однажды я решил сделать подарок своей любимой девушке. Для этого вооружился я паяльником, программатором и компьютером. И, как художник, сотворил светодиодное сердце. Чтобы сердце было особенным, я постарался реализовать всевозможные режимы мигания светодиодами.
Схема
Что из себя представляет схема? Здесь ничего необычного. Управляющим ядром сердца, своеобразным «кардиостимулятором», выступает, всем известный, микроконтроллер AVR Atmega16, окруженный минимально необходимой обвязкой. От кварца тактировать не стал, микроконтроллер работает на внутренней RC-цепочке на частоте 1 МГц.
Каждый светодиод, образующий сердце, подключен к отдельной «ноге» микроконтроллера через токоограничивающий резистор 500 Ом. Всего восемнадцать светодиодов, подключенных к портам A (все выводы), С (все выводы), D (два вывода). Светодиоды управляются «единицей».
Монтаж
Все элементы были спаяны проводом МГТФ на макетной плате.
Макетная плата сзади была «зашита» тонким пенопластом, чтобы защитить монтаж. Также были сделаны «ножки» из стоек, прикрученных винтами.
Потому что светодиоды оказались очень яркими, пришлось сделать приглушающий защитный экран из зеленого оргстекла и поднять его на стойки. Для чего предварительно были просверлены отверстия.
Программа
Для программирования микроконтроллера Atmega16 решил я использовать среду разработки E-LAB. Почему E-LAB? Наверно ответ в том, что в то время платформа Arduino еще не появилась на свет. А для реализации такого проекта требовался сравнительно простой и удобный инструмент. E-LAB — это такой своеобразный «дедушка» Arduino IDE. E-LAB обеспечивает создание программ для микроконтроллеров AVR на языке программирования высокого уровня Pascal, который всем известен. Хотя Pascal и является языком высокого уровня, но знание архитектуры микроконтроллеров AVR и общих принципов их функционирования крайне необходимо для успешного использования этого языка. E-LAB — это кладезь библиотек для работы со встроенными в микроконтроллер периферийными модулями (таймеры, ШИМ, I2C, UART и т.д.), так и с различными внешними периферийными устройствами (клавиатура, знакосинтезирующий ЖКИ, «семисегметнк», Ethernet и т.д.).
Основная логика работы программы заключается в последовательном переключении режимов индикации светодиодов в прерывании программного таймера. Когда я писал эту программу, мною не использовались системы контроля версий, поэтому последняя версия программы не сохранилась. Но я нашел промежуточный вариант программы, в котором основные режимы работы, в частности программный 18-ти канальный ШИМ, присутствуют.
{$NOSHADOW}
{ $WG} {global Warnings off}
//Контроллер ATmega16
//Напряжения питания 3.3 В
Device = mega16, VCC=3.3;
{ $BOOTRST $01C00} {Reset Jump to $01C00}
Import SysTick, TickTimer;
From System Import LongWord;
Define
//Рабочая частота 1 MГц (Внутренняя RC-цепочка)
ProcClock = 1000000; {Hertz}
SysTick = 10; {msec}
StackSize = $0032, iData;
FrameSize = $0032, iData;
TickTimer = Timer1;
//Задержка переключения системного
//в милисекундах
Define_USR SysLED_Delay = 500;
Implementation
{$IDATA}
{--------------------------------------------------------------}
{ Type Declarations }
type
{--------------------------------------------------------------}
{ Const Declarations }
const
TimeCount: Byte = 70;
MOutBits_L1:array[0..7] of Byte = (
$00,
$00,
$00,
$00,
$00,
$00,
$00,
$ff
);
MOutBits_L2:array[0..7] of Byte = (
$00,
$00,
$00,
$00,
$00,
$00,
$ff,
$ff
);
MOutBits_L3:array[0..7] of Byte = (
$00,
$00,
$00,
$00,
$00,
$ff,
$ff,
$ff
);
MOutBits_L4:array[0..7] of Byte = (
$00,
$00,
$00,
$00,
$ff,
$ff,
$ff,
$ff
);
MOutBits_L5:array[0..7] of Byte = (
$00,
$00,
$00,
$ff,
$ff,
$ff,
$ff,
$ff
);
MOutBits_L6:array[0..7] of Byte = (
$00,
$00,
$ff,
$ff,
$ff,
$ff,
$ff,
$ff
);
MOutBits_L7:array[0..7] of Byte = (
$00,
$ff,
$ff,
$ff,
$ff,
$ff,
$ff,
$ff
);
{*
MOutBits: array[0..24] of LongWord =(
%010000000000000101,
%010000000000000101,
%010000000000000101,
%010000000000000101,
%010000000000000101,
%010000000000000001,
%010000000000000001,
%010000000000000001,
%010000000000000001,
%010000000000000001,
%010000000000000001,
%010000000000000001,
%010000000000000001,
%010000000000000001,
%010000000000000001,
%000000000000000101,
%000000000000000101,
%000000000000000101,
%000000000000000101,
%000000000000000101,
%000000000000000001,
%000000000000000001,
%000000000000000001,
%000000000000000001,
%000000000000000001
);
*}
{--------------------------------------------------------------}
{ Var Declarations }
{$IDATA}
var
OutBitsIndex: Byte;
St_Level: Byte;
St_Timer: Byte;
PortDataA: Byte;
PortDataC: Byte;
PortDataD: Byte;
ShiftCounter: Byte;
TimerTickCounter: LongWord;
{--------------------------------------------------------------}
{ functions }
//Функци инициализации
//портов ввода-вывода
procedure InitPorts;
begin
//Первый сегмент сердца ( 8 светодиодов)
//Порт на вывод
DDRA:= %11111111;
//Записать нули
PortA:= %00000000;
//Второй сегмент сердца
//Порт на вывод
DDRC:= %11111111;
//Записать нули
PortC:= %00000000;
//Третий сегмент сердца ()
//Два вывода порта на вывод
DDRD:= %00000011;
//Записать нули
PortD:= %00000000;
//системный двухцветный светодиод (2 ножки)
//Порт на вывод
DDRB:= %00000011;
end InitPorts;
//Индикация красным
procedure SysLED_Red;
begin
//Подтянуть к единице 1-й вывод
//порта B
incl(PortB,1);
//Подтянуть к нулю 0-й вывод
//порта B
excl(PortB,0);
end SysLED_Red;
//Индикация зеленым
procedure SysLED_Green;
begin
//Подтянуть к единице 0-й вывод
//порта B
incl(PortB,0);
//Подтянуть к нулю 1-й вывод
//порта B
excl(PortB,1);
end SysLED_Green;
//Переключение индикации
//зеленный-красный
procedure SysLED_SwColor;
begin
//Зажечь красный
SysLED_Red;
//Задержка
mDelay(Word(SysLED_Delay));
//Зажечь зеленый
SysLED_Green;
//Задержка
mDelay(Word(SysLED_Delay));
end SysLED_SwColor;
//Обработчик прерывания программного таймера
procedure onTickTimer; //(SaveAllRegs);
begin
//SysLED_SwColor;
case St_Timer of
0:
toggle(PortA,0);
toggle(PortA,1);
toggle(PortA,2);
toggle(PortA,3);
toggle(PortA,4);
toggle(PortA,5);
toggle(PortA,6);
toggle(PortA,7);
toggle(PortC,0);
toggle(PortC,1);
toggle(PortC,2);
toggle(PortC,3);
toggle(PortC,4);
toggle(PortC,5);
toggle(PortC,6);
toggle(PortC,7);
toggle(PortD,0);
toggle(PortD,1);
|
1:
PortDataA := PortDataA ror 1;
PortA := PortDataA;
PortDataC := PortDataC ror 1;
PortC := PortDataC;
PortDataD := PortDataD ror 1;
PortD := PortDataD;
|
2:
// PortDataC := PortDataC ror 1;
// PortC := PortDataC;
// PortDataD := PortDataD ror 1;
// PortD := PortDataD;
if (ShiftCounter = 0) or (ShiftCounter = 18)
then
PortD := $00;
ShiftCounter := 0;
PortDataA := $01;
PortA := PortDataA;
inc(ShiftCounter);
elsif (ShiftCounter < 8) and (ShiftCounter > 0)
then
PortDataA := PortDataA rol 1;
PortA := PortDataA;
inc(ShiftCounter);
elsif (ShiftCounter = 8)
then
PortA := $00;
PortDataC := $01;
PortC := PortDataC;
inc(ShiftCounter);
elsif (ShiftCounter > 8) and (ShiftCounter < 16)
then
PortDataC := PortDataC rol 1;
PortC := PortDataC;
inc(ShiftCounter);
elsif (ShiftCounter = 16)
then
PortC := $00;
PortDataD := $01;
PortD := PortDataD;
inc(ShiftCounter);
elsif (ShiftCounter > 16) and (ShiftCounter < 18)
then
PortDataD := PortDataD rol 1;
PortD := PortDataD;
inc(ShiftCounter);
endif;
|
3:
inc(TimerTickCounter);
if ( ( TimerTickCounter mod TimeCount ) = 0 )
then
inc(St_Level);
if ( St_Level >= 16)
then
St_Level := 1;
endif;
endif;
case St_Level of
0:
PortA := $00;
PortC := $00;
PortD := $00;
|
1:
PortA := MOutBits_L1[OutBitsIndex];
PortC := MOutBits_L1[OutBitsIndex];
PortD := MOutBits_L1[OutBitsIndex];
|
2:
PortA := MOutBits_L2[OutBitsIndex];
PortC := MOutBits_L2[OutBitsIndex];
PortD := MOutBits_L2[OutBitsIndex];
|
3:
PortA := MOutBits_L3[OutBitsIndex];
PortC := MOutBits_L3[OutBitsIndex];
PortD := MOutBits_L3[OutBitsIndex];
|
4:
PortA := MOutBits_L4[OutBitsIndex];
PortC := MOutBits_L4[OutBitsIndex];
PortD := MOutBits_L4[OutBitsIndex];
|
5:
PortA := MOutBits_L5[OutBitsIndex];
PortC := MOutBits_L5[OutBitsIndex];
PortD := MOutBits_L5[OutBitsIndex];
|
6:
PortA := MOutBits_L6[OutBitsIndex];
PortC := MOutBits_L6[OutBitsIndex];
PortD := MOutBits_L6[OutBitsIndex];
|
7:
PortA := MOutBits_L7[OutBitsIndex];
PortC := MOutBits_L7[OutBitsIndex];
PortD := MOutBits_L7[OutBitsIndex];
|
8:
PortA := $FF;
PortC := $FF;
PortD := $FF;
|
9:
PortA := MOutBits_L7[OutBitsIndex];
PortC := MOutBits_L7[OutBitsIndex];
PortD := MOutBits_L7[OutBitsIndex];
|
10:
PortA := MOutBits_L6[OutBitsIndex];
PortC := MOutBits_L6[OutBitsIndex];
PortD := MOutBits_L6[OutBitsIndex];
|
11:
PortA := MOutBits_L5[OutBitsIndex];
PortC := MOutBits_L5[OutBitsIndex];
PortD := MOutBits_L5[OutBitsIndex];
|
12:
PortA := MOutBits_L4[OutBitsIndex];
PortC := MOutBits_L4[OutBitsIndex];
PortD := MOutBits_L4[OutBitsIndex];
|
13:
PortA := MOutBits_L3[OutBitsIndex];
PortC := MOutBits_L3[OutBitsIndex];
PortD := MOutBits_L3[OutBitsIndex];
|
14:
PortA := MOutBits_L2[OutBitsIndex];
PortC := MOutBits_L2[OutBitsIndex];
PortD := MOutBits_L2[OutBitsIndex];
|
15:
PortA := MOutBits_L1[OutBitsIndex];
PortC := MOutBits_L1[OutBitsIndex];
PortD := MOutBits_L1[OutBitsIndex];
|
endcase;
inc( OutBitsIndex );
if OutBitsIndex >= 8
then
OutBitsIndex := 0;
endif;
|
endcase;
end;
{--------------------------------------------------------------}
{ Main Program }
{$IDATA}
//Код выполняемый сразу после Reset'a
begin
//Инициализировать порты ввода/вывода
InitPorts;
//Настроить программный таймер
// Период = 1 мс
// Частота = 1 кГц
TickTimerTime(1000);
// Запустить таймер
TickTimerStart;
// Остановить таймер
TickTimerStop;
//Разрешить прерывания
EnableInts;
{
//Последовательное включение
//светодиодов c секундным интервалом
incl(PortA,0);
mDelay(1000);
incl(PortA,1);
mDelay(1000);
incl(PortA,2);
mDelay(1000);
incl(PortA,3);
mDelay(1000);
incl(PortA,4);
mDelay(1000);
incl(PortA,5);
mDelay(1000);
incl(PortA,6);
mDelay(1000);
incl(PortA,7);
mDelay(1000);
incl(PortC,0);
mDelay(1000);
incl(PortC,1);
mDelay(1000);
incl(PortC,2);
mDelay(1000);
incl(PortC,3);
mDelay(1000);
incl(PortC,4);
mDelay(1000);
incl(PortC,5);
mDelay(1000);
incl(PortC,6);
mDelay(1000);
incl(PortC,7);
mDelay(1000);
incl(PortD,0);
mDelay(1000);
incl(PortD,1);
mDelay(1000);
//Переход в первый режим таймера (Toggle)
St_Timer := 0;
//Период 200 мс
TickTimerTime(200000);
// Запустить таймер
TickTimerStart;
//Задержка 2 секунды
mDelay(2000);
//Период 150 мс
TickTimerTime(150000);
// Запустить таймер
TickTimerStart;
mDelay(2000);
// Остановить таймер
TickTimerStop;
//Период 100 мс
TickTimerTime(100000);
// Запустить таймер
TickTimerStart;
//Задержка 2 секунды
mDelay(2000);
// Остановить таймер
TickTimerStop;
//Период 50 мс
TickTimerTime(50000);
// Запустить таймер
TickTimerStart;
//Задержка 2 секунды
mDelay(2000);
// Остановить таймер
TickTimerStop;
//Период 25 мс
TickTimerTime(25000);
// Запустить таймер
TickTimerStart;
//Задержка 2 секунды
mDelay(2000);
}
// Остановить таймер
TickTimerStop;
PortDataA := $AA;
PortDataC := $AA;
PortDataD := $AA;
//Переход во второй режим (Shift Inv)
St_Timer := 1;
//Период 200 мс
TickTimerTime(200000);
// Запустить таймер
TickTimerStart;
//Задержка 5 секунд
mDelay(2000);
// Остановить таймер
TickTimerStop;
PortA := $00;
PortC := $00;
PortD := $00;
PortDataA := $00;
PortDataC := $00;
PortDataD := $00;
ShiftCounter := 0;
//Переход во второй режим (Shift One)
St_Timer := 2;
//Период 200 мс
TickTimerTime(200000);
// Запустить таймер
TickTimerStart;
//Задержка 5 секунд
mDelay(5000);
// Остановить таймер
TickTimerStop;
//Переход в третий режим таймера (PWM)
St_Timer := 3;
// Частота = 1 кГц
TickTimerTime(1000);
// Запустить таймер
TickTimerStart;
//Основной цикл
loop
//inc(St_Level);
//if ( St_Level >= 9)
//then
// St_Level := 0;
//endif;
//mDelay(200);
//SysLED_SwColor;
// incl(PortC,1);
// mDelay(1);
endloop;
end Love_Machine.
Серьезным недостатком E-LAB, являлось и является то, что для программирования из среды нужен особый фирменный программатор. За не имением такового, я зашивал hex-файл в микроконтроллер «народным» программатором AVR910 собственного производства.
Заключение
Думаю, что сейчас, похожий подарок на платформе Arduino может сделать каждый, и тем самым порадовать своих любимых. Было бы желание.
Автор: love_energy