打印

<已解决>如何优化从SRAM(XData)倒数据到外设的代码

[复制链接]
3863|34
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
古道热肠|  楼主 | 2009-3-22 10:21 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
俺的MP3经过n次尝试后,发现P89V51RD2的硬件SPI无法以同一种模式分时访问SPI总线上的两个设备SD卡和VS1003B,只好回头.重新考虑优化代码,用软件方式实现高速播放.
俺的目标是流畅播放256Kbps的MP3,争取上到320Kbps

先贴上代码:

void main(void)
{
    unsigned char xdata *pDataPoint;
    unsigned char ucCount;
    unsigned char uci;

    pDataPoint = ucTestDataBuff;

    for(ucCount=0; ucCount<255; ucCount++)
    {
        *pDataPoint++ = ucCount;
    }

    while(1)
    {
         //以下代码段模拟将数据发送到VS1003B模块,未考虑忙信号,用于测试模拟SPI发码能达到的最快的C语言程序
         
        ucCount = 16;
        while(ucCount--)
        {
    
            uci = 32;
            while(uci--)
            {
                SD_SPI_WriteByte(*pDataPoint++);
    
              }
        }
          //发码结束
        ucCount=88;            //测试语句,用于设置断点
    }
}

通过跟踪汇编,发现那段32字节While循环,还大有潜力可挖,pDataPoint指针变量的取数和加1操作,C语言编译居然没有采用INC DPTR指令来实现SRAM的指针操作,而是外加两个寄存器,构成指向SRAM的指针,然后对先对RX指针加1,再送到DPL和DPH,再用MOVX指令取数,太浪费了,在SD_SPI_WriteByte函数中辛苦省下的Clk全给它白白空耗掉了.

当然如此操作也是有它的道理的,DPTR是通用的,不能做为长期指针,任何进程只有短期使用权.

讨论的重点就是以下核心部分

    
        ucCount = 16;
        while(ucCount--)
        {
    
            uci = 32;
            while(uci--)
            {
                SD_SPI_WriteByte(*pDataPoint++);
    
              }
        }

在C51语言中,如何直接操作DPTR(对DPTR加1),除了嵌入汇编代码,还有更好的办法吗?

以下是较为合理的伪代码
{
    DPL = DPL_BAK;
    DPH = DPH_BAK;

    R_COUNT = 32

loop_32Byte_Send_Enter:
     
    MOVX ACC,DPTR;
    SD_SPI_Send(ACC)
    INC DPTR

    DJNZ RCOUNT loop_32Byte_Send_Enter
   
    DPL_BAK = DPL  ;
    DPH_BAK = DPH


}

相关帖子

沙发
古道热肠|  楼主 | 2009-3-22 10:24 | 只看该作者

贴上完整的测试项目包,大家直接在测试项目下优化.

测试包见下述压缩文件:
相关链接:https://bbs.21ic.com/upfiles/img/20093/2009322102211187.rar

使用特权

评论回复
板凳
古道热肠|  楼主 | 2009-3-22 10:26 | 只看该作者

贴上该段代码核心地带的反汇编截图

使用特权

评论回复
地板
古道热肠|  楼主 | 2009-3-22 10:38 | 只看该作者

320Kbps的数据流,理论上能否达到呢?看看俺的分析

根据VS1003b的32字节FiFO特性,只要保证总线上数据流能翻一倍,就能保证数据不断流,即要求单机数据传输速度达到640Kbps,折合成字节,就是80k字节/秒
俺的板子上使用的24M晶体,单片机工作在6时钟周期,1uS能够执行4条指令.

如果能在10uS(可执行40条机器周期)能完成一个从SRAM中取数据并转换成串行数据输出的操作,则可高达100K字节/s

同理80K字节/S,速度间隔必须在12.5uS内完成,方能不断流,也就是所有操作必须将机器周期限定在50以内

大家多出招吧,SPI总线软件并转串数据操作已经优化到最快了.
就是下面这段:也是21IC上诸多高人擂出来的.

