Вы еще не программируете микроконтроллеры? Тогда мы идем к вам!
Время на прочтение
9 мин
Количество просмотров 375K

В этой статье я хочу рассказать о том, как однажды решил начать программировать микроконтроллеры, что для этого понадобилось и что в итоге получилось.
Тема микроконтроллеров меня заинтересовала очень давно, году этак в 2001. Но тогда достать программатор по месту жительства оказалось проблематично, а о покупке через Интернет и речи не было. Пришлось отложить это дело до лучших времен. И вот, в один прекрасный день я обнаружил, что
лучшие времена пришли
не выходя из дома можно купить все, что мне было нужно. Решил попробовать. Итак, что нам понадобится:
1. Программатор
На рынке предлагается много вариантов — от самых дешевых ISP (In-System Programming) программаторов за несколько долларов, до мощных программаторов-отладчиков за пару сотен. Не имея большого опыта в этом деле, для начала я решил попробовать один из самых простых и дешевых — USBasp. Купил в свое время на eBay за $12, сейчас можно найти даже за $3-4. На самом деле это китайская версия программатора от Thomas Fischl. Что могу сказать про него? Только одно — он работает. К тому же поддерживает достаточно много AVR контроллеров серий ATmega и ATtiny. Под Linux не требует драйвера.
Для прошивки надо соединить выходы программатора VCC, GND, RESET, SCK, MOSI, MISO с соответствующими выходами микроконтроллера. Для простоты я собрал вспомогательную схему прямо на макетной плате:
Слева на плате — тот самый микроконтроллер, который мы собираемся прошивать.
2. Микроконтроллер
С выбором микроконтроллера я особо не заморачивался и взял ATmega8 от Atmel — 23 пина ввода/вывода, два 8-битных таймера, один 16-битный, частота — до 16 Мгц, маленькое потребление (1-3.6 мА), дешевый ($2). В общем, для начала — более чем достаточно.
Под Linux для компиляции и загрузки прошивки на контроллер отлично работает связка avr-gcc + avrdude. Установка тривиальная. Следуя инструкции, можно за несколько минут установить все необходимое ПО. Единственный ньюанс, на который следует обратить внимание — avrdude (ПО для записи на контроллер) может потребовать права супер-пользователя для доступа к программатору. Выход — запустить через sudo (не очень хорошая идея), либо прописать специальные udev права. Синтаксис может отличаться в разных версиях ОС, но в моем случае (Linux Mint 15) сработало добавление следующего правила в файл /etc/udev/rules.d/41-atmega.rules:
# USBasp programmer
SUBSYSTEM=="usb", ATTR{idVendor}=="16c0", ATTR{idProduct}=="05dc", GROUP="plugdev", MODE="0666"
После этого, естественно, необходим перезапуск сервиса
service udev restart
Компилировать и прошивать без проблем можно прямо из командной строки (кто бы сомневался), но если проектов много, то удобнее поставить плагин AVR Eclipse и делать все прямо из среды Eclipse.
Под Windows придется поставить драйвер. В остальном проблем нет. Ради научного интереса попробовал связку AVR Studio + eXtreme Burner в Windows. Опять-таки, все работает на ура.
Начинаем программировать
Программировать AVR контроллеры можно как на ассемблере (AVR assembler), так и на Си. Тут, думаю, каждый должен сделать свой выбор сам в зависимости от конкретной задачи и своих предпочтений. Лично я в первую очередь начал ковырять ассемблер. При программировании на ассемблере архитектура устройства становится понятнее и появляется ощущение, что копаешься непосредственно во внутренностях контроллера. К тому же полагаю, что в особенно критических по размеру и производительности программах знание ассемблера может очень пригодиться. После ознакомления с AVR ассемблером я переполз на Си.
После знакомства с архитектурой и основными принципами, решил собрать что-то полезное и интересное. Тут мне помогла дочурка, она занимается шахматами и в один прекрасный вечер заявила, что хочет иметь часы-таймер для партий на время. БАЦ! Вот она — идея первого проекта! Можно было конечно заказать их на том же eBay, но захотелось сделать свои собственные часы, с блэк… эээ… с индикаторами и кнопочками. Сказано — сделано!
В качестве дисплея решено было использовать два 7-сегментных диодных индикатора. Для управления достаточно было 5 кнопок — “Игрок 1”, “Игрок 2”, “Сброс”, “Настройка” и “Пауза”. Ну и не забываем про звуковую индикацию окончания игры. Вроде все. На рисунке ниже представлена общая схема подключения микроконтроллера к индикаторам и кнопкам. Она понадобится нам при разборе исходного кода программы:
Разбор полета
Начнем, как и положено, с точки входа программы — функции main. На самом деле ничего примечательного в ней нет — настройка портов, инициализация данных и бесконечный цикл обработки нажатий кнопок. Ну и вызов sei() — разрешение обработки прерываний, о них немного позже.
int main(void)
{
init_io();
init_data();
sound_off();
sei();
while(1)
{
handle_buttons();
}
return 0;
}
Рассмотрим каждую функцию в отдельности.
void init_io()
{
// set output
DDRB = 0xFF;
DDRD = 0xFF;
// set input
DDRC = 0b11100000;
// pull-up resistors
PORTC |= 0b00011111;
// timer interrupts
TIMSK = (1<<OCIE1A) | (1<<TOIE0);
TCCR0 |= (1 << CS01) | (1 << CS00);
TCCR1B = (1<<CS12|1<<WGM12);
//OCRn = (clock_speed / prescaler) * seconds - 1
OCR1A = (F_CPU / 256) * 1 -1;
}
Настройка портов ввода/вывода происходит очень просто — в регистр DDRx (где x — буква, обозначающая порт) записивается число, каждый бит которого означает, будет ли соответствующий пин устройством ввода (соответствует 0) либо вывода (соответствует 1). Таким образом, заслав в DDRB и DDRD число 0xFF, мы сделали B и D портами вывода. Соответственно, команда DDRC = 0b11100000; превращает первые 5 пинов порта C во входные пины, а оставшиеся — в выходные. Команда PORTC |= 0b00011111; включает внутренние подтягивающие резисторы на 5 входах контроллера. Согласно схеме, к этим входам подключены кнопки, которые при нажатии замкнут их на землю. Таким образом контроллер понимает, что кнопка нажата.
Далее следует настройка двух таймеров, Timer0 и Timer1. Первый мы используем для обновления индикаторов, а второй — для обратного отсчета времени, предварительно настроив его на срабатывание каждую секунду. Подробное описание всех констант и метода настройки таймера на определенноый интервал можно найти в документации к ATmega8.
Обработка прерываний
ISR (TIMER0_OVF_vect)
{
display();
if (_buzzer > 0)
{
_buzzer--;
if (_buzzer == 0)
sound_off();
}
}
ISR(TIMER1_COMPA_vect)
{
if (ActiveTimer == 1 && Timer1 > 0)
{
Timer1--;
if (Timer1 == 0)
process_timeoff();
}
if (ActiveTimer == 2 && Timer2 > 0)
{
Timer2--;
if (Timer2 == 0)
process_timeoff();
}
}
При срабатывании таймера управление передается соответствующему обработчику прерывания. В нашем случае это обработчик TIMER0_OVF_vect, который вызывает процедуру вывода времени на индикаторы, и TIMER1_COMPA_vect, который обрабатывает обратный отсчет.
Вывод на индикаторы
void display()
{
display_number((Timer1/60)/10, 0b00001000);
_delay_ms(0.25);
display_number((Timer1/60)%10, 0b00000100);
_delay_ms(0.25);
display_number((Timer1%60)/10, 0b00000010);
_delay_ms(0.25);
display_number((Timer1%60)%10, 0b00000001);
_delay_ms(0.25);
display_number((Timer2/60)/10, 0b10000000);
_delay_ms(0.25);
display_number((Timer2/60)%10, 0b01000000);
_delay_ms(0.25);
display_number((Timer2%60)/10, 0b00100000);
_delay_ms(0.25);
display_number((Timer2%60)%10, 0b00010000);
_delay_ms(0.25);
PORTD = 0;
}
void display_number(int number, int mask)
{
PORTB = number_mask(number);
PORTD = mask;
}
Функция display использует метод динамической индикации. Дело в том, что каждый отдельно взятый индикатор имеет 9 контактов (7 для управления сегментами, 1 для точки и 1 для питания). Для управления 4 цифрами понадобилось бы 36 контактов. Слишком расточительно. Поэтому вывод разрядов на индикатор с несколькими цифрами организован по следующему принципу:
Напряжение поочередно подается на каждый из общих контактов, что позволяет высветить на соответствующем индикаторе нужную цифру при помощи одних и тех же 8 управляющих контактов. При достаточно высокой частоте вывода это выглядит для глаза как статическая картинка. Именно поэтому все 8 питающих контактов обоих индикаторов на схеме подключены к 8 выходам порта D, а 16 управляющих сегментами контактов соединены попарно и подключены к 8 выходам порта B. Таким образом, функция display с задержкой в 0.25 мс попеременно выводит нужную цифру на каждый из индикаторов. Под конец отключаются все выходы, подающие напряжение на индикаторы (команда PORTD = 0;). Если этого не сделать, то последняя выводимая цифра будет продолжать гореть до следующего вызова функции display, что приведет к ее более яркому свечению по сравнению с остальными.
Обработка нажатий
void handle_buttons()
{
handle_button(KEY_SETUP);
handle_button(KEY_RESET);
handle_button(KEY_PAUSE);
handle_button(KEY_PLAYER1);
handle_button(KEY_PLAYER2);
}
void handle_button(int key)
{
int bit;
switch (key)
{
case KEY_SETUP: bit = SETUP_BIT; break;
case KEY_RESET: bit = RESET_BIT; break;
case KEY_PAUSE: bit = PAUSE_BIT; break;
case KEY_PLAYER1: bit = PLAYER1_BIT; break;
case KEY_PLAYER2: bit = PLAYER2_BIT; break;
default: return;
}
if (bit_is_clear(BUTTON_PIN, bit))
{
if (_pressed == 0)
{
_delay_ms(DEBOUNCE_TIME);
if (bit_is_clear(BUTTON_PIN, bit))
{
_pressed |= key;
// key action
switch (key)
{
case KEY_SETUP: process_setup(); break;
case KEY_RESET: process_reset(); break;
case KEY_PAUSE: process_pause(); break;
case KEY_PLAYER1: process_player1(); break;
case KEY_PLAYER2: process_player2(); break;
}
sound_on(15);
}
}
}
else
{
_pressed &= ~key;
}
}
Эта функция по очереди опрашивает все 5 кнопок и обрабатывает нажатие, если таковое случилось. Нажатие регистрируется проверкой bit_is_clear(BUTTON_PIN, bit), т.е. кнопка нажата в том случае, если соответствующий ей вход соединен с землей, что и произойдет, согласно схеме, при нажатии кнопки. Задержка длительностью DEBOUNCE_TIME и повторная проверка нужна во избежание множественных лишних срабатываний из-за дребезга контактов. Сохранение статуса нажатия в соответствующих битах переменной _pressed используется для исключения повторного срабатывания при длительном нажатии на кнопку.
Функции обработки нажатий достаточно тривиальны и полагаю, что в дополнительных комментариях не нуждаются.
Полный текст программы
#define F_CPU 4000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#define DEBOUNCE_TIME 20
#define BUTTON_PIN PINC
#define SETUP_BIT PC0
#define RESET_BIT PC1
#define PAUSE_BIT PC2
#define PLAYER1_BIT PC3
#define PLAYER2_BIT PC4
#define KEY_SETUP 0b00000001
#define KEY_RESET 0b00000010
#define KEY_PAUSE 0b00000100
#define KEY_PLAYER1 0b00001000
#define KEY_PLAYER2 0b00010000
volatile int ActiveTimer = 0;
volatile int Timer1 = 0;
volatile int Timer2 = 0;
volatile int _buzzer = 0;
volatile int _pressed = 0;
// function declarations
void init_io();
void init_data();
int number_mask(int num);
void handle_buttons();
void handle_button(int key);
void process_setup();
void process_reset();
void process_pause();
void process_timeoff();
void process_player1();
void process_player2();
void display();
void display_number(int mask, int number);
void sound_on(int interval);
void sound_off();
// interrupts
ISR (TIMER0_OVF_vect)
{
display();
if (_buzzer > 0)
{
_buzzer--;
if (_buzzer == 0)
sound_off();
}
}
ISR(TIMER1_COMPA_vect)
{
if (ActiveTimer == 1 && Timer1 > 0)
{
Timer1--;
if (Timer1 == 0)
process_timeoff();
}
if (ActiveTimer == 2 && Timer2 > 0)
{
Timer2--;
if (Timer2 == 0)
process_timeoff();
}
}
int main(void)
{
init_io();
init_data();
sound_off();
sei();
while(1)
{
handle_buttons();
}
return 0;
}
void init_io()
{
// set output
DDRB = 0xFF;
DDRD = 0xFF;
// set input
DDRC = 0b11100000;
// pull-up resistors
PORTC |= 0b00011111;
// timer interrupts
TIMSK = (1<<OCIE1A) | (1<<TOIE0);
TCCR0 |= (1 << CS01) | (1 << CS00);
TCCR1B = (1<<CS12|1<<WGM12);
//OCRn = (clock_speed / prescaler) * seconds - 1
OCR1A = (F_CPU / 256) * 1 -1;
}
void init_data()
{
Timer1 = 0;
Timer2 = 0;
ActiveTimer = 0;
}
int number_mask(int num)
{
switch (num)
{
case 0 : return 0xC0;
case 1 : return 0xF9;
case 2 : return 0xA4;
case 3 : return 0xB0;
case 4 : return 0x99;
case 5 : return 0x92;
case 6 : return 0x82;
case 7 : return 0xF8;
case 8 : return 0x80;
case 9 : return 0x90;
};
return 0;
}
void process_setup()
{
Timer1 += 60;
Timer2 += 60;
// overflow check (5940 seconds == 99 minutes)
if (Timer1 > 5940 || Timer2 > 5940)
{
Timer1 = 0;
Timer2 = 0;
}
}
void process_reset()
{
init_data();
}
void process_timeoff()
{
init_data();
sound_on(30);
}
void process_pause()
{
ActiveTimer = 0;
}
void process_player1()
{
ActiveTimer = 2;
}
void process_player2()
{
ActiveTimer = 1;
}
void handle_button(int key)
{
int bit;
switch (key)
{
case KEY_SETUP: bit = SETUP_BIT; break;
case KEY_RESET: bit = RESET_BIT; break;
case KEY_PAUSE: bit = PAUSE_BIT; break;
case KEY_PLAYER1: bit = PLAYER1_BIT; break;
case KEY_PLAYER2: bit = PLAYER2_BIT; break;
default: return;
}
if (bit_is_clear(BUTTON_PIN, bit))
{
if (_pressed == 0)
{
_delay_ms(DEBOUNCE_TIME);
if (bit_is_clear(BUTTON_PIN, bit))
{
_pressed |= key;
// key action
switch (key)
{
case KEY_SETUP: process_setup(); break;
case KEY_RESET: process_reset(); break;
case KEY_PAUSE: process_pause(); break;
case KEY_PLAYER1: process_player1(); break;
case KEY_PLAYER2: process_player2(); break;
}
sound_on(15);
}
}
}
else
{
_pressed &= ~key;
}
}
void handle_buttons()
{
handle_button(KEY_SETUP);
handle_button(KEY_RESET);
handle_button(KEY_PAUSE);
handle_button(KEY_PLAYER1);
handle_button(KEY_PLAYER2);
}
void display()
{
display_number((Timer1/60)/10, 0b00001000);
_delay_ms(0.25);
display_number((Timer1/60)%10, 0b00000100);
_delay_ms(0.25);
display_number((Timer1%60)/10, 0b00000010);
_delay_ms(0.25);
display_number((Timer1%60)%10, 0b00000001);
_delay_ms(0.25);
display_number((Timer2/60)/10, 0b10000000);
_delay_ms(0.25);
display_number((Timer2/60)%10, 0b01000000);
_delay_ms(0.25);
display_number((Timer2%60)/10, 0b00100000);
_delay_ms(0.25);
display_number((Timer2%60)%10, 0b00010000);
_delay_ms(0.25);
PORTD = 0;
}
void display_number(int number, int mask)
{
PORTB = number_mask(number);
PORTD = mask;
}
void sound_on(int interval)
{
_buzzer = interval;
// put buzzer pin high
PORTC |= 0b00100000;
}
void sound_off()
{
// put buzzer pin low
PORTC &= ~0b00100000;
}
Прототип был собран на макетной плате:
После тестирования прототипа пришло время все это добро разместить в корпусе, обеспечить питание и т.д.
Ниже показан окончательный вид устройства. Часы питаются от 9-вольтовой батарейки типа “Крона”. Потребление тока — 55 мА.
Заключение
Потратив $20-25 на оборудование и пару вечеров на начальное ознакомление с архитектурой микроконтроллера и основными принципами работы, можно начать делать интересные DIY проекты. Статья посвящается тем, кто, как и я в свое время, думает, что начать программировать микроконтроллеры — это сложно, долго или дорого. Поверьте, начать намного проще, чем может показаться. Если есть интерес и желание — пробуйте, не пожалете!
Удачного всем программирования!
P.S. Ну и напоследок, небольшая видео-демонстрация прототипа:
Достаточно часто в личке ко мне обращаются люди с просьбой дать ссылки на полезные сайты, нужную информацию по программированию микроконтроллеров, необходимые программы и т.п. находясь при этом в самом начале своего познания микроконтроллеров. Сам я проходил через это буквально полтора года назад, имея нулевые знания и знаю, насколько это сложно, дать себе первоначального пинка, разобраться в лавине информации по микроконтроллерам, которую выдают поисковики, когда на тебя обрушивается куча непонятной информации и т. п.
Постараюсь объяснить на простом языке, для людей, умеющих держать паяльник, знающих, что такое цифровая микросхема логики, умеющих читать схемы и пользоваться мультиметром.
Микроконтроллеры бывают разных фирм, которые делают одно и тоже дело, но разными методами. Сравнить это можно с человеческими расами: европейцы, китайцы и африканцы например. Я лично работаю с микроконтроллерами фирмы Атмел, про них и буду говорить. Ну уж пошло сравнение с расами, пускай это будут европейцы.) Программы для микроконтроллеров пишут на языках программирования. Я рекомендую начать с языка Си. Это древний и простой язык. Для написания текста програмы используют программы компиляторы. Они позволяют создавать, редактировать и переваривать написанный программистом текст программы в код (прошивку), который можно загрузить (прошить) в микроконтроллер. Таких программ есть множество. Пример для Атмел: Code VisionAVR, родная от Атмел AVR Studio, Bascom-avr и ещё.
Эти программы делают одно и тоже дело, но своими методами, особенностями достоинствами и недостатками. При это текст Си в тих программах компиляторах немного отличается, но в общем похож. Можно сравнить с различием украинского, русского и белорусского языка. Я использую Code VisionAVR, что и советую начинающим.
Далее я приведу простой текст программы, написанный на языке Си в компиляторе Code VisionAVR для микроконтроллера ATTiny13A. В конце темы есть проект, прошивка и проект для эмулятора протеуса. Микроконтроллер в этой программе умеет делать простую вещь: при помощи кнопки менять логическое состояние на двух выходах, при этом короткое нажатие меняет состояние первого выхода а длинное — второго. В автомобиле например эту схему можно применить для управления одной кнопкой обогревом заднего стекла (которая есть у многих штатно) и добавленным обогревом зеркал. Нажал коротко на кнопку — сработал обогрев стекла, нажал ещё — обогрев стекла выключился. Если нажать и удерживать кнопку, то через какое-то время включиться обогрев зеркал. Если нажать и удерживать кнопку повторно — обогрев зеркал отключится.
Для понятия текста нужно знать грамматику, правила писанины языка Си, этого материала в интернете предостаточно. Так же желательно ознакомиться хотя бы с материалом, по использованию мастера создания проектов в CodeVisionAVR.
Текст программы:
/*****************************************************
This program was produced by the
CodeWizardAVR V2.05.0 Professional
Automatic Program Generator
© Copyright 1998-2010 Pavel Haiduc, HP InfoTech s.r.l.
www.hpinfotech.com
Project :
Version :
Date : 28.01.2012
Author :
Company :
Comments:
Chip type : ATtiny13A
AVR Core Clock frequency: 9,600000 MHz
Memory model : Tiny
External RAM size : 0
Data Stack size : 16
*****************************************************/
#include <tiny13a.h>
#include <delay.h>
unsigned char b, trig;
void main(void)
{
// Declare your local variables here
// Crystal Oscillator division factor: 1
#pragma optsize-
CLKPR=0x80;
CLKPR=0x00;
#ifdef _OPTIMIZE_SIZE_
#pragma optsize+
#endif
PORTB=0x01;
DDRB=0x06;
TCCR0A=0x00;
TCCR0B=0x00;
TCNT0=0x00;
OCR0A=0x00;
OCR0B=0x00;
GIMSK=0x00;
MCUCR=0x00;
TIMSK0=0x00;
ACSR=0x80;
ADCSRB=0x00;
DIDR0=0x00;
ADCSRA=0x00;
while (1)
{
if (PINB.0==0)
{
if (trig==0) b++;
if (b>100)
{
if (PINB.2==0)PORTB.2=1;
else PORTB.2=0;
trig=1;
b=0;
}
}
else
{
if (b>4)
{
if (PINB.1==0)PORTB.1=1;
else PORTB.1=0;
b=0;
}
b=0;
trig=0;
}
delay_ms(10);
}
}
А теперь поподробнее.
/*****************************************************
This program was produced by the
CodeWizardAVR V2.05.0 Professional
Automatic Program Generator
© Copyright 1998-2010 Pavel Haiduc, HP InfoTech s.r.l.
www.hpinfotech.com
Project :
Version :
Date : 28.01.2012
Author :
Company :
Comments:
Chip type : ATtiny13A
AVR Core Clock frequency: 9,600000 MHz
Memory model : Tiny
External RAM size : 0
Data Stack size : 16
*****************************************************/
Это шапка, в которой содержится описание проекта, необходимые данные. Текст закомментирован знаками комментария /* в начале и */ в конце. Все, что находится между этими знаками программой не выполняется. Самое полезное здесь это указание типа микроконтроллера и его частота.
#include <tiny13a.h>
#include <delay.h>
Это ссылка на библиотеку. Если какая либо библиотека необходима, то она должна быть здесь указана. У нас есть библиотека самого микроконтроллера tiny13a.h, и библиотека задержек времени.
unsigned char a, b, trig;
Объявление трех переменных. unsigned char . Что это такое можно посмотреть здесь Вообще всё непонятное копируем в буфер и ищем в поисковике.
void main(void)
{
// Declare your local variables here
void main(void) — это оператор, говорящий что началась основная часть программы на Cи и микроконтроллер будет её с этого места выполнять. Все что начинается с // — это комментарий. Старайтесь чаще ими пользоваться. Вообще конкретный комментарий генерирует сам компилятор, как и во многих других местах. Большинство комментариев я удалил, что уменьшить текст.
// Crystal Oscillator division factor: 1
#pragma optsize-
CLKPR=0x80;
CLKPR=0x00;
#ifdef _OPTIMIZE_SIZE_
#pragma optsize+
#endif
В комментарии по английски написано, что это такое. Это первая команда микроконтроллеру, одна из команд, которая настраивает нужные функции, порты и необходимые части микроконтроллера, необходимые для его работы и запуска.
Конкретно это настройка частоты делителя тактовой частоты микроконтроллера. Теперь подробнее:
Микроконтроллер имеет тактовый генератор, который задается в мастере и который потом можно изменить в свойствах проекта если что. У нас эта частота 9.6 мегагерца, как видно в шапке.
При прошивке микроконтроллера эту же частоту нужно указать во фьюзах.
CLKPR=0x80; и CLKPR=0x00; это команды настройки регистра внутреннего делителя этой частоты. Задается оно в мастере в первом окне «CHIP». Если у нас выбран делитель 1, то тактовая частота делиться на 1, то есть остается без изменений. Если указать например делитель 128, то соответственно тактовая частота делиться на это число. 9.6Мгц / 128 = 75кГц. и значения регистра делителя будет выглядеть:
CLKPR=0x80;
CLKPR=0x07;
Особо внимательные заметили, в регистр делителя CLKPR сначала пишется число 0x80 а затем сразу 0x00. Нафига пишется сначала одно значение а потом сразу другое? Если у вас возникают какие либо вопросы по регистрам и не только, приучайтесь сразу читать даташиты. Там все подробные ответы на чистом английском языке. Открываете даташит, вставляете в поисковик текста название регистра (CLKPR ) и ищете его описание, за что какие биты данного регистра отвечают. Конкретно у этого регистра для изменения делителя необходимо записать единичку в седьмой бит, после чего микроконтроллер даст изменить и выбрать необходимый делитель в первых четырех битах этого регистра. После того, как пройдет четыре такта выполнения команд процессора, изменить регистр будет уже нельзя. Нужно снова сначала изменить седьмой бит CLKPR=0x80 а затем указать делитель CLKPR=нужный делитель
PORTB=0x01;
DDRB=0x06;
Команды управления и настройкой портов микроконтроллеров — ножек чипа. Задается тоже в мастере. В этих регистрах задается работа на вход порта PB0 и подключается к нему внутренний Pull-up резистор. Порты PB1 и PB2 настраиваются «на выход» с логическим нулем на выходе в их состоянии.
В колонке DataDitection мы указывает тип порта: вход или выход (in или out)
В колонке PullUp/Output Value указываем подключение подтягивающего резистора pullup если порт настроен на вход (P — подключен, Т — неподключен) Если порт настроен на выход, то можно указать его логическое состояние 0 или 1. У нас нули. Строчки Bit0 — Bit5 это порты микроконтроллера PORTB0 — PORTB5
Если посмотреть сгенерированный компилятором комментарий, то можно увидеть соответствие пинов и их настройку:
// Input/Output Ports initialization
// Port B initialization
// Func5=In Func4=In Func3=In Func2=Out Func1=Out Func0=In
// State5=T State4=T State3=T State2=0 State1=0 State0=P
Если перевести из 16-тиричного в двоичный значение регистров, то можно понять даже без даташита назначение битов в регистре:
PORTB=0x01 PORTB=0b00000001
DDRB=0x06 DDRB=0b00000110
Напоминаю, что при разложении в двоичный код младшие значения справа а старшие слева.
Незначащие нули слева можно не писать:
PORTB=0b1;
DDRB=0b110;
А можно вообще написать в десятичной системе:
PORTB=1;
DDRB=6;
Далее по тексту кода идет:
TCCR0A=0x00;
TCCR0B=0x00;
TCNT0=0x00;
OCR0A=0x00;
OCR0B=0x00;
TCCR0A=0x00;
TCCR0B=0x00;
TCNT0=0x00;
OCR0A=0x00;
OCR0B=0x00;
GIMSK=0x00;
MCUCR=0x00;
TIMSK0=0x00;
ACSR=0x80;
ADCSRB=0x00;
DIDR0=0x00;
ADCSRA=0x00;
Настройка таймера микроконтроллера, прерываний, АЦП, компаратора и всего такого пока сложного. Пока его не используем — рановато. У всех этих регистров стоят в значениях нули, это значит, что они отключены. А особо внимательные заметили, что какой-то регистр ACSR имеет значение =0x80; Лезем в даташит и читаем:
Analog Comparator Control and Status Register – ACSR
Вообще, как правило название всех регистров это сокращение от первых букв или части их полного названия.
Если стоит значение данного регистра 0x80, значит в двоичной системе это число 10000000, значит стоит единичка в 7 бите этого регистра, значит читаем в даташите, что он означает:
• Bit 7 – ACD: Analog Comparator Disable
When this bit is written logic one, the power to the Analog Comparator is switched off.
This bit can be set at any time to turn off the Analog Comparator. This will reduce power
consumption in Active and Idle mode. When changing the ACD bit, the Analog Comparator
Interrupt must be disabled by clearing the ACIE bit in ACSR. Otherwise an interrupt
can occur when the bit is changed.
По-русски это означает, что установка единицы в этом бите отключает аналоговый компаратор, что его можно выключить в любой момент, что это приводит к снижению потребления электричества, и в конце текста написано про зависимости и что нужно соблюдать, если нужно изменять этот бит.
while (1)
После того, как микроконтроллер настроен запущен и выполнена инициализация необходимых частей и выполнены необходимые первоначальные команды в void main(void) в дело вступает его величество главный цикл. Все что находиться внутри этого главного цикла while (1) и заключено в скобки начала { и конца } будет крутиться, команды выполняться по кругу от начал до конца. А у нас в нашем коде будет крутиться алгоритм опроса кнопки, подключенной к порту PB0, от состояния которой (нажата кнопка или нет) будет меняться состояние выходных портов PB1 и PB2
На картинке видна схема собранную в эмуляторе Протеус схему, которая позволяет видеть работу кода программы.
Теперь про сами основные команды, которые находятся внутри цикла. Все команды используют один оператор if Это условие ЕСЛИ.
if (PINB.0==0)
{
кучка кода;
}
else
{
ещё кучка кода;
}
Подробнее:
if (PINB.0==0)
Если в регистре PINB в бите, отвечающем за порт PB0 микроконтроллера.0 содержится значение равное нулю ==0, то выполняем кучку кода, которая находится далее в границах скобок { и }
Короче, если нажата кнопка то выполняется следующий код в границах последующих скобок { и }
Далее после кучки кода в скобках видим оператор else и ещё кучку кода за ним в скобках { и }
Оператор else переводится не как ещё а как иначе
Оператор if и else всегда работают в паре, сначала идет if затем else. Оператор else можно не использовать совсем, если он не нужен.
В нашей ситуации алгоритм можно описать так:
если (нажата кнопка подключенная к порту PB0)
{
то выполняем кучку кода;
}
иначе (кнопка не нажата)
{
выполняем эту кучку кода;
}
Так как это все находится внутри главного цикла, то этот код будет выполняться по кругу, будет постоянно опрашиваться кнопка и будет выполняться нужная кучка кода
Теперь рассмотрим кучку кода, которая выполняется, если кнопка нажата:
if (trig==0) b++;
if (b>100)
{
if (PINB.2==0)PORTB.2=1;
else PORTB.2=0;
trig=1;
b=0;
}
Операторы можно вкладывать друг в друга, как матрешку. то есть выполняется одно условие, потом если условие сработало, то другое внутри первого условия и т.д.
if (trig==0) b++;
Если переменное значение trig равняется нулю, то выполняем инкремент переменной b Инкремент — операция увеличения значения, хранящегося в переменной, на 1. То есть при проходе выполнения кода, если процессор натыкается на команду инкремента b++, то процессор прибавляет единичку в число, которое находится в переменной b
Так же здесь применяется упрощенная «орфография» написания условия и команды, без скобок { и }:
if (trig==0) b++;
это же самое что:
if (trig==0)
{
b++;
}
Такое представление используют, если после условия всего одна команда.
Немного отвлеклись, возвращаемся:
if (trig==0) b++; — если значение переменной равно нулю (а оно у нас равно нулю) то выполняем инкремент переменной b — переменная в была равна нулю, теперь стало единице.
Следующая операция:
if (b>100)
{
кучка кода;
}
Если переменная b больше ста, то выполняем кучку кода внутри скобок.
Переменная b за каждый круг цикла прибавляется на единичку и в итоге через сто «кругов» главного цикла выполниться условие, которая находится далее внутри скобок { и }
Теперь рассмотрим что же там делается, если нажата кнопка, если прошло сто кругов цикла:
if (PINB.2==0)PORTB.2=1;
else PORTB.2=0;
trig=1;
b=0;
Здесь мы видим ещё одно условие (жирная такая матрешка получилась))
if (PINB.2==0)PORTB.2=1;
Если регистр состояния выходного порта PB, а точнее PB2 равен нулю, то меняем его состояние на единичку PORTB.2=1.
else PORTB.2=0;
Иначе пишем в регистр нолик. Или если по-другому: если регистр состояния выходного порта PB2 равен единице, то меняем его на ноль.
Короче если происходит выполнение этих условий и команд, то меняется логическое состояние выхода 2 (PB2) на схеме.
Если полностью описать: если нажата кнопка, если прошло сто кругов главного цикла, то меняем логическое состояние выхода 2 — PORTB.2 в коде он же порт PB2 на схеме.
Как уже стало понятно этот кусок кода отрабатывает длительное нажатие кнопки.
Но этого мало, дальше ещё есть две выполняемые команды присвоения:
trig=1;
b=0;
trig=1; присвоение единице этой переменной необходимо, что бы описанное выше условие работы инкремента b++ перестало работать
b=0; обнуляем переменную b.
В итоге при длительном нажатии кнопки, условие при котором меняется состояние порта PB2 выполняется единожды, до тех пор, пока кнопка не будет отжата кнопка, ибо инкремент не будет работать и условие if (b>100) больше не сработает, если тупо нажать кнопку и не отпускать совсем.
Теперь вторая часть кучки кода, которая следует за первым условием:
else
{
if (b>4)
{
if (PINB.1==0)PORTB.1=1;
else PORTB.1=0;
b=0;
}
b=0;
trig=0;
}
Если кнопка отжата:
Опишем её с конца:
trig=0; присваиваем переменной trig значение ноль. Необходимо, что бы после длительного нажатия, когда наступит последующее отжатие кнопки микроконтроллер снова был готов к нажатиям кнопки ( срабатывало условие инкремента if (trig==0) b++;)
b=0; При не нажатой кнопке значение переменной b равняется нулю.
if (b>4)
{
if (PINB.1==0)PORTB.1=1;
else PORTB.1=0;
b=0;
}
Подробнее:
if (b>4)
Если значение переменной b больше четырех, то выполняем следующий код:
if (PINB.1==0)PORTB.1=1;
else PORTB.1=0;
Если состояние порта BP1 равно нулю, то делаем единицу, если нет, то ноль.
Это условие и команда отрабатывает кроткое нажатие кнопки. Если нажата кнопка, то начинает работать инкремент b++; значение которого начинает увеличиваться. Если отжать кнопку и при этом значение переменной b будет больше четырех ( но меньше ста — а то сработает длинное нажатие) то состояние выходного порта PB1 (он же выход 1 на схеме, он же PORTB.1 в коде) поменяется, сработает алгоритм короткого нажатия кнопки.
Если значение переменной b при отжатии меньше четырех, то условие не срабатывает и ничего не происходит. необходимо для работы «дребезга контактов» и ложных срабатываний.
И последнее это присвоение переменной b нулевого значения, что бы обработка алгоритма короткого нажатия происходило единожды.
В оконцовке главного цикла виднеется команда:
delay_ms(10);
Это задержка в главном цикле. То есть, выполняется пошагово команды, затем процессор натыкается на команду delay_ms(10); и начинает её выполнять. В итоге процессор будет 10 миллисекунд ждать и ничего не делать в этой строчке, затем опять приступит к выполнению команд.
Находясь в одном общем цикле, скорость нарастания значения инкремента b++ зависит от времени задержки, указанной в delay_ms.
Команда delay_ms находится в библиотеке задержек #include <delay.h>, которую мы для этого и включили в начале кода.
Как видно из описания, длинное нажатие срабатывает от фронта сигнала нажатия кнопки ( начинает работать инкремент) а короткое нажатие кнопки — по спаду, то есть срабатывает по отжатию кнопки.
Вообще выполняемая здесь последовательность: условие + инкремент достаточно часто используемая команда и в языке Си присутствует отдельный оператор для этого for
Архив с прошивкой, исходником и моделью Протеуса:
umat.ru/files/Button_13.zip
ВНИМАНИЕ!
Архив перезалил 22 сентября 2014 года, обнаружил косяк в выставленной частоте в проекте. Теперь тактовая частота 1.2 Мегагерца, при этом фьюзы стоят по дефолту и их при прошивке трогать вообще не надо
Пример реализации схемы rusgg
www.drive2.ru/cars/gaz/ga…usgg/journal/570874/#post
Работа с микроконтроллерами: прошивка программатором и чистый «Си»
В этой статье я расскажу о том, как программировать микроконтроллеры без использования Arduino. Мы будем использовать программатор AvrISP STK500 для программирования контроллера ATtiny84.
Нам понадобится

