В прош­лой статье мы крат­ко прош­лись по архи­тек­туре и инс­тру­мен­там раз­работ­ки для ATmega 2560, написа­ли прос­тень­кую прог­рам­мку и даже про­шили ее в кон­трол­лер. Но, как ты понима­ешь, Hello world — это толь­ко цве­точ­ки, поп­робую угос­тить тебя ягод­ками. Сегод­ня в меню: пре­рыва­ния, работа с EEPROM, работа с UART и дис­крет­ными вхо­дами.

warning

Ре­дак­ция и автор не несут ответс­твен­ности за воз­можный при­чинен­ный вред здо­ровью и иму­щес­тву при несоб­людении тех­ники безопас­ности работы с элек­троп­рибора­ми.

 

Постановка задачи

Ме­сяц назад мы сде­лали малень­кий main, который зас­тавлял весело мор­гать све­тоди­од на пла­те. В этот раз мы пой­дем даль­ше, наш про­ект будет соот­ветс­тво­вать сле­дующим тре­бова­ниям:

  • ус­трой­ство дол­жно иметь два режима инди­кации: режим ожи­дания и «ава­рий­ный»;
  • ус­трой­ство дол­жно перехо­дить в ава­рий­ный режим, если зам­кну­лись опре­делен­ные кон­такты на пла­те, и воз­вра­щать­ся обратно при их раз­мыкании;
  • поль­зователь дол­жен иметь воз­можность нас­тро­ить инди­кацию по сво­ему вку­су.

Так как сухая пос­танов­ка задачи скуч­на, мы при­дума­ем жиз­ненную ситу­ацию. Нап­ример, пока ты сидишь и смот­ришь кино в науш­никах, в холодиль­ник на кух­не про­ника­ет неиз­вес­тный враг и похища­ет отту­да кол­басу. Никог­да не стал­кивал­ся с таким? А ведь это очень воз­можно, и нуж­но быть готовым ко все­му заранее! Нуж­на сиг­нализа­ция. Для ее реали­зации дверь холодиль­ника обо­рудуй кон­так­тным или маг­нитным дат­чиком (нап­ример, ИО-102-16/2, но на кус­тарном уров­не сго­дят­ся и два про­вода витой пары, прик­леен­ные скот­чем так, что­бы при зак­рытой двер­це холодиль­ника они замыка­лись), Arduino положи в ком­нате на вид­ном мес­те, заведи про­вода от дат­чика к Arduino по сле­дующей схе­ме (какой кон­крет­но про­вод куда под­клю­чать — зна­чения не име­ет):

  • один про­вод на колод­ке DIGITAL на любой из кон­тактов GND;
  • вто­рой про­вод на колод­ке DIGITAL на кон­такт 43.
 

План решения задачи

Что­бы отсле­живать, зам­кну­ты ли про­вода, необ­ходимо пери­оди­чес­ки опра­шивать сос­тояние вход­ного сиг­нала на мик­рокон­трол­лере.

В задаче ука­зано, что поль­зователь дол­жен иметь воз­можность нас­тра­ивать инди­кацию. Вос­поль­зуем­ся памятью EEPROM мик­рокон­трол­лера и при запус­ке будем читать отту­да нас­трой­ки. А для записи нас­тро­ек напишем неболь­шой инте­рак­тивный тер­минал, к которо­му мож­но под­клю­чить­ся любой тер­миналь­ной прог­раммой по пор­ту RS-232. Вста­ет воп­рос: где взять RS-232 на Arduino и на компь­юте­ре? Все очень прос­то, если ты не потерял схе­му пла­ты Arduino. USB-разъ­ем пла­ты Arduino заведен на UART0 мик­рокон­трол­лера через мик­росхе­му‑пре­обра­зова­тель USB — COM (на самом деле, как ты пом­нишь, там сто­ит ATmega16U, он‑то и игра­ет роль это­го пре­обра­зова­теля).

