一、建立三个文件夹Main,inc,driver,分别存放main.c,自建.h头文件,驱动.c文件
二、sopc.h程序解析
#ifndef SOPC_H_ #define SOPC_H_ /*----------------------------------------------------------------------------- * Include *-----------------------------------------------------------------------------*/ #include "system.h" /*----------------------------------------------------------------------------- * Define *-----------------------------------------------------------------------------*/ #define _PIO_LEDR /*----------------------------------------------------------------------------- * Peripheral registers structures *-----------------------------------------------------------------------------*/ typedef struct { unsigned long int DATA; unsigned long int DIRECTION; unsigned long int INTERRUPT_MASK; unsigned long int EDGE_CAPTURE; }PIO_STR; /*----------------------------------------------------------------------------- * Peripheral declaration *-----------------------------------------------------------------------------*/ #ifdef _PIO_LEDR #define PIO_LEDR ((PIO_STR *) PIO_LEDR_BASE) #endif /*_LED*/ #endif /*SOPC_H_*/
这个表格就是PIO Core寄存器映射,我的结构体就是根据它来写的,名字不重要,重要的是它的顺序,也就是偏移量(offset)。第一项是数据data,第二项是IO口方向,第三项是中断控制位,第四项是边沿控制位。他们每一项都有n位(共32位),这个n就是我们在软核构建中的宽度(width),如18个LED,则width=18.
接下来,我们来讲红框3中的代码,这部分代码中你一定记得PIO_LED_BASE,对,这就是我之前在system.h中特意强调的,PIO_LED的基地址。那我红圈3中的代码意图就很明显了,定义了一个宏,命名为LED,它是指向PIO_LED_BASE的结构体指针,这个结构体就是PIO_STR(如果大家对结构体指针不理解,那你就得回去看C语言书了,我在这就不具体说了)。红圈1的代码也很简单,是为了控制红圈3的定义的,这样做是为了增强代码的严谨性和可控制性。当你没有定义PIO_LED_BASE时,你就可以将红圈1中的宏定义#define _LED去。
大家理解了sopc.h代码以后,我们就可以进行C代码编程了。忘了说了,在NIOS II IDE中一定要记得修改以后保存,它很幼稚的,编译前不会提醒你去保存的,如果你忘记了保存,相当于你没有修改。
三、main.c程序解析/*----------------------------------------------------------------------- * Include *---------------------------------------------------------------------*/ #include "../inc/sopc.h" #include <stdio.h> #include <unistd.h> /* * === FUNCTION ======================================================== * Name: main * Description: * ======================================================================= */ int main(void) { int i; while(1){ for(i=0;i<18;i++){ PIO_LEDR->DATA = 1 << i; usleep(100000); } } return 0; }
首先说红框1,有些人可能没用过这种方式,这是一种相对路径的方式调用头文件,好处就是可移植性好,不管工程放到其他什么地方,这个头文件和c文件的相对位置都不会变,也就不需要对这个地方进行修改。再说红框2处的代码,关键是LED->DATA = 1<<i;LED大家应该记得,LED是我们在sopc.h中定义的宏,是结构体指针,那么LED->DATA是什么意思大家就应该很清楚了,是它的结构体中DATA的内容(不是地址)。LED->DATA = 1<<i;就是对LED->DATA的四个位循环置1。所以,4个LED就会按顺序闪烁了。usleep是微妙级延时,我们在这里每次延时0.5ms。
接下来,我们总结一下这节内容。我们来对比一下寄存器操作方式与API之间有什么联系和不同,上面的程序,如果用NIOS II IDE提供的API来写,那么如下图所示
IOWR_ALTERA_AVALON_PIO是一个宏,在altera_avalon_pio_regs.h中,其定义如下
(大家可以按住ctrl键后,用鼠标点击进入定义所在的位置),大家可以看到,它是一个IOWR的宏,而IOWR的具体写法我就在此不详细说了(大家感兴趣的可以去NIOS的源码),反正就是对硬件地址的控制。我的做法就是绕过这个大圈子,直接去控制它的寄存器。
大家可能可能有点纳闷,我们的机构体中定义了四个变量,但只用了DATA一个,在这说明一下原因,首先是DIRECTION,这个是IO的方向,就是说是输入还是输出,或者是双向的。因为在我们构建PIO模块的构成中有了一个选项,我们选择了输出(Output ports only),也就是我们在底层就已经固定了它的方向,所以在软件中就不需要在定义了。还有两个变量是涉及到中断时才会用到,所以在这个程序中也没有用到。
这一节是以后内容的基础,希望大家好好的理解,尤其是sopc.h中定义的那个结构体,大家一定要理解清楚。以后,还会涉及到UART,SPI等,都需要建立结构体,这个都是触类旁通,举一反三的东西,弄清楚一个,其他的都好理解了。
我要提醒大家一句,我虽然提倡大家用操作寄存器方式编程,但并不希望所有的程序都按这种方式来写,比如说对flash的操作,我们就可以使用API来写,因为flash的操作相对复杂,而利用API可以很简单的几个语句就能完成,没必要自己来写。