SPI总线及其demo

SPI总线是微处理器和外设之间进行数据交互的常用串行总线接口。本文主要介绍了SPI的接口类型、数据传输(时钟极性、时钟相位)、读、写操作的具体代码,最后是一个完整的xpt2046实现ADC的代码实现及注意事项。

1 、接口简介

串行外设接口(SPI)是微控制器(MCU)和外围IC(如传感器、ADC、DAC、移位寄存器、SRAM等)之间使用较广泛的接口之一。SPI是一种同步、全双工、主从式接口。来自主机或者从机的数据在时钟上升沿或下降沿同步。主机和从机可以同时传输数据。SPI接口可以是三线式或者4线式。本文主要介绍4线式SPI接口。

图1 SPI总线:单一主机对单一从机模式

4线SPI总线有四个信号:

时钟(SPI CLK,SCLK)。

片选(CS)。

主机输出、从机输入(MOSI)。

主机输入、从机输出(MISO)。

产生时钟信号的器件称为主机(MCU)。主机和从机之间传输的数据与主机产生的时钟同步。SPI接口只能有一个主机,但可以有多个从机。

图2 SPI总线:单一主机对多从机模式(2个从机)

来自主机的片选信号用于选择从机。这通常是一个低电平有效的信号,拉高时,从机与主机断开连接。当使用多个从机时,主机需要为每个从机提供单独的片选信号。MOSI和MISO是数据线。MOSI将数据从主机发送到从机,MISO将数据从从机发送到主机,实现主机和从机的数据交互。

2、 数据传输

要开始SPI通信,主机通过使能CS信号选择从机,发送时钟信号。片选(CS)一般是低电平有效信号。因此,主机必须在该信号上发送逻辑“0”以选择从机。SPI是全双工通信接口,主机和从机可以分别通过MOSI和MISO线路发送和接收数据(数据交互)。在SPI通信期间,数据的发送(串行移出到MOSI/MISO)和接收(MISO/MOSI)可以同时进行。串行时钟沿同步数据的移位和采样。SPI接口允许用户灵活选择上升沿或者下降沿采样(读操作,输入)或者移位(写操作,输出)。读、写操作字节都是从最高位(MSB)开始的。

图3 SPI通信读、写的bit顺序

3 、时钟极性(clock polarity,CPOL)与时钟相位(clock phase,CPHA)

在SPI通信接口中,主机可以选择时钟极性和时钟相位。空闲状态就是片选(CS)无效时的状态。在空闲状态期间,CPOL位设置时钟信号(SCLK)的极性。

表1 CPOL极性规定

CPHA位选择时钟相位,规定主机采样数据时的时钟边沿,包括第一个跳变沿和第二个跳变沿。

表2 CPHA极性规定

根据CPOL位和CPHA位的不同组合,有四种SPI模式可用。

表3 CPOL位和CPHA位的不同组合条件下的SPI模式

图4 CPOL和CPHA时序关系

主机在与采用SPI通信接口的从机(ADC(xpt2046))数据交互过程中,要严格按照从机的时序要求进行读写操作,且从机的SPI通信时序变体很多,具体有从机采用只读模式、从机采用只写模式、先写后读模式、数据位数不同等配置非常灵活。并不是只有图4中的模式类别,具体读/写操作要参考从机的数据手册。

4 、读写操作(代码实现)

4.1、 写(主机在上升沿往从机写1byte数据):

void send_byte(dat)  //主机发送数据到从机  写操作
//数据传输顺序:MSB→LSB
{
    unsigned char i;
    cs_n  = 0;
    for(i=0;i<8;i++)
    {
        sclk    = 0;
        if(dat & 0x80)
        {
            mosi = 1;   
        }
        else
        {
            mosi = 0;   
        }
        sclk = 1;//上升沿锁存数据
        dat = (dat << 1);   
    }
    sclk = 0;//拉低dclk,方便后续操作
}

4.2 、读(主机在时钟的下降沿从从机读取一个字节的数据)

unsigned char receive_byte()
{
    unsigned char dat     = 0x00;
    unsigned char shifter = 0x80;
    unsigned char i;
    cs_n = 0;
    sclk = 0;
    for(i=0;i<8;i++)
    {
        sclk = 1;
        sclk = 0;//下降沿dout数据移出 miso从机发送数据到主机
        if(miso)
        {
            dat = (dat | shifter);      
        }
        else
        {
            dat = dat;      
        }
        shifter = (shifter >> 1);
        
    }
    sclk = 0;//拉低dclk,方便后续操作。
    cs_n = 1;//结束主机、从机数据交互过程。
    return dat;
}