Ин­дикацию из основно­го цик­ла прог­раммы при­дет­ся уби­рать, потому что вре­мя обра­бот­ки команд у нас неп­ред­ска­зуемо, а зна­чит, мы будем иметь проб­лемы с выдер­живани­ем вре­мени мор­гания све­тоди­одом.

info

В реаль­ной жиз­ни для ана­лиза сос­тояния вход­ных сиг­налов исполь­зуют аппа­рат­ные и прог­рам­мные филь­тры, так как мир неидеален и при раз­мыкании/замыка­нии кон­тактов воз­ника­ет так называ­емый дре­безг кон­такта.

 

Решение

Периферия

На­пом­ню, что ATmega 2560 явля­ется пред­ста­вите­лем SoC. Это озна­чает, что на одном крис­талле мик­рокон­трол­лер содер­жит раз­личную перифе­рию. Перечис­лю, что при­годит­ся для решения нашей задачи (пол­ный перечень, как всег­да, в докумен­тации):

  • тай­меры — основное их пред­назна­чение, как нес­ложно догадать­ся, — отсчи­тывать вре­мя и совер­шать какие‑то дей­ствия по резуль­татам этих изме­рений. Очень час­то тай­меры про­сят генери­ровать пре­рыва­ние при отме­рива­нии какого‑то крат­ного интерва­ла вре­мени для реали­зации часов или опре­деле­ния задер­жек;
  • UART — основной для неболь­ших и малень­ких мик­рокон­трол­леров канал свя­зи с внеш­ним миром;
  • GPIO — самый базовый класс перифе­рии, поз­воля­ющий нап­рямую работать с ногами мик­рокон­трол­лера.
 

Ввод/вывод

Настройка таймера

Нач­нем, как при­нято, с кон­ца :). Как ты понял, раз из основно­го цик­ла мы уби­раем управле­ние све­тоди­одом, то куда‑то его надо вста­вить. Самое логич­ное — это повесить обра­бот­чик пре­рыва­ния по тай­меру и в нем отсчи­тывать вре­мя зажига­ния или гашения све­тоди­ода, а так­же собс­твен­но зажигать/гасить све­тоди­од. При­веду неболь­шой кусочек кода, который ини­циали­зиру­ет тай­мер на генера­цию пре­рыва­ния один раз в 1 мс:

TCCR1A = 0x00;
TCCR1B = ( 1 << WGM12 ) | ( 1 << CS11 ) | (1 << CS10);
TCCR1C = 0x00;
TCNT1 = 0x0000;
OCR1A = 250;
OCR1B = 0x0000;
OCR1C = 0x0000;
TIMSK1 |= (1 << OCIE1A);

Что тут про­исхо­дит? Что­бы не занимать­ся неудоб­ными перес­четами в прог­рамме, про­ще нас­тро­ить тай­мер на сра­баты­вание раз в 1 мс. Тай­меры в ATmega — шту­ка кру­тая и име­ют кучу воз­можнос­тей, о которых ты можешь почитать в ману­але, я выб­рал режим работы тай­мера по срав­нению со сбро­сом ( 1 << WGM12 ). В этом режиме тай­мер начина­ет счи­тать с час­тотой clk/64 (( 1 << CS11 ) | (1 << CS10)), при дос­тижении зна­чения в регис­тре OCR1A (OCR1A = 250 – 250 так­тов) сра­баты­вает пре­рыва­ние и счет­чик начина­ет заново.

Пос­ле нас­трой­ки тай­мера выс­тавляй флаг раз­решения пре­рыва­ния по срав­нению Timer1 TIMSK1 |= (1 << OCIE1A).

Те­перь объ­являй обра­бот­чик пре­рыва­ния, выг­лядит это сле­дующим обра­зом:

ISR(TIMER1_COMPA_vect)
{
// Вызываем пользовательский обработчик прерывания по таймеру
user_timer_ISR();
}

Таблица векторов прерываний

