打印

单片机 SPI 总线的原理、电路设计、编程方法

[复制链接]
1199|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
qbwww|  楼主 | 2022-1-1 22:01 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
1. 基于proteus的51单片机开发实例28-SPI总线的读写

1.1. 实验目的
图1 SPI串行总线的读写

本实例中,我们将要学习SPI总线的读写。

1.2. 设计思路
本例通过51单片机连接控制SPI总线器件X5045,使用SPI总线通信协议对X5045进行数据读写,并把写入的数据和读出的数据分别通过51单片机P0和P2口连接的8个LED显示出来,用以对比写入和读出的数据是否相同。

1.3. 基础知识
我们之前学过的RS-232串口属于异步串行接口,而SPI总线属于同步串行接口。SPI串行通信在单片机系统中有着广泛应用。
单片机与RS-232串口通信时,需要RXD(数据接收)和TXD(数据发送)两根线。
单片机与I2C串口通信时,需要SCL(总线时钟)和SDA(数据收发)两根线。
单片机与SPI串口通信时,需要SCK(总线时钟)、MISO(主机接收、从机发送)和MOSI(主机发送、从机接收)三根线。
下面我们结合X5045来了解SPI的基础知识。
X5045主要功能:单片机上电复位控制,看门狗定时器,降压管理,写保护功能的EEPROM数据存储器。
下图是x5045的引脚图。

图2 X5045引脚图

下图是X5045内部结构图。

图3 X5045内部结构

下图是X5045引脚功能说明。

图4 X5045引脚功能

图5 X5045引脚功能(续)

本实例我们主要通过X5045来了解SPI串行总线,所以对X5045的其它功能留待后续实例讲解。
X5045与单片机之间的串口通信,必须在严格的指令控制下进行。X5045的控制是通过其内部的一个8位指令寄存器进行。它可以通过SI引脚访问,数据在SCK的上升沿由同步时钟脉冲控制输入。在整个X5045工作期间,片选端口CS必须保持低电平,写保护引脚WP必须保持高电平。
X5045一共有6条操作指令。如下图所示。所有指令、地址、数据都是以高位在前的方式传送。输入的数据在CS变为低电平之后的SCK第一个上升沿被采用。
图6 X5045指令

向存储器写数据协议:
首先CS置低电平以选中芯片,然后写入WREN(写允许)指令,接着将CS拉到高电平,然后再次将CS拉到低电平,随后写入WRITE指令并跟随欲写入的8位地址。WRITE指令的第3位用于确定存储器的上半区和下半区。如果没有在WREN和WRITE两个指令之间将CS变为高电平,WRITE指令将被忽略,最后需要将CS变为高电平。
从存储器读数据协议:
首先将CS拉低以选中芯片,然后写入READ(读出)指令,跟着写入欲读出数据的8位地址。READ指令的第3位用以确定存储器的上半区和下半区。在读操作指令码发送完毕后,所选中地址单元的数据将通过SO引脚送出,最后还需要将CS拉到高电平。

1.4. 电路设计
本实例电路图如图1所示。单片机P3口连接X5045。同时单片机的P0口和P2口分别连接8个LED,用以指示写入和读出的数据是否一致。

1.5. 程序设计
本实例程序代码如下。由于51单片机没有内置SPI模块,所以本例中我们仍然使用模拟SPI时序的方法实现SPI总线的读、写操作。
  • #include<reg51.h>
  • #include<intrins.h>
  • sbit SCK=P3^3;       //SCK引脚定义
  • sbit SI=P3^4;        //SI引脚定义
  • sbit SO=P3^5;        //SO引脚定义
  • sbit CS=P3^6;        //CS引脚定义
  • #define WREN 0x06    //写使能锁存器允许
  • #define WRDI 0x04    //写使能锁存器禁止
  • #define WRSR 0x01    //写状态寄存器
  • #define READ 0x03    //读数据
  • #define WRITE 0x02   //写数据
  • //延时1ms
  • void delay1ms();
  • //延时 Counter*1ms
  • void delaynms(unsigned int Counter);
  • //从当前地址读数据
  • unsigned char ReadCurrent(void);
  • //向当前地址写数据
  • void WriteCurrent(unsigned char WriteData);
  • //写状态寄存器
  • void WriteSR(unsigned char RegistData);
  • //写数据到指定地址
  • void WriteSet(unsigned char Writedata,unsigned char WriteAddr);
  • //从指定地址读数据
  • unsigned char ReadSet(unsigned char ReadAddr);
  • //看门狗喂狗
  • void WatchDog(void);
  • //
  • void main(void)
  • {
  •         unsigned char ucWriteDataTmp=1,ucReadDataTmp;
  •         P0=0xff;
  •         P0=0x00;
  •   WriteSR(0x12);        //写状态寄存器:看门狗溢出时间600ms,没有写保护
  •   delaynms(10);         //x5045写入周期约为10ms
  •   while(1)
  •   {
  •                 WatchDog();           //喂狗
  •                         P0=ucWriteDataTmp;//在P0口指示要写入的数据
  •          WriteSet(ucWriteDataTmp,0x10);  //向指定地址写入数据
  •          delaynms(10);         //x5045写入周期约为10ms
  •    ucReadDataTmp=ReadSet(0x10);      //从指定地址读出数据
  •                 P2=ucReadDataTmp;//在p2口指示读出的数据
  •     WatchDog();           //喂狗
  •                 delaynms(400);         //延时一会,有时间看到LED显示的数值
  •                 ucWriteDataTmp++;//写入的数据+1
  •   }
  • }
  • //延时1ms
  • void delay1ms()
  • {
  •    unsigned char i,j;
  •          for(i=0;i<10;i++)
  •           for(j=0;j<33;j++)
  •            ;
  • }
  • //延时若干ms
  • void delaynms(unsigned int Counter)
  • {
  •    unsigned int i;
  •         for(i=0;i<Counter;i++)
  •            delay1ms();
  • }
  • //读当前地址数据
  • unsigned char ReadCurrent(void)
  • {
  •           unsigned char i;
  •         unsigned char ReadTmp=0x00;
  •         SCK=1;                     //SCK拉高
  •    for(i = 0; i < 8; i++)//一个字节8位,循环8次
  •         {
  •            SCK=1;
  •            SCK=0;                 //SCK下降沿时输出数据
  •            ReadTmp<<=1;  //接收的数据是最高位
  •                 ReadTmp|=(unsigned char)SO;  //读出SO上的数据
  •         }
  •         return(ReadTmp);   //返回读出的数据
  • }
  • //写数据到当前地址
  • void WriteCurrent(unsigned char WriteData)
  • {
  •    unsigned char i;
  •         SCK=0;                 //SCK拉低
  •   for(i = 0; i < 8; i++)  // 一个字节8位,循环写入
  •         {
  •          SI=(bit)(WriteData&0x80);   //取所写数据最高位,送到SI
  •                                     //传送顺序是高位在前
  •          SCK=0;
  •          SCK=1;               //SCK上升沿时,写入数据
  •     WriteData<<=1;   //数据左移,始终保持发送最高位
  •   }
  • }
  • //
  • void WriteSR(unsigned char RegistData)
  • {
  •          CS=0;                 //CS拉低,选中芯片
  •          WriteCurrent(WREN);   //写使能锁存器允许
  •          CS=1;                 //CS拉高
  •          CS=0;                 //CS拉低,否则后面写指令会被忽略
  •          WriteCurrent(WRSR);   //写状态寄存器
  •          WriteCurrent(RegistData);     //写入新设置的状态
  •          CS=1;                 //CS拉高
  • }
  • //
  • void WriteSet(unsigned char Writedata,unsigned char WriteAddr)
  • {
  •           SCK=0;                 //SCK拉低
  •    CS=0;                  //CS拉低,选中芯片
  •         WriteCurrent(WREN);    //写使能锁存器允许
  •         CS=1;                  //CS拉高
  •    CS=0;                  //再次拉低CS,否则写数据指令会被忽略
  •         WriteCurrent(WRITE);   //写指令
  •         WriteCurrent(WriteAddr);    //写入指定地址
  •         WriteCurrent(Writedata);     //写入数据
  •         CS=1;                  //CS拉高
  •    SCK=0;                 //SCK拉低
  • }
  • //从指定地址读数据
  • unsigned char ReadSet(unsigned char ReadAddr)
  • {
  • unsigned char ReadData;
  • SCK=0;                 //SCK置低
  • CS=0;                  //CS拉低,选中芯片
  • WriteCurrent(READ);   //开始读操作
  • WriteCurrent(ReadAddr);   //指定读数据的地址
  • ReadData=ReadCurrent();    //读出数据
  • CS=1;                 //CS拉高
  • SCK=0;                //SCK拉低
  • return ReadData;           //返回读出的数据
  • }
  • //看门狗狗喂狗
  • void WatchDog(void)
  • {
  • CS=1;    //CS拉高
  • CS=0;    //CS拉低,CS一个下降沿操作喂狗
  • CS=1;    //CS拉高
  • }


1.6. 实例仿真
编写程序代码,编译生成HEX文件,将HEX文件装载到proteus电路的单片机中,开始仿真,连接在P0口和P2口的两组发光二极管,看看他们显示的是否一致。

使用特权

评论回复

相关帖子

沙发
qbwww|  楼主 | 2022-1-1 22:01 | 只看该作者
单片机与RS-232串口通信时,需要RXD(数据接收)和TXD(数据发送)两根线。
单片机与I2C串口通信时,需要SCL(总线时钟)和SDA(数据收发)两根线。
单片机与SPI串口通信时,需要SCK(总线时钟)、MISO(主机接收、从机发送)和MOSI(主机发送、从机接收)三根线。

使用特权

评论回复
板凳
甘木| | 2022-1-1 22:04 | 只看该作者
51单片机连接控制SPI总线器件X5045,使用SPI总线通信协议对X5045进行数据读写,并把写入的数据和读出的数据分别通过51单片机P0和P2口连接的8个LED显示出来,用以对比写入和读出的数据是否相同。

使用特权

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

本版积分规则

733

主题

4517

帖子

14

粉丝