Микроконтроллер зависает при срабатывании затвора N-канального МОП-транзистора.

Отказ от ответственности: у меня есть заданные вопросы об этом проекте раньше, но это еще одна проблема, с которой я столкнулся.

Что я действительно хочу сделать: создать устройство с батарейным питанием и шестью цветными кнопками. При нажатии одной из кнопок гаджет должен издать звук, произносящий название цвета.

Для экономии заряда батареи не требуется переключатель ВКЛ/ВЫКЛ, а требуется очень низкий ток холостого хода, чтобы батарея могла работать неделями (или даже месяцами), когда она не используется.

Что-то вроде этого

Аппаратное обеспечение: ATtiny85 (обычно низкое энергопотребление, очень низкое энергопотребление в спящем режиме), DfPlayer, батарея 3,7 В (вероятно, 18650)

6 кнопок подключены к одному входному контакту. Микроконтроллер может отличить их, считывая значение АЦП. К кнопкам прикреплены разные резисторы

Как я хотел это сделать: Мой первоначальный план состоял в том, чтобы нажать кнопку, которая разбудит микроконтроллер. Затем MCU пробуждает dfplayer, воспроизводит звук и снова переходит в режим сна. Если это будет недостаточно быстро, добавьте задержку в режим сна, например: если ни одна кнопка не была нажата в течение последних 30 секунд, перейдите в режим сна. Это сработало после некоторых первоначальных проблем, но оказалось, что «спящий режим» DfPlayer перестал работать. Это не означает, что он переходит в спящий режим, поскольку потребляет меньше тока, а скорее означает, что он просто работает на холостом ходу, не воспроизводя никаких звуков.

Как я хочу это сделать сейчас: Почти то же самое, что и выше, но вместо того, чтобы отправлять DfPlayer в спящий режим, просто включите и выключите его:
Нажмите кнопку, которая активирует микроконтроллер. Затем микроконтроллер включает DfPlayer, устанавливая затвор n-канального МОП-транзистора (IRF3708) ВЫСОКИЙ, ждет короткий период времени, воспроизводит звук, выключает dfplayer и снова переходит в режим сна. Если этого недостаточно, добавьте задержку сна, как описано выше.

Что я уже умею:

  • заставить DfPlayer воспроизводить определенный звук по последовательному соединению
  • различение шести кнопок ввода
  • разбудите микроконтроллер с помощью любой из этих кнопок
  • включить/выключить DfPlayer с помощью n-канального МОП-транзистора

У меня проблема: микроконтроллер зависает, когда я устанавливаю выходной контакт, подключенный к затвору мосфета, на ВЫСОКИЙ уровень. Заморозка означает, что он просто не будет выполнять какой-либо код после вызова digitalWrite(PIN_MOS, HIGH);. Я проверил это, включив и выключив светодиод на другом контакте, вот так

digitalWrite(PIN_LED, HIGH);
digitalWrite(PIN_MOS, HIGH);
digitalWrite(PIN_LED, LOW);

Светодиод включается, но никогда не гаснет. Когда я использую внешний сброс attiny, мосфет отключится, и код снова выполнится нормально. Пока я снова не попытаюсь установить вывод MOSFET на ВЫСОКИЙ уровень.
Однако DfPlayer включится, и я смогу воспроизводить на нем звуки, замкнув на нем 2 контакта. Он тоже не выглядит нестабильным. Просто аттини зависает.
Я использую дешевый лабораторный блок питания, но он должен быть достаточно хорош, чтобы не быть проблемой. Я также пробовал полностью заряженную батарею 18650 и батарею CR2032. 18650 работал так же, как и блок питания, а CR2032 просто не справлялся с розыгрышем, и DfPlayer не включался должным образом.
Я пробовал разные напряжения от 3,3 В до 5 В. По идее всё должно работать и по факту тоже ничего не изменилось.

Вот макет, который я использую.
Я также добавил электролитический и керамический колпачок между Vcc/GND на DfPlayer, но это тоже не помогло

