Страница 1 из 13
Как поместить цикл в case оператора switch
Добавлено: Вт окт 12, 2021 01:09:16
uuu000
Необходимо при переключении кнопкой с помощью оператора switch включать мигание светодиода с различной частотой на каком либо выводе.при
использовании конструкции (для примера):
PORTB |= (1<<PB0);
_delay_ms(100);
PORTB&= ~(1<<PB0);
_delay_ms(100);
необходимо зациклить ее в теле case,но невозможно переключить switch в следующий case.Просто невозможно выйти из бесконечного цикла.
Я использовал while(),может быть есть какой либо способ решить эту проблему, я начал изучать программирование МК недавно и до сложных
кодов еще мне очень далеко. Надеюсь на помощь.
Спасибо.
Re: Как поместить цикл в case оператора switch
Добавлено: Вт окт 12, 2021 05:38:47
Starichok51
для реальной помощи положено приводить исходный код и схему.
а также нужно подробно и четко описать, что тебе надо и как это должно работать.
при предоставленном минимуме информации могут помочь только гадалки и экстрасенсы.
Re: Как поместить цикл в case оператора switch
Добавлено: Вт окт 12, 2021 10:22:30
parovoZZ
Ну какой код? Студент не понятно чем занимался, как вдург препод огорошил!
Re: Как поместить цикл в case оператора switch
Добавлено: Вт окт 12, 2021 11:01:14
uuu000
#include <avr/io.h>
#define F_CPU 8000000UL
#include <util/delay.h>
#define time 300
//attiny2313,8Mhz,ДЕЛИТЕЛЬ НА 8 ОТКЛЮЧЕН
void pause (unsigned long a)//Использую вместо стандартной задержки
{ unsigned long counter ;
for(counter=a;counter>0;counter--)
asm("nop");
}
int main(void)
{ uint8_t i=0;
DDRD = 0xFF;
PORTD=0x00;
DDRA = 0x00;
PORTA|=(1<<PA0);
while(1)
{
if (~PINA&(1<<PA0))
{
if (i<3)
{
i++;
}
else
{
i=0;
}
}
switch(i)
{
case 1:
PORTD|=(1<<PD0);
pause (10000L*time);
PORTD&=~(1<<PD0);
pause (10000L*time);
break;
case 2:
PORTD|=(1<<PD0);
pause (5000L*time);
PORTD&=~(1<<PD0);
pause (5000L*time);
break;
case 3:
PORTD|=(1<<PD0);
pause (1000L*time);
PORTD&=~(1<<PD0);
pause (1000L*time);
break;
}
_delay_ms(300);
}
}
При симуляции в Proteus зацикливается в первом case , т.е. не переключается в следующий .На макетной плате после первого нажатия кнопки светодиод горит постоянно примерно 5 сек(pause (10000L*time)
и потом гаснет ,дальнейшие манипуляции кнопкой просто повторяют эту картину(нет мигания светодиода).
Что касается студента - я вышел на пенсию и для поддержания здоровья занялся программированием МК, база у меня есть(физфак БГУ) и пока голова работает хочу нагружать ее насколько это возможно.
В марте мне будет 71.
Спасибо за Ваши комментарии, надеюсь на Вашу помощь.
Re: Как поместить цикл в case оператора switch
Добавлено: Вт окт 12, 2021 11:18:31
Карбофос
У вас переменная i принимает значения 0,1,2, а switch(i) 1,2,3.
Re: Как поместить цикл в case оператора switch
Добавлено: Вт окт 12, 2021 11:20:32
Reflector
У вас внутри switch одинаковые ветки отличающиеся только задержкой, проще ее хранить в массиве:
и потом просто подставлять:
Код: Выделить всё
if (~PINA & (1<<PA0))
{
if(++i >= 3) i = 0;
}
uint32_t delay = arr[i] * time;
PORTD|=(1<<PD0);
pause (delay);
PORTD&=~(1<<PD0);
pause (delay);
На AVR умножение наверно лучше делать один раз, после инкремента i, или сразу перемноженные значения хранить...
Re: Как поместить цикл в case оператора switch
Добавлено: Вт окт 12, 2021 11:48:04
uuu000
К сожалению не работает, если только я правильно понял идею с массивами:
#include <avr/io.h>
#define F_CPU 8000000UL
#include <util/delay.h>
#define time 300
//attiny2313,8Mhz,ДЕЛИТЕЛЬ НА 8 ОТКЛЮЧЕН
void pause (unsigned long a)
{ unsigned long counter ;
for(counter=a;counter>0;counter--)
asm("nop");
}
int main(void)
{
uint16_t arr[] = { 10000, 5000, 1000 };
uint8_t i=0;
DDRD = 0xFF;
PORTD=0x00;
DDRA = 0x00;
PORTA|=(1<<PA0);
while(1)
{
if (~PINA & (1<<PA0))
{
if(++i >= 3)
{
uint32_t delay = arr * time;
PORTD|=(1<<PD0);
pause (delay);
PORTD&=~(1<<PD0);
pause (delay);
}
else
i=0;
}
}
_delay_ms(300);
}
Re: Как поместить цикл в case оператора switch
Добавлено: Вт окт 12, 2021 11:56:17
Reflector
Конечно не работает, потому что это какой-то другой код

Тут, например, будет выход за предела массива:
Код: Выделить всё
if(++i >= 3)
{
uint32_t delay = arr[i] * time;
....
Да, еще на AVR же вроде int 16-ти битный, значит нужно при умножении приведение к uint32_t использовать... Наверно эффективнее сразу перемноженное хранить:
Код: Выделить всё
uint32_t arr[] = { 10000 * time, 5000 * time, 1000 * time };
while(1)
{
if (~PINA & (1<<PA0))
{
if(++i >= 3) i = 0;
}
PORTD|=(1<<PD0);
pause (arr[i]);
PORTD&=~(1<<PD0);
pause (arr[i]);
}
Опять же, это примерный код, я его не проверял потому что с AVR уже давно дела не имею, мелкие правки нужно самому делать. Например, если массив 32-х битный, то time должен быть задефайнен как 32-х битное значение.
Re: Как поместить цикл в case оператора switch
Добавлено: Вт окт 12, 2021 12:19:02
DX168B
Как уже сказали, switch у Вас реагирует только на значения i=1, 2 или 3, а переменная i принимает значения 0..2
Соответсвенно,
case 3 у Вас никогда не сработает.
Ну и пока кнопка нажата, у Вас переменная i будет постоянно меняться, пока Вы ее не отпустите.
Как я понял, вы хотите мигать светодиодом с переключаемой с помощью кнопки задержкой.
Вам нужно после фиксации факта нажатия кнопки ожидать ее отпускание и уже после отпускания инкрементировать переменную i.
Вам еще нужно изучить такой эффект как "дребезг контактов" и методы борьбы с ним. Ибо в Протеусе дребезга нет,
а в реальной кнопке он есть.
И чтение лучше явно проверять:
Код: Выделить всё
if( (PINA & (1<<PA0)) == 0) // Бит, соответсвующий выводу PA0 равен нулю (прижали подтянутую ногу к минусу)
{
// Делаем то, что надо
}
Мало того, пока протекают задержки, весь цикл заморожен и даже кнопка не опрашивается.
Для решения таких проблем используют неблокирующие задержки на "системном таймере",
программу разбивают на модули и реализуют их в виде конечных автоматов, которые меняют свои состояния
в зависимости от различных событий и делают различные действия в зависимости от своего состояния.
Re: Как поместить цикл в case оператора switch
Добавлено: Вт окт 12, 2021 12:26:23
Reflector
[uquote="DX168B",url="/forum/viewtopic.php?p=4104589#p4104589"]Ну и пока кнопка нажата, у Вас переменная i будет постоянно меняться, пока Вы ее не отпустите.[/uquote]
Там в конце задержка 300ms, если кнопку быстро нажимать, то для проверки сойдет, а дальше если еще и с антидребезгом делать, то это уже продвинутый уровень

Re: Как поместить цикл в case оператора switch
Добавлено: Вт окт 12, 2021 12:30:58
DX168B
К ней еще добавить задержки в switch и там уже гораздо больше, чем 300мс.
Re: Как поместить цикл в case оператора switch
Добавлено: Вт окт 12, 2021 13:03:55
metan
Вариантов решения много.
Задержки лучше бы не использовать, или использовать по минимуму.
Для организации задержек идеально подходят таймеры чипа плюс флаги.
1) заводим таймер на срабатывание прерывания с частотой, например в 10 мс.
2) в обработчике прерывания просто инкрементируем переменную-счетчик (не забыть сделать ее при определении как volatile)
3) в начале main обнуляем переменную-счетчик и задаем переменную - максимум (например, через настройку индекса массива на его начало, как вам уже предлагали)
4) главный цикл:
4.1) читаем состояние кнопки (кнопок)
4.2) каскад проверок:
Если переменная - счетчик достигла значения переменной - максимум, обнуляем счетчик и инвертируем выход светодиода. Например, если время свечения / паузы светодиода должно быть полсекунды, переменная - максимум должна быть равна 50.
Если была нажата кнопка, переопределям переменную - максимум (например, через приращение индекса массива, как вам уже предлагали)
Вроде всё. В протеусе должно взлететь, я думаю. В реальной схеме нужно будет добавить обработку дребезга контактов кнопки.
Re: Как поместить цикл в case оператора switch
Добавлено: Вт окт 12, 2021 13:53:17
uuu000
Cпасибо.Сейчас не могу проверить,
доберусь до компьютера -отпишусь.
Re: Как поместить цикл в case оператора switch
Добавлено: Ср окт 13, 2021 22:56:42
uuu000
Всем спасибо .Все получилось. Для прерываний использовал Т0 в режиме переполнения и срабатывания 8мс.Правда обошелся без массивов.
Re: Как поместить цикл в case оператора switch
Добавлено: Пт окт 15, 2021 16:19:30
DX168B
Рады, что смогли чем-то помочь.
Вообще, можете взять за правило.
Пишите один раз код, реализующий задержки на таймере и каждый раз,
когда создаете проект, копируете туда этот код и налаживаете его работу.
Задержки на таймере позволяют организовать асинхронные задержки, а таких задержек можно организовать
великое множество. Саму же программу реализовать можно в виде конечного автомата, который будет состоять
из множества состояний для различных действий. Главный цикл постоянно проходится по автомату и выполняет действия,
согласно текущему состоянию и переключается в другие состояния по каким-то событиям, значениям в переменных или флажках.
Благодаря такому подходу легко можно реализовать нечто, вроде "мигать светодиодом в течении 10 секунд. Если за это время нажали кнопку, зажечь светодиод, а иначе потушить."
Есть по этим вещам множество статей в блоге
easyelectronics.
Вот пример того, как выглядит один из конечных автоматов в одном из моих проектов:
Спойлер
Тут язык C++, но это не суть.
Эта функция постоянно вызывается из главного цикла.
Она реализует опрос клавиатуры и антидребезг.
Состояния автомата определяются перечислительным типом enum KbProcState (это такой удобный способ дать числам названия)
и хранятся переменной state
Тут же используются и задержки с таймером.
Все разбито на определенный порядок действий.
Состояния физических кнопок читаются постоянно, функцией CheckPressedKey()
А дальше автомат:
1 состояние (оно же и начальное):
Проверяется, нажато ли вообще что-то.
Если нет, то остаемся в этом же состоянии.
Если нажалось что-то, значит запускаем таймер антидребезга и меняем состояние автомата (переход во второе состояние)
2:
Проверка, не поменялся ли номер нажатой кнопки при очередном опросе.
Если в течении задержки антидребезга номер не менялся, значит кнопка в стабильном состоянии,
происходит переключение в следующее состояние.
Если же номер изменялся (контакт еще нестабилен), то автомат переключается в начальное состояние и все начинается заново.
Последующие состояния уже детектируют короткое или длинное нажатие и выставляют событие (коротко/длинно нажата такая-то кнопка).
Это событие уже используется другими конечными автоматами, реализованными в программе.
Код: Выделить всё
///////////////////////////////////////////////////////////////////////
// Keyboard process
void CKeyboard::Process()
{
// Detect pressed key
KbKeys pressedKey = CheckPressedKey();
// Keyboard process state machine
switch (state)
{
// Initial state
case KbProcState::INITIAL:
if(pressedKey != KbKeys::KEY_NONE)
{
prevKey = pressedKey;
kbTim.SetTimer(KEYBOARD_DEBOUNCE_DELAY); // begin debounce timer
state = KbProcState::KEY_PRESS_DEBOUNCE;
}
break;
// Press debounsce
case KbProcState::KEY_PRESS_DEBOUNCE:
if(pressedKey != prevKey)
{
state = KbProcState::INITIAL;
}
else
{
if(kbTim.CheckTimer() == true) // check debounce timer
{
kbTim.SetTimer(KEYBOARD_LONGPRESS_DELAY);
#ifndef KEYBOARD_EVENT_KEY_AND_CLICK_SYNC
currentKey = pressedKey;
#endif
currentClick = ClickType::NO_CLICK;
state = KbProcState::KEY_PRESS;
}
}
break;
// Key pressed state
case KbProcState::KEY_PRESS:
if(pressedKey == KbKeys::KEY_NONE)
{
if(kbTim.CheckTimer() == false)
{
currentClick = ClickType::SHORT_CLICK;
#ifdef KEYBOARD_EVENT_KEY_AND_CLICK_SYNC
currentKey = prevKey;
#endif
}
state = KbProcState::INITIAL;
}
if(kbTim.CheckTimer() == true)
{
currentClick = ClickType::LONG_CLICK;
#ifdef KEYBOARD_EVENT_KEY_AND_CLICK_SYNC
currentKey = pressedKey;
#endif
state = KbProcState::KEY_UNPRESS;
}
break;
// Wait for unpress key
case KbProcState::KEY_UNPRESS:
if(pressedKey == KbKeys::KEY_NONE)
{
#ifndef KEYBOARD_EVENT_KEY_AND_CLICK_SYNC
currentKey = pressedKey;
#endif
state = KbProcState::INITIAL;
}
break;
// Other states
default:
currentKey = KbKeys::KEY_NONE;
currentClick = ClickType::NO_CLICK;
state = KbProcState::INITIAL;
break;
}
}
Re: Как поместить цикл в case оператора switch
Добавлено: Сб окт 16, 2021 23:29:02
uuu000
Подскажите, если не трудно литературу по конечным автоматам .Может быть есть ссылки в интернете по этой теме.
Спасибо.
Re: Как поместить цикл в case оператора switch
Добавлено: Сб окт 16, 2021 23:42:53
parovoZZ
Цифровые устройства Пухальского и Новосельцевой.
Re: Как поместить цикл в case оператора switch
Добавлено: Пн окт 18, 2021 02:24:41
Demiurg
Цикл статей Татарчевского.
Здесь тоже статья, в ней ссылка на архив.
Моя реализация программных таймеров.
Re: Как поместить цикл в case оператора switch
Добавлено: Чт фев 03, 2022 14:30:05
uuu000
DX168B , metan!
Попробовал эту методику, с прерываниями и конечным автоматом. Все получилось.
Спасибо!
Re: Как поместить цикл в case оператора switch
Добавлено: Чт окт 06, 2022 11:03:05
uuu000
Возникла проблема. Хочу написать код для переключения кнопками(например три кнопки)
трех выходов. С помощью методики "конечных автоматов" попробовал сделать программу.
Но получился трехканальный переключатель без фиксации ,т.е. включение и выключение
любого канала происходит независимо от состояния других каналов.
Мне нужно превратить этот переключатель в переключатель с фиксацией. Т.е.
при нажатии на любую кнопку остальные выходы обнуляются(неважно что на них было -
0v,+5v или blink для светодиода) и на выходе канала с этой включенной кнопкой появляется
нужный сигнал +5v или blink.Причем выключить этот канал можно включением другого("переключение
с фиксацией") или просто повторным нажатием на кнопку включенного канала.
Проблема в том, что я не смог добиться отключения активного канала включением любого другого.
Перепробовал различные приемы выключения других выходов при включении нужного мне канала.
Помещал код типа PORTB&=~(1<<PB3); в switch для blink_click,пробовал в button_ KA после строки
поднятия флага fclk: if(fclk =1){PORTB&=~(1<<PB3)}.Номера портов здесь условные.
Я понимаю, что мне не хватает опыта для использования таких тонкостей.
Поэтому и прошу помощи.
Спасибо.
В архиве код и протеус
https://drive.google.com/file/d/1kOjc23 ... sp=sharing