Подключаем питание
Arduino мы не используем, поэтому обо всем нам придется думать самостоятельно. И первое, с чем необходимо разобраться — питание. Мы будем использовать преобразователь L7805, обладающей следующими характеристиками:
-
Выходной ток до 1.5 А
-
Выходное напряжение — ровные 5 В
-
Защита от перегрева
-
Защита от короткого замыкания
Теперь нам надо узнать схему подключения этого преобразователя. Ее мы найдем на странице 3 даташита.

Помимо самого преобразователя, мы видим еще 2 конденсатора — входной Сi и выходной Сo. Входной конденсатор необходим для того, чтобы сгладить пульсации на входе в случае удаленности L7805 от источника. В нашем случае длина соединительных проводов не будет превышать 15 см, поэтому входного конденсатора у нас не будет. Зато будет выходной, поскольку мы хотим «кормить» наш контроллер стабильным питанием.
Распиновка
Необходимо знать назначение ножек преобразователя. Это описано на 2-й странице даташита.

Схема
С учетом всего вышеописанного, получается схема для организации питания.

Программатор
В качестве программатора мы использовали AvrISP STK500 от Seeed Studio. Для его работы под Windows и Mac OS необходимы драйверы. Их можно скачать с официального сайта. Пользователям Linux устанавливать ничего не нужно — программатор будет сразу готов к работе.
Подключение к контроллеру
Распиновка разъема программатора такова:

