搜索
bottom↓
回复: 33

LCD-based VU meter / spectrum analyzer

[复制链接]

出0入0汤圆

发表于 2010-5-9 21:26:53 | 显示全部楼层 |阅读模式
many people built LED based vu meter, using analog solutions that tend to be complicated.

since many mcus have built-in adc, why not build a mcu-based vu meter?

here is an example of a vu meter built using a pic driving a 16x2 LCD module.

the mcu will adc two channels, left and right, and display the output, as two bars of characters, on a 16x2 lcd display module.

the output is user-defined and in this particular case logarithmic: the first character lights up at 1mv of input, and the 14 characters light up at about 5v, for a dynamic range of about 40db, far greater than most vu meter.


(原文件名:lcd vu meter.JPG)

here is the code
#include <htc.h>
#include <string.h>
#include "delay.h"
//#include "lcd_4bit.h"
#include "lcd_3wi.h"

__CONFIG(MCLRDIS & BORDIS & WDTDIS & PWRTEN & INTIO);

#define sleep()                asm("sleep")                //put the mcu to sleep
#define ADC_CH0                (1<<0)                                //adc on an0
#define ADC_CH1                (1<<1)                                //adc on an1
#define ADC_L                ADC_CH0                                //adc_left on adc0
#define ADC_R                ADC_CH1                                //adc_right on adc1
#define LCD_Line_L        LCD_Line0                        //L ch on lcd_line0
#define LCD_Line_R        LCD_Line1                        //R ch on lcd_line1
#define LED_PIN                (1<<5)                                //led on GPIO1
#define PA_SET(bits)        PORTA |= (bits);        //set bits on porta
#define PA_CLR(bits)        PORTA &=~(bits);        //clear bits on porta
#define PA_FLP(bits)        PORTA ^= (bits);        //flip bits on porta
#define DLY_Main_Loop        50                                //main loop delay of 10ms
#define LCD_char        0x05                                        //lcd char
#define LCD_char_B        ' '                                        //lcd background

const unsigned char str_L[]="L:              ";
const unsigned char str_R[]="R:              ";
const unsigned char str_0[]="16F684 VU Meter1";
const unsigned char str_v[]="version 0.1-1234";
const unsigned int val2ln[]={
        1, 2, 3, 5, 8, 14, 25, 42,
        71, 121, 207, 352, 600, 1022};       
unsigned char vRAM[17];

typedef struct {
        unsigned char char_code;
        unsigned char char_pattern[8];
} LCD_CGRAM_CHAR;

const LCD_CGRAM_CHAR LCD_CGRAM_CHAR_PATTERN[]={
        {0, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},        //empty background
        {1, {0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x00}},        //1 vertical line
        {2, {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00}},        //2 vertical line
        {3, {0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x00}},        //3 vertical line
        {4, {0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x00}},        //4 vertical line
        {5, {0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x00}},        //5 vertical line = block / bar
        {6, {0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x00}},
        {7, {0x1f,0x1f,0x1f,0x1b,0x1f,0x1f,0x1f,0x00}}
//        {7, {0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x00}}
};

void lcd_cgram_init(void) {                                        //initiate the cgram
        unsigned char i, j;
       
        for (i=0; i<8; i++) {
//                lcd_cgram_write(LCD_CGRAM_CHAR_PATTERN);
                lcd_write(0x40+(LCD_CGRAM_CHAR_PATTERN.char_code*8), 0);        //send the command
                for(j=0;j<8;j++)
                        lcd_write(LCD_CGRAM_CHAR_PATTERN.char_pattern[j], 1);                //send the bigmap data
     }
}

void adc_init(void) {                                        //initialize the adc
//        ADCON0 = config_mask;
        ADCS2=0, ADCS1=1, ADCS0=0;                        //adc running at fosc/32
        ADFM=1;                                                                //adc results right justified
        VCFG=0;                                                                //Vdd as reference voltage
        ADON=0;                                                                //adc off
}

unsigned int adc_read(unsigned char ch){//read adc
        ANSEL |= ch;                                                        //select the adc channel, and adc at fosc/32
//        TRISIO |= ch;                                                //turn it as input
        CHS1=(ch-1) >> 1;                                                //select the channel
        CHS0=(ch-1);
        ADON=1; delay_us(1);                                                                //turn on the adc
        GODONE=1;
        while (GODONE) continue;                                //wait for the adc to complete;
        ADON=0;                                                                //turn off the adc
        return (ADRESH<<8) + ADRESL;
}

void ultoa(char *s, unsigned long ul, unsigned char length) {        //convert unsigned long to a string, 3 dps
        unsigned char i;
       
        i=length;
        do {
//                if (i==(length-3)) s[i--]='.';
                s[i--]=ul % 10 + '0';
                ul = ul / 10;
        } while (ul);
}

