FPGA 串口中断_正点原子开拓者 Nios II资料连载第五章串口IP核
1)实验平台:正点原子开拓者FPGA 开发板2)摘自《开拓者 Nios II开发指南》关注官方微信号公众号,获取更多资料:正点原子3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/index.html第五章串口IP核我们在让 Altera FPGA 上的嵌入式系统和外部设备进行通信方式的时候,通常会用到拥有Avalon 接口的通用异步收发传输器——UAR
1)实验平台:正点原子开拓者FPGA 开发板
2)摘自《开拓者 Nios II开发指南》关注官方微信号公众号,获取更多资料:正点原子
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/index.html
第五章串口IP核
我们在让 Altera FPGA 上的嵌入式系统和外部设备进行通信方式的时候,通常会用到拥有
Avalon 接口的通用异步收发传输器——UART IP 核。UART 核实现了 RS-232 通讯协议,并使得
大家可以设置串口通信相关的波特率、奇偶校验位、停止位和数据位,以及可选的 RTS/CTS 流
控制信号等参数。
本章包括以下几个部分:
5.1 简介
5.2 实验任务
5.3 硬件设计
5.4 软件设计
5.5 下载验证
简介
UART 核提供了一个 Avalon 存储器映射(Avalon-MM)的接口,这个接口使得 Avalon-MM
的主要周边设备(例如 Nios II 处理器)通过读和写数据、控制寄存器,就能实现和 UART 核通
信的任务。所以在本章的简介部分,我们会对常用的寄存器进行详细的介绍。
图 5.1.1 一个典型系统中的UART核
如图 5.1.1 所示为一个典型系统中的 UART 核,它有两个用户可见的部分:
⚫ 可通过 Avalon-MM 从接口访问的寄存器
⚫ RXD, TXD, CTS, and RTS 等 RS-232 接口信号
图中可以看到,在 RXD 端口与 rxdata 寄存器之间、TXD 与 txdata 寄存器之间存在着移位
寄存器。
RS232 的工作原理可以在前面的 verilog 文档部分查阅。在 Qsys 篇,我们只讲解 UART 核
的工作原理,以及该核的使用方法。前面提到,我们是通过读、写相关的寄存器,实现的串口
通信功能。那么接下来,我们将着重讲解主要的寄存器——状态寄存器、控制寄存器、数据寄
存器。其中状态、控制寄存器与配置 IRQ(中断请求)相关,需要大家掌握它们的使用方法和
工作原理。
寄存器相关简介
下图为 UART 核的寄存器信息表,其中数据寄存器(rxdata、
txdata)、状态寄存器(status)、
控制寄存器(control)是重点,括号部分的注释内容在表的下部。
图 5.1.2 UART核寄存器
(1)这些位可能不存在,取决于数据位宽的硬件选项。如果它们不存在,读出的值为 0。且如果对它
们进行写操作,则没有意义。
(2)给 status(状态)寄存器写 0 将清零 dcts, e, toe, roe, brk, fe, and pe 等位。
(3)这些寄存器可能不存在,取决于硬件配置选项。如果寄存器不存在,对它进行读操作会返回未定
义的值,且进行写操作则无意义。
rxdata 寄存器
rxdata 寄存器用于存储 RXD 输入引脚接收的数据。当一个新的数据被 RXD 输入引脚完全
接受后,会被传输并存储到 rxdata 寄存器,此时 status 寄存器(状态寄存器)的 rrdy 位会被置
1。当 rxdata 寄存器中的值被读了之后,
status 寄存器中的 rrdy 位会被清零。当 rrdy 位为 1 时,
又有一个新的字符传输给 rxdata 寄存器,则会产生溢出错误,状态寄存器的 ROE 位被置 1。
不管前一个字符是否被读出,新接收到的字符总是会被自动保存到 rxdata 寄存器。另外,对
rxdata 寄存器进行写操作无意义。
txdata 寄存器
Avalon 主控制器把要发送的字符写入到 txdata 寄存器中。当一个字符写入 txdata 寄存时,
状态寄存器(status 寄存器)的 TRDY 位会被置 0;当字符从 txdata 寄存器传输到发送移位寄
存器时,状态寄存器(status 寄存器)的 TRDY 位被置为 1。当 TRDY 位为 0 时,将字符写入
txdata 寄存器的结果是未定义的。读 txdata 返回未定义的值。
status 寄存器(状态寄存器)
状态寄存器(status 寄存器)由可以反应 UART 核状态情况的独立位组成。每个状态位和
对应的控制寄存器中,能使能中断的位相联系。任何时候都可以读取状态寄存器。且读操作不
会改变寄存器任何位的值。给状态寄存器写 0 ,会清零 DCTS、E、TOE、ROE、BRK、FE 和 PE
位。状态寄存器的位相关信息在下表中列出:
图 5.1.3 状态寄存器
Control 寄存器(控制寄存器)
控制寄存器由独立的各个位构成,每个位控制 UART 核操作的一个方面。我们在任何时间
都可以读控制寄存器。
每一个控制寄存器中与状态寄存器对应的位,都可以使能一个 IRQ。当控制寄存器中与状
态寄存器对应的位的值都为 1 时,就会触发一个 IRQ。
图 5.1.4 控制寄存器
divisor 寄存器(可选)
divisor 寄存器中的值是用来产生波特率时钟的。有效的波特率由这个公式决定:
波特率=(时钟频率)/(divisor+1)
divisor 寄存器是一种可选的硬件功能。如果没有使能 Baud Rate Can Be Changed By Software
(波特率能被软件改变)这个选项,将不存在 divisor 寄存器。这种情况下,写 divisor 寄存器
没有作用,读它返回一个未定义的值。
endofpacket 寄存器(可选)
endofpacket 寄存器中的值决定了可变长度 DMA 传输的结束字符。复位后,默认值是 0,
是 ASCII 码中的空值字符(0)。endofpacket 寄存器是一种可选的硬件功能。如果没有使能
Include end-of-packet register(这个选项,将不存在 endofpacket 寄存器。这种情况下,写endofpacket 寄存器没有作用,读它返回一个未定义的值。
(2)中断操作
UART 核输出一个单独的 IRQ 信号给 Avalon-MM 接口。而 Avalon-MM 接口能连到系统中的
任意一个主设备,例如 Nios II 处理器。主设备在对 status 寄存器进行读操作之后,才能确定中
断产生的原因。
每一个中断在 status 寄存器有相应的位,并在 control 寄存器中有一个使能位。当一个中
断发生时,相关的 status 位置 1,直到它被回应了(acknowledged)。当任意一个 status 位置
1,且相应的中断使能位是 1 时触发 IRQ。一个主设备可以通过清零 status 寄存器来回应
(acknowledge)IRQ。
复位的时候,所有的中断使能位置 0。因此,核无法触发 IRQ,直到主设备使一个或多个
中断使能位置 1。
这里总结一下:一个可以触发的中断与它相应的 status 和 control(中断使能)位有关。
(3)硬件配置内容
如图为 UART 核的配置界面:
图 5.1.5 UART核配置界面
从图中可以看出,UART 核有 Basic settings(基础设置)和 Baud rate(波特率)这两个可
以设置的部分。
1.Parity(奇偶校验)
Partity 一栏有 None(无)、Even(偶)、Odd(奇)这三个选项。这个设置用来确定 UART
是否发送有奇偶校验的字符,以及它是否期望接收到的有奇偶校验的字符。
当 Parity 设为 None 时,发送逻辑发送不包含校验位的数据,且接收逻辑设定接收到的数
据也不包含校验位。status 寄存器中的 PE(校验错误)位无效,其数值始终为 0。当 Parity 设
为 Odd 或 Even 时,发送逻辑计算并插入所需的校验位到将要输出的 TXD 数据流,且接收逻
辑检验接收到的 RXD 位流中的校验位。如果接收器发现结果不正确,则 status 寄存器中的 PE
位会被置 1.当 Parity 设置为 Even(偶)
时,字符中有偶数个 1 ,则校验位为 0;同样,当 Parity
设为 Odd 时,若字符中有奇数个 1 ,则校验位为 0。
2.Data bits(数据位)
Data bits 一栏有7、
8、
9这三个可以设置的选项。这个设置决定了txdata、
rxdata、endofpacket
这三个寄存器的位宽。
3. Stop bits(停止位)
Stop bits 一栏有 7、8、9 这三个可以设置的选项。这个设置决定了核在传输每一个字符
时,是有 1 还是 2 个停止位。UART 核总是在接收到第一个停止位的时候,就停止接收操作,
忽略掉附带的停止位,无论什么设置。
4. Synchronizer Stages
这个设置与寄存器的长度以及亚稳态事件相关,这里一般使用默认设置即可。
5. Include CTS/RTS
选择是否使用串口的“流控”功能,一般很少使用。
6. Include end-of-packet
选择是否设置数据流的结束标志(end-of-packet),一般很少使用。
7. Baud Rate(波特率设置)
UART 内核可实现 RS-232 标准中的任意波特率。波特率可配置为以下方式中的一种:
⚫ 固定的波特率——波特率在系统生成时被确定,且不能通过 Avalon 从控制器端口改变它的值。
⚫ 可变的波特率——基于 divisor 寄存器中存储的时钟分频值,波特率是可变的。主控制
器通过向 divisor 寄存器中写入新值来改变波特率。
波特率的计算依赖于Avalon-MM接口提供的时钟频率。在硬件改变系统时钟频率,却没有
重新生成UART核会导致错误的信号。
Baud Rate设置决定了复位后的波特率。Baud Rate选项提供了标准的预设值。也允许用
户输入任何非标准波特率。为了实现所需要的波特率,通常根据波特率计算时钟分频系数。
波特率与分频系数的关系如下:
除数=int((时钟频率)/(波特率)+ 0.5 )
波特率=(时钟频率)/(除数 + 1 )
当选择Fixed baud rate 时,UART 硬件中不再包括divisor寄存器。UART 硬件使用固定
的波特率分频系数,且在系统生成后无法改变。这种情况下向地址偏移值4的地方写数据无作
用,且读地址偏移值4的地址返回未定义的结果。当不选择Fixed baud rate 时,硬件中会在
地址偏移值4生成一个16 位的divisor寄存器。divisor寄存器是可写的,所以可以通过向分
频寄存器写入新值来改变波特率。
实验任务
本节实验任务是:在Qsys系统中通过使用官方UART IP核和PC进行环回,将收到的字符发
送给PC。
硬件设计
创建 Quartus II 工程
首先要创建 Quartus II 工程,工程名为“top_UART”。
创建 Qsys 系统
实验中要用到的 IP 核有:
clk(时钟)、niosII(处理器)、onchip_ram(片内存储)、jtag_uart、
sysid_qsys、UART。其中只有 UART IP 核讲解一下,其他的 IP 核都是按照以前的配置方法进行
设置,本节就讲讲如何配置 UART IP 核。
从 Library 中选择 UART IP 核(RS-232 Serial Port)并打开,出现以下界面:
图 5.3.1 UART核的配置界面
这里我们使用默认设置,直接点击【Finish】即可。
然后,我们打开 niosII IP 核配置界面,因为这里只用了 onchip_ram IP 核存储代码和指令,
所以需要对相关的设置进行修改。如图 5.3.2 所示,在 Reset Vector 处将 Reset vector memory
处的选项选为 onchip_ram,同时在 Exception Vector 处也将 Exception vector memory 处的选项
选为 onchip_ram。需要注意的是,
onchip_ram IP 核之后,niosII IP 核的 Reset Vector 和 Exception
Vector 选项中才会出现 onchip_ram 选项。
图 5.3.2 nios II IP核设置界面
添加完 IP 核后就可以开始连线,大家若是不熟悉怎么连线,可以照着下面完成的 Qsys 系
统界面图连。需要注意的是,要将 UART IP 核的端口引出来,如图 5.3.3 所示。引出端口的方
法是双击图 5.3.3 中 IP 核的 Export 一栏的红框位置,然后修改名称,按下 Enter 键即可。
然后,点击 System→Assign Base Addresses 让系统自动分配地址,这里最好把 onchip_ram
的地址锁住,这是因为这个 IP 核里存储着指令,最好不要让其地址发生变动。锁住地址的方
法是先点击 IP 核,然后点击右键→Lock Base Address。我们还可以将各个 IP 核的名称修改
一下。最后就是生成系统了,操作可以按照“Hello,World”文档里的进行。
图 5.3.3 nios II IP核设置界面
集成 Qsys 系统
这一步依然可以按照“Hello,World”文档里的操作进行。
下面将 Quartus II 工程中的顶层代码贴出来。
1 module top_UART(
2 input sys_clk ,
3 input sys_rst_n ,
4
5 //UART 端口
6 input rxd , //UART 接收端
7 output txd //UART 发送端
8 );
9
10 //wire define
11 wire clk_100m; //100mHZ 时钟
12
13 //例化 pll 模块,用以产生
14 pll pll_inst (
15 .inclk0 (sys_clk) ,
16 .c0 (clk_100m)
17 );
18
19 //例化 UART 核
20 UART u0 (
21 .clk_clk (clk_100m), // clk.clk
22 .reset_reset_n (sys_rst_n), // reset.reset_n
23 .uart_rxd (rxd), // uart.rxd
24 .uart_txd (txd) // .txd
25 );
26
27 endmodule
编译和下载
这时,我们便能够进行编译查错了,我们可以通过 Quartus II 软件菜单栏中的【Processing】
→【Start Compilation】来进行编译,也可以通过快捷栏中的快捷键进行编译。
接下来我们就需要进行配置 IO,分配管脚。首先,点击 Quartus II 软件菜单栏中的
【Assignment】→【Device】,然后我们在 Device 界面中找到【Device and Pin Options…】进入
图 5.3.4 所示页面配置 IO。将未使用引脚设置为高阻输入(As input tri-state),这样上电后
FPGA 的所有不使用引脚都将进入高阻抗状态。
图 5.3.4 未使用引脚设置界面
接下来,将一些 IO 设置成普通 IO,通过双击红框位置,将一个个 Value 的值修改过来。
如图 5.3.5 所示。
图 5.3.5 IO设置界面
我们通过 Quartus II 软件菜单栏中的【Assignments】→【Pin Planner】选项分配引脚,如
图 5.3.6 所示。
图 5.3.6 引脚分配界面
最后我们再进行一次全编译,成功编译硬件系统后,将产生用于配置 FPGA 的 top_UART.sof
文件。下面我们就来说明一下将.sof 文件下载到 FPGA 目标器件的步骤。
(1)将下载器一端连接电脑,另一端与开发板上对应端口连接,最后连接电源线并打开
电源开关。开拓者开发板实物图如下所示:
图 5.3.7 开发板实物图
接下来我们下载程序。工程打开后通过点击工具栏中的“Programmer”图标打开下载界面,
通过“Add File”按钮选择 top_UARTparoutput_files 目录下的“top_UART.sof”文件。
开发板电源打开后, 在程序下载界面点击“Hardware Setup”,在弹出的对话框中选择当前
的硬件连接为“USB-Blaster[USB-0]”。然后点击“Start”将工程编译完成后得到的 sof 文
件下载到开发板中。
至此,硬件部分设计完成,下面开始基于 Nios II SBT for Eclipse 的软件部分的设计。
软件设计
我们通过 Quartus II 软件菜单栏中的【Toos】→【Nios II SBT for Eclipse】,来启动 NiosII SBT
for Eclipse 软件。打开 Nios II SBT for Eclipse 软件后,会弹出 Workspace Launcher 页面。我们这
里将工作空间设置为 top_UART qsys 路径下的 software 文件夹,如图 5.4.1 所示。
图 5.4.1 设置工作空间
设置好工作空间后,我们点击【OK】进入 Nios II SBT for Eclipse 软件主界面中,在该页面
我们通过单击菜单栏中的【File】→【New】→【Nios II Application and BSP from Template】,来
新建工程,如图 5.4.2 所示。
图 5.4.2 新建Nios II SBT for Eclipse 工程
单击【…】按钮来选择 top_UARTqsyshardware 下的 top_UART.sopcinfo 文件,即指向当前
硬件设计系统。Nios II SBT for Eclipse 软件会自动识别 Qsys 系统中 CPU 的名称,所以 CPU name
一项会自动生成。接下来,要给 Nios II SBT for Eclipse 软件中的工程命名,这里的名称没有特
殊要求,我们这里名为 UART。然后将工程存放的位置修改为 top_UARTqsyssoftwareUART。注
意不要漏掉了“UART”,不然生成系统的时候会报错。最后我们来看下 Project template 窗口,
该窗口中陈列的都是已经设计好的软件工程。我们可以从中选择一个,作为自己的工程的模板
来使用。当然也可以选择 Bland Project(空白工程),就需要自己写所有的代码。这里我们选
择的是 Hello World 模板工程,然后我们在它的基础上进行修改,这样比空白工程更加方便。
设置完工程后,直接点击【Finish】完成工程创建。然后,在 Nios II SBT for Eclipse 软件的
左侧 Project Explorer 窗口中有两个工程:UART 和 UART_bsp。其中 UART 是 C/C++应用工程,而
UART_bsp 是描述 Qsys 系统硬件细节的系统库。打开 UART 工程里的 hello_world.c 文件,出现
如图 5.4.3 所示的图。
图 5.4.3 hello_world工程代码图
由代码可知,下载程序到开发板后会在窗口上输出 “Hello from Nios II!”。我们在这里要
验证之前创建的 Qsys 系统是否能正常工作。验证方法是先编译 UART 工程,然后将工程模板程
序下载到开发板上,看是否能正常运行。再次之前,需要先简化代码。方法和“Hello,World”
实验里一样是一样的,大家可以照着操作。
优化完代码之后,再编译一次 UART 工程,会出现以下的界面。这表示编译通过,可以将
程序下载到开发板上了。
图 5.4.4 编译通过后的console窗口
这时大家点击【Run As】→【Nios II Hardware】,然后点击【Target Connection】标签,然
后在 Target Connection 窗口中点击【Refresh Connections】按钮后。这时软件便会自动识别我
们开发板上的 Qsys 系统,并显示 Qsys 系统的相关信息。我们接着点击【Run】,软件会把 irq.elf
文件下载至开发板中运行起来。更加详细的图和文字描述,可以在“Hello,World”实验的下载
验证部分查看。
这时,若之前创建的 Qsys 系统无误,代码下载完成后在 Nios II console 窗口会显示“Hello
from Nios II!”字符,如下图所示。
图 5.4.5 下载代码后的console窗口
验证完 Qsys 系统是否能正常运行之后,我们就可以开始软件部分的设计了。这时只需要
在当前的代码窗口修改代码就可以了。代码如下所示。
1 #include <stdio.h>
2 #include "unistd.h"
3 #include "system.h"
4 #include "alt_types.h"
5 #include "altera_avalon_uart_regs.h"
6 #include "sysalt_irq.h"
7 #include "stddef.h"
8 #include "priv/alt_legacy_irq.h"
9 static alt_u8 txdata = 0;
10 static alt_u8 rxdata = 0;
11
12 void IRQ_UART_Interrupts(); //中断初始化函数
13 void IRQ_init(); //中断服务子程序
14
15 int main()
16 {
17 printf("Hello from Nios II!");
18 IRQ_init();
19 return 0;
20 }
21
22 void IRQ_init()
23 {
24 //清除状态寄存器
25 IOWR_ALTERA_AVALON_UART_STATUS(UART_BASE,0);
26 //使能接受准备好中断,给控制寄存器相应位写 1
27 IOWR_ALTERA_AVALON_UART_CONTROL(UART_BASE,0X80);
28 // 注册 ISR
29 alt_ic_isr_register(
30 UART_IRQ_INTERRUPT_CONTROLLER_ID, // 中断控制器标号,从 system.h 复制
31 UART_IRQ , // 硬件中断号,从 system.h 复制
32 IRQ_UART_Interrupts , // 中断服务子函数
33 0x0 , // 指向与设备驱动实例相关的数据结构体
34 0x0); // flags,保留未用
35 }
36
37 //UART 中断服务函数
38 void IRQ_UART_Interrupts()
39 {
40 //将 rxdata 寄存器中存储的值读入变量 rxdata 中
41 rxdata = IORD_ALTERA_AVALON_UART_RXDATA(UART_BASE);
42 //进行串口自收发,将变量 rxdata 中的值赋值给变量 txdata
43 txdata = rxdata;
44 //查询发送准备好信号,如果没有准备好,则等待。
45 while(!(IORD_ALTERA_AVALON_UART_STATUS(UART_BASE)&
46 ALTERA_AVALON_UART_STATUS_TRDY_MSK));
47 //发送准备好,发送 txdata
48 IOWR_ALTERA_AVALON_UART_TXDATA(UART_BASE,txdata);
49 }
代码的第 10 行至第 20 行是主函数。主函数中第 18 行代码完成了初始化中断的任务,完
整的初始化中断函数在代码的第 22 行到第 35 行。在初始化中断的时候,需要先清除 status 寄
存器(状态寄存器),这是在代码的第 25 行完成的。然后是使能中断,也就是给 control 寄存
器(控制寄存器)相应的位写 1,这个操作在代码的第 27 行完成,通过简介部分的内容我们
知道:给控制寄存器第 7 位置 1 的时候(使能第 7 位也就是 bit7 的中断),rxdata 寄存器每接
收一次数据就发出 ISR。在代码的第 29 行至第 34 行,完成了注册中断的任务。在代码的第 38
行至代码的第 49 行是程序的中断函数。当 rxdata 寄存器接收到数据后,就触发 ISR,然后主程
序开始运行中断函数。在中断函数中,先把 rxdata 寄存器中存储的值读入变量 rxdata 中,然后
将变量 rxdata 中的值赋值给变量 txdata,再判断 txdata 寄存器是否处于空闲状态,若处于空闲
状态,就把变量 txdata 中的值发送给 txdata 寄存器,否则就一直等待直到空闲状态的出现。在
代码的第 45 行 至 46 行完成了判断 txdata 寄存器工作状态的任务:首先通过
IORD_ALTERA_AVALON_UART_STATUS(UART_BASE)这段代码读取状态寄存器的值,然后与
ALTERA_AVALON_UART_STATUS_TRDY_MSK(系统定义其值为 0x40)按位相与,这样可以得出状
态寄存器第 6 位的值。若状态寄存器第 6 位为 1,则表示 txdata 寄存器处于空闲状态,那么代
码相与的结果为非零值,经过逻辑取反则为零。若第 6 位的值为 0,经过逻辑取反后的结果为
1。当结果为 0 时,经过 while 判断,主程序会接着运行下一条代码。若结果为 1 时,经过 while
判断,主程序会一直在当前代码处运行。
代码修改完成后,大家记得要点一下快捷菜单中的【Save】,或者菜单栏中的【File】→
【Save】,来保存修改后的程序。
下载验证
现在可以编译 UART 工程了。右键 UART 工程,点击 build project。稍等片刻,Console 窗
口显示的内容如下图所示,这表示工程编译成功。
图 5.5.1 编译工程后的console窗口图
这时大家右键 UART 工程,点击【Run As】→【Nios II Hardware】,代码就被下载到开发板
上了。接下来需要连接串口线并利用串口助手进行验证,这里请大家参考《开拓者 FPGA 开发
指南》第十六章下载验证部分进行操作
更多推荐
所有评论(0)