上一节说了在nios ii软核中,uart的printf和scanf函数的用法。简单,强大,但是在阻塞上,存在一些小问题,用的时候要注意。
这一节说一下uart的寄存器操作方法,用过单片机的人肯定对这种方式最熟悉。接下来详细说明NIOS II软核的UART串口寄存器如何配置
一、获取数据手册
要想查看串口相关的寄存器,首先要有这个IP核的数据手册。数据手册怎么获得?
打开Qsys页面,双击打开UART模块,如下所示,点击右上角的:Documentation。
点击后,弹出:UART Documentation页面,鼠标左键点击红色框住的位置,直接跳转到浏览器。
在浏览器中,参照下面1、2、3、4顺序,依次点击:FPGA Device Famil、Intel Cyclone、Cyclone IV FPGAs、Embedded Peripherals IP User Guide。
在弹出的页面中,直接点击下载即可。
二、熟悉寄存器
打开手册,11.4.3章节,可以看到UART核的各个寄存器,如图:
简单说下各个寄存器的功能。
2.1 rxdata和txdata
rxdata是数据接收寄存器,外部设备发送给串口的数据第一时间送到这里;
txdata是数据发送寄存器,NIOS II向外发出的串口数据,直接放到这里就行。
2.2 status状态寄存器
这是一个16位寄存器,状态寄存器由多个单独的位组成,这些位指示UART核心内部的特定条件。每个状态位都与控制寄存器中相应的中断使能位相关联。状态寄存器可以在任何时候被读取。读取不会改变任何位的值。向状态寄存器写入零会清除DCTS、E、TOE、ROE、BRK、FE和PE位。接下来,从低到高说一下每一位的功能。
PE,奇偶校验错误标志位。发生奇偶校验错误时,该位置一。该功能未使能的情况下,读取该位状态,一直是0。
FE,帧错误标志位。无法正确检测停止位时,该位置一。发生该错误时,需要手动清零。
BRK,Break detect,可以理解为打断检测。正常情况下,UART的RXD线应该保持高电平,如果发生意外,导致该线长时间(超过一个字节的传输时间)维持低电平,那么该位置一。需要手动清零。
ROE,接收溢出错误。串口接收缓冲器(rxdata)中的数据没拿走,新的数据又来了,就会发生这种错误。需要手动清零。
TOE,发送溢出错误。同理,txdata里面的数据还没发完,又往里写数据,就会发送这种错误。需要手动清零。
TMT,发送移位寄存器状态,非txdata。发送流程:数据写入txdata->txdata转入发送移位寄存器->发送移位寄存器逐位发送。当该寄存器空的时候,该位自动置一,发送过程中,该位值为0。
TRDY,发送就绪状态位。txdata为空时,该位为1,说明可以发送数据。向txdata写入数据后,该位变为0。
RRDY,接收成功标志位。串口成功收到数据后,该位置一;读走串口的数据后,该位清零。
E,异常标志位。前面说的TOE,ROE,BRK,FE,和PE,只要有一个置一,该位就置一。手动清零。
DCTS,CTS,EOP,用不上,暂时不考虑。
2.3 control控制寄存器
这是一个16位寄存器,它和状态寄存器类似,也是由多个单独的位组成。每个位的功能和状态寄存器里的状态基本对应,表示该功能的中断使能。接下来,从低到高说一下每一位的功能。
IPE,奇偶校验故障中断使能。
IFE,帧故障中断使能。
IBRK,打断检测中断使能。
IROE,接收溢出中断使能。
ITOE,发送溢出中断使能。
ITMT,移位寄存器空中断使能。
ITRDY,发送就绪中断使能。
IRRDY,接收成功中断使能。
IE,异常中断使能。
TBRK,发送打断信号使能。
IDCTS,RTS,IEOP,暂时不用管。
2.4 divisor寄存器
主要用于控制波特率的生成,但是UART核在生成的时候,波特率已经固定为115200,所以这个寄存器是空的,不用管。
2.5 endofpacket 寄存器
用不到,不用管。
三、创建工程
使用core_led.sopcinfo创建一个新的NIOS II工程,用于验证寄存器方式控制UART接口收发数据。创建步骤参照前面章节,工程命名为:uart_reg。
同时,由于该环境下有多个工程,需要将这两个工程相互关联。
四、UART寄存器头文件
包含UART核的NIOS II工程,在创建的时候会自动带上串口相关寄存器的头文件。打开:uart_reg_bsp下面的drivers->inc-> altera_avalon_uart_regs.h,如下:
双击打开,可以看到右侧和串口相关的寄存器相关的一些宏定义。
例如:
IOWR_ALTERA_AVALON_UART_TXDATA(base, data) ;
有基本编程经验的应该都能看懂,该函数的功能就是向寄存器:txdata写入要发送的数据。
五、编写串口相关配置函数
这部分包括串口初始化函数、接收中断相应函数、字节发送函数、字符串发送函数。接下来依次编写各个函数
5.1 串口初始化函数
因为波特率已经固定了,不需要管。这里只需要清除状态寄存器,然后开启接收中断,并给接收中断配置中断回调函数。代码如下:
void uart_init(void)
{
IOWR_ALTERA_AVALON_UART_STATUS(UART_0_BASE, 0);
IOWR_ALTERA_AVALON_UART_CONTROL(UART_0_BASE , ALTERA_AVALON_UART_STATUS_RRDY_MSK);
alt_ic_isr_register(UART_0_IRQ_INTERRUPT_CONTROLLER_ID,
UART_0_IRQ,
uart0_isr_interrupt,
0,
0);
}
注册中断回调函数,这里用到的是:
int alt_ic_isr_register(alt_u32 ic_id, alt_u32 irq, alt_isr_func isr, void *isr_context, void *flags)
ic_id:中断控制ID,打开system.h文件,找到串口的中断控制ID,如下图中箭头2所示:
Irq:中断号,如上图中箭头1所示。
Isr:中断回调函数,需要自己定义,这里写的是:uart0_isr_interrupt
isr_context和flags:不用管,写0就行。
5.2 接收中断回调函数
即,发生串口接收中断时,需要执行的函数,代码如下:
void uart0_isr_interrupt(void)
{
alt_u16 status_buf = 0;
status_buf = IORD_ALTERA_AVALON_UART_STATUS(UART_0_BASE);
if(status_buf & ALTERA_AVALON_UART_STATUS_RRDY_MSK)
{
data_buf = IORD_ALTERA_AVALON_UART_RXDATA(UART_0_BASE);
rece_flag = 1;
}
IOWR_ALTERA_AVALON_UART_STATUS(UART_0_BASE, 0);
}
在这个函数里,先读状态寄存器,看发生中断的是不是串口接收那里。是的话读取接收缓冲器里面的值,并置一一个标志位。
5.3 字节发送函数
即,实现单个字节的发送,代码如下:
void send_byte(alt_8 c_send)
{
while(!(IORD_ALTERA_AVALON_UART_STATUS(UART_0_BASE) & ALTERA_AVALON_UART_STATUS_TRDY_MSK));
IOWR_ALTERA_AVALON_UART_TXDATA(UART_0_BASE, c_send);
}
在这个函数中,先判断发送就绪标志位是否置一。是的话,说明可以发送,把数据送进去;不是的话,等待,等到就绪。
5.4 字符串发送函数
在单个字节发送的基础上,实现字符串的发送,代码如下:
void send_str(alt_u8 * addr)
{
while(*addr != '\0')
{
send_byte(*addr++);
}
}
常用单片机的都懂,不解释。
5.5 主函数部分
主函数这里,做了个简单的判断,当收到的字符是’a’时,LED对应的端口输出高电平;当收到的字符是’b’时,LED对应的端口输出低电平;当收到的字符是’c’时,输出字符串:“Hello uart!”;当收到的字符是其它值时,输出字符串:“Try again!”。
六、编译、烧录、测试
代码编写完成后,保存、编译。没有问题的话,进行烧录。
测试效果如上,程序按照我们需要的功能执行。 打完收工!