void mcu_init(void) {
        CMCON0=0x07;                                //turn off comparators;
        //TRISIO=0b11;                        //input on GPIO0, all others digital output;
        ANSEL=0x00;                                //all ports gpio
        IRCF2=1, IRCF1=1, IRCF0=0;        //4Mhz
}

void bar_display(unsigned char lcd_line, unsigned int val) {        //display a value
        unsigned char i;
        unsigned char vstr[17];
       
        for (i=0; i<14; i++) {                        //fill the lcd array
                if (val<val2ln) vstr=LCD_char_B;
                else vstr=LCD_char;
        }
//        ultoa(vstr, val, 10);
        lcd_display(lcd_line+2, vstr);
}

void main(void)
{
        //unsigned char temp=0, gSPad[9], i;
        unsigned int i;
        //unsigned int val_L, val_R;
       
        mcu_init();                                //initialize the mcu
        lcd_init();                                //initialize the lcd
        lcd_cgram_init();                //initialize the cgram for 8 user definable characters, 0..7
        adc_init();                                //initialize the adc
        strcpy(vRAM, str_0); lcd_display(LCD_Line0, vRAM);
        strcpy(vRAM, str_v); lcd_display(LCD_Line1, vRAM); delay_ms(50);
        strcpy(vRAM, str_L); lcd_display(LCD_Line_R, vRAM);
        strcpy(vRAM, str_R); lcd_display(LCD_Line_L, vRAM);
//        sleep();
        while (1){
                //TODO Auto-generated main function
                bar_display(LCD_Line_L, adc_read(ADC_L));
                bar_display(LCD_Line_R, adc_read(ADC_R));
                delay_us(DLY_Main_Loop);                        //delay some time
        }
}

出0入0汤圆

 楼主| 发表于 2010-5-9 21:29:59 | 显示全部楼层
the code displays 14 distinct bars: 16 characters per row, minus two characters per row.

the lcd module allows the use of user defined characters. with that, you can define each vertical line separately. since each character has 5 lines, that allows the use of 5*14 = 70 distinct resolution, per channel.

I included those functions in the code, but did not implement them - those are the CGRAM functions in the above code.

出0入0汤圆

 楼主| 发表于 2010-5-9 21:31:03 | 显示全部楼层
another way to improve this code is to create a spectrum analyzer: you can have 16 bandpass filters, and then digitize their output with a mcu, and then display the output on the lcd.

that's my next project, :)

出0入0汤圆

 楼主| 发表于 2010-5-9 22:50:30 | 显示全部楼层
how about this one? 16 channel spectrum analyzer. each channel has 14 bits of resolution.


(原文件名:lcd spectrum analyzer.JPG)

出0入0汤圆

 楼主| 发表于 2010-5-9 22:52:56 | 显示全部楼层
one additional feature that you can add to the programs is to add a "peak-and-hold" function where the meter will display the peak, and gradually pull it back / down, like you see in some professional meters.

出0入0汤圆

发表于 2010-5-10 00:44:18 | 显示全部楼层
如果是楼主整的,还是讲中文的好。
否则,还是放出来源网址吧。

出0入618汤圆

发表于 2010-5-10 01:12:48 | 显示全部楼层
回复【5楼】mlhorizon 阿盟
如果是楼主整的,还是讲中文的好。
否则,还是放出来源网址吧。
-----------------------------------------------------------------------

楼主从来都是讲英文的……

出0入0汤圆

发表于 2010-5-10 03:19:44 | 显示全部楼层
平时不怎么见LZ发主题帖,特意跑过来支持。。

出0入0汤圆

 楼主| 发表于 2010-5-10 04:41:24 | 显示全部楼层
here is the latest run, where the mcu will display 70 distinct bars, rather than just 14 blocks, :).

the next is to add the peak-and-hold functionality.

enjoy.


(原文件名:lcd vu meter - 70 bars.JPG)

出0入0汤圆

发表于 2010-5-10 09:00:22 | 显示全部楼层
nice~!

出0入0汤圆

发表于 2010-5-10 09:22:19 | 显示全部楼层
回复【6楼】gzhuli  咕唧霖
回复【5楼】mlhorizon 阿盟
如果是楼主整的,还是讲中文的好。
否则,还是放出来源网址吧。
-----------------------------------------------------------------------
楼主从来都是讲英文的……
-----------------------------------------------------------------------

抱歉,误会楼主显摆了。

出0入0汤圆

发表于 2010-5-10 20:31:38 | 显示全部楼层
喔,你又开猛贴了
Qian lai zhichi louzhu.

出0入0汤圆

