树莓派SPI使用

 

 

 

截图软件

sudo apt-get install scrot

 scrot -s /home/pi/Desktop/example.png

 

 

 

 

 

 

关于SPI

SPI是串行外设接口(Serial Peripheral Interface)的简写,其实就是一种总线通信协议,该总线以主从方式工作,通常是一个主设备和一个或多个从设备,一般需要4根线来进行数据通信,即SDI(数据输入)、SDO(数据输出)、SCLK(时钟)、CS(片选),相应的,有一套时序逻辑规定了通信过程。一般使用SPI的设备都配备了现成的SPI模块,只需要对相应的寄存器写入指令就可以进行通信了,如果没有SPI接口,也可以软件实现,就是根据时序图手动操作GPIO来实现通信,这就比较费劲了。

树莓派已经带了SPI接口,不过默认是关闭的。执行sudo raspi-config 进入配置页,打开SPI接口。
这里写图片描述

也可以使用树莓派的图形化设置工具

设置完后可以执行lsmod 查看是否有spi模块存在

//设置之前
pi@raspberrypi:~ $ lsmod
Module                  Size  Used by
rfcomm                 45056  4
bnep                   20480  2

//设置之后
pi@raspberrypi:~ $ lsmod
Module                  Size  Used by
spidev                 20480  0
spi_bcm2835            24576  0

还可以查看在/dev下是否存在spidev0.0和spidev0.1两个文件,这两个文件代表了树莓派SPI模块可以使能两个SPI从设备,也对应了GPIO上的CE0和CE1脚。

 

接线

把模块的引脚接到树莓派的GPIO上, 需要注意这里要分清GPIO引脚号是BCM编号还是实际编号。GND脚和树莓派的GND连接;VCC接到3.3V脚;D0接到树莓派的SCLK脚(第23号脚),即BCM.11脚;D1接到MOSI脚(第19号脚),即BCM.10;RES接到BCM.17脚,即第11号脚;DC接到BCM.22脚,即第15号脚;CS接到CE0脚(第24号脚)。总之,除了RES和DC是可以任意指定GPIO口,其他引脚是必须和树莓派上的指定脚连接的。

这里写图片描述

安装相关库

执行下面的更新内核并安装基本环境,这个不是必须的,一般都已经装好了:

sudo apt-get update
sudo apt-get install build-essential python-dev python-pip
sudo pip install RPi.GPIO

 

参考:

https://blog.csdn.net/wangzhenyang2/article/details/80299625

https://blog.csdn.net/qq_32481879/article/details/78546851 //need review

https://blog.csdn.net/borntox/article/details/51871480

https://www.cnblogs.com/sankye/p/3955630.html //need review

 


https://blog.csdn.net/TAlice/article/details/83868713?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control

一、SPI协议介绍:

1、【SPI基础知识简介】

SPI全称是串行外设接口(Serial Peripheral Interface),是由Motorola提出的一种全双工同步串行通信接口,通信波特率可以高达5Mbps,但具体速度大小取决于SPI硬件。SPI接口具有全双工操作,操作简单,数据传输速率较高的优点,但也存在没有指定的流控制,没有应答机制确认是否接收到数据的缺点。SPI总线只需四条线就可以完成MCU与各种外围器件的通讯:

SCLK:Serial Clock,(串行)时钟
MISO:Master In Slave Out,主设备输入,从设备输出
MOSI:Master Out  Slave In,主设备输出,从设备输入
SS:        Slave Select,选中从设备,片选,片选的其他别称[① CS(Chip Select)芯片选择、②CE(Chip Enable)芯片使能]


2、【SPI相关的缩写或说法】

先简单说一下,关于SPI中一些常见的说法:
SPI的极性Polarity和相位Phase,最常见的写法是CPOL和CPHA,不过也有一些其他写法,简单总结如下:
(1) CKPOL (Clock Polarity) = CPOL = POL = Polarity = (时钟)极性
(2) CKPHA (Clock Phase)   = CPHA = PHA = Phase = (时钟)相位
(3) SCK=SCLK=SPI的时钟
(4) Edge=边沿,即时钟电平变化的时刻,即上升沿(rising edge)或者下降沿(falling edge)
对于一个时钟周期内,有两个edge,分别称为:
Leading edge=前一个边沿=第一个边沿,对于开始电压是1,那么就是1变成0的时候,对于开始电压是0,那么就是0变成1的时候;
Trailing edge=后一个边沿=第二个边沿,对于开始电压是1,那么就是0变成1的时候(即在第一次1变成0之后,才可能有后面的0变成1),对于开始电压是0,那么就是1变成0的时候;

 

