打印
[应用及方案]

从零入手Kinetis系统开发之SPI模块

[复制链接]
5505|26
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
fhguo1990|  楼主 | 2015-4-23 11:13 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

前段时间完成了SPI模块驱动的编写和测试,所以今天就抓紧抽空把它写出来,唯恐拖久了就该忘了,然后就又得重新温习一遍了,呵呵。所以毫无疑问,今天的主角就是Kinetis的SPI模块了,个人感觉相对来说SPI模块的驱动比较好写,毕竟本身SPI协议就比较简单,那下面我们就直入主题了:

SPI,即Serial Peripheral Interface,就是所谓的串行外设接口。这里虽说是串行通信接口,不过相比于传统的UART来讲,其优势当属高速、全双工和同步这三大特性了,呵呵,当然它兼有串行总线的占用管脚少的特点,只需要4根线即可,即串行时钟线(SCK,同步必须的)、主出从入(MOSI)、主入从出(MISO)及从机片选(nSS)。同时拥有这么多优势那自然广受各大半导体厂商的欢迎了,所以SPI接口的应用比较广泛,如EEPROM、片外Flash及各种外设芯片,总之很多很多的片子都会选择SPI作为其通信接口,以致于如果一款SOC或者MCU没有SPI接口的话都会显得非常另类了,哈哈。当然还有一个小插曲了,那就是SPI的老祖宗就是Motorola了,Motorola半导体(Freescale前身)最早提出该接口并应用于其当年闻名遐迩的68k处理器了,所以Freescale自家的产品上的SPI估计不会太差吧,呵呵,当然现在讨论这个有点为时过早,用过了才知道,下面我们就正式说说Kinetis的SPI模块吧:

1.首先还是按套路出牌吧,呵呵,在浅浅的给大家普及完下SPI的常识之后,那下面就具体化些,以Kinetis的片上SPI模块为例捡重点的说说Kinetis SPI的特性:

(1)SPI的共性,全双工,四线同步传输(基本等于废话,呵呵,上面提到了);

(2)支持主机与从机模式,主模式支持最高busclk/2的传输速率;

(3)支持深度为4宽度为32bit的发送和接收FIFO,这个不错;

(4)可编程控制的SPI发送接收属性,包括可编程一帧发送位数(4bit到16bit可选,当然也可以支持连续发送,这个发送位数就不受限了)、可编程的SS有效到SCK有效延迟时间、可编程时钟信号极性及相位等等;

(5)支持多个SPI模块(我的K60 144pin有3个),并且每个SPI模块最多支持6个外设片选SSx,且可以使用外部编码器扩展成64个片选,这个我们平时用基本用不到这么多,可能用在复杂的系统里;

(6)支持多达6个中断源,但注意这些中断源共用一个中断向量,所以进ISR后需要软件判断具体是哪个中断源;

(7)允许Interrupt、DMA及查询方式发送和接受SPI数据。

下图为SPI模块系统框图,官方SPI框图比较粗,只能凑合着看了,呵呵:

2.说完Kinetis的基本特性之后,我们还需要做些准备,了解了解Kinetis的管脚描述及其中断源。

SPI引脚分配:

PCS0/nSS0 —— 主机模式为片选输出PCS0,从机为片选输入SS0;

PCS[4:1] —— 主机模式为外设片选PCS[1:4],从机无效;

PCS5/nPCSS —— 主机模式下位外设片选PCS5或者频闪(频闪用于和PCS[0:4]搭配编码片选64个SPI外设),从机无效;

SIN —— 即MISO,主入从出;

SOUT ——即MOSI,主出从入;

SCK —— 主机作为输出同步时钟,从机则作为输入接收同步时钟。

中断源:

3.到此准备工作就都结束了,下面就可以放开手脚开始软件的编写了,这也是前面啰嗦了半天之后最关键的一步了,估计大家也都等着这一步呢,呵呵,不过放心本篇会继续开源软件包,可以到**最后的附件下载。当然,所谓的硬件驱动无非就是设置寄存器了,难的是从一堆英文datasheet里摘出完成相应功能所需的基本的寄存器进行恰当的设置来完成相应的功能。所以这里就拿出SPI驱动所需要的关心的几个最基本的寄存器设置了,如下:

