目录
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有四种工作模式,CPOL表示SPICLK初始状态是高电平还是低电平,CPHA表示在第几个时钟沿采样数据,具体选择哪种模式主要取决于外接的SPI设备
CPOL | CPHA | |
模式0 | 0 | 0 |
模式1 | 0 | 1 |
模式2 | 1 | 0 |
模式3 | 1 | 11 |
文章图片
举例OLED 对于OLED来说只需要单向传输,因此只需要CS、SCK和DO三条线,并不需要读取OLED的,这里通过软件来模拟SPI和硬件SPI两种方式来实现,首先需要实现SPI的写操作例如gpio_spi.c,然后写出oled的控制oled.c
软件SPI
- 首先先将OLED的引脚初始化为输出或者输入状态
文章图片
- 对于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首先需要初始化,根据芯片手册来查看初始化的过程
文章图片
- 写出自己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的初始引脚为低电平或者高电平都可以,同时在上升沿读取数据
文章图片
- 对于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页
文章图片
- 现在要显示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
文章图片
文章图片
- 对于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_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();
}
推荐阅读
- 单片机|自学单片机难吗(单片机编程教学怎么选?)
- 单片机|学好单片机好找工作吗(单片机学到什么程度可以找工作?)
- 单片机|单片机学多久能工作,单片机学好了能应聘什么工作()
- 单片机|单片机值得学吗(会单片机能找什么工作?)
- harmony|手把手教你移植openharmony3.0到stm32(liteos_m)
- 学习记录|C语言学习(1)VScode配置C语言环境(超详细)
- 鸿蒙硬件开发|鸿蒙硬件开发(HarmonyOS下的LiteOS-M)
- C语言|嵌入式机考笔试之C语言数组问题
- 嵌入式|stm32指纹、蓝牙识别门禁系统项目详解