3、【SPI总线通信】

①、SPI是[单主设备(single-master )]通信协议,这意味着总线中的只有一支中心设备能发起通信。当SPI主设备想读/写[从设备]时,它首先拉低[从设备]对应的SS线(SS是低电平有效),接着开始发送工作脉冲到时钟线上,在相应的脉冲时间上,[主设备]把信号发到MOSI实现“写”,同时可对MISO采样而实现“读”

 

②、SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响,SPI主模块和与之通信的外设音时钟相位和极性应该一致。
如果CPOL=0,串行同步时钟的空闲状态为低电平; 
如果CPOL=1,串行同步时钟的空闲状态为高电平; 
时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。 
如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样; 
如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样;

4、【数据传输】

在一个SPI时钟周期内,会完成如下操作:

1) 主设备通过MOSI线发送1位数据,从设备通过该线读取这1位数据;

2) 从设备通过MISO线发送1位数据,主设备通过该线读取这1位数据。

这是通过移位寄存器来实现的。如图所示,主设备和从设备各有一个移位寄存器,且二者连接成环。随着时钟脉冲,数据按照从高位到低位的方式依次移出主设备寄存器和从机寄存器,并且依次移入从设备寄存器和主设备寄存器。当寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换。

5、【内部工作机制】

SSPSR 是 SPI 设备内部的移位寄存器(Shift Register). 它的主要作用是根据 SPI 时钟信号状态, 往 SSPBUF 里移入或者移出数据, 每次移动的数据大小由Bus-Width 以及 Channel-Width 所决定。

 

二、树莓派40Pin引脚对照图:

三、本次SPI编程要实现的结果:

用排线短接树莓派的19脚和21脚,实现树莓派spi通信的自发自收

四、SPI编程实现:

1、编程思路:

①、使能内核SPI驱动模块抽象出spi设备
②、利用open系统调用打开spi设备 "/dev/spidev0.0"
③、利用ioctl设置spi设备各项参数
④、进行读写操作

2、实际操作与代码部分:

①、使能内核SPI驱动
pi@raspberrypi:~ $ sudo raspi-config     弹出系统配置对话框,使能spi,重新启动树莓派,内核加载成功

重启之后可以确认系统已自动加载SPI驱动

pi@raspberrypi:~ $ ls /dev/spidev0.*
/dev/spidev0.0      /dev/spidev0.1

这里抽象出两个spi接口设备,但树莓派只引出来一组spi(引脚19、21、23、24),对应的设备文件为/dev/spidev0.0

②树莓派自发自收代码

/*********************************************************************************

* Copyright: (C) 2018 wangtao

* All rights reserved.

*

* Filename: spi_own.c

* Description: This file

*

* Version: 1.0.0(11/07/2018)

* Author: WangTao <TAlicer@163.com>

* ChangeLog: 1, Release initial version on "11/07/2018 17:15:56 PM"

*

********************************************************************************/


#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include "SPISet.h"


int initSPI()

{

int spiFd;

spiFd=SPISetup(0,500000); //初始化SPI通道0,并设置为最大速度500000hz

if(spiFd==-1)

{

printf("init spi failed!\n");

}

}


int main()

{

char tx_Data[10]={1,2,3,4,5,6,7,8,9,10}; //定义读写的数据

char rx_Data[10]={0,0,0,0,0,0,0,0,0,0};

int i=0;


initSPI(); //spi的初始化

while(1)

{

SPIDataRW(0,tx_Data,rx_Data,7); //向总线中写入7个数据

printf("read spi_rx_data is:\n"); //读出总线的数据,引脚19与21短接打印【1,2,3,4,5,6,7,0,0,0】

// 引脚19与21不短接打印【0,0,0,0,0,0,0,0,0,0】

for(i=0;i<10;i++)

{

printf("%d\n",rx_Data[i]);

}

printf("\n");

sleep(1);

}

return 0;

}
 
