jicheng0622

【技术分享】【原创】从零入手Kinetis系统开发(十二)之SPI模块

0
阅读(13646) 评论(33)

    呼。。。真是太久没有更新从零入手系列了,掐指一算距离上次更新(第十一篇Flexbus)已经足足六个月有余了。正印证了信息时代更新发展迅猛的那句话(这句话在移动领域感触尤其深刻,今早不小心看到自己半年前花1200买的手机现在都不到600了,刺激死我了,哎),这大半年期间,Kinetis的资料从当初的凤毛麟角到现在的百花争放,各种开源资料层出不穷,各种牛人大展身手。可以说各大网站开发板、开源软件包及开发教程的出现极大的降低了开发者们的入手门槛(怎么评价飞思卡尔的产品呢,一般是入手比较难,等熟悉了就感觉好用,又爱又恨的感觉啊有木有,呵呵),很多人都从中受益,so,此时再写从零入手系列已感觉作用不是那么明显了,不过我不喜欢半途而废的感觉,还是习惯坚持把它写完,一方面把它保存下来作为自己的学习笔记,另一方面肯定还是会对一些人有些帮助的,所以何乐为不为呢,哈哈~

    前段时间完成了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框图比较粗,只能凑合着看了,呵呵:

QQ截图20121228143940

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 —— 主机作为输出同步时钟,从机则作为输入接收同步时钟。

中断源:

QQ截图20121228145218

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

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

QQ截图20121228153946

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:

QQ截图20121228155959

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寄存器,即中断使能寄存器。

QQ截图20121228161221

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

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

QQ截图20121228162041

QQ截图20121228162339

 

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

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

IMG_20121227_162431

附件为Kinetis SPI模块完整代码,呵呵,欢迎投票,谢谢~

  1. 学习,多多分享

  2. 学习  学习

  3. 学习了!!!!!!求附件

  4. 附件在哪儿?急求楼楼谢谢

  5. 不过附件在哪里呢

  6. 学习了!!

  7. 学习了!!

  8. 学习了!!

  9. 给力!!!

  10. @匿名用户(172.30.68.20 )
    这个是我工程配置文件,不过与这个SPI关系不大。
  11. 匿名用户匿名用户
    你好, 请问从哪里可以找到Kinetis_Config.h文件?
  12. @冰水混合物
    Hi 不好意思,最近比较忙,才回复你,关于SPI的DMA我之前没有使用过,不过我看了下,我感觉是需要每次都是命令+数据一块发送,也就是活你的数组得是32位的,每个元素都包含指令和数据,然后再一块发出去。
  13. 刚刚没有登录,重新发下:

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

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