|
Мгновенно реагировать на нажатие кнопки. Attiny 2313, прерывания |
|
|
|
Mar 8 2013, 20:12
|
Частый гость
 
Группа: Участник
Сообщений: 114
Регистрация: 3-10-09
Пользователь №: 52 731

|
Учусь программировать под Attiny 2313, написал такую программку: Если нажать на кнопку, то запускается цикл мигания 6 светодиодов, если в конце мигания шестого светодиода нажать кнопку, то мигание выключится. Вот код для gcc: Код unsigned char LIGHT = 0; while (1) { // Если свет не горит и нажата кнопку if (LIGHT == 0 && bit_is_clear(PIND,0)) { // Включить свет while (LIGHT == 0) { PORTB = 0b11111110; _delay_ms(200); PORTB = 0b11111101; _delay_ms(200); PORTB = 0b11111011; _delay_ms(200); PORTB = 0b11110111; _delay_ms(200); PORTB = 0b11101111; _delay_ms(200); PORTB = 0b11011111; _delay_ms(200); // Если нажата кнопка, то выключаем свет if (bit_is_clear(PIND,0)) { PORTB = 0b11111111; _delay_ms(200); LIGHT = 1; } } } // Если свет выключен и нажата кнопка, то включаем свет if (LIGHT == 1 && bit_is_clear(PIND,0)) { LIGHT = 0; } } Работает как я и планировал, но есть одна недоработка. Как сделать так, чтобы нажав на кнопку в ЛЮБОЙ момент времени светодиоды остановились мигать (а не только в конце цикла мигания)? подскажите куда копать? если есть простой пример на Си буду рад его изучить.
Сообщение отредактировал aivs - Mar 8 2013, 20:14
|
|
|
|
2 страниц
1 2 >
|
 |
Ответов
(1 - 28)
|
Mar 9 2013, 08:19
|

Профессионал
    
Группа: Свой
Сообщений: 1 001
Регистрация: 27-06-06
Пользователь №: 18 409

|
Цитата(kovigor @ Mar 9 2013, 07:24)  По-научному это называется "машина состояний" ("state machine")", или "цифровой автомат". +1 Сам поначалу описал вариант с применением автомата состояний, но потом для простоты предложил заменить процедуру. Опишу ещё раз. Вводите переменную состояние устройства state которая будет принимать значения 0 - ожидание нажатия, 1 - исполнение цикла. button - флаг нажатия кнопки. timer - таймер для формирования требуемой задержки led_cnt - счётчик морганий Код switch(state) { case 0: if(button) { //переход на моргание по нажатию кнопки button = 0; state = 1; ledOn(); timer = _200mS; led_cnt = 12; } break;
case 1: if(button) { //переход в исходное состояние по нажатию кнопки button = 0; state = 0; ledOff(); timer = 0; }
if(timer == 0) {
if(led_cnt>0) { //инвертируем состояние светодиодов led_cnt--; ledInvert(); timer = _200mS; } else { //переход в исходное состояние т.к. отработали цикл button = 0; state = 0; ledOff(); timer = 0; }
} break;
default: //переход в исходное из неизвестного состояния автомата (это на всякий случай) button = 0; state = 0; ledOff(); timer = 0; break; } Запускаете такой автомат в вечном цикле. А в прерывании от таймера сканируете кнопку и выполняете обратный отсчёт переменной timer с самоостановом Код if(timer) timer--; Если ввести много состояний и определить условия перехода из одного в другой, то можете реализовывать различные световые эффекты или обеспечить некоторую световую индикацию. Я подобный образом формирую индикацию режимов работы своих устройств где в зависимости от состояния устройства(устройство спит, подключено к серверу, ищет сеть GSM и т.д.) светодиод зажигается/гасится на абсолютно разные интервалы времени.
|
|
|
|
|
Mar 9 2013, 11:13
|

I WANT TO BELIEVE
     
Группа: Свой
Сообщений: 2 617
Регистрация: 9-03-08
Пользователь №: 35 751

|
Могу предложить рабочий кусок кода с простеньким планировщиком задач и службой времени. Правда немного поправить наверно придется...это всё для меги8 писалось. Антидребезг также присутсвтует. Основной цикл будет выглядеть как-то так: Код void some_task() { //делаем что-то каждые 200ms }
void some_task2() { //делаем что-то каждые 50ms }
int main(void) { uint8_t last_keys, keys; timer0_init(); kbd_init(); //клавиатура опрашивается каждые 20 миллисекунд shed_add_task(kbd_upd_rawstate, 20); //делаем что-то каждые 200ms shed_add_task(some_task, 200); //делаем что-то каждые 50ms shed_add_task(some_task2, 50); last_keys = 0; while(1) { shed_upd_state(); shed_run_tasks(); keys = kbd_get_keys(); if ( (last_keys != keys) ) { if ( (keys & 0x2) ) { //делаем что-то по кнопке 2 } if ( (keys & 0x1) ) { //делаем что-то по кнопке 1 } last_keys = keys; } } } Выкладывать? Или сами птренеруетесь?
--------------------
The truth is out there...
|
|
|
|
|
Mar 11 2013, 13:43
|
Участник

Группа: Участник
Сообщений: 32
Регистрация: 24-11-07
Пользователь №: 32 633

|
Цитата(sigmaN @ Mar 10 2013, 02:56)  Вообще нет, надо отечественный четырехядерник ставить, в интеле закладки же могут быть! Можно кстати одним ядром клаву опрашивать, а вторым лампочки зажигать ) профит)) Вы забыли упомянуть, что это всё надо делать только под Линуксом, потому что микро-РТОС для таких задач совершенно не подходят, т.к. для них нет готовых коммуникационных стеков и драйверов! )
--------------------
Если друг оказался вдруг и не друг и не враг, а - JTAG.
|
|
|
|
|
Mar 13 2013, 12:19
|
Частый гость
 
Группа: Участник
Сообщений: 114
Регистрация: 3-10-09
Пользователь №: 52 731

|
Решил пока освоить прерывания INT0 и INT1 для решения своей задачи. Возник параллельный вопрос по функции _delay_ms() Она должна создавать задержку на указанное время в миллисекундах Но у меня какая то фигня выходит Если установить частоту МК 8Мгц Код #define F_CPU 8000000UL и вызвать задержку на 200 мс Код _delay_ms(200); то, задержка реально будет не 200 мс, а 1.6 сек. Т.е. МК выполняет функцию _delay_ms() в 8 раз дольше, чем я планировал Если установить частоту МК 20Мгц Код #define F_CPU 20000000UL и вызвать задержку на 200 мс Код _delay_ms(200); то, задержка реально будет не 200 мс, а 4 сек. Т.е. МК выполняет функцию _delay_ms() в 20 раз дольше, чем я планировал Что не так?
Сообщение отредактировал aivs - Mar 13 2013, 12:22
|
|
|
|
|
Mar 14 2013, 02:26
|
Местный
  
Группа: Участник
Сообщений: 298
Регистрация: 26-01-09
Из: Пермь
Пользователь №: 43 940

|
1) Да. Некоторые функции, в том числе и _delay_ms и _delay_us используют, этот макрос для расчета своих внутренних параметров. Я использую этот макрос , например, для расчета скорости USART и/или различных таймеров, что бы в случае изменения тактирования не пересчитывать все временные характеристики. 2) От внешнего кварца можно получить любые частоты. Берете справочник/каталог к-либо производителя кварца и подбираете то, что вам нужно. Для AVR-ок обычно используют из диапазона до 20 МГц. Вот некоторые стандартные частоты внешних кварцов (Мгц): 1; 1,8432; 2; 2,4576; 3,2768; и т.д. Частота кварца (и источник тактирования в целом) выбирается исходя из назначения устройства и выполняемых задач (пониженное энергопотребление, связь на стандартной скорости по USART, максимальная производительность, наличие номиналов кварца и т.д.). С помощью FUSE-бит Вы сообщаете железу в каком диапазоне находится ваша частота и какой источник используется. Например, от внешнего генератора кварц тактируется по одной схеме,а от резонатора - по другой. Собственно говоря с помощь FUSE-бит выбирается та или иная схема подключения/выработки тактовой частоты для ядра и периферии AVR. А с помощью F_CPU Вы сообщаете софту о точном значении частоты для расчета к-либо параметров различным функциям, в том числе и своим. Например, вот мой расчет UBRR в зависимости от частоты USART: Код // Установка скорости в асинхронном режиме работы static inline void usart_set_baudratea(double __pr) __attribute__((always_inline)); void usart_set_baudratea(double __pr) { unsigned int bauddiv = ( (F_CPU+((__pr)*8L))/((__pr)*16L)-1 ); UBRRH = bauddiv>>8; UBRRL = bauddiv; }
|
|
|
|
|
Mar 14 2013, 10:00
|
Местный
  
