单片机
返回首页

基于ARM板s3c2440---SPI协议

2022-03-03 来源:eefocus

SPI简介

SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议,比如AT91RM9200。

简单的说,就是SPI是一种告诉的协议,相当于IIC,只是IIC是两根线(时钟线SCL,数据地址线SDA)而SPI是四根线(SCK时钟信号,DO输出线,DI输入线,CSn片选信号),同样可以挂在多个SPI高速设备。

在这里插入图片描述

数据传输

在这里插入图片描述

数据传输由CPOL,CPHA两个控制器决定,CPOL决定电平启示状态是低电平还是高电平,CPHA决定在第一个沿采样还是第二个沿采样

在这里插入图片描述

通过SPI协议实现对OLED屏幕的显示

在这里插入图片描述

OLED显示原理

在这里插入图片描述

屏幕分辨率是12864,如同LCD一样,我们需要一个显存来存放数据,这个显存在内存中开辟,一个字节八位,分别表示该列元素的亮(1)或者灭(0)

在这里插入图片描述
在这里插入图片描述

按168的大小在屏幕上显示,一行为一个page,在光标移动到底8列时,光标归为0,page+1,实现第二个page的写操作


通过GPIO模拟SPI的数据收发

在这里插入图片描述

原理图中使用到GPG3567和GPF1,所以需要设置S3C2440相应的管脚的输入输出模式,除开GPG5为输入模式,其余管脚都是输出模式。

在这里插入图片描述
在这里插入图片描述

static void SPI_GPIO_INIT(void)

{

    /*GPF1 OLED_CSn OUTPUT*/    /*主机是输出模式*/

    GPFCON &= ~(3<<2);

    GPFCON |= (1<<2);

    GPFDAT |= (1<<1);    /*因为接了OLED 和 FLASH  如果同时为0  会产生冲突*/

    /*GPG2 FLASH_CSn  OUTPUT*/

    /*GPG4 OLED_DC OUTPUT*/

    /*GPG5 SPIMISO INPUT*/    /*MI:MASTER INPUT*/

    /*GPG6 SPIMOSI OUTPUT*/

    /*GPG7 SPICLK OUTPUT*/

    GPGCON &= ~((3<<4)|(3<<8)|(3<<12)|(3<<14)|(3<<10));

    GPGCON |= ((1<<4)|(1<<8)|(1<<12)|(1<<14));

    GPGDAT |=(1<<2);    /*因为接了OLED 和 FLASH  如果同时为0  会产生冲突*/

                        /*CSn低电平有效*/

                        /*都不片选,使其在使用时裁片选*/

}


由于芯片上接入了FLASH和oled的片选信号,所以在GPIO的初始化时,需要将两个片选信号都拉高,避免默认状态冲突。


OLED初始化

void oledinit(void)

{

    /*向OLED发命令初始化*/

    /*SPEC UG手册提供的初始化命令*/

    OLEDWriteCmd(0xAE); /*display off*/ 

    OLEDWriteCmd(0x00); /*set lower column address*/ 

    OLEDWriteCmd(0x10); /*set higher column address*/ 

    OLEDWriteCmd(0x40); /*set display start line*/ 

    OLEDWriteCmd(0xB0); /*set page address*/ 

    OLEDWriteCmd(0x81); /*contract control*/ 

    OLEDWriteCmd(0x66); /*128*/ 

    OLEDWriteCmd(0xA1); /*set segment remap*/ 

    OLEDWriteCmd(0xA6); /*normal / reverse*/ 

    OLEDWriteCmd(0xA8); /*multiplex ratio*/ 

    OLEDWriteCmd(0x3F); /*duty = 1/64*/ 

    OLEDWriteCmd(0xC8); /*Com scan direction*/ 

    OLEDWriteCmd(0xD3); /*set display offset*/ 

    OLEDWriteCmd(0x00); 

    OLEDWriteCmd(0xD5); /*set osc division*/ 

    OLEDWriteCmd(0x80); 

    OLEDWriteCmd(0xD9); /*set pre-charge period*/ 

    OLEDWriteCmd(0x1f); 

    OLEDWriteCmd(0xDA); /*set COM pins*/ 

    OLEDWriteCmd(0x12); 

    OLEDWriteCmd(0xdb); /*set vcomh*/ 

    OLEDWriteCmd(0x30); 

    OLEDWriteCmd(0x8d); /*set charge pump enable*/ 

    OLEDWriteCmd(0x14);


    OLEDPAGEADDRMODE();

    OLEDCLEAR();

    OLEDWriteCmd(0xAF); /*display ON*/

}


在OLED初始中实现对地址采用Page address mode