Век­тор пре­рыва­ния — это не что иное, как адрес, на который мик­рокон­трол­лер переда­ет управле­ние при воз­никно­вении пре­рыва­ния. По это­му адре­су рас­полага­ется инс­трук­ция перехо­да в фун­кцию — обра­бот­чик пре­рыва­ния или (не обя­затель­но) инс­трук­ция воз­вра­та из пре­рыва­ния. Зачас­тую все сре­ды раз­работ­ки пред­лага­ют запол­нять по умол­чанию таб­лицу век­торов пре­рыва­ний инс­трук­цией воз­вра­та из пре­рыва­ния, что­бы при ошиб­ке во вре­мя раз­работ­ки (ты вклю­чил пре­рыва­ние или забыл / не пла­ниро­вал писать обра­бот­чик) выпол­нение прог­раммы вер­нулось в нор­маль­ное рус­ло. Таб­лица век­торов пре­рыва­ний рас­полага­ется по млад­шим адре­сам Flash мик­рокон­трол­лера. По нулево­му адре­су рас­полага­ется так называ­емый Reset-век­тор, на этот век­тор мик­рокон­трол­лер переда­ет управле­ние при стар­те или при перезаг­рузке.

Для уни­вер­саль­нос­ти я сде­лал вызов внеш­ней фун­кции, в нее ты потом можешь вынес­ти раз­личные вкус­ности, которые хочешь обра­баты­вать раз в мил­лисекун­ду. Я же пос­тавил мор­гание све­тоди­одом и ана­лиз сос­тояния дис­крет­ного вхо­да (то есть про­вер­ка на замыка­ние/раз­мыкание кон­так­тно­го дат­чика).

// Моргаем светодиодом
blink_led();
// Анализируем состояние дискретных входов
process_input();

Обработка состояния дискретного входа

Для начала надо ини­циали­зиро­вать GPIO мик­рокон­трол­лера, ска­зав ему, что ты хочешь исполь­зовать опре­делен­ные ноги как получа­тель вход­ных сиг­налов. Дела­ется это сле­дующим обра­зом:

// Включаем внутреннюю подтяжку к «1»
PORTL |= _BV(PL6);
// Настраиваем пин PL6 на вход
DDRL &= ~_BV(PL6);

Что такое внут­ренняя под­тяжка? Перек­лючив ногу на вход, ты ска­зал мик­рокон­трол­леру, что он дол­жен сни­мать сиг­нал с этой ноги. Но что делать, если нога «бол­тает­ся в воз­духе», то есть кон­так­тный дат­чик разом­кнут?

info

Су­щес­тву­ют мик­рокон­трол­леры, поз­воля­ющие делать под­тяжку к обо­им зна­чени­ям: или к 1, или к 0.

Да­вай опре­делим­ся для начала, какое сос­тояние будет счи­тать­ся зам­кну­тым. Я выб­рал замыка­ние ноги на GND. Сей­час объ­ясню почему.

Мик­рокон­трол­лер понима­ет толь­ко два сос­тояния дис­крет­ного вхо­да: к ноге при­ложе­но нап­ряжение (логичес­кая еди­ница) или нога зам­кну­та на зем­лю (логичес­кий ноль), все осталь­ное есть неоп­ределен­ность. То есть каж­дый раз, счи­тывая сос­тояние, ты не можешь точ­но ска­зать, какое же оно на самом деле, и мик­рокон­трол­лер может пос­читать его как за 0, так и за 1.

Для раз­решения этой дилем­мы исполь­зуют под­тяжки к 1 (через огра­ничи­тель­ный резис­тор замыка­ют на линию питания) или к 0 (замыка­ют ногу на зем­лю), то есть зада­ется зна­чение по умол­чанию. Таким обра­зом, если нога «висит в воз­духе», то, зная, к какому зна­чению у тебя под­тяжка, ты можешь точ­но под­твер­дить факт «нога в воз­духе» и получить впол­не опре­делен­ное зна­чение.

