SPI协议介绍

目录

SPI简介

数据传输配图

SPI模式

举例OLED

软件SPI

硬件SPI


SPI简介

SPI协议是一种同步串行接口技术(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,SPI是一主多从的,SPI通信都是由主机发起的,SPI只需要4根线

  • CS:片选引脚,多个SPI设备都会有一个片选引脚,当引脚拉低后就能选中该芯片
  • SCK:串行时钟,为SPI通信提供时钟
  • MISO/SDO:主设备数据输入,从设备数据输出
  • MOSI/SDI:主设备数据输出,从设备数据输入

 

数据传输配图

例如我们发出0x56的数据即0b0101 0110,首先我们需要选中引脚CS,然后数据从最高位0开始传输让DO引脚变为0,假设SCK是在上升沿的时候数据发送出去,执行8次,最后再取消片选引脚

SPI协议介绍_第1张图片

 

SPI模式

SPI有四种工作模式,CPOL表示SPICLK初始状态是高电平还是低电平,CPHA表示在第几个时钟沿采样数据,具体选择哪种模式主要取决于外接的SPI设备

  CPOL CPHA
模式0 0 0
模式1 0 1
模式2 1 0
模式3 1 11

CPOL为高电平一般SCK初始状态为高电平,CPHA为高电平一般在第二个上升沿采样数据,经常使用模式0和模式3,因为模式0和模式3都是在上升沿采样数据,不需要我们在乎SPICLK的初始状态是什么,只要在上升沿采样数据就行了

SPI协议介绍_第2张图片

 

举例OLED

对于OLED来说只需要单向传输,因此只需要CS、SCK和DO三条线,并不需要读取OLED的,这里通过软件来模拟SPI和硬件SPI两种方式来实现,首先需要实现SPI的写操作例如gpio_spi.c,然后写出oled的控制oled.c

软件SPI

  • 首先先将OLED的引脚初始化为输出或者输入状态

SPI协议介绍_第3张图片

  • 对于OLED来说,其中OELD_DC用来控制发出的是命令还是数据
static void SPI_GPIO_Init(void)
{
    /* GPF1 OLED_CSn output */
    GPFCON &= ~(3<<(1*2));
    GPFCON |= (1<<(1*2));
    GPFDAT |= (1<<1);

    /* GPG2 FLASH_CSn output
    * GPG4 OLED_DC   output  
    * GPG5 SPIMISO   input
    * GPG6 SPIMOSI   output
    * 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)) | (1<<(6*2)) | (1<<(7*2)));
    GPGDAT |= (1<<2);
}
  • OLED首先需要初始化,根据芯片手册来查看初始化的过程

SPI协议介绍_第4张图片

  • 写出自己OLEDInit
void OLEDInit(void)
{
    /* 向OLED发命令以初始化 */
    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); 

    OLEDSetPageAddrMode();

    OLEDClear();
    
    OLEDWriteCmd(0xAF); /*display ON*/    
}
  • 写出OLEDWriteCmd函数,根据SPI协议我们首先需要选中引脚即将CS引脚拉低,对于OLED发送命令需要将DC引脚拉低,同理我们可以写出发出数据的函数
static void OLEDWriteCmd(unsigned char cmd)
{
    OLED_Set_DC(0); /* command */
    OLED_Set_CS(0); /* select OLED */

    SPISendByte(cmd);

    OLED_Set_CS(1); /* de-select OLED */
    OLED_Set_DC(1); /*  */
}

static void OLEDWriteDat(unsigned char dat)
{
    OLED_Set_DC(1); /* data */
    OLED_Set_CS(0); /* select OLED */

    SPISendByte(dat);

    OLED_Set_CS(1); /* de-select OLED */
    OLED_Set_DC(1); /*  */
}
  • 由于OLED的SCLK的初始引脚为低电平或者高电平都可以,同时在上升沿读取数据

