基于ARM---s3c2440的SPI协议

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)
在这里插入图片描述
在这里插入图片描述
按16
8的大小在屏幕上显示,一行为一个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)是单字符寄存器,所以在地址声明时需要定义为单字节。

猜你喜欢

转载自blog.csdn.net/Parismoor/article/details/89920717