Важно!
Это распиновка разъема программатора, если смотреть на него сверху (отверстиями от себя). Не перепутайте!

Разъем программатора необходимо подключить к микроконтроллеру. Можно использовать как 10-пиновый разъём, так и 6-пиновый. Без разницы. Соединим проводами соответствующие пины, т.е:
| 10-пиновый ICSP | ATtiny84 | |
|---|---|---|
| Reset | 5 | 4 |
| MOSI | 1 | 7 |
| MISO | 9 | 8 |
| SCK | 7 | 9 |
Прошивка
Напишем код прошивки на чистом «C», которая заставит светодиод мигать. Использование ШИМ-сигналов и считывание аналоговых сигналов на чистом «C» не так тривиальна, и может являться темой отдельной статьи, поэтому остановимся пока на простейшем примере.
- blink.c
-
#include <avr/io.h> #include <util/delay.h> int main(void) { // номер пина 2 в порту А -- на выход DDRA = 1 << 2; // основной цикл while (1==1) { _delay_ms(500); // задержка 500 мс PORTA ^= 1 << 2; // инвертирование значения на выводе } return 0; }
После скетчей Arduino, код малопонятен, правда? Ничего, сейчас я объясню, что да как.
В первых двух строчках мы подключаем необходимые библиотеки, чтобы воспользоваться такими штуками, как DDRA, PORTA, _delay_ms.
Что же такое DDRA?
Это регистр микроконтроллера, управляющий направлением работы порта А. Он содержит в себе 8 бит. Если установить какой-то бит в 1, то пин с соответствующим номером станет выходом.
PORTA — тоже регистр, но он содержит в себе данные порта А. Если мы хотим на вывод номер 2 записать логическую единицу, то мы должны поместить 1 в соответсвующий бит регистра.
А _delay_ms — функция задержки.
Исходя из этого можно составить таблицу соответствия:
| Arduino | C | |
|---|---|---|
| Направление | pinMode(led, OUTPUT); |
DDRA = 1 << 2; |
| Значение | digitalWrite(led, HIGH); |
PORTA = 1 << 2; |
| Задержка | delay(1000); |
_delay_ms(50); |
Однако, самым важным различием кода является то, что в программе на С нет разделений функций setup и loop. За все это отвечает функция int main(void). И она выполняется всего 1 раз! А мы хотим, чтобы наш светодиод моргал не один раз, а постоянно. Как раз для этого и используется бесконечный цикл while (1==1).
Поэтому легко сделать вывод, что этот цикл и есть аналог функции loop() в Arduino. А то, что до него — аналог функции setup().
Далее начинается самое интересное. Нам нужно скомпилировать и загрузить прошивку. Однако, в зависимости от вашей операционной системы, методика будет различаться.
Mac OS X
Первым делом необходимо скачать и установить CrossPack for AVR Development. Это даст нам все необходимые инструменты.
CrossPack состоит из двух частей.
-
AVR Libc — a C library for GCC on AVR microcontrollers
-
AVRDUDE — AVR Downloader/Uploader
Первая нам нужна для написания кода и создания файла прошивки, а вторая — для заливки прошивки в контроллер.
Проект создается в три шага.
-
Запустите терминал
-
Перейдите в нем в нужную папку
-
Создайте проект с помощью команды
avr-project
$ mkdir ~/AVR $ cd AVR $ avr-project firstProject Using template: /usr/local/CrossPack-AVR-20130212/etc/templates/TemplateProject
В результате будет создано следующее дерево файлов.
$ tree . |-- firmware | |-- main.c | `-- Makefile `-- firstProject.xcodeproj 1 directory, 3 files
На данном этапе нас интересует содержимое файла Makefile. В нем содержится информация о том, что вообще мы используем: какой контроллер, программатор. Это все описывается в строках с 20 по 24:
DEVICE = atmega8 CLOCK = 8000000 PROGRAMMER = #-c stk500v2 -P avrdoper OBJECTS = main.o FUSES = -U hfuse:w:0xd9:m -U lfuse:w:0x24:m
Пройдемся по строкам:
-
DEVICEсодержит в себе название контроллера, который мы программируем -
CLOCK— частота работы -
PROGRAMMER— используемый программатор -
OBJECTS— какие объектные файлы будут сгененрированы -
FUSES— конфигурация fuse-битов в микроконтроллере
Это автосгенерированный make-файл, поэтому нам необходимо вручную его подправить. Править будем строку DEVICE у нас же микроконтроллер attiny84 и строку FUSES. А вот с ней все сложнее.
Fuse-биты, или просто «фьюзы» — два (иногда три) особых байта, в которых содержится фундаментальая конфигурация работы контроллера. Очень важно правильно их задать.
Внимание!
Задание неверных fuse-битов может привезти к тому, что микроконтроллер перестанет работать и вернуть его к нормальной жизни может быть либо очень сложно либо невозможно!
Воспользеумся сайтом AVR Fuse Calcuator.
Сначала из выпадающего списка выберем нужный нам контроллер (ATtiny84).