发表于 2010-5-11 00:35:07 | 显示全部楼层
用 16x2 LCD 制作双声道电平表、16通道频谱分析器,还要加上峰值保持。
顶楼主 millwood 。

出0入0汤圆

发表于 2010-5-11 00:46:12 | 显示全部楼层
up!

出0入0汤圆

 楼主| 发表于 2010-5-11 07:15:34 | 显示全部楼层
"用 16x2 LCD 制作双声道电平表、16通道频谱分析器,还要加上峰值保持。 "

peak-and-hold isn't difficult to implement. and multi-channel spectrum analyzer isn't difficult to do, on a small mcu, IF you utilize bandpass amplifiers outside of the mcu.

but bandpass amps aren't a smart solution - if you can find fast integer FFT routines, you should be able to implement a spectrum analyzer on a medium sized mcu.

that would be really cool, :).

出0入0汤圆

发表于 2010-5-11 09:05:47 | 显示全部楼层
这个1602太神了,不知道TAOBAO上有卖么?

最近LZ不在HIFIDIY混了?

出0入0汤圆

 楼主| 发表于 2010-5-15 08:17:21 | 显示全部楼层
sometimes, you may want to add a (pseudo) random number generator in order to see how your program works. here is the one that I use:

=========code===============
unsigned long random_vN(void) {        //using von Neuman middle square approach to return a random number
        static unsigned long seed=5634;
        seed=((seed*seed) & 0x00ffff00) >> 8;
        return seed;
}
============================

it uses the von Neuman middle square approach, and generates a 16-bit seemingly random number, and updates its own seed.

you can adjust the initial value of seed to tweak the behavior of the random number generator.

the execution time is less than 1ms on a 4Mhz 16F684.

出0入0汤圆

 楼主| 发表于 2010-5-16 02:35:24 | 显示全部楼层
there actually is a bug in the code I posted earlier: if the adc_read() equals to multiples of 5, the code would not properly display the bargraph.

here is the revised code:

1) it now calculates its own moving average. No need to rectify the incoming ac signal, and no need to get the 1/2Vcc bias accurate: if there is DC content in the incoming signal, the software will track it.
2) I added the option to use a random number generator to test the code, in case you don't have a source ready.

the maximum results from the adc now is 512. so in theory the last 5 or so bars aren't used. you can recalculate the mapping table in val2ln[] to remap the log function.

enjoy.

点击此处下载 ourdev_554597.rar(文件大小:2K) (原文件名:main.rar)

出0入0汤圆

发表于 2010-5-16 09:23:03 | 显示全部楼层
mark

出0入0汤圆

 楼主| 发表于 2010-5-16 11:22:17 | 显示全部楼层
I rewrote the bar_display() routine to give it a little bit better structure; and to unify the version with 70 bars and the  earlier one with 14 blocky characters.

============new bar_display()===============
unsigned char val2bar(unsigned int val) {                                //map val to a bar, per val2ln[]. 0=1 bar, 1023=70 bar
        unsigned char i=0;

        val &= 0x3ff;                                                                                //max of val = 1023       
        while (val2ln[i++]<=val) continue;
        return i;                                                                                        //i is such that val2ln[i-1]<=val<val2ln
}

void bar_display(unsigned char lcd_line, unsigned int val) {        //display a value
        unsigned char bar = val2bar(val);
        unsigned char i=1, t;
        unsigned char vstr[17];
       
        for (i=0; i < (bar / 5); i++) vstr=LCD_char_BAR;                        //fill the leading bars
       
        vstr= (bar % 5) +1;                                                                                //fill in the fraction
       
        for (i; i<14; i++) vstr=LCD_char_CLR;                                        //full blocks. no fraction
//        for (i++; i<14; i++) vstr=LCD_char_CLR;                                        //fill in the trailing blanks
       
        lcd_display(lcd_line+2, vstr);                                                                //display the string
}

============================

by commenting and uncommenting the two "for" loops, you can switch between the two display format.

enjoy.

出0入0汤圆

发表于 2010-5-16 17:10:15 | 显示全部楼层
楼主可否提供头文件参考?  Function lcd_display(), lcd_init() 都找不到, 谢谢

#include "lcd_3wi.h"

出0入0汤圆

发表于 2010-5-16 18:35:33 | 显示全部楼层

出0入0汤圆

 楼主| 发表于 2010-5-16 20:14:17 | 显示全部楼层
"楼主可否提供头文件参考?  Function lcd_display(), lcd_init() 都找不到, 谢谢 "

sure. those files are specifically designed to be used with a 3-wire interface, through a HC164 - because that's how my development board was setup. you do NOT have to use them: I will post a lcd library that utilizes the 4-bit mode. The LCD function calls follow the same format and all you need to do is to change the header and recompile.