Группа: Участник
Сообщений: 298
Регистрация: 26-01-09
Из: Пермь
Пользователь №: 43 940

|
Цитата вообще говоря, есть в комплекте WinAVR готовый хидер util\setbaud.h - что с ним не так, что все тут свои функции и вычисления норовят делать? Когда я начинал работать с WinAVR, этого хидера еще не было. А после со своим как-то роднее......
|
|
|
|
|
Mar 16 2013, 10:43
|
Частый гость
 
Группа: Участник
Сообщений: 114
Регистрация: 3-10-09
Пользователь №: 52 731

|
Вопрос про прерывания. Решил для отслеживания нажатия кнопки использовать прерывание по таймеру, соответственно переопределил вектора прерываний:
;--------- Переопределение векторов прерываний start rjmp init ; 0x0000 Переход на начало программы reti ; 0x0001 Внешнее прерывание 0. reti - завершение подпрограммы обработки прерывания reti ; 0x0002 Внешнее прерывание 1 reti ; 0x0003 Таймер/счетчик 1, захват rjmp prtim1 ; 0x0004 Таймер/счетчик 1, совпадение канал А reti ; 0x0005 Таймер/счетчик 1, прерывание по переполнению reti ; 0x0006 Таймер/счетчик 0, прерывание по переполнению reti ; 0x0007 Прерывание UART прием завершение reti ; 0x0008 Прерывание UART регистр данных пуст reti ; 0x0009 Прерывание UART передача завершена reti ; 0x0010 Прерывание по компаратору reti ; 0x0011 Прерывание по изменению на любом контакте reti ; 0x0012 Таймер/счетчик 1, совпадение канал B reti ; 0x0013 Таймер/счетчик 0, совпадение канал B reti ; 0x0014 Таймер/счетчик 0, совпадение канал А reti ; 0x0015 USI готовность к старту reti ; 0x0016 USI переполнение reti ; 0x0017 EEPROM готовность reti ; 0x0018 Переполнение охранного таймера
Вектор прерывания по совпадению таймера 1 ведёт на подпрограмму prtim1. Два вопроса: 1) У меня в программе используется только прерывания по таймеру, значит по адресу 0x0004 должна быть инструкция перехода на подпрограмму обработки прерывания, если я вообще не опишу таблицу векторов прерываний, то при сработке прерывания выполнится инструкция по адресу 0x0004? 2) Как еще описывают таблицу векторов прерываний, без использования reti? Я так понимаю до адреса вызова "rjmp prtim1" обязательно должны быть какие нибудь nop ?
Сообщение отредактировал aivs - Mar 16 2013, 10:43
|
|
|
|
|
Mar 16 2013, 18:01
|

