[经验] PROTEUS仿真学习笔记05 (SPI 协议 外设)——2014_7_15

SPI 的概念

***************

对初学者来说,SPI 应该比 I2C 难一些,原因:
1、C51 用SPI 的资料不多,要到STM32 等更高级MCU 资料才会多;
2、SPI 的资料比较生硬,不够形象;
3、极性和相位的组合。

目前,网上找到的,感觉最好的资料应该是,
http://www.niwozhi.net/demo_c487_i35069.html
http://my.oschina.net/freeblues/blog/67400

下面的内容有部分摘自上面的链接:

SPI 总线实施简单,仅使用2条数据信号线和控制信号线



        SCK, Serial Clock, 主要的作用是 Master 设备往 Slave 设备传输时钟信号, 控制数据交换的时机以及速率;

        SS/CS, Slave Select/Chip Select, 用于 Master 设备片选 Slave 设备, 使被选中的 Slave 设备能够被 Master 设备所访问;

        SDO/MOSI, Serial Data Output/Master Out Slave In, 在 Master 上面也被称为 Tx-Channel, 作为数据的出口, 主要用于 SPI 设备发送数据;

        SDI/MISO, Serial Data Input/Master In Slave Out, 在 Master 上面也被称为 Rx-Channel, 作为数据的入口, 主要用于SPI 设备接收数据;

一主多从:



因为图片是摘录的,所以有的用 SDO,有的用 MOSI,在理解上可以暂时认为一回事。
同理 SDI MISO
****************
环形数据交换
—— 等价交换
****************
数据传输通常会包含一次数据交换。当主节点向从节点发送数据时,从节点也会向主节点发送数据。为此,主节点的内部移位寄存器和从节点被设置成环形




由SCK提供时钟脉冲,SDI,SDO则基于此脉冲完成数据传输。数据输出通过 SDO线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。这样,在至少8次时钟信号的改变 (上沿和下沿为一次),就可以完成8位数据的传输。 



上面的概念 和 原理图就是 教程的主流,这也是 比较难于理解的原因,还不够具体。

最好,借鉴一个实例,来观察一下

SPI协议举例

SPI是一个环形总线结构,由ss(cs)、sck、sdi、sdo构成,其时序其实很简单,主要是在sck的控制下,两个双向移位寄存器进行数据交换。
       假设下面的8位寄存器装的是待发送的数据10101010,上升沿发送、下降沿接收、高位先发送。
       那么第一个上升沿来的时候 数据将会是sdo=1;寄存器=0101010x。下降沿到来的时候,sdi上的电平将所存到寄存器中去,那么这时寄存器=0101010sdi,这样在 8个时钟脉冲以后,两个寄存器的内容互相交换一次。这样就完成里一个spi时序。
举例:
       假设主机和从机初始化就绪:并且主机的sbuff=0xaa,从机的sbuff=0x55,下面将分步对spi的8个时钟周期的数据情况演示一遍:假设上升沿发送数据



这样就完成了两个寄存器8位的交换,上面的上表示上升沿、下表示下降沿,sdi、sdo相对于主机而言的。

已经很接近理解了,下一步就是把 上面的过程转为动画,









。。。。。。



有了上面的基础,就可以 来理解 让人 痛不欲生 的 极性和相位搭配了。

只看该作者 分享淘帖举报

 

20 条评论

只看该作者

发表评论
 

oldbeginner 2014-7-15 18:58:11

沙发

*********************
极性和相位

*********************

概念 摘录
http://blog.sina.com.cn/s/blog_69b5d2a50101am99.html

SPI的极性Polarity和相位Phase,最常见的写法是CPOL和CPHA,不过也有一些其他写法,简单总结如下:
(1) CKPOL (Clock Polarity) = CPOL = POL = Polarity = (时钟)极性
(2) CKPHA (Clock Phase)   = CPHA = PHA = Phase =(时钟)相位
(3) SCK=SCLK=SPI的时钟
(4) Edge=边沿,即时钟电平变化的时刻,即上升沿(rising edge)或者下降沿(falling edge)
对于一个时钟周期内,有两个edge,分别称为:
(1)Leadingedge=前一个边沿=第一个边沿,对于开始电压是1,那么就是1变成0的时候,对于开始电压是0,那么就是0变成1的时候;
(2)Trailingedge=后一个边沿=第二个边沿,对于开始电压是1,那么就是0变成1的时候(即在第一次1变成0之后,才可能有后面的0变成1),对于开始电压是0,那么就是1变成0的时候;

采用如下用法:
  • 极性=CPOL
  • 相位=CPHA
  • SCLK=时钟
  • 第一个边沿和第二个边沿
CPOL和CPHA,分别都可以是0或时1,对应的四种组合就是:





CPOL = 0 时,时钟在逻辑 0 处空闲:

如果 CPHA = 0,数据会在 SCK 的上升沿上读取,在下降沿上变化。

如果 CPHA = 1,数据会在 SCK 的下降沿上读取,在上升沿上变化。
 

CPOL = 1时,时钟在逻辑高电平处空闲:
  • 如果 CPHA = 0,数据会在 SCK的下降沿上读取,在上升沿上变化。
  • 如果 CPHA = 1,数据会在 SCK 的上升沿上读取,在下降沿上变化。


下面详细介绍。
CPOL极性        先说什么是SCLK时钟的空闲时刻,其就是当SCLK在发送8个bit比特数据之前和之后的状态,于此对应的,SCLK在发送数据的时候,就是正常的工作的时候,有效active的时刻了。其英文精简解释为:ClockPolarity = IDLE state of SCK。
SPI的CPOL,表示当SCLK空闲idle的时候,其电平的值是低电平0还是高电平1:
CPOL=0,时钟空闲idle时候的电平是低电平,所以当SCLK有效的时候,就是高电平,就是所谓的active-high;
CPOL=1,时钟空闲idle时候的电平是高电平,所以当SCLK有效的时候,就是低电平,就是所谓的active-low