Вот код, который я использую

    #include <Arduino.h>
    #include <avr/sleep.h>
    #include <DFMiniMp3.h>
    #include <avr/interrupt.h>
    #include <avr/power.h>
    #include "LemonSerial.h"
    #include "mp3notify.h"
    
    #define PIN_TX PB0
    #define PIN_RX PB1
    #define PIN_MOS PB2
    #define PIN_LED PB3
    #define PIN_A PB4
    
    LemonSerial secondarySerial(PIN_RX, PIN_TX);
    DfMp3 dfmp3(secondarySerial);
    
    void sleep();
    
    void initADC()
    {
      ADMUX =
          (1 << ADLAR) | // результат сдвига влево
          (0 << REFS1) | // Устанавливает ссылку. напряжение до VCC, бит 1
          (0 << REFS0) | // Устанавливает ссылку. напряжение до VCC, бит 0
          (0 << MUX3) |  // используем АЦП2 для входа (PB4), бит 3 мультиплексора
          (0 << MUX2) |  // используем ADC2 для входа (PB4), бит MUX 2
          (1 << MUX1) |  // используем АЦП2 для входа (PB4), бит мультиплексора 1
          (0 << MUX0);   // используем АЦП2 для ввода (PB4), бит мультиплексора 0
    
      ADCSRA =
          (1 << ADEN) |  // Включаем АЦП
          (1 << ADPS2) | // устанавливаем прескалер на 128, бит 2
          (1 << ADPS1) | // устанавливаем прескалер на 128, бит 1
          (1 << ADPS0);  // устанавливаем прескалер на 128, бит 0
    }
    
    void setup()
    {
      initADC();
    
      pinMode(PB5, INPUT_PULLUP);
      pinMode(PIN_A, INPUT);
      pinMode(PIN_LED, OUTPUT);
      digitalWrite(PIN_LED, LOW);
      pinMode(PIN_MOS, OUTPUT);
      digitalWrite(PIN_MOS, LOW);
    }
    
    void play(uint16_t track)
    {
      if (digitalRead(PIN_MOS) == LOW)
      {
        digitalWrite(PIN_LED, HIGH);
        delay(500);
        digitalWrite(PIN_MOS, HIGH);
        digitalWrite(PIN_LED, LOW);
    
        delay(500); // ждем, пока DfPlayer включится
    
        dfmp3.begin();
        dfmp3.reset();
        dfmp3.disableDac();
        dfmp3.setVolume(30);
        dfmp3.setPlaybackSource(DfMp3_PlaySource_Sd);
      }
    
      dfmp3.playGlobalTrack(track);
    }
    
    void loop()
    {    
      ADCSRA |= (1 << ADSC); // начинаем измерение АЦП
      while (ADCSRA & (1 << ADSC))
      {
          // ждем завершения преобразования
      }
    
      uint8_t adcValue = ADCH;
      // фактически вычисляем параметр на основе adcValue здесь. опущено для удобства чтения
      play(1);
    }
    
    // Подпрограмма обслуживания прерывания (Pin-Change-Interrupt)
    ISR(PCINT0_vect)
    {
      LemonSerial::handle_interrupt();
    }
    
    // процедура обслуживания прерывания АЦП
    ISR(ADC_vect)
    {
      ; // нет
    }

, 👍1

Обсуждение

Есть ссылка на техническое описание МОП-транзистора, который вы используете?, @VE7JRO

@ VE7JRO, это IRF3708. Я прикрепил ссылку на свой пост, @boop

Вам также следует установить развязывающий конденсатор (керамический, скажем, 100 нФ) непосредственно между шинами питания ATtiny85 и как можно ближе физически к чипу. Возможно, причиной проблемы является пусковой ток на сглаживающем конденсаторе проигрывателя SD-карт при включении МОП-транзистора. Вы можете попробовать установить конденсатор емкостью 100 нф непосредственно между затвором мосфета и землей и резистор от 10 до 100 кОм между выводом Arduino и затвором, чтобы замедлить включение. Также найдите «attiny85 Brown Out Fuse». Это может зависеть от опций, предоставляемых используемым вами ядром Arduino., @6v6gt

while (ADCSRA & (1 << ADSC)) { SleepIfIdle(started) ; }. Я думаю, что он будет постоянно зависать в этом цикле, потому что переменная «started» не обновляется. Попробуйте сделать started глобальным и обновить его также в конце кода сна. Если проблема в этом, самостоятельно создайте ответ, показывающий изменения в коде., @6v6gt

Пожалуйста, сократите свой скетч до абсолютного минимума, чтобы кадры отображали поведение. Затем [отредактируйте] свой вопрос и добавьте этот код, пожалуйста., @the busybee

«Я начал обновление в начале цикла. Разве это не должно сработать?» Нет. started в начале цикла не будет выполнен, если он застрянет во внутреннем цикле. После выхода из спящего режима поток управления возвращается обратно по цепочке вызовов Sleep() -> SleepIfIdle() во внутренний цикл, не касаясь started. Однако похоже, что проблема не в этом. Какое ядро Arduino вы используете? Пожалуйста, добавьте это в описание проблемы., @6v6gt

Вы внесли много изменений в код, смотрите правки. Он по-прежнему ведет себя так же? Комментарии выше теперь, похоже, относятся к удаленному коду., @Nick Gammon

