SPI协议介绍

目录
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协议介绍
文章图片


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协议介绍
文章图片


举例OLED 对于OLED来说只需要单向传输,因此只需要CS、SCK和DO三条线,并不需要读取OLED的,这里通过软件来模拟SPI和硬件SPI两种方式来实现,首先需要实现SPI的写操作例如gpio_spi.c,然后写出oled的控制oled.c
软件SPI
  • 首先先将OLED的引脚初始化为输出或者输入状态
SPI协议介绍
文章图片

  • 对于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_DCoutput * GPG5 SPIMISOinput * GPG6 SPIMOSIoutput * GPG7 SPICLKoutput */ 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协议介绍
文章图片

  • 写出自己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协议介绍
文章图片

  • 对于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协议介绍
文章图片

  • 现在要显示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协议介绍
文章图片

SPI协议介绍
文章图片

  • 对于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的命令表格中查找
SPI协议介绍
文章图片

  • 需要发出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_DCoutput * 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 = https://www.it610.com/article/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(); }


    推荐阅读