И затем укажем необходимые опции, которые нам нужны. Сейчас для нас важны 2 вещи: сохранение возможности прошивать контроллер через SPI и сохранение его работоспособности без внешнего резонатора, поэтому выбираем соответствующие пункты, а остальные оставляем по умолчанию.

Видим, как поменялись сгенерированные значения.

Внесем изменения в Makefile.
DEVICE = attiny84 CLOCK = 8000000 PROGRAMMER = -c stk500v2 -P /dev/tty.usbserial OBJECTS = main.o FUSES = -U lfuse:w:0xe2:m -U hfuse:w:0xdf:m -U efuse:w:0xff:m
Прошивка
Она происходит в 2 этапа.
Сначала необходимо перейти в папку firmware и выполнить команду make. Если ошибок нет, то результат выполнения команды будет таким:
$ make avr-gcc -Wall -Os -DF_CPU=8000000 -mmcu=attiny84 -c main.c -o main.o avr-gcc -Wall -Os -DF_CPU=8000000 -mmcu=attiny84 -o main.elf main.o rm -f main.hex avr-objcopy -j .text -j .data -O ihex main.elf main.hex avr-size --format=avr --mcu=attiny84 main.elf AVR Memory Usage ---------------- Device: attiny84 Program: 126 bytes (1.5% Full) (.text + .data + .bootloader) Data: 0 bytes (0.0% Full) (.data + .bss + .noinit)
Эта команда сделает из нашего исходника main.c файл, пригодный для заливки в контроллер — main.hex.
Второй этап — как раз заливка прошивки. Делается это с помощью команды make flash. Ее нормальный вывод выглядит следующим образом:
- make-flash-result
-
$ make flash avrdude -c stk500v2 -P /dev/tty.usbserial -p attiny84 -U flash:w:main.hex:i avrdude: AVR device initialized and ready to accept instructions Reading | ################################################## | 100% 0.01s avrdude: Device signature = 0x1e930c avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed To disable this feature, specify the -D option. avrdude: erasing chip avrdude: reading input file "main.hex" avrdude: writing flash (126 bytes): Writing | ################################################## | 100% 0.10s avrdude: 126 bytes of flash written avrdude: verifying flash memory against main.hex: avrdude: load data flash data from input file main.hex: avrdude: input file main.hex contains 126 bytes avrdude: reading on-chip flash data: Reading | ################################################## | 100% 0.08s avrdude: verifying ... avrdude: 126 bytes of flash verified avrdude: safemode: Fuses OK avrdude done. Thank you.
Все, прошивка контроллера завершена.
Windows
Здесь все проще.
Первым делом необходимо скачать и уствновить среду разработки для AVR — Atmel AVR Studio 4. А вторым — Atmel AVR Toolchain.
После запуска среды, необходимо создать новый проект.

