суббота, 8 сентября 2018 г.

Регулятор громкости: Arduino, энкодер и pt2257. Версии 2, 3, 4 и 5

Данная статья является продолжением этой статьи. Так сказать, работой над ошибками. Первое исправление коснулось схем, где я в этот раз исправил название микросхемы на pt2257 и изменил рисунок Arduino Uno на Arduino nano:






Функционально эти платы идентичны и собраны на одном микроконтроллере, поэтому скетчи (прошивки) подходят к обоим. Также подойдут другие платы Arduino и их клоны, собранные на микроконтроллерах ATmega168/328, например, Arduino mini, Arduino pro mini, Arduino pro и т.д.
Далее пойдут изменения, коснувшиеся программной части. Выбирайте тот скетч, который Вам больше нравится по функционалу:
Версия 2:
В данном скетче часть программы, отвечающая за передачу данных от микроконтроллера в микросхему регулировки громкости выведена в отдельную функцию (подпрограмму) и обращаться к ней после каждого поворота ручки энкодера или нажатия на его кнопку. Это позволило чуть-чуть уменьшить размер скетча. Также я заметил, что в режиме "MUTE" звук продолжает регулироваться. В некоторых случаях это очень удобно: допустим у Вас в семье спит маленький ребенок или кто-то из домочадцев, а Вы захотели тихонько послушать музыку. Включаете магнитофон, а звук по умолчанию, громче желаемого, и чтобы не крутить 1-3 оборота энкодера, плавно уменьшая громкость, вы нажимаете на ручку регулировки громкости, звук выключается. Вы уменьшаете громкость (в режиме "MUTE") и нажимаете еще раз на ручку энкодера. Включается музыка, но уже намного тише. 
Текст программы версии 2:

#include <Wire.h>

/********************************************************
**   Для управлением громкостью используется энкодер   **
**                    Arduino и pt2257  v2             **                  
** Вращением энкодера регулируется громкость, нажатием **                  
**       кнопки включается-отключается режим "MUTE"    **
**          Выключение "Mute" только кнопкой.          **
** Регулировка громкости в режиме "Mute" изменяется    **      
**                        http://mynobook.blogspot.com **                
*********************************************************/
 
int Volume=40;       // Начальная громкость составляет половину (50%)
int Mute=0;          // Отключение звука
int fadeAmount = 3;        // шаг изменения громкости
unsigned long currentTime;
unsigned long loopTime;
const int pin_A = 12;       // Подключение вывода A (CLK) энкодера
const int pin_B = 11;       // Подключение вывода B (DT) энкодера
const int pin_SW = 9;       // Подключение вывода кнопки (SW) энкодера
unsigned char encoder_A;
unsigned char encoder_B;
unsigned char encoder_A_prev=0;

int i2c_write(int Az){ // функция записи данных в pt2257 121 - MUTE ON, 120 -MUTE OFF, 0 - Volume
    Wire.beginTransmission(0x44);  // i2c адрес pt2257
if (Az == 0)   {  Wire.write(Volume/10+224);   // Отправляем десятки громкости в pt2257 значение 224-231(00-70)
              Wire.write(Volume%10+208);}   // Отправляем единицы громкости в pt2257 значение 208-217(0-9)
else Wire.write(Az);
    Wire.endTransmission(); 
  }
  

void setup()  {
  Wire.begin();
  pinMode(pin_SW,INPUT);  // устанавливаем pin pin_SW как вход
  digitalWrite(pin_SW,HIGH); // Подтяжка вывода к 1         
  pinMode(pin_A, INPUT);
  pinMode(pin_B, INPUT);
  currentTime = millis();
  loopTime = currentTime;
  i2c_write(0); // Задаем громкость при включении
 
} 
 
