spi协议->裸机程序->master驱动程序分析

FPGA学习之SPI通讯--MASTER以及SLAVE端数据收发 1、MASTER端SPI代码:配置为模式0,一次传输8bit数据,可实现数据的收发;sclk,cs信号均由MASTER端产生。 2、SLAVE端SPI代码:配置为模式0,一次传输8bit数据。 阅读详情
版权声明:欢迎转载,转载请注明出处 http://blog.youkuaiyun.com/lizuobin2/ https://blog.youkuaiyun.com/lizuobin2/article/details/51742443

SPI协议参考:

SPI协议及其工作原理浅析 

    http://bbs.chinaunix.net/thread-1916003-1-1.html

SPI总线协议及SPI时序图详解

    http://blog.163.com/sunshine_linting/blog/static/44893323201181482335951  


一、概述
    SPI, Serial Perripheral Interface, 串行外围设备接口, 是 Motorola 公司推出的一种同步串行接口技术. SPI 总线在物理上是通过接在外围设备微控制器(PICmicro) 上面的微处理控制单元 (MCU) 上叫作同步串行端口(Synchronous Serial Port) 的模块(Module)来实现的, 它允许 MCU 以全双工的同步串行方式, 与各种外围设备进行高速数据通信.
    SPI 主要应用在 EEPROM, Flash, 实时时钟(RTC), 数模转换器(ADC), 数字信号处理器(DSP) 以及数字信号解码器之间. 它在芯片中只占用四根管脚 (Pin) 用来控制以及数据传输, 节约了芯片的 pin 数目, 同时为 PCB 在布局上节省了空间. 正是出于这种简单易用的特性, 现在越来越多的芯片上都集成了 SPI技术.

二、 特点

  1. 采用主-从模式(Master-Slave) 的控制方式
    SPI 规定了两个 SPI 设备之间通信必须由主设备 (Master) 来控制次设备 (Slave). 一个 Master 设备可以通过提供 Clock 以及对 Slave 设备进行片选 (Slave Select) 来控制多个 Slave 设备, SPI 协议还规定 Slave 设备的Clock 由 Master 设备通过 SCK 管脚提供给 Slave 设备, Slave 设备本身不能产生或控制 Clock, 没有 Clock 则 Slave 设备不能正常工作.
  2. 采用同步方式(Synchronous)传输数据
    Master 设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样, 来保证数据在两个设备之间是同步传输的.

  3. 数据交换(Data Exchanges)
    SPI 设备间的数据传输之所以又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 "发送者(Transmitter)" 或者 "接收者(Receiver)".在每个 Clock 周期内, SPI 设备都会发送并接收一个 bit 大小的数据, 相当于该设备有一个 bit 大小的数据被交换了.
    一个 Slave 设备要想能够接收到 Master 发过来的控制信号, 必须在此之前能够被 Master 设备进行访问 (Access). 所以, Master 设备必须首先通过 SS/CS pin 对 Slave 设备进行片选, 把想要访问的 Slave 设备选上.

    在数据传输的过程中,  每次接收到的数据必须在下一次数据传输之前被采样. 如果之前接收到的数据没有被读取, 那么这些已经接收完成的数据将有可能会被丢弃,  导致 SPI 物理模块最终失效. 因此, 在程序中一般都会在 SPI 传输完数据后, 去读取 SPI 设备里的数据, 即使这些数据(Dummy Data)在我们的程序里是无用的.

三、 工作机制 

  1. 概述


    通常情况下, 我们只需要对上图所描述的四个管脚(pin) 进行编程即可控制整个 SPI 设备之间的数据通信:
    SCK :Serial Clock主要的作用是 Master 设备往 Slave 设备传输时钟信号, 控制数据交换的时机以及速率;
    SS/CS:Slave Select/Chip Select, 用于 Master 设备片选 Slave 设备, 使被选中的 Slave 设备能够被 Master 设备所访问;
    SDO/MOSI: Serial Data Output/Master Out Slave In, 在 Master 上面也被称为 Tx-Channel, 作为数据的出口, 主要用于 SPI 设备发送数据;
    SDI/MISO: Serial Data Input/Master In Slave Out, 在 Master 上面也被称为 Rx-Channel, 作为数据的入口, 主要用于SPI 设备接收数据;
    SPI 设备在进行通信的过程中, Master 设备和 Slave 设备之间会产生一个数据链路回环(Data Loop), 就像上图所画的那样, 通过 SDO 和 SDI 管脚, SSPSR 控制数据移入移出 SSPBUF, Controller 确定 SPI 总线的通信模式, SCK 传输时钟信号.

  2.工作模式

    CPOL位表示初始时钟极性:
      CPOL = 0表示时钟初始为低电平,所以开始的第一(leading)边缘是上升沿,第二边缘(trailing)是下降沿。 
      CPOL = 1时的时钟启动为高电平,所以第一(leading)的边缘是下降沿。
    CPHA的指示用于采样数据的时钟相位(注意是采样数据,采样是指的将数据线上的数据所存起来)
      CPHA = 0时表示边沿超前
      CPHA = 1表示边沿滞后
  以 S3C2440 为例:


    模式0:CPOL = 0 CPHA = 0 主机从机的数据均是在下降沿改变,上升沿锁存
    模式1:CPOL = 0 CPHA = 1 主机从机的数据均是在上升沿改变,下降沿锁存
    模式2:CPOL = 1 CPHA = 0 主机从机的数据均是在上升沿改变,下降沿锁存
    模式3:CPOL = 0 CPHA = 1 主机从机的数据均是在下降沿改变,上升沿锁存
  以模式0为例,看看具体时序图:



     通常情况下,我们并不关心初始时钟电平状态,大多采用上升沿锁存,下降沿改变数据的方式,也就是模式0和模式3,下面看一个具体的spi_flash读数据的时序图

    显然,主机和从机都是在上升沿锁存数据,下降沿改变数据。

四、裸机程序
    在使用spi控制器的情况下,裸机程序十分简单,以 S3C2440 为例,简单说明一下。
      1、配置引脚
        1. 设置片选输出低电平
        2. 配置 GPE11 SPIMISO 、GPE12 SPIMOSI 、GPE13 SPICLK 为特殊功能引脚
      2、配置控制寄存器
        SPCONn[6-5]: SPI模式选择,查询 中断 DMA等
        SPCONn[4]  : 时钟使能
        SPCONn[3]  : 主机模式、从机模式
        SPCONn[2]  : CPOL
        SPCONn[1]  : CPHA
        SPCONn[0]  : 接收数据是是否自动发送,这里选普通模式,我们自己发送就好
      3、发送数据
        1、检查状态寄存器 传输是否可用 SPSTAn[0] 1表示可用,0表示正在发送或者接收
        2、将数据写入 SPTDATn
      4、接收数据
        1、检查状态寄存器 传输是否可用 SPSTAn[0] 1表示可用,0表示正在发送或者接收
        2、写数据 0xff 到 SPTDATn ,这就是普通模式的要求,接收的同时发送,0xff发送完了,数据也就接收完了
        3、检查状态寄存器 传输是否可用 SPSTAn[0] 1表示可用,0表示正在发送或者接收
        4、从 SPRDATn 取数据

static void SPIFlash_Set_CS(char val)
{
    if (val)
        GPGDAT |= (1<<10);
    else
        GPGDAT &= ~(1<<10);
}
static void SPI_GPIO_Init(void)
{
    /* GPG1 OLED_CSn output 
    * GPG10 FLASH_CSn output
    */
    GPGCON &= ~((3<<(1*2)) | (3<<(10*2)));
    GPGCON |= (1<<(1*2)) | (1<<(10*2));
    GPGDAT |= (1<<1) | (1<<10);
<span class="hljs-comment">/* 
* GPF3  OLED_DC   output
* GPE11 SPIMISO   
* GPE12 SPIMOSI   
* GPE13 SPICLK    
*/</span>
GPFCON &amp;= ~(<span class="hljs-number">3</span>&lt;&lt;(<span class="hljs-number">3</span>*<span class="hljs-number">2</span>));    
GPFCON |= (<span class="hljs-number">1</span>&lt;&lt;(<span class="hljs-number">3</span>*<span class="hljs-number">2</span>));    

GPECON &amp;= ~((<span class="hljs-number">3</span>&lt;&lt;(<span class="hljs-number">11</span>*<span class="hljs-number">2</span>)) | (<span class="hljs-number">3</span>&lt;&lt;(<span class="hljs-number">12</span>*<span class="hljs-number">2</span>)) | (<span class="hljs-number">3</span>&lt;&lt;(<span class="hljs-number">13</span>*<span class="hljs-number">2</span>)));
GPECON |= ((<span class="hljs-number">2</span>&lt;&lt;(<span class="hljs-number">11</span>*<span class="hljs-number">2</span>)) | (<span class="hljs-number">2</span>&lt;&lt;(<span class="hljs-number">12</span>*<span class="hljs-number">2</span>)) | (<span class="hljs-number">2</span>&lt;&lt;(<span class="hljs-number">13</span>*<span class="hljs-number">2</span>)));

}

void SPISendByte(unsigned char val)
{
while (!(SPSTA0 & 1));
SPTDAT0 = val;
}

unsigned char SPIRecvByte(void)
{
SPTDAT0 = 0xff;
while (!(SPSTA0 & 1));
return SPRDAT0;
}

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;

<span class="hljs-comment">/* [6:5] : 00, polling mode
* [4]   : 1 = enable 
* [3]   : 1 = master
* [2]   : 0
* [1]   : 0 = format A
* [0]   : 0 = normal mode
*/</span>
SPCON0 = (<span class="hljs-number">1</span>&lt;&lt;<span class="hljs-number">4</span>) | (<span class="hljs-number">1</span>&lt;&lt;<span class="hljs-number">3</span>);
SPCON1 = (<span class="hljs-number">1</span>&lt;&lt;<span class="hljs-number">4</span>) | (<span class="hljs-number">1</span>&lt;&lt;<span class="hljs-number">3</span>);

}

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

SPIControllerInit();

}

    实际的SPI通信中,是由片选函数、字节读、字节写三部分组合而成~例如:

void SPIFlashRead(unsigned int addr, unsigned char *buf, int len)
{
    int i;
SPIFlash_Set_CS(<span class="hljs-number">0</span>);
SPISendByte(<span class="hljs-number">0x03</span>);
SPISendByte(addr &gt;&gt; <span class="hljs-number">16</span>);
SPISendByte(addr &gt;&gt; <span class="hljs-number">8</span>);
SPISendByte(addr &amp; <span class="hljs-number">0xff</span>);
<span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; len; i++)
    buf[i] = SPIRecvByte();

SPIFlash_Set_CS(<span class="hljs-number">1</span>);    

}

五、master 驱动实例详细分析
    上一篇文章中,分析了 master 和 spi 设备驱动的框架,我们知道 master 是在 atmel_spi_probe 函数中分配、设置、注册。其实在哪不重要,主要是设置的那些东西,我们再来看一下都设置了哪些东西

    master = spi_alloc_master(&pdev->dev, sizeof *as);  
  
    /* 设置 master  */  
    master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;   // 所支持的模式  
    master->bus_num = pdev->id;   // 控制器编号,用于分辨外围spi设备是连接在哪一个控制器上  
    master->num_chipselect = 4;  // 片选最大值+1,spi设备的片选值要小于它  
    master->setup = atmel_spi_setup; // 一个控制器上可能接有多个spi设备,它们的频率和模式是不一样的,用于设备之间切换时设置这些信息。  
    master->transfer = atmel_spi_transfer;   // 最重要的发送函数  
    ret = request_irq(irq, atmel_spi_interrupt, 0,  dev_name(&pdev->dev), master);
    master->transfer 函数,就跟I2c里的adapter->xxx_i2c_xfer函数一样,是最底层的发送接收函数,在spi中,设备驱动层调用到master层transfer函数的过程是这样的spi_sync(spi, &m)->spi_async(spi,&m)->master->transfer(spi, &m),也就是说,设备驱动层传递给我们一个spi_device 和 一个 spi_message ,还有一点值得注意的。

int spi_sync(struct spi_device *spi, struct spi_message *message)
{
	DECLARE_COMPLETION_ONSTACK(done);
	int status;
message-&gt;complete = spi_complete;
message-&gt;context = &amp;done;
status = spi_async(spi, message);
<span class="hljs-keyword">if</span> (status == <span class="hljs-number">0</span>) {
	wait_for_completion(&amp;done);
	status = message-&gt;status;
}
message-&gt;context = <span class="hljs-literal">NULL</span>;
<span class="hljs-keyword">return</span> status;

}

    在 spi_sync 中,调用 spi_async(spi, message) 之后,会 wait_for_completion(&done)休眠,那么我们在master的发送函数中,发送完成,自然要 mesg->complete(mesg->context),还要设置 mesg->status = 0

    在 spi_new_device 我们根据 Board_list 里的信息创建了 spi_device,它含有一下信息

struct spi_device *spi_new_device(struct spi_master *master,  
                  struct spi_board_info *chip)  
{  
    struct spi_device   *proxy;  
    int         status;  
proxy = spi_alloc_device(master);  

proxy-&gt;chip_select = chip-&gt;chip_select;  
proxy-&gt;max_speed_hz = chip-&gt;max_speed_hz;  
proxy-&gt;mode = chip-&gt;mode;  
proxy-&gt;irq = chip-&gt;irq;  
strlcpy(proxy-&gt;modalias, chip-&gt;modalias, <span class="hljs-keyword">sizeof</span>(proxy-&gt;modalias));  
proxy-&gt;dev.platform_data = (<span class="hljs-keyword">void</span> *) chip-&gt;platform_data;  
proxy-&gt;controller_data = chip-&gt;controller_data;  
proxy-&gt;controller_state = <span class="hljs-literal">NULL</span>;  

status = spi_add_device(proxy);  

<span class="hljs-keyword">return</span> proxy;  

}

    对于 spi_message,它对应于一个不可打断的spi传输过程,可以理解为片选选中直到取消选中的过程(特殊情况下,一个spi_message里面是可以取消片选再选中的),而 spi_message 由 spi_transfer 组成,根据 tx_buf  rx_buf 是否为空来判断是 写还是读 操作。

    那么,我们master->transfer函数需要做什么?

      1、传输前,根据 spi_device 里的信息,对 master 进行设置(调用master->setup),因为一个spi控制器可能接有多个spi设备,它们的频率 模式可能是不一样的
      2、取出 spi_message 里的 spi_transfer 进行寄存器操作,发送或接收,这个过程和 I2C 的传输过程是类似的。
    下面开始分析代码,先看setup函数。代码参考:韦东山老师的spi教程

static int s3c2440_spi_setup(struct spi_device *spi)
{
    struct s3c_spi_info *info;
    struct clk *clk;
<span class="hljs-keyword">int</span> cpol = <span class="hljs-number">0</span>;
<span class="hljs-keyword">int</span> cpha = <span class="hljs-number">0</span>;

<span class="hljs-keyword">int</span> div;

info = spi_master_get_devdata(spi-&gt;master);
clk = clk_get(<span class="hljs-literal">NULL</span>, <span class="hljs-string">"plck"</span>);

<span class="hljs-comment"><span class="hljs-comment">/* 设置传输模式 : mode</span>

* 传输频率 : max_speed_hz
* bits_per_word : 不用管
*/

<span class="hljs-comment">/* spi_mode: CPOL,CPHA组成一个值,0,1,2,3
 */</span>

<span class="hljs-keyword">if</span> (spi-&gt;mode &amp; <span class="hljs-number">1</span>)
{
    cpha = <span class="hljs-number">1</span>;
}
<span class="hljs-keyword">if</span> (spi-&gt;mode &amp; <span class="hljs-number">2</span>)
{
    cpol = <span class="hljs-number">1</span>;
}

<span class="hljs-comment">/* 写入SPCON0,1 */</span>
<span class="hljs-comment">/* [6:5] : 01, polling mode
 * [4]   : 1 = enable 
 * [3]   : 1 = master
 * [2]   : CPOL
 * [1]   : CPHA
 * [0]   : 0 = normal mode
 */</span>
writeb((<span class="hljs-number">1</span>&lt;&lt;<span class="hljs-number">5</span>) | (<span class="hljs-number">1</span>&lt;&lt;<span class="hljs-number">4</span>) | (<span class="hljs-number">1</span>&lt;&lt;<span class="hljs-number">3</span>) | (cpol &lt;&lt; <span class="hljs-number">2</span>) | (cpha &lt;&lt; <span class="hljs-number">1</span>), info-&gt;reg_base + S3C2410_SPCON);

<span class="hljs-comment">/*
 * Baud rate = PCLK / 2 / (Prescaler value + 1)
 * Prescaler value = PCLK/(2*Baud rate) - 1
 * Prescaler Value : 0,1,...,255
 */</span>
div = DIV_ROUND_UP(clk_get_rate(clk), spi-&gt;max_speed_hz * <span class="hljs-number">2</span>) - <span class="hljs-number">1</span>;
clk_put(clk);

<span class="hljs-keyword">if</span> (div &gt; <span class="hljs-number">255</span>)
	div = <span class="hljs-number">255</span>;

writeb(div, info-&gt;reg_base + S3C2410_SPPRE);
    
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;

}

    info 是我们用 spi_alloc_master 申请内存时多申请出来的那个自定义结构体,韦东山老师在里面放了 寄存器基地址等信息,后面完整代码会看到,setup 函数很简单,就是根据 spi_device 里的模式,设置master的模式,根据 spi_device 的最大频率,设置 master 的工作频率。

  下面来看transfer函数
    思路:

      1、首先一个 spi_message 对应一个 片选过程,我们要先片选

      2、master->setup
      3、取出 spi_message 中的 第一个 spi_transfer

        如果是读:跟裸机一样,先写0xff,等待中断

        如果是写:那么将 spi_transfer->tx_buf[0]里的数据写到寄存器,等待休眠

     何时唤醒?

        在中断服务程序里,判断当前 spi_transfer 的发送接收完毕了,唤醒,取出下一个 spi_transfer ,如果有所得 spi_transfer 传输完了,则唤醒设备驱动层进程

     中断服务程序:

        读:先把上次读的取出来,判断是否还有数据要读?如果有则继续发送0xff,等待中断,没有则唤醒,进入下一个spi_transfer

        写:判断当前 spi_transfer 的发送是否完成,没有完成则把下一个Buf里的数据发送或接收。完成了,则唤醒,进入下一个spi_transfer。


附上韦东山老师完整源码:

#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/slab.h>

#include <linux/spi/spi.h>
#include <linux/spi/spi_bitbang.h>
#include <linux/spi/s3c24xx.h>
#include <linux/module.h>

#include <plat/regs-spi.h>
#include <mach/regs-gpio.h>

/* 构造注册spi_master */

static struct spi_master *spi0_controller;
static struct spi_master *spi1_controller;

struct s3c_spi_info {
int irq;
unsigned int reg_base;
struct completion done;
struct spi_transfer *cur_t;
int cur_cnt;
struct platform_device *pdev;
};

static int s3c2440_spi_setup(struct spi_device *spi)
{
struct s3c_spi_info *info;
struct clk *clk;

<span class="hljs-keyword">int</span> cpol = <span class="hljs-number">0</span>;
<span class="hljs-keyword">int</span> cpha = <span class="hljs-number">0</span>;

<span class="hljs-keyword">int</span> div;

info = spi_master_get_devdata(spi-&gt;master);
clk = clk_get(<span class="hljs-literal">NULL</span>, <span class="hljs-string">"plck"</span>);

<span class="hljs-comment"><span class="hljs-comment">/* 设置传输模式 : mode</span>

* 传输频率 : max_speed_hz
* bits_per_word : 不用管
*/

<span class="hljs-comment">/* spi_mode: CPOL,CPHA组成一个值,0,1,2,3
 */</span>

<span class="hljs-keyword">if</span> (spi-&gt;mode &amp; <span class="hljs-number">1</span>)
{
    cpha = <span class="hljs-number">1</span>;
}
<span class="hljs-keyword">if</span> (spi-&gt;mode &amp; <span class="hljs-number">2</span>)
{
    cpol = <span class="hljs-number">1</span>;
}

<span class="hljs-comment">/* 写入SPCON0,1 */</span>
<span class="hljs-comment">/* [6:5] : 01, polling mode
 * [4]   : 1 = enable 
 * [3]   : 1 = master
 * [2]   : CPOL
 * [1]   : CPHA
 * [0]   : 0 = normal mode
 */</span>
writeb((<span class="hljs-number">1</span>&lt;&lt;<span class="hljs-number">5</span>) | (<span class="hljs-number">1</span>&lt;&lt;<span class="hljs-number">4</span>) | (<span class="hljs-number">1</span>&lt;&lt;<span class="hljs-number">3</span>) | (cpol &lt;&lt; <span class="hljs-number">2</span>) | (cpha &lt;&lt; <span class="hljs-number">1</span>), info-&gt;reg_base + S3C2410_SPCON);

<span class="hljs-comment">/*
 * Baud rate = PCLK / 2 / (Prescaler value + 1)
 * Prescaler value = PCLK/(2*Baud rate) - 1
 * Prescaler Value : 0,1,...,255
 */</span>
div = DIV_ROUND_UP(clk_get_rate(clk), spi-&gt;max_speed_hz * <span class="hljs-number">2</span>) - <span class="hljs-number">1</span>;
clk_put(clk);

<span class="hljs-keyword">if</span> (div &gt; <span class="hljs-number">255</span>)
	div = <span class="hljs-number">255</span>;

writeb(div, info-&gt;reg_base + S3C2410_SPPRE);
    
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;

}