Затем указать имя, расположение и то, что мы хотим использовать С (GCC).

Третий шаг — настройка отладчика.

На этом все, проект готов к использованию.
Теперь необходимо написать и сохранить исходник, который мы уже обсудили.
В результате общий вид среды разработки выглядит вот так:

Теперь необходимо подключиться к программатору. Делается это с помощью нажатия на кнопку con.

В качестве Platform выбираем STK500, а в Port — Auto. Затем нажимаем Connect.

Если все правильно, то в открывшемся окне выбираем вкладку Main и нажимаем в ней на кнопку Read Signature.

Строка Reading signature from device .. 0x1E, 0x93, 0x0C .. OK! говорит о том, что все хорошо и сигнатура успешно прочиталась. Сигнатура — это своего рода позывной микроконтроллера, которым он сообщает собственную модель.
Это окно нельзя закрывать, иначе соединение с программатором будет потеряно. Просто сверните его.
Теперь нажмем Build → Build. Это заставит программу скомпилироваться.
Прошьем контроллер с помощью кнопки Write Flash Memory Using Current Settings — это заставит скомпилированную программу загрузиться в память микроконтроллера.

Заключение
Мы собрали простейшее устройство мигалку, но сделали это на низком уровне. С использованием программатора и «продвинутой» среды разработки, а не Arduino.