В ATmega 2560 (как и во всем семей­стве мик­рокон­трол­леров mega) сущес­тву­ет фун­кция внут­ренней под­тяжки к 1, то есть мик­рокон­трол­лер сам раз­реша­ет ситу­ацию «нога в воз­духе». Теперь ты зна­ешь ответ на воп­рос, зачем исполь­зовать сос­тояние «зам­кну­то» кон­так­тно­го дат­чика как замыка­ние на GND. Если нога «в воз­духе», то ты про­чита­ешь 1, если нога зам­кну­та на зем­лю, то ты про­чита­ешь 0.

Чте­ние сос­тояния дис­крет­ного вхо­да осу­щест­вля­ется чте­нием регис­тра PINx и мас­кирова­нием соот­ветс­тву­юще­го бита:

uint8_t cur_state = (PINL & _BV(PL6));

Даль­ше ана­лизи­руем сос­тояние и в зависи­мос­ти от него уста­нав­лива­ем нуж­ный режим:

// Изменилось, начинаем моргать в соответствии с новым режимом
if (cur_state == 0)
ch_blink_mode(wm_alarm);
else
ch_blink_mode(wm_normal);

warning

При работе с мик­рокон­трол­лером пос­тарай­ся убрать все метал­личес­кие пред­меты, что­бы пре­дот­вра­тить слу­чай­ное корот­кое замыка­ние и выход пла­ты из строя.

 

А поговорить?

Те­перь давай зай­мем­ся реали­заци­ей тер­минала, то есть орга­низу­ем обще­ние с мик­рокон­трол­лером. Как я уже упо­минал, исполь­зовать мы будем UART0. UART0 явля­ется перифе­рий­ным устрой­ством, и для ука­зания мик­рокон­трол­леру, что ты хочешь имен­но его, необ­ходимо про­извести некото­рые манипу­ляции, то есть нас­тро­ить ско­рость, про­вер­ку чет­ности, дли­ну сло­ва.

Для облегче­ния жиз­ни я сде­лал неболь­шой мак­рос (он стан­дар­тный, ничего инно­ваци­онно­го) для нас­трой­ки регис­тра ско­рос­ти:

#define UART_CALC_BR( br ) ( ( uint16_t )( ( F_CPU / ( 16UL * (br) ) ) - 1 ) )

Нас­тро­им сам USART0.

uint16_t br = UART_CALC_BR(9600);
// Настройка скорости обмена
UBRR0H = br >> 8;
UBRR0L = br & 0xFF;
// 8 бит данных, 1 стоп-бит, без контроля четности
UCSR0C = ( 1 << USBS0 ) | ( 1 << UCSZ01 ) | ( 1 << UCSZ00 );
// Разрешить прием и передачу данных
UCSR0B = ( 1 << TXEN0 ) | ( 1 << RXEN0 ) | (1 << RXCIE0 );

От­дель­но про­ком­менти­рую пос­леднюю стро­ку. В ней мы вклю­чаем передат­чик 1 << TXEN0, при­емник 1 << RXEN0 и вклю­чаем пре­рыва­ние по при­ему бай­та 1 << RXCIE0. Обра­щаю твое вни­мание, что всю работу я переки­дываю на пре­рыва­ния. Таким обра­зом я раз­гру­жаю основной цикл прог­раммы от необ­ходимос­ти кон­тро­лиро­вать наличие оче­ред­ного бай­та в при­емни­ке UART, так как это чре­вато либо про­пус­ком бай­та (пока ты занимал­ся дру­гими делами, приш­ло 2 бай­та), либо ожи­дани­ем оче­ред­ного бай­та, что оста­новит работу основно­го кода. Для при­ема/переда­чи UART вос­поль­зуем­ся буфера­ми FIFO по одно­му на каж­дое нап­равле­ние.

Не подскажете, как пройти в библиотеку?