/*********************************************************************************

* Copyright: (C) 2018 wangtao

* All rights reserved.

*

* Filename: SPISet.c

* Description: This file

*

* Version: 1.0.0(11/07/2018)

* Author: WangTao <TAlicer@163.com>

* ChangeLog: 1, Release initial version on "11/07/2018 17:15:56 PM"

*

********************************************************************************/


#include <stdint.h>

#include <fcntl.h>

#include <errno.h>

#include <string.h>

#include <stdio.h>

#include <sys/ioctl.h>

#include <asm/ioctl.h>

#include <linux/spi/spidev.h>

#include "SPISet.h"



static const char *spiDev0 = "/dev/spidev0.0" ;

static const char *spiDev1 = "/dev/spidev0.1" ;

static uint8_t spiBPW = 8 ;

static uint16_t spiDelay = 0 ;


static uint32_t spiSpeeds [2] ;

static int spiFds [2] ;


/*

* SPIDataRW:

* Write and Read a block of data over the SPI bus.

* Note the data ia being read into the transmit buffer, so will

* overwrite it!

* This is also a full-duplex operation.

*********************************************************************************

*********************************************************************************/

int SPIDataRW (int channel, unsigned char *tx_data, unsigned char *rx_data, int len)

{

int i = 0;

struct spi_ioc_transfer spi ;


channel &= 1 ;


memset (&spi, 0, sizeof (spi)) ;


spi.tx_buf = (unsigned long)tx_data ;

spi.rx_buf = (unsigned long)rx_data ;

spi.len = len ;

spi.delay_usecs = spiDelay ;

spi.speed_hz = spiSpeeds [channel] ;

spi.bits_per_word = spiBPW ;


return ioctl (spiFds [channel], SPI_IOC_MESSAGE(1), &spi) ; //SPI_IOC_MESSAGE(1)的1表示spi_ioc_transfer的数量

}



/*

* SPISetupMode:

* Open the SPI device, and set it up, with the mode, etc.

*********************************************************************************

*********************************************************************************/


int SPISetupMode (int channel, int speed, int mode)