void loop()  {
  currentTime = millis();
  if(currentTime >= (loopTime + 5)){ // проверяем каждые 5мс (200 Гц)
    encoder_A = digitalRead(pin_A);     // считываем состояние выхода А энкодера 
    encoder_B = digitalRead(pin_B);     // считываем состояние выхода B энкодера    
    if((!encoder_A) && (encoder_A_prev)){    // если состояние изменилось с положительного к нулю
      if(encoder_B) {
        // выход В в полож. сост., значит вращение по часовой стрелке
        // увеличиваем громкость, не более чем до 79 и записываем в pt2257
        if(Volume + fadeAmount <= 79) {Volume += fadeAmount;
                                      i2c_write(0);
                                      }               
      }   
      else {
        // выход В в 0 сост., значит вращение против часовой стрелки     
        // уменьшаем яркость, но не ниже 0 и записываем в pt2257
        if(Volume - fadeAmount >= 0) {Volume -= fadeAmount;               
                                     i2c_write(0); 
                                     }
      }   
 
    }   
    encoder_A_prev = encoder_A;     // сохраняем значение А для следующего цикла 
    if  (digitalRead(pin_SW) == 0) // Если нажата кнопка то выключить или включить звук 
    {
     if (Mute == 0) 
     {
      Mute = 1;
      while (digitalRead(pin_SW) == 0) delay(100);   //  Дождаться отпускания кнопки
      i2c_write(121);
 
     }
     else {
      while (digitalRead(pin_SW) == 0) delay(100);  //  Дождаться отпускания кнопки
      Mute = 0;
      i2c_write(120);
      
     }     
    }

    
    loopTime = currentTime;
  }                       
}
Скачать скетч можно тут.

Версия 3:
От версии 2 отличается тем, что в режиме "MUTE" регулировка громкости не осуществляется. Т.е. на какой громкости вы включили режим "MUTE" на такой еe и включите, независимо от того, крутили ли Вы в этом режиме ручку регулировки громкости или нет.
Текст программы версии 3:

#include <Wire.h>

/********************************************************
**   Для управлением громкостью используется энкодер   **
**                    Arduino и pt2257  v3             **                  
** Вращением энкодера регулируется громкость, нажатием **                  
**       кнопки включается-отключается режим "MUTE"    **
**          Выключение "Mute" только кнопкой.          **
** Регулировка громкости в режиме "Mute" не изменяется **      
**                        http://mynobook.blogspot.com **                
*********************************************************/
 
int Volume=40;       // Начальная громкость составляет половину (50%)
int Mute=0;          // Отключение звука
int fadeAmount = 3;        // шаг изменения громкости
unsigned long currentTime;
unsigned long loopTime;
const int pin_A = 12;       // Подключение вывода A (CLK) энкодера
const int pin_B = 11;       // Подключение вывода B (DT) энкодера
const int pin_SW = 9;       // Подключение вывода кнопки (SW) энкодера
unsigned char encoder_A;
unsigned char encoder_B;
unsigned char encoder_A_prev=0;

int i2c_write(int Az){ // функция записи данных в pt2257 121 - MUTE ON, 120 -MUTE OFF, 0 - Volume
    Wire.beginTransmission(0x44);  // i2c адрес pt2257
if (Az == 0)   {  Wire.write(Volume/10+224);   // Отправляем десятки громкости в pt2257 значение 224-231(00-70)
              Wire.write(Volume%10+208);}   // Отправляем единицы громкости в pt2257 значение 208-217(0-9)
else Wire.write(Az);
    Wire.endTransmission(); 
  }
  

void setup()  {
  Wire.begin();
  pinMode(pin_SW,INPUT);  // устанавливаем pin pin_SW как вход
  digitalWrite(pin_SW,HIGH); // Подтяжка вывода к 1         
  pinMode(pin_A, INPUT);
  pinMode(pin_B, INPUT);
  currentTime = millis();
  loopTime = currentTime;
  i2c_write(0); // Задаем громкость при включении
 
} 
 
void loop()  {
  currentTime = millis();
  if(currentTime >= (loopTime + 5)){ // проверяем каждые 5мс (200 Гц)
    encoder_A = digitalRead(pin_A);     // считываем состояние выхода А энкодера 
    encoder_B = digitalRead(pin_B);     // считываем состояние выхода B энкодера    
    if((!encoder_A) && (encoder_A_prev)&&(Mute == 0)){    // если состояние изменилось с положительного к нулю
      if(encoder_B) {
        // выход В в полож. сост., значит вращение по часовой стрелке
        // увеличиваем громкость, не более чем до 79 и записываем в pt2257
        if(Volume + fadeAmount <= 79) {Volume += fadeAmount;
                                      i2c_write(0);
                                      }               
      }   
      else {
        // выход В в 0 сост., значит вращение против часовой стрелки     
        // уменьшаем яркость, но не ниже 0 и записываем в pt2257
        if(Volume - fadeAmount >= 0) {Volume -= fadeAmount;               
                                     i2c_write(0); 
                                     }
      }   
 
    }   
    encoder_A_prev = encoder_A;     // сохраняем значение А для следующего цикла 
    if  (digitalRead(pin_SW) == 0) // Если нажата кнопка то выключить или включить звук 
    {
     if (Mute == 0) 
     {
      Mute = 1;
      while (digitalRead(pin_SW) == 0) delay(100);   //  Дождаться отпускания кнопки
      i2c_write(121);
 
     }
     else {
      while (digitalRead(pin_SW) == 0) delay(100);  //  Дождаться отпускания кнопки
      Mute = 0;
      i2c_write(120);
      
     }     
    }

    
    loopTime = currentTime;
  }                       
}
Скачать скетч можно тут.

