VU метр на ILI9481

Alexandrid
Offline
Зарегистрирован: 05.09.2015

Всем привет. Хочу выводить на ILI9481 (480*320) VU метр или анализатор. Пока написал простой скетч VU метра и есть подозрения, что с анализатором будут тормоза. Кто-то делал что-то подобное на этом экране. Поделитесь скетчем красивого VU метра и анализатора.

Волшебник
Offline
Зарегистрирован: 22.12.2016

Чего анализировать? А ардуино какая?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Чего бы он ни анализировал, от экрана это никак не зависит.

Alexandrid
Offline
Зарегистрирован: 05.09.2015

Arduino Mega. Хочу сделать интересный VU метр для стерео усилителя. На ютюбе видел интересные графические решения, но кодом там не делятся.

Волшебник
Offline
Зарегистрирован: 22.12.2016

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 На УНО как-то писал, и дисплей 240х320, могу выложить если интересно

Alexandrid
Offline
Зарегистрирован: 05.09.2015

Очень интересно. Мне еще интересен стандартный VU столбцом с пиковыми значениями. Я сделал свой, но очень резко изменения отрабатывает или меджу двух значений перемигивается при определенных уровнях.

Мой VU meter https://youtu.be/60dR3q-1IVw

Я хочу подобие этого https://youtu.be/vQxXD6dpaCo

Волшебник
Offline
Зарегистрирован: 22.12.2016
Украли чужую библиотеку, вот и не выкладывают код. Не думают о последствиях, лошары.
Как Вам эта презентация, музыка немного не в такт, одна дорожка с интернета, другая ФМ:

https://youtu.be/r7Mm5jKa0qc

Alexandrid
Offline
Зарегистрирован: 05.09.2015

Хорошо сделано, но мне хотелось анализатор по 10-15 полос на канал и интересные варианты VU метра, где пиковые значения красиво изменяются. Да и вообще понять как лучше писать код, что бы тормозов не словить.

Волшебник
Offline
Зарегистрирован: 22.12.2016

Из больше полос сделать меньше? Трудная задача. 

По поводу оптимизации графики, могу из собственного опыта подсказать, что не надо перерисовывать весь экран каждый раз.

Недавно делал блок питания, посмотрите здесь http://forum.cxem.net/index.php?/topic/174170-%D1%86%D0%B8%D1%84%D1%80%D0%BE%D0%B2%D0%BE%D0%B8-%D0%BD%D0%B0-%D0%B7%D0%B2%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%BC-%D1%83%D1%81%D0%B8%D0%BB%D0%B8%D1%82%D0%B5%D0%BB%D0%B5/

Видите там 8 цифровых полей? Я пробовал сначала все их перерисовать, смотрелось просто отвратительно, мыргает экран, потом появляются поля цифр, одно за другим,  а поскольку мега не 1ГГц, на это уходило чуть не пол секунды. Потом мне пришла идея не рисовать все цифры, а по одной в 0.1 секунды. Человек всё одно не способен заметить изменения 40 цифр каждую секунду. Визуально всё просто "летать" стало, очень быстро, и экран не мерцает.

Такая же идея применена в спекто-анализаторе из видео ролика, все полоски не рисуются одновреммено, а их там 250 или около того, можете посметреть адафрутовский бенчмарк и сколько времени бы заняло рисование 250 линий. При том , что ардуино Уно там ещё БПФ-512 делает, в реальном масштабе времени.

Рисуются поочереди "группами", на глаз всё сливается.  

Короче пишите код, мы посмотрим и может чего подскажем

Alexandrid
Offline
Зарегистрирован: 05.09.2015
//#include <Wire.h>
//#include <PinChangeInt.h>
#include <LCD.h>
#include <LiquidCrystal.h>
#include <EEPROM.h>
#include <UTFT.h>                          // подключаем библиотеку UTFT
extern uint8_t SmallFont[];                // подключаем маленький шрифт
extern uint8_t BigFont[];                  // подключаем большой шрифт
extern uint8_t SevenSegNumFont[];          // подключаем шрифт имитирующий семисегментный индикатор

UTFT myGLCD(TFT32MEGA, 38, 39, 40, 41);    // задаем тип дисплея


//LED
int led = 9;           // the pin that the LED is attached to
int brightness = 0;    // how bright the LED is
int fadeAmount = 5;    // how many points to fade the LED by
// Init LCD
int s;
int l;
int lp;
int r;
int A0val;
int A1val;
int barl;
int barr;