Раз­работ­чики стан­дар­тных биб­лиотек для встра­иваемых решений позабо­тились о приб­лижении этих биб­лиотек к стан­дарту С/С++. Клю­чевое сло­во тут «приб­лижение». Реали­зация мно­гих фун­кций дос­таточ­но уре­зана вви­ду огра­ничен­ности ресур­сов мик­рокон­трол­леров, а некото­рые зак­рыты заг­лушка­ми для сов­мести­мос­ти. Так, фун­кции по обра­бот­ке строк (sscanf, sprintf и подоб­ные) дос­таточ­но тре­бова­тель­ны к исполь­зованию сте­ка. При наличии на бор­ту 4 Кб опе­ратив­ной памяти это дос­таточ­но кри­тич­но. Поэто­му, если ты решишь исполь­зовать ту или иную фун­кцию, читай опи­сание к ней не в стан­дар­тных ману­алах, а в докумен­тации на кон­крет­ную биб­лиоте­ку.

Те­перь позабо­тим­ся об удоб­ном обме­не дан­ными. Так как у нас будет реали­зация тер­минала, я решил, что удоб­нее все­го исполь­зовать стан­дар­тные биб­лиотеч­ные фун­кции printf и fgets. Для того что­бы эти фун­кции зарабо­тали в том виде, как они задума­ны, необ­ходимо соз­дать свой поток и реали­зовать фун­кции отправ­ки и при­ема бай­та:

// Объявляем поток ввода/вывода, который будем использовать для перенаправления stdio
static FILE uart_stream = FDEV_SETUP_STREAM(uart_putc, uart_getc, _FDEV_SETUP_RW);

А так­же перенап­равить stdio в наш поток

stdout = stdin = &uart_stream;

Рас­смот­рим чуть бли­же при­ем бай­та из пор­та:

int uart_getc( FILE* file )
{
int ret;
// Ждем, пока появятся данные в FIFO, если там ничего нет
while(FIFO_IS_EMPTY( uart_rx_fifo ) );
__builtin_avr_cli(); // Запрещаем прерывания
ret = FIFO_FRONT( uart_rx_fifo );
FIFO_POP( uart_rx_fifo );
__builtin_avr_sei(); // Разрешаем прерывания
return ret;
}

Для того что­бы фун­кция gets работа­ла, при­ходит­ся ждать, пока оче­ред­ной байт пос­тупит в FIFO. При извле­чении байт из FIFO рекомен­дует­ся отклю­чать пре­рыва­ния, что­бы во вре­мя извле­чения бай­та не про­изош­ло пре­рыва­ние и один ресурс (FIFO) не начали исполь­зовать с двух сто­рон.

Зай­мем­ся отправ­кой бай­та.

int uart_putc( char c, FILE *file )
{
int ret;
__builtin_avr_cli(); // Запрещаем прерывания
if( !FIFO_IS_FULL( uart_tx_fifo ) ) {
// Если в буфере есть место, то добавляем туда байт
FIFO_PUSH( uart_tx_fifo, c );
// и разрешаем прерывание по освобождению передатчика
UCSR0B |= ( 1 << UDRIE0 );
ret = 0;
}
else {
ret = -1; // Буфер переполнен
}
__builtin_avr_sei(); // Разрешаем прерывания
return ret;
}

Вот тут встре­чает­ся инте­рес­ный момент: вклю­чение пре­рыва­ния по осво­бож­дению передат­чика. Так как не очень‑то хочет­ся вруч­ную скла­дывать бай­ты в передат­чик, то вос­поль­зуем­ся пре­рыва­нием по очис­тке передат­чика. Повесим на это пре­рыва­ние обра­бот­чик, который кла­дет в передат­чик оче­ред­ной байт из FIFO, если в FIFO что‑то есть:

ISR( USART0_UDRE_vect )
{
if( FIFO_IS_EMPTY( uart_tx_fifo ) ) {
// Если данных в FIFO больше нет, то запрещаем это прерывание
UCSR0B &= ~( 1 << UDRIE0 );
}
else {
// Иначе передаем следующий байт
char txbyte = FIFO_FRONT( uart_tx_fifo );
FIFO_POP( uart_tx_fifo );
UDR0 = txbyte;
}
}

Для завер­шения кар­тины при­веду код обра­бот­чика по при­ему бай­та:

ISR( USART0_RX_vect )
{
unsigned char rxbyte = UDR0;
if( !FIFO_IS_FULL( uart_rx_fifo ) ) {
FIFO_PUSH( uart_rx_fifo, rxbyte );
}
}

Отладка

Так как мы огра­ничи­ваем­ся толь­ко самими Arduino и не покупа­ем желез­ный отладчик, то реали­зован­ный класс работы с UART удоб­но исполь­зовать для отладки тво­ей прог­раммы. Прос­то в нуж­ных мес­тах встав­ляешь трейс‑вывод с помощью printf. Толь­ко не увле­кай­ся и пом­ни про пожира­ние сте­ка и памяти биб­лиотеч­ными фун­кци­ями. Для вывода зна­чения регис­тров и зна­чения перемен­ных в нуж­ных мес­тах это­го впол­не дос­таточ­но.

 

Хранение настроек

Вот мы и подош­ли к самому, с моей точ­ки зре­ния, любопыт­ному. Нас­трой­ки хотелось бы хра­нить даже пос­ле перезаг­рузки или отклю­чения питания устрой­ства. Для этой цели в мик­рокон­трол­лере есть EEPROM. Для работы с этой памятью при­сутс­тву­ет биб­лиоте­ка eeprom.h. Мож­но пой­ти аль­тер­натив­ным путем и реали­зовать запись/чте­ние самос­тоятель­но, это нес­ложно. Но если есть уже готовое решение, то пред­лагаю им и вос­поль­зовать­ся.

Итак, в арсе­нале име­ются фун­кции eeprom_read_byte, eeprom_write_byte, eeprom_read_block, eeprom_write_block. У EEPROM есть одна осо­бен­ность — она быва­ет занята, поэто­му раз­работ­чики биб­лиоте­ки (и в этом я к ним при­соеди­няюсь) рекомен­дуют вызывать eeprom_busy_wait или про­верять готов­ность фун­кци­ей eeprom_is_ready.

Так как софт для мик­рокон­трол­лера — это вещь авто­ном­ная, то нас­тоятель­но рекомен­дует­ся вся­чес­ки защищать нас­трой­ки кон­троль­ными бло­ками от слу­чай­ных или несан­кци­они­рован­ных изме­нений. В нашем при­мере я исполь­зую один кон­троль­ный байт на блок нас­тро­ек, который сиг­нализи­рует о том, что дан­ные мною были записа­ны. Соот­ветс­твен­но, если этот байт равен опре­делен­ному зна­чению (в нашем слу­чае это 0), то это озна­чает, что дан­ные в бло­ке вер­ны. Для безопас­ности перед записью этот байт я перево­жу в сос­тояние 1, пос­ле окон­чания записи воз­вра­щаю это зна­чение в 0". Дан­ные манипу­ляции нуж­ны для того, что­бы во вре­мя записи при вне­зап­ной перезаг­рузке мик­рокон­трол­лер не заг­рузил мусор из памяти, а взял нас­трой­ки по умол­чанию.

danger

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

 

3, 2, 1, поехали!

Итак, теперь про­шива­ем кон­трол­лер, запус­каем тер­минал

#minicom -D `ls /dev/serial/by-id/*arduino*` -c on -b 9600

Arduino выводит сооб­щение о стар­те и дает приг­лашение:

Started...
Arduino>

Для тер­минала я реали­зовал сле­дующий набор команд (help не реали­зовы­вал):