{

int fd ;


if ((fd = open (channel == 0 ? spiDev0 : spiDev1, O_RDWR)) < 0)

{

printf("Unable to open SPI device: %s\n", strerror (errno)) ;

return -1;

}


spiSpeeds [channel] = speed ;

spiFds [channel] = fd ;


/*

* 设置spi的读写模式:

* Mode 0: CPOL=0, CPHA=0

* Mode 1: CPOL=0, CPHA=1

* Mode 2: CPOL=1, CPHA=0

* Mode 3: CPOL=1, CPHA=1

* 这里我们默认设置为模式0

*********************************************************************************

*/

if (ioctl (fd, SPI_IOC_WR_MODE, &mode) < 0)

{

printf("Can't set spi mode: %s\n", strerror (errno)) ;

return -1;

}


if (ioctl (fd, SPI_IOC_RD_MODE, &mode) < 0)

{

printf("Can't get spi mode: %s\n", strerror (errno)) ;

return -1;

}


/*

* spi的读写bit/word设置可写

* 这里设置为8个位为一个字节

*********************************************************************************

*/

if (ioctl (fd, SPI_IOC_WR_BITS_PER_WORD, &spiBPW) < 0)

{

printf("Can't set bits per word: %s\n", strerror (errno)) ;

return -1;

}


if (ioctl (fd, SPI_IOC_RD_BITS_PER_WORD, &spiBPW) < 0)

{

printf("Can't get bits per word: %s\n", strerror (errno)) ;

return -1;

}


/*

* 设置spi读写速率

*********************************************************************************

*/

if (ioctl (fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0)

{

printf("Can't set max speed hz: %s\n", strerror (errno));

return -1;

}


if (ioctl (fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0)

{

printf("Can't get max speed hz: %s\n", strerror (errno));

return -1;

}


return fd ;

}



/*

* SPISetup:

* Open the SPI device, and set it up, etc. in the default MODE 0

*********************************************************************************

*********************************************************************************/


int SPISetup (int channel, int speed)

{

return SPISetupMode (channel, speed, 0) ;

}

 

/*********************************************************************************

* Copyright: (C) 2018 wangtao

* All rights reserved.

*

* Filename: SPISet.h

* Description: This file

*

* Version: 1.0.0(11/07/2018)

* Author: WangTao <TAlicer@163.com>

* ChangeLog: 1, Release initial version on "11/07/2018 17:15:56 PM"

*

********************************************************************************/


#ifdef __cplusplus

extern "C" {

#endif


int SPIDataRW (int channel, unsigned char *tx_data,unsigned char *rx_data, int len) ;

int SPISetupMode (int channel, int speed, int mode) ;

int SPISetup (int channel, int speed) ;


#ifdef __cplusplus

}

#endif

 

五、程序运行结果:

1、MISO与MOSI短接                                      2、MISO与MOSI未短接 

                                

关于Linux下ioctl的理解推荐这篇博客 https://www.cnblogs.com/tdyizhen1314/p/4896689.html    
关于spi的ioctl的理解推荐这两篇博客  https://blog.csdn.net/borntox/article/details/51871480       
                                                            http://www.cnblogs.com/sankye/p/3955630.html             


https://blog.csdn.net/u010058695/article/details/102544523?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.control

1.打开树莓派spi驱动

sudo raspi-config
在这里插入图片描述

2.查看spi设备驱动

ls /dev
其中 spidev0.0 和spidev0.1即是树莓派下的两个spishebei
在这里插入图片描述

3.查看树莓派下spi设备引脚定义

gpio readall
左边方框的管脚分别为MOSI MISO SCLK,右边CE0,CE1为两个片选管脚,分别对应上图中的spidev0.0,spidev0.1两个设备。对这两个文件读写操作即可控制spi设备。
在这里插入图片描述
树莓派引脚定义
在这里插入图片描述

 


https://blog.csdn.net/u010058695/article/details/102544523?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.control

一、SPI接口

树莓派3B+上的SPI接口如下所示,有两组SPI,分别由CE0和CE1来进行选择。

首先查看树莓派的SPI是否启用,在/dev查看是否有spidev0.0和spidev0.1

如果不存在spi设备号,需要在raspi-config中启用,在命令行输入:sudo raspi-config 

选Interfacing Options,选择SPI,选择enable。

二、wiringPiSPI库

wiringPISPI库提供了树莓派的SPI驱动调用,包含四个接口函数。

2.1 获取SPI的描述符 wiringPiSPIGetFd    

int wiringPiSPIGetFd     (int channel) ;

输入参数为通道,由CE0和CE1决定连接的是哪一个SPI,CE0连接SPI0,CE1连接SPI1。

2.2 SPI读写函数 wiringPiSPIDataRW  

int wiringPiSPIDataRW    (int channel, unsigned char *data, int len) ;

由于SPI是上升沿下数据,下降沿读数据的,因此,一个时钟即可同时完成读写。wiringPiSPIDataRW函数同时执行读写,因此,读取的数据会覆盖写入的数据空间,即读写命令都会操作data指针指向的buff。调用一次即完成一次读写,片选使能CE0和CE1由该函数自动设置,无需再次操作。

2.3 SPI模式设置函数 wiringPiSPISetupMode 

int wiringPiSPISetupMode (int channel, int speed, int mode) ;

该函数设置SPI的模式,包括通道(0,1),SPI速度,和SPI模式(0,1,2,3)。SPI有四种工作模式,各个工作模式的不同在于SCLK不同, 具体工作由CPOL,CPHA决定

  • CPOL: (Clock Polarity),时钟极性

          当CPOL为0时,时钟空闲时电平为低;

          当CPOL为1时,时钟空闲时电平为高;

  • CPHA:(Clock Phase),时钟相位

          当CPHA为0时,时钟周期的上升沿采集数据,时钟周期的下降沿输出数据;

          当CPHA为1时,时钟周期的下降沿采集数据,时钟周期的上升沿输出数据;

一般采用模式0,可以设置为:

wiringPiSPISetupMode (0, 500000, 0);//500kHz

2.4 SPI默认模式0设置 wiringPiSPISetup    

int wiringPiSPISetup     (int channel, int speed) ;

此函数直接默认了SPI模式为0,只需设置通道和速度即可。

三、SX1278 移植SPI

根据SX1278的接口函数驱动代码可知,只需要自己实现LoRa_Write_Buff和LoRa_Read_Buff即可。两者函数的接口形式为:

void LoRa_Write_Buff(uint8_t addr,uint8_t *buffer,uint8_t size);


void LoRa_Read_Buff(uint8_t addr, uint8_t *buffer, uint8_t size);

即调用上述函数对SX1278的寄存器进行指定长度的读写。根据手册,可知在进行SPI读写之前需要先发送操作地址码,同时读操作的地址最高位为1,写操作的地址最高位为地址0。

因此,可以在读写之前需要进行寄存器的地址读写配置,static uint8_t Cfg_SpiModeRW(uint8_t rw, uint8_t val)。

/*!

* \brief Configure the SPI read and write mode

* \para rw,uint8_t, read mode = SPI_LORA_READ and write mode = SPI_LORA_WRITE

SPI_LORA_READ = 0, SPI_LORA_WRITE = 1, a Marao define

* \retval a_val:uint8_t,cfg_addr

*/

static uint8_t Cfg_SpiModeRW(uint8_t rw, uint8_t val)

{

uint8_t a_val;

if(rw == SPI_LORA_READ)

{

a_val = 0x7F & val;

}

else if(rw == SPI_LORA_WRITE)

{

a_val = 0x80 | val;

}

return a_val;

}

 

 

SX1278的SPI时序图如下:

wiringPiSPI的wiringPiSPIDataRW 函数同时执行读写,且自动控制CE接口,因此,LoRa_Write_Buff和LoRa_Read_Buff中需要把数据buff和地址操作CMD提前准备好,然后调用一次wiringPiSPIDataRW函数即可实现指定长度和寄存器的SPI读写。具体移植如下:

/*!

* \brief LoRa write buff with SPI, using wiringPiSPI wiringPiSPIDataRW interface

* \para addr:uint8_t, LoRa Reg addr

buffer:uint8_t pointer, write buffer

size:uint8_t,size of buffer byte to write

* \retval none

*/

void LoRa_Write_Buff(uint8_t addr,uint8_t *buffer,uint8_t size)

{

uint8_t cmd;

uint8_t buf[256];

buf[0] = Cfg_SpiModeRW(SPI_LORA_WRITE,addr);

memcpy(buf+1,buffer,size);

wiringPiSPIDataRW(SPI_CHANNEL_0,buf,size+1);//build addr+buff cmd

}

/*!

* \brief LoRa read buff with SPI, using wiringPiSPI wiringPiSPIDataRW interface

* \para addr:uint8_t, LoRa Reg addr

buffer:uint8_t pointer, read buffer

size:uint8_t,size of buffer byte to read

* \retval none

*/

void LoRa_Read_Buff(uint8_t addr, uint8_t *buffer, uint8_t size)

{

uint8_t cmd;

uint8_t buf[257];

buf[0] = Cfg_SpiModeRW(SPI_LORA_READ,addr);

wiringPiSPIDataRW(SPI_CHANNEL_0,buf,size+1);

memcpy(buffer,buf+1,size);//remove fisrt val, which means addr

}

移植的时候,别忘了头文件

#include "wiringPi.h"

#include "wiringPiSPI.h"

至此,SPI移植完毕。

其他:

树莓派移植SX1278 LoRa通信--使用wiringPi 移植GPIO中断

树莓派移植SX1278 LoRa通信--使用wiringPiSPI移植SPI通信接口

树莓派UART串口编程--使用wiringPi库-C开发【1-基础应用】

树莓派UART串口编程--使用wiringPi库-C开发【2-修改驱动】

树莓派Raspberry 操作GPIO--LED

 

 

 

 

 

 

 

  • 14
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值