(1)SPIx_MCR寄存器(重点的设置位我已用红圈标识)

MSTR:主从模式选择位

0 —— 从机,1——主机

PCSIS[5:0]:片选端无效状态

0 —— PCSx无效状态为低电平,1 —— PCSx无效状态为高电平

MDIS:模块禁能(注意默认上电为1,而且该位需要清零才能进一步设置DIS_TXF和DIS_RXF)

0 —— 使能SPI时钟,1 —— 通过外部逻辑禁能SPI时钟

DIS_TXF、DIS_RXF:禁能发送接收FIFO

0 —— 使能FIFO缓冲,1 —— 禁能FIFO缓冲

CLR_TXF、CLR_RXF:清空FIFO计数器(需要用到)

0 —— 不清零TX_FIFO\RX_FIFO counter,1 —— 清零TX_FIFO\RX_FIFO counter

HALT:停止SPI发送接收位(注意默认上电是置1的,在发送数据的时候需要清零该位才可)

0 —— 启动SPI发送,1 —— 停止SPI发送

(2)SPIx_CTARn寄存器,即时钟及发送属性寄存器,该寄存器有一定特殊性,在主机模式下该寄存器有CTAR[0:1]两个寄存器可选(具体使用哪个由SPI_PUSHR寄存器的CTAS位决定),而从机模式下同样有两个个专用寄存器为SPIx_CTAR[0:1]_Slave:

FMSZ:帧大小设置位,即决定每次发送多少个数据位。

最小为3,最大15,且实际发送位数为FMSZ+1位。另外需要注意的是该位默认上电为1111,即一次发送16位,这点需要注意。

CPOL:时钟极性设置

0——SCK空闲状态为低, 1——SCK空闲状态为高

CPHA:时钟相位设置

0 —— Data is captured on the leading edge of SCK and changed on the following edge
1 —— Data is changed on the leading edge of SCK and captured on the following edge

LSBFE:最低位先发设置

0 —— 最高为先发(MSB first),1 —— 最低位先发(LSB first)

DBR、PBR、BR:SCK时钟频率设置

f(SCK)=[f(Busclk)/PBR]*[(1+DBR)/BR],f(SCK)最大为f(Busclk)/2

PASC、CSSCK:PCS有效到SCK有效时间和SCK无效到PCS无效时间间隔设置

(3)SPIx_RSER寄存器,即中断使能寄存器。

上图画圈的两个中断使能比较常用,即发送完成中断使能(TCF_RE,每发送完一次即置位SPI_SR_TCF)和SPI队列发送完毕中断使能(EOQF_RE,即FIFO发送完毕置位SPI_SR_EOQF),其他的设置为可以看**开头的那个中断源截图所示。

(4)SPIx_PUSHER寄存器及SPI_xPOPER寄存器,即SPI的发送寄存器和接收寄存器,不过与普通的发送寄存器不同的是,该发送寄存器还包含相应的指令,实际数据位最多为16位,而接收寄存器则可以32位接收。

CTAS:主机模式下选择SPIx_CTAR[0:1]中的一个,从机模式下选择SPIx_CTAR——Slave[0:1]中一个;

EOQ:指示当前要发送的数据是否为最后要发送的数据

0 —— 指示TXDATA中的数据不是最后要发送的数据,1 —— 指示TXDATA中的数据是最后要发送的数据;

PCS[5:0]:设定在发送数据时哪个PCSx有效

00000——全部PCS无效
00001——PCS0有效
.....
11111——全部PCS有效 ;

TXDATA:要发送的数据,最多为16位,由SPI_CTAR_FMSZ决定;

RXDATA:接收的数据,最多为32位,由主机决定。

4.呼呼...一个一个的解释寄存器真够累的,不过我个人觉着这样效果会好些,所以多写了些,呵呵。下面就可以软件实现了,由于上面寄存器已经说的很明白了,所以这部分我就只贴出部分代码加注释了,多余的我就不解释了,完整代码见附件:

/**********************************************************************************
@ Routine:SPI_Init
@ Parameter:无
@ Descriptior:SPI模块初始化
**********************************************************************************/
void SPI_Init(void);

/**********************************************************************************
@ Routine:SPI_Send()
@ Parameter:jc_data 要发送的数据(可以为16bit或者8bit)
@ Descriptior:SPI数据发送(本函数兼容缓冲FIFO和不带FIFO)
**********************************************************************************/
void SPI_Send(uint8 jc_data);
/**********************************************************************************
@ Routine:SPI_Read()
@ Parameter:
            输入 jc_data 发送的数据(可以任意,只是为了提供从机相应的SCK时钟)
            输出 读取到的数据
@ Descriptior:SPI数据接收(本函数兼容缓冲FIFO和不带FIFO)
**********************************************************************************/
uint8 SPI_Read(uint8 jc_data);


相关帖子

沙发
fhguo1990|  楼主 | 2015-4-23 11:14 | 只看该作者

好长时间没写Kinetis的从零入手系列了,没想到这次毫无手生的感觉,而且还颇有些文思泉涌控制不住的感脚来,哈哈哈。不过这次真的感觉写的有点多了,估计会有人不能**看完呢,呵呵,无所谓了,反正下面贴出附件了,有兴趣的自己下下来研究研究吧。另外为了测试该模块的实现,我用FPGA编了一个SPI的从机对Kinetis的模块进行了验证,效果很好啊,呵呵,所以**最后贴个SPI的时钟图给大家瞅瞅,由于自己的示波器只有两通道的所以只显示了PCS0和SCK的相位关系,如下图所示:另外还是那句话,转载请注明俺原作者信息jicheng0622,首发于ChinaAET,谢谢支持,未完待续:


使用特权

评论回复
板凳
fhguo1990|  楼主 | 2015-4-23 11:15 | 只看该作者
附件为Kinetis SPI模块完整代码
174401165057.rar (2.23 KB)

使用特权

评论回复
地板
后会无期1| | 2015-4-23 11:16 | 只看该作者
好复杂……

使用特权

评论回复
5
后会无期1| | 2015-4-23 11:16 | 只看该作者
楼主Kinetis L系列的开始用木有,求更新!  用什么环境开发的。

使用特权

评论回复
6
fhguo1990|  楼主 | 2015-4-23 11:18 | 只看该作者
后会无期1 发表于 2015-4-23 11:16
楼主Kinetis L系列的开始用木有,求更新!  用什么环境开发的。

我用的是IAR+Jlinkhttp://blog.chinaaet.com/detail/30541.html,前段时间捣鼓了一下,觉着跟Kinetis差不多,寄存器很大程度上跟Kinetis兼容~

使用特权

评论回复
7
我思故我在12345| | 2015-4-23 11:20 | 只看该作者
有个问题想请教您,估计也只有您会了!我原本想通过FRDM-KL25板内部的SPI模块驱动一个SPI接口的液晶显示屏,但SPI通信上始终有问题!就是当我写入数据到SPI_D寄存器后SPTEF位没有清零,反而SPRF置1了,并且SPI_D寄存器中的值也不是我写入的值。后来我为什么调试SPI就让KL25内部的两个SPI模块一个作主机,一个作从机进行通信,还是出现如上所述问题。实在不得其解!期间在论坛里发过几个贴子,FAE也都作出了回答,并给出了例程,FAE都说我的代码没有问题,并且我也按照他们给出的例程进行了修改,还是一样的情况!已经快一个星期了,实在纠结!我把几个文件上传给您,您帮我看一下,我个人觉得像是我的FRDM-KL25板有问题了(很荒谬)!
期盼您的回复,祝工作顺利!
void SPI0_Init(void)
{
  SIM_SCGC4 |= SIM_SCGC4_SPI0_MASK;                     // Enable SPI0 clock gating
  SIM_SCGC5 |= SIM_SCGC5_PORTC_MASK;                    // Enable PORTC Module
  
  PORTC_PCR4 = PORT_PCR_MUX(2);          // PTD0 as SPI0_PCS0
  PORTC_PCR5 = PORT_PCR_MUX(2);          // PTD1 as SPI0_SCK
  PORTC_PCR6 = PORT_PCR_MUX(2);          // PTD2 as SPI0_SOUT
  PORTC_PCR7 = PORT_PCR_MUX(2) | PORT_PCR_PE_MASK | PORT_PCR_PS_MASK;          // PTD3 as SPI0_SIN
  
  SPI0_BR = SPI_BR_SPR(2) | SPI_BR_SPPR(2);             // SPI0 baudrate = BUSCLOCK/(SPPR+1)*2^(SPR+1) = 1MHz
  
  SPI0_C1 = SPI_C1_SPE_MASK
          | SPI_C1_MSTR_MASK
          | SPI_C1_SSOE_MASK;
  
  SPI0_C2 = SPI_C2_MODFEN_MASK;
}
void SPI0_WriteByte( unsigned char dat )
{
    while (!(SPI0_S & SPI_S_SPTEF_MASK));
    SPI0_D = dat;
}
void SPI1_Init(void)
{
  SIM_SCGC4 |= SIM_SCGC4_SPI1_MASK;                     // Enable SPI0 clock gating
  SIM_SCGC5 |= SIM_SCGC5_PORTE_MASK;                    // Enable PORTC Module
  
  PORTE_PCR1 = PORT_PCR_MUX(2);          // PTE1 as SPI1_MOSI
  PORTE_PCR2 = PORT_PCR_MUX(2);          // PTE2 as SPI1_SCK
  PORTE_PCR3 = PORT_PCR_MUX(2);          // PTE3 as SPI1_MISO
  PORTE_PCR4 = PORT_PCR_MUX(2);          // PTE4 as SPI1_PCS0
  
  //SPI1_BR = SPI_BR_SPR(2) | SPI_BR_SPPR(2);             // SPI0 baudrate = BUSCLOCK/(SPPR+1)*2^(SPR+1) = 1MHz
  
  SPI1_C1 = SPI_C1_SPE_MASK
          & (~SPI_C1_CPHA_MASK);        // SPI0 in master mode,enable SPI0
}

