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

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<VLEVELS-1)
                        {
                                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();
        }
}


  • 7
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一些思路和代码示例。 首先,需要连接旋转编码器和数码管到51单片机上。旋转编码器一般有三个引脚,其中两个是A、B相位信号,另一个是中间的按键信号。数码管需要连接到单片机的IO口上,控制其显示。 接着,需要编写代码来读取旋转编码器的信号,并进行计数。一般来说,可以通过检测A、B相位信号的变化来判断旋转方向,然后更新计数器的值。同时,需要注意去抖动,即在旋转编码器信号变化时,需要延时一段时间再进行判断,避免误判。 最后,将计数器的值转换为对应的数码管显示,并输出到IO口上即可。 下面是一个简单的代码示例,仅供参考: ``` #include <reg52.h> sbit CLK = P1^0; // 旋转编码器CLK引脚 sbit DT = P1^1; // 旋转编码器DT引脚 sbit SW = P1^2; // 旋转编码器按键引脚 sbit D1 = P2^0; // 数码管第一位 sbit D2 = P2^1; // 数码管第二位 sbit D3 = P2^2; // 数码管第三位 sbit D4 = P2^3; // 数码管第四位 unsigned char code table[] = { // 数码管字符表 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f }; int count = 0; // 计数器初始值为0 void display(int num) { // 将计数器的值转换为数码管显示 D1 = 0; D2 = 0; D3 = 0; D4 = 0; D4 = table[num % 10]; D3 = table[num / 10 % 10]; D2 = table[num / 100 % 10]; D1 = table[num / 1000 % 10]; } void delay(unsigned int t) { // 延时函数 unsigned int i, j; for (i = 0; i < t; i++) for (j = 0; j < 1000; j++); } void main() { unsigned char lastCode = 0; // 上一次旋转编码器的状态 unsigned char curCode = 0; // 当前旋转编码器的状态 while (1) { curCode = (DT << 1) | CLK; // 读取旋转编码器的状态 if (curCode != lastCode) { // 如果状态变化 delay(5); // 延时去抖动 if (curCode == 0b10 && lastCode == 0b00) { // 判断旋转方向 count++; } if (curCode == 0b01 && lastCode == 0b00) { count--; } display(count); // 更新数码管显示 } lastCode = curCode; } } ``` 注意:以上代码仅供参考,实际应用中需要根据具体情况进行修改和调试。同时,需要注意电路连接和编程的正确性,避免对单片机和其他设备造成损坏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值