SPI使用介绍

SPI协议介绍:

硬件连接:

SPI Flash和SPI OLED。
两种方式:
1、用GPIO模拟SPI
2、用S3C2440的SPI控制器
我们先介绍下SPI协议,硬件框架如下:
在这里插入图片描述
SCK:提供时钟
DO:作为数据输出
DI:作为数据输入
CS0/CS1:作为片选
同一时刻只能有一个SPI设备处于工作状态。
假设现在2440传输一个0x56(二进制就是0b0101 0110)数据给SPI Flash,时序如下:
在这里插入图片描述

在SPI协议中,有两个值来确定SPI的模式。
CPOL:表示SPICLK的初始电平,0为低电平,1为高电平
CPHA:表示相位,即第一个还是第二个时钟沿采样数据,0为第一个时钟沿,1为第二个时钟沿。
CPOL CPHA 模式 含义
0 0 0 初始电平为低电平,在第一个时钟沿采样数据
0 1 1 初始电平为低电平,在第二个时钟沿采样数据
1 0 2 初始电平为高电平,在第一个时钟沿采样数据
1 1 3 初始电平为高电平,在第二个时钟沿采样数据
我们常用的是模式0和模式3,因为它们都是在上升沿采样数据,不用去在乎时钟的初始电平是什么,只要在上升沿采集数据就行。
极性选什么?格式选什么?通常去参考外接的模块的芯片手册。比如对于OLED,查看它的芯片手册时序部分:
在这里插入图片描述
SCLK的初始电平我们并不需要关心,只要保证在上升沿采样数据就行。
使用GPIO实现SPI协议操作OLED
现在开始写代码,使用GPIO实现SPI协议操作。
我们现在想要操作OLED,通过三条线(SCK、DO、CS)与OLED相连,这里没有DI是因为2440只会向OLED传数据而不用接收数据。
我们要用GPIO来实现SOC向OLED写数据,这一层用gpio_spi.c来实现,负责发送数据。
对于OLED,有专门的指令和数据格式,要传输的数据内容,在oled.c这一层来实现,负责组织数据。
因此,我们需要实现以上两个文件。
在这里插入图片描述
需要实现的函数:先SPI初始化SPIInt(),再初始化OLEDOLEDInit(),最后再显示OLEDPrint()。
新建一个gpio_spi.c文件,实现SPI初始化SPIInt()

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

具体实现SPI_GPIO_Init()。这里使用GPIO实现SPI协议:
GPF1作为OLED片选引脚,设置为输出;
GPG2作为FLASH片选引脚,设置为输出;
GPG4作为OLED的数据(Data)/命令(Command)选择引脚,设置为输出;
GPG5作为SPI的MISO,设置为输入;
GPG6作为SPI的MOSI,设置为输出;
GPG7作为SPI的时钟CLK,设置为输出;
/* 用GPIO模拟SPI */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.c文件,以实现初始化OLEDOLEDInit(),对于OLED,除了SPI的片选、时钟、数据引脚,还有一个数据/命令切换引脚。
这里的D/C即数据(Data)/命令(Command)选择引脚,它为高电平时,OLED即认为收到的是数据;它为低电平时,OLED即认为收到的是命令。
对于OLED,命令由开启/关闭显示、背光亮度等,具体有什么命令,可以查阅OLED的主控芯片手册
因此,在编写OLEDWriteCmd()时,需要先设置为命令模式:

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

即:先设置为命令模式,再片选OLED,再传输命令,再恢复成原来的模式和取消片选。
片选函数和模式切换函数都比较简单,设置为对应的高低电平即可:

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

还剩下SPISendByte()函数,它属于SPI协议,放在gpio_spi.c里面:

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

发送数据要满足SPI的时序要求,参考前面:
先设置CLK为低,然后数据引脚输出数据的最高位,然后CLK为高,在CLK这个上升沿中,OLED就读取了一位数据。接着左移一位,将原来的第7位移动到了第8位,重复8次,传输完成。
再完成SPI_Set_CLK()和SPI_Set_DO():

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