CPHA相位         首先说明一点,capture strobe = latch= read =sample,都是表示数据采样,数据有效的时刻。相位,对应着数据采样是在第几个边沿(edge),是第一个边沿还是第二个边沿,0对应着第一个边沿,1对应着第二个边沿。
对于:
CPHA=0,表示第一个边沿:
对于CPOL=0,idle时候的是低电平,第一个边沿就是从低变到高,所以是上升沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从高变到低,所以是下降沿;
CPHA=1,表示第二个边沿:
对于CPOL=0,idle时候的是低电平,第二个边沿就是从高变到低,所以是下降沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从低变到高,所以是上升沿;

****************************************
极性和相位一共有 4种组合,利用 (极性,相位)来表示,即模式
(0,0)(0,1)(1,0)(1,1)

其中 (0,0)和(1,1)都是最常用的的,尤其是(0,0)

再来看



上图,表示的是(0,0)或者(1,1)模式,即
数据会在 SCK 的上升沿上读取,在下降沿上变化。

 
1回复

举报

 

oldbeginner 2014-7-16 18:37:54

板凳

oldbeginner 发表于 2014-7-15 18:58
*********************
极性和相位

***********************
W25Q16 的例子
—— 宁波芯动
***********************


W25Q16 是一个 SPI FLASH,C51开发板上不多,STM32 开发板上常见;不过 STM32 有自己的 SPI 寄存器,可以进行设置;而C51 没有 SPI 寄存器,需要模拟出 SPI 协议。

看了一些C51 SPI例子,一方面例子真得非常稀少,另一方面 通用性也差。

这里先看一下 芯动开发板的 W25Q16 实验。
W25Q16的手册
http://wenku.baidu.com/view/5e6bcbf604a1b0717fd5dd95.html

其工作模式 0 或模式 3。

根据SPI 协议,主从 是同时交换数据,所以 读 和 写可以在 一个函数内完成,但是 实际当中,读数据 和写数据 都分成了两个函数。


//名称: SPI写入一个字节函数
void Send_OneByte(unsigned char DATA8) //从SPI发8位数
{                                                                           //上升沿写入
   unsigned char x;      
   for (x=0;x<8;x++)
   { 
                 SCLK=0;
                 if(DATA8&0x80)DIO=1;
                 else DIO=0;
                 SCLK=1;
                 DATA8=DATA8<<1;
        }     


写数据的同时,其实也可以接受数据。不过,这里 把 接受数据 去掉了,但是 动画还是 展示了 接受数据。




再来看 读一个字节,

//名称: SPI读出一个字节函数
unsigned char Read_OneByte(void)     //从SPI收8位数
   {                                                         //下降沿输出
   unsigned char DATA8;
   unsigned char x;
   SCLK=1;
   DATA8=0x00;
   for (x=0;x<8;x++)
   { 
                 _nop_();
                 SCLK=0;
                 DATA8=DATA8<<1;
                 if(DO)DATA8=DATA8|0x01;
                 SCLK=1;         
        }
   return (DATA8);   
}



 

 
回复

举报

 

oldbeginner 2014-7-16 19:02:58

3#

oldbeginner 发表于 2014-7-16 18:37
***********************
W25Q16 的例子
—— 宁波芯动

********************
把 读和写 合并成一个函数

修改芯动的读函数
********************

unsigned char Read_OneByte(unsigned char DATA8)     //从SPI收8位数,并送出 DATA8
   {                                                         //下降沿输出

   unsigned char x;
   SCLK=1;

   for (x=0;x<8;x++)
   { 
                 _nop_();
                 SCLK=0;

                 if(DATA8&0x80)DIO=1;
                 else DIO=0;

                 DATA8=DATA8<<1;
                 if(DO)DATA8=DATA8|0x01;
                 SCLK=1;         
        }
   return (DATA8);   
}


因为 数据会在 SCK 的上升沿上读取,在下降沿上变化。

感觉还是有点怪。
 
回复

举报

 

oldbeginner 2014-7-16 19:18:43

4#

本帖最后由 oldbeginner 于 2014-7-17 18:42 编辑
oldbeginner 发表于 2014-7-16 19:02
********************
把 读和写 合并成一个函数

********************奇怪的变形
——再改 
********************

unsigned char Read_OneByte(unsigned char DATA8)     //从SPI收8位数,并送出 DATA8
   {                                                         //下降沿输出

   unsigned char x;
   SCLK=0;

   for (x=0;x<8;x++)
   { 
                   SCLK=0;

                 if(DATA8&0x80)DIO=1;
                 else DIO=0;

                 DATA8=DATA8<<1;

                 SCLK=1;     
    
                 if(DO)DATA8=DATA8|0x01;       
     }
   return (DATA8);   
}

改成这样,一样是可以的。但是 DIO赋值的语句不能 随便改。
难道 这就是 模式0。

同样,只能在 下面的方式下工作:

SCLK=0;
for( ; ; ;)
{
   SCLK=0;

   发送数据();

   SCLK=1;

    接收数据();
}

同样,也可以更改成 模式3 (1,1)

unsigned char Read_OneByte(unsigned char DATA8)     //从SPI收8位数,并送出 DATA8
   {                                                         //下降沿输出

   unsigned char x;
   SCLK=1;

   for (x=0;x<8;x++)
   { 
                   SCLK=0;

                 if(DATA8&0x80)DIO=1;
                 else DIO=0;

                 DATA8=DATA8<<1;

                 SCLK=1;     
    
                 if(DO)DATA8=DATA8|0x01;       
     }
   return (DATA8);   
}



SCLK=1;
for( ; ; ;)
{
   SCLK=0;

   发送数据();

   SCLK=1;

    接收数据();
}


**************************

SCLK=0; //模式0
// SCLK=1; //模式1

for( ; ; ;)
{
   SCLK=0;

   发送数据();

   SCLK=1;

    接收数据();
}

原理如此,还是比较好理解的。
 
回复

举报

 

折羽燕 2014-7-16 20:10:19

5#

讲的很透彻,谢谢了,辛苦了~~
 
回复

举报

 

oldbeginner 2014-7-16 20:22:12

6#

oldbeginner 发表于 2014-7-16 19:18
********************奇怪的变形
——再改 
********************

************
NRF24L01 的SPI
************

这个看起来就很简单了,也更加清晰。

uchar SPI_RW(uchar byte)
{
        uchar bit_ctr;
        for(bit_ctr=0;bit_ctr<8;bit_ctr++)  // 输出8位
        {
                NRF_MOSI=(byte&0x80);                         // MSB TO MOSI
                byte=(byte<<1);                                        // shift next bit to MSB
                NRF_SCK=1;
                byte|=NRF_MISO;                                // capture current MISO bit
                NRF_SCK=0;
        }
        return byte;
}

SCK  0 ---> 1 之间,MOSI 赋值;
SCK  1 ---->0 之间,byte 赋值。

 
 
回复

举报

 

oldbeginner 2014-7-16 20:45:43

7#

本帖最后由 oldbeginner 于 2014-7-17 16:06 编辑
oldbeginner 发表于 2014-7-16 20:22
************
NRF24L01 的SPI
************

******************
93C66 的例子
******************

http://wenku.baidu.com/view/1f8e9d06eff9aef8941e06bc.html

// 函数名称: SPISendByte
// 入口参数: ch
// 函数功能: 发送一个字节
//--------------------------------------------------------------------------------------------------
void SPISendByte(unsigned char ch)

        unsigned char idata n=8;      // 向SDA上发送一位数据字节,共八位      
         SCK = 1 ;                   //时钟置高
         SS1 = 0 ;                   //选择从机

        while(n--)
        { 
             
                     SCK = 0 ;                   //时钟置低
                   if((ch&0x80) == 0x80)     // 若要发送的数据最高位为1则发送位1
                   {       
                           MOSI = 1;     // 传送位1
                   }
                   else
                   {  
                            MOSI = 0;     // 否则传送位0
                       }
                 
                  ch = ch<<1;         // 数据左移一位
                    SCK = 1 ;                   //时钟置高 
          }
}



