codeblocks 51单片机学习(三)总线协议

  • 引言:

这次总结包括很多的模块,这些模块无一例外都是需要编写底层时序的,都是需要与各种时序线打交道的。所以要理解起来就会比较难,刚开始我只是对着模板程序照抄,看一眼模板程序,写一下自己的,虽然它说的什么东西还是能够理解的,但是一旦脱离了,就什么都写不出来。
越是这样的情况越是需要一些时间自己去把内容花时间消化,如果仅仅是利用这些底层时序,在应用层做一下文章,那是很简单的,你甚至一晚上就能将这些模块全部掌握,但是我觉得在学习阶段更是应该深入理解,特别是自学这些东西的时候,更应该在这些东西上深入思考一下。

  • 一、I2C协议与EEPROM
    之前学的一个模块叫做EEPROM,是一个外部存储器,能够存放一些数据,特点是即使断电也不会丢失,可以看到其中有一个词ROM,这其实就是一个只读存储器,只能在断电时将数据写入,并且芯片中带有写保护,需要接触这个保护才能进行数据的写入。主机与外部存储器EEPROM之间的交互是通过I2C协议的,我在实验中做的就是就是将一个字符串存储到EEPROM里再从EEPROM中读取这些数据。
    在这里插入图片描述
    I2C协议,通过两条线:SCL和SDA线进行数据的传输,SCL(Serial Clock)是串行时钟线,SDA(Serial Data Address)串行数据/地址线,这里我先按照我的理解去分析这两条线的作用。首先I2C和SPI一样都是通过传输线的上升沿和下降沿来进行数据的传输的,上升沿就是数据线由低电平提升为高电平的过程,下降沿同理,这个过程可以读或写数据。就拿我今天学习的24C02来说,写入数据时,将SCL由低电平提升到高电平产生一个上升沿,就能将一个位的数据读入,如果SCL这段上升沿是在SDA高电平时完成的,那么将会读入一个1,如果是在SDA低电平完成的,那么将会读入一个0,而读取时则是通过SCL的下降沿完成的。具体的操作就是用一个循环,将八位数据一位一位赋值给SDA,然后每次产生一个上升沿,这样就将这个八位数据读入了,为什么是八位数据呢,是这个外部存储器24C02有两个模式,一个是8位传输,一个是12位传输,为了方便这里使用的是8位传输模式。
    以上只是数据的传输机制,怎样完成读写操作呢,通过查阅数据手册,我才真正理解了。首先数据手册有一句话,大概意思是主器件发送起始命令和从器件地址后,从器件会产生一个应答,这时候可以将数据发送过来,发送完后从器件再次产生一个应答,主器件接收到了之后就停止,这时候主器件进行内部数据的擦写,也就完成了数据写入的操作。而数据读取,我记得也是差不多的,一位一位地读取用一个变量去保存它,最后得到地这个变量的值就是读出的结果。
    起始命令和终止命令在数据手册上都有图片示意,很好理解,就是在SCL处于高电平时,SDA完成下降沿就是开始,SDA完成上升沿就是结束。发送从器件的地址需要去数据手册上找,写入器件的地址16进制是0xa1,读取器件是0xa2。虽说数据手册说在发送地址后,要让从器件产生应答并让主器件接收到应答后,才能发送数据,但操作上我们就只需要在发送地址后,再发送数据就可以了,大概是因为已经有某个程序写在芯片中所以我们不需要做这些操作吧。
/*这是项目中实现24C02功能的函数文件*/
#include "common.h"

#define AT24C02_WRITE_ADDR		0xa0  		// 24C02写的从地址
#define AT24C02_READ_ADDR		0xa1	 	// 24C02读的从地址

static void I2C_start(void);
static void I2C_stop(void);
static void delay5us(void);
static void I2C_send_byte(u8 dat);
static u8 I2C_read_byte(void);
/*起始信号,维持SCL高电平,SDA拉高在拉低*/
static void I2C_start(void)
{
    SCL=1;
    delay5us();
    SDA=1;
    delay5us();
    SDA=0;
    delay5us();
    SCL=0;//后续写入数据需要SCL拉高再拉低,所以这里需要将SCL变为低电平
    delay5us();
}
/*中止信号,维持SCL高电平,SDA拉低再拉高*/
static void I2C_stop(void)
{
     SDA=0;
    delay5us();
    SCL=1;
    delay5us();
    SDA=1;
    delay5us();
}

/*将主机的数据发送写入EEPROM*/
static void I2C_send_byte(u8 dat)
{
    for(u8 i=0;i<8;i++)
    {
        SDA=dat >> 7;//写时序,先改变SDA,然后拉高SCL拉低SCL电平,完成写入操作
        dat <<=1;
        delay5us();
        SCL=1;
        delay5us();
        SCL=0;
        delay5us();
    }
        SDA=1;
        delay5us();
        SCL=1;
        delay5us();
        SCL=0;
        delay5us();
}
/*读取数据,先拉高SCL,在中间将SDA改变读取一位的数据,再拉低SCL,反复读取8位*/
static u8 I2C_read_byte(void)
{
    u8 dat=0;
    SDA=1;
    delay5us();
    SCL=0;
    delay5us();
    for(u8 i=0;i<8;i++)
    {
        SCL=1;//先拉高时钟电平
        delay5us();
        dat <<=1;
        dat |=SDA;//或非将最低位替换为SDA的大小(0,1)
        delay5us();
        SCL=0;//拉低时钟电平
        delay5us();
    }
    return dat;
}
/*测试数据是否被写入,试着将它发送回来,此函数与Uart串口通信连接*/
void I2CWrite(u8 addr,u8 dat)
{
    I2C_start();
    I2C_send_byte(AT24C02_WRITE_ADDR);//发送写器件地址
    I2C_send_byte(addr);//发送要写入的内部存储器的地址
    I2C_send_byte(dat);//发送要写入的数据
    I2C_stop();
}