here is the .h and .c file for the lcd-3wi connection, and the schematic.

点击此处下载 ourdev_554708.rar(文件大小:4K) (原文件名:16F684 VU Meter - 70 bars.rar)

(原文件名:16F684 VU meter 70-bar lcd-3wi.PNG)

出0入0汤圆

 楼主| 发表于 2010-5-16 20:18:23 | 显示全部楼层
the beauty of a 3-wire connection is that it uses only 3 pins out of a mcu so you can drive a lcd with 12F675.

but it does require a hc164.

alternatively, if you have sufficient pins (at least 6 of them), you can drive the lcd directly.

in this case, you will need the 4-bit lcd routines below, and the connection is specified in the header file.

点击此处下载 ourdev_554711.rar(文件大小:3K) (原文件名:16F684 DS1822 4bit LCD.rar)

enjoy.

出0入0汤圆

发表于 2010-5-16 20:27:33 | 显示全部楼层
mark

出0入0汤圆

 楼主| 发表于 2010-5-16 20:48:56 | 显示全部楼层
I also revised the bar_graph() routine in the spectrum analyzer, to make it more readable and more flexible.

=============bar_graph()=================
unsigned char val2bar(unsigned int val) {                                //map val to a bar, per val2ln[]. 0=1 bar, 1023=14 bar
        unsigned char i=0;

        val &= 0x3ff;                                                                                //max of val = 1023       
        while (val2ln[i++]<=val) continue;
        return i;                                                                                        //i is such that val2ln[i-1]<=val<val2ln
}

void bar_display(unsigned char lcd_ch, unsigned int val) {        //display a value
        static unsigned char peak[2]={0,0};                                                                //to store historical peak
        unsigned char bar = val2bar(val);
//        unsigned char i=1, t;
        unsigned char vstr_top, vstr_bottom;
       
        if (bar / 7) {                                                                                                // bottom char if filled
                vstr_bottom=LCD_char_BAR;                                                                //bottom char is the bar
                vstr_top=bar % 7;                                                                                //top char is the fraction
        }
        else {
                vstr_bottom=bar % 7;                                                                        //bottom char is the fraction
                vstr_top=LCD_char_CLR;                                                                        //top char is blank
        }
        lcd_goto(LCD_Line0+lcd_ch); lcd_putch(vstr_top);
        lcd_goto(LCD_Line1+lcd_ch); lcd_putch(vstr_bottom);
}

here is a shot of the new spectrum analyzer. you will notice that a full bar now is 5x8, vs. 5x7 previously. This makes the bar more smooth.


(原文件名:16f684 spectrum analyzer v2.PNG)

出0入0汤圆

 楼主| 发表于 2010-5-16 20:53:18 | 显示全部楼层
going back to the VU meter. if you dc couple it to the source, and you add a little DC-detection routine, you can build a speaker protection circuit out of it.

出0入0汤圆

 楼主| 发表于 2010-5-16 21:00:07 | 显示全部楼层
or you can add power-on delay, over-heat protection, over/under-voltage protection, etc. and display the status on the lcd.

出0入0汤圆

发表于 2010-5-19 13:21:33 | 显示全部楼层
cool!  thanks millwood.

出0入0汤圆

发表于 2010-5-21 13:36:43 | 显示全部楼层
millwood, are you in China? Try to reach you, and give you one JLH1969 MOSFET PCB sometime while you in China, 'cause international express freight is quite much :)

出0入0汤圆

发表于 2010-5-21 13:38:16 | 显示全部楼层
or can email me back by the last email address same as before, because i'm not always online.

出0入0汤圆

 楼主| 发表于 2010-5-22 09:33:24 | 显示全部楼层
LaMer, thanks for the offer. It is a great little amp and I am sure you and other builders will enjoy it.

I have too many amps, :) so you don't have to send me the pcb. Thanks for the kind offer, though.

take care.

出0入0汤圆

发表于 2010-5-22 12:40:11 | 显示全部楼层
回复【31楼】millwood0
-----------------------------------------------------------------------
Hi, millwood. That's not an offer but free. As you created this circuit originally, I kept one PCB for you as an original author. If you buy something at someday from China, just please give me the sellers' address, I will send it to them to put into package. Don't forget it. And give me information by email enable me to send it out in time. Wow.


(原文件名:DT812_PCB_Kit_Gold_millwood.jpg)


(原文件名:DT812_PCB_Kit_Gold_1969M.jpg)

出0入0汤圆

 楼主| 发表于 2010-5-22 23:07:17 | 显示全部楼层
that's a nice looking PCB. great work.

the credit for designing the jlh goes to John Lindsay Hood. I experimented with different output devices and showed that using mosfets have considerable advantages.
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-19 17:09

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表