get <mode>
вывод настроенных временных интервалов для режима mode
<mode> := [norm|alarm]
norm — режим нормы
alarm — режим аварии
set <mode> <time_on1> <time_off1> ... <time_onN> <time_offN> [0]
установка (только запись в память) временных интервалов для режима mode
<mode> см. команду get
<time_on1> — задержка для зажженного светодиода первого периода
<time_off1> — задержка для погашенного светодиода первого периода
<time_onN> задержка для зажженного светодиода N-го периода
<time_offN> задержка для погашенного светодиода N-го периода
0 — завершает последовательность, необязательный параметр
Примечание: чтобы настройки вступили в силу, необходимо дать команду reload
reload
перечитывание и применение сохраненных настроек
eeprom
вывод EEPROM на экран

Пос­ле самого пер­вого запус­ка можешь пос­мотреть, что в EEPROM мик­рокон­трол­лера пус­то и взя­ты нас­трой­ки по умол­чанию. Отправь коман­ду eeprom и ищи адрес 0x100 — это стар­товый адрес. Начиная с это­го адре­са, идет 20 слов (по 2 бай­та) зна­чений задер­жек для сос­тояния нор­мы, за ними кон­троль­ный байт пер­вого бло­ка, пос­ле это­го 20 слов зна­чений задер­жек для сос­тояния ава­рии и кон­троль­ный байт вто­рого бло­ка.

Рис. 1. Просмотр EEPROM
Рис. 1. Прос­мотр EEPROM

Да­вай теперь изме­ним зна­чения для сос­тояния нор­мы:

Arduino> set norm 300 200 300 200 500 500 500 500
Writing new parameters
OK
Arduino>

Те­перь ска­жи Arduino, что­бы он перечи­тал нас­трой­ки:

Arduino> reload
Reloading settings
OK
Arduino>

За­мечу, что здесь ты не перезаг­рузил мик­рокон­трол­лер и про­читал нас­трой­ки при стар­те, а нас­трой­ки перечи­тались и при­мени­лись на лету. Теперь смот­ри на све­тоди­од Arduino, он стал мигать в соот­ветс­твии с вновь задан­ными нас­трой­ками.

Рис. 2. Просмотр EEPROM
Рис. 2. Прос­мотр EEPROM

Да­вай теперь заг­лянем в EEPROM и пос­мотрим, что там изме­нилось. Сно­ва давай коман­ду eeprom. Ты дол­жен уви­деть что‑то подоб­ное рисун­ке.

Рис. 3. Замкнули контакты
Рис. 3. Зам­кну­ли кон­такты

Ну а теперь самое вол­нующее. Возь­ми скре­поч­ку (у меня это кусочек зачищен­ной витой пары) и зам­кни кон­такты. Теперь Arduino стал мор­гать ава­рий­но. И сра­зу же воз­ника­ет воп­рос, ведь ава­рия дол­жна быть на раз­мыкание? Да, для отладки сос­тояния переме­шаны. Что­бы сде­лать боевую вер­сию, най­ди ана­лиз сос­тояния дис­крет­ного вхо­да и поменяй мес­тами режимы:

// Изменилось, начинаем моргать в соответствии с новым режимом
if (cur_state == 0)
ch_blink_mode(wm_normal);
else
ch_blink_mode(wm_alarm);
 

Заключение

Ис­поль­зование ATmega 2560 в реали­зации Arduino откры­вает боль­шой прос­тор для обу­чения прог­рамми­рова­нию мик­рокон­трол­леров, так как это дос­таточ­но мощ­ный с широким набором перифе­рии мик­рокон­трол­лер. Сегод­ня я бег­ло поз­накомил тебя с пре­рыва­ниями, тай­мерами, UART, GPIO и EEPROM, но это все­го лишь самая вер­шина айсбер­га под наз­вани­ем embedded development. Будь акку­ратен и вни­мате­лен, так как си + мик­рокон­трол­лер — это воз­можность не толь­ко прос­тре­лить себе ногу, но и уку­сить себя за локоть :).

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии