2008年4月13日 星期日

NiosII PIO interrupt (轉貼)

PIO(Parallel Input/Output)是SOPCBuilder里面一个最常用的IP之一,而且相对简单,容易上手使用。个人感觉PIO可类比于单片机的GPIO管 脚,用于用户自己定义的外设操作。一般来说,我们在SOPC系统中用PIO主要是因为方便,因为FPGA的管脚越来越多,内部逻辑资源越来越多,所以设计 时几乎不用担心管脚的数目或者逻辑资源是否不够;相比之下单片机的GPIO可能会让用户感到数目不够,有时不够方便。所以在SOPC系统我们可以同时用很 多使用PIO接口的设备。大体来说,这些设备主要分输入和输出两类:

输入类:按钮,自定义键盘等
输出类:LED,LCD定义接口连线等
G oL5NggO5M*rJmB,`X1l %S hf M2C%z U,e 标准件:如pwm步进电机等1ZA8v"a"L9X_$^
[attach]42[/attach]
(gw2O-C#f8R
如上图所示,在Nios/NiosII系统中,PIO接口IP可用于输入、输出、以及双向口三种类型。另外,SOPCBuilder中的PIO还支持中断 检测。不过中断检测及处理只在其作为输入设备时可用。如果是用PIO连接一个输出设备(例如LED),则系统自动禁止中断。
L]C!L;F fTp8^3PNV
K@?J8q
这里只举一个button_pio(4位)作为输入接口的例子,如下图:

可以看到,作为输入接口时,另外两个标签项也可选,先看看第二个标签项(作输出接口时另外两个标签选项是自动禁止的):
]5zhw uDS%lZiQ
Edge Capture Register下面的三个选项是指捕获输入动作时是检测哪一种,用户可以选择只检测上升沿(Rising Edge),这样PIO内部的Edge Capture Register只在有上升沿动作时接收数据。同样道理,另外的两个选项一看便知。对于Button,我们一般倾向于选双向沿均可监测到数据变化。
下面的Interrupt选项是指用户选择以什么方式作为中断的输入,一种是电平变化产生中断,一种是沿的变化产生中断。第一种方式只有在输入由低电平变 化为高电平时才有效(不过用户可以根据需要,在做好的Nios模块外面的相应管脚上加上非门,使之由高电平变电平时生效);第二种方式只有当Edge Capture Register位是1(高电平)时才有用。
BEH+FZ^d-T(XYx h

再来看一看第三个标签下面的东东:
J%b4`/?(I{l$p-V KS9z }@p$L ~ZZ _'[4[
/]S3QRm&} C
这个标签下面的东西是用于仿真的。例如如果我们在测试输入设备时想给他一个激励,这里我用的是0x0001。什么意思呢?即表示最低位的button位为 高电平1。如果我用的激励数据是0x0003,这又是什么意思呢?聪明的你一定会发现这表示激励使低两位的button位为1,呵呵。
9\+w(pu9F.G0Q

下面我们来分析一个SOPCBuilder生成的HDL文件:
n8Yo`/{4^Q+| S2hl#| yv,eW1K;[ p3p
module led_pio (
//inputs:
address,
S:e:|a*M,ET%Bl D7x W i2dh'X {!{Z5I%?"sI
chipselect,
+C,d9B2Y,r)YY
clk,
mT?+B{
reset_n,
!U*O:u%@v i"^
write_n,
r GS'M?:s
writedata,
n&?VUf H //outputs:
out_port
);

output [ 3: 0] out_port;

wire clk_en;
reg [ 3: 0] data_out;
wire [ 3: 0] out_port;
ap9bwMQW6s({)nc,@Lf,C'AJ'a c,Bn*W&f *p%wt$xi }'l` Zn5~U#T {;g5\:St-ZT O(N(U9N:lV5y:| EO,_%EOh]4K
assign clk_en = 1;
//s1, which is an e_avalon_slave
always @(posedge clk or negedge reset_n)begin
5y.sV.j;Jt%O~.Y2`:^te.W#Zw Ude E{a(W TngY N&vVFi$q(S |
if (reset_n == 0)
data_out <= 0;
else if (chipselect && ~write_n && (address = = 0))
data_out <= writedata[3 : 0];
end

assign out_port = data_out;
endmodule

你一定立刻注意到这段描述的输入端口怎么有点似曾相识?没错,avalon总线的要求就是这样!回忆一下在SOPCBuilder下加入用户自定义逻辑的 要求是不是必须得有这几个端口?呵呵。上面的代码并不难看懂,大家可以看到led_pio是如何被avalon总线驱动的。

关于PIO的软件编程,NiosII的IDE的样板工程里有很多程序可以参考,这里我就不赘述了,只是还想捎带介绍一下altera_avalon_pio_regs.h和另外一个细节。
N)I/f BG.A1Ei8K2kFV)^*D8a~ ov n2Tq(n9^5s"?Pr f$k8R} k.mA;^^)@ VM!I?J~5w SmM.h(u 9Q lr'hp6O^DH]V5n%_W!jM3g

这个头文件比较奇怪,它和其它设备的驱动文件独立开来,专门控制PIO设备。所以一定要记住在你对PIO外设进行编程时一定要包含这个头文件,否则会出错。
Z+R Y$B C p5AS"MG(r]ol{]5V

include
T/BUK|`t%m G!` E3re8?"q @

#define IOADDR_ALTERA_AVALON_PIO_DATA(base) __IO_CALC_ADDRESS_NATIVE(base, 0)
#define IORD_ALTERA_AVALON_PIO_DATA(base) IORD(base, 0)
9Pj3Z!Gd+bcV S U2V9o }4\Uvs ar v3E#JA^al
#define IOWR_ALTERA_AVALON_PIO_DATA(base, data) IOWR(base, 0, data)
B3Cg&_ L"ns%J{*q FQ(QHFC$}:c$P@
#define IOADDR_ALTERA_AVALON_PIO_DIRECTION(base) __IO_CALC_ADDRESS_NATIVE(base, 1)
#define IORD_ALTERA_AVALON_PIO_DIRECTION(base) IORD(base, 1)
#define IOWR_ALTERA_AVALON_PIO_DIRECTION(base, data) IOWR(base, 1, data)

#define IOADDR_ALTERA_AVALON_PIO_IRQ_MASK(base) __IO_CALC_ADDRESS_NATIVE(base, 2)
#define IORD_ALTERA_AVALON_PIO_IRQ_MASK(base) IORD(base, 2)
JH ^b f&sK#r"j9gh a-o uJh4Vo7?$? k h%|KJ"Y s6y5v i\*R1h8f9oD)l1u9SB
#define IOWR_ALTERA_AVALON_PIO_IRQ_MASK(base, data) IOWR(base, 2, data)

#define IOADDR_ALTERA_AVALON_PIO_EDGE_CAP(base) __IO_CALC_ADDRESS_NATIVE(base, 3)
#define IORD_ALTERA_AVALON_PIO_EDGE_CAP(base) IORD(base, 3)
%f#AD3p v:xY `*S$q s"J0W"l`!mS"@.RDp icxn U WOt U5s|
#define IOWR_ALTERA_AVALON_PIO_EDGE_CAP(base, data) IOWR(base, 3, data)

可以看出,这里主要有DATA、DIRECTION、IRQ_MASK、EDGE_CAP几个寄存器或位的宏定义,而你可以从SOPCBuilder生成 的文件中找到相对应的硬件定义。这也说明了PIO实际上主要是由以上几个寄存器控制工作。因此,软件中只要对相应的寄存器针对基地址进行操作写入数据就可 以了。
1Z(D)[ ['PW-HUH 1|$|"p

再来看看另一个值得注意的细节。我们可以从样板工程里发现类似于这样的语句:
D

volatile int edge_capture_button;
!m'Fd| x9?&f+p
#ifdef BUTTON_PIO_BASE
_(} i*}?0o5Qh4oF
void handle_button_interrupts(void* context, alt_u32 id)
{
&w!~ hO0yP(Nsj2~X D
volatile int* edge_capture_ptr = (volatile int*) context;
?,@PrK(?Z
*edge_capture_ptr = IORD_ALTERA_AVALON_PIO_EDGE_CAP(BUTTON_PIO_BASE);
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(BUTTON_PIO_BASE, 0);
Q:o#L,J Q:\mia.~7OtD:nv S;i,f
}

void init_button_pio()
3iKf'QK~;h"^ h,N)YUPF0ff3J3^P(h A(E'n o9]S
{
{!DU7dEu
void* edge_capture_ptr = (void*) &edge_capture_button;
ZQg N IV
IOWR_ALTERA_AVALON_PIO_IRQ_MASK(BUTTON_PIO_BASE, 0xf);
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(BUTTON_PIO_BASE, 0x0);
aV']Tl6Z |t/m-v%M1rA;T^ ~!w{
alt_irq_register( BUTTON_PIO_IRQ, edge_capture_ptr, handle_button_interrupts );
5B Cv%?5t%IA
}
#endif

第一个函数是中断处理,这里暂不介绍。第二个函数是button的初始化函数。我们可以看到主要是三个步骤:将IRQ_MASK置为1(因为这个寄存器位 在相应的硬件文件里要取and操作),EDGE_CAP则置为0,因为此时并没有输入设备的沿状态发生变化,因此表示按钮尚未按下;第三步则是中断注册, 只要按照软件开发手册上说的去做就好了,不用在乎细节也是可以的,只要注册完毕,你的PIO外设就可以接收外部的输入啦,然后Nios会自动跳到相应的处 理程序。

"?eI;\7X&X*AZ)qH d6m2d#k8a vM9x%Xj;Q sb vgFS)ZdR gL 9DJ

沒有留言: