Nios II开发--流水灯
文章摘要:
本文以流水灯为例,描述了基于NIOS II系统的软件开发流程;
硬件平台:EP4CE6F17C8
开发环境:Nios II 13.1
生成BSP(Board Support Package 板级支持包)
1.打开Nios II 13.1软件,新建BSP工程
File --> New --> Nios II Board Support Package
2.修改BSP
右键项目工程 --> Nios II --> BSP Editor...
完成后点击Generate,并退出。
3.编译工程
右键项目工程 --> Build Project
新建应用工程
File --> New --> Nios II Application
创建源文件:
右键项目工程 --> New --> Source File
/*
* main.c
* Created on: 2017-4-18
* Author: alex
*/
#include "altera_avalon_pio_regs.h"
#include "system.h"
// 延时程序
void delay(int n)
{
while(n--);
}
// 主程序
int main(void)
{
while(1){
IOWR_ALTERA_AVALON_PIO_DATA(PIO_LEDS_BASE,0x01);
delay(500000);
IOWR_ALTERA_AVALON_PIO_DATA(PIO_LEDS_BASE,0x02);
delay(500000);
IOWR_ALTERA_AVALON_PIO_DATA(PIO_LEDS_BASE,0x04);
delay(500000);
IOWR_ALTERA_AVALON_PIO_DATA(PIO_LEDS_BASE,0x08);
delay(500000);
}
return 0;
}
编译工程:
右键项目工程 --> Build Project
运行配置:
Run --> Run Configures...
Project
Target Connection
点击Refresh Connection,直到显示设备连接(如果一直不显示,则可能是Niso配置有问题);
运行测试:
Run --> Run
PIO接口说明:
PIO接口定义于altera_avalon_pio_regs.h中(bsp/drivers/inc):
IORD_ALTERA_AVALON_PIO_DATA 读数据寄存器
IOWR_ALTERA_AVALON_PIO_DATA 写数据寄存器
IORD_ALTERA_AVALON_PIO_DIRECTION 读方向寄存器
IOWR_ALTERA_AVALON_PIO_DIRECTION 写方向寄存器
IORD_ALTERA_AVALON_PIO_IRQ_MASK 读中断掩码寄存器
IOWR_ALTERA_AVALON_PIO_IRQ_MASK 写中断掩码寄存器
IORD_ALTERA_AVALON_PIO_EDGE_CAP 读中断标识寄存器
IOWR_ALTERA_AVALON_PIO_EDGE_CAP 写中断标识寄存器
IOWR_ALTERA_AVALON_PIO_SET_BITS 位操作置位
IOWR_ALTERA_AVALON_PIO_CLEAR_BITS 位操作清零
实际上此操作接口比较繁琐,不如直接定义寄存器地址指针,然后直接操作更简单:
#define LEDS_PORT *((volatile int *)(PIO_LEDS_BASE + 0))
LEDS_PORT = x;
FPGA开发--Nios II最小系统搭建
文章摘要:
网上对于FPGA嵌入CPU的讨论颇多,硬核、固核、软核各有千秋,这里就不做具体分析优劣,也不太建议做过多的争执,只有先彻底的了解其相关技术,才能做出比较中肯的评价;
做为技术层面来说,不能太主观或者过于轻信某一种技术的好于坏,关键是看攻城狮用的场合不合适,多掌握一门手艺,毕竟不是坏事,软核的唯一好处就是,你如果觉得不想用,就可以不用,把节省下的逻辑单元用来干其他的事,想用的话,也很容易配置进去当然前提是你要知道怎么来做才行;
任何一种技术,如果掌握了其技能,你可以有选择不用的权利,但是如果没有掌握,在选择上就会比较局限,所以,个人认为,做为一个技术匠人,应该以包容的心态去学习每一种技术,也只有这样,在创作的时候,才更容易得心应手;
硬件平台:EP4CE6F17C8
开发环境:Quartus II 13.1
创建一个Quartus II的项目工程
工程名为:nios_test
通过Qsys向导来新建NIOS2;
通过Tools-->Qsys菜单启动Qsys软件;
配置系统时钟
打开Qsys软件时,系统会建立一个默认工程,并自动创建了一个clock组件(由此可见时钟的重要性);
双击时钟即可参数进行配置,时钟设计为50MHz,与外部晶振的频率一致,其他采用默认值;
右键-->Rename修改名称为clock
注意事项:
配置的时钟参数必须要和实际的一致,为了简单起见,暂不采用PLL时钟,而采用50MHz外部晶振;
添加cpu核
Library -->
Embedded Processors -->
Nios II Processor
Nios II Core: Nios II/s
其他项目先采用默认值(后边还需要修改)
点击Finish完成创建;
右键-->Rename修改名称为 cpu.
信号连接:
将时钟信号(clk)连接至clock模块的clk输出信号;
将复位信号(reset_n)连接至clock模块的clk_reset输出信号;
添加片内RAM
Library -->
Memories and Memory Controllers -->
On-Chip -->
On-Chip Menory(RAM or ROM)
Type选择 RAM(Writable)
Data width: 32.(数据宽度32位)
Total memory size: 20480 bytes(容量20K).
其他参数暂时选择默认;
信号连接:
将时钟信号(clk1)连接至clock模块的clk输出信号;
将复位信号(reset1)连接至clock模块的clk_reset输出信号;
将s1信号,连接分别连接至cpu的数据总线(data_master)和指令总线(instruction_master);
注意事项:
1.虽然根据数据手册显示EP4CE6一共有270Kb(33KB)RAM,但是在布线时会提示部分RAM无法布局,所以这里先用20KB;
2.由于程序在RAM中运行,所以指令总线也需要连接;
添加JTAG-UART
Library -->
Interface Protocols -->
Serial -->
JTAG UART
Buffer depth(bytes): 16
不选 Construct using registers instead of memory blocks
信号连接:
将时钟信号(clk)连接至clock模块的clk输出信号;
将复位信号(reset)连接至clock模块的clk_reset输出信号;
将avalon_jtag_slave信号连接至cpu的数据总线(data_master);
添加LED外设
Library -->
Peripherals -->
Microcontroller Peripherals -->
PIO (Parallel I/O)
Width(1-32bits): 数据宽度,选择4,因为硬件只连接了4个LED端口;
Direction: 方向,选择Output(输出);
Output Port Reset Value: 复位值,选择全0;
其他值默认;
信号连接:
将时钟信号(clk)连接至clock模块的clk输出信号;
将复位信号(reset)连接至clock模块的clk_reset输出信号;
将s1信号连接至cpu的数据总线(data_master);
引脚导出:
双击external_connection的Export项目,导出引脚;
重新设置复位与异常向量
双击cpu项,打开配置界面,指定复位和异常向量们于ram区;
保存Qsys工程:
保存在项目工程目录下的core目录下,名称为nios_mini.qsys;
分配地址:
System --> Assign Base Addresses
生成
Generate
--> Generate...
会产生一些警告,暂时不用理;
获取例化代码
Generate --> HDL Example
生成顶层模块
- 关闭Qsys回到Quartus II
2.将core/nios_mini.qsys文件添加至工程
3.建立顶层文件,名称为nios_test.v,内容如下:
module nios_test(
input clk,
input rst_n,
output leds
);
// 例化nios_mini
nios_mini u0 (
.clock (clk),
.out_port_from_the_pio_leds (leds),
.reset_n (rst_n)
);
endmodule
分析综合,分配引脚,全编译,下载
FPGA开发--AT24C读写
文章摘要:
本文主要是用于对I2C模块的测试程序,实现对AT24C进行读测试;
重点内容: SingleTap应用,AT24C读写时序;
硬件平台:EP4CE6F17C8
开发环境:Quartus II 13.1
AT24C读写时序图
测试代码:
module at24c02(
clk, // 模块时钟
rst_n, // 模块复位(低电平有效)
scl, // 时钟引脚
sda, // 数据引脚(双向)
rdata // 接收到的数据
);
input clk; // 模块时钟
input rst_n; // 模块复位(低电平有效)
output scl; // 时钟引脚
inout sda; // 数据引脚(双向)
output[7:0] rdata;
//-----------------------------------------------
localparam T1SEC = 32'd50_000_000;
//-----------------------------------------------
reg i2c_start_req;
reg i2c_wr_req;
reg i2c_rd_req;
reg i2c_stop_req;
reg[7:0] i2c_wdata;
wire i2c_done;
reg[3:0] cstate;
reg[3:0] nstate;
reg init_flag;
//-----------------------------------------------
reg [31:0] timer;
// 计时时钟
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
timer <= 0;
end
else if(timer == T1SEC) begin
timer <= 0;
end
else begin
timer <= timer + 1'b1;
end
end
//-----------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
i2c_wr_req <= 0;
i2c_rd_req <= 0;
i2c_start_req <= 0;
i2c_stop_req <= 0;
cstate <= 0;
end
else begin
case(cstate)
0: begin
if(timer == T1SEC) begin
cstate <= cstate + 1'b1;
init_flag <= 0;
end
end
// 发送起始位及设备地址
1: begin
if(init_flag == 0) begin
i2c_wdata <= 8'b1010_000_0; // 设备地址
i2c_start_req <= 1'b1; // 启动发送
init_flag <= 1;
end
else if(i2c_done) begin
init_flag <= 0;
cstate <= cstate + 1'b1;
end
else begin
i2c_start_req <= 0;
end
end
// 发送字地址
2: begin
if(init_flag == 0) begin
init_flag <= 1;
i2c_wr_req <= 1'b1;
i2c_wdata <= 8'b0000_0000;
end
else if(i2c_done) begin
init_flag <= 0;
cstate <= cstate + 1'b1;
end
else begin
i2c_wr_req <= 0;
end
end
// 重启总线及发送设备地址
3: begin
if(init_flag == 0) begin
init_flag <= 1;
i2c_wdata <= 8'b1010_000_1;
i2c_start_req <= 1'b1;
end
else if(i2c_done) begin
init_flag <= 0;
cstate <= cstate + 1'b1;
end
else begin
i2c_start_req <= 0;
end
end
// 读操作
4: begin
if(init_flag == 0) begin
init_flag <= 1;
i2c_rd_req <= 1'b1; // 读操作
end
else if(i2c_done) begin
cstate <= cstate + 1'b1;
init_flag <= 0;
end
else begin
i2c_rd_req <= 0;
end
end
// 发送停止位
5: begin
if(init_flag == 0) begin
init_flag <= 1;
i2c_stop_req <= 1'b1; // 读操作
end
else if(i2c_done) begin
init_flag <= 0;
cstate <= 0;
end
else begin
i2c_stop_req <= 0;
end
end
default: begin
cstate <= 0;
end
endcase
end
end
// 例化模块
i2c_comm m1(
.clk(clk), // 模块时钟
.rst_n(rst_n), // 模块复位(低电平有效)
.scl(scl), // 时钟引脚
.sda(sda), // 数据引脚(双向)
.start_req(i2c_start_req), // 发送起始位及设备地址
.stop_req(i2c_stop_req), // 发送停止位
.wr_req(i2c_wr_req), // 发送数据命令
.rd_req(i2c_rd_req), // 读取数据命令
.rdata(rdata), // 读取数据
.wdata(i2c_wdata), // 待写数据
.done(i2c_done), // 单步操作完成标志
.busy() // 繁忙信号
);
endmodule
添加SingleTap测试文件:
1.采样时钟设置为timer[1],由于存储深度的问题,如果直接采用clk做为采样时钟,则存储不够一个完整的流程,所以需要降低采样频率来现;
2.添加SCL信号;
3.添加SDA信号,并设置SDA下降沿触发;
4.添加rdata信号;
测试结果:
点击Run Analysis进行采集,自动在SDA下降(起始位)沿进行触发;
FPGA开发--I2C读写时序
文章摘要:
本文实现了类似于单片机的外设的I2C读写模块,将I2C各步骤独立出来,可以更清晰的描述I2C时序及工作原理,也使代码具有更发了好的通用性;
重点内容: 三段式状态机,双向IO,I2C时序;
硬件平台:EP4CE6F17C8
开发环境:Quartus II 13.1
时序图:
起始位:SCL高电平时,SDA下跳;
停止位:SCL高电平时,SDA上跳;
数据位:SCL低电平时,SDA可改变状态,SCL上跳时传输;
应答位: 0 - ACK, 1 - NAK(只有主机才允许发送NAK)
I2C模块
/*
* 功能描述:I2C接口模块
*/
module i2c_comm(
clk, // 模块时钟
rst_n, // 模块复位(低电平有效)
scl, // 时钟引脚
sda, // 数据引脚(双向)
start_req, // 发送起始位
wr_req, // 发送请求
rd_req, // 接收请求
stop_req, // 发送停止位
wdata, // 待发数据
rdata, // 接收数据
busy, // 空闲(0)/繁忙(1)
done // 操作完成(脉冲信号)
);
input clk; // 模块时钟
input rst_n; // 模块复位(低电平有效)
output scl; // 时钟引脚
inout sda; // 数据引脚(双向)
input start_req; // 发送直始位
input wr_req; // 发送数据
input rd_req; // 读取数据
input stop_req; // 发送停止位
input [7:0]wdata; // 待发数据
output[7:0]rdata; // 待发数据
output busy; // 操作完成(空闲)
output reg done;
//-----------------------------------------
parameter I2C_COUNT = 20'd500; // 100KHz = 10us
parameter S_IDLE = 4'd0;
parameter S_START = 4'd1;
parameter S_SEND_DATA = 4'd2;
parameter S_RECV_DATA = 4'd3;
parameter S_WAIT_ACK = 4'd4;
parameter S_SEND_ACK = 4'd5;
parameter S_STOP = 4'd6;
//-----------------------------------------
reg [3:0] nstate; // 后续状态
reg [3:0] cstate; // 当前状态
//-----------------------------------------
reg isout;
reg scl_r;
reg sda_r;
reg [7:0]rdata_r;
reg [3:0] bits;
reg[19:0] i2c_cnt;
//-----------------------------------------
assign scl = scl_r;
assign sda = isout?sda_r:1'bz;
assign busy = (cstate == S_IDLE)?1'b0:1'b1;
assign rdata = rdata_r;
//-----------------------------------------
// 传输计时逻辑
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
i2c_cnt <= 0;
end
else if(cstate == S_IDLE) begin
i2c_cnt <= 0;
end
else if(i2c_cnt == I2C_COUNT) begin
i2c_cnt <= 0;
end
else begin
i2c_cnt <= i2c_cnt + 1'b1;
end
end
//-----------------------------------------
// 三段状态机第一段(标准代码)
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
cstate <= S_IDLE;
end
else begin
cstate <= nstate;
end
end
//-----------------------------------------
// 三段状态机第二段(状态转移)
always @(*) begin
case(cstate)
S_IDLE: begin
if(start_req) begin
nstate = S_START; // 启动总线
end
else if(stop_req) begin
nstate = S_STOP; // 停止总线
end
else if(wr_req) begin
nstate = S_SEND_DATA; // 发送数据
end
else if(rd_req) begin
nstate = S_RECV_DATA; // 发送数据
end
else begin
nstate = S_IDLE;
end
end
// 发送起始位
S_START: begin
if(i2c_cnt == I2C_COUNT) begin
nstate = S_SEND_DATA; // 发送完起始位,发送设备地址
end
else begin
nstate = S_START;
end
end
// 发送数据
S_SEND_DATA: begin
if(bits == 4'd8) begin
nstate = S_WAIT_ACK; // 发送完数据,接收ACK
end
else begin
nstate = S_SEND_DATA;
end
end
// 等待设备响应
S_WAIT_ACK: begin
if(i2c_cnt == I2C_COUNT) begin
nstate = S_IDLE; // 接收完ACK,转向空闲
end
else begin
nstate = S_WAIT_ACK;
end
end
// 接收数据
S_RECV_DATA: begin
if(bits == 4'd8) begin
nstate = S_SEND_ACK; // 接收完数据,发送ACK/NAK
end
else begin
nstate = S_RECV_DATA;
end
end
// 主机响应
S_SEND_ACK: begin
if(i2c_cnt == I2C_COUNT) begin
nstate = S_IDLE; // 发送完ACK/NAK,转向空闲
end
else begin
nstate = S_SEND_ACK;
end
end
// 发送停止位
S_STOP: begin
if(i2c_cnt == I2C_COUNT) begin
nstate = S_IDLE; // 发送完停止位,转向空闲
end
else begin
nstate = S_STOP;
end
end
default: begin
nstate = S_IDLE;
end
endcase
end
//-----------------------------------------
// 三段状态机第三段(输出)
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
isout <= 0; // 设置双向端口为输入
sda_r <= 1; // 复位时,设置为高电平
scl_r <= 1; // 复位时,设置为高电平
end
else begin
case(cstate)
S_IDLE: begin
done <= 0;
bits <= 0;
end
// 发送起始位:SCL为高时,SDA下跳
// 前置状态:SCL为高,SDL为高(S_IDLE);
// 后续状态:SCL为低,SDA为低(S_SEND_DATA);
S_START: begin
if(i2c_cnt == 20'd100) begin
scl_r <= 1'b1; // SCL拉高
end
else if(i2c_cnt == 20'd200) begin
sda_r <= 1'b0; // SDA下跳(产生起始位)
end
else if(i2c_cnt == 20'd400) begin
scl_r <= 1'b0; // SCL拉低为次数据做准备
end
else begin
isout <= 1'b1; // 设置为输出
end
end
// 发送数据:SCL低电平准备,SCL上跳送出
// 前置状态:SCL为低(重要)(S_START/S_WAIT_ACK);
// 后续状态:SCL为低,SDAl输入(S_WAIT_ACK)
S_SEND_DATA: begin
if(bits == 4'd8) begin
isout <= 0; // 设置为输入准备接收ACK
sda_r <= 1;
end
else if(i2c_cnt == 20'd100) begin
isout <= 1'b1; // 设置为输出
sda_r <= wdata[7 - bits]; // 低电平时准备SDA数据
end
else if(i2c_cnt == 20'd200) begin
scl_r <= 1'b1; // SCL上跳发送数据
end
else if(i2c_cnt == 20'd400) begin
scl_r <= 1'b0; // SCL拉低准备下次发送
end
else if(i2c_cnt == I2C_COUNT) begin
bits <= bits + 1'b1; // 时序结束时才计数
end
end
// 等待ACK: 由设备回应,设备在SCL拉低之后一段时间内已经准备好了数据;
// 前置状态:SCL为低,SDA输入(S_SEND_DATA)
// 后续状态:SCL为低,SDA输入
S_WAIT_ACK: begin
if(i2c_cnt == 20'd200) begin
isout <= 0; // 设置为输入准备接收ACK
scl_r <= 1'b1; // SCL上跳以接收ACK
end
if(i2c_cnt == 20'd400) begin
scl_r <= 1'b0; // SCL拉低以准备下次发送
// 在此处理ACK
end
else if(i2c_cnt == I2C_COUNT) begin
bits <= 0;
done <= 1;
end
else begin
isout <= 1'b0; // 设置为输入
end
end
// 接收数据:SCL低电平准备
// 前置状态:SCL为低,SDA输入(S_WAIT_ACK)
// 后续状态:SCL为低,SDA输入(S_SEND_ACK)
S_RECV_DATA: begin
if(bits == 4'd8) begin
bits <= 0;
end
else if(i2c_cnt == 20'd100) begin
scl_r <= 1'b0; // SCL先拉低,因为不确定之前的电平
end
else if(i2c_cnt == 20'd200) begin
scl_r <= 1'b1; // SCL上跳接收数据
end
else if(i2c_cnt == 20'd300) begin
rdata_r[7 - bits] <= sda; // 数据稳定后读取数据
end
else if(i2c_cnt == 20'd400) begin
scl_r <= 1'b0; // SCL拉低准备下次发送
end
else if(i2c_cnt == I2C_COUNT) begin
bits <= bits + 1'b1;
end
else begin
isout <= 1'b0; // 设置为输入
end
end
// 发送ACK: ACK(0)/NAK(1)
// 前置状态:SCL为低,SDA输入,
// 后续状态:SCL为高,SDA为低(STOP)
S_SEND_ACK: begin
if(i2c_cnt == 20'd100) begin
isout <= 1; // 设置为输出
sda_r <= 1'b1; // SCL低电平设置ACK(0)/NAK(1)数据
end
else if(i2c_cnt == 20'd200) begin
scl_r <= 1'b1; // SCL上跳发送
end
else if(i2c_cnt == 20'd400) begin
scl_r <= 1'b0; // SCL拉低以准备修改SDA
end
else if(i2c_cnt == I2C_COUNT) begin
sda_r <= 0; // 恢复为低电平(准备上跳停止)
done <= 1;
end
else begin
end
end
// 发送停止位: SCL为高电平时,SDA上跳
// 前置状态:SCL为低,SDA不确定
// 后续状态:SCL为高,SDA为高(准备下次起始位)
S_STOP: begin
if(i2c_cnt == 20'd200) begin
isout <= 1'b1; // 输出
sda_r <= 1'b0; // 设置SDA输出低电平
end
else if(i2c_cnt == 20'd300) begin
scl_r <= 1'b1; // SCL设置为高电平
end
else if(i2c_cnt == 20'd400) begin
sda_r <= 1'b1; // SDA上跳
end
else if(i2c_cnt == I2C_COUNT) begin
done <= 1;
end
else begin
isout <= 1'b1;
end
end
default: begin
done <= 0;
end
endcase
end
end
endmodule