Микроконтроллер зависает при срабатывании затвора 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)
{
; // нет
}
@boop, 👍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
- Программирование сервопривода на ATtiny85
- Digispark ATtiny 85 - не распознается как HID устройство
- Клавиатура Digispark ATtiny85
- Настройка опорного напряжения АЦП ATtiny88
- Digispark Micro (ATTINY85) не работает на Macbook Pro 2016 г.
- Как правильно читать АЦП на ATtiny85?
- Digispark Attiny85 потребляет больше энергии для цифрового ввода, чем ожидалось
- ATtiny85 USB Устранение неполадок. Устройство не распознается, когда программатор просит подключить устройство
Есть ссылка на техническое описание МОП-транзистора, который вы используете?, @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