单片机在音响上的应用(一)旋转编码器的解码

2019-04-15 12:22发布

sword__yang 
      【导语】:高端的音响设备目前已经大量采用了先进的控制技术,其中使用单片机(MCU)控制是必不可少的,举凡遥控、显示、电子音量控制等都离不开单片机。本文拟通过几个单片机的应用实例来和大家分享一下自己的DIY心得。(文中所有的程序实例都已经在PIC16F873A单片机上通过实验的检验)。
     本文共有4个部分:
一、旋转编码器的解码
二、电子音量控制
三、荧光显示VFD、按键控制
四、红外遥控解码

音响应用之一:旋转编码器的解码 
      旋转编码器(外形参见图II-1.0)在音响中多用于取代普通的滑动电阻电位器作为音量/音调控制的编码输入。它使用寿命长达100万次,比普通电位器长得多,而且不会因为机械磨损造成阻值的偏差,影响声道的平衡。其调节的精度仅仅取决于与MCU配合的音量控制芯片的控制级数,与本身的旋转角度无关,这也是普通电位器无法做到的,因此旋转编码器也大量地用于精密仪器的调节上。 
      旋转编码器内部就是两个长寿命开关,可以根据旋转方向产生不同相位信号。电路如图II-1.1所示:当我们顺时针旋转时,开关A的输出信号A signal相位超前;如果我们逆时针旋转时,则是开关B的输出信号B signal相位超前,我们把A/B端分别接到MCU的两个输入端口,并在MCU内设置一个音量计数器;就可以用软件来判别是顺时针旋转还是逆时针旋转,以此判断是增加还是减少音量计数器的值,最后把这个计数值送到相应的电子音量控制芯片就可以实现音量(或者其他需要增量/减量的)控制了。 
      由于旋转编码器是随时改变的,我们的软件也要能够跟踪各个瞬时的状态变化,为了判断旋转编码器的相位我们还需要用三个标志位(Bit变量)来记住开关A,B的“瞬时状态”。

(原文件名:1.jpg) 


(原文件名:2.jpg) 



      旋转编码器的解码例程如下:
     【说明】: Bit变量定义:状态1:FLG0,ECA 当开关A变高,B变低时,置位(设为1),为即将到来的增量做准备;状态2:FLG0,ECB 当开关A变低,B变高时,置位(设为1),为即将到来的减量做准备;状态3:FLG0,ECV: 当完成一次增加/减少时,置位(设为1),相位变动一次,只做一次增/减量;变量寄存器:VOLUE 用来保存音量变化数值。 16F873A的I/O端口B: VOA:连接到编码器的A端子; VOB:连接到编码器的B端子。
;====================================== 
VOLUME_CONTROL: 
      BTFSC   FLG0,ECA                                      ; “状态1”检测:若已经满足了状态1,就去查看开关B是否变高 
      GOTO    KE_1E 
      BTFSC   FLG0,ECB                                      ; “状态2”检测:若已经满足了状态2,就去查看开关A是否变高 
      GOTO    KE_1F 
      BTFSS   PORTB,VOA                                   ; “IN A”检测   
      GOTO    KE_1B 
KE_1A: 
      BTFSC   PORTB,VOB                                  ; “IN B”检测 
      GOTO    KE_1D 
      BTFSC  FLG0,ECV 
      GOTO   KE_EX 
      BSF        FLG0,ECA                                      ; 满足“状态1”的设置条件 
      GOTO    KE_EX 
KE_1B: 
      BTFSC   PORTB,VOB 
      GOTO    KE_1C 
      BCF        FLG0,ECV                                       ; 清除“状态3” 
      GOTO     KE_1D 
KE_1C: 
      BTFSC   FLG0,ECV 
      GOTO    KE_EX 
      BSF        FLG0,ECB                                       ; 满足“状态2”的设置条件 
      GOTO    KE_EX 
KE_1D: 
       BCF       FLG0,ECA 
       BCF       FLG0,ECB 
       GOTO    KE_EX 