至此,SPI初始化和OLED初始化就基本完成了,接下来就是OLED显示部分。

使用SPI控制器:
前面我们都是通过GPIO管脚来实现的SPI通信,这节我们使用2440里面的GPIO控制器来实现SPI通信。
前面使用GPIO发送数据时,是手工的控制时钟线、数据线,我们使用SPI控制器的话,只需要
把数据写入寄存器,它就可以帮我自动那些时钟线和数据线,我们继续在上一节的基础上修改,添加一个文件s3c2440_spi.c和s3c2440_spi.h,同时修改Makefile,替换gpio_spi.c为s3c2440_spi.o。
从初始化函数开始,需要管脚初始化和SPI控制器初始化:

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

   SPIControllerInit();
}

管脚初始化即需要把SPI相关的CLK、MOSI、MISO配置为对应的功能引脚:

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

然后是SPI控制器的初始化,控制器的初始化可以参考芯片手册介绍的编程步骤:
在这里插入图片描述
首先是设置波特率,要根据外设所能接受的范围来设置,比如查阅OLED的芯片手册得知其时钟最小值为100ns,即最小为10MHz;Flash时钟支持最大104MHz,为了代码简单,就直接取10MHz,根据等式推出寄存器值:
Baud rate = PCLK / 2 / (Prescaler value + 1)
10 = 50 / 2 / (Prescaler value + 1)
Prescaler value = 1.5 = 2
实际的波特率为:50/2/3=8.3MHz
根据参考流程,接下来设置SPI控制寄存器:

[6:5]设置为查询模式: 00 polling mode
[4]设置时钟使能: 1 = enable
[3]设置为主机模式: 1 = master
[2]设置无数据时时钟为低电平: 0
[1]设置工作模式为模式A: 0 = format A
[0]设置发送数据时无需读取数据: 0 = normal mode

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

发送数据时,先检查状态寄存器,判断发送/接收数据是否准备好了,准备好后就把数据放在寄存器SPTDAT1里,SPI控制器就自己控制时序把数据自动发送出去了。

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

接收数据时,先写0xFF到寄存器SPTDAT1,再检查状态寄存器,判断发送/接收数据是否准备好了,准备好后就读取寄存器SPTDAT1,读取出来的就是接收到的数据。

unsigned char SPIRecvByte(void){
    SPTDAT1 = 0xff;    
    while (!(SPSTA1 & 1));
    return SPRDAT1;    
}
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MCP23017是一种16位I/O扩展器,可以通过SPI(串行外围接口)进行控制和通信。以下是MCP23017的一些SPI使用案例: 1. LED模块控制:通过MCP23017的I/O引脚控制LED的亮灭。通过SPI接口将控制命令发送到MCP23017,设置相应的I/O引脚为输出模式,并将特定引脚设置为高电平或低电平以点亮或关闭LED。 2. 按钮输入处理:连接按钮到MCP23017的输入引脚,并使用SPI接口读取按钮状态。在每次读取按钮状态时,通过SPI发送命令到MCP23017以获取输入引脚的状态,并根据结果进行相应处理,例如触发事件或执行特定动作。 3. 温度传感器监控:连接温度传感器到MCP23017的ADC(模数转换)引脚,并使用SPI接口读取传感器数据。通过发送命令到MCP23017,转换ADC引脚的模拟电压为数字数据,并获取传感器测量的温度值。可以进一步对温度数据进行处理和监控。 4. 数码管驱动:连接数码管到MCP23017的输出引脚,并使用SPI接口发送显示数据。通过发送命令到MCP23017,设置输出引脚为高电平或低电平以控制数码管的显示内容。可以通过多个MCP23017扩展I/O引脚数量以驱动更多数码管。 这些都是MCP23017的一些SPI使用案例,通过SPI接口可以实现对MCP23017的控制和通信,从而实现各种不同的应用。由于MCP23017内部集成了大量的I/O引脚,加上SPI接口的使用,使得MCP23017成为一个非常灵活和可扩展的外围设备控制器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

起风就扬帆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值