void setup()
{ myGLCD.InitLCD();                        // инициируем дисплей
  // Serial.begin (9600);

  pinMode(A0, INPUT);
  pinMode(A4, INPUT);
  myGLCD.clrScr();



  myGLCD.setColor(VGA_WHITE);
  myGLCD.setFont(BigFont);
}
void loop()
{

  // Считывание значения напряжения
  A0val = analogRead(A0);
  A1val = analogRead(A5);
  barl = 20.0 / 1024.0 * int(A0val);
  barr = 20.0 / 1024.0 * int(A1val);


  // Вывод L

  // myGLCD.setColor(l*10,0,150);
  // myGLCD.fillRect(l*20,0,l*20+15,50);
  // myGLCD.setColor(VGA_BLACK);



  if ((l > barl))

  {
    myGLCD.setColor(VGA_BLACK);
    myGLCD.fillRect(l * 20 + 20, 0, l * 20 + 40, 50);

    l = l - 1;
    // myGLCD.setColor(VGA_BLACK);
    //myGLCD.fillRect(lp+5,0,lp+15,50);
    //myGLCD.fillRect(s*20+32,0,s*20+34,20);
    //delay(50);
  }

  if ((l < barl))
  { l = l + 1;

    //lp = l*20;

  }


  //Пик--------------------------------
  //s = barl * 10;

  if ((lp > l))
  {

    lp = (s / 10);
    s = s - 1;
  }

  if ((lp < l))
  {
    s = s + 10;
    lp = lp + 1 ;

  }


  if ((lp == l))
  {
    // myGLCD.setColor(255,255,150);
    ////  myGLCD.fillRect(lp*20+20,0,lp*20+35,50);
    // lp = lp - 1;

  }
  //пик
  myGLCD.setColor(255, 255, 150);
  myGLCD.fillRect(lp * 20 + 40, 0, lp * 20 + 55, 50);
  //черный
  myGLCD.setColor(VGA_BLACK);
  myGLCD.fillRect(lp * 20 + 60, 0, lp * 20 + 80, 50);

  //уровень
  myGLCD.setColor(l * 12, 0, 120);
  //myGLCD.setColor(l*10,0,150);
  myGLCD.fillRect(l * 20, 0, l * 20 + 15, 50);

  // myGLCD.print("  ", 14, 100);
  delay(30);



  // Вывод R------------------------------

  myGLCD.setColor(0, 100, 100);
  myGLCD.fillRect(r * 20, 200, r * 20 + 15, 250);
  //



  if ((r > barr))
  { myGLCD.setColor(VGA_BLACK);
    myGLCD.fillRect(r * 20 + 20, 200, r * 20 + 40, 250);
    // myGLCD.setColor(VGA_WHITE);
    r = r - 1;
    //myGLCD.fillRect(s*20+32,0,s*20+34,20);
    //delay(50);
  }

  if ((r < barr))
  { r = r + 1;




  }

  //   myGLCD.printNumI(s, 50, 130);


  //  myGLCD.printNumI(barl, 0, 100);
  //  myGLCD.printNumI(lp, 0, 130);
  //  myGLCD.printNumI(barr, 0, 150);
  // myGLCD.print("  ", 14, 100);


}



//-----------------------------------------------------------------

 

Alexandrid
Offline
Зарегистрирован: 05.09.2015

Это просто пробный набросок, очень много лишнего и буду переписывать по другому.

Выводит только изменения, без прорисовки всей графики.

Волшебник
Offline
Зарегистрирован: 22.12.2016

Первое, что бросается в глаза - вы не правильно меряете. Индикатор VU должен показывать среднеквадратичное значение (RMS), аудио сигнал - переменка а не постоянный ток, и просто аналогРеад тут не работает.  Тут как пример из скетча (Уно), дисплей 12864:

 
#include <glcd.h>                     // http://playground.arduino.cc/Code/GLCDks0108
#include <avr/pgmspace.h>

#include "fonts/allFonts.h"           // system and arial14 fonts are used
#include "bitmaps/allBitmaps.h"       // all images in the bitmap dir 

#define  SMP_RATE                   40       // Sampling Rate, in kHz
#define  SMP_TMR1 ((16000/SMP_RATE) -1)      // Sampling Period of Timer1