Вы сказали, что используете Attiny85 для снижения энергопотребления. Atmega328P имеет намного больше портов и по-прежнему потребляет только 100 нА в спящем режиме «выключение питания», и его все равно можно разбудить при замыкании переключателя. Это будет означать, что в этот корпус вам придется поместить гораздо меньше резисторов., @Nick Gammon

@NickGammon, я думаю, справедливо, но 85-й намного меньше, чем 328p. Длина хуже количества. Моя конечная цель — в любом случае сделать из нее печатную плату с smd-компонентами. Однако dfplayer может быть проблемой, поскольку он довольно большой., @boop

@6v6gt ты был прав. резистор между PB4 и затвором + керамический конденсатор между GND и затвором в конечном итоге заставили его работать. Ваши комментарии о функции сна были верными, и мне тоже удалось это исправить., @boop

Замечательно. Я оставляю вас представить свой пересмотренный код и расположение компонентов в качестве ответа, чтобы закрыть это дело. Это может помочь кому-то еще, и вы также должны получить несколько очков Брауни., @6v6gt

@Rohit Gupta - Вы можете выполнить раскраску синтаксиса с помощью <!-- Language: lang-c++ --> в качестве комментария (без отступа) непосредственно перед кодом. Это избавит вас от изменения отступа и необходимости добавлять обратные кавычки до и после него. Теперь он имеет чрезмерный отступ., @Nick Gammon

@boop Разница в размерах для устройств SMD составляет всего пару мм, но как вам удобно. Рад, что у вас все получилось!, @Nick Gammon


1 ответ


1

@6v6gt был тем, кто понял, чего не хватает.

Мне пришлось увеличить сопротивление 1 Ом между выходным контактом и затвором до 1 кОм и добавить керамический конденсатор емкостью 100 нФ между GND и затвором. В конце концов все сработало так, как и ожидалось.

Это полный код, который я в итоге использовал. Я постарался добавить достаточно комментариев, чтобы было понятно, что происходит.

#include <Arduino.h>
#include <avr/sleep.h>
#include <DFMiniMp3.h>
#include <avr/interrupt.h>
#include <avr/power.h>
// это просто "SoftwareSerial" без блокировки прерываний
// таким образом, отсюда вам придется вызвать handle_interrupt()
#include "LemonSerial.h" 
#include "mp3notify.h"

#define PIN_TX PB0
#define PIN_RX PB1
#define PIN_MOS PB2
#define PIN_LED PB3
#define PIN_A PB4
// включение плеера и его инициализация занимает довольно много времени
// поэтому не возвращайтесь в спящий режим сразу после каждого нажатия кнопки
uint32_t timeoutMs = 30000;
uint64_t started = 0;

// API dfplayer
LemonSerial secondarySerial(PIN_RX, PIN_TX);
DfMp3 dfmp3(secondarySerial);

void sleep();

uint8_t readADC()
{
  ADCSRA |= (1 << ADSC); // начинаем измерение АЦП
  while (ADCSRA & (1 << ADSC))
    ; // ждем завершения преобразования

  uint8_t adcValue = ADCH;
}

void initADC()
{
  ADMUX =
      (1 << ADLAR) | // результат сдвига влево
      (0 << REFS1) | // Устанавливает ссылку. напряжение до VCC, бит 1
      (0 << REFS0) | // Устанавливает ссылку. напряжение до VCC, бит 0
      (0 << MUX3) |  // используем АЦП2 для входа (PB4), бит 3 мультиплексора
      (0 << MUX2) |  // используем ADC2 для входа (PB4), бит MUX 2
      (1 << MUX1) |  // используем АЦП2 для входа (PB4), бит мультиплексора 1
      (0 << MUX0);   // используем АЦП2 для ввода (PB4), бит мультиплексора 0

  ADCSRA =
      (1 << ADEN) |  // Включаем АЦП
      (1 << ADPS2) | // устанавливаем прескалер на 128, бит 2
      (1 << ADPS1) | // устанавливаем прескалер на 128, бит 1
      (1 << ADPS0);  // устанавливаем прескалер на 128, бит 0
}

void setup()
{
  initADC();

  // устанавливаем INPUT_PULLUP, чтобы уменьшить текущее потребление
  pinMode(PB5, INPUT_PULLUP); // PB5 не присвоен ни одной константе
  pinMode(PIN_LED, INPUT_PULLUP);

  pinMode(PIN_A, INPUT); // На выводе АЦП физически установлен низкий уровень
  pinMode(PIN_MOS, OUTPUT);
  digitalWrite(PIN_MOS, LOW);

  started = millis();
}