Разобравшись в премудростях программирования микроконтроллеров на чистом «Си», вы сможете выжимать из них максимум возможности, затрачивая при этом минимум места и денег.
Из песочницы, Программирование микроконтроллеров
Рекомендация: подборка платных и бесплатных курсов монтажа видео — https://katalog-kursov.ru/
В статье я хотел бы описать шаги на пути к написанию прошивки для микроконтроллеров stm32 без использования специальных сред разработки типа keil, eclipse и тому подобных. Я опишу подготовку прошивки с самых основ, начиная с написания загрузчика на ассемблере, скрипта для линкера и заканчивая основной программы на C. В коде на C буду использовать заголовочные файлы из CMSIS. Редактор кода может быть любым на ваш вкус, vim, emacs, блокнот, все что угодно. Для сборки проекта буду использовать утилиту make. Итак, начнем!
Почему так сурово, спросите вы. Во-первых, чтобы что-то хорошо освоить, необходимо начинать с основ. Я не хочу, чтобы мой читатель бездумно щелкал клавишами клавиатуры набирая текст очередной супер-программы для устройства, не понимая, как работает устройство. Stm32 гораздо более сложный микроконтроллер по сравнению, например с atmega8 — atmega328 (микроконтроллером, установленным на самой популярной плате серии arduino). Во-вторых, я люблю сам разбираться в любом деле с нуля, и можно сказать, данная статья — это заметки для меня в будущем, чтобы открыть и вспомнить некоторые нюансы.
Да, я забыл еще сказать, что разработку буду вести под Linux. Подойдет любой дистрибутив, например, у меня это Arch Linux. Для ubuntu процесс установки необходимых утилит я постараюсь описать в следующих частях. Можете попробовать Windows, MacOS, но для этого вам самим придется разобраться, как установить необходимые утилиты для компиляции и прошивки.
Первое, что вам нужно сделать, это приобрести плату для разработки на основе контроллера stm32f103. У меня это blue pill:
Еще одна вещь, необходимая для старта, это программатор st-link:
Плату blue pill и программатор я приобрел на aliexpress, заплатив 200 руб. за все вместе.
Второе, что необходимо сделать, это скачать набор для компиляции кода под arm GNU GCC.
Для arch linux необходимо поставить пакет gcc-arm-none-eabi:
yaourt -Syy arm-none-eabi-gcc
Далее нам понадобится утилита st-link для работы с одноименным программатором st-link2:
yaourt -Syy stlink
Теперь давайте попробуем подключить нашу плату к компьютеру через программатор.
Соединяем программатор с платой blue pill в таком порядке:
- Подключить к пину GND (ground — земля, пина два, возьмите любой, например, 4-й) на программаторе провод (желательно следовать некоторым стандартам, для земли используйте черный или синий) и подключите к пину на плате подписанному GND;
- Подключить пин SWCLK (clock — синхронизация) на программаторе к пину SWCLK на плате;
- Подключить пин SWDIO (IO — ввод/ввод) на прогамматоре к пину SWIO на плате;
- И наконец, пин 3,3V на программаторе соедините с пином 3,3V на плате.
Пока все очень просто. Теперь подключаем программатор в USB порт компьютера, открываем терминал и проверяем, что устройство успешно определилось в системе:
dmesg
При этом на самой плате загорится два диода, один красный должен гореть постоянно, что сигнализирует о том, что питание подается, второй зеленый, должен мигать. Это работает прошивка по-умолчанию.
Теперь давайте проверим характеристики нашей демо-платы. Для этого в терминале запускаем команду st-info из установленного до этого пакета stlink:
На выбор можем посмотреть:
—version — текущая версии утилиты st-info
—flash — выведет информацию о размере flash-памяти программ микроконтроллера, в моем случае это 0x10000 (65536 байт)
—sram — объем статической памяти — 0x5000 (20480 байт)
—descr — описание — F1 Medium-density device
—pagesize — размер страницы памяти — 0x400 (1024 байт)
—hla-serial — «x30x30x30x30x30x30x30x30x30x30x30x31»
—probe — Found 1 stlink programmers
serial: 303030303030303030303031
openocd: «x30x30x30x30x30x30x30x30x30x30x30x31»
flash: 65536 (pagesize: 1024)
sram: 20480
chipid: 0x0410
descr: F1 Medium-density device
Из важного для нас — размер flash-памяти и размер статической памяти, а также стоит запомнить что у нас устройство Medium-density.
Не следует начинать разработку без документации под рукой. Во-первых следует скачать с официального сайта Reference Manual. В нем полное описание всей периферии, регистров периферии микроконтроллера. Во-вторых, скачиваем Programmer Manual по той же ссылке. В нем узнаете о микропроцессоре семейства контроллеров STM32F10xxx/20xxx/21xxx/L1xxxx Cortex-M3, его архитектуре, наборе команд.
Далее разберем, с чего вообще начинается исполнение программы на микроконтроллере.
- Наш микроконтроллер stm32f103c8 сразу после включения начинает считывать по адресу 0x08000000 (для удобства чтения я буду делить тетрады пробелом — 0x0800 0000) значение для регистра SP. SP (Stack pointer) — регистр указателя стека (стр. 15 Programmer Manual). Стек начинается с конца доступной RAM-памяти и растет “вверх”;
- По адресу 0x0800 0004 считывает значение в регистр PC — Program counter. Это значение — адрес точки входа в нашу основную программу, другими словами по адресу 0x0800 0004 flash должен лежать адрес C — функции main(), определенной нами далее;
- Микроконтроллер начинает выполнение программы.
Чтобы вычислить начальное расположение стека (значение для SP регистра), обратимся к мануалу Reference Manual на стр. 65. Там указано, что RAM начинается с адреса 0x2000 0000. Ранее мы определили, что у микроконтроллера объем RAM 20480 байт:
0x2000 0000 + 0x5000 = 0x2000 5000
То есть по адресу 0x0800 0000 мы должны поместить значение 0x2000 5000.
По адресу 0x0800 0004 мы должны положить указатель на начало нашей программы. Каждый указатель имеет размер 4 байта, значит следующий адрес за 0x0800 0004 во flash памяти будет 0x0800 0004 + 4 = 0x0800 0008. Это значение и необходимо поместить по адресу 0x0800 0004.
Так будет выглядеть начальный участок нашей прошивки:
+-------------+-------------+
| Адрес flash | Значение |
+-------------+-------------+
| 0x0800 0000 | 0x2000 5000 |
| 0x0800 0004 | 0x0800 0008 |
+-------------+-------------+
Теперь об одной особенности микроконтроллеров stm32. Дело в том, что формат команд для stm32 должен быть в Thumb представлении вместо стандартного ARM. Это значит, что при указании указателей мы должны прибавлять 1. Запомните это правило.
Хватит теории, пора переходить к практике. Надеюсь, вы еще не спите. Открывайте ваш любимый редактор кода, будем писать начальный файл для запуска нашего контроллера. Мы начнем с startup файла и он будет написан на ассемблере. Это будет единственный раз, когда я заставляю вас писать на скучном ассемблере, зато вы начнете понимать и “чувствовать” устройство изнутри.
Пишем в самом начале:
@stm32f103
Это комментарий на языке ассемблера, каждый комментарий начинается с символа @.
Далее указываем директивы ассемблеру
.syntax unified
@тип команд для stm32 - Thumb!
.thumb
@семейство процессора микроконтроллера cortex-m3
@(в этом можно убедиться из мануала)
.cpu cortex-m3
И далее коротенькая bootstrap-программа:
@указатель на вершину стека. Пишем без пробелов!
@.equ директива ассемблера это почти
@тоже что и define в C или на худой конец
@думайте, что это обычное присваивание переменной
.equ StackPointer 0x20005000
@.word - указываем, что здесь машинное слово - 4 байта
@по сути отсюда (0x0800 0000) процессор
@начинает свою работу после включения
.word StackPointer
@”кладем” указатель на начало основной программы.
@Reset в данном случае - метка, адрес точки входа.
@не забываем о том, что у нас набор команд Thumb,
@поэтому к указателю прибавляем единицу
.word Reset + 1
@метка Reset. Здесь мы встречаем первую, настоящую и
@единственную команду, которая нам понадобится на
@протяжении всего руководства. Это команда B -
@безусловный переход в системе команд ARM,
@аналог JMP в ассемблере для x86, или простыми
@словами goto в языках более высокого уровня.
@Аргумент команды B - это адрес безусловного перехода, в нашем случае мы пока
@указываем метку Reset, тем самым заводим процессор в бесконечный цикл.
Reset: B Reset
Программу целиком вы можете скачать по ссылке https://bit.ly/2rc7bcf
Сохраните ее под названием bootstrap.s.
А теперь давайте скомпилируем и прошьем нашу плату.
Прежде всего я покажу, как скачать прошивку по-умолчанию с вашей платы, ту, которая мигает светодиодом. Вдруг когда-нибудь пригодится.
Снова вставляем программатор с подключенной платой в usb и запускаем в терминале Linux команду:
st-flash read ./default.bin 0x08000000 0x10000
Здесь мы указываем, что хотим прочитать в файл default.bin flash-память начиная с адреса 0x08000000 и размером 0x10000 (64K), то есть всю flash-память.
st-flash — утилита для работы с прошивкой микроконтроллера, полное описание ее читайте в терминале: st-flash --help.
После этого проверим, что прошивка корректно считалась. Загрузим ее вновь, перезаписывая старую.
st-flash write ./default.bin 0x08000000
Что означает записать default.bin в flash память контроллера начиная с адреса 0x08000000.
Выньте и снова вставьте программатор, на плате зеленый диод должен как и раньше мигать.
Теперь давайте скомпилируем нашу самописную прошивку. В терминале в той же директории, что и сохранили запустите:
arm-none-eabi-as -o bootstrap.o bootstrap.s
Здесь мы компилируем наш исходный файл в объектный код. Это еще не готовая прошивка, годная для заливки в микроконтроллер. Нам необходимо еще “указать” куда, по каким адресам размещать нашу программу. Этим занимается компоновщик. Мы воспользуемся самым популярным компоновщиком LD, который входит в поставку пакета arm-none-eabi-gcc. Подробнее о компоновщике и описание скрипта для компоновщика ld я расскажу в следующей части, когда мы перейдем к автоматической сборке нашей супер-простой прошивки. А пока просто скачайте этот маленький файл stm32f103.ld https://bit.ly/2HXIydu, и выполните команду:
arm-none-eabi-ld -o main.elf -T stm32f103.ld bootstrap.o
Этой командой мы компонуем наш объектный файл с помощью скрипта stm32f103.ld, на выходе получаем elf файл.
Чтобы окончательно подготовить исполнимый elf файл к прошиванию, выполним последнюю команду:
arm-none-eabi-objcopy main.elf main.bin -O binary
Здесь мы преобразуем elf файл в чистый бинарный формат, пригодный для заливки в нашу плату.
Итак, наша первая программа для контроллера stm32 готова! Прошиваем!
st-flash write ./main.bin 0x08000000
Поздравляю! Теперь микроконтроллер обречен на вечное выполнение безусловного перехода. До следующей встречи!
Содержание
- 1 Создание проекта в Atmel Studio
- 2 Код программы в Atmel Studio
- 3 Компиляция программы
- 4 Загрузка прошивки в микроконтроллер
Решил начать осваивать микроконтроллеры (далее МК) AVR. Думал что все просто раз и прошил микроконтроллера, но не так все просто как казалось на первый взгляд. В процессе прошивки контроллера возникли ряд трудностей о которых я хочу описать в этом посте. Я сам начинающий в этом нелегком деле, поэтому если увидели ошибки или другие косяки то прошу сообщить.
В качестве среды разработки я взял Atmel Studio на мой взгляд она очень удобная и постоянно обновляется к тому же абсолютно бесплатная. Скачать ее можно с официального сайта Atmel. Тут думаю проблем никаких не возникнет, скачиваем устанавливаем запускаем и все и наслаждается кучей разных непонятных кнопочек)
Подопытным МК будет Atmega8. Для того чтобы ее прошить нужна будет ее распиновка выводов представлена ниже. Распиновку также можно посмотреть тут. Нам нужно понимать куда подключать программатор куда подавать напряжение.
Далее нам необходимо определиться с программатором которым будем шить МК. Я взял USBasp программатор, самый простой программатор. Они бывают с разными разъемами, какой взять не принципиально. Главное правильно подключить выводы MOSI, MISO, RST, SCK а также питалово VCC и GND к микроконтроллеру. Также нужно установить под этот программатор драйвера, без них ПК просто не поймет что это за устройство такое мы подключили. Ссылка на драйвера для программатора USBasp приложена в конце статьи.
После того как мы выбрали программатор и МК нам нужно их соединить) Вот схема подключения программатора и микроконтроллера
Создание проекта в Atmel Studio
Вывод AVCC подключать не обязательно, у меня камень прошивался спокойно и без этого провода. После того как мы подключили нашего подопытного и программатор. Приступаем к написанию программы и ее компиляции.
Запускам среду Atmel Studio и создаем новый проект.
И выбираем в списке устройств наш камень Atmega8
И пишем программу, в качестве примера возьмем код мигания светодиодом. Так же вот есть проект мигалки на микроконтроллере с 4-мя светодиодами.
Код программы в Atmel Studio
/*
* LED blink.c
*
* Created: 06.04.2020 21:31:26
* Author : Mudji
*/
#ifndef F_CPU
#define F_CPU 1000000UL // 1 MHz clock speed
#endif
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRC = 0xFF; //Nakes PORTC as Output
while(1) //infinite loop
{
PORTC = 0xFF; //Turns ON All LEDs
_delay_ms(1000); //1 second delay
PORTC= 0x00; //Turns OFF All LEDs
_delay_ms(1000); //1 second delay
}
}
Следует обратить внимание вот на первые 2 две сточки:
#ifndef F_CPU
#define F_CPU 1000000UL // 1 MHz clock speed
#endif
тут мы определяем частоту тактирования микроконтроллера. В нашем случаем это 1 МГц. И запоминаем это значение — оно нам еще пригодится.
Компиляция программы
Далее необходимо откомпилировать программу, для этого нажимаем Build -> Build Solution или просто нажимаем клавишу F7 . Если никаких ошибок не было то программа откомпилируется и в консоле появится сообщение что все у нас гуд нет никаких варнингов и ошибок.
И так поздравляю Вас с успешной компиляции программы. Далее нам нужно найти файл hex который появляется после компиляции программы. Его можно найти в папке с проектом, лежит он в папке , в моем случае папка проект Led blink и там в папке Debug находится наш долгожданный файл hex.
Загрузка прошивки в микроконтроллер
Остается только залить этот файл в память прошиваемого микроконтроллера. Для этого я использовал программу Khazama AVR Programmer на мой взгляд очень удобная программа.
Для загрузки прошивки в МК делаем следующее:
- Запускаем программу и устанавливаем состояние fuse битов. Что такое фьюз биты мы говорили ранее. Для того чтобы установить их сначала нужно считать, для это жмем Command->Fuses and Lock Bits и далее во всплывающем окне жмем Read All
Тут выставляем нужные нам Fuse биты Lock биты лучше пока не трогать если не знаете какой бит за что отвечает.
Нас интересуют биты CKSEL 0…3 они отвечают за выбор типа тактирования внешний кварц или внутренний RC генератор. В нашем случае частота 1 МГц и тактирование от внутреннего RC генератора. Поэтому пишем значение 0001.
После того как выставили Fuse биты нажимаем кнопку Write All после чего биты установятся в МК.
Ну и последний этап это загружаем hex файл сначала в буфер программы и далее заливаем в микроконтроллер.
После загрузки прошивки, микроконтроллер автоматом запуститься и начнет мигать светодиодом, который подключен к порту выводу порта C.
Опубликовано 2010-01-24 11:11:02 автором MRS
В этом учебном курсе по avr я постарался описать все самое основное для начинающих программировать микроконтроллеры avr. Все примеры построены на микроконтроллере atmega8. Это значит, что для повторения всех уроков вам понадобится всего один МК.
В этом учебном курсе будет рассказано и показано на простых примерах как:
- Начать программировать микроконтроллеры, с чего начать, что для этого нужно.
- Какие программы использовать для написания прошивки для avr, для симуляции и отладки кода на ПК,
- Какие периферийные устройства находятся внутри МК, как ими управлять с помощью вашей программы
- Как записать готовую прошивку в микроконтроллер и как ее отладить
- Как сделать печатную плату для вашего устройства
Для того, чтобы сделать первые шаги на пути программирования МК, вам потребуются всего две программы:
- Proteus — программа-эмулятор (в ней можно разработать схему, не прибегая к реальной пайке и потом на этой схеме протестировать нашу программу). Мы все проекты сначала будем запускать в протеусе, а потом уже можно и паять реальное устройство. Скачать можно здесь
- CodeVisionAVR — компилятор языка программирования С для AVR. В нем мы будем разрабатывать программы для микроконтроллера, и прямо с него же можно будет прошить реальный МК. Скачать можно здесь
После установки Proteus, запускаем егоОн нам предлагает посмотреть проекты которые идут с ним, мы вежливо отказываемся. Теперь давайте создадим в ней самую простую схему. Для этого кликнем на значок

