声明:本文为原创作品,版权归本博文作者所有,如需转载,请注明出处http://www.cnblogs.com/kingst/
这一节,我们来讲讲NIOS II中的SPI总线的用法。首先,我们来简单介绍一下SPI总线吧,SPI是英文Serial Peripheral Interface的缩写,中文意思是串行外围设备接口,是Motorola公司推出的一种同步串行通讯方式,是一种四线同步总线,因其硬件功能很强,与SPI有关的软件就相当简单,使CPU有更多的时间处理其他事务。
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(用于单向传输时,也就是半双工方式)。也是所有基于SPI的设备共有的,它们是MISO(主入从出),MOSI(主出从入),SCK(时钟),CS(片选)。
(1)MISO – 主设备数据输出,从设备数据输入
(2)MOSI – 主设备数据输入,从设备数据输出
(3)SCK – 时钟信号,由主设备产生
(4)CS – 从设备使能信号,由主设备控制
其中CS是控制芯片是否被选中的,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),对此芯片的操作才有效。这就允许在同一总线上连接多个SPI设备成为可能。
SPI总线的理论知识就介绍这么多,想要看具体点的去网上百度一下吧。下面我们就开始SPI总线的开发旅程吧。
在我们开发板中网口部分是用SPI总线实现的,网络芯片是MICROCHIP公司的ENC28J60,我们先看一下这部分的电路,如下图所示,其中与SPI总线相关的有,LAN_MISO,LAN_MOSI,LAN_SCK这三个根线,其余的都是通过PIO模块实现的。而且有些线还用不到,比如LAN_nWOL。
我们这一节主要是教大家如何来实现SPI总线的功能,对于ENC28J60的原理相对复杂,在这里我就不详细讲解了,大家有兴趣的可以自己研究一下。
下 面我们就来构建SPI模块,进入SOPC BUILDER后,我们如下图所示,点击红圈处(SPI)
点击后,如下图所示,在这里面,我们有5个地方需要注意,
红圈1处是主从模式选择,我们选择主模式(Master);
红圈2处是从设备的个数,我们选择1;
红圈3处是SPI时钟速率,我们选择10M,这个地方需要注意一下,我们设置的频率与实际的频率有时候是不一致的(下面显示的是实际频率),例如,我们输入50MHz,实际的频率只有25MHz。
红圈4处是数据的位数,我们选择8;
红圈5处是移位的方向,就是说串行数据过来时,是最高位先来还是最低位先来,我们选择MSB first。
处理好这些以后,点击Finish,完成构建。
接下来,我们还要构建两个PIO模块,一个用作CS信号控制,一个用作中断信号。之所以没有用SPI总线本身的CS,是由程序处理本身决定的。中断信号的PIO模块,构建过程需要注意一下内容,首先作为中断信号,是输入信号,所以在选择过程中,如下图所示,红圈1处选择为1,红圈2处选择Input ports only,仅作为输入端口,点击Next,进行下一步
点击后,进入下一步,如下图所示,外部中断要求电平触发,所以按红圈处选择方式。
然后,我们点击Finish,完成构建。
完成上述内容以后,我们需要对模块进行改名,如下图所示,
一切就绪,别忘了自动地址分配和中断分配。哦了,我们开始编译吧,等待……
编译好以后,我们回到Quartus界面,根据TCL脚本文件进行管脚分配,如下图所示
接下来我们运行脚本文件,进行编译,又一次漫长的等待……
编译成功以后,我们开始进行软件部分的开发
打开NIOS II 9.0 IDE,然后进行编译,快捷键Ctrl+b,等待编译成功后,我们来看看system.h中多了些什么,如下表所示,
/* * LAN configuration * */ #define LAN_NAME "/dev/LAN" #define LAN_TYPE "altera_avalon_spi" #define LAN_BASE 0x00201020 …… /* * LAN_CS configuration * */ #define LAN_CS_NAME "/dev/LAN_CS" #define LAN_CS_TYPE "altera_avalon_pio" #define LAN_CS_BASE 0x00201060 …… /* * LAN_nINT configuration * */ #define LAN_NINT_NAME "/dev/LAN_nINT" #define LAN_NINT_TYPE "altera_avalon_pio" #define LAN_NINT_BASE 0x00201070 ……
我们需要以下内容
#define LAN_BASE 0x00201020 #define LAN_CS_BASE 0x00201060 #define LAN_NINT_BASE 0x00201070
接下来,我们需要对sopc.h进行修改,在其中加入以下代码
typedef struct{ volatile unsigned long int RXDATA; volatile unsigned long int TXDATA; union{ struct{ volatile unsigned long int NC :3; volatile unsigned long int ROE :1; volatile unsigned long int TOE :1; volatile unsigned long int TMT :1; volatile unsigned long int TRDY :1; volatile unsigned long int RRDY :1; volatile unsigned long int E :1; volatile unsigned long int NC1 :23; }BITS; volatile unsigned long int WORD; }STATUS; union{ struct{ volatile unsigned long int NC :3; volatile unsigned long int IROE :1; volatile unsigned long int ITOE :1; volatile unsigned long int NC1 :1; volatile unsigned long int ITRDY :1; volatile unsigned long int IRRDY :1; volatile unsigned long int IE :1; volatile unsigned long int NC2 :1; volatile unsigned long int SSO :21; }BITS; volatile unsigned long int CONTROL; }CONTROL; unsigned long int RESERVED0; unsigned long int SLAVE_SELECT; }SPI_STR;
这部分代码是根据《n2cpu_Embedded Peripherals.pdf》的第7-10页,如下表所示,结构体的顺序是根据下表的排列顺序进行设计的,与串口中结构体的道理相同。
除了上述结构体以外,我们还要在sopc.h中加入以下代码
#ifdef _LAN #define LAN ((SPI_STR *) LAN_BASE) #define LAN_CS ((PIO_STR *) LAN_CS_BASE) #endif /*_LAN */
修改好sopc.h以后,我们需要在inc文件夹下建立一个enc28j60.h,在其中加入以下内容,(这只是enc28j60.h文件中的一部分,还有很大一部分宏定义没有写出)
/*----------------------------------------------------------- * Data Struct *----------------------------------------------------------*/ typedef const struct{ unsigned char (* read_control_register)(unsigned char address); void (* initialize)(void); void (* packet_send)(unsigned short len,unsigned char * packet); unsigned int (* packet_receive)(unsigned short maxlen,unsigned char * packet); }ENC28J60; /*---------------------------------------------------------- * external variable *----------------------------------------------------------*/ extern ENC28J60 enc28j60;
大家可以看出,在我们的程序中,这样的结构体随处可见,在之前的串口程序,还是这个SPI程序,我们都在用。它的好处就在于,可以将零散的函数和变量整合在一起,通过结构体的形式来处理,大大提高了程序的可读性,也增强了程序的可维护性和可移植性。
处理好上述内容后,我们开始编写enc28j60的驱动程序,内容很多,我们截取其中一部分有关SPI的内容来进行讲解
/* * ============================================================== * Filename: enc28j60.c * Description: enc28j60 device driver * Version: 1.0.0 * Created: 2009-8-7 13:05:54 * Revision: none * Compiler: Nios II IDE * Author: AVIC * Company: 金沙滩工作室 * * ============================================================== */ /*--------------------------------------------------------------- * Include *---------------------------------------------------------------*/ #include "../inc/enc28j60.h" #include "../inc/sopc.h" #include <stdio.h> /*-------------------------------------------------------------- * Function Prototype *-------------------------------------------------------------*/ static unsigned char enc28j60_read_control_register( unsigned char address); static void enc28j60_initialize(void); static void enc28j60_packet_send(unsigned short len, unsigned char * packet); static unsigned int enc28j60_packet_receive( unsigned short maxlen,unsigned char * packet); /*------------------------------------------------------------ * Variable *------------------------------------------------------------*/ //结构体初始化,注意初始化的写法 ENC28J60 enc28j60={ .read_control_register = enc28j60_read_control_register, .initialize = enc28j60_initialize, .packet_send = enc28j60_packet_send, .packet_receive = enc28j60_packet_receive }; static unsigned char enc28j60_bank = 1; static unsigned short next_packet_pointer; /* * === FUNCTION ============================================================== * Name: set_cs * Description: * ============================================================ */ static void set_cs(unsigned char level) { if(level) LAN_CS->DATA = 1; else LAN_CS->DATA = 0; } /* * === FUNCTION =============================================== * Name: enc28j60_write_operation * Description: ENC28J60的写操作 * ============================================================= */ static void enc28j60_write_operation(unsigned char op, unsigned char address, unsigned char data) { //首先将CS置低,CS低电平有效 set_cs(0); //首先是写命令,等待状态状态寄存器的TMT位,当该位为0,说明正在发送数据,当该位//为1时,说明发送完毕,此时寄存器为空 LAN->TXDATA = (op | (address & 0x1F)); while(!(LAN->STATUS.BITS.TMT)); //写数据,等待状态状态寄存器的TMT位,当该位为0,说明正在发送数据,当该位 //为1时,说明发送完毕,此时寄存器为空 LAN->TXDATA = data; while(!(LAN->STATUS.BITS.TMT)); //发送完毕以后,将CS置高 set_cs(1); } /* * === FUNCTION ================================================ * Name: enc28j60_read_operation * Description: ENC28J60的读操作 * ============================================================== */ static unsigned char enc28j60_read_operation(unsigned char op, unsigned char address) { unsigned char data; //首先将CS置低,CS低电平有效 set_cs(0); //首先是写命令,等待状态状态寄存器的TMT位,当该位为0,说明正在发送数据,当该位//为1时,说明发送完毕,此时寄存器为空 LAN->TXDATA = op|(address&0x1f); while(!(LAN->STATUS.BITS.TMT)); //写数据,发送0x00,0x00是个随机数,为了使能时钟,发送的数据与硬件有关系 LAN->TXDATA = 0x00; //0x00 is random number ,to enable clock while(!(LAN->STATUS.BITS.TMT)); //MAC和MII寄存器读的第一个字节是无效的,所以他们需要写两次 if(address&0x80){ LAN->TXDATA = 0x00; while(!(LAN->STATUS.BITS.TMT)); } //开始读数据 data = LAN->RXDATA; //读完以后,将CS置高 set_cs(1); return data; }
对于网口这部分程序,需要结合TCP/IP协议才能进行通信,所以,这部分主函数就暂时不写了,等讲到TCP/IP协议那部分的时候,我们再进行讲解。
我们这一节主要是讲解SPI总线的使用方法,对于数据的收发都有所涉及,希望大家能够充分的理解其中的编程方法。有关ENC28J60的完整驱动,我将以附件形式提供给大家,这一节到此结束了,如果大家对这部分内容有疑问,可以加入我们的NIOS技术群,或者通过邮件形式与我沟通,谢谢大家。