SPI协议介绍_第5张图片

  • 对于SPISendByte函数我们在gpio_spi.c中实现,如下,参考数据传输配图我们就可以写出了OLED的命令和数据函数
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);
}


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;
    }
}
  • 虽然OLED就不需要DI,有些SPI设备例如Flash就需要我们通样也可以写出输入函数
static char SPI_Get_DI(void)
{
    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_Get_DI())
            val |= 1;
        SPI_Set_CLK(1);
    }
    return val;    
}
  • 现在来实现OLED的打印函数,对于个人的OLED来说有三种寻址方式,这里采用页地址模式,OLED的分辨率128*64,对于这显存128*64bit,显存中对于字节每个对应着像素点列向8位,注意不是横向,页地址模式8行表示1页

SPI协议介绍_第6张图片

  • 现在要显示8*16像素的字符,对于OLED来说,每写一位地址自动加一,64行代表着8也,8*16即一个字符占2页,写出OLEDPrint函数如下,OLEDPutChar实现打印一个字符,对于第一个字符我们需要写两次,首先在第一页发出8字节数据,页加一再发出8字节数据,其中oled_asc2_8x16为8*16字库,在网上找,OLEDSetPos设置显存的地址,后面解释
static void OLEDSetPos(int page, int col)
{
    OLEDWriteCmd(0xB0 + page); /* page address */

    OLEDWriteCmd(col & 0xf);   /* Lower Column Start Address */
    OLEDWriteCmd(0x10 + (col >> 4));   /* Lower Higher Start Address */
}

/* page: 0-7
 * col : 0-127
 * 字符: 8x16象素
 */
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]);

}

/* page: 0-7
 * col : 0-127
 * 字符: 8x16象素
 */
void OLEDPrint(int page, int col, char *str)
{
    int i = 0;
    while (str[i])
    {
        OLEDPutChar(page, col, str[i]);
        col += 8;
        if (col > 127)
        {
            col = 0;
            page += 2;
        }
        i++;
    }
}
  • 对于 OLEDSetPos函数,在页模式下页地址从0xB0开始,行地址我们需要先发出低4位,再发出高四位,注意第五位D4为1

SPI协议介绍_第7张图片

SPI协议介绍_第8张图片

  • 对于OLED清屏函数,只需要再每个像素写入0就行了
static void OLEDClear(void)
{
    int page, i;
    for (page = 0; page < 8; page ++)
    {
        OLEDSetPos(page, 0);
        for (i = 0; i < 128; i++)
            OLEDWriteDat(0);
    }
}
  • 设置页地址模式,在OLED的命令表格中查找

  • 需要发出2次,其中A1,A0为10即0x20就是页地址模式,我们将其写在OLED的初始化OLEDInit中,这样我们初始化完OLED后就可以根据OLEDPrint函数来打印字符串了
static void OLEDSetPageAddrMode(void)
{
    OLEDWriteCmd(0x20);
    OLEDWriteCmd(0x02);
}

硬件SPI

硬件SPI没什么好说的直接怼寄存器


#include "s3c24xx.h"

/* SPI controller */

static void SPI_GPIO_Init(void)
{
    /* GPF1 OLED_CSn output */
    GPFCON &= ~(3<<(1*2));
    GPFCON |= (1<<(1*2));
    GPFDAT |= (1<<1);

    /* GPG2 FLASH_CSn output
    * GPG4 OLED_DC   output
    * GPG5 SPIMISO   
    * GPG6 SPIMOSI   
    * GPG7 SPICLK    
    */
    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);
}


void SPISendByte(unsigned char val)
{
    while (!(SPSTA1 & 1));
    SPTDAT1 = val;    
}

unsigned char SPIRecvByte(void)
{
    SPTDAT1 = 0xff;    
    while (!(SPSTA1 & 1));
    return SPRDAT1;    
}


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);
    
}

void SPIInit(void)
{
    /* 初始化引脚 */
    SPI_GPIO_Init();

    SPIControllerInit();
}

 

你可能感兴趣的:(嵌入式)