/// @brief воспроизводит трек в dfplayer. включает плеер при необходимости
/// @param track номер трека для воспроизведения
void playTrack(uint16_t track)
{
  started = millis();

  if (digitalRead(PIN_MOS) == LOW)
  {
    digitalWrite(PIN_MOS, HIGH);
    delay(50); // ждем 50 мс, пока DfPlayer включится

    // скорость 4800 бод, потому что attiny работает на частоте 16 МГц (8 МГц -> 9600)
    dfmp3.begin(4800);
    dfmp3.setVolume(30);
  }

  dfmp3.playGlobalTrack(track);
}

void loop()
{
  uint8_t adcValue = readADC();

  if (adcValue >= 227 && adcValue <= 237)
  {
    // 10 кОм (3,0 В -> АЦП 232)
    playTrack(1);
  }
  else if (adcValue >= 212 && adcValue <= 223)
  {
    // 18 кОм (2,8 В -> АЦП 217)
    playTrack(2);
  }
  else if (adcValue >= 196 && adcValue <= 206)
  {
    // 27 кОм (2,6 В -> adc 201)
    playTrack(3);
  }
  else if (adcValue >= 181 && adcValue <= 191)
  {
    // 38 кОм (2,4 В -> АЦП 186)
    playTrack(4);
  }
  else if (adcValue >= 165 && adcValue <= 175)
  {
    // 50 кОм (2,2 В -> 170 АЦП)
    playTrack(5);
  }
  else if (adcValue >= 150 && adcValue <= 160)
  {
    // 65 кОм (2,0 В -> 155 АЦП)
    playTrack(6);
  }
  // здесь заканчивается каждый цикл, в котором не была нажата ни одна кнопка. так что в основном в 99,99% случаев
  else
  {
    // start получает значение millis() каждый раз при воспроизведении трека
    if (millis() - started > timeoutMs)
    {
      // спать, если прошло некоторое время без ввода (см. timeoutMs)
      sleep(); 
    }
  }
}

// переводит устройство в режим POWER DOWN. Возможно, вы сможете настроить его на более эффективную работу
void sleep()
{
  digitalWrite(PIN_MOS, LOW);

  // прерывание по смене контакта
  PCMSK |= bit(PIN_A);
  GIFR |= bit(PCIF);  // очищаем все ожидающие прерывания
  GIMSK |= bit(PCIE); // включаем прерывания по смене контактов

  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  power_all_disable(); // выключение АЦП, таймера 0 и 1, последовательного интерфейса
  sleep_enable();
  sleep_cpu();
  sleep_disable();
  power_all_enable(); // снова включаем все
}

// Подпрограмма обслуживания прерывания (Pin-Change-Interrupt)
ISR(PCINT0_vect)
{
  // SoftwareSerial больше не блокирует никакие контакты прерывания, и поэтому мы должны вызывать его отсюда
  LemonSerial::handle_interrupt();
}

// процедура обслуживания прерывания АЦП
ISR(ADC_vect)
{
  ; // нет
}

Это схема того, как все устроено сейчас. Я впервые нарисовал настоящую схему, поэтому надеюсь, что понятно, что происходит

,

RC-цепь, состоящая из конденсатора емкостью 100 нФ и резистора сопротивлением 1 кОм в цепи затвора мосфета, имеет постоянную времени 100 нс. Кажется, этого достаточно, чтобы "смягчить" включение мини DF плеера. Однако я считаю, что дополнительные конденсаторы, которые вы добавили (100 мкФ и 100 нФ) в настоящее время между Vcc и (переключаемым) заземлением плеера, на самом деле должны находиться между Vcc плеера и основным заземлением, в противном случае они также будут иметь пусковой ток, когда Мосфет включен. Почему, кстати, вы считаете нужным именно конденсатор емкостью 100 мкФ?, @6v6gt

@6v6gt хорошая мысль. Это было просто то, что я нашел, гугля. Хотя, возможно, я неправильно это понял. Он работает без этих заглушек, но насколько я понимаю, это помогает стабилизировать плеер. Стабилизация, например: меньше нежелательного шума/треска., @boop

Я не обязательно не согласен с конденсатором, а скорее с тем, как вы его подключили. Вы измеряли ток, когда устройство спит? Чаще всего модули, в данном случае DFplayer, переключаются на верхнюю сторону, то есть на их Vcc, а не на Gnd, и с использованием МОП-транзистора P-канала. Риск какого-либо паразитного питания выше, если земля переключена, поскольку более вероятно, что будет найден альтернативный путь к земле, что приведет к некоторому остаточному току потребления модулем. Если потребление тока невелико, конечно, вы можете игнорировать его., @6v6gt