Обсуждаем контроллеры компании Atmel.
Ответить

Как поместить цикл в case оператора switch

Вт окт 12, 2021 01:09:16

Необходимо при переключении кнопкой с помощью оператора 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

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

Re: Как поместить цикл в case оператора switch

Вт окт 12, 2021 10:22:30

Ну какой код? Студент не понятно чем занимался, как вдург препод огорошил!

Re: Как поместить цикл в case оператора switch

Вт окт 12, 2021 11:01:14

#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

У вас внутри switch одинаковые ветки отличающиеся только задержкой, проще ее хранить в массиве:
Код:
uint16_t arr[] = { 10000, 5000, 1000 };

и потом просто подставлять:
Код:
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

К сожалению не работает, если только я правильно понял идею с массивами:


#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[i] * 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

Конечно не работает, потому что это какой-то другой код :) Тут, например, будет выход за предела массива:
Код:
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-х битное значение.
Последний раз редактировалось Reflector Вт окт 12, 2021 12:19:07, всего редактировалось 2 раз(а).

Re: Как поместить цикл в case оператора switch

Вт окт 12, 2021 12:19:02

Как уже сказали, switch у Вас реагирует только на значения i=1, 2 или 3, а переменная i принимает значения 0..2
Соответсвенно, case 3 у Вас никогда не сработает.
Ну и пока кнопка нажата, у Вас переменная i будет постоянно меняться, пока Вы ее не отпустите.
Как я понял, вы хотите мигать светодиодом с переключаемой с помощью кнопки задержкой.
Вам нужно после фиксации факта нажатия кнопки ожидать ее отпускание и уже после отпускания инкрементировать переменную i.
Вам еще нужно изучить такой эффект как "дребезг контактов" и методы борьбы с ним. Ибо в Протеусе дребезга нет,
а в реальной кнопке он есть.

И чтение лучше явно проверять:
Код:
if( (PINA & (1<<PA0)) == 0// Бит, соответсвующий выводу PA0 равен нулю (прижали подтянутую ногу к минусу)
{
   
// Делаем то, что надо
}
 


Мало того, пока протекают задержки, весь цикл заморожен и даже кнопка не опрашивается.
Для решения таких проблем используют неблокирующие задержки на "системном таймере",
программу разбивают на модули и реализуют их в виде конечных автоматов, которые меняют свои состояния
в зависимости от различных событий и делают различные действия в зависимости от своего состояния.
Последний раз редактировалось DX168B Вт окт 12, 2021 12:29:28, всего редактировалось 1 раз.

Re: Как поместить цикл в case оператора switch

Вт окт 12, 2021 12:26:23

Ну и пока кнопка нажата, у Вас переменная i будет постоянно меняться, пока Вы ее не отпустите.

Там в конце задержка 300ms, если кнопку быстро нажимать, то для проверки сойдет, а дальше если еще и с антидребезгом делать, то это уже продвинутый уровень :)

Re: Как поместить цикл в case оператора switch

Вт окт 12, 2021 12:30:58

К ней еще добавить задержки в switch и там уже гораздо больше, чем 300мс.

Re: Как поместить цикл в case оператора switch

Вт окт 12, 2021 13:03:55

Вариантов решения много.
Задержки лучше бы не использовать, или использовать по минимуму.
Для организации задержек идеально подходят таймеры чипа плюс флаги.
1) заводим таймер на срабатывание прерывания с частотой, например в 10 мс.
2) в обработчике прерывания просто инкрементируем переменную-счетчик (не забыть сделать ее при определении как volatile)
3) в начале main обнуляем переменную-счетчик и задаем переменную - максимум (например, через настройку индекса массива на его начало, как вам уже предлагали)
4) главный цикл:
4.1) читаем состояние кнопки (кнопок)
4.2) каскад проверок:
Если переменная - счетчик достигла значения переменной - максимум, обнуляем счетчик и инвертируем выход светодиода. Например, если время свечения / паузы светодиода должно быть полсекунды, переменная - максимум должна быть равна 50.
Если была нажата кнопка, переопределям переменную - максимум (например, через приращение индекса массива, как вам уже предлагали)

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

Re: Как поместить цикл в case оператора switch

Вт окт 12, 2021 13:53:17

Cпасибо.Сейчас не могу проверить,
доберусь до компьютера -отпишусь.

Re: Как поместить цикл в case оператора switch

Ср окт 13, 2021 22:56:42

Всем спасибо .Все получилось. Для прерываний использовал Т0 в режиме переполнения и срабатывания 8мс.Правда обошелся без массивов.

Re: Как поместить цикл в case оператора switch

Пт окт 15, 2021 16:19:30

Рады, что смогли чем-то помочь. :beer:

Вообще, можете взять за правило.
Пишите один раз код, реализующий задержки на таймере и каждый раз,
когда создаете проект, копируете туда этот код и налаживаете его работу.
Задержки на таймере позволяют организовать асинхронные задержки, а таких задержек можно организовать
великое множество. Саму же программу реализовать можно в виде конечного автомата, который будет состоять
из множества состояний для различных действий. Главный цикл постоянно проходится по автомату и выполняет действия,
согласно текущему состоянию и переключается в другие состояния по каким-то событиям, значениям в переменных или флажках.
Благодаря такому подходу легко можно реализовать нечто, вроде "мигать светодиодом в течении 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

Подскажите, если не трудно литературу по конечным автоматам .Может быть есть ссылки в интернете по этой теме.
Спасибо.

Re: Как поместить цикл в case оператора switch

Сб окт 16, 2021 23:42:53

Цифровые устройства Пухальского и Новосельцевой.

Re: Как поместить цикл в case оператора switch

Пн окт 18, 2021 02:24:41

Цикл статей Татарчевского. Здесь тоже статья, в ней ссылка на архив.
Моя реализация программных таймеров.

Re: Как поместить цикл в case оператора switch

Чт фев 03, 2022 14:30:05

DX168B , metan!
Попробовал эту методику, с прерываниями и конечным автоматом. Все получилось.
Спасибо!

Re: Как поместить цикл в case оператора switch

Чт окт 06, 2022 11:03:05

Возникла проблема. Хочу написать код для переключения кнопками(например три кнопки)
трех выходов. С помощью методики "конечных автоматов" попробовал сделать программу.
Но получился трехканальный переключатель без фиксации ,т.е. включение и выключение
любого канала происходит независимо от состояния других каналов.
Мне нужно превратить этот переключатель в переключатель с фиксацией. Т.е.
при нажатии на любую кнопку остальные выходы обнуляются(неважно что на них было -
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
Вложения
temp2.rar
(103.71 KiB) Скачиваний: 49
Ответить