城会玩小组吧 关注:38贴子:2,335

回复:第一个分享帖——基于蜂鸣器的多音轨“播放器”

取消只看楼主收藏回复

差点忘了说一件重要的事情:如果下一次还想正常中断,那么一定要在中断服务程序里把中断标志位CCFn清零,不然可能会发生莫名其妙的错误。
思路考虑好了,就该看看具体的代码实现了。
实现的方法有三种。我最早的时候用的程序是:
//假设需要每a周期中断一次,a是一个unsigned int变量
void PCAinterrupt(void) interrupt 7 using 2 //interrupt 7就是PCA中断
{
if(CCF0) //如果是PCA第0路引发中断
{
CCAP0L += a; //CCAP0L加上a的低8位
CCAP0H += PSW >> 7; //如果有进位,CCAP0H加1
CCAP0H += a >> 8; //CCAP0H加上a的高8位
}
CCON &= 0xF8; //清0标志位,以助于下次中断
return;
}


IP属地:上海42楼2016-07-10 13:43
回复
    @happymax1212 这个帖本来更到一半弃坑了,现在为你重拾吧


    IP属地:上海45楼2017-02-14 20:15
    收起回复
      接42楼,继续!!!
      手动操作两个寄存器是非常繁的,因为要手动处理进位标志位。不过这是我直觉想到的解决方案。
      有没有更可读的办法呢?有的。其实,就是设置一个unsigned int变量temp,然后把想加到CCAPnH、CCAPnL里的值先加到temp里,然后把temp高8位的值给CCAPnH,低8位给CCAPnL。因为Keil能给temp自动弄好进位处理,所以就不用我们自己操劳了。
      光看解释不容易理解,看新的代码就知道了。
      //假设需要每a周期中断一次,a是一个unsigned int变量
      void PCAinterrupt(void) interrupt 7 using 2 //interrupt 7就是PCA中断
      {
      static uint16 temp = 0; //暂存值
      if(CCF0) //如果是PCA第0路引发中断
      {
      temp += a;
      CCAP0L = temp; //取低8
      CCAP0H = temp >> 8; //取高8位
      }
      CCON &= 0xF8; //清0标志位,以助于下次中断
      return;
      }


      IP属地:上海46楼2017-02-14 20:42
      回复
        或许你会觉得PCA这种类似于手动重装模式的使用方法会给它带来和手动重装模式一样的大误差。这样想非常好,说明你正在边学边思考。不过,如果仔细研究一下,会发现PCA的误差比手动重装模式的普通定时器要小多了。看我模拟一下手动重装模式的普通定时器:(假设每次定时时间均为0x2000时钟周期)
        工作在手动重装模式的普通定时器工作流程:
        第0 时钟周期:定时器已被装入适当的值开始运行。
        第0x2000时钟周期:定时器第一次溢出,等待进入中断程序。
        约第0x2010时钟周期:CPU处理完手上运行到一半的指令,经过了压栈、跳转等处理,终于进入了中断处理程序。
        约第0x2018时钟周期:又执行了几条指令,才给定时器装入想要的值开始下一轮计时。现在才空出来处理音乐播放。
        ……
        约第0x4036时钟周期:第二次得以处理音乐播放。
        ……
        以此类推。就如你所知的那样,每一计时轮回不是预期的0x2000时钟周期,而是多出了约24周期。这误差还会累积,对时钟这样的应用有着致命威胁。虽然播放方波对误差累积不敏感,但是每一方波周期都变长了,导致听到的音偏低。更麻烦的是,如果每次多出的时间是固定的24周期,那么我们可以人工地给初值减去24,但是这个时间实际上不是固定的,所以最多也只能做到部分补偿。
        现在模拟一下PCA定时。(假设每次定时时间也为0x2000时钟周期,使用模块0)
        第0 时钟周期:PCA定时器已被装入适当的值开始运行。
        第0x2000时钟周期:PCA模块0的值与PCA定时器匹配了,等待进入中断程序。
        约第0x2018时钟周期:给PCA模块0装入想要的值(这里是0x4000)以备下次中断,然后处理音乐播放。
        ……
        第0x4000时钟周期:PCA模块0的值第二次与PCA定时器匹配,等待进入中断程序。
        约第0x4018时钟周期:再准备好下次中断,然后处理音乐播放。
        ……
        可以看出,虽然第一次中断差了18时钟周期,但是后来不再出现误差。因此,用PCA来定时不会产生持续累积的误差,对时钟的影响可以忽略。应用于方波的播放时,也不会影响实际的方波频率。


        IP属地:上海47楼2017-02-15 18:57
        回复
          暂停一下,回到25楼看一看。
          “你问我为什么不干脆就在电脑里事先把这个减法也算好直接装进NoteCycle节省单片机运作的时间呢?第一个,做了这个减法以后,在T0上面会节省一点时间,但是以后在PCA上面反而会浪费更多时间,这一点我也会放到以后再讲。”有印象吧?
          现在讲了PCA的使用方式,大家应该很清楚了。普通定时器重装时,要求把“0x10000-定时时间“送入TH0和TL0。而PCA在装入新值的时候要求给CCAPnH和CCAPnL加上(在这里再提醒一下是加上,不是直接赋值!)定时时间原值。因此,如果为普通定时器做好减法,到了PCA这里还要反过来再减回去。何况PCA有三路。因此,事先做好这减法费力不讨好。


          IP属地:上海本楼含有高级字体48楼2017-02-18 08:57
          回复
            我回来啦
            关于PCA的寄存器,其实STC的数据手册讲得已经很清楚了,我引用一句话:“大家不要感觉害怕。说句不过分的话,单片机这些逻辑上的问题,只要小学毕业就可以理解,很多时候是因为大家把问题想象得(原文误作“的”)太难才学不下去的。”何况STC的数据手册也确实很接地气,虽说广告很多,但是坚持用中文,而且还送了很多例程。在这里照顾一下觉得有困难的同学,我就再拎一遍重难点吧(怎么我说话又俨然一个老师样子,我好怕
            CMOD这个寄存器位置是0xD9,所以不可寻址。比较有用的是B3(CPS2)、B2(CPS1)、B1(CPS0)(再注意,这是一字节的低2~4位,永远不要忘记最低位是B0不是B1),赋予不同的值可以给PCA定时器分频,简单来说就是控制它计数的频率。对照着数据手册看一下,Sysclk意思是系统主频。比如按照手册上面赋值1、0、0,分频状态是“系统时钟 Sysclk”,也就是计数和主频同步,和普通定时器的1T模式一样快;赋值0、0、0,分频状态是“Sysclk/12”,也就是计数频率是主频的十二分之一,和普通定时器的12T模式一样快。现在理解了吧?


            IP属地:上海49楼2017-03-02 20:16
            回复
              CMOD B0(ECF)有什么用呢?ECF是允许CF引起中断,CF又是什么呢?PCA定时器一般我们放任它每65536时钟溢出一回,移除的时候就会置位(意即”把……位置为1“)CF,由CF经ECF允许引起PCA中断。联想到Timer 0了吧!Timer 0就是这样的,溢出的时候置位它的TF0,TF0可以经ET0允许引起T0中断。


              IP属地:上海50楼2017-03-02 20:32
              回复
                下面是寄存器CCON。它的地址是0xD8,这是个好位子,因此它可位寻址。
                B7(CF)作用刚才讲了,就是再强调一下手册的话:CF是不会自动硬件清零的!平时我们使用T0的中断并不用手动清零TF0,那是因为硬件已经自动帮我们做好了。而CF是不会这样的,所以如果我们用到CF中断,每次在PCA中断里都一定要记住写一句CF = 0;,不然就会出问题了。
                B6(CR)就和TR0一样,直接控制PCA定时器是否计数。所以要用PCA中的任何功能的话,程序里千万不要忘了打开CR,不然配置半天,PCA根本没在工作……
                低位的CCFn是用来标志PCA模块的中断请求的,等一下再结合各路模块讲一下。


                IP属地:上海51楼2017-03-02 20:43
                回复


                  IP属地:上海52楼2017-03-02 20:44
                  回复