KE_1E: 
        BTFSS  PORTB,VOB                                      ; “状态1”下,开关B变高? 
        GOTO   KE_EX 
INC_VOL: 
        BCF       FLG0,ECA                                         ; 是的,完成一次增量, 清除“状态1” 
        BCF       FLG0,ECB                                         ; 清除“状态2” 
        BSF       FLG0,ECV                                         ; 设置“状态3” 
        INCF      VOLUE,F                                           ; 音量寄存器加1 
        GOTO   KE_EX 
KE_1F: 
        BTFSS   PORTB,VOA                                     ; “状态2”下,开关A变高? 
        GOTO    KE_EX 
DEC_VOL: 
        BCF        FLG0,ECA                                        ; 是的,完成一次减量,清除“状态1” 
        BCF        FLG0,ECB                                        ; 清除“状态2” 
        BSF        FLG0,ECV                                        ; 设置“状态3” 
        DECF VOLUE,F                                               ; 音量寄存器减1 
KE_EX: 
        RETURN 
;===================================== 
;开关的瞬时位置比较抽象,大家要对比波形图和软件的状态去加深理解。
Reading Rotary Encoders

here's my first contribution to this wiki. I hope this is the right place for it.

(原文件名:alps_stec12e07_encoder.jpg) 
A rotary or "shaft" encoder is an angular measuring device. It is used to precisely measure rotation of motors or to create wheel controllers (knobs) that can turn infinitely (with no end stop like a potentiometer has). Some of them are also equipped with a pushbutton when you press on the axis (like the ones used for navigation on many music controllers). They come in all kinds of resolutions, from maybe 16 to at least 1024 steps per revolution, and cost from 2 to maybe 200 EUR.
I've written a little sketch to read a rotary controller and send it's readout via RS232.
It simply updates a counter (encoder0Pos) every time the encoder turns by one step, and sends it via serial to the PC.
This works fine with an ALPS STEC12E08 encoder which has 24 steps per turn. I can imagine it could fail with encoders with higher resolution, or when it rotates very quickly (think: motors), or when you extend it to accomodate multiple encoders. Please give it a try.
I learned about how to read the encoder from the file encoder.h included in the arduino distribution as part of the AVRLib. Thanks to its author, Pascal Stang, for the friendly and newbie-proof explanation of the functionings of encoders there. here you go:
/* Read Quadrature Encoder
  * Connect Encoder to Pins encoder0PinA, encoder0PinB, and +5V.
  *
  * Sketch by max wolf / www.meso.net
  * v. 0.1 - very basic functions - mw 20061220
  *
  */  


int val; 
int encoder0PinA = 3;
int encoder0PinB = 4;
int encoder0Pos = 0;
int encoder0PinALast = LOW;
int n = LOW;