/* VU Meter / The audio level meter most frequently encountered is the VU meter. Its characteristics are 
defined as the ANSI specification C165. Some of the most important specifications for an AC meter
are its dynamic characteristics. These define how the meter responds to transients and how fast the reading 
decays. The VU meter is a relatively slow full-wave averaging type, specified to reach 99% deflection in
300 ms and overshoot by 1 to 1.5%. In engineering terms this means a slightly underdamped second order 
response with a resonant frequency of 2.1 Hz and a Q of 0.62. 
While several European organizations have specifications for peak program meters, the German DIN specification
45406 is becoming a de facto standard. Rather than respond instantaneously to peak, however, PPM specifications re-
quire a finite “integration time” so that only peaks wide enough to be audible are displayed. DIN 45406 
calls for a response of 1 dB down from steady-state for a 10 ms tone burst and 4 dB down for a 3 ms tone burst.
These requirements are consistent with the other frequently encountered spec of 2 dB down for a 5 ms burst and
are met by an attack time constant of 1.7 ms. The specified return time of 1.5s to −20 dB requires a 650 ms 
decay time constant.*/

            Image_t      icon;
            gText        countdownArea =  gText(GLCD.CenterX, GLCD.CenterY,1,1,Arial_14); 
            
             int16_t     adc_Offst =   512;
volatile     int32_t     ppm_Level =     0;
             float       rms_Level =   0.0;
//             int16_t     x10_coeff =    10;
           
ISR(TIMER1_COMPB_vect)
{ 
  int32_t temp = ADC - adc_Offst; 

          temp = temp * temp;
          
  if ( temp > ppm_Level ) ppm_Level = ((ppm_Level * 255) + temp) >> 8;
  else ppm_Level = (ppm_Level * 16383) >> 14;   
}

void   Draw_Table()
{
  GLCD.CursorToXY( 3,  3);
  GLCD.Puts("-20 10 5 3 1 0 1 2 3");
  GLCD.CursorToXY( 5, 52);
  GLCD.Puts("VU meter");
  GLCD.CursorToXY(75, 52);
  GLCD.Puts("Magician");
  GLCD.DrawRoundRect( 0, 0, 126, 63, 5);  
  GLCD.DrawLine( 64, 62, 5, 10, BLACK ) ;
  GLCD.DrawLine( 63, 62, 4, 10, BLACK ) ;
}

void   Draw_Arrow( int32_t scale )
{
  static int st1 = 5;
  static int st2 = 5;

  st2 = map( scale, 20, 300, 5, 122);     //  23.5 dB  

  if ( st2 > 122 ) st2 = 122;
  if ( st2 <   5 ) st2 =   5;
 
  if ( abs(st1 - st2) > 3 )               // 1/3 dB
  { 
  GLCD.DrawLine( 64, 62, st1, 10, WHITE ) ;
  GLCD.DrawLine( 63, 62, st1 -1, 10, WHITE ) ;

  GLCD.DrawLine( 64, 62, st2, 10, BLACK ) ;
  GLCD.DrawLine( 63, 62, st2 -1, 10, BLACK ) ;

  st1 = st2;
  }   
}

void setup()
{
  Serial.begin(115200);  
  GLCD.Init();   
  if(GLCD.Height >= 64)   
    icon = ArduinoIcon64x64;  // the 64 pixel high icon
  else
    icon = ArduinoIcon64x32;  // the 32 pixel high icon

  introScreen();
  GLCD.ClearScreen(); 
  GLCD.SelectFont(System5x7, BLACK);

  adc_Offst = analogRead(A5);        

  Draw_Table();
  
	/* Setup ADC */
        ADMUX    = 0x45;        // PIN 5 Analog. 

	ADCSRA = ((1<< ADEN)|	// 1 = ADC Enable
		  (0<< ADSC)|	// ADC Start Conversion 
		  (1<<ADATE)|	// 1 = ADC Auto Trigger Enable
		  (0<< ADIF)|	// ADC Interrupt Flag
		  (0<< ADIE)|	// ADC Interrupt Enable
		  (1<<ADPS2)|
		  (0<<ADPS1)|	// ADC Prescaler : 1 MHz.
		  (0<<ADPS0));  

	ADCSRB = ((1<<ADTS2)|   // Sets Auto Trigger source - Timer/Counter1 Compare Match B
		  (0<<ADTS1)|
		  (1<<ADTS0));

        /* Set up TIMER 1 - ADC sampler */
        TIMSK0 = 0x00;
        TIMSK1 = 0x00;
        TIMSK2 = 0x00; 

        TCCR1A = 0;
        TCCR1B = 0;
        TCCR1C = 0;

        TCCR1A =  ((1<<WGM11) | (1<<WGM10));       // Mode 15, Fast PWM
        TCCR1B =  ((1<<WGM13) | (1<<WGM12));       // Mode 15, Fast PWM

        TCCR1B |=  (1<<CS10);                      // clk/1 prescaling.
        OCR1A  = SMP_TMR1;
        OCR1B  = SMP_TMR1;

        TCNT1  = 0;
        TIFR1   |= (1<<OCF1B); 
        TIMSK1  |= (1<<OCIE1B);
}

