原创 【连载】NIOSII那些事儿--SPI实验 (八)

2010-8-2 00:21 5300 7 7 分类: FPGA/CPLD

黑金动力社区

黑金动力社区:http://www.heijin.org

声明:本文为原创作品,版权归本博文作者所有,如需转载,请注明出处



简介


      这一节,我们来讲讲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)


clip_image004


点击后,如下图所示,在这里面,我们有5个地方需要注意,


红圈1处是主从模式选择,我们选择主模式(Master);


红圈2处是从设备的个数,我们选择1;


红圈3处是SPI时钟速率,我们选择10M,这个地方需要注意一下,我们设置的频率与实际的频率有时候是不一致的(下面显示的是实际频率),例如,我们输入50MHz,实际的频率只有25MHz。


红圈4处是数据的位数,我们选择8;


红圈5处是移位的方向,就是说串行数据过来时,是最高位先来还是最低位先来,我们选择MSB first。


clip_image006


处理好这些以后,点击Finish,完成构建。


      接下来,我们还要构建两个PIO模块,一个用作CS信号控制,一个用作中断信号。之所以没有用SPI总线本身的CS,是由程序处理本身决定的。中断信号的PIO模块,构建过程需要注意一下内容,首先作为中断信号,是输入信号,所以在选择过程中,如下图所示,红圈1处选择为1,红圈2处选择Input ports only,仅作为输入端口,点击Next,进行下一步


clip_image008


点击后,进入下一步,如下图所示,外部中断要求电平触发,所以按红圈处选择方式。


然后,我们点击Finish,完成构建。


clip_image010


完成上述内容以后,我们需要对模块进行改名,如下图所示,


clip_image012


一切就绪,别忘了自动地址分配和中断分配。哦了,我们开始编译吧,等待……


编译好以后,我们回到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技术群,或者通过邮件形式与我沟通,谢谢大家。

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
7
关闭 站长推荐上一条 /3 下一条