sbit ACC0 = ACC^0;
sbit ACC1 = ACC^1;
sbit ACC2 = ACC^2;
sbit ACC3 = ACC^3;
sbit ACC4 = ACC^4;
sbit ACC5 = ACC^5;
sbit ACC6 = ACC^6;
sbit ACC7 = ACC^7;

void SD_SPI_WriteByte(unsigned char  ucSendData)  
{   
    ACC = ucSendData;
    
//Bit7 Shift Out To SD Card
    Macro_Set_CLK_Low();
//    c_SPI_SI = WriteData_Bit7;
    c_SPI_SI = ACC7;
//    c_SPI_SI = CY;

    Macro_Set_CLK_High();

//Bit6 Shift Out To SD Card
    Macro_Set_CLK_Low();
//    c_SPI_SI = WriteData_Bit6;
    c_SPI_SI = ACC6;

    Macro_Set_CLK_High();
//Bit5 Shift Out To SD Card
    Macro_Set_CLK_Low();
//    c_SPI_SI = WriteData_Bit5;
    c_SPI_SI = ACC5;

    Macro_Set_CLK_High();
//Bit4 Shift Out To SD Card
    Macro_Set_CLK_Low();
//    c_SPI_SI = WriteData_Bit4;
    c_SPI_SI = ACC4;

    Macro_Set_CLK_High();
//Bit3 Shift Out To SD Card
    Macro_Set_CLK_Low();
//    c_SPI_SI = WriteData_Bit3;
    c_SPI_SI = ACC3;

    Macro_Set_CLK_High();
//Bit2 Shift Out To SD Card
    Macro_Set_CLK_Low();
//    c_SPI_SI = WriteData_Bit2;
    c_SPI_SI = ACC2;

    Macro_Set_CLK_High();
//Bit1 Shift Out To SD Card
    Macro_Set_CLK_Low();
//    c_SPI_SI = WriteData_Bit1;
    c_SPI_SI = ACC1;

    Macro_Set_CLK_High();
//Bit0 Shift Out To SD Card
    Macro_Set_CLK_Low();
//    c_SPI_SI = WriteData_Bit0;
    c_SPI_SI = ACC0;

    Macro_Set_CLK_High();

使用特权

评论回复
5
xwj| | 2009-3-22 11:18 | 只看该作者

热肠,怎么又玩到51上面去了???

要快的话只能用嵌入汇编(老Hot的数组也是嵌入汇编),
而51的话最快的SPIrit还是串口模式0,8个指令周期即可完成,而且这8个指令周期内你还刚好可以完成数据搬移、准备工作。

使用特权

评论回复
6
xwj| | 2009-3-22 11:36 | 只看该作者

然后:Keil编译的代码玩玩最大的时间开销在子程序的参数传

(包括他自己的数据计算等功能子程序),因为它要保证所有的子程序都遵循统一的入口、出口参数规则。(别的编译器其实也是一样的)
然后,LCALL和RET 又会分别占用2个周期

所以,优化的第一点就是避免参数传递,--可以把循环和子功能写到一个函数里;
优化的第二点,把(i++)循环改成do{}while(--i);或者for(i=n;i;i--){;}
这就可以让编译器编译出DJNZ代码了,而(i++)或while(uci--)之类的写法由于判断和减的顺序不对,从语义上就不应该被编译成DJNZ 
对于大于256次的循环,应该用多个字节搞个多层循环,把他们全部用单字节变量循环的方式,这样整体的循环效率最高

当然,对速度没要求时还是可以优先照顾程序可读性的,而直接用uint



只要合理的优化下,就算是51这么老的东东,用来操作VS1003b放320Kbps的数据流也是一点问题都没有的

使用特权

评论回复
7
ayb_ice| | 2009-3-22 17:12 | 只看该作者

关键部分直接用汇编写

使用特权

评论回复
8
古道热肠|  楼主 | 2009-3-22 17:24 | 只看该作者

哈哈,谢谢大家出主意,谢谢版主置顶求解

俺就想了解除了汇编,是否有还有其它能直接操纵DPTR的方式,而采用的是C51的写法,因为许多地方还有可能用到DPTR,甚至双DPTR的使用.

使用特权

评论回复
9
ayb_ice| | 2009-3-22 17:45 | 只看该作者

除非函数很简单

否则很难把变量直接分配给DPTR,导致赋值都要浪费很多时间,而用汇编却是很容易的事.
据说IAR的51也不错,可以看看它的反汇编,参考一下.

使用特权

评论回复
10
hotc51| | 2009-3-23 08:59 | 只看该作者

记得我跟帖过inc dptr的问题,有空找找

使用特权

评论回复
11
古道热肠|  楼主 | 2009-3-23 13:10 | 只看该作者

试了一下在线嵌入式汇编,很不爽,最后改成汇编函数方式

在线嵌入式汇编,使用#pragma asm 后,源程序先编译成汇编文件,再生成可执行文件,仿真调试时,直接进入汇编文件,没法玩.
最后还是用老办法,用C文件生成一汇编函数框架,自己填充内容.再将汇编文件加入项目.这样很方便,能在C层次仿真调试.

使用特权

评论回复
12
古道热肠|  楼主 | 2009-3-23 13:15 | 只看该作者

汇编发码到VS1003B,每次发送32字节,大家看看是否还能优

; .SendPackageToVS1003B.SRC generated from: SendPackageToVS1003B.c
; COMPILER INVOKED BY:
;        C:KeilC51BINC51.EXE SendPackageToVS1003B.c BROWSE DEBUG OBJECTEXTEND SRC(.SendPackageToVS1003B.SRC)

SPI_CLK        BIT     P1.7
SPI_MOSI    BIT     P1.5
SPI_MISO    BIT     P1.6
ACC_7        BIT    ACC.7
ACC_6        BIT    ACC.6
ACC_5        BIT    ACC.5
ACC_4        BIT    ACC.4
ACC_3        BIT    ACC.3
ACC_2        BIT    ACC.2
ACC_1        BIT    ACC.1
ACC_0        BIT    ACC.0



NAME    SENDPACKAGETOVS1003B

?PR?SendDataPackageToVS1003B?SENDPACKAGETOVS1003B SEGMENT CODE 
    PUBLIC    SendDataPackageToVS1003B
; //生成发数据到VS1003B的源C程序框架


; void SendDataPackageToVS1003B(void)

    RSEG  ?PR?SendDataPackageToVS1003B?SENDPACKAGETOVS1003B
SendDataPackageToVS1003B:
            ; SOURCE LINE # 4
; {
            ; SOURCE LINE # 5
;     
; }
            ; SOURCE LINE # 7
;Save Background
    PUSH B
    PUSH ACC

;Initialize Loop Count Register
    MOV B,#20H

;Loop Enter
  SendOneByte_To_VS1003B_LoopEnter_1:
      ;Loop Body
        MOVX A,@DPTR;
    
    ;Send Byte Bit7
        CLR SPI_CLK
        MOV C,ACC_7
        MOV SPI_MOSI,C
        SETB SPI_CLK
    
    ;Send Byte Bit6
        CLR SPI_CLK            ;1
        MOV C,ACC_6            ;1
        MOV SPI_MOSI,C            ;2
        SETB SPI_CLK            ;1
    
    ;Send Byte Bit5
        CLR SPI_CLK
        MOV C,ACC_5
        MOV SPI_MOSI,C
        SETB SPI_CLK
    
    ;Send Byte Bit4
        CLR SPI_CLK
        MOV C,ACC_4
        MOV SPI_MOSI,C
        SETB SPI_CLK
    
    ;Send Byte Bit3
        CLR SPI_CLK
        MOV C,ACC_3
        MOV SPI_MOSI,C
        SETB SPI_CLK
    
    ;Send Byte Bit2
        CLR SPI_CLK
        MOV C,ACC_2
        MOV SPI_MOSI,C
        SETB SPI_CLK
    
    ;Send Byte Bit1
        CLR SPI_CLK
        MOV C,ACC_1
        MOV SPI_MOSI,C
        SETB SPI_CLK
    
    ;Send Byte Bit0
        CLR SPI_CLK
        MOV C,ACC_0
        MOV SPI_MOSI,C
        SETB SPI_CLK
        
        INC DPTR
    ;loop untill The 32 Byte Send Complete
    DJNZ B,SendOneByte_To_VS1003B_LoopEnter_1

;Restore Background
    POP ACC
    POP B
    RET      
; END OF SendDataPackageToVS1003B

    END

使用特权

评论回复
13
古道热肠|  楼主 | 2009-3-23 13:21 | 只看该作者

主调程序是采用读地址方式取出数据缓冲区的基地址,详情

                        DPH_BAK = (unsigned char )(Sector_Buf)>>8;
            DPL_BAK = (unsigned char )(Sector_Buf);

while(ucCount)
            {
                        
//                if(CheckVS1003B_DRQ())
                if(MP3_DREQ)
                {
                    Mp3SelectData();

                    DPH = DPH_BAK;
                    DPL = DPL_BAK;                //4735

                    SendDataPackageToVS1003B();            //Sum Cost is 1482  
                    //This Speed is enough for Play 320Kbps MP3 Music File, Because the Max Up Limited is 50X32 = 1600

        
                    DPH_BAK = DPH;
                    DPL_BAK = DPL;

                    Mp3DeselectData();
                    ucCount--;
                }
                else
                {
                    ComShowString(c_COM1,"Now Mcu is free:");
                    if(!GetIR_Remote_Status())        //检测到红外线遥控信号的起始位
                    {
            
                        IRProcess();
                        ucOldKeyValue = c_Key_NoUse;
                    }

使用特权

评论回复
14
古道热肠|  楼主 | 2009-3-23 13:30 | 只看该作者

最后测试发现播放256Kbps的文件时,MCU满负荷

串口看不到"MCU is Free"的调试信息.大家看看还有什么招,俺只有用硬件的招了,软件优化就看大家了的.
备注:
mp3selectData(),mP3DeselectData()均是用#define 定义的单指令操作,无调用开销.

使用特权

评论回复
15
hunetson| | 2009-3-23 15:24 | 只看该作者

看得云里雾里的

不如全部用汇编写吧,哈哈,最高效了

使用特权

评论回复
16
ayb_ice| | 2009-3-23 20:39 | 只看该作者

不用

PUSH B
PUSH ACC
...
POP ACC
POP B
非中断程序可以自由使用任意寄存器而不用保存,这是编译规则决定的,这又可以节约一点时间了...

使用特权

评论回复
17
xwj| | 2009-3-24 09:11 | 只看该作者

对于这种情况,你应该把负荷最高的SPI部分移到串口或硬件SP

再把负荷很轻的串口用软件方式来模拟

当然,最好的办法还是换个IC,就别再去折磨51了:-)

使用特权

评论回复
18
winloop| | 2009-3-24 13:26 | 只看该作者

为何不用硬SPI

这些问题全能解决

使用特权

评论回复
19
ljxh401| | 2009-3-24 13:52 | 只看该作者

用mega8就什么事情都搞定了

看到51的256的堆栈就觉得没有前途

使用特权

评论回复
20
古道热肠|  楼主 | 2009-3-24 14:03 | 只看该作者

回18楼,硬件SPI在P89LV51RD上使用不成功,估计是硬件BUG

单独用SPI读写SD卡用SCR = 0x50,SPI总线速度高达48/4=12M,可全速访问
单独用SPI读写SD卡用SCR = 0x54,SPI总线速度不能取最大,大了VS1003B吃不消.让人头疼的就是这两个芯片挂在一条SPI线上时,无法统一SCR(SPI总线控制寄存器),已用仿真器调试和测试了n次.

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

284

主题

6411

帖子

16

粉丝