void setup() { 
   pinMode (encoder0PinA,INPUT);
   pinMode (encoder0PinB,INPUT);
   Serial.begin (9600);


void loop() { 
   n = digitalRead(encoder0PinA);
   if ((encoder0PinALast == LOW) && (n == HIGH)) {
     if (digitalRead(encoder0PinB) == LOW) {
       encoder0Pos--;
     } else {
       encoder0Pos++;
     }
     Serial.print (encoder0Pos);
     Serial.print ("/");
   } 
   encoder0PinALast = n;

Oh, a few notes:
encoder0Pos will be counting forever, that means that if you keep turning into the same direction, the serial message will become longer (up to 6 characters), costing more time to transmit.
you need to make sure yourself (on the PC side) that nothing bad happens when encoder0Pos overflows - if the value becomes larger than the maximum size of an INT (32,767), it will flip to -32,768! and vice versa.
suggestion for improvement: make it spit out the counter only when it is polled from the PC. Count only the relative change of the encoder between two polls.
obviously, if you add more code to the loop(), or use higher resolution encoders, there is a possibility that this sketch will not see every individual step. The better way of counting encoder steps is to use an interrupt on every flank of the signal. The library I mentioned above does just that, but currently (2006-12) it doesn't compile under the arduino environment - or I just don't know how to do so...
I'm not sure about the etiquette of this, but I'm just going to add onto this tutorial. Paul Badger
Below is an image showing the waveforms of the A & B channels of an encoder.


//在24MHz下,延时常数, 51单片机, 如: STC89C52 

#define TICKSCODE 100

sbit APIN = P3^3;
sbit BPIN = P3^7;



#define STA    0x10

#define CLKW   0x20
#define CCLKW  0x40
#define RDY    0x80

unsigned char flag=0;

/*
        flag:
                位0-3:        内部测试计数
                位4:         稳定标记
                
                位5:         正向旋转标记
                位6:        反向旋转标记
                位7:         应用标记         
*/


void VOLUME_CONTROL()  
{
        if (flag & CLKW)    //正向旋转,A低/B高 
        {
                //连续测几次,防抖
                if ((flag & STA)==0)        //是否还不稳定
                {
                        if (APIN==0)        //脚A是低电平
                        {
                                flag++;
                                if (flag>=3)                //连续3次测试稳定(由于本程序使用环境包括键盘测试等,实际测试间隔比现在大)
                                        flag |= STA;        //置稳定标记,以防继续判断.
                                else
                                        return;                //继续测试
                        }
                        else                                //A脚变高电平,可能是抖动
                        {
                                flag &= 0xf0;                //清计数
                                 return;
                        } 
                
                }
        
   
                if ( (flag & RDY)==0)                        //本次旋转还没有计数?
                {
                            //B脚变低了吗?

                        if (BPIN==1) return;                //没有反转,返回

                           //是的,一次增量

                        flag |= RDY;                         // 设置"状态"
                         
                        //安设定的音量间隔调整音量
                        /***
                        while (volume                         {
                                volume++;
                                if (volInterval==1) break;
                                if (volumeDB[volume]/volInterval * volInterval==volumeDB[volume]) break;
                        }
                        vol_out();
                        ****/
                        volume++;

                }
                else  //等待B变高,完成一次完整的跳变
                {
                         if (APIN==0) return;
                         if (BPIN==0) return;
           
                        flag = RDY;
                }

                return;
        }

        if (flag & CCLKW)         //反向旋转,A高/B低
        {
                //连续测几次测试稳定,消抖
                if ((flag & STA)==0)
                {
                        if (BPIN==0)
                        {
                                flag++;
                                if (flag>=3)
                                        flag |= STA;
                                else
                                        return;
                        }
                        else
                        {
                                flag &= 0xf0;
                                 return;
                        } 
                }

           
                if ((flag & RDY)==0)
                {
                        //A脚是否变低?
                        if ( APIN==1) return;                //没有反转,返回
        
                           // 是的,一次减量 

                        flag |= RDY;                         // 设置"状态"  
                        /***
                        while (volume>0)
                        {
                                volume--;
                                if (volInterval==1) break;
                                if (volumeDB[volume]/volInterval * volInterval==volumeDB[volume]) break;
        
                        } 
                        vol_out();
                        ***/

                }
                else        //等待A变高,完成一次完整的跳变
                {
                        if (APIN==0) return;
                         if (BPIN==0) return;

                        flag = RDY; 
                }
                return;
            }  

        if (APIN==0)        //A脚检测
            {
                if (BPIN==1) 
                {
                        if ((flag & RDY)==0)
                        {       
                                flag |= CLKW;         // A低,B高,正向旋转
                        }
                        return;
                }
              
                flag = 0;

                return;
        }
        else
        {
                if (BPIN == 0)                          //B脚检测
                {
                        if  ( (flag & RDY)==0)
                        {
                                flag |= CCLKW;          //B低, A高,反向旋转
                        }
                        return;
                }
                flag = 0;
                return;
        }         
}

void VOLUME_CONTROL() ;
void main()
{
        while(1)
        {
                scan_key();

                VOLUME_CONTROL();
        }
}