static int s3c2440_spi_transfer(struct spi_device *spi, struct spi_message *mesg)
{
struct spi_master *master = spi->master;
struct s3c_spi_info *info;
struct spi_transfer *t = NULL;

info = spi_master_get_devdata(master);

<span class="hljs-comment">/* 1. 选中芯片 */</span>
s3c2410_gpio_setpin(spi-&gt;chip_select, <span class="hljs-number">0</span>);  <span class="hljs-comment">/* 默认为低电平选中 */</span>

<span class="hljs-comment">/* 2. 发数据 */</span>

<span class="hljs-comment">/* 2.1 发送第1个spi_transfer之前setup */</span>
master-&gt;setup(spi);

<span class="hljs-comment">/* 2.2 从spi_message中逐个取出spi_transfer,执行它 */</span>
list_for_each_entry (t, &amp;mesg-&gt;transfers, transfer_list) {
    <span class="hljs-comment">/* 处理这个spi_transfer */</span>
    info-&gt;<span class="hljs-keyword">cur_t</span> = t;
    info-&gt;cur_cnt = <span class="hljs-number">0</span>;
	init_completion(&amp;info-&gt;done);

    <span class="hljs-comment">/* 如果该spi_transfer的speed_hz或bits_per_word
     * 不是0, 则调用setup来设置
     */</span>

    <span class="hljs-keyword">if</span> (t-&gt;tx_buf)
    {
        <span class="hljs-comment">/* 发送 */</span>
        writeb(((<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">char</span> *)t-&gt;tx_buf)[<span class="hljs-number">0</span>], info-&gt;reg_base + S3C2410_SPTDAT);

        <span class="hljs-comment">/* 它会触发中断 */</span>

        <span class="hljs-comment">/* 休眠 等待当前transfer 发送完毕 */</span>
        wait_for_completion(&amp;info-&gt;done);
    }
    <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(t-&gt;rx_buf)
    {
        <span class="hljs-comment">/* 接收 */</span>
        writeb(<span class="hljs-number">0xff</span>, info-&gt;reg_base + S3C2410_SPTDAT);
        <span class="hljs-comment">/* 它会触发中断 */</span>

        <span class="hljs-comment">/* 休眠 等待当前 transfer 接收完毕 */</span>
        wait_for_completion(&amp;info-&gt;done);
    }

    <span class="hljs-comment">/* 如果该spi_transfer的cs_change为1
     * 则取消片选
     */</span>
}


<span class="hljs-comment">/* 2.3 唤醒等待的进程 */</span>
mesg-&gt;status = <span class="hljs-number">0</span>;
mesg-&gt;complete(mesg-&gt;context);    

<span class="hljs-comment">/* 3. 取消片选 */</span>
s3c2410_gpio_setpin(spi-&gt;chip_select, <span class="hljs-number">1</span>);  <span class="hljs-comment">/* 默认为低电平选中 */</span>

<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;

}

static irqreturn_t s3c2440_spi_irq(int irq, void *dev_id)
{
struct spi_master *master = (struct spi_master *)dev_id;
struct s3c_spi_info *info = spi_master_get_devdata(master);
struct spi_transfer *t = info->cur_t;

<span class="hljs-keyword">if</span> (!t)
{
    <span class="hljs-comment">/* 误触发 */</span>
	<span class="hljs-keyword">return</span> IRQ_HANDLED;            
}

<span class="hljs-comment">/* 处理 spi_transfer */</span>

<span class="hljs-keyword">if</span> (t-&gt;tx_buf) <span class="hljs-comment">/* 是发送 */</span>
{
    info-&gt;cur_cnt++;
    
    <span class="hljs-keyword">if</span> (info-&gt;cur_cnt &lt; t-&gt;len)<span class="hljs-comment">/* 没发完? */</span>
        writeb(((<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">char</span> *)t-&gt;tx_buf)[info-&gt;cur_cnt], info-&gt;reg_base + S3C2410_SPTDAT);        
    <span class="hljs-keyword">else</span>
        complete(&amp;info-&gt;done); <span class="hljs-comment">/* 唤醒 */</span>
}
<span class="hljs-keyword">else</span> <span class="hljs-comment">/* 接收 */</span>
{
    <span class="hljs-comment">/* 读/存数据 */</span>
    ((<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">char</span> *)t-&gt;rx_buf)[info-&gt;cur_cnt] = readb(info-&gt;reg_base + S3C2410_SPRDAT);
    info-&gt;cur_cnt++;
    
    <span class="hljs-keyword">if</span> (info-&gt;cur_cnt &lt; t-&gt;len)<span class="hljs-comment">/* 没收完? */</span>
        writeb(<span class="hljs-number">0xff</span>, info-&gt;reg_base + S3C2410_SPTDAT);        
    <span class="hljs-keyword">else</span>
        complete(&amp;info-&gt;done); <span class="hljs-comment">/* 唤醒 */</span>
}
<span class="hljs-keyword">return</span> IRQ_HANDLED;    

}

static void s3c2440_spi_controler_init(int which)
{
struct clk *clk = clk_get(NULL, “spi”);

<span class="hljs-comment">/* 使能spi controller 0/1的时钟 */</span>
clk_enable(clk);
clk_put(clk);

<span class="hljs-comment">/* GPIO */</span>
<span class="hljs-keyword">if</span> (which == <span class="hljs-number">0</span>)
{
    <span class="hljs-comment">/* SPI controller 0 */</span>
    <span class="hljs-comment">/*
     * GPE11 SPIMISO   
     * GPE12 SPIMOSI   
     * GPE13 SPICLK    
     */</span>
    s3c2410_gpio_cfgpin(S3C2410_GPE(<span class="hljs-number">11</span>), S3C2410_GPE11_SPIMISO0);
    s3c2410_gpio_cfgpin(S3C2410_GPE(<span class="hljs-number">12</span>), S3C2410_GPE12_SPIMOSI0);
    s3c2410_gpio_cfgpin(S3C2410_GPE(<span class="hljs-number">13</span>), S3C2410_GPE13_SPICLK0);        

}
<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (which == <span class="hljs-number">1</span>)
{
    <span class="hljs-comment">/* SPI controller 1 */</span>
    <span class="hljs-comment">/*
     * GPG5 SPIMISO   
     * GPG6 SPIMOSI   
     * GPG7 SPICLK    
     */</span>
    s3c2410_gpio_cfgpin(S3C2410_GPG(<span class="hljs-number">5</span>), S3C2410_GPG5_SPIMISO1);
    s3c2410_gpio_cfgpin(S3C2410_GPG(<span class="hljs-number">6</span>), S3C2410_GPG6_SPIMOSI1);
    s3c2410_gpio_cfgpin(S3C2410_GPG(<span class="hljs-number">7</span>), S3C2410_GPG7_SPICLK1);        

    <span class="hljs-comment">/* 使能spi controller 1的时钟 */</span>
}

}

static struct spi_master *create_spi_master_s3c2440(int bus_num, unsigned int reg_base_phy, int irq)
{
int ret;
struct spi_master *master;
struct s3c_spi_info *info;
struct platform_device *pdev;

pdev = platform_device_alloc(<span class="hljs-string">"s3c2440_spi"</span>, bus_num);
platform_device_add(pdev);

master = spi_alloc_master(&amp;pdev-&gt;dev, <span class="hljs-keyword">sizeof</span>(struct s3c_spi_info));
master-&gt;bus_num = bus_num;
master-&gt;num_chipselect = <span class="hljs-number">0xffff</span>;
master-&gt;mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;

master-&gt;setup    = s3c2440_spi_setup;
master-&gt;transfer = s3c2440_spi_transfer;

info = spi_master_get_devdata(master);
info-&gt;reg_base = (<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span>)ioremap(reg_base_phy, <span class="hljs-number">0x18</span>);
info-&gt;irq = irq;
info-&gt;pdev = pdev;

<span class="hljs-comment">/* 硬件初始化 */</span>
s3c2440_spi_controler_init(bus_num);

ret = request_irq(irq, s3c2440_spi_irq, <span class="hljs-number">0</span>, <span class="hljs-string">"s3c2440_spi"</span>, master);

spi_register_master(master);

<span class="hljs-keyword">return</span> master;

}

static void destroy_spi_master_s3c2440(struct spi_master *master)
{
struct s3c_spi_info *info = spi_master_get_devdata(master);;

spi_unregister_master(master);
platform_device_del(info-&gt;pdev);
platform_device_put(info-&gt;pdev);
free_irq(info-&gt;irq, master);
iounmap((<span class="hljs-keyword">void</span> *)info-&gt;reg_base);
<span class="hljs-comment">//kfree(master);</span>

}

static int spi_s3c2440_init(void)
{
spi0_controller = create_spi_master_s3c2440(0, 0x59000000, IRQ_SPI0);
spi1_controller = create_spi_master_s3c2440(1, 0x59000020, IRQ_SPI1);

<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;

}

static void spi_s3c2440_exit(void)
{
destroy_spi_master_s3c2440(spi0_controller);
destroy_spi_master_s3c2440(spi1_controller);
}

module_init(spi_s3c2440_init);
module_exit(spi_s3c2440_exit);
MODULE_DESCRIPTION(“SPI Controller Driver”);
MODULE_AUTHOR("weidongshan@qq.com,www.100ask.net");
MODULE_LICENSE(“GPL”);







SPI协议的verilog实现(spi master slave联合实现) 阅读详情

相关推荐

SpiIdleTime/SpiTrailingTime/SpiTimeClk2Cs三个配置项的含义(三)

在对SPI模块进行MCAL配置时,有三个时间相关参数SpiIdleTime/ SpiTrailingTime/ SpiTimeClk2Cs,很多人不了解其具体含义,下面对其做一个具体的说明,并通过示波器测试SPI传输波形,验证与实际配置值是否一致。

m0_38045338的博客 1981

spi总线概述1(spi-summary)

什么是SPI? ------------ “串行外设接口”(SPI)是一个同步的四线串行链接用于微控制器连接传感器,内存和外设的。这是一个简单的“时点”的标准,获取一个标准化结构也不复杂。 SPI使用主/从配置。   三个信号线外加一个时钟(SCK,往往在10 MHz),并行数据线“主机输出,从机输入”(MOSI)或“主机输入,从机输出”(MISO)。数据交换有四种时钟模式:模式0和模式3

zhangxiaopeng0829的专栏 4896

《我的 Classic Platform AUTOSAR之路》 SPI介绍与EB配置

AUTOSAR SPI配置解析 AUTOSAT SPI 以下内容基于标准AUTOSAR文档以及EB Tresos工具说明 SPI 在AUTOSAR的配置主要参数 波特率 极性相位 CS使能电平 CLK Idle电平 数据大小端 数据位宽 数据长度 CS2CLK时间 CLK2CS时间 CS2CS时间 SPI通信模式(同步 or 异步) Buffer模式(IB or EB) … SPI波特率设置 ...

libuping2023的博客 1万+

【SPI】SPI学习之SPI硬件相关

参考: 文档:linux spi驱动开发学习(一)-----spi子系统架构 http://blog.chinaunix.net/uid-21501855-id-5211900.html 参考: 文档:SPI通信简介 https://blog.youkuaiyun.com/firefly_cjd/article/details/51935079#t17 什么是spi?spi的作用? SPI硬件知识 SPI外部信号描述 SPI硬件寄存器映射 SPI特性 SPI相位和极性 CPOL和CP...

诸葛一帆丶的博客 4089

comsol 多物理场仿真流程-以开关柜为例

1、主要方法 在SolidWorks中对于开关柜的内部与外部结构进行建模,生成能够导入软件计算的3D模型。将SolidWorks中的模型导入comsol软件中,设置参数进行多物理场耦合仿真。 1、具体步骤 删除高压开关柜中对其内部电磁环境无较大影响的结构,如低压测量柜、断路器内部结构等。 导入模型:将厂家提供的3D模型转化为solidworks结构模型。 修复模型:修复已转化模型中转化错误的部分、修复已转化模型中非实体部分、修复已转化模型中重叠实体部分 化简模型:去除过于...

健忘16的博客 7020

北航论文模板:解决XeLaTeX中Font shape'TU/SimSun(1)/b/n' undefined(font) using 'TU/SimSun(1)/m/n' instead

北航论文模板:解决XeLaTeX中Font shape ‘TU/SimSun(1)/b/n’ undefined(font) using ‘TU/SimSun(1)/m/n’ instead 问题 问题描述: 在使用北航论文模板时,产生了warning:Font shape 'TU/SimSun(1)/b/n' undefined(font) using 'TU/SimSun(1)/m/n' i...

qq_39560620的博客 1万+

linux驱动系列学习之spi子系统(五)

Linux的spi框架,以icm20608传感器为例

zichuanning520的博客 2334

【算法】基于STM32的MPU6050卡尔曼滤波算法(入门级)

1. 简介 卡尔曼滤波(Kalman filtering)是一种利用线性系统状态方程,通过系统输入输出观测数据,对系统状态进行最优估计的算法。由于观测数据中包括系统中的噪声和干扰的影响,所以最优估计也可看作是滤波过程。详情见:卡尔曼滤波简介 MPU6050的解算主要有三种姿态融合算法:四元数法 、一阶互补算法和卡尔曼滤波算法。我们常用的DMP库使用的是四元数法,本文采用卡尔曼滤波算法,使用RT-Thread国产操作系统,利用env工具进行串口、模拟IIC环境配置,使用10ms的线程进行卡尔曼滤波解算。 2.

weixin_44549777的博客 3万+

2.16 SPI协议的4种模式

四线控制SDO - 主设备数据输出,从设备输入 对应MOSISDI - 主设备数据输入,从设备输出 对应MISOSCLK - 时钟信号,由主设备产生CS - 从设备使能信号,由主设备控制。

oDuanYanGuHong的博客 1886

未来智能家居解决方案:AI应用架构师的前沿探索

当你加班到深夜回家时,门锁自动识别你的脚步声解锁,玄关灯渐亮成你最爱的暖光,空调早已调到25℃(你夏天的习惯温度),音箱播放着最近循环的爵士乐,茶几上的智能水壶烧好了温水——这不是科幻电影,而是未来智能家居的日常。如何让“冰冷的设备”变成“懂你的伙伴”。我们将深入探讨五大前沿技术(边缘AI、多模态融合、联邦学习、数字孪生、个性化推荐)的底层原理,用“家里的小管家”“邻居互相学习”等生活化比喻简化复杂概念,结合代码示例与架构图还原实现流程,并通过智能卧室、智能厨房等真实案例展示落地路径。

AI天才研究院 1410

LeGO-LOAM相对LOAM的改进---地面分离的方法-算法原理+代码讲解

实际上此种地⾯分离算法有⼀些简单,我们可以结合激光雷达安装⾼度等其他先验信息进⾏优化,避免将桌子这些当作地面点。LeGO-LOAM中前端改进中很重要的⼀点就是充分利⽤了地⾯点,那⾸先⾃然是提取对地⾯点的提取。理想情况下, 应该接近0,考虑到⼀⽅⾯激光雷达安装也⽆法做到绝对⽔平,另⼀⽅⾯,地⾯也不是绝。对⽔平,因此,这个⾓度会略微⼤于0,考虑到作者实际在草坪之类的场景下运动,因此这个值被设置成。如上图,相邻的两个扫描线束的同⼀列打在地⾯上如AB点所⽰,他们的垂直⾼度差。,计算垂直⾼度差和⽔平⾼度差的⾓度。..

zysss_的博客 775

【入门-09】队列SPI(QSPI)

英飞凌AURIX官方培训

CrazyRabbit的博客 6528

Trimble RealWorks处理点云数据(一)之坐标转换

Trimble RealWorks处理点云数据之坐标转换

yilvyangguang520的博客 3618

硬件:SPI时序简单理解

SPI接口相对简单(4根线),主要应用在EEPROM、FLASH、实时时钟、AD转换器、数字信号处理器与数字信号解码器之间 SPI中的时钟和相位指的是SCLK时钟的特性。 SPI的极性Polarity和相位Phase,常见写法CPOL和CPHA。其他写法: (1)CKPOL(Clock Polarity) = CPOL = POL = Polarity = (时钟)极性 (2)CKPHA(Clock Phase) = CPHA = PHA = Phase = (时钟)相位 (3)..

what, when, why, how... 1969

嵌入式u-boot最全面试题及参考答案(5万字长文持续更新)

例如,在一个便携式设备中,U-Boot 在启动时初始化电源控制器,将输出电压设置为合适的值。命令是u-boot构建过程中用于配置u-boot选项的命令。在嵌入式系统中,启动画面(也称为启动Logo或Splash Screen)是展示给用户的第一印象,它可以包含公司的标志、产品的名称或其他相关信息。u-boot允许通过环境变量来进行配置,这使得开发者可以根据不同的硬件平台和项目需求灵活地调整u-boot的行为。写入操作可以是字节级别的,也可以是块级别的,具体取决于设备的性能和操作系统的需求。

大模型大数据攻城狮的专栏 997

CVPR 2026 即插即用 | 注意力篇 | MSEF:超轻注意力,空间-通道双分支并行融合,既增强特征又抑制噪声!

Multinex提出了一种超轻量级微光图像增强框架,其核心创新是MSEF模块。MSEF采用双分支并行架构,结合空间局部特征(深度可分离卷积)和全局通道注意力(改进SE),通过Tanh激活实现特征增强与抑制。相比传统注意力模块,MSEF在几乎不增加计算量的情况下显著提升性能,特别适合轻量级网络和边缘设备部署。实验表明,该模块能有效提升PSNR约3dB,同时保持极低的参数量(0.7K-45K)。

主要更新底层视觉(去噪、超分等)相关的科研内容,形式为【论文精读】+【论文复现】 603

SPI通信协议详细介绍

SPI通信一般为四根信号,分为SCK(Serial Clock)串行时钟CS(Chip Select)片选主发从收及从发主收。从信号定义可以看出其拥有系统时钟,通讯方式为同步通讯。还可以看出其收发模式可以分为主发和从发两种,由于其收发信号线为两根,因此主机在进行发送的同时,从机也可以将数据发送给主机。因此,其为全双工通信。综上所述,我们可以了解到SPI通信是一种同步、全双工、串行的一种通讯方式。SPI分为主设备和从设备之分,但在同一个时刻,只能有一个主设备,但可以有多个从设备。

qq_37668831的博客 3633

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

SPI通信的主模式、从模式

主设备向从设备发送一个数据字节并等待从设备返回一个响应字节(或多个字节),然后再发送下一个数据字节。当主设备向从设备发送数据时,从设备会立即接收并响应该数据,然后从设备会将响应数据返回给主设备。在SPI通信中,主设备(Master)控制整个通信过程,与之相对的是从设备(Slave)。在SPI通信中,从设备(Slave)被动等待主设备(Master)的指令,并进行响应。与之相对的是主设备。通常,从设备用于采集或处理来自各种传感器或其他设备的数据,并将其传输回主设备进行进一步处理。

嵌入式之斋 1万+

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

SPI总线笔记

参考博客 ① SPI WIKI 模式示意图 A timing diagram showing clock polarity and phase. Red lines denote clock leading edges Blue lines denote clock trailing edges 解读 ① CPHA = 0时,leading edge采样;CPHA = 1时,...

MTzhou的专栏 884

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

STM32硬件SPI时钟频率与时钟解析(基于逻辑分析仪的抓包试验)

首先粘贴出我们CubeMX生成的时钟配置: 然后启用SPI3的功能,这里因为博主的逻辑分析仪比较low,所以把SPI的波特率设置成最大分频,即256分频,此时CubeMX工具计算出来的时钟频率为1.5625MBits/s: 我们都知道,SPI3挂载在APB1总线上,受到总线的最大时钟120M的限制,由前面的时钟图可以知道,APB1总线时钟速度为100M,那么经过256分频应该是390.625KHz才对。1.5625M/390.625K=4,这里的4倍频,是CubeMX软件计算的问题,还是真的哪里有了4倍

ShineRoyal 2万+

ESP32学习笔记(19)——SPI(主机)接口使用

一、SPI简介 SPI(Serial Peripheral Interface) 协议是由摩托罗拉公司提出的通讯协议,即串行外围设备接口,是一种高速全双工的通信总线。它被广泛地使用在 ADC、LCD 等设备与 MCU 间,要求通讯速率较高的场合。 芯片的管脚上只占用四根线。 MISO: 主器件数据输出,从器件数据输入。 MOSI:主器件数据输入,从器件数据输出。 SCK: 时钟信号,由主设备控制发出。 NSS(CS): 从设备选择信号,由主设备控制。当NSS为低电平则选中从器件。 1.1 ESP32中

Leung的博客 5万+

spi_master_slave

spi_master_slave 一 注意事项

sym_07的博客 3948

S32K3学习笔记---S32K3之LPSPI

本文主要是记录一下自己用过的几种SPI不同形式,以下是基于S32K324做的一个SPI通信的例子,会分别在两个核分别注册一路SPI,以及记录一下同步、异步、有无DMA的SPI方式

Abler_xaing的博客 9958

spi slave及master接口驱动及传输时序

spi slave驱动spi slave驱动在kernel中可以主要参考spidev.c,这是一个字符驱动,可以匹配kernel中的多个名称为“spidev”的spi设备,分析这个文件,主要有以下几个重点:1. 如何编写多设备公用驱动2. 如何封装读写请求到spi框架层3. spi message请求如何分发到master自spi_board_info或者spi master注册后,两者就已经完成...

weixin_30909575的博客 879

Leading and Trailing

链接 题意:给定两个数n,k 求n^k的前三位和最后三位 解法: 后三位:快速幂求即可 前三位:设方程, P由 整数部分和小数部分组成 P= X+ Y ,则 对前三位的数没影响,重点是的值,再*100即是前三位的数 PS:注意后三位输出是使用%03d补全前缀0! #include<bits/stdc++.h> #define ll long long using namespace std; int T; int n,k; int main() { sca...

郭源潮 265
上一篇: linux中spi驱动框架
下一篇: linux驱动编写(总结篇)
文心雕龍3
博客等级 码龄8年 5粉丝 0原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值