在这里插入图片描述

static void OLEDPAGEADDRMODE(void)

{

    OLEDWriteCmd(0x20);

    OLEDWriteCmd(0x02);

}


清屏幕


static void OLEDCLEAR(void)

{

    int page,col,i;

    for(page = 0; page <8;page++)

    {

        OLEDSETPOS(page,0);

        for(i=0;i<128;i++)

        {

            OLEDWriteDAT(0);

        }

    }

}


地址/数据模式

OLED_DC:高电平表示数据,低电平表示数据。


/*命令*/

static void OLEDWriteCmd(unsigned int cmd)

{

    OLED_SET_DC(0); /*COMMAND*/

    OLED_SET_CS(0); /*选中OLED*/


    SPISENDBYTE(cmd);


    OLED_SET_CS(1); /*取消选中OLED*/

    OLED_SET_DC(1); /*COMMAND*/

}

/*地址*/

static void OLEDWriteDAT(unsigned int dat)

{

    OLED_SET_DC(1); /*data*/

    OLED_SET_CS(0); /*选中OLED*/


    SPISENDBYTE(dat);

    

    OLED_SET_CS(1); /*取消选中OLED*/

    OLED_SET_DC(1); /*data*/

}


根据传入0/1设置相应的GPIO口输出高低电平


static void OLED_SET_DC(char val)

{

    if(val)

    {

        GPGDAT |= (1<<4);

    }

    else

    {

        GPGDAT &= ~(1<<4);

    }

    

}


片选信号


static void OLED_SET_CS(char val)

{

    if(val)

    {

        GPFDAT |= (1<<1);

    }

    else

    {

        GPFDAT &= ~(1<<1);

    }

    

}


数据传输


void SPISENDBYTE(unsigned char val)

{

    int i;


    for(i=0;i<8;i++)

    {

        SPI_SET_CLK(0);

        SPI_SET_DO(val & 0x80);

        SPI_SET_CLK(1);

        val <<= 1;

    }

}


时钟设置


static void SPI_SET_CLK(char val)

{

    if(val)

    {

        GPGDAT |= (1<<7);

    }

    else

    {

        GPGDAT &= ~(1<<7);

    }

    

}


数据输出--------按位传递,熊高位到低位


static void SPI_SET_DO(char val)

{

    if(val)

    {

        GPGDAT |= (1<<6);

    }

    else

    {

        GPGDAT &= ~(1<<6);

    }

    

}


程序到这里已经实现了CPU的数据传出,接下来需要将数据收到,以及打印到OLED的相应位置


void oledprint(int page,int col,char *str)

{

    int i = 0;

    while(str[i])

    {

        OLEDPUTCHAR(page,col,str[i]);

        col += 8;

        if(col > 127)

        {

            page += 2;

            col = 0;

        }

        i++;

    }

}


一个字节占8列,16行(16*8)

在这里插入图片描述

void OLEDPUTCHAR(int page,int col,char c)

{

    int i=0;

    /*字摸*/

    const unsigned char *dots = oled_asc2_8x16[c - ' '];

    /*发给OLED*/

    OLEDSETPOS(page,col);

    /*发出8字节数据*/

    for(i=0;i<8;i++)

    {

        OLEDWriteDAT(dots[i]);

    }

    OLEDSETPOS(page+1,col);

    /*发出8字节数据*/

    for(i=0;i<8;i++)

    {

        OLEDWriteDAT(dots[i+8]);    /*原因是字符数组二维数组是16个为一组,前8个是上一排的,后8个是下一排*/

    }

}

在这里插入图片描述

static void OLEDSETPOS(int page, int col)

{

    OLEDWriteCmd(0xb0 + page);      /*page address*/


    OLEDWriteCmd(col & 0xf);        /*Set Lower Column Start Address for Page Addressing Mode*/

    OLEDWriteCmd(0x10 +(col>>4));   /*Set Higher Column Start Address for Page Addressing Mode*/

}


通过SPI协议实现对FLASH的操作

读厂家ID、设备ID

在这里插入图片描述

首先发送0x90,然后发出3个字节的0(是一个24位的数据,所以需要分割为3个字节来发送),然后GPG5就有数据读入。


void SPIflashreadID(int *pMID,int *pDID)

{

    SPI_FLASH_set_cs(0);    /*选中SPIflash*/


    SPISENDBYTE(0X90);

    SPIFlashSendAddr(0);      /*当发完这些指令和地址是,GPGDAT5上就有数据传入*/

    *pMID = SPIRecvByte();

    *pDID = SPIRecvByte();

    SPI_FLASH_set_cs(1);    /*取消选中SPIflash*/

}


