打印

软件模拟SPI总线读写,速度No.1,请高手打檑台

[复制链接]
15978|83
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
古道热肠|  楼主 | 2008-3-2 11:26 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
最近在调试我设计的MP3,用到SPI读写SD卡和向VS1003B发送数据,因为MP3对数据流速度相当敏感,遂对软件SPI进行了优化,目前已能流畅播放了。
我的SPI总线单字节读写函数如下,自认为速度最快了,已至极致,纵然汇编实现,也只能打个平手,51高手请指正。

#include "..incincludes.h"




sbit c_SPI_SI = P1^5;
sbit c_SPI_SO = P1^6;
sbit c_SPI_CLK = P1^7;

#define Macro_Set_SI_High()      c_SPI_SI = 1
#define Macro_Set_SI_Low()      c_SPI_SI = 0
#define Macro_Set_CLK_High()      c_SPI_CLK = 1
#define Macro_Set_CLK_Low()      c_SPI_CLK = 0

/*
//----------------标准C语言版-----------------------------------------
//可移植性好,易读,易移植
uint8 SD_SPI_ReadByte(void)
{
    uchar ucReadData;
    uchar ucCount;

    ucReadData = 0;
    Macro_Set_SI_High();

    for(ucCount=0; ucCount<8; ucCount++)
    {
        ucReadData <<= 1;
        Macro_Set_CLK_Low();

        Macro_Set_CLK_High();
    
        if(c_SPI_SO)
        {
            ucReadData |= 0x01;
        }
    }

    return(ucReadData);
}

void SD_SPI_WriteByte(uint8 ucSendData)  
{   
    uchar ucCount;
    uchar ucMaskCode;

    ucMaskCode = 0x80;
    for(ucCount=0; ucCount<8; ucCount++)
    {
        Macro_Set_CLK_Low();

        if(ucMaskCode & ucSendData)
        {
            Macro_Set_SI_High();
        }
        else
        {
            Macro_Set_SI_Low();
        }
        Macro_Set_CLK_High();
        ucMaskCode >>= 1;
    }


*/
//-------------------------标准优化版SPI读写函数---------
uchar bdata ucReadData;
sbit ReadData_Bit0 = ucReadData^0;
sbit ReadData_Bit1 = ucReadData^1;
sbit ReadData_Bit2 = ucReadData^2;
sbit ReadData_Bit3 = ucReadData^3;
sbit ReadData_Bit4 = ucReadData^4;
sbit ReadData_Bit5 = ucReadData^5;
sbit ReadData_Bit6 = ucReadData^6;
sbit ReadData_Bit7 = ucReadData^7;

uchar bdata ucWriteData;
sbit WriteData_Bit0 = ucWriteData^0;
sbit WriteData_Bit1 = ucWriteData^1;
sbit WriteData_Bit2 = ucWriteData^2;
sbit WriteData_Bit3 = ucWriteData^3;
sbit WriteData_Bit4 = ucWriteData^4;
sbit WriteData_Bit5 = ucWriteData^5;
sbit WriteData_Bit6 = ucWriteData^6;
sbit WriteData_Bit7 = ucWriteData^7;

uint8 SD_SPI_ReadByte(void)
{
//初始化SI引脚状态
    Macro_Set_SI_High();

//Bit7 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    ReadData_Bit7 = c_SPI_SO;    

//Bit6 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    ReadData_Bit6 = c_SPI_SO;

//Bit5 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    ReadData_Bit5 = c_SPI_SO;

//Bit4 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    ReadData_Bit4 = c_SPI_SO;

//Bit3 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    ReadData_Bit3 = c_SPI_SO;

//Bit2 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    ReadData_Bit2 = c_SPI_SO;

//Bit1 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    ReadData_Bit1 = c_SPI_SO;

//Bit0 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    ReadData_Bit0 = c_SPI_SO;

    return(ucReadData);
}

