前段时间完成了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);
|