zoukankan      html  css  js  c++  java
  • 【连载】【FPGA黑金开发板】NIOS II那些事儿SPI实验 (八)

    声明:本文为原创作品,版权归本博文作者所有,如需转载,请注明出处http://www.cnblogs.com/kingst/

     fpga

    简介

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

    clip_image002

    我们这一节主要是教大家如何来实现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脚本文件进行管脚分配,如下图所示

    clip_image014

    接下来我们运行脚本文件,进行编译,又一次漫长的等待……

    编译成功以后,我们开始进行软件部分的开发

    软件开发

          打开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页,如下表所示,结构体的顺序是根据下表的排列顺序进行设计的,与串口中结构体的道理相同。

    clip_image016

    除了上述结构体以外,我们还要在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技术群,或者通过邮件形式与我沟通,谢谢大家。

  • 相关阅读:
    光线投射算法与光线跟踪算法
    体绘制(Volume Rendering)概述之4:光线投射算法(Ray Casting)实现流程和代码(基于CPU的实现)
    体绘制(Volume Rendering)概述之3:光线投射算法(Ray Casting)原理和注意要点(强烈推荐呀,讲的很好)
    PCL学习笔记二:Registration (ICP算法)
    局部坐标系和全局坐标系
    Kinect for Windows SDK开发入门(十九):Kinect Fusion
    谈谈论文级别
    在Linux中搭建一个FTP服务器
    Java 编程实践
    Oracle数据库查询语句
  • 原文地址:https://www.cnblogs.com/kingst/p/1705550.html
Copyright © 2011-2022 走看看