u8 I2CRead(u8 addr)
{
    u8 dat;
    I2C_start();
    I2C_send_byte(AT24C02_WRITE_ADDR);//发送写器件地址
    I2C_send_byte(addr);//发送要读取的内部存储器的地址

    I2C_start();
    I2C_send_byte(AT24C02_READ_ADDR);//发送读器件地址
    dat=I2C_read_byte();
    I2C_stop();

    return dat;
}

static void delay5us(void)   //误差 0us
{
    unsigned char a;
    for(a=1;a>0;a--);
}

  • SPI协议与AD转换
    通过模数转换,我们就能够实现与外界环境的交互,这是计算机从外部获取信息的重要能力,比如通过温度感应器测量温度,烟雾检测器进行烟雾报警。首先模数转换(AD转换),是将环境中的模拟信号,也就是连续的无法测量的信号进行离散化,其实就是将连续的线转化成成离散的点,这样就可以被计算机识别,从而可以进行计算。这些转换的概念还是很好理解的,而这些转换过程都是通过一个叫XPT2046的芯片完成的,这个芯片就是专门处理模数转换和数模转换这个过程的,所以这个过程也是需要与我们的51单片机进行交互的。
    交互就又要涉及到通信协议了,这次使用的则是SPI协议,SPI协议呢不同于I2C的两条线,总共有需要控制4条线,串行时钟线DCLK、输入线DIN、输出线DOUT、总线CS,由于熟悉过了I2C协议,所以这次我只需要看数据手册就将程序主体完成了,但还有一些细节还是需要参考一下的,像是在哪里需要延时,这些操作我是能理解,但什么时候该用该延时多久我就不知道了,就像是上次对使用I2C协议的24C02进行编程,在进行线操作时都需要一个5微妙的延时,而这次却不需要。
    在这里插入图片描述
    看一下SPI的时序图,通过了解各线作用,我们开始编程。首先我们将CS线由高电平拉为低电平,这个CS总线我理解为一个保护线,CS线控制着整个输入输出过程,只有它为低电平时才能进行读入读取操作。而DCLK的作用和I2C作用很像,通过它的上升或者下降沿来读入读取数据。DIN,DOUT,输入输出线,好理解,就像是I2C协议里SDA线拆成了两个,在输入时只需对DIN按位输入,在读取时对DOUT按位读取。
    但是这个读入的数据是什么呢,是一个8位数据,控制了这个XPT2046的工作机制,输入第一个8位数据,第一位S作为起始位,接下来三位A2,A1,A0选择通道,第四位MODE设置ADC的分辨率,MODE=0将是12位模式,MODE=1为8位模式,接下来一位是SER/DFR,单端模式设置为1,差分模式设置为0,最后两位PD1,PD0,11代表总处于供电状态,00代表低功耗状态。所以按照本次程序的需要,我们应设置为10011111,也就是16进制数0x9F。
/*这是项目中实现AD转换芯片XPT2046功能的函数文件*/
#include "common.h"
#include "XPT2046.h"

static void SPI_WRITE(u8 dat);
static void XPT2046_INIT_IN(void);
static u8 SPI_READ8(void);
static u8 SPI_READ12(void);

static void delay5us(void)   //误差 0us
{
    unsigned char a;
    for(a=1;a>0;a--);
}
/*初始化,输入第一个8位数据,第一位S
作为起始位,接下来三位A2,A1,A0选择地址,
第四位MODE设置ADC的分辨率,MODE=0
将是12位模式,MODE=1为8位模式,接下来
一位是SER/DFR,单端模式设置为1,差分模式
设置为0,最后两位PD1,PD0,11代表总处于供
电状态,00代表低功耗状态
按照本次程序的需要,我们应设置为10011111,
也就是0x9F,为了方便修改,专门写一个函数
可以输入任意8位数据。
*/
static void SPI_WRITE(u8 dat)
{
    u8 i=0;
    DCLK=0;
    for(i=0;i<8;i++)
    {
        DIN=dat >> 7;
        dat <<=1;
        DCLK=1;
        DCLK=0;
    }
}

static u8 SPI_READ8(void)
{
    u8 i=0,dat=0;
    DCLK=0;
    for(i=0;i<8;i++)
    {
        dat<<=1;
        DCLK=1;
        DCLK=0;
        dat |= DOUT;
    }
    return dat;
}

static u8 SPI_READ12(void)
{
    u8 i=0,dat=0;
    for(i=0;i<12;i++)
    {
        dat<<=1;
        DCLK=1;
        DCLK=0;
        dat |= DOUT;
    }
    return dat;
}

u8 XPT2046_WRITE(void)
{
    u8 i=0,val=0;
    DCLK=0;
    CS=0;
    SPI_WRITE(0x9F);
    for(i=0;i<6;i++);//延时

    DCLK=1;
    _nop_();
    _nop_();

    DCLK=0;
    _nop_();
    _nop_();

    val=SPI_READ8();
    CS=1;

    return val;
}

猜你喜欢

转载自blog.csdn.net/weixin_43960398/article/details/89284895