Слабое мерцание светодиодной ленты при нулевой скважности программного ШИМ

Тема в разделе "Arduino & Shields", создана пользователем ArhAngeL3000, 13 фев 2018.

  1. ArhAngeL3000

    ArhAngeL3000 Нуб

    Добрый день. Столкнулся с такой проблемой: используя программный ШИМ при выставлении скважности на выходе 0 остается слабое свечение с мерцанием светодиодной ленты.

    Вкладка программы
    Код (C++):
    #define PIN_4                  // подсветка FANfront №1
    #define PIN_7                  // подсветка FANtop

    int brightnessDDR = 0;         // яркость модулей ОЗУ
    int fadeAmountDDR = 1;         // прирощение яркости ОЗУ
    int modDDR = 0;                // режим подсветки ОЗУ
    byte incomingByte;             // переменная сериал

    void setup()
    {
      Init_PWM();
      analog_Frequence(1); // предделитель от 1 до 7 частоты ШИМ
      pinMode(4, OUTPUT);
      pinMode(7, OUTPUT);
      Serial.begin(9600); // скорость порта 9600
    }

    void loop()
    {
      if (Serial.available() > 0) {
          incomingByte = Serial.read() ;
          if(incomingByte == '0'){
              modDDR = 0 ;
              Serial.println("Подсветка ОЗУ выключены") ;
          }
          else if (incomingByte == '1'){
              modDDR = 1 ;
              Serial.println("Подсветка ОЗУ постоянный режим") ;
          }
          else if (incomingByte == '2'){
              modDDR = 2 ;
              brightnessDDR = 0 ;
              Serial.println("Подсветка ОЗУ синхронная пульсация") ;
          }
          else if (incomingByte == '3'){
              modDDR = 3 ;
              brightnessDDR = 0 ;
              Serial.println("Подсветка ОЗУ асинхронная пульсация") ;
          }    
        }
         
      if (modDDR == 0) {
          brightnessDDR = 0 ;
          analog_Write(4, brightnessDDR) ;  
          analog_Write(7, brightnessDDR) ;
      }
      if (modDDR == 1) {
          brightnessDDR = 50 ;
          analog_Write(4, brightnessDDR) ;  
          analog_Write(7, brightnessDDR) ;
      }
      if (modDDR == 2) {
          analog_Write(4, brightnessDDR) ;  
          analog_Write(7, brightnessDDR) ;
          brightnessDDR = brightnessDDR + fadeAmountDDR ;
          if (brightnessDDR == 0 || brightnessDDR == 50) {
              fadeAmountDDR = -fadeAmountDDR ;
          }
      }
      if (modDDR == 3) {
          analog_Write(4, 50-brightnessDDR) ;  
          analog_Write(7, brightnessDDR) ;
          brightnessDDR = brightnessDDR + fadeAmountDDR ;
          if (brightnessDDR == 0 || brightnessDDR == 50) {
              fadeAmountDDR = -fadeAmountDDR ;
          }
      }
      delay(40);
    }
                                 
    Вкладка PWM
    Код (C++):
    #ifdef ALL
    #define PIN_0
    #define PIN_1
    #define PIN_2
    #define PIN_3
    #define PIN_4
    #define PIN_5
    #define PIN_6
    #define PIN_7
    #define PIN_8
    #define PIN_9
    #define PIN_10
    #define PIN_11
    #define PIN_12
    #define PIN_13
    #define PIN_14
    #define PIN_15
    #define PIN_16
    #define PIN_17
    #define PIN_18
    #define PIN_19
    #endif

    #define SBI(port, pin) asm volatile ("sbi %0, %1" :: "I" (_SFR_IO_ADDR(port)), "I" (pin))

    #define CHECK(pwms, port, pin) \
    asm volatile ( \
       "cpc %0, %1      \n\t" \
       "brlo 0f         \n\t" \
       "cbi %2, %3      \n\t" \
       "rjmp 1f         \n\t" \
       "0: sbi %2, %3   \n\t" \
       "1: nop          \n\t" \
       : "+r" (pwm), "+r" (pwms) \
       : "I" (_SFR_IO_ADDR(port)), "I" (pin)\
       )\
     

    void Init_PWM()
    {
      cli();
      TCCR2A = 0;  
      TCCR2B = 3;   //CLK
      OCR2A = 1;
      TIMSK2 = 2;   //разрешаем прерывание по совпадению
      sei();

    #ifdef PIN_0
      SBI(DDRD, 0);
    #endif
    #ifdef PIN_1
      SBI(DDRD, 1);
    #endif
    #ifdef PIN_2
      SBI(DDRD, 2);
    #endif
    #ifdef PIN_3
      SBI(DDRD, 3);
    #endif
    #ifdef PIN_4
      SBI(DDRD, 4);
    #endif
    #ifdef PIN_5
      SBI(DDRD, 5);
    #endif
    #ifdef PIN_6
      SBI(DDRD, 6);
    #endif
    #ifdef PIN_7
      SBI(DDRD, 7);
    #endif
    #ifdef PIN_8
      SBI(DDRB, 0);
    #endif
    #ifdef PIN_9
      SBI(DDRB, 1);
    #endif
    #ifdef PIN_10
      SBI(DDRB, 2);
    #endif
    #ifdef PIN_11
      SBI(DDRB, 3);
    #endif
    #ifdef PIN_12
      SBI(DDRB, 4);
    #endif
    #ifdef PIN_13
      SBI(DDRB, 5);
    #endif
    #ifdef PIN_14
      SBI(DDRC, 0);
    #endif
    #ifdef PIN_15
      SBI(DDRC, 1);
    #endif
    #ifdef PIN_16
      SBI(DDRC, 2);
    #endif
    #ifdef PIN_17
      SBI(DDRC, 3);
    #endif
    #ifdef PIN_18
      SBI(DDRC, 4);
    #endif
    #ifdef PIN_19
      SBI(DDRC, 5);
    #endif
    }

    volatile uint8_t pwm;
    volatile uint8_t pwms[20];

    void analog_Frequence(byte prescaler)
    {
      TCCR2B = prescaler;   //CLK
    }

    void analog_Write(byte pin, byte value)
    {
      pwms[pin] = value;
    }

    byte analog_State(byte pin)
    {
      return pwms[pin];
    }

    ISR(TIMER2_COMPA_vect)
    {
    asm volatile ("clr %0" : "+r" (TCNT2)); // TCNT2 = 0;

    #ifdef PIN_0
      CHECK(pwms[0], PORTD, 0);
    #endif
    #ifdef PIN_1
      CHECK(pwms[1], PORTD, 1);
    #endif
    #ifdef PIN_2
      CHECK(pwms[2], PORTD, 2);
    #endif
    #ifdef PIN_3
      CHECK(pwms[3], PORTD, 3);
    #endif
    #ifdef PIN_4
      CHECK(pwms[4], PORTD, 4);
    #endif
    #ifdef PIN_5
      CHECK(pwms[5], PORTD, 5);
    #endif
    #ifdef PIN_6
      CHECK(pwms[6], PORTD, 6);
    #endif
    #ifdef PIN_7
      CHECK(pwms[7], PORTD, 7);
    #endif
    #ifdef PIN_8
      CHECK(pwms[8], PORTB, 0);
    #endif
    #ifdef PIN_9
      CHECK(pwms[9], PORTB, 1);
    #endif
    #ifdef PIN_10
      CHECK(pwms[10], PORTB, 2);
    #endif
    #ifdef PIN_11
      CHECK(pwms[11], PORTB, 3);
    #endif
    #ifdef PIN_12
      CHECK(pwms[12], PORTB, 4);
    #endif
    #ifdef PIN_13
      CHECK(pwms[13], PORTB, 5);
    #endif
    #ifdef PIN_14
      CHECK(pwms[14], PORTC, 0);
    #endif
    #ifdef PIN_15
      CHECK(pwms[15], PORTC, 1);
    #endif
    #ifdef PIN_16
      CHECK(pwms[16], PORTC, 2);
    #endif
    #ifdef PIN_17
      CHECK(pwms[17], PORTC, 3);
    #endif
    #ifdef PIN_18
      CHECK(pwms[18], PORTC, 4);
    #endif
    #ifdef PIN_19
      CHECK(pwms[19], PORTC, 5);
    #endif

    asm volatile ("inc %0" : "+r" (pwm)); //pwm++;
    }
    Схема управления нагрузкой собрана на мосфетах IRF520, токоограничительные резисторы затвора 100 Ом, подтягивающие резисторы 10 кОм, лента 12 В пробовал на разных светодиодах, эффект один и тот же.

    Без ардуино лента не горит вообще, утечки через мосфет нет, напряжение на выходе ардуино при скважности 0 тестером не померить, несколько милливольт, осцилографа к сожалению нет. Свечение появляется только после запуска программы контроллера и перехода в режим "отключено".

    Информации по этому поводу не нашел в интернете. Само мерцание меня не напрягает и в режиме, когда лента горит или "дышит" - незаметно для глаза. Но почему при скважности 0 на затворе мосфета остается потенциал, который заставляет ленту подсвечиваться мне не понятно.

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

    Заранее благодарю за ответ! Не судите строго, если просмотрел эту тему на форуме, но инфу нашел только о сглаживании пульсаций ШИМ. Еще говорили о том, что у ШИМ таймера нет нулевого дьюти... Это не понял и мне, немного далекому от глубин программирования, ни о чем не говорит.
     
  2. AlexU

    AlexU Гуру

    Есть мнение, что перемудрили с ассемблерными вставками -- лучше бы это дело (генерацию ассемблерного кода) доверили компилятору языка С.
    В макросе 'CHECK' в ассемблерной вставке первая команда сравнение регистров с учётом флага переноса:
    Вы уверены, что флаг переноса во время выполнения команды всегда будет иметь определённое значение? И на чём основана Ваша уверенность?
    Регистр TCNT2 доступен только через адрес оперативной памяти (команды sts и lds). У меня есть сомнения, что команда очистки регистра сработает правильно.
    Есть и нулевое (когда постоянный низкий уровень) и стопроцентное (когда постоянный высокий уровень).
     
  3. ostrov

    ostrov Гуру

    Ключ попробуйте притянуть к GND резистором 10КОм.
     
  4. ArhAngeL3000

    ArhAngeL3000 Нуб

    Код PWM не мой, взят с форума ардуино.ру. Иного способа генерации ШИМ на более чем 6 пинах нано не нашел.
    Описал что стоят подтягивающие резисторы 10 кОм.
     
  5. mcureenab

    mcureenab Гуру

    Подтягивающие, это к Vcc надо понимать?
    А зачем он вообще нужен? Out порт всегда подключен к Gnd или к Vcc. И нечего там подтягивать или приземлять.
     
  6. mcureenab

    mcureenab Гуру

    cpc это сравнение операндов минус бит переноса.
    Бит переноса у вас нигде явно не устанавливается, поэтому может оказаться любым и при pwms == 0 это значение будет определяющим.

    Замените cpc на cp.
     
    Последнее редактирование: 14 фев 2018
  7. AlexU

    AlexU Гуру

    Код кривой, требует исправлений.
    Как минимум:
    и строку:
    Код (C++):
    asm volatile ("clr %0" : "+r" (TCNT2));
    на
    Код (C++):
    TCNT2 = 0;
    Прежде, чем порт станет OUT пройдёт определённое время, в течении которого состояние выхода МК будет не пойми каким, и чтобы в течении этого времени затвор полевика был в определённом состоянии применяют подтягивающие резисторы. Потом уже такие резисторы скорее мешают, чем помогают.
     
  8. ArhAngeL3000

    ArhAngeL3000 Нуб

    AlexU и mcureenab спасибо огромное. Все получилось. При нулевой скважности наглухо тухнет. Отмечу, что на форуме arduino.ru так и не смогли ответить или не захотели.
     
  9. zsedcd

    zsedcd Нуб

    На arduino.ru вообще какая то недружелюбгая атмосфера. Есть кучка завсегдатаев, которые не очень жалуют новичков.
     
  10. DIYMan

    DIYMan Гуру

    Там халявщиков и ленивых жоп не любят :)
     
    vvr нравится это.
  11. ArhAngeL3000

    ArhAngeL3000 Нуб

    Я это понял уже. Но общение в режиме "Холоп, иди учи матчасть - здесь тебе объяснять ничего не будут" меня немного напрягло, при условии что я вежливо объяснил, что знания в программировании на уровне школьного Паскаля. Может я конечно не прав был в чем-то, но когда люди перешли на личности - стало понятно, что туда я еще раз напишу в крайнем случае.
     
  12. DIYMan

    DIYMan Гуру

    Не, ты не прав. Если вопрос задан корректно - обязательно ответят, и по теме. Со ссылками на матчасть. А вот если холопу этого недостаточно и он начинает вставать в позу - тогда да, осадят по самые не балуй. Думающему и желающему учиться человеку - вполне достаточно ссылки на матчасть, в 99% случаев. Все остальные - ленивые жопы :)
     
  13. ArhAngeL3000

    ArhAngeL3000 Нуб

    В общем с нулевой скважностью разобрался, но вот код программного ШИМ почему-то блокирует обмен по serial.
    Скину еще раз код, с которым работаю.
    Код (C++):
    #define PIN_4                
    #define PIN_7                

    int brightnessDDR = 0;         // яркость модулей ОЗУ
    int fadeAmountDDR = 1;         // прирощение яркости ОЗУ
    int modDDR = 3;                // режим подсветки ОЗУ
    int incomingByte;              // переменная сериал

    void setup()
    {
      Init_PWM();
      analog_Frequence(3); // предделитель от 1 до 7 частоты ШИМ
      pinMode(4, OUTPUT);
      pinMode(7, OUTPUT);
      Serial.begin(9600); // скорость порта 9600
    }

    void loop()
    {
        if (Serial.available() > 0) {
          incomingByte = Serial.read() ;
          if(incomingByte == '0'){
              modDDR = 0 ;
              Serial.println("подсветка ОЗУ выключена") ;
          }
          else if (incomingByte == '1'){
              modDDR = 1 ;
              Serial.println("подсветка ОЗУ постоянный режим") ;
          }
          else if (incomingByte == '2'){
              modDDR = 2 ;
              Serial.println("подсветка ОЗУ синхронная пульсация") ;
          }
          else if (incomingByte == '3'){
              modDDR = 3 ;
              Serial.println("подсветка ОЗУ асинхронная пульсация") ;
          }
          else {
              Serial.println("недопустимая команда") ;
          }      
        }
         
      if (modDDR == 0) {
          brightnessDDR = 0 ;
          analog_Write(4, brightnessDDR) ;  
          analog_Write(7, brightnessDDR) ;
      }
      if (modDDR == 1) {
          brightnessDDR = 50 ;
          analog_Write(4, brightnessDDR) ;  
          analog_Write(7, brightnessDDR) ;
      }
      if (modDDR == 2) {
          analog_Write(4, brightnessDDR) ;  
          analog_Write(7, brightnessDDR) ;
          brightnessDDR = brightnessDDR + fadeAmountDDR ;
          if (brightnessDDR == 0 || brightnessDDR == 50) {
              fadeAmountDDR = -fadeAmountDDR ;
          }
      }
      if (modDDR == 3) {
          analog_Write(4, 50-brightnessDDR) ;  
          analog_Write(7, brightnessDDR) ;
          brightnessDDR = brightnessDDR + fadeAmountDDR ;
          if (brightnessDDR == 0 || brightnessDDR == 50) {
              fadeAmountDDR = -fadeAmountDDR ;
          }
      }
      delay(40);
    }
    Код (C++):
    #ifdef ALL
    #define PIN_0
    #define PIN_1
    #define PIN_2
    #define PIN_3
    #define PIN_4
    #define PIN_5
    #define PIN_6
    #define PIN_7
    #define PIN_8
    #define PIN_9
    #define PIN_10
    #define PIN_11
    #define PIN_12
    #define PIN_13
    #define PIN_14
    #define PIN_15
    #define PIN_16
    #define PIN_17
    #define PIN_18
    #define PIN_19
    #endif

    #define SBI(port, pin) asm volatile ("sbi %0, %1" :: "I" (_SFR_IO_ADDR(port)), "I" (pin))

    #define CHECK(pwms, port, pin) \
    asm volatile ( \
       "cp %0, %1      \n\t" \
       "brlo 0f         \n\t" \
       "cbi %2, %3      \n\t" \
       "rjmp 1f         \n\t" \
       "0: sbi %2, %3   \n\t" \
       "1: nop          \n\t" \
       : "+r" (pwm), "+r" (pwms) \
       : "I" (_SFR_IO_ADDR(port)), "I" (pin)\
       )\
     

    void Init_PWM()
    {
      cli();
      TCCR2A = 0;  
      TCCR2B = 3;   //CLK
      OCR2A = 1;
      TIMSK2 = 2;   //разрешаем прерывание по совпадению
      sei();

    #ifdef PIN_0
      SBI(DDRD, 0);
    #endif
    #ifdef PIN_1
      SBI(DDRD, 1);
    #endif
    #ifdef PIN_2
      SBI(DDRD, 2);
    #endif
    #ifdef PIN_3
      SBI(DDRD, 3);
    #endif
    #ifdef PIN_4
      SBI(DDRD, 4);
    #endif
    #ifdef PIN_5
      SBI(DDRD, 5);
    #endif
    #ifdef PIN_6
      SBI(DDRD, 6);
    #endif
    #ifdef PIN_7
      SBI(DDRD, 7);
    #endif
    #ifdef PIN_8
      SBI(DDRB, 0);
    #endif
    #ifdef PIN_9
      SBI(DDRB, 1);
    #endif
    #ifdef PIN_10
      SBI(DDRB, 2);
    #endif
    #ifdef PIN_11
      SBI(DDRB, 3);
    #endif
    #ifdef PIN_12
      SBI(DDRB, 4);
    #endif
    #ifdef PIN_13
      SBI(DDRB, 5);
    #endif
    #ifdef PIN_14
      SBI(DDRC, 0);
    #endif
    #ifdef PIN_15
      SBI(DDRC, 1);
    #endif
    #ifdef PIN_16
      SBI(DDRC, 2);
    #endif
    #ifdef PIN_17
      SBI(DDRC, 3);
    #endif
    #ifdef PIN_18
      SBI(DDRC, 4);
    #endif
    #ifdef PIN_19
      SBI(DDRC, 5);
    #endif
    }

    volatile uint8_t pwm;
    volatile uint8_t pwms[20];

    void analog_Frequence(byte prescaler)
    {
      TCCR2B = prescaler;   //CLK
    }

    void analog_Write(byte pin, byte value)
    {
      pwms[pin] = value;
    }

    byte analog_State(byte pin)
    {
      return pwms[pin];
    }

    ISR(TIMER2_COMPA_vect)
    {
    TCNT2 = 0; // asm volatile ("clr %0" : "+r" (TCNT2));

    #ifdef PIN_0
      CHECK(pwms[0], PORTD, 0);
    #endif
    #ifdef PIN_1
      CHECK(pwms[1], PORTD, 1);
    #endif
    #ifdef PIN_2
      CHECK(pwms[2], PORTD, 2);
    #endif
    #ifdef PIN_3
      CHECK(pwms[3], PORTD, 3);
    #endif
    #ifdef PIN_4
      CHECK(pwms[4], PORTD, 4);
    #endif
    #ifdef PIN_5
      CHECK(pwms[5], PORTD, 5);
    #endif
    #ifdef PIN_6
      CHECK(pwms[6], PORTD, 6);
    #endif
    #ifdef PIN_7
      CHECK(pwms[7], PORTD, 7);
    #endif
    #ifdef PIN_8
      CHECK(pwms[8], PORTB, 0);
    #endif
    #ifdef PIN_9
      CHECK(pwms[9], PORTB, 1);
    #endif
    #ifdef PIN_10
      CHECK(pwms[10], PORTB, 2);
    #endif
    #ifdef PIN_11
      CHECK(pwms[11], PORTB, 3);
    #endif
    #ifdef PIN_12
      CHECK(pwms[12], PORTB, 4);
    #endif
    #ifdef PIN_13
      CHECK(pwms[13], PORTB, 5);
    #endif
    #ifdef PIN_14
      CHECK(pwms[14], PORTC, 0);
    #endif
    #ifdef PIN_15
      CHECK(pwms[15], PORTC, 1);
    #endif
    #ifdef PIN_16
      CHECK(pwms[16], PORTC, 2);
    #endif
    #ifdef PIN_17
      CHECK(pwms[17], PORTC, 3);
    #endif
    #ifdef PIN_18
      CHECK(pwms[18], PORTC, 4);
    #endif
    #ifdef PIN_19
      CHECK(pwms[19], PORTC, 5);
    #endif

    asm volatile ("inc %0" : "+r" (pwm)); //pwm++;
    }

    Если в скетче закоментировать строку Init_PWM(); - общение по serial команда/ответ проходят без проблем, как только подключаю код PWM - все, глухо. При попытке вывести значение любой переменной по serial - программа зависает вовсе. Пытался понять, что мешает: предположил, что код PWM задействует ШИМ и на RX TX, так как рассчитан на все пины ардуино. Пробовал оставить только строки с пинами, которые использую - эффект тот же. Видимо там что-то "зарыто" в коде ассемблера...
     
  14. mcureenab

    mcureenab Гуру

    Какая модель ардуинки?
     
  15. mcureenab

    mcureenab Гуру

    Убери строчку

    Код (C++):
    analog_Frequence(1); // предделитель от 1 до 7 частоты ШИМ
     
  16. mcureenab

    mcureenab Гуру

    Код (C++):
    TCCR2B = 3;
    устанавливает деление частоты i/o для таймера на 32. Вызов
    analog_Frequence(1);
    заменяет 1/32 на 1/1, т.е. без деления.

    Код (C++):
    OCR2A = 1;
    выставляет порог для прерывания на TCNT2 == 1. те получается прерывание срабатывает на каждый тик таймера TCNT2.
    В самом прерывании есть очистка счётчика
    Код (C++):
    asm volatile ("clr %0" : "+r" (TCNT2)); // TCNT2 = 0;
    но пока процедура возится с пинами, счётчик может прокрутиться дальше 1 и даже пройти несколько циклов. Процессор занимается только обслуживанием прерываний по таймеру.


    Тебе какую частоту ШИМ надо выставить?
    Например:
    Код (C++):
    TCCR2B = 3; // Прескейлер 1/32
    OCR2A = 8; // Считаем до 8 тиков
     
    Чатота прерываний
    20MHz/32/8=78 kHz

    чатота ШИМ
    20MHz/32/8/256 = 305 Hz.

    тут 256 - полный цикл счёта переменной pwm.

    и вызов
    analog_Frequence(1);
    убери.
     
  17. ArhAngeL3000

    ArhAngeL3000 Нуб

    Nano на Atmega328P.
    Частота нужна, чтобы на глаз не видно было мерцание.
    Сейчас попробую, что посоветовал. Спасибо за ответы:

    Спасибо, вроде вылечил. Теперь осталось подобрать значения для адекватной частоты при работе с 11 пинами одновременно. Я так понимаю для увеличения частоты нужно TCCR2B и
    OCR2A уменьшать, но в разумных пределах, чтобы не заставлять контроллер постоянно заниматься только PWM
     
    Последнее редактирование: 20 фев 2018
  18. AlexU

    AlexU Гуру

    Прерывание будет срабатывать только на втором такте таймера и только один раз за "цикл".
    Соответственно:
    Частота прерываний -- 16MHz / 32 / 256 = ~2kHz. Это с учётом настроек таймера.
    При таком режиме работы таймера (WGM22..WGM20 = 0) на частоту будет влиять только TCCR2B. Значение OCR2A в данном коде ни на что влиять не будет. И -- "Да" -- с увеличением частоты надо быть аккуратнее.