void SD_SPI_WriteByte(uint8 ucSendData)  
{   
    ucWriteData = ucSendData;
    
//Bit7 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = WriteData_Bit7;
    Macro_Set_CLK_High();

//Bit6 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = WriteData_Bit6;
    Macro_Set_CLK_High();
//Bit5 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = WriteData_Bit5;
    Macro_Set_CLK_High();
//Bit4 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = WriteData_Bit4;
    Macro_Set_CLK_High();
//Bit3 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = WriteData_Bit3;
    Macro_Set_CLK_High();
//Bit2 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = WriteData_Bit2;
    Macro_Set_CLK_High();
//Bit1 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = WriteData_Bit1;
    Macro_Set_CLK_High();
//Bit0 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = WriteData_Bit0;
    Macro_Set_CLK_High();



相关帖子

来自 2楼
xwj| | 2008-3-3 11:41 | 只看该作者

俺来打擂了!!!

#include <REGX52.H>

#define uchar unsigned char
#define uint8 unsigned char
#define uint  unsigned int
#define ulong unsigned long

//               累加器带进位右移指令 RRC A                                 
#define _rrca_()          CY  = ACC & 0x01                                
//汇编代码       rrc a

//               累加器带进位左移指令 RLC A                                 
#define _rlca_()          CY  = ACC & 0x80
//汇编代码       rlc a


sbit c_SPI_SI = P1^5;
sbit c_SPI_SO = P1^6;
sbit c_SPI_CLK = P1^7;

#define Macro_Set_SI_High()      c_SPI_SI = 1
#define Macro_Set_SI_Low()      c_SPI_SI = 0
#define Macro_Set_CLK_High()      c_SPI_CLK = 1
#define Macro_Set_CLK_Low()      c_SPI_CLK = 0

/*
//----------------标准C语言版-----------------------------------------
//可移植性好,易读,易移植
uint8 SD_SPI_ReadByte(void)
{
    uchar ACC;
    uchar ucCount;

    ACC = 0;
    Macro_Set_SI_High();

    for(ucCount=0; ucCount<8; ucCount++)
    {
        ACC <<= 1;
        Macro_Set_CLK_Low();

        Macro_Set_CLK_High();
    
        if(c_SPI_SO)
        {
            ACC |= 0x01;
        }
    }

    return(ACC);
}

void SD_SPI_WriteByte(uint8 ucSenddata)  
{   
    uchar ucCount;
    uchar ucMaskcode;

    ucMaskcode = 0x80;
    for(ucCount=0; ucCount<8; ucCount++)
    {
        Macro_Set_CLK_Low();

        if(ucMaskcode & ucSenddata)
        {
            Macro_Set_SI_High();
        }
        else
        {
            Macro_Set_SI_Low();
        }
        Macro_Set_CLK_High();
        ucMaskcode >>= 1;
    }


*/

//-------------------------标准优化版SPI读写函数---------
//uchar bdata ACC;
sbit ACC_0 = ACC^0;
sbit ACC_1 = ACC^1;
sbit ACC_2 = ACC^2;
sbit ACC_3 = ACC^3;
sbit ACC_4 = ACC^4;
sbit ACC_5 = ACC^5;
sbit ACC_6 = ACC^6;
sbit ACC_7 = ACC^7;


uint8 SD_SPI_ReadByte(void)
{
//初始化SI引脚状态
    Macro_Set_SI_High();

//bit7 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    CY = c_SPI_SO;
    _rlca_();    
//    ACC = P1;

//bit6 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    CY = c_SPI_SO;
    _rlca_();    

//bit5 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    CY = c_SPI_SO;
    _rlca_();    

//bit4 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    CY = c_SPI_SO;
    _rlca_();    

//bit3 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    CY = c_SPI_SO;
    _rlca_();    

//bit2 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    CY = c_SPI_SO;
    _rlca_();    

//bit1 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    CY = c_SPI_SO;
    _rlca_();    

//bit0 Shift Out
    Macro_Set_CLK_Low();
    Macro_Set_CLK_High();
    CY = c_SPI_SO;
    _rlca_();    

    return(ACC);
}

void SD_SPI_WriteByte(uint8 ucSenddata)  
{   
    ACC = ucSenddata;
    
//bit7 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = ACC_7;
    Macro_Set_CLK_High();

//bit6 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = ACC_6;
    Macro_Set_CLK_High();
//bit5 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = ACC_5;
    Macro_Set_CLK_High();
//bit4 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = ACC_4;
    Macro_Set_CLK_High();
//bit3 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = ACC_3;
    Macro_Set_CLK_High();
//bit2 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = ACC_2;
    Macro_Set_CLK_High();
//bit1 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = ACC_1;
    Macro_Set_CLK_High();
//bit0 Shift Out To SD Card
    Macro_Set_CLK_Low();
    c_SPI_SI = ACC_0;
    Macro_Set_CLK_High();


//2楼: 奉上测试程序及测试记录 

//void TestSD_SPIFunc(void)
void main(void)
{
    uchar ucCount;
    uchar ucReaddata;


    ucCount = 0;        //开始

    for(ucCount=10; ucCount!=0; ucCount--)    //xwj改进的写法,机器周期数从502减少到481,节约21
    {
        SD_SPI_WriteByte(0x88);            //用C语言实现的写字节机器周期数为188
                                        //用C51的bdata变量,采用位指令直接传送操作,共用机器周期47
                                        //xwj改进的写法,共用机器周期46
    }

    for(ucCount=50; ucCount!=0; ucCount--)    //xwj改进的写法,机器周期数从2607减少到2091,节约516
    {
        ucReaddata = SD_SPI_ReadByte();    //用C语言实现的SPI读字节,机器周期为154
                                        //自用C51的bdata变量,采用位指令操作读字节机器周期为49
                                        //xwj改进的写法,共用机器周期40
    }
    ucCount= 88;        //结束
//    SD_read_sector(0,Page_Buf);
}
 
 

 

//本程序由xwj设计的UltraEdit脚本加亮显示,如需要脚本访问我的Blog 或发送邮件至:xwjfile@21cn.com

使用特权

评论回复
板凳
古道热肠|  楼主 | 2008-3-2 11:28 | 只看该作者

奉上测试程序及测试记录

void TestSD_SPIFunc(void)
{
    uchar ucCount;
    uchar ucReadData;


    ucCount = 0;        //开始

    for(ucCount=0; ucCount<10; ucCount++)
    {
        SPI_TransferByte(0x88);            //用C语言实现的写字节机器周期数为188
                                        //用C51的bData变量,采用位指令直接传送操作,共用机器机器49
    }

    for(ucCount=0; ucCount<50; ucCount++)
    {
        ucReadData = SPI_ReadByte();    //用C语言实现的SPI读字节机器赌周期为154
                                        //自用C51的bData变量,采用位指令操作读字节机器周期为49

    }
    ucCount= 88;        //结束
    SD_read_sector(0,Page_Buf);
}

使用特权

评论回复
地板
huangqi412| | 2008-3-2 11:41 | 只看该作者

收下,谢谢

使用特权

评论回复
5
3.3v| | 2008-3-2 11:44 | 只看该作者

以前搞了12232 LCD驱动,跟你的想法一样啊

去掉了for,真是轻松,空间也省出了很多.

使用特权

评论回复
6
古道热肠|  楼主 | 2008-3-2 12:06 | 只看该作者

每移一位占用4个机器周期

很奇怪51指令,读一个位到C用一个机器周期,而将C输出到位地址用2个机器周期,而且必须通过C中转。

MOV C,Bit   周期1,编码10100010
MoV   bit,C     周期2,编码10010010

使用特权

评论回复
7
hotpower| | 2008-3-2 12:14 | 只看该作者

mov bit,c是多一个周期~~~

使用特权

评论回复
8
hotpower| | 2008-3-2 12:17 | 只看该作者

ucReadData |= 0x01;和ucReadData ++;不知哪个快~~~

使用特权

评论回复
9
hotpower| | 2008-3-2 12:22 | 只看该作者

SD_SPI_ReadByte()内的ucReadData = 0;是废话~~~

使用特权

评论回复
10
xwj| | 2008-3-2 12:25 | 只看该作者

什么芯片? 51就用串口模式0,谁能更快???

使用特权

评论回复
11
hotpower| | 2008-3-2 12:25 | 只看该作者

for(ucCount=8; ucCount!=0; ucCount--)会简化为djnz~~~

使用特权

评论回复
12
xwj| | 2008-3-2 13:03 | 只看该作者

就是典型的空间换时间,51的任何端口操作都需要2个周期

51的串口模式0其实就是SPI方式,只有这种方法才是最快的

        while(~TI);    //可省略用于别的指令时间
        TI=0;          //可省略用于别的指令时间
        SBUF=temp;

使用特权

评论回复
13
hotpower| | 2008-3-2 13:13 | 只看该作者

51的串口模式0实际就是不带SS的SPI,肯定快

TXD->SCK,RXD->SIO

不过while(~TI);应该写成while(!TI);
~是对字节的取反,不过有些编译器是不区分的

使用特权

评论回复
14
xhtxzxw| | 2008-3-2 16:36 | 只看该作者

嘿嘿

也许还可以节约一个机器周期呢!
把“
sbit c_SPI_SI = P1^5;
sbit c_SPI_SO = P1^6;
sbit c_SPI_CLK = P1^7;
”换成

sbit c_SPI_SI = P1^7;
sbit c_SPI_SO = P1^6;
sbit c_SPI_CLK = P1^5;

(当然了,得重新做板子啦!嘿嘿,为了节约一个机器周期,花这么大代价啊!)
然后再把“ReadData_Bit7 = c_SPI_SO; ”换成“ucReadData = P1;”
这个句子换以前要三个机器周期,换了以后只要俩机器周期?似乎,也许,好象,呵呵

使用特权

评论回复
15
xhtxzxw| | 2008-3-2 17:34 | 只看该作者

嘿嘿

还有没有人能节约一个或几个机器周期啊?

使用特权

评论回复
16
古道热肠|  楼主 | 2008-3-3 10:05 | 只看该作者

回诸位

回XWJ,提到的单片机用串口工作在移位寄存器的方式模拟SPI发码,如果是单发设备是可以的,比如常见的HC595,就可以通过串口高速移出,但是呢,必须看到SPI是有数据输出,还有数据输入,所以如果要用51单片机串口直接接口SPI器件是需要用三态门来进行发送与接收数据的通道切换的,详见电力出版社的《PC机并行端口编程大全》,内有相关实例图纸。
回xhtxzxw,您提的建议不现实,即使改为P1口,也是一样的,读取SPI设备的数据必须经过一个串行数据并行化的操作。
把引脚配置成
sbit c_SPI_SI = P1^5;
sbit c_SPI_SO = P1^6;
sbit c_SPI_CLK = P1^7;
是为了与51单片机的硬件兼容,因为如果软件达不到速度,还可以采用带硬件SPI总线的单片机直接PinToPin替换,目前测试,128KbPs的Mp3数据流用软件模拟SPI收发,完全能满足要求,但播放256KBps的Mp3文件,出现停顿现象。

使用特权

评论回复
17
农民讲习所| | 2008-3-3 11:26 | 只看该作者

还可以优化

直接使用ACC,且将ACC做函数入口参数。

sbit I_SPI_SI = P1^5;
sbit O_SPI_SO = P1^6;
sbit O_SPI_CLK = P1^7;

sbit ACC0 = ACC^0;
sbit ACC0 = ACC^1;
sbit ACC0 = ACC^2;
sbit ACC0 = ACC^3;
sbit ACC0 = ACC^4;
sbit ACC0 = ACC^5;
sbit ACC0 = ACC^6;
sbit ACC0 = ACC^7;

#define SPI_CLK()    do{ O_SPI_CLK=0; O_SPI_CLK=1;}while(0)

void InSPI_WriteByte()
{
    O_SPI_SO = ACC0;
    SPI_CLK();

    O_SPI_SO = ACC1;
    SPI_CLK();

    O_SPI_SO = ACC2;
    SPI_CLK();

    O_SPI_SO = ACC3;
    SPI_CLK();

    O_SPI_SO = ACC4;
    SPI_CLK();

    O_SPI_SO = ACC5;
    SPI_CLK();

    O_SPI_SO = ACC6;
    SPI_CLK();

    O_SPI_SO = ACC7;
    SPI_CLK();
}

#define SD_SPI_WriteByte(ucSendData) do{ ACC=ucSendData;InSPI_WriteByte();}while(0)

使用特权

评论回复
18
xwj| | 2008-3-3 11:46 | 只看该作者

当然,汇编的话还可以避免通过R7的参数传递,继续减小一点

再快就用inline,避免CALL和RET

最快当然还是俺9楼说的: 什么芯片? 51就用串口模式0,一个周期传1位谁能更快???

使用特权

评论回复
19
古道热肠|  楼主 | 2008-3-3 12:11 | 只看该作者

经测试,农民代表出招效果甚微,写函数擂主暂为农民讲习

经测试,数据经ACC输出到SPI的输出引脚与经由BData变量一样,O_SPI_SO = ACC6;执行周期为3

节省的1周期为
原语句ucWriteData = ucSendData; 2周期
新语句ACC = ucSendData; 1周期

每扇区读写节约512X1 = 512个机器周期。

查看汇编发现即使是ACC的位寻址,也不能直接将其内容送IO管脚,必须先送到C,再经C输出到管脚。

使用特权

评论回复
20
古道热肠|  楼主 | 2008-3-3 12:19 | 只看该作者

好,XWJ出高招。

用与指令每位能节省1周期

使用特权

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

本版积分规则

284

主题

6411

帖子

16

粉丝