void  loop()
{  
  char incomingByte;
  int32_t temp;
   
  temp = ppm_Level;                   // Take a copy, so Real Value not affected by calculation.
  temp = sqrt(temp);

  rms_Level = 20.0 * log10(temp +1);  // Calculated, available over Serial

  Draw_Arrow( temp );

   if (Serial.available() > 0) {   
    incomingByte = Serial.read();
    // "x" command - DEBUG
    if (incomingByte == 'x') {
       Serial.println("\n\t");
       Serial.println(adc_Offst, DEC);
       Serial.println(ppm_Level, DEC);
       Serial.println(rms_Level, 2);
      }
    if (incomingByte == 'c') {
      GLCD.ClearScreen(); 
      Draw_Table();
      }
   }
}

void countdown(int count){
  while(count--){  // do countdown  
    countdownArea.ClearArea(); 
    countdownArea.print(count);
    delay(1000);  
  }  
}

void introScreen(){  
  GLCD.DrawBitmap(icon, 32,0); 
  countdown(3);
  GLCD.ClearScreen();
  GLCD.SelectFont(Arial_14);    
  GLCD.CursorToXY(GLCD.Width/2 - 44, 3);
  GLCD.print("*** VU Meter ***");
  GLCD.DrawRoundRect(8,0,GLCD.Width-19,17, 5);   
  countdown(3);  
  GLCD.ClearScreen(); 
}

Идея в том, что на АЦП вход подается смещение - половина напряжение, и переменка расчитывается как корень квадратный из суммы квадратов. Смотрите в учебниках подробней. Потом добавлюется логарифмическая шкала.

Второе, задайте тактовую частоту, без неё я вообще не представляю как мерять или рисовать . В примере таймер инициирует измерения, и поскольку частота известна, к ней легко привязать обновлвние экрана введя простой счётчик. 

Alexandrid
Offline
Зарегистрирован: 05.09.2015

Читаю и не пойму с 94 по 128. Что там происходит?

Волшебник
Offline
Зарегистрирован: 22.12.2016

Там настраивается таймер на частоту 40 кГц, по теореме Котел'никова это минимальная частота для сэмплинга входного аудио сигнала до 20 кГц.

Потом настраивается АЦП работать по таймеру, быстро, конверсия аналог-цифра должна быть быстрее чем 25 микросек. И мерять на А5 - остальные заняты жадным до пинов экраном,

Все эти регистры расписаны в даташите на микропроцессор АТмега328. Погуглите , наверняка есть лекции на русском по настройке , или книжки. И меня тут с десяток книжек по ардуино завалялось, но все на английском.

Alexandrid
Offline
Зарегистрирован: 05.09.2015

Переписал скетч на мой индикатор, но если я вывожу на экран текст, то все начинает тормозить... И не очень пойму, для чего нужен rms_Level, если он к индикатору вообще не касается. Значит происходит вывод не среднеквадратичного значения?

Волшебник
Offline
Зарегистрирован: 22.12.2016

rms_Level у меня логарифмическое и читается через сериал монитор, сейчас и не помню почему на экран я выводил среднеквадратичное -линейное а на его. Короче, простое RMS оно среднеквадратичное, то которое надо для измерения переменки, после sqrt оно и вычисляется.

Но проблема со шкалами, если для вольтметра скажем шкала нужна линейная, 10, 20, 30 В и так далее, то для VU она обычно логарифмическая, в дБ меряется. Я там и вычислял в дБ, но на экран вывел Вольты, наверно для красоты.

Alexandrid
Offline
Зарегистрирован: 05.09.2015

Я делаю именно VU в db.

Любое мелкое изменение парализует всю работу, добавил знак умножения или еще мелочи и все сразу глючит.

Волшебник
Offline
Зарегистрирован: 22.12.2016

Это всё происки вражеских нейро-сетей.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Alexandrid пишет:

Любое мелкое изменение парализует всю работу, добавил знак умножения или еще мелочи и все сразу глючит.

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

И, в общем-то, я на это намекал уже в посте №2.