Гуру
     
Группа: Модератор FTP
Сообщений: 4 479
Регистрация: 20-02-08
Из: Москва
Пользователь №: 35 237

|
Цитата(aivs @ Mar 16 2013, 14:43)  Два вопроса: 1) У меня в программе используется только прерывания по таймеру, значит по адресу 0x0004 должна быть инструкция перехода на подпрограмму обработки прерывания, если я вообще не опишу таблицу векторов прерываний, то при сработке прерывания выполнится инструкция по адресу 0x0004? 2) Как еще описывают таблицу векторов прерываний, без использования reti? Я так понимаю до адреса вызова "rjmp prtim1" обязательно должны быть какие нибудь nop ? Обычно таблицу векторов прерываний не описывают, а компилятор формирует ее сам. Для того, что бы "встроиться" в эту таблицу (т.е. чтобы в нужное место был вставлен переход на требуемую процедуру), то ее либо называют специальным образом (т.е. когда за всеми процедурами обработки прерываний застолблены постоянные имена) и/или такие процедуры предваряются каким-то указанием компилятору (#pragma). Например, в компиляторе IAR EWAVR это выглядит так: Код #pragma vector=INT0_vect __interrupt void INT0_interrupt() { ... } - тут и #pragma стоит и функцию обычно так называют. Но, наверное, все-таки #pragma здесь важнее. Увидев функцию, оформленную таким образом, компилятор сделает следующие вещи: 1) САМ вставит в таблицу векторов прерываний переход на нее. Причем, не абы куда, а как раз в ячейку для прерывания INT0. 2) САМ оформит возврат из этой процедуры, как iret. 3) САМ сохранит в стеке флаг текущих арифметических операций и старые значения всех (мусорных) регистров, которые он внутри этой процедуры будет использовать, а в конце процедуры ВОССТАНОВИТ эти значения из стека. Всё это касается компилятора C/C++, тогда как на ассемблере всеми этими вещами должен озаботиться программист. Хотя именно поэтому в программировании на ассемблере есть своя неповторимая прелесть.
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|