Версия 4:
Режим "MUTE" включается кнопкой энкодера, выход из режима осуществляется либо повторным нажатием кнопки, либо поворотом ручки регулировки громкости в любую сторону. На мой взгляд, самый удобный алгоритм, из всех ранее предложенных. 
Текст программы версии 4:

#include <Wire.h>

/********************************************************
**   Для управлением громкостью используется энкодер   **
**                    Arduino и pt2257  v4             **                  
** Вращением энкодера регулируется громкость, нажатием **                  
**       кнопки включается-отключается режим "MUTE"    **
**  Выключение "Mute" кнопкой или поворотом энкодера.  **   
**                        http://mynobook.blogspot.com **                
*********************************************************/
 
int Volume=40;       // Начальная громкость составляет половину (50%)
int Mute=0;          // Отключение звука
int fadeAmount = 3;        // шаг изменения громкости
unsigned long currentTime;
unsigned long loopTime;
const int pin_A = 12;       // Подключение вывода A (CLK) энкодера
const int pin_B = 11;       // Подключение вывода B (DT) энкодера
const int pin_SW = 9;       // Подключение вывода кнопки (SW) энкодера
unsigned char encoder_A;
unsigned char encoder_B;
unsigned char encoder_A_prev=0;

int i2c_write(int Az){ // функция записи данных в pt2257 121 - MUTE ON, 120 -MUTE OFF, 0 - Volume
    Wire.beginTransmission(0x44);  // i2c адрес pt2257
if (Az == 0)   {  Wire.write(Volume/10+224);   // Отправляем десятки громкости в pt2257 значение 224-231(00-70)
              Wire.write(Volume%10+208);}   // Отправляем единицы громкости в pt2257 значение 208-217(0-9)
else Wire.write(Az);
    Wire.endTransmission(); 
  }
  

void setup()  {
  Wire.begin();
  pinMode(pin_SW,INPUT);  // устанавливаем pin pin_SW как вход
  digitalWrite(pin_SW,HIGH); // Поддяжка вывода к 1         
  pinMode(pin_A, INPUT);
  pinMode(pin_B, INPUT);
  currentTime = millis();
  loopTime = currentTime;
  i2c_write(0); // Задаем громкость при включении
 } 
 
void loop()  {
  currentTime = millis();
  if(currentTime >= (loopTime + 5)){ // проверяем каждые 5мс (200 Гц)
    encoder_A = digitalRead(pin_A);     // считываем состояние выхода А энкодера 
    encoder_B = digitalRead(pin_B);     // считываем состояние выхода B энкодера    
    if((!encoder_A) && (encoder_A_prev)){    // если состояние изменилось с положительного к нулю
     if (Mute == 1) {Mute=0; i2c_write(120);} // если включен режим "Mute" то выключить его, иначе регулировать громкость
     else if(encoder_B) {
        // выход В в полож. сост., значит вращение по часовой стрелке
        // увеличиваем громкость, не более чем до 79 и записываем в pt2257
        if(Volume + fadeAmount <= 79) {Volume += fadeAmount;
                                      i2c_write(0);
                                      }               
      }   
      else {
        // выход В в 0 сост., значит вращение против часовой стрелки     
        // уменьшаем яркость, но не ниже 0 и записываем в pt2257
        if(Volume - fadeAmount >= 0) {Volume -= fadeAmount;               
                                     i2c_write(0); 
                                     }
      }   
     }   
    encoder_A_prev = encoder_A;     // сохраняем значение А для следующего цикла 
    if  (digitalRead(pin_SW) == 0) // Если нажата кнопка то выключить или включить звук 
    {
     if (Mute == 0) 
     {
      Mute = 1;
      while (digitalRead(pin_SW) == 0) delay(100);   //  Дождаться отпускания кнопки
      i2c_write(121);
 
     }
     else {
      while (digitalRead(pin_SW) == 0) delay(100);  //  Дождаться отпускания кнопки
      Mute = 0;
      i2c_write(120);
      
     }     
    }   
    loopTime = currentTime;
  }                       
}
Скачать скетч можно тут.

Версия 5:
Самое интересное, как всегда в конце. Во всех предыдущих версиях программ громкость при включении была фиксированной и составляла 50% от максимальной. Первоначальный уровень громкости задавался в первой строке программы:
int Volume=40; 
Но микроконтроллеры ATmega168/328 имеют собственную энергонезависимую память EEPROM. Таким образом, добавив в программу библиотеку EEPROM.h удалось добиться того, что при включении магнитофона громкость такая, какая была при выключении. Алгоритм включения-выключения режима "MUTE" полностью соответствует описанному в программе версии 4. Т.е. режим "MUTE" включается кнопкой энкодера, а выход из режима осуществляется либо повторным нажатием кнопки, либо поворотом ручки регулировки громкости в любую сторону.
Текст программы версии 5:

#include <Wire.h>
#include <EEPROM.h>
/********************************************************
**   Для управлением громкостью используется энкодер   **
**                    Arduino и pt2257  v5             **                  
** Вращением энкодера регулируется громкость, нажатием **                  
**       кнопки включается-отключается режим "MUTE"    **
**    Выключение "Mute" кнопкой и поворотом энкодера.  **   
**    Запоминает значение громкости при выключении     **
**                        http://mynobook.blogspot.com **                        **                
*********************************************************/
 
int Volume;          // Переменная уровня громкости
int Mute=0;          // Переменная значения "Mute" (Отключение звука) - 0 отключено, 1 включено
int fadeAmount = 3;        // шаг изменения громкости
unsigned long currentTime;
unsigned long loopTime;
const int pin_A = 12;       // Подключение вывода A (CLK) энкодера
const int pin_B = 11;       // Подключение вывода B (DT) энкодера
const int pin_SW = 9;       // Подключение вывода кнопки (SW) энкодера
unsigned char encoder_A;
unsigned char encoder_B;
unsigned char encoder_A_prev=0;

int i2c_write(int Az){ // функция записи данных в pt2257 121 - MUTE ON, 120 -MUTE OFF, 0 - Volume
    Wire.beginTransmission(0x44);  // i2c адрес pt2257
if (Az == 0)   {  Wire.write(Volume/10+224);   // Отправляем десятки громкости в pt2257 значение 224-231(00-70)
              Wire.write(Volume%10+208);   // Отправляем единицы громкости в pt2257 значение 208-217(0-9)
              EEPROM.write(0, Volume);}  // Сохраняем значение громкости во встроенную память EEPROM
else Wire.write(Az);
    Wire.endTransmission(); 
  }
  

void setup()  {
  Wire.begin();
  pinMode(pin_SW,INPUT);  // устанавливаем pin pin_SW как вход
  digitalWrite(pin_SW,HIGH); // Поддяжка вывода к 1         
  pinMode(pin_A, INPUT);
  pinMode(pin_B, INPUT);
  currentTime = millis();
  loopTime = currentTime;
   Volume=EEPROM.read(0);
   if (Volume == 255) Volume = 40; //Если считано значение 255, то установить громкость 50%
  i2c_write(120); // "Mute" при включении отключен
  i2c_write(0); // Задаем громкость при включении
 } 
 
void loop()  {
  currentTime = millis();
  if(currentTime >= (loopTime + 5)){ // проверяем каждые 5мс (200 Гц)
    encoder_A = digitalRead(pin_A);     // считываем состояние выхода А энкодера 
    encoder_B = digitalRead(pin_B);     // считываем состояние выхода B энкодера    
    if((!encoder_A) && (encoder_A_prev)){    // если состояние изменилось с положительного к нулю
     if (Mute == 1) {Mute=0; i2c_write(120);} // если включен режим "Mute" то выключить его, иначе регулировать громкость
     else if(encoder_B) {
        // выход В в полож. сост., значит вращение по часовой стрелке
        // увеличиваем громкость, не более чем до 79 и записываем в pt2257
        if(Volume + fadeAmount <= 79) {Volume += fadeAmount;
                                      i2c_write(0);
                                      }               
      }   
      else {
        // выход В в 0 сост., значит вращение против часовой стрелки     
        // уменьшаем яркость, но не ниже 0 и записываем в pt2257
        if(Volume - fadeAmount >= 0) {Volume -= fadeAmount;               
                                     i2c_write(0); 
                                     }
      }   
     }   
    encoder_A_prev = encoder_A;     // сохраняем значение А для следующего цикла 
    if  (digitalRead(pin_SW) == 0) // Если нажата кнопка то выключить или включить звук 
    {
     if (Mute == 0) 
     {
      Mute = 1;
      while (digitalRead(pin_SW) == 0) delay(100);   //  Дождаться отпускания кнопки
      i2c_write(121);
 
     }
     else {
      while (digitalRead(pin_SW) == 0) delay(100);  //  Дождаться отпускания кнопки
      Mute = 0;
      i2c_write(120);
      
     }     
    }   
    loopTime = currentTime;
    Serial.print(EEPROM.read(0));
  }                       
}
Скачать скетч можно тут.

На этом все. До новых встреч.

Upd 31.05.2022:Выложил файлы в первых комментариях к данной статье на нашем телеграмм канале. Не забудьте подписаться на канал!

18 комментариев:

  1. Спасибо за труд. Заказал микруху и енкодер. Хочу повторить.
    Больше бы таких изобретателей.

    ОтветитьУдалить
  2. Огромное спасибо. Все детали заказал. Как прийдет и сделаю. Напишу

    ОтветитьУдалить
  3. Этот комментарий был удален автором.

    ОтветитьУдалить
  4. Интересное решение, уже месяц ищу подобное, будем пробовать. Энкодер определенный или любой пойдет? И еще вопрос, вы можете дописать скетч для управления адресной светодиодной лентой? Остался небольшой кусок ленты, на ее основе можно реализовать круговую индикацию положения энкодера. Т.е. Добавляем громкость светодиоды по кругу вокруг ручки включаются, убавляем выключаются.

    ОтветитьУдалить
    Ответы
    1. Энкодер любой, с функцией нажатия. У меня нет адресной светодиодной ленты, поэтому под нее писать программу пока не буду.

      Удалить
  5. https://m.youtube.com/watch?v=DydNINFv548. Вот что то типа этого

    ОтветитьУдалить
    Ответы
    1. Для этого необязательно использовать адресно-управляемые светодиоды. У меня одной где-то есть программа с семисегментным индикатором, таким на основе платы со статьи "74НС595 и 7-ми сегментный светодиодный дисплей".
      Найду - выложу. Заменить индикатор на светодиоды - дело 10 мин.

      Удалить
  6. Сделал регулятор от кнопок пульта телевизора, скетч на основе вашего. Ньюансы: при увеличении Volume громкость уменьшается; диапазон получился не от 0 до 79, а до 40 (0 это максимум, а на 40 уже ничего не слышно). Почему так?

    ОтветитьУдалить
    Ответы
    1. Я же не экстрасенс. Не могу догадаться что не так. Покажите скетч - попробуем разобраться.

      Удалить
  7. У меня вопрос: если место энкодера использовать кнопки без фиксации, скетч без корректировки будет работать?

    ОтветитьУдалить
    Ответы
    1. Нет не будет. Нужно менять в программе, чтоб при нажатии кнопки "+" значение переменной Volume увеличивалось на значение переменной fadeAmount, а при нажатии кнопки "-" - уменьшалось...

      Удалить
  8. Всем привет! Автору есть вопрос, решил повторить, всё сделал, как описано, а громкость не регулируется

    ОтветитьУдалить
    Ответы
    1. PT2257 рабочая? На i2c запросы отвечает? (Попробуйте ее просканировать https://robotchip.ru/i2c-skaner-na-arduino/ )

      Удалить
    2. Спасибо за совет! как проверю, - отпишусь. На связи!

      Удалить
    3. Еще чтобы исключить неисправность обвязки или ошибки монтажа просто подключите PT2257 к ардуино 4 проводниками (SDA, SCL, GND и питание) и просканируйте еще раз.

      Удалить
  9. Походу, купил бракованную. Дивайс нот фоунд, йопта... Буду заказывать снова ((

    ОтветитьУдалить
  10. Привет подскажи .я могу подключить регулировку собранную на ардуинке к колонкам компьютера. Там используется микросхем SJ2882M. К каким контактам подключить А4 иА5 ???

    ОтветитьУдалить
    Ответы
    1. SJ2882M (TDA2822) - это обычный УНЧ. В нем нет функции регулировки громкости по шине I2С
      А4 и А5 подключаются к pt2257, а вся схема включается между выходом ПК и Вашими колонками

      Удалить