static void SPIFlashSendAddr(unsigned int addr)

{

    SPISENDBYTE(addr>>16);      /*地址由24位组成,先发高高8位*/

    SPISENDBYTE((addr>>8)&0xff);      /*地址由24位组成,再发高8位*/

    SPISENDBYTE(addr&0xff);      /*地址由24位组成,最后发低8位*/


}


static void SPI_FLASH_set_cs(char val)

{

    if(val)

    {

        GPGDAT |= (1<<2);       /*不选中*/

    }

    else

    {

        GPGDAT &= ~(1<<2);      /*选中*/

    }

    

}


/*主机传入*/

static char SPI_SET_DI(void)

{

    /*需要判定GPGDAT第5位是什么数据*/

    if(GPGDAT &(1<<5))

    {

        return 1;

    }

    else

    {

        return 0;

    }

}

unsigned char SPIRecvByte(void)

{

    int i;

    unsigned char val = 0;  /*用于存放本字节的东西*/

    for(i=0;i<8;i++)

    {

        val <<= 1;

        SPI_SET_CLK(0);

        if(SPI_SET_DI())

        {   

            val |= 1;       /*数据为1就或1,否则就是0,前面左移已经产生了,所以不用赋值*/

        }

        SPI_SET_CLK(1);

    }

    return val;

}


对FLASH进行读写

在这里插入图片描述

对FLASH的写操作,需先进进行写使能,取出状态寄存器的保护,然后擦除,然后写入

写使能:

在这里插入图片描述

写保护:

在这里插入图片描述

static void SPIFlashWriteEnable(int enable)

{

    if (enable)

    {

        SPI_FLASH_set_cs(0);

        SPISENDBYTE(0x06);

        SPI_FLASH_set_cs(1);

    }

    else

    {

        SPI_FLASH_set_cs(0);

        SPISENDBYTE(0x04);

        SPI_FLASH_set_cs(1);

    }

}


读状态寄存器

在这里插入图片描述

发送0x05命令,接下来就可以读数据了


static unsigned char SPIFlashReadStatusReg1(void)

{

    unsigned char val;

    SPI_FLASH_set_cs(0);

    SPISENDBYTE(0x05);

    val = SPIRecvByte();

    SPI_FLASH_set_cs(1);

    return val;

}

static unsigned char SPIFlashReadStatusReg2(void)

{

    unsigned char val;

    SPI_FLASH_set_cs(0);

    SPISENDBYTE(0x35);

    val = SPIRecvByte();

    SPI_FLASH_set_cs(1);

    return val;

}


写状态寄存器


static void SPIFlashWriteStatusReg(unsigned char reg1, unsigned char reg2)

{    

    SPIFlashWriteEnable(1);  

    

    SPI_FLASH_set_cs(0);

    SPISENDBYTE(0x01);

    SPISENDBYTE(reg1);

    SPISENDBYTE(reg2);

    SPI_FLASH_set_cs(1);


    SPIFlashWaitWhenBusy();

}


根据状态寄存器1的第0位可以判定是写数据已经完成。1表示正在进行,0表示已经完成


static void SPIFlashWaitWhenBusy(void)

{

    while (SPIFlashReadStatusReg1() & 1);

}


去除对状态寄存器的保护

在这里插入图片描述
在这里插入图片描述

将SRP0,SRP1置0就能去除对状态寄存器的保护


static void SPIFlashClearProtectForStatusReg(void)

{

    unsigned char reg1, reg2;


    reg1 = SPIFlashReadStatusReg1();

    reg2 = SPIFlashReadStatusReg2();


    reg1 &= ~(1<<7);

    reg2 &= ~(1<<0);


    SPIFlashWriteStatusReg(reg1, reg2);

}


去除对数据域的保护

在这里插入图片描述

将BP2,BP1,BP0三位清0就可以去除保护


static void SPIFlashClearProtectForData(void)

{

    /* cmp=0,bp2,1,0=0b000 */

    unsigned char reg1, reg2;


    reg1 = SPIFlashReadStatusReg1();

    reg2 = SPIFlashReadStatusReg2();


    reg1 &= ~(7<<2);

    reg2 &= ~(1<<6);


    SPIFlashWriteStatusReg(reg1, reg2);

}


擦除

在这里插入图片描述

擦除4k大小的区域

在这里插入图片描述

void SPIFlashEraseSector(unsigned int addr)

{

    SPIFlashWriteEnable(1);  


    SPI_FLASH_set_cs(0);

    SPISENDBYTE(0x20);

    SPIFlashSendAddr(addr);

    SPI_FLASH_set_cs(1);


    SPIFlashWaitWhenBusy();

}


烧写

在这里插入图片描述
在这里插入图片描述

/* program */

void SPIFlashProgram(unsigned int addr, unsigned char *buf, int len)

{

    int i;

    

    SPIFlashWriteEnable(1);  


    SPI_FLASH_set_cs(0);

    SPISENDBYTE(0x02);

    SPIFlashSendAddr(addr);


    for (i = 0; i < len; i++)

        SPISENDBYTE(buf[i]);

    

    SPI_FLASH_set_cs(1);


    SPIFlashWaitWhenBusy();

    

}


读数据

在这里插入图片描述
在这里插入图片描述

void SPIFlashRead(unsigned int addr, unsigned char *buf, int len)

{

    int i;

    

    SPI_FLASH_set_cs(0);

    SPISENDBYTE(0x03);

    SPIFlashSendAddr(addr);

    for (i = 0; i < len; i++)

        buf[i] = SPIRecvByte();


    SPI_FLASH_set_cs(1);    

}


使用SPI控制器取代GPIO模拟

同样是使用相同的管家,只是不再手动操作相应的GPIO管脚,实现0/1的输出,而是操作SPI控制器,让控制器自动进行相应的数据收发。

在这里插入图片描述

SPI控制器初始化

在这里插入图片描述

将GPGCON中的456位设置为SPI模式,其余不变


static void SPI_GPIO_Init(void)

{

    /*GPF1 OLED_CSn OUTPUT*/    /*主机是输出模式*/

    GPFCON &= ~(3<<(1*2));

    GPFCON |= (1<<(1*2));

    GPFDAT |= (1<<1);       /*因为接了OLED 和 FLASH  如果同时为0  会产生冲突*/


    /*GPG2 FLASH_CSn  OUTPUT*/

    /*GPG4 OLED_DC OUTPUT*/

    /*GPG5 SPIMISO INPUT*/    /*MI:MASTER INPUT*/

    /*GPG6 SPIMOSI OUTPUT*/     /*这里所有的输入输出都是站在主机CPU的角度谈的*/

    /*GPG7 SPICLK OUTPUT*/

    GPGCON &= ~((3<<(2*2)) | (3<<(4*2)) | (3<<(5*2)) | (3<<(6*2)) | (3<<(7*2)));

    GPGCON |= ((1<<(2*2)) | (1<<(4*2)) | (3<<(5*2)) | (3<<(6*2)) | (3<<(7*2)));

    GPGDAT |= (1<<2);       /*因为接了OLED 和 FLASH  如果同时为0  会产生冲突*/

                            /*CSn低电平有效*/

                            /*都不片选,使其在使用时裁片选*/

}


SPI控制器初始化,设置相应的时钟,因为是自动传输,所以时钟(也就是波特率)需要预先设置。

在这里插入图片描述
在这里插入图片描述

static void SPIControllerInit(void)

{

    /* OLED  : 100ns, 10MHz

    * FLASH : 104MHz

    * 取10MHz

    * 10 = 50 / 2 / (Prescaler value + 1)

    * Prescaler value = 1.5 = 2

    * Baud rate = 50/2/3=8.3MHz

    */

    SPPRE0 = 2;

    SPPRE1 = 2;


    /* [6:5] : 00, polling mode

    * [4]   : 1 = enable 

    * [3]   : 1 = master

    * [2]   : 0

    * [1]   : 0 = format A

    * [0]   : 0 = normal mode

    */

    SPCON0 = (1<<4) | (1<<3);

    SPCON1 = (1<<4) | (1<<3);

    

}


收发数据只需要将数据写给SPSTA1寄存器就可以

在这里插入图片描述

由于使用的是SPIMISO1,所以使用的是第二控制器


void SPISENDBYTE(unsigned char val)

{

    while (!(SPSTA1 & 1));

    SPTDAT1 = val;    

}

//判定SPSTA1第一位十位准备就绪,然后写数据

unsigned char SPIRecvByte(void)

{

    SPTDAT1 = 0xff;    

    while (!(SPSTA1 & 1));

    return SPRDAT1;    

}


首先将0xff写入SPTDAT1 ,然后判定SPSTA1第一位十位准备就绪,然后读数据

特别注意:这里的数据寄存器(SPRDATn)是单字符寄存器,所以在地址声明时需要定义为单字节。

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • 家用电源无载自动断电装置的设计与制作

  • 用数字电路CD4069制作的万能遥控轻触开关

  • 使用ESP8266从NTP服务器获取时间并在OLED显示器上显示

  • 开关电源的基本组成及工作原理

  • 用NE555制作定时器

  • 带有短路保护系统的5V直流稳压电源电路图

    相关电子头条文章