Мистическое поведение программы на attiny10

Тема в разделе "Микроконтроллеры AVR", создана пользователем Антон336699, 6 дек 2023.

Метки:
  1. Вот и у меня было такое предчувствие, но если перейти на стандартные задержки, и избавиться от прерываний, вообще все должно работать четко, завтра сначала проверю ваш код, а затем попробую его переделать. Сегодня под конец дня еще экспериментировал, и по итогу нужно было применять высоковольтный программатор, и я по запаре его подключил переполюсовав, как итог запорол порт РВ3, он же Reset, чип выкидывать, завтра нужно новый запаять сперва)))))))))
     
  2. Единственное пока не до конца разобрался, _delay_ms вроде как имеет ограничение, и разную точность задержки, в зависимости от превышения этих лимитов. Но в моем случае микронная точность не особо важна +-0,5 секунды меня устроит. И так же эта задержка не любит прерывания.
     
  3. Протестировал ваш код, работает корректно как и задумывалось. Сейчас еще перепишу под стандартные функции задержки.
     
  4. В итоге код вышел такой, с учетом всех замечаний.
    Код (C++):
    #define F_CPU 8000000UL
    #define F_TIMER 1000000UL         // частота таймера для рассчетов.

    #include <avr/io.h>
    #include <avr/interrupt.h>
    #include <util/delay.h>

    #define timer0_max_count 20000UL    // до этого числа считает таймер.
    #define servo_poz_start 1400U        // позиция сервопривода в режиме ожидания, в нее он возвращается при загрузке
    #define servo_poz_alarm 2350U        // позиция сервопривода когда происходит срабатывание механизна

    uint8_t flag = 0x00;    // флаг срабатывания

    int main(void)
    {
        cli();            // запрещаем прерывания
        CCP = 0xD8;        // разблокируем
        CLKPSR = 0x00;    // выстявляем предделитель частоты 1
           
        CCP = 0xD8;        // на всякий случай еще раз разблокируем
        CLKMSR = 0x00;    // Calibrated Internal 8 MHz Oscillator (Default)
           
        //............................... Конфигурируем регистры портов
        // PB0(выход) - подключена серва, PB1(выход) - мосфет, PB2(вход) - вход с компаратора
        DDRB = 0b00000011;      // устанавили пин PB0 и PB1 как выходы.
        PUEB = 0x00;            // отключаем подтягивающие резисторы порта B
        PORTB &= ~(1 << PORTB1);    // устанавливаем низкий уровень на PB1 (мосфет питания сервопривода)
       
        //............................... Выбор предделителя таймера0
        TCCR0A = 0; // обнуляем регистры таймера
        TCCR0B = 0;
        TCCR0B |= (1 << CS01);                // 8
       
        //............................... Быбор режима работы таймера0
        TCCR0A |= (1 << WGM01); TCCR0B |= (1 << WGM02) | (1 << WGM03); ICR0 = (timer0_max_count - 1);  // Fast PWM любой битности
        OCR0A = servo_poz_start;    // задаем стартовое положение сервопривода
        PORTB |= (1 << PORTB1);        // включаем мосфет, тем самым подаем питание на сервопривод
        TCCR0A |= 1 << COM0A1;                        //не инвертный режим
       
        _delay_ms(2000);
        PORTB &= ~(1 << PORTB1);                // выключаем мосфет, отключаем питание сервопривода
        _delay_ms(3000);
       
        while (1)
        {
            if ((PINB & (1 << PINB2)) == 0 && flag == 0)
            {
                flag = 1;
                OCR0A = servo_poz_alarm;    // задаем положение сервопривода когда ловушка сработала
                _delay_ms(100);
                PORTB |= (1 << PORTB1);        // включаем мосфет, тем самым подаем питание на сервопривод
                _delay_ms(2000);
                PORTB &= ~(1 << PORTB1);    // выключаем мосфет
               
            }
        }
    }
     
  5. Asper Daffy

    Asper Daffy Иксперд

    Ну, значит, проблема действительно была в том, что "не вовремя" прерывание прилетает, а значит, Вы тогда её (проблему) действительно только замаскировали. При любом изменении (что-то вставили в программу) время бы сдвинулось и проблема бы вернулась.

    Покритиковать новый код?
     
  6. Обязательно))))
     
  7. Asper Daffy

    Asper Daffy Иксперд

    Мои комментарии везде начинаются со строки

    //???????

    Код (C++):
    #define F_CPU 8000000UL
    #define F_TIMER 1000000UL         // частота таймера для рассчетов.

    #include <avr/io.h>
    #include <avr/interrupt.h>
    #include <util/delay.h>

    //???????
    // зачем timer0_max_count быть UL, если его пихают в 16-ти разрядный ICR0? Достаточно U
    #define timer0_max_count 20000UL    // до этого числа считает таймер.
    #define servo_poz_start 1400U        // позиция сервопривода в режиме ожидания, в нее он возвращается при загрузке
    #define servo_poz_alarm 2350U        // позиция сервопривода когда происходит срабатывание механизна

    //???????
    // По flag два замечания.
    // 1. Судя по его использованию, ему логичнее быть bool, чем  uint8_t
    // 2. Изначально эта переменная равно 0.
    //        Единожды попав внутрь "if ((PINB & (1 << PINB2)) == 0 && flag == 0)" она становится 1 и всё!
    //        Больше она не изменяется никогда и остаётся 1 до выключения питания.
    //        Это точно так задумано было? Типа захлопнулась крышка и всё, правильно?
    uint8_t flag = 0x00;    // флаг срабатывания

    int main(void)
    {
        cli();            // запрещаем прерывания
    //???????
    // Я не в курсе, две строки ниже точно нужно дважды подряд повторять?
        CCP = 0xD8;        // разблокируем
        CLKPSR = 0x00;    // выстявляем предделитель частоты 1
     
        CCP = 0xD8;        // на всякий случай еще раз разблокируем
        CLKMSR = 0x00;    // Calibrated Internal 8 MHz Oscillator (Default)
     
        //............................... Конфигурируем регистры портов
        // PB0(выход) - подключена серва, PB1(выход) - мосфет, PB2(вход) - вход с компаратора
        DDRB = 0b00000011;      // устанавили пин PB0 и PB1 как выходы.
        PUEB = 0x00;            // отключаем подтягивающие резисторы порта B
        PORTB &= ~(1 << PORTB1);    // устанавливаем низкий уровень на PB1 (мосфет питания сервопривода)
     
        //............................... Выбор предделителя таймера0
        TCCR0A = 0; // обнуляем регистры таймера

    //???????
    //    Эту команду лучше ставить самой первой, перед настройкой таймера (см. пояснения ниже)
        TCCR0B = 0;

    //???????
    //   Делитель таймера лучше задавать в самом конце настройки таймера, последней командой
    //    Дело в том, что пока в битах CSхх нули, таймер остановлен, а эта команда его запускает
    //    лучше запускать таймер, когда он полностью настроен.
        TCCR0B |= (1 << CS01);                // 8
     
        //............................... Быбор режима работы таймера0
        TCCR0A |= (1 << WGM01); TCCR0B |= (1 << WGM02) | (1 << WGM03); ICR0 = (timer0_max_count - 1);  // Fast PWM любой битности
        OCR0A = servo_poz_start;    // задаем стартовое положение сервопривода
        PORTB |= (1 << PORTB1);        // включаем мосфет, тем самым подаем питание на сервопривод
        TCCR0A |= 1 << COM0A1;                        //не инвертный режим
     
        _delay_ms(2000);

    //???????
    // Такие вещи лучше оформлять inline функцией или макросом, будет гораздо читабельнне
    // Например, если в начале программы определить макросы:
    //
    //    #define ServoBIT    PORTB1
    //    #define ServoPowerON() do { PORTB |= (1 << ServoBIT); } while (0)
    //    #define ServoPowerOFF() do { PORTB &= ~(1 << ServoBIT); } while (0)
    //
    //    То здесь (и везде) можно было бы написать просто
    //        ServoPowerOFF(); или там ServoPowerON();
    //    такому коду и комментарий не нужен
    //
        PORTB &= ~(1 << PORTB1);                // выключаем мосфет, отключаем питание сервопривода
        _delay_ms(3000);
     
        while (1)
        {

    //???????
    // То же и здесь. Выражение "(PINB & (1 << PINB2)) == 0", видимо, на практике означает,
    //    что ловушка засекла цель (луч перекрыт) - так? Ну и обзовите её
    //    как-нибудь типа MouseDetected
    //
    //    #define MouseDetected ((PINB & (1 << PINB2)) == 0)
    //
    // А если ещё и flag дать осмысленное название (например, MouseCaptured), то условие
    //    будет очень неплохо самодокументировано
    //
    //    if (MouseDetected && ! MouseCaptured)
    //
    // Сразу понятно что за условие, разве нет?
            if ((PINB & (1 << PINB2)) == 0 && flag == 0)
            {
                flag = 1;
                OCR0A = servo_poz_alarm;    // задаем положение сервопривода когда ловушка сработала
                _delay_ms(100);
                PORTB |= (1 << PORTB1);        // включаем мосфет, тем самым подаем питание на сервопривод
                _delay_ms(2000);
                PORTB &= ~(1 << PORTB1);    // выключаем мосфет
             
            }
        }
    }
     
    Антон336699 и KindMan нравится это.
  8. parovoZZ

    parovoZZ Гуру

    их вообще не надо писать. При любых раскладах все регистры в самом начале работы программы обнулены. Если только не рассматривать вариант искусственного перехода на начало программы.
    А защитные регистры типа CCP разрешают запись в следующие 4 такта. Но лучше почитать даташит. У тиньки может быть по-другому.
     
  9. Код (C++):
    //???????
    // зачем timer0_max_count быть UL, если его пихают в 16-ти разрядный ICR0? Достаточно U
    #define timer0_max_count 20000UL    // до этого числа считает таймер.
    Согласен, даже не знаю сам как так вышло

    Код (C++):
    //???????
    // По flag два замечания.
    // 1. Судя по его использованию, ему логичнее быть bool, чем  uint8_t
    // 2. Изначально эта переменная равно 0.
    //        Единожды попав внутрь "if ((PINB & (1 << PINB2)) == 0 && flag == 0)" она становится 1 и всё!
    //        Больше она не изменяется никогда и остаётся 1 до выключения питания.
    //        Это точно так задумано было? Типа захлопнулась крышка и всё, правильно?
    uint8_t flag = 0x00;    // флаг срабатывания
    Может быть ему и логичнее быть булиевым типом данных, но я привык хранить несколько флагов в одной переменной,
    либо использую битовые поля, но здесь всего один флаг

    Код (C++):
    //???????
    // Я не в курсе, две строки ниже точно нужно дважды подряд повторять?
        CCP = 0xD8;        // разблокируем
        CLK[B]P[/B]SR = 0x00;    // выстявляем предделитель частоты 1
        CCP = 0xD8;        // на всякий случай еще раз разблокируем
        CLK[B]M[/B]SR = 0x00;    // Calibrated Internal 8 MHz Oscillator (Default)
    Строчки разные, одна выбирает предделитель, вторая источник тактирования. Экспериментировал, и еще планирую.
    Да, после разблокировки есть 4 такта на изменение защищенного регистра, прикинул что может не хватить одной разблокировки
    для изменения двух регистров.

    Код (C++):
    //............................... Выбор предделителя таймера0
        TCCR0A = 0; // обнуляем регистры таймера

    //???????
    //    Эту команду лучше ставить самой первой, перед настройкой таймера (см. пояснения ниже)
        TCCR0B = 0;
    обнуление идет перед настройкой таймера, обнуляю два регистра по очереди.

    Код (C++):
    //???????
    //   Делитель таймера лучше задавать в самом конце настройки таймера, последней командой
    //    Дело в том, что пока в битах CSхх нули, таймер остановлен, а эта команда его запускает
    //    лучше запускать таймер, когда он полностью настроен.
        TCCR0B |= (1 << CS01);                // 8
    Вот это полностью мой косяк. Знаю же что таймер запускается после того как на него подается частота

    Со всем полностью согласен, то о чем вы говорите это не только читабильно но и правила хорошего тона.
    Я очень рад что вы мне показали мои огрехи. Приму их во внимание и постараюсь применять.
    Жаль только практики мало.
     
  10. Мне нужен был предделитель 1, а после перезагрузки он по умолчанию 8, от того мне и приходится его менять.
     

    Вложения:

  11. parovoZZ

    parovoZZ Гуру

    Его ж можно во фьюзах скинуть?
     
  12. под
    Нет, там всего три фьюза:
    - перевод ножки RST в обычный порт ввода-вывода
    - включение вотчдога
    - вывод тактовых импульсов на пин.
     
  13. подкорректировал
    Код (C++):
    #define F_CPU 8000000UL
    #define F_TIMER 1000000UL         // частота таймера для рассчетов.

    #include <avr/io.h>
    #include <util/delay.h>

    #define timer0_max_count 20000U    // до этого числа считает таймер.
    #define servo_poz_start 1400U        // позиция сервопривода в режиме ожидания, в нее он возвращается при загрузке
    #define servo_poz_alarm 2350U        // позиция сервопривода когда происходит срабатывание механизна

    #define ServoON PORTB |= 1 << PORTB1;
    #define ServoOFF PORTB &= !(1 << PORTB1);
    #define MousDetect (PINB & (1 << PINB2))

    bool Ready = true;    // флаг срабатывания

    int main(void)
    {
        cli();            // запрещаем прерывания
        CCP = 0xD8;        // разблокируем
        CLKPSR = 0x00;    // выстявляем предделитель частоты 1
         
        //............................... Конфигурируем регистры портов
        // PB0(выход) - подключена серва, PB1(выход) - мосфет, PB2(вход) - вход с компаратора
        DDRB = 0b00000011;      // устанавили пин PB0 и PB1 как выходы.
        PUEB = 0x00;            // отключаем подтягивающие резисторы порта B
        ServoOFF    // устанавливаем низкий уровень на PB1 (мосфет питания сервопривода)
         
        TCCR0A = 0; // обнуляем регистры таймера
        TCCR0B = 0;
             
        //............................... Быбор режима работы таймера0
        TCCR0A |= (1 << WGM01); TCCR0B |= (1 << WGM02) | (1 << WGM03); ICR0 = (timer0_max_count - 1);  // Fast PWM любой битности
        OCR0A = servo_poz_start;    // задаем стартовое положение сервопривода
        TCCR0A |= 1 << COM0A1;                        //не инвертный режим
         
        //............................... Выбор предделителя таймера0
        TCCR0B |= (1 << CS01);                // 8
     
        ServoON        // включаем мосфет, тем самым подаем питание на сервопривод
         
        _delay_ms(2000);
        ServoOFF                // выключаем мосфет, отключаем питание сервопривода
        _delay_ms(3000);
     
        while (1)
        {
            if (!MousDetect && Ready)
            {
                Ready = false;
                OCR0A = servo_poz_alarm;    // задаем положение сервопривода когда ловушка сработала
                _delay_ms(100);
                ServoON        // включаем мосфет, тем самым подаем питание на сервопривод
                _delay_ms(2000);
                ServoOFF    // выключаем мосфет
             
            }
        }
    }
     
  14. upload_2023-12-11_22-3-46.png
     
  15. А вот собственно и само устройство.
    Потом сделал домик с подогревом для размещения ловушки на улице, когда температура за бортом около -40. Работает отлично, когда на улице -40, в домике +10)))
    upload_2023-12-23_20-32-9.jpeg
    upload_2023-12-23_20-32-51.jpeg
    upload_2023-12-23_20-34-36.jpeg
    upload_2023-12-23_20-34-55.jpeg
    upload_2023-12-23_20-36-29.jpeg
    upload_2023-12-23_20-36-45.jpeg
    upload_2023-12-23_20-36-54.jpeg
    upload_2023-12-23_20-37-9.jpeg
    upload_2023-12-23_20-37-19.jpeg
    upload_2023-12-23_20-38-7.jpeg
     
  16. a1000

    a1000 Гуру

    Да не, там-же специальная процедура чтения/записи двухбайтных регистров. Даже если прерывание вклинится между чтением младшего и старшего байтов, вы всёравно получите правильный результат.
     
  17. parovoZZ

    parovoZZ Гуру

    там же есть специальный регистр temp, куда пишется половинка от двухбайтного регистра. Вот только я сейчас не помню, а в даташит смотреть лень - при чтении то ли младшего, то ли старшего байта другой байт в этот регистр помещается автоматом. С записью тоже всё через temp. Но на сях об этом думать не надо - в библиотеке всё уже написано, если читать регистр целиком.
     
    a1000 нравится это.
  18. a1000

    a1000 Гуру

    Вот об этом я и писал. Регистр temp используется для старших байтов всех 16-битных регистров таймера. По сему при записи мы первым пишем старший байт, а читаем первым младший.
     
  19. Asper Daffy

    Asper Daffy Иксперд

    Нет. это касается специальных буферизуемых регистров (про них так и сказано в ДШ, то они буферизуемы), но никак не произвольных переменных, которые читает ТС. Наши переменные никто буферизовать не будет. Здесь-то как раз проблема встаёт в полный рост (и, кстати, мы её решили именно cli/sei).
     
  20. a1000

    a1000 Гуру

    Я подумал, что проблема касается чтения/записи аппаратных регистров 16-битного таймера. Ну а если так, то да.
    Глобальный запрет прерываний на время копирования таких переменных решает проблему.