黑金动力社区: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)
点击后,如下图所示,在这里面,我们有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技术群,或者通过邮件形式与我沟通,谢谢大家。
文章评论(0条评论)
登录后参与讨论