5 、例子

5.1、 ADC(XPT2046 Vbat)

图5 ADC(XPT2046 Vbat)时序(12bit)

控制字节的具体含义见xpt2046数据手册。

代码实现:

1       /*
2       程序功能:A/D转换     采集电位器的电压值。
3       state:success!!
4  
5       */
6       #include <reg52.h>
7       //led灯
8       sbit led_10 = P1^0;
9       sbit led_11 = P1^1;
10      sbit led_12 = P1^2;
11      sbit led_13 = P1^3;
12      sbit led_14 = P1^4;
13      sbit led_15 = P1^5;
14      sbit led_16 = P1^6;
15      sbit led_17 = P1^7;
16      //A/D
17      sbit dclk      = P2^1;
18      sbit xpt_in    = P2^0;
19      sbit xpt_out   = P2^5;
20      sbit xpt_cs_n  = P3^7; //低电平有效     _n表示低电平有效
21      //数码管显示
22      //位选
23      sbit wei_enable  = P2^7;  //与锁存器配合使用
24      //个位→0x7f;十位→0xbf;百位→0xdf;千位→0xef;万位→0xf7;十万位→0xfb;百万位→0xfd;千万位→0xfe;
25      sbit duan_enable = P2^6;  //与锁存器配合使用
26      //0x3f→0;0x06→1;0x5b→2;0x4f→3;0x66→4;0x6d→5;0x7d→6;0x07→7;0x7f→8;0x6f→9;
27      //0x77→A;0x7c→B;0x39→C;0x5e→D;0x79→E;0x71→F;0x76→H;0x38→L;0x40→-;0x00→熄灭
28      unsigned char byte_ctrl = 0xa4;  //1010_0100 0xa4  配置成低功耗模式,不然ADC结果不准,至于原因,不清楚。
29      unsigned int receive_dat = 0x0000;
30      //自定义函数声明:
31      //0.delay
32      void delay_us(unsigned int z);
33      //1.send_byte_ctrl
34      void send_byte_ctrl(dat);
35      //2.receive_data
36      unsigned int receive_data_AD();
37      //3.led_display_dec_3
38      void led_display_dec_3(unsigned int dat);//小数点后两位
39 
40      //main函数
41      void main()
42      {
43 
44          //display
45          while(1)
46          {
47              send_byte_ctrl(byte_ctrl);
48              receive_dat = receive_data_AD();
49              led_display_dec_3(receive_dat); 
50              led_10 = 0; 
51          }
52 
53      }
54      //
55      //0.delay
56      void delay_us(unsigned int z)
57      {
58          unsigned int x;
59          for(x=0;x<z;x++);
60      }
61      //
62      //1.send_byte_ctrl
63      void send_byte_ctrl(dat)   //xpt_cs = 0时,数据在dclk上升沿时锁存进来
64      //数据传输顺序:MSB→LSB
65      {
66          unsigned char i;
67          xpt_cs_n  = 0;
68          for(i=0;i<8;i++)
69          {
70              dclk    = 0;
71              if(dat & 0x80)
72              {
73                  xpt_in = 1; 
74              }
75              else
76              {
77                  xpt_in = 0; 
78              }
79              dclk = 1;//上升沿锁存数据
80              dat = (dat << 1);   
81          }
82          dclk = 0;//拉低dclk,方便后续操作。
83          for(i=0;i<6;i++)
84          {
85              dclk = 0;   
86          }
87          
88      }
89      //
90      //2.receive_data
91      unsigned int receive_data_AD()
92      {
93          unsigned int dat     = 0x0000;
94          unsigned int shifter = 0x0800;
95          unsigned char i;
96          xpt_cs_n = 0;
97          dclk = 0;
98          for(i=0;i<12;i++)
99          {
100             dclk = 1;
101             dclk = 0;//下降沿dout数据移出
102             if(xpt_out)
103             {
104                 dat = (dat | shifter);      
105             }
106             else
107             {
108                 dat = dat;      
109             }
110             shifter = (shifter >> 1);
111             
112         }
113         dclk = 0;//拉低dclk,方便后续操作。
114         for(i=0;i<3;i++)
115         {
116             dclk = 0;
117             dclk = 1;   
118         }
119         xpt_cs_n = 1;//结束A/D转换操作。
120         dat = (dat * 8.4); //显示小数点后两位  实测:VCC=4.3,VCC/4=1.075V,对应得数值是1280(十进制),1.075/1280=8.4e-4。
121         return dat;
122     }
123     //
124     //3.led_display_dec_3
125     void led_display_dec_3(unsigned int dat)//dec表示10进制
126     {
127         unsigned char wei_point_2;
128         unsigned char wei_point_1;
129         unsigned char wei_point;
130         unsigned char wei_ge;
131         wei_enable  = 0;
132         duan_enable = 0;
133         wei_ge = dat / 10000;               //12345   1  个位
134         wei_point_1 = dat % 10000 /1000;    //12345   2  小数点后第一位
135         wei_point_2 = dat % 1000 / 100;     //12345   3  小数点后第二位
136         //wei_point_2
137         //位选
138         P0 = 0x7f;//个位
139         wei_enable = 1;
140         wei_enable = 0;
141         //段选
142         switch(wei_point_2)
143         {
144             case 0 : { P0 = 0x3f; }break;
145             case 1 : { P0 = 0x06; }break;
146             case 2 : { P0 = 0x5b; }break;
147             case 3 : { P0 = 0x4f; }break;
148             case 4 : { P0 = 0x66; }break;
149             case 5 : { P0 = 0x6d; }break;
150             case 6 : { P0 = 0x7d; }break;
151             case 7 : { P0 = 0x07; }break;
152             case 8 : { P0 = 0x7f; }break;
153             case 9 : { P0 = 0x6f; }break;
154             default: { P0 = 0x3f; }break;       
155         }
156         duan_enable = 1;
157         duan_enable = 0;
158         delay_us(300);
159         //wei_point_1
160         //位选
161         P0 = 0xbf; //十位
162         wei_enable = 1;
163         wei_enable = 0;
164         //段选
165         switch(wei_point_1)
166         {
167             case 0 : { P0 = 0x3f; }break; 
168             case 1 : { P0 = 0x06; }break; 
169             case 2 : { P0 = 0x5b; }break; 
170             case 3 : { P0 = 0x4f; }break; 
171             case 4 : { P0 = 0x66; }break; 
172             case 5 : { P0 = 0x6d; }break; 
173             case 6 : { P0 = 0x7d; }break; 
174             case 7 : { P0 = 0x07; }break; 
175             case 8 : { P0 = 0x7f; }break; 
176             case 9 : { P0 = 0x6f; }break; 
177             default: { P0 = 0x3f; }break;
178         }
179         duan_enable = 1;
180         duan_enable = 0;
181         delay_us(300);
182         //wei_ge
183         //位选
184         P0 = 0xdf; //百位
185         wei_enable = 1;
186         wei_enable = 0;     
187         //段选
188         switch(wei_ge)
189         {
190             case 0 : { P0 = 0xbf; }break;//0xbf
191             case 1 : { P0 = 0x86; }break;//0x86
192             case 2 : { P0 = 0xdb; }break;//0xdb
193             case 3 : { P0 = 0xcf; }break;//0xcf
194             case 4 : { P0 = 0xe6; }break;//0xe6
195             case 5 : { P0 = 0xed; }break;//0xed
196             case 6 : { P0 = 0xfd; }break;//0xfd
197             case 7 : { P0 = 0x87; }break;//0x87
198             case 8 : { P0 = 0xff; }break;//0xff
199             case 9 : { P0 = 0xef; }break;//0xef
200             default: { P0 = 0x3f; }break;       
201         }
202         duan_enable = 1;
203         duan_enable = 0;
204         delay_us(300);
205     }

6 、注意事项

①缺乏数据接收确认机制。

②单主机。

③xpt2046进行ADC时,要配置成低功耗模式(PD1=0,PD0=0),不然ADC结果不准,至于为什么,不清楚。

参考文献:

1.https://aticleworld.com/spi-communication-protocol/

2.https://www.analog.com/cn/analog-dialogue/articles/introduction-to-spi-interface.html

本人水平有限,如有错误,望大佬多多指教。非常感谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值