发送和读取也是分成了两个函数,

// 函数名称: SPIreceiveByte
// 返回接收的数据
// 函数功能: 接收一字节子程序
//--------------------------------------------------------------------------------------------------
unsigned char SPIreceiveByte()
{
        unsigned char idata n=8;     // 从MISO线上读取一上数据字节,共八位
        unsigned char tdata;

         SCK = 1;                    //时钟为高
         SS1 = 0;                    //选择从机

                while(n--)
                {       
                         
                         SCK = 0;                    //时钟为低

                           tdata = tdata<<1;     // 左移一位,或_crol_(temp,1)
                           if(MISO == 1)
                                    tdata = tdata|0x01;     // 若接收到的位为1,则数据的最后一位置1
                           else 
                                   tdata = tdata&0xfe;     // 否则数据的最后一位置0
                           SCK=1;
                }
        return(tdata);
}


很奇怪,读和写 都是 在 SCK 位于 0 1 之间赋值的。

SPI 协议 真乱。
7-17 补充:感觉这篇文章的代码有问题,

修改如下并合并,
unsigned char SPISendByte(unsigned char ch)

        unsigned char idata n=8;      // 向SDA上发送一位数据字节,共八位      

        while(n--)
        { 
             
                     SCK = 0 ;                   //时钟置低
                   if((ch&0x80) == 0x80)     // 若要发送的数据最高位为1则发送位1
                   {       
                           MOSI = 1;     // 传送位1
                   }
                   else
                   {  
                            MOSI = 0;     // 否则传送位0
                       }
                 
                  ch = ch<<1;         // 数据左移一位


                  tdata = tdata<<1;     // 左移一位,或_crol_(temp,1)

                    SCK = 1 ;                   //时钟置高 
                         
                           if(MISO == 1)
                                    tdata = tdata|0x01;     // 若接收到的位为1,则数据的最后一位置1
                           else 
                                   tdata = tdata&0xfe;     // 否则数据的最后一位置0

          }
        return(tdata);
}

 
 
回复

举报

 

oldbeginner 2014-7-16 21:15:13

8#

oldbeginner 发表于 2014-7-16 20:45
******************
93C66 的例子
******************

*******************
常用的SPI C51 模拟
重复
*******************

http://www.dz863.com/Microprocessors/MCS-8051/SPI-C51.htm

SPI接口有一个特点,即在时钟SCK的上升沿打入数据MOSI,在下降沿读入数据MISO. 
片选信号CS有正负区别.在硬件上MOSI与MISO是可以短路变为SIO可读写IO的. 
故SPI可为(不包括CS) 
三线(SCK,MOSI,MISO)协议,两线(SCK,SIO)协议 
再者,SPI一般为双向同时高速收发数据的,方向由时钟SCK的跳变沿决定。 

根据以上所述,模拟SPI读写模块编制成为一体化模块是必要的。 
而且调用规则只需注意读数据时要写入0xff即可。非常方便好用。 

例如: 
res = SpiReadWrite(val);//模块写 
SpiReadWrite(0xff);//模块读 
对于具体器件,由于涉及到命令、地址及数据等,故一个完整的SPI读或写操作 
可能需要几个模拟SPI读写一体化模块来完成。 

所以一般完整的SPI读或写操作需以下函数组合完成 

void SpiOpen(void); //打开片选 CS=0或CS=1 
void SpiClose(void); //关闭片选 CS=1或CS=0 
unsigned char SpiReadWrite(val); //模拟SPI读写一体化模块 
void SpiWriteEnable(void); //使能写操作 
void SpiWriteDISAble(void); //禁止写操作 
unsigned char SpiReadStatus(void); //读状态 
void SpiWriteStatus(unsigned char val); //写状态 
void SpiWriteWait(void); //等待写入完成 
void SpiWriteByte(unsigned int addr, unsigned char val);//写一个字节 
void SpiWriteWord(unsigned char addr, unsigned int val);//写一个字 
unsigned char SpiReadByte(unsigned int addr); //读一个字节 
unsigned int SpiReadWord(unsigned char addr); //读一个字 
/*---------------------------------------------------------------------------- 

/*----------------------------------------------- 
例: 
X5045模拟SPI读写一体化模块 
PTR905模拟SPI读写一体化模块 
------------------------------------------------* 
unsigned char SpiReadWrite(unsigned char val) 

unsigned char i; 
ACC = val; 
for (i = 8; i > 0; i --) 

CY = MISO;//取数据SO 
_rlca_();//存数据ACC.0读数据ACC.7同时进行 
MOSI = CY;//送数据SI 
SCK = 1;//上升沿打入数据 
_nop_();//延时 
SCK = 0;//下降沿读入数据 

return ACC; 


/*----------------------------------------------- 
例: 
X5045模拟SPI读写一体化模块 
ISD4004模拟SPI读写一体化模块 
------------------------------------------------* 
unsigned char SpiReadWrite(unsigned char val) 

unsigned char i; 
ACC = val; 
for (i = 8; i > 0; i --) 

SCK = 0;//下降沿读入数据 
_nop_();//延时 
CY = MISO;//取数据SO 
_rlca_();//存数据ACC.0读数据ACC.7同时进行 
MOSI = CY;//送数据SI 
_nop_();//延时 
SCK = 1;//上升沿打入数据 
MOSI = 1;//释放总线SI 

return ACC; 


/*----------------------------------------------- 
例: 
AT93C46模拟SPI读写模块 
------------------------------------------------* 
sbit ACC_7 = ACC^7; 
unsigned char SpiReadWrite(unsigned char val) 

unsigned char i; 
ACC = val; 
for (i = 8; i > 0; i --) 

CY = ACC_7;//读数据ACC.7 
MOSI = CY;//送数据SI 
SCK = 1;//上升沿打入数据 
CY = MISO;//取数据SO 
_rlca_();//存数据ACC.0 
SCK = 0;//下降沿 

return ACC; 

 
 
回复

举报

 

oldbeginner 2014-7-17 15:47:44

9#

oldbeginner 发表于 2014-7-16 21:15
*******************
常用的SPI C51 模拟
重复

********************
SD卡 的 SPI 模式

********************

SD 的 SPI 模式 写出来 是如此简单。

//写一字节到SD卡,模拟SPI总线方式
void SdWrite(unsigned char n)
{

unsigned char i;

for(i=8;i;i--)
{
SD_CLK=0;
SD_DI=(n&0x80);
n<<=1;
SD_CLK=1;
}
SD_DI=1; 

下降沿 发出数据。

//===========================================================
//从SD卡读一字节,模拟SPI总线方式
unsigned char SdRead()
{
unsigned char n,i;
for(i=8;i;i--)
{
SD_CLK=0;
SD_CLK=1;
n<<=1;
if(SD_DO) n|=1;

}
return n;
}

上升沿 接收数据。
 
回复

举报

 

slim443 2014-7-18 16:16:08

10#

协议介绍的很详细
 
回复

举报

 

gh037 2014-7-21 01:15:03

11#

{:1:}
 
回复

举报

 

家瑞 2014-9-25 11:28:15

12#

顶楼主,写的不错
 
回复

举报

 

我是流氓羽 2015-6-17 10:57:06

13#

讲的太好了,默默的收藏
 
回复

举报

 

shunbaiwang 2016-2-14 21:39:06

14#

看起来挺有用的,感谢楼主
 
回复

举报

 

qq543538634 2016-8-31 14:23:27

15#

这么深入浅出 通俗易懂的教程居然没人顶??  
谢谢楼主啦!
 
回复

举报

 

我是你的唯一 2016-11-11 21:02:45

16#

不错,确实很有用的 一定好好学习
 
回复

举报

 
SPI 的概念

***************

对初学者来说,SPI 应该比 I2C 难一些,原因:
1、C51 用SPI 的资料不多,要到STM32 等更高级MCU 资料才会多;
2、SPI 的资料比较生硬,不够形象;
3、极性和相位的组合。

目前,网上找到的,感觉最好的资料应该是,
http://www.niwozhi.net/demo_c487_i35069.html
http://my.oschina.net/freeblues/blog/67400

下面的内容有部分摘自上面的链接:

SPI 总线实施简单,仅使用2条数据信号线和控制信号线



        SCK, Serial Clock, 主要的作用是 Master 设备往 Slave 设备传输时钟信号, 控制数据交换的时机以及速率;

        SS/CS, Slave Select/Chip Select, 用于 Master 设备片选 Slave 设备, 使被选中的 Slave 设备能够被 Master 设备所访问;

        SDO/MOSI, Serial Data Output/Master Out Slave In, 在 Master 上面也被称为 Tx-Channel, 作为数据的出口, 主要用于 SPI 设备发送数据;

        SDI/MISO, Serial Data Input/Master In Slave Out, 在 Master 上面也被称为 Rx-Channel, 作为数据的入口, 主要用于SPI 设备接收数据;

一主多从:



因为图片是摘录的,所以有的用 SDO,有的用 MOSI,在理解上可以暂时认为一回事。
同理 SDI MISO
****************
环形数据交换
—— 等价交换
****************
数据传输通常会包含一次数据交换。当主节点向从节点发送数据时,从节点也会向主节点发送数据。为此,主节点的内部移位寄存器和从节点被设置成环形




由SCK提供时钟脉冲,SDI,SDO则基于此脉冲完成数据传输。数据输出通过 SDO线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。这样,在至少8次时钟信号的改变 (上沿和下沿为一次),就可以完成8位数据的传输。 



上面的概念 和 原理图就是 教程的主流,这也是 比较难于理解的原因,还不够具体。

最好,借鉴一个实例,来观察一下

SPI协议举例

SPI是一个环形总线结构,由ss(cs)、sck、sdi、sdo构成,其时序其实很简单,主要是在sck的控制下,两个双向移位寄存器进行数据交换。
       假设下面的8位寄存器装的是待发送的数据10101010,上升沿发送、下降沿接收、高位先发送。
       那么第一个上升沿来的时候 数据将会是sdo=1;寄存器=0101010x。下降沿到来的时候,sdi上的电平将所存到寄存器中去,那么这时寄存器=0101010sdi,这样在 8个时钟脉冲以后,两个寄存器的内容互相交换一次。这样就完成里一个spi时序。
举例:
       假设主机和从机初始化就绪:并且主机的sbuff=0xaa,从机的sbuff=0x55,下面将分步对spi的8个时钟周期的数据情况演示一遍:假设上升沿发送数据



这样就完成了两个寄存器8位的交换,上面的上表示上升沿、下表示下降沿,sdi、sdo相对于主机而言的。

已经很接近理解了,下一步就是把 上面的过程转为动画,









。。。。。。



有了上面的基础,就可以 来理解 让人 痛不欲生 的 极性和相位搭配了。

只看该作者 分享淘帖举报

 

20 条评论

只看该作者

发表评论
 

oldbeginner 2014-7-15 18:58:11

沙发

*********************
极性和相位

*********************

概念 摘录
http://blog.sina.com.cn/s/blog_69b5d2a50101am99.html

SPI的极性Polarity和相位Phase,最常见的写法是CPOL和CPHA,不过也有一些其他写法,简单总结如下:
(1) CKPOL (Clock Polarity) = CPOL = POL = Polarity = (时钟)极性
(2) CKPHA (Clock Phase)   = CPHA = PHA = Phase =(时钟)相位
(3) SCK=SCLK=SPI的时钟
(4) Edge=边沿,即时钟电平变化的时刻,即上升沿(rising edge)或者下降沿(falling edge)
对于一个时钟周期内,有两个edge,分别称为:
(1)Leadingedge=前一个边沿=第一个边沿,对于开始电压是1,那么就是1变成0的时候,对于开始电压是0,那么就是0变成1的时候;
(2)Trailingedge=后一个边沿=第二个边沿,对于开始电压是1,那么就是0变成1的时候(即在第一次1变成0之后,才可能有后面的0变成1),对于开始电压是0,那么就是1变成0的时候;

采用如下用法:
  • 极性=CPOL
  • 相位=CPHA
  • SCLK=时钟
  • 第一个边沿和第二个边沿
CPOL和CPHA,分别都可以是0或时1,对应的四种组合就是:





CPOL = 0 时,时钟在逻辑 0 处空闲:

如果 CPHA = 0,数据会在 SCK 的上升沿上读取,在下降沿上变化。

如果 CPHA = 1,数据会在 SCK 的下降沿上读取,在上升沿上变化。
 

CPOL = 1时,时钟在逻辑高电平处空闲:
  • 如果 CPHA = 0,数据会在 SCK的下降沿上读取,在上升沿上变化。
  • 如果 CPHA = 1,数据会在 SCK 的上升沿上读取,在下降沿上变化。


下面详细介绍。
CPOL极性        先说什么是SCLK时钟的空闲时刻,其就是当SCLK在发送8个bit比特数据之前和之后的状态,于此对应的,SCLK在发送数据的时候,就是正常的工作的时候,有效active的时刻了。其英文精简解释为:ClockPolarity = IDLE state of SCK。
SPI的CPOL,表示当SCLK空闲idle的时候,其电平的值是低电平0还是高电平1:
CPOL=0,时钟空闲idle时候的电平是低电平,所以当SCLK有效的时候,就是高电平,就是所谓的active-high;
CPOL=1,时钟空闲idle时候的电平是高电平,所以当SCLK有效的时候,就是低电平,就是所谓的active-low

CPHA相位         首先说明一点,capture strobe = latch= read =sample,都是表示数据采样,数据有效的时刻。相位,对应着数据采样是在第几个边沿(edge),是第一个边沿还是第二个边沿,0对应着第一个边沿,1对应着第二个边沿。
对于:
CPHA=0,表示第一个边沿:
对于CPOL=0,idle时候的是低电平,第一个边沿就是从低变到高,所以是上升沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从高变到低,所以是下降沿;
CPHA=1,表示第二个边沿:
对于CPOL=0,idle时候的是低电平,第二个边沿就是从高变到低,所以是下降沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从低变到高,所以是上升沿;

****************************************
极性和相位一共有 4种组合,利用 (极性,相位)来表示,即模式
(0,0)(0,1)(1,0)(1,1)

其中 (0,0)和(1,1)都是最常用的的,尤其是(0,0)

再来看



上图,表示的是(0,0)或者(1,1)模式,即
数据会在 SCK 的上升沿上读取,在下降沿上变化。

 
1回复

举报

 

oldbeginner 2014-7-16 18:37:54

板凳

oldbeginner 发表于 2014-7-15 18:58
*********************
极性和相位

***********************
W25Q16 的例子
—— 宁波芯动
***********************


W25Q16 是一个 SPI FLASH,C51开发板上不多,STM32 开发板上常见;不过 STM32 有自己的 SPI 寄存器,可以进行设置;而C51 没有 SPI 寄存器,需要模拟出 SPI 协议。

看了一些C51 SPI例子,一方面例子真得非常稀少,另一方面 通用性也差。

这里先看一下 芯动开发板的 W25Q16 实验。
W25Q16的手册
http://wenku.baidu.com/view/5e6bcbf604a1b0717fd5dd95.html

其工作模式 0 或模式 3。

根据SPI 协议,主从 是同时交换数据,所以 读 和 写可以在 一个函数内完成,但是 实际当中,读数据 和写数据 都分成了两个函数。


//名称: SPI写入一个字节函数
void Send_OneByte(unsigned char DATA8) //从SPI发8位数
{                                                                           //上升沿写入
   unsigned char x;      
   for (x=0;x<8;x++)
   { 
                 SCLK=0;
                 if(DATA8&0x80)DIO=1;
                 else DIO=0;
                 SCLK=1;
                 DATA8=DATA8<<1;
        }     


写数据的同时,其实也可以接受数据。不过,这里 把 接受数据 去掉了,但是 动画还是 展示了 接受数据。




再来看 读一个字节,

//名称: SPI读出一个字节函数
unsigned char Read_OneByte(void)     //从SPI收8位数
   {                                                         //下降沿输出
   unsigned char DATA8;
   unsigned char x;
   SCLK=1;
   DATA8=0x00;
   for (x=0;x<8;x++)
   { 
                 _nop_();
                 SCLK=0;
                 DATA8=DATA8<<1;
                 if(DO)DATA8=DATA8|0x01;
                 SCLK=1;         
        }
   return (DATA8);   
}



 

 
回复

举报

 

oldbeginner 2014-7-16 19:02:58

3#

oldbeginner 发表于 2014-7-16 18:37
***********************
W25Q16 的例子
—— 宁波芯动

********************
把 读和写 合并成一个函数

修改芯动的读函数
********************

unsigned char Read_OneByte(unsigned char DATA8)     //从SPI收8位数,并送出 DATA8
   {                                                         //下降沿输出

   unsigned char x;
   SCLK=1;

   for (x=0;x<8;x++)
   { 
                 _nop_();
                 SCLK=0;

                 if(DATA8&0x80)DIO=1;
                 else DIO=0;

                 DATA8=DATA8<<1;
                 if(DO)DATA8=DATA8|0x01;
                 SCLK=1;         
        }
   return (DATA8);   
}


因为 数据会在 SCK 的上升沿上读取,在下降沿上变化。

感觉还是有点怪。
 
回复

举报

 

oldbeginner 2014-7-16 19:18:43

4#

本帖最后由 oldbeginner 于 2014-7-17 18:42 编辑
oldbeginner 发表于 2014-7-16 19:02
********************
把 读和写 合并成一个函数

********************奇怪的变形
——再改 
********************

unsigned char Read_OneByte(unsigned char DATA8)     //从SPI收8位数,并送出 DATA8
   {                                                         //下降沿输出

   unsigned char x;
   SCLK=0;

   for (x=0;x<8;x++)
   { 
                   SCLK=0;

                 if(DATA8&0x80)DIO=1;
                 else DIO=0;

                 DATA8=DATA8<<1;

                 SCLK=1;     
    
                 if(DO)DATA8=DATA8|0x01;       
     }
   return (DATA8);   
}

改成这样,一样是可以的。但是 DIO赋值的语句不能 随便改。
难道 这就是 模式0。

同样,只能在 下面的方式下工作:

SCLK=0;
for( ; ; ;)
{
   SCLK=0;

   发送数据();

   SCLK=1;

    接收数据();
}

同样,也可以更改成 模式3 (1,1)

unsigned char Read_OneByte(unsigned char DATA8)     //从SPI收8位数,并送出 DATA8
   {                                                         //下降沿输出

   unsigned char x;
   SCLK=1;

   for (x=0;x<8;x++)
   { 
                   SCLK=0;

                 if(DATA8&0x80)DIO=1;
                 else DIO=0;

                 DATA8=DATA8<<1;

                 SCLK=1;     
    
                 if(DO)DATA8=DATA8|0x01;       
     }
   return (DATA8);   
}



SCLK=1;
for( ; ; ;)
{
   SCLK=0;

   发送数据();

   SCLK=1;

    接收数据();
}


**************************

SCLK=0; //模式0
// SCLK=1; //模式1

for( ; ; ;)
{
   SCLK=0;

   发送数据();

   SCLK=1;

    接收数据();
}

原理如此,还是比较好理解的。
 
回复

举报

 

折羽燕 2014-7-16 20:10:19

5#

讲的很透彻,谢谢了,辛苦了~~
 
回复

举报

 

oldbeginner 2014-7-16 20:22:12

6#

oldbeginner 发表于 2014-7-16 19:18
********************奇怪的变形
——再改 
********************

************
NRF24L01 的SPI
************

这个看起来就很简单了,也更加清晰。

uchar SPI_RW(uchar byte)
{
        uchar bit_ctr;
        for(bit_ctr=0;bit_ctr<8;bit_ctr++)  // 输出8位
        {
                NRF_MOSI=(byte&0x80);                         // MSB TO MOSI
                byte=(byte<<1);                                        // shift next bit to MSB
                NRF_SCK=1;
                byte|=NRF_MISO;                                // capture current MISO bit
                NRF_SCK=0;
        }
        return byte;
}

SCK  0 ---> 1 之间,MOSI 赋值;
SCK  1 ---->0 之间,byte 赋值。

 
 
回复

举报

 

oldbeginner 2014-7-16 20:45:43

7#

本帖最后由 oldbeginner 于 2014-7-17 16:06 编辑
oldbeginner 发表于 2014-7-16 20:22
************
NRF24L01 的SPI
************

******************
93C66 的例子
******************

http://wenku.baidu.com/view/1f8e9d06eff9aef8941e06bc.html

// 函数名称: SPISendByte
// 入口参数: ch
// 函数功能: 发送一个字节
//--------------------------------------------------------------------------------------------------
void SPISendByte(unsigned char ch)

        unsigned char idata n=8;      // 向SDA上发送一位数据字节,共八位      
         SCK = 1 ;                   //时钟置高
         SS1 = 0 ;                   //选择从机

        while(n--)
        { 
             
                     SCK = 0 ;                   //时钟置低
                   if((ch&0x80) == 0x80)     // 若要发送的数据最高位为1则发送位1
                   {       
                           MOSI = 1;     // 传送位1
                   }
                   else
                   {  
                            MOSI = 0;     // 否则传送位0
                       }
                 
                  ch = ch<<1;         // 数据左移一位
                    SCK = 1 ;                   //时钟置高 
          }
}



发送和读取也是分成了两个函数,

// 函数名称: SPIreceiveByte
// 返回接收的数据
// 函数功能: 接收一字节子程序
//--------------------------------------------------------------------------------------------------
unsigned char SPIreceiveByte()
{
        unsigned char idata n=8;     // 从MISO线上读取一上数据字节,共八位
        unsigned char tdata;

         SCK = 1;                    //时钟为高
         SS1 = 0;                    //选择从机

                while(n--)
                {       
                         
                         SCK = 0;                    //时钟为低

                           tdata = tdata<<1;     // 左移一位,或_crol_(temp,1)
                           if(MISO == 1)
                                    tdata = tdata|0x01;     // 若接收到的位为1,则数据的最后一位置1
                           else 
                                   tdata = tdata&0xfe;     // 否则数据的最后一位置0
                           SCK=1;
                }
        return(tdata);
}


很奇怪,读和写 都是 在 SCK 位于 0 1 之间赋值的。

SPI 协议 真乱。
7-17 补充:感觉这篇文章的代码有问题,

修改如下并合并,
unsigned char SPISendByte(unsigned char ch)

        unsigned char idata n=8;      // 向SDA上发送一位数据字节,共八位      

        while(n--)
        { 
             
                     SCK = 0 ;                   //时钟置低
                   if((ch&0x80) == 0x80)     // 若要发送的数据最高位为1则发送位1
                   {       
                           MOSI = 1;     // 传送位1
                   }
                   else
                   {  
                            MOSI = 0;     // 否则传送位0
                       }
                 
                  ch = ch<<1;         // 数据左移一位


                  tdata = tdata<<1;     // 左移一位,或_crol_(temp,1)

                    SCK = 1 ;                   //时钟置高 
                         
                           if(MISO == 1)
                                    tdata = tdata|0x01;     // 若接收到的位为1,则数据的最后一位置1
                           else 
                                   tdata = tdata&0xfe;     // 否则数据的最后一位置0

          }
        return(tdata);
}

 
 
回复

举报

 

oldbeginner 2014-7-16 21:15:13

8#

oldbeginner 发表于 2014-7-16 20:45
******************
93C66 的例子
******************

*******************
常用的SPI C51 模拟
重复
*******************

http://www.dz863.com/Microprocessors/MCS-8051/SPI-C51.htm

SPI接口有一个特点,即在时钟SCK的上升沿打入数据MOSI,在下降沿读入数据MISO. 
片选信号CS有正负区别.在硬件上MOSI与MISO是可以短路变为SIO可读写IO的. 
故SPI可为(不包括CS) 
三线(SCK,MOSI,MISO)协议,两线(SCK,SIO)协议 
再者,SPI一般为双向同时高速收发数据的,方向由时钟SCK的跳变沿决定。 

根据以上所述,模拟SPI读写模块编制成为一体化模块是必要的。 
而且调用规则只需注意读数据时要写入0xff即可。非常方便好用。 

例如: 
res = SpiReadWrite(val);//模块写 
SpiReadWrite(0xff);//模块读 
对于具体器件,由于涉及到命令、地址及数据等,故一个完整的SPI读或写操作 
可能需要几个模拟SPI读写一体化模块来完成。 

所以一般完整的SPI读或写操作需以下函数组合完成 

void SpiOpen(void); //打开片选 CS=0或CS=1 
void SpiClose(void); //关闭片选 CS=1或CS=0 
unsigned char SpiReadWrite(val); //模拟SPI读写一体化模块 
void SpiWriteEnable(void); //使能写操作 
void SpiWriteDISAble(void); //禁止写操作 
unsigned char SpiReadStatus(void); //读状态 
void SpiWriteStatus(unsigned char val); //写状态 
void SpiWriteWait(void); //等待写入完成 
void SpiWriteByte(unsigned int addr, unsigned char val);//写一个字节 
void SpiWriteWord(unsigned char addr, unsigned int val);//写一个字 
unsigned char SpiReadByte(unsigned int addr); //读一个字节 
unsigned int SpiReadWord(unsigned char addr); //读一个字 
/*---------------------------------------------------------------------------- 

/*----------------------------------------------- 
例: 
X5045模拟SPI读写一体化模块 
PTR905模拟SPI读写一体化模块 
------------------------------------------------* 
unsigned char SpiReadWrite(unsigned char val) 

unsigned char i; 
ACC = val; 
for (i = 8; i > 0; i --) 

CY = MISO;//取数据SO 
_rlca_();//存数据ACC.0读数据ACC.7同时进行 
MOSI = CY;//送数据SI 
SCK = 1;//上升沿打入数据 
_nop_();//延时 
SCK = 0;//下降沿读入数据 

return ACC; 


/*----------------------------------------------- 
例: 
X5045模拟SPI读写一体化模块 
ISD4004模拟SPI读写一体化模块 
------------------------------------------------* 
unsigned char SpiReadWrite(unsigned char val) 

unsigned char i; 
ACC = val; 
for (i = 8; i > 0; i --) 

SCK = 0;//下降沿读入数据 
_nop_();//延时 
CY = MISO;//取数据SO 
_rlca_();//存数据ACC.0读数据ACC.7同时进行 
MOSI = CY;//送数据SI 
_nop_();//延时 
SCK = 1;//上升沿打入数据 
MOSI = 1;//释放总线SI 

return ACC; 


/*----------------------------------------------- 
例: 
AT93C46模拟SPI读写模块 
------------------------------------------------* 
sbit ACC_7 = ACC^7; 
unsigned char SpiReadWrite(unsigned char val) 

unsigned char i; 
ACC = val; 
for (i = 8; i > 0; i --) 

CY = ACC_7;//读数据ACC.7 
MOSI = CY;//送数据SI 
SCK = 1;//上升沿打入数据 
CY = MISO;//取数据SO 
_rlca_();//存数据ACC.0 
SCK = 0;//下降沿 

return ACC; 

 
 
回复

举报

 

oldbeginner 2014-7-17 15:47:44

9#

oldbeginner 发表于 2014-7-16 21:15
*******************
常用的SPI C51 模拟
重复

********************
SD卡 的 SPI 模式

********************

SD 的 SPI 模式 写出来 是如此简单。

//写一字节到SD卡,模拟SPI总线方式
void SdWrite(unsigned char n)
{

unsigned char i;

for(i=8;i;i--)
{
SD_CLK=0;
SD_DI=(n&0x80);
n<<=1;
SD_CLK=1;
}
SD_DI=1; 

下降沿 发出数据。

//===========================================================
//从SD卡读一字节,模拟SPI总线方式
unsigned char SdRead()
{
unsigned char n,i;
for(i=8;i;i--)
{
SD_CLK=0;
SD_CLK=1;
n<<=1;
if(SD_DO) n|=1;

}
return n;
}

上升沿 接收数据。
 
回复

举报

 

slim443 2014-7-18 16:16:08

10#

协议介绍的很详细
 
回复

举报

 

gh037 2014-7-21 01:15:03

11#

{:1:}
 
回复

举报

 

家瑞 2014-9-25 11:28:15

12#

顶楼主,写的不错
 
回复

举报

 

我是流氓羽 2015-6-17 10:57:06

13#

讲的太好了,默默的收藏
 
回复

举报

 

shunbaiwang 2016-2-14 21:39:06

14#

看起来挺有用的,感谢楼主
 
回复

举报

 

qq543538634 2016-8-31 14:23:27

15#

这么深入浅出 通俗易懂的教程居然没人顶??  
谢谢楼主啦!
 
回复

举报

 

我是你的唯一 2016-11-11 21:02:45

16#

不错,确实很有用的 一定好好学习
 
回复

举报

 

我是你的唯一 2016-11-11 21:13:32

17#

666666666666666666666
 
 
回复

举报

 

wenzi122255 2017-2-21 10:25:37

18#

非常好,值得学习啊!
 
回复

举报

 

769295513 2017-12-17 11:29:31

19#

不好意思想请问一下,关于相位的那个动画,数据采样是指把SDI的值存入寄存器中,存入的过程中整个8位数据已经改变;数据输出是指8位数据向左移1位,将最左端的数据放在SDO中,是这样么?
如果是的话,那在(0,0)的时候,应该是先采样,改变8位数据;再移位,把最左端的一位数取出来。
那最后一个相位的动画是不是反了呢?

我是你的唯一 2016-11-11 21:13:32

17#

666666666666666666666
 
 
回复

举报

 

wenzi122255 2017-2-21 10:25:37

18#

非常好,值得学习啊!
 
回复

举报

 

769295513 2017-12-17 11:29:31

19#

不好意思想请问一下,关于相位的那个动画,数据采样是指把SDI的值存入寄存器中,存入的过程中整个8位数据已经改变;数据输出是指8位数据向左移1位,将最左端的数据放在SDO中,是这样么?
如果是的话,那在(0,0)的时候,应该是先采样,改变8位数据;再移位,把最左端的一位数取出来。
那最后一个相位的动画是不是反了呢?
  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值