void SPI1_ReadByte( unsigned char *result )
{
  while( !(SPI0_S & SPI_S_SPRF_MASK) );
    *result = SPI1_D;
}

使用特权

评论回复
8
我思故我在12345| | 2015-4-23 11:20 | 只看该作者

  • 怎么无法插入附件啊!
    祝君工作顺利!


使用特权

评论回复
9
fhguo1990|  楼主 | 2015-4-23 11:21 | 只看该作者
我思故我在12345 发表于 2015-4-23 11:20

  • 怎么无法插入附件啊!祝君工作顺利!

  • 你好,
    我看了你的代码,发现个问题,如下:
    SIM_SCGC5 |= SIM_SCGC5_PORTC_MASK;  // Enable PORTC Module
      PORTC_PCR4 = PORT_PCR_MUX(2);          // PTD0 as SPI0_PCS0
      PORTC_PCR5 = PORT_PCR_MUX(2);          // PTD1 as SPI0_SCK
      PORTC_PCR6 = PORT_PCR_MUX(2);          // PTD2 as SPI0_SOUT
      PORTC_PCR7 = PORT_PCR_MUX(2);          // PTD3 as SPI0_SIN

    你使能了PORTC的SPI0,但是你注释上却是用PORTD的SPI0,有点矛盾。是你注释的错误还是你真的是这么连接的?

    使用特权

    评论回复
    10
    我思故我在12345| | 2015-4-23 11:25 | 只看该作者
    fhguo1990 发表于 2015-4-23 11:21
    你好,
    我看了你的代码,发现个问题,如下:
    SIM_SCGC5 |= SIM_SCGC5_PORTC_MASK;  // Enable PORTC Mod ...

    是按照代码进行连接的,因为之前是用PORTD端口试的,后面的注释忘记改了!

    使用特权

    评论回复
    11
    我思故我在12345| | 2015-4-23 11:25 | 只看该作者
    fhguo1990 发表于 2015-4-23 11:21
    你好,
    我看了你的代码,发现个问题,如下:
    SIM_SCGC5 |= SIM_SCGC5_PORTC_MASK;  // Enable PORTC Mod ...

    你用我的那个工程试一下你的FRDM-KL25板看能不能正常通信,真怀疑是我的板子坏了!

    使用特权

    评论回复
    12
    fhguo1990|  楼主 | 2015-4-23 11:30 | 只看该作者
    我思故我在12345 发表于 2015-4-23 11:25
    你用我的那个工程试一下你的FRDM-KL25板看能不能正常通信,真怀疑是我的板子坏了! ...

    你先把SPI的波特率降到500K再试一下~

    使用特权

    评论回复
    13
    我思故我在12345| | 2015-4-23 11:33 | 只看该作者
    fhguo1990 发表于 2015-4-23 11:30
    你先把SPI的波特率降到500K再试一下~

    还是不行,我将速度调到100k以下了!

    使用特权

    评论回复
    14
    我是MT| | 2015-4-23 11:34 | 只看该作者
    本帖最后由 我是MT 于 2015-4-23 11:37 编辑

    • 能帮我看下SPI0的代码吗
      今天调了半天,收到的数据老是0,可以发送数据,不知道我接收那里设置错了,帮我看下可以吗,我把SIN口设置成高电平了。

      #define AD7687_SPI SPI0
      #define AD7687_SIN PTA
      #define AD7687_SIN_PORT PORTA
      #define AD7687_SIN_Pin GPIO_Pin_17
      #define AD7687_SOUT PTA
      #define AD7687_SOUT_PORT PORTA
      #define AD7687_SOUT_Pin GPIO_Pin_16
      #define AD7687_SCK PTA
      #define AD7687_SCK_PORT PORTA
      #define AD7687_SCK_Pin GPIO_Pin_15

      void AD7687_SendWord (u16 data)
      {
          AD7687_SPI->SR |= SPI_SR_EOQF_MASK;
          AD7687_SPI->MCR &= (~SPI_MCR_HALT_MASK) & (~SPI_MCR_MDIS_MASK);   /* 进入RUNNING状态 */
         AD7687_SPI->PUSHR = SPI_PUSHR_CTAS(0) | SPI_PUSHR_EOQ_MASK | SPI_PUSHR_CTCNT_MASK | data;
                                                                                    /* 写入数据并清空计数寄存器 */
                                                                                /* 置位EOQ表示该数据是队列中最后一个,即使能发送 */
          while((AD7687_SPI->SR & SPI_SR_TCF_MASK) == 0);  
        AD7687_SPI->SR |= SPI_SR_TCF_MASK;
          AD7687_SPI->MCR |= SPI_MCR_CLR_TXF_MASK | SPI_MCR_CLR_RXF_MASK;   /* 清空队列(这句很重要) */

      u16 AD7687_ReceiveWord(u16 data)

          u16 dat;  
      AD7687_SendWord(data);
        STK_delay10ms (1);
          dat = (u16)AD7687_SPI->POPR;
      AD7687_SPI->MCR |= SPI_MCR_CLR_TXF_MASK | SPI_MCR_CLR_RXF_MASK;
          return dat;

      static void SPI_Configuration (void){
         SIM->SCGC6 |= SIM_SCGC6_DSPI0_MASK;                                      // 使能SPI0时钟
          AD7687_SIN_PORT->PCR[AD7687_SIN_Pin] = PORT_PCR_MUX(2) | PORT_PCR_PE_MASK;//SPI0_SIN
        AD7687_SOUT_PORT->PCR[AD7687_SOUT_Pin] = PORT_PCR_MUX(2) | PORT_PCR_PE_MASK | PORT_PCR_PS_MASK;//SPI0_SOUT
          AD7687_SCK_PORT->PCR[AD7687_SCK_Pin] = PORT_PCR_MUX(2) | PORT_PCR_PE_MASK | PORT_PCR_PS_MASK;//SPI0_SCK
         /* SPI MCR模块配置寄存器初始化 */
          AD7687_SPI->MCR &= ~SPI_MCR_MDIS_MASK;   
    •     AD7687_SPI->MCR |= (SPI_MCR_MSTR_MASK)
                              //+ SPI_MCR_PCSIS(0x1F)
                           + (SPI_MCR_DIS_TXF_MASK)
                            + (SPI_MCR_DIS_RXF_MASK);
         /* SPI CTAR0时钟及发送器属性寄存器初始化 */
          AD7687_SPI->CTAR[0]  = SPI_CTAR_FMSZ(fmsz);                        // 注意这句必须是“=”号,因为fmsz默认是1111
         AD7687_SPI->CTAR[0] |= SPI_CTAR_CPOL_MASK
                                  + SPI_CTAR_CPHA_MASK;
          /* SCK波特率分频 f(SCK)=[f(Busclk)/PBR]*[(1+DBR)/BR],f(SCK)最大为f(Busclk)/2 */
          AD7687_SPI->CTAR[0] |= SPI_CTAR_BR(10)                              // BR=0,PBR=0,SCK波特率最大为f(Busclk/2)     
                                  + SPI_CTAR_PBR(0);
      }






    使用特权

    评论回复
    15
    我是MT| | 2015-4-23 11:38 | 只看该作者
    我没有使用CS功能。

    使用特权

    评论回复
    16
    我是MT| | 2015-4-23 11:38 | 只看该作者
    #define mstr           1UL            /* SPI工作模式选择: 0——从机, 1——主机 */
    #define Dis_TXF        1UL            /* SPI发送FIFO禁能:0——使能,1——禁能 */
    #define Dis_RXF        1UL            /* SPI接收FIFO禁能:0——使能,1——禁能 */
    #define fmsz           15UL            /* SPI发送数据位设定,实际数据位为该值+1,最小为3 */
    #define pcsx           0UL            /* 设定在发送数据时哪个PCSx有效
                                              00000——全部PCS无效
                                              00001——PCS0有效
                                              .....
                                              11111——全部PCS有效  
                                          */
    #define cpol           0UL            /* SPI时钟极性设定:0——SCK空闲状态为低, 1——SCK空闲状态为高 */
    #define cpha           0UL            /* SPI时钟极性设定:
                                              0——Data is captured on the leading edge of SCK and changed on the following edge
                                              1——Data is changed on the leading edge of SCK and captured on the following edge
                                          */

    使用特权

    评论回复
    17
    fhguo1990|  楼主 | 2015-4-23 11:39 | 只看该作者
    我是MT 发表于 2015-4-23 11:38
    #define mstr           1UL            /* SPI工作模式选择: 0——从机, 1——主机 */
    #define Dis_TXF    ...

    你是跟片外ADC的SPI通信是吗,这个我以前调过AD7714,调了半天也是发现不成功,最后发现是SPI时序极性错了,你可以确定一下极性问题。另外关于代码,这个SPI模块我写了好长时间了,忘记了好多,你可以直接参考**中我上传的代码,这个是可以用的。

    使用特权

    评论回复
    18
    我是MT| | 2015-4-23 11:40 | 只看该作者
    fhguo1990 发表于 2015-4-23 11:39
    你是跟片外ADC的SPI通信是吗,这个我以前调过AD7714,调了半天也是发现不成功,最后发现是SPI时序极性错 ...

    学习啦,谢谢

    使用特权

    评论回复
    19
    后会无期1| | 2015-4-23 11:41 | 只看该作者
    我有个问题想请教你,我现在想通过DMA将数据传输到K60的SPI的数据寄存器,我是通过SPI的TFFF标志触发DMA的,源地址为一个数组,目的地址为SPI0的数据寄存器,K60的SPI0的数据寄存器PUSHR在发送的时候只能发送16位,前面还要加上命令(拉低哪一根片选等等),我在测试的时候发现DMA已经被触发(通过两块内存的数据拷贝成功),但是从数组传输到SPI0的数据寄存器移植发送不过去,我在想是不是因为在向数据寄存器PUSHR写数据时需要带上高16字节的命令所以导致主机没有拉低从机的片选,我又进行测试,发现如果先写入PUSHR寄存器拉低片选的话,就已经发送了一组空数据过去,在写PUSHR寄存器就会触发数据发送。想请教下使用DMA的话如何先拉低片选再进行DMA的数据传输,才能保证数据全部被发送出去?

    使用特权

    评论回复
    20
    fhguo1990|  楼主 | 2015-4-23 11:42 | 只看该作者
    后会无期1 发表于 2015-4-23 11:41
    我有个问题想请教你,我现在想通过DMA将数据传输到K60的SPI的数据寄存器,我是通过SPI的TFFF标志触发DMA的 ...

    关于SPI的DMA我之前没有使用过,不过我看了下,我感觉是需要每次都是命令+数据一块发送,也就是活你的数组得是32位的,每个元素都包含指令和数据,然后再一块发出去。

    使用特权

    评论回复
    发新帖 我要提问
    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    23

    主题

    254

    帖子

    2

    粉丝