в поле маска вводим название компонента, который мы хотим найти в библиотеке. Например, нам нужно добавить микроконтроллер mega8
в списке результатов тыкаем на mega8 и нажимаем кнопку ОК. У нас в списке компонентов появляется микроконтроллер mega8
Таким образом добавляем в список компонентов еще резистор, введя в поле маска слово res и светодиод led
Чтобы разместить детали на схеме, кликаем на деталь, далее кликаем по полю схемы, выбираем место расположения компонента и еще раз кликаем. Для добавления земли или общего минуса на схему слева кликаем «Терминал» и выбираем Ground. Таким образом, добавив все компоненты и соединив их, получаем вот такую простенькую схемку
Все, теперь наша первая схема готова! Но вы, наверное, спросите, а что она может делать? А ничего. Ничего, потому что для того, чтобы микроконтроллер заработал, для него нужно написать программу. Программа — это список команд, которые будет выполнять микроконтроллер. Нам нужно, чтобы микроконтроллер устанавливал на ножке PC0 логический 0 (0 вольт) и логическую 1 (5 вольт).
Написание программы для микроконтроллера
Программу мы будем писать на языке С в компиляторе CodeVisionAVR. После запуска CV, он спрашивает нас, что мы хотим создать: Source или Project
Мы выбираем последнее и нажимаем кнопку ОК. Далее нам будет предложено запустить мастер CVAVR CodeWizard (это бесценный инструмент для начинающего, потому как в нем можно генерировать основной скелет программы) выбираем Yes
Мастер запускается с активной вкладкой Chip, здесь мы можем выбрать модель нашего МК — это mega8, и частоту, на которой будет работать МК (по умолчанию mega8 выставлена на частоту 1 мегагерц), поэтому выставляем все, как показано на скриншоте выше.
Переходим во вкладку Ports
У микроконтроллера atmega8 3 порта: Port C, Port D, Port B. У каждого порта 8 ножек. Ножки портов могут находиться в двух состояниях:
- Вход
- Выход
С помощью регистра DDRx.y мы можем устанавливать ножку входом или выходом. Если в
- DDRx.y = 0 — вывод работает как ВХОД
- DDRx.y = 1 вывод работает на ВЫХОД
Когда ножка сконфигурирована как выход, мы можем выставлять на ней лог 1 (+5 вольт) и логический 0 (0 вольт). Это делается записью в регистр PORTx.y. Далее будет подробно рассказано про порты ввода-вывода. А сейчас выставляем все, как показано на скриншоте, и кликаем File->Generate, Save and Exit. Дальше CodeWizard предложит нам сохранить проект, мы его сохраняем и смотрим на код:
#include <mega8.h> //библиотека для работы с микроконтроллером mega8
#include <delay.h> //библиотека для создания временных задержек
void main(void)
{
PORTB=0x00;
DDRB=0x00;
PORTC=0x00;
DDRC=0x01; // делаем ножку PC0 выходом
PORTD=0x00;
DDRD=0x00;
// Timer/Counter 0 initialization
TCCR0=0x00;
TCNT0=0x00;
// Timer/Counter 1 initialization
TCCR1A=0x00;
TCCR1B=0x00;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;
// Timer/Counter 2 initialization
ASSR=0x00;
TCCR2=0x00;
TCNT2=0x00;
OCR2=0x00;
// External Interrupt(s) initialization
MCUCR=0x00;
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x00;
// Analog Comparator initialization
ACSR=0x80;
SFIOR=0x00;
while (1)
{
};
}
Здесь вам может показаться все страшным и незнакомым, но на самом деле все не так. Код можно упростить, выкинув инициализацию неиспользуемых нами периферийных устройств МК. После упрощения он выглядит так:
#include <mega8.h> //библиотека для работы с микроконтроллером mega8
#include <delay.h> //библиотека для создания временных задержек
void main(void)
{
DDRC=0x01; /* делаем ножку PC0 выходом запись 0x01 может показаться вам незнакомой, а это всего лишь число 1 в шестнадцатиричной форме,
эта строка будет эквивалентна 0b00000001 в двоичной, далее я буду писать именно так.*/
while (1)
{
};
}
Всё хорошо. Но для того, чтобы светодиод замигал, нам нужно менять логический уровень на ножке PC0. Для этого в главный цикл нужно добавить несколько строк:
#include <mega8.h> //библиотека для работы с микроконтроллером mega8
#include <delay.h> //библиотека для создания временных задержек
void main(void)
{
DDRC=0x01; /* делаем ножку PC0 выходом запись 0x01 может показаться вам незнакомой, а это всего лишь число 1 в шестнадцатиричной форме,
эта строка будет эквивалентна 0b00000001 в двоичной, далее я буду писать именно так.*/
while (1)//главный цикл программы
{// открывается операторная скобка главного цикла программы
PORTC.0=1; //выставляем на ножку 0 порта С 1
delay_ms(500); //делаем задержку в 500 милисекунд
PORTC.0=0; //выставляем на ножку 0 порта С 0
delay_ms(500); //делаем задержку в 500 милисекунд
};// закрывается операторная скобка главного цикла программы
}
Все, теперь код готов. Кликаем на пиктограму Build all Project files, чтобы скомпилировать (перевести в инструкции процессора МК) нашу программу. В папке Exe, которая находится в нашем проекте, должен появиться файл с расширением hex, это и есть наш файл прошивки для МК. Для того, чтобы нашу прошивку скормить виртуальному микроконтроллеру в Proteus, нужно два раза кликнуть на изображении микроконтроллера в протеусе. Появится вот такое окошко
кликаем на пиктограму папки в поле Program File, выбераем hex — файл нашей прошивки и нажимаем кнопку ОК.
Теперь можно запустить симуляцию нашей схемы. Для этого нажимаем кнопку «Воспроизвести» в нижнем левом углу окна Протеус.
Комментарии — (6)
-
yu говорит:
Здравствуйте! Вот прочитал Вашу статью и решил проверить, но разочаровался…. Сделал все как написано, но ничего не заработало. При этом никаких ошибок обе программы не выдали. В чем может быть проблема? Возможно, такое, что Вы допустили ошибку? Прошу проверьте, пожалуйста, код или еще лучше залейте файлы работоспособного проекта. Спасибо за внимание!
-
Admin говорит:
Здравствуйте. У меня все правильно проверил.
-
Андрей говорит:
Здравствуйте сделал все как у Вас после запуска программы сигнал доходит до резистора а дальше не поступает на светодиод. Объясните почему?
-
Admin говорит:
Здравствуйте. На ножке микроконтроллера 5в устанавливается?
-
Филипп говорит:
У меня тоже не мигает.
Ошибка в том, что сопротивление резистора устанавливается как 10к по умолчанию.
На вашем примере просто 10.
Когда я у себя убрал букву «К» то диод замигал -
Admin говорит:
Все правильно, от 10к ему не хватает тока чтобы засветится, у меня стоит просто 10, это 10 ом
Добавить комментарий
Для отправки комментария вы должны авторизоваться.
Ну вот, писать программы для микроконтроллеров мы научились. Работоспособность прошивки тоже проверили, пусть и виртуально.
Пора переходить на следующую ступень. Раньше мы их боялись, а теперь — они нас!
Будем шить, шить и еще раз шить!!!
Камрад, рассмотри датагорские рекомендации
🌼 Полезные и проверенные железяки, можно брать
Опробовано в лаборатории редакции или читателями.
Список всех частей:
Грызём микроконтроллеры. Урок 1. Моргаем 8-ю светодиодами. CodeVision, Proteus, ISIS
Грызём микроконтроллеры. Урок 2. CodeVision и С
Грызём микроконтроллеры. Урок 3. Циклы, прерывания и массивы
Грызём микроконтроллеры. Урок 4. Мерим температуру или напряжение
Грызём микроконтроллеры. Урок 5. Кодовый замок
Грызём микроконтроллеры. Урок 6. Прошиваем МК
Грызём микроконтроллеры. Урок 7. Подключение к МК кнопок, клавиатуры, энкодера
Грызём микроконтроллеры. Урок 8. Программирование кнопок, клавиатуры, энкодера
Грызём микроконтроллеры. Урок 9. Клавиатура вглубину
Микроконтроллеры корпорации Atmel имеют очень удобный, особенно нам, изобретателям, интерфейс программирования.
Называется он Serial Programming Interface (SPI), или, интерфейс последовательного программирования.
Причем, большинство контроллеров серии AVR поддерживают режим ISP (In System Programming) — Внутресхемное программирование.
Т.е., программировать мы можем уже впаянный в наше устройство микроконтроллер.
Но чтобы воспользоваться всеми этими возможностями, нам нужен программатор…
В интернете можно найти немало схем, но нам будет достаточно самой простейшей, тем более, что CVAvr ее поддерживает
Называется эта схема «Пять проводков». Почему? Да потому, что пять проводов, подключенных к LPT порту компьютера и будут простейшим программатором.
Резисторы можно и не ставить, но без них можно спалить выходы LPT порта (не лучший исход!), что приведет к необходимости сборки более сложного программатора, или необходимости покупи новой материнской платы.
А еще лучше собрать программатор с буферной микросхемой. Это сохранит ваши LPT порт и нервы
Хотя я, в силу своей лени, так его и не собрал…
А на плате нашего устройства предусматриваем разъем для подключения этого самого программатора. К каким выводам МК всё это подключать смотрим в даташите, в разделе Memory Programming ->SPI.
После сборки программатора и нашего устройства, переходим к самому процессу прошивки.
Создаем новую или открываем готовую программу, компилируем.
В меню выбирам команду
И устанавливаем тип программатора STK200+/300
Теперь жмем кнопочку
Открылось окно, в котором уже любезно выбран наш тип микроконтроллера.
Обратите внимание на правую часть окна
Это фьюз биты. Они отвечают за настройку основных режимов работы микроконтроллера.
К примеру, фьюзы CKSEL и CKOPT отвечают за выбор тактового генератора микроконтроллера, а SUT — определяет время, необходимое для установления четкого тактового сигнала.
Для начала очень советую отключить галочку «Program Fuse Bit(s)», во избежание неприятных последствий. И по даташиту на ваш микроконтроллер изучить назначение каждого фьюза!
Вот таблица для настройки фьюз-битов для разных тактовых генераторов.
ОЧЕНЬ ВАЖНО!
В таблице «1» означает НЕ запрограммированный бит! В CVAvr это означает СНЯТУЮ галочку. И вообще, если говорят, что фьюз запрограммирован, то это значит, что он равен НУЛЮ, а если НЕ запрограммирован, то он равен ЕДИНИЦЕ. Значение каждого вьюза лучше проверить десять раз, иначе потом будет много проблем!
Теперь, чтобы запрограммировать конроллер нам достаточно нажать кнопку
Или, можно вручную очистить память МК
И прошить туда программу
Вот и всё! Отключаем программатор и наслаждаемся результатом
Схемы программаторов и таблица настройки фьюзов взяты с сайта
//avr123.nm.ru/
.
Советую почитать! Там можно найти много полезной информации по микроконтроллерам!

























