- 2023-06-10
-
发表了主题帖:
国产FPGA安路SF1系列测评【智能垃圾桶设计】
本期测评,我们使用安路SF1开发板进行Verilog纯逻辑的应用设计,实现智能垃圾桶的功能。测评中使用到的外设有HC-SR04超声波模块、SG90舵机,因此本次工程也主要分为两个模块。分别为sonic超声波模块,以及servos舵机模块。
首先介绍超声波模块。如下图所示,基本原理是超声波发射模块向某一方向发射超声波,在发射时刻的同时开始计时,超声波在空气中传播,途中碰到障碍物就立即返回来,超声波接收器收到反射波就立即停止计时。计算公式为:距离 = 声速 × 发出超声波到接收返回的时间/2。本工程中因为取的时钟频率是17Khz,则distance=340*T/2=170*(1/17000)*100,单位是cm。
超声波模块代码如下:
module sonic(
input clk,
input rst_n,
input Echo,
output reg Trig,
output reg [7:0] data
);
//Trig
reg [23:0] cnt_trig;
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_trig<=1'b0;
else
if(cnt_trig =='d250) begin
Trig<=0;
cnt_trig<=cnt_trig+1'b1;
end
else
begin
if(cnt_trig=='d500000)
begin
Trig<=1;
cnt_trig<=0;
end
else
cnt_trig<=cnt_trig+1'b1;
end
end
reg Echo_2,Echo_1,cnt_en,flag;
assign pose_Echo =(~Echo_2)&&Echo_1;
assign nege_Echo = Echo_2&&(~Echo_1);
parameter S0 = 2'b00, S1 = 2'b01, S2 = 2'b10;
reg[1:0] curr_state;
reg [15:0] cnt;
reg [15:0] dis_reg;
reg [15:0] cnt_17k;
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
Echo_1 <= 1'b0;
Echo_2 <= 1'b0;
cnt_17k <=1'b0;
dis_reg <=1'b0;
curr_state <= S0;
end
else
begin
Echo_1<=Echo;
Echo_2<=Echo_1;
case(curr_state)
S0:begin
if (pose_Echo)
curr_state <= S1;
else
begin
cnt <= 1'b0;
end
end
S1:begin
if(nege_Echo)
curr_state <= S2;
else
begin
if(cnt_17k <16'd1470)
begin
cnt_17k <= cnt_17k + 1'b1;
end
else
begin
cnt_17k <= 1'b0;
cnt[3:0] <= cnt[3:0] +1'b1;
end
if(cnt[3:0] >= 'd10)
begin
cnt [3:0] <=1'b0;
cnt [7:4]<=cnt[7:4]+1'b1;
end
if (cnt[7:4] >= 'd10)
begin
cnt[7:4]<=1'b0;
cnt[11:8]<=cnt[11:8]+1'b1;
end
if (cnt[11:8]>='d10)
begin
cnt[11:8]<=1'b0;
cnt[15:12]<=cnt[15:12]+1'b1;
end
if(cnt[15:12]>='d10)
begin
cnt[15:12]<=1'b0;
end
end
end
S2:begin
dis_reg<=cnt;
cnt <=1'b0;
curr_state <= S0;
end
endcase
end
end
//assign data = dis_reg ; //对输出的data赋值,因为取的时钟频率是17Khz,340*T/2=170*(1/17000)*100=T单位是cm
always @ (posedge clk or negedge rst_n)begin
if(!rst_n)
data <= 8'd0;
else if(dis_reg>=255)
data <= 255;
else
data <= dis_reg[7:0];
end
endmodule
下面介绍舵机模块。SG90舵机是一种位置(角度)伺服的驱动器,适用于那些需要角度不断变化并可以保持的控制系统。在机器人机电控制系统中,舵机控制效果是性能的重要影响因素。舵机可以在微机电系统和航模中作为基本的输出执行机构,其简单的控制和输出使得控制系统非常容易与之接口。舵机的控制一般需要一个20ms 左右的时基脉冲,该脉冲的高电平部分一般为0.5ms~2.5ms 范围内的角度控制脉冲部分。以180 度角度伺服为例,那么对应的控制关系是这样的:
舵机角度的输出采用PWM控制,由于SF1开发板的系统时钟是25MHz,1s的计数为25000000,所以舵机角度与计数次数的关系为:计数次数=278*角度+12500。具体代码如下:
module servos(
input clk,
input rst_n,
input en,
output reg pwm
);
parameter TIME_20MS = 20'd500_000;
parameter TIME_1_5MS = 17'd37_500;
reg [19:0] cnt;
//20ms cnt
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt <= 20'd0;
else if(cnt == TIME_20MS-1'b1)
cnt <= 20'd0;
else
cnt <= cnt + 1'b1;
end
//pwm
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
pwm <= 1'b0;
else if(en)begin
if(cnt<=TIME_1_5MS-1'b1)
pwm <= 1'b1;
else
pwm <= 1'b0;
end
else begin
if(cnt<=17'd10417)
pwm <= 1'b1;
else
pwm <= 1'b0;
end
end
endmodule
在顶层文件 trash_can_top 中,我们设置触发距离为25cm,即当超声波检测到25cm内有物体时,控制舵机自动打开垃圾桶,否则关闭垃圾桶,代码如下:
module trash_can_top(
input clk,
input rst_n,
input echo,
output trig,
output pwm
);
wire [7:0] distance;
wire [0:0] en;
assign en = (distance<=25) ? 1'b1 : 1'b0;
sonic sonic_u1(
.clk(clk),
.rst_n(rst_n),
.Echo(echo),
.Trig(trig),
.data(distance) //cm
);
servos servos_u1(
.clk(clk),
.rst_n(rst_n),
.en(en),
.pwm(pwm)
);
endmodule
引脚约束文件如下:
set_pin_assignment { clk } { LOCATION = D7; }
set_pin_assignment { echo } { LOCATION = B10; }
set_pin_assignment { pwm } { LOCATION = C10; }
set_pin_assignment { rst_n } { LOCATION = G3; }
set_pin_assignment { trig } { LOCATION = B11; }
编译综合无误后下载至开发板,智能垃圾桶外观如下:
视频演示如下,当人距离垃圾桶25cm以内时,垃圾桶自动打开,否则,垃圾桶关闭,实现了无接触的功能:
[localvideo]51241cad35f08e8b6bc5fe6835b9dedf[/localvideo]
工程如下:
SF1芯片由于集成了FPGA与MCU两大模块,开发一些控制系统还是很有优势的。
- 2023-04-14
-
加入了学习《直播回放: 安路全新FPSoC产品SF1系列FPGA+MCU, Demo板介绍和案例解析》,观看 安路全新FPSoC产品SF1系列FPGA+MCU
-
发表了主题帖:
国产FPGA安路SF1系列测评【使用 FPGA 进行图像处理】
本帖最后由 Grayson__ 于 2023-4-14 21:11 编辑
本次测评我们将进行 FPGA 的图像处理,将图像数据先通过RGB转YCbCr模块转为灰度数据后进行中值滤波,测评主要分析 SF1 的资源使用量。
工程中各模块的层次如下图所示:
中值滤波算法就是取一个滤波窗口内的中间值进行计算的算法,选用中间值进行计算,理论上可以消除噪声。本测评使用 3x3 窗口进行中值滤波的实现方案。
PART 1: RGB图像格式转YCbCr图像格式
YCbCr由 Y、Cb、Cr 组成,Y代表颜色的明亮度和浓度,Cb代表颜色的蓝色浓度偏移,Cr代表颜色的红色浓度偏移。Y分量就是我们常说的灰度值,因为很多图像处理不关注色彩,只需在灰度域处理即可,所以使用 Y 分量可以对传输带宽进行一定程度的压缩。YCbCr分为两种格式:tv range和full range,其中full range是我们本次使用的转换格式,对应的R、G、B范围均是0-255,Y、Cb、Cr也都是0-255。公式如下:
FPGA对浮点数进行运算要使用大量资源,所以我们将公式做如下转换,可大大减少资源的使用量并提高运算速度,这种运算转换的思想也是FPGA在进行图像处理或数字信号处理中一些算法常用的思想:
在运算过程中使用三级流水线,第一级是各分量的值对应相乘,第二级进行相乘后的值得相加,第三级就是右移8bit。由于是三级流水线,最后输出的数据延迟了三个时钟周期,所以我们需要对输出的行场同步信号以及使能信号进行打三拍的操作,才能使输出的数据在时序上对齐。完整代码如下:
module rgb2ycbcr(
//module clock
input clk , // 模块驱动时钟
input rst_n , // 复位信号
//图像处理前的数据接口
input pre_frame_vsync , // vsync信号
input pre_frame_hsync , // hsync信号
input pre_frame_de , // data enable信号
input [4:0] img_red , // 输入图像数据R
input [5:0] img_green , // 输入图像数据G
input [4:0] img_blue , // 输入图像数据B
//图像处理后的数据接口
output post_frame_vsync, // vsync信号
output post_frame_hsync, // hsync信号
output post_frame_de , // data enable信号
output [7:0] img_y , // 输出图像Y数据
output [7:0] img_cb , // 输出图像Cb数据
output [7:0] img_cr // 输出图像Cr数据
);
//reg define
reg [4:0] img_red_r0;
reg [5:0] img_green_r0;
reg [4:0] img_blue_r0;
reg [4:0] img_red_r1;
reg [5:0] img_green_r1;
reg [4:0] img_blue_r1;
reg [15:0] rgb_r_m0, rgb_r_m1, rgb_r_m2;
reg [15:0] rgb_g_m0, rgb_g_m1, rgb_g_m2;
reg [15:0] rgb_b_m0, rgb_b_m1, rgb_b_m2;
reg [15:0] img_y0 ;
reg [15:0] img_cb0;
reg [15:0] img_cr0;
reg [ 7:0] img_y1 ;
reg [ 7:0] img_cb1;
reg [ 7:0] img_cr1;
reg [ 2:0] pre_frame_vsync_d;
reg [ 2:0] pre_frame_hsync_d;
reg [ 2:0] pre_frame_de_d;
//wire define
wire [ 7:0] rgb888_r;
wire [ 7:0] rgb888_g;
wire [ 7:0] rgb888_b;
//RGB565 to RGB 888,使用高位进行量化补偿
assign rgb888_r = {img_red , img_red[4:2] };
assign rgb888_g = {img_green, img_green[5:4]};
assign rgb888_b = {img_blue , img_blue[4:2] };
//同步输出数据接口信号
assign post_frame_vsync = pre_frame_vsync_d[2];
assign post_frame_hsync = pre_frame_hsync_d[2];
assign post_frame_de = pre_frame_de_d[2] ;
assign img_y = img_y1;
assign img_cb = img_cb1;
assign img_cr = img_cr1;
//--------------------------------------------
//RGB 888 to YCbCr
/********************************************************
RGB888 to YCbCr888
Y = 0.299R +0.587G + 0.114B
Cb = 0.568(B-Y) + 128 = -0.172R-0.339G + 0.511B + 128
CR = 0.713(R-Y) + 128 = 0.511R-0.428G -0.083B + 128
Y = (77 *R + 150*G + 29 *B)>>8
Cb = (-43*R - 85 *G + 128*B)>>8 + 128
Cr = (128*R - 107*G - 21 *B)>>8 + 128
Y = (77 *R + 150*G + 29 *B )>>8
Cb = (-43*R - 85 *G + 128*B + 32768)>>8
Cr = (128*R - 107*G - 21 *B + 32768)>>8
*********************************************************/
//step1 pipeline mult
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
rgb_r_m0 <= 16'd0;
rgb_r_m1 <= 16'd0;
rgb_r_m2 <= 16'd0;
rgb_g_m0 <= 16'd0;
rgb_g_m1 <= 16'd0;
rgb_g_m2 <= 16'd0;
rgb_b_m0 <= 16'd0;
rgb_b_m1 <= 16'd0;
rgb_b_m2 <= 16'd0;
end
else begin
rgb_r_m0 <= rgb888_r * 8'd77 ;
rgb_r_m1 <= rgb888_r * 8'd43 ;
rgb_r_m2 <= rgb888_r << 3'd7 ;
rgb_g_m0 <= rgb888_g * 8'd150;
rgb_g_m1 <= rgb888_g * 8'd85 ;
rgb_g_m2 <= rgb888_g * 8'd107;
rgb_b_m0 <= rgb888_b * 8'd29 ;
rgb_b_m1 <= rgb888_b << 3'd7 ;
rgb_b_m2 <= rgb888_b * 8'd21 ;
end
end
//step2 pipeline add
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
img_y0 <= 16'd0;
img_cb0 <= 16'd0;
img_cr0 <= 16'd0;
end
else begin
img_y0 <= rgb_r_m0 + rgb_g_m0 + rgb_b_m0;
img_cb0 <= rgb_b_m1 - rgb_r_m1 - rgb_g_m1 + 16'd32768;
img_cr0 <= rgb_r_m2 - rgb_g_m2 - rgb_b_m2 + 16'd32768;
end
end
//step3 pipeline div
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
img_y1 <= 8'd0;
img_cb1 <= 8'd0;
img_cr1 <= 8'd0;
end
else begin
img_y1 <= img_y0 [15:8];
img_cb1 <= img_cb0[15:8];
img_cr1 <= img_cr0[15:8];
end
end
//延时3拍以同步数据信号
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
pre_frame_vsync_d <= 3'd0;
pre_frame_hsync_d <= 3'd0;
pre_frame_de_d <= 3'd0;
img_red_r0 <= 5'd0;
img_green_r0 <= 6'd0;
img_blue_r0 <= 5'd0;
img_red_r1 <= 5'd0;
img_green_r1 <= 6'd0;
img_blue_r1 <= 5'd0;
end
else begin
pre_frame_vsync_d <= {pre_frame_vsync_d[1:0], pre_frame_vsync};
pre_frame_hsync_d <= {pre_frame_hsync_d[1:0], pre_frame_hsync};
pre_frame_de_d <= {pre_frame_de_d[1:0] , pre_frame_de };
img_red_r0 <= img_red;
img_green_r0 <= img_green;
img_blue_r0 <= img_blue;
img_red_r1 <= img_red_r0;
img_green_r1 <= img_green_r0;
img_blue_r1 <= img_blue_r0;
end
end
endmodule
PART 2 :中值滤波模块
1.生成 3x3 格式的矩阵。
由于本测评中使用的是 3x3 的滤波窗口,所以我们首先要生成一个 3x3 矩阵模块,这里我们使用 FPGA 中的 RAM IP核,使用简单双端口ram,数据位宽为8bit,深度为256,读写时钟,写使能,读写地址配置如下:
这样,在 line_shift_ram_8bit 模块中调用ram模块可以获得三行一列的数据:
module line_shift_ram_8bit(
input clock,
input clken,
input per_frame_href,
input [7:0] shiftin, //当前行(第三行)
output [7:0] taps0x, //第二行
output [7:0] taps1x //第一行
);
//reg define
reg [2:0] clken_dly;
reg [9:0] ram_rd_addr;
reg [9:0] ram_rd_addr_d0;
reg [9:0] ram_rd_addr_d1;
reg [7:0] shiftin_d0;
reg [7:0] shiftin_d1;
reg [7:0] shiftin_d2;
reg [7:0] taps0x_d0;
//在数据来到时,ram地址累加
always@(posedge clock)begin
if(per_frame_href)
if(clken)
ram_rd_addr <= ram_rd_addr + 1 ;
else
ram_rd_addr <= ram_rd_addr ;
else
ram_rd_addr <= 0 ;
end
//时钟使能信号延迟三拍,从RAM中读取,以及向RAM0 RAM1中更新数据各需要一个时钟
always@(posedge clock) begin
clken_dly <= { clken_dly[1:0] , clken };
end
//将ram地址延迟二拍
always@(posedge clock) begin
ram_rd_addr_d0 <= ram_rd_addr;
ram_rd_addr_d1 <= ram_rd_addr_d0;
end
//输入数据延迟三拍
always@(posedge clock)begin
shiftin_d0 <= shiftin;
shiftin_d1 <= shiftin_d0;
shiftin_d2 <= shiftin_d1;
end
//用于存储前一行图像的RAM
ram_8x256 u_ram_8x256_0(
.clka (clock),
.cea (clken_dly[2]), //在延迟的第三个时钟周期,将当前行的数据写入RAM0
.addra (ram_rd_addr_d1),
.dia (shiftin_d2),
.clkb (clock),
.addrb (ram_rd_addr),
.dob (taps0x) //延迟一个时钟周期,输出RAM0中前一行图像的数据
);
//寄存一次前一行图像的数据
always@(posedge clock) begin
taps0x_d0 <= taps0x;
end
//用于存储前前一行图像的RAM
ram_8x256 u_ram_8x256_1(
.clka (clock),
.cea (clken_dly[1]), //在延迟的第二个时钟周期,将前一行图像的数据写入RAM1
.addra (ram_rd_addr_d0),
.dia (taps0x_d0),
.clkb (clock),
.addrb (ram_rd_addr),
.dob (taps1x) //延迟一个时钟周期,输出RAM1中前前一行图像的数据
);
endmodule
之后生成 3x3 的矩阵:
module matrix_generate_3x3_8bit(
input clk,
input rst_n,
input per_frame_vsync,
input per_frame_href,
input per_frame_clken,
input [7:0] per_img_y,
output matrix_frame_vsync,
output matrix_frame_href,
output matrix_frame_clken,
output reg [7:0] matrix_p11,
output reg [7:0] matrix_p12,
output reg [7:0] matrix_p13,
output reg [7:0] matrix_p21,
output reg [7:0] matrix_p22,
output reg [7:0] matrix_p23,
output reg [7:0] matrix_p31,
output reg [7:0] matrix_p32,
output reg [7:0] matrix_p33
);
//wire define
wire [7:0] row1_data; //第一行
wire [7:0] row2_data; //第二行
wire read_frame_href;
wire read_frame_clken;
//reg define
reg [7:0] row3_data; //当前行
reg [1:0] per_frame_vsync_r;
reg [1:0] per_frame_href_r;
reg [1:0] per_frame_clken_r;
assign read_frame_href = per_frame_href_r[0] ;
assign read_frame_clken = per_frame_clken_r[0];
assign matrix_frame_vsync = per_frame_vsync_r[1];
assign matrix_frame_href = per_frame_href_r[1] ;
assign matrix_frame_clken = per_frame_clken_r[1];
//当前数据放在第3行
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
row3_data <= 0;
else begin
if(per_frame_clken)
row3_data <= per_img_y ;
else
row3_data <= row3_data ;
end
end
//用于存储列数据的RAM,消耗一个时钟周期
line_shift_ram_8bit u_line_shift_ram_8bit
(
.clock (clk),
.clken (per_frame_clken),
.per_frame_href (per_frame_href),
.shiftin (per_img_y),
.taps0x (row2_data),
.taps1x (row1_data)
);
//将同步信号延迟两拍,用于同步化处理
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
per_frame_vsync_r <= 0;
per_frame_href_r <= 0;
per_frame_clken_r <= 0;
end
else begin
per_frame_vsync_r <= { per_frame_vsync_r[0], per_frame_vsync };
per_frame_href_r <= { per_frame_href_r[0], per_frame_href };
per_frame_clken_r <= { per_frame_clken_r[0], per_frame_clken };
end
end
//在同步处理后的控制信号下,输出图像矩阵,消耗一个时钟周期
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
{matrix_p11, matrix_p12, matrix_p13} <= 24'h0;
{matrix_p21, matrix_p22, matrix_p23} <= 24'h0;
{matrix_p31, matrix_p32, matrix_p33} <= 24'h0;
end
else if(read_frame_href) begin
if(read_frame_clken) begin
{matrix_p11, matrix_p12, matrix_p13} <= {matrix_p12, matrix_p13, row1_data};
{matrix_p21, matrix_p22, matrix_p23} <= {matrix_p22, matrix_p23, row2_data};
{matrix_p31, matrix_p32, matrix_p33} <= {matrix_p32, matrix_p33, row3_data};
end
else begin
{matrix_p11, matrix_p12, matrix_p13} <= {matrix_p11, matrix_p12, matrix_p13};
{matrix_p21, matrix_p22, matrix_p23} <= {matrix_p21, matrix_p22, matrix_p23};
{matrix_p31, matrix_p32, matrix_p33} <= {matrix_p31, matrix_p32, matrix_p33};
end
end
else begin
{matrix_p11, matrix_p12, matrix_p13} <= 24'h0;
{matrix_p21, matrix_p22, matrix_p23} <= 24'h0;
{matrix_p31, matrix_p32, matrix_p33} <= 24'h0;
end
end
endmodule
2.下面就开始进行矩阵中值的寻找,流程如下:
先将每行3个数据进行两两比较,得到最大值Max,中间值Mid,最小值Min;
再求3个最大值的最小值Max_min,3个中间值的中间值Mid_mid,3个最小值的最大值Min_max;
最后求Max_min、Mid_mid、Min_max的中间值,即为最终结果。
求最大最小中间值需要对三个数进行排序才可得出,此处使用sort3模块,代码如下:
module sort3(
input clk,
input rst_n,
input [7:0] data1,
input [7:0] data2,
input [7:0] data3,
output reg [7:0] max_data,
output reg [7:0] mid_data,
output reg [7:0] min_data
);
//对三个数据进行排序
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
max_data <= 0;
mid_data <= 0;
min_data <= 0;
end
else begin
//取最大值
if(data1 >= data2 && data1 >= data3)
max_data <= data1;
else if(data2 >= data1 && data2 >= data3)
max_data <= data2;
else//(data3 >= data1 && data3 >= data2)
max_data <= data3;
//取中值
if((data1 >= data2 && data1 <= data3) || (data1 >= data3 && data1 <= data2))
mid_data <= data1;
else if((data2 >= data1 && data2 <= data3) || (data2 >= data3 && data2 <= data1))
mid_data <= data2;
else//((data3 >= data1 && data3 <= data2) || (data3 >= data2 && data3 <= data1))
mid_data <= data3;
//取最小值
if(data1 <= data2 && data1 <= data3)
min_data <= data1;
else if(data2 <= data1 && data2 <= data3)
min_data <= data2;
else//(data3 <= data1 && data3 <= data2)
min_data <= data3;
end
end
endmodule
之后在median_filter_3x3模块中进行上述三步骤的实现:
module median_filter_3x3(
input clk,
input rst_n,
input median_frame_vsync,
input median_frame_href,
input median_frame_clken,
input [7:0] data11,
input [7:0] data12,
input [7:0] data13,
input [7:0] data21,
input [7:0] data22,
input [7:0] data23,
input [7:0] data31,
input [7:0] data32,
input [7:0] data33,
output [7:0] target_data,
output pos_median_vsync,
output pos_median_href,
output pos_median_clken
);
//--------------------------------------------------------------------------------------
//FPGA Median Filter Sort order
// Pixel -- Sort1 -- Sort2 -- Sort3
// [ P1 P2 P3 ] --> [ Max1 Mid1 Min1 ]
// [ P4 P5 P6 ] --> [ Max2 Mid2 Min2 ] --> [Max_min, Mid_mid, Min_max] --> mid_valid
// [ P7 P8 P9 ] --> [ Max3 Mid3 Min3 ]
//reg define
reg [2:0] median_frame_vsync_r;
reg [2:0] median_frame_href_r;
reg [2:0] median_frame_clken_r;
//wire define
wire [7:0] max_data1;
wire [7:0] mid_data1;
wire [7:0] min_data1;
wire [7:0] max_data2;
wire [7:0] mid_data2;
wire [7:0] min_data2;
wire [7:0] max_data3;
wire [7:0] mid_data3;
wire [7:0] min_data3;
wire [7:0] max_min_data;
wire [7:0] mid_mid_data;
wire [7:0] min_max_data;
assign pos_median_vsync = median_frame_vsync_r[2];
assign pos_median_href = median_frame_href_r[2];
assign pos_median_clken = median_frame_clken_r[2];
//Step1 对stor3进行三次例化操作
sort3 u_sort3_1( //第一行数据排序
.clk (clk),
.rst_n (rst_n),
.data1 (data11),
.data2 (data12),
.data3 (data13),
.max_data (max_data1),
.mid_data (mid_data1),
.min_data (min_data1)
);
sort3 u_sort3_2( //第二行数据排序
.clk (clk),
.rst_n (rst_n),
.data1 (data21),
.data2 (data22),
.data3 (data23),
.max_data (max_data2),
.mid_data (mid_data2),
.min_data (min_data2)
);
sort3 u_sort3_3( //第三行数据排序
.clk (clk),
.rst_n (rst_n),
.data1 (data31),
.data2 (data32),
.data3 (data33),
.max_data (max_data3),
.mid_data (mid_data3),
.min_data (min_data3)
);
//Step2 对三行像素取得的排序进行处理
sort3 u_sort3_4( //取三行最大值的最小值
.clk (clk),
.rst_n (rst_n),
.data1 (max_data1),
.data2 (max_data2),
.data3 (max_data3),
.max_data (),
.mid_data (),
.min_data (max_min_data)
);
sort3 u_sort3_5( //取三行中值的最小值
.clk (clk),
.rst_n (rst_n),
.data1 (mid_data1),
.data2 (mid_data2),
.data3 (mid_data3),
.max_data (),
.mid_data (mid_mid_data),
.min_data ()
);
sort3 u_sort3_6( //取三行最小值的最大值
.clk (clk),
.rst_n (rst_n),
.data1 (min_data1),
.data2 (min_data2),
.data3 (min_data3),
.max_data (min_max_data),
.mid_data (),
.min_data ()
);
//step3 将step2 中得到的三个值,再次取中值
sort3 u_sort3_7(
.clk (clk),
.rst_n (rst_n),
.data1 (max_min_data),
.data2 (mid_mid_data),
.data3 (min_max_data),
.max_data (),
.mid_data (target_data),
.min_data ()
);
//延迟三个周期进行同步
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
median_frame_vsync_r <= 0;
median_frame_href_r <= 0;
median_frame_clken_r <= 0;
end
else begin
median_frame_vsync_r <= {median_frame_vsync_r[1:0],median_frame_vsync};
median_frame_href_r <= {median_frame_href_r [1:0], median_frame_href};
median_frame_clken_r <= {median_frame_clken_r[1:0],median_frame_clken};
end
end
endmodule
最后在gray_median_filter模块中例化matrix_generate_3x3_8bit与median_filter_3x3模块,需要注意的是3x3阵列的中值滤波,需要3个时钟:
module gray_median_filter(
//时钟
input clk, //50MHz
input rst_n,
//处理前图像数据
input pe_frame_vsync, //处理前图像数据场信号
input pe_frame_href, //处理前图像数据行信号
input pe_frame_clken, //处理前图像数据输入使能效信号
input [7:0] pe_img, //灰度数据
//处理后的图像数据
output pos_frame_vsync, //处理后的图像数据场信号
output pos_frame_href, //处理后的图像数据行信号
output pos_frame_clken, //处理后的图像数据输出使能效信号
output [7:0] pos_img //处理后的灰度数据
);
//wire define
wire matrix_frame_vsync;
wire matrix_frame_href;
wire matrix_frame_clken;
wire [7:0] matrix_p11; //3X3 阵列输出
wire [7:0] matrix_p12;
wire [7:0] matrix_p13;
wire [7:0] matrix_p21;
wire [7:0] matrix_p22;
wire [7:0] matrix_p23;
wire [7:0] matrix_p31;
wire [7:0] matrix_p32;
wire [7:0] matrix_p33;
wire [7:0] mid_value;
//在延迟后的行信号有效,将中值赋给灰度输出值
assign pos_img = pos_frame_href ? mid_value : 8'd0;
matrix_generate_3x3_8bit u_matrix_generate_3x3_8bit(
.clk (clk),
.rst_n (rst_n),
//处理前图像数据
.per_frame_vsync (pe_frame_vsync),
.per_frame_href (pe_frame_href),
.per_frame_clken (pe_frame_clken),
.per_img_y (pe_img),
//处理后的图像数据
.matrix_frame_vsync (matrix_frame_vsync),
.matrix_frame_href (matrix_frame_href),
.matrix_frame_clken (matrix_frame_clken),
.matrix_p11 (matrix_p11),
.matrix_p12 (matrix_p12),
.matrix_p13 (matrix_p13),
.matrix_p21 (matrix_p21),
.matrix_p22 (matrix_p22),
.matrix_p23 (matrix_p23),
.matrix_p31 (matrix_p31),
.matrix_p32 (matrix_p32),
.matrix_p33 (matrix_p33)
);
//3x3阵列的中值滤波,需要3个时钟
median_filter_3x3 u_median_filter_3x3(
.clk (clk),
.rst_n (rst_n),
.median_frame_vsync (matrix_frame_vsync),
.median_frame_href (matrix_frame_href),
.median_frame_clken (matrix_frame_clken),
//第一行
.data11 (matrix_p11),
.data12 (matrix_p12),
.data13 (matrix_p13),
//第二行
.data21 (matrix_p21),
.data22 (matrix_p22),
.data23 (matrix_p23),
//第三行
.data31 (matrix_p31),
.data32 (matrix_p32),
.data33 (matrix_p33),
.pos_median_vsync (pos_frame_vsync),
.pos_median_href (pos_frame_href),
.pos_median_clken (pos_frame_clken),
.target_data (mid_value)
);
endmodule
PART 3 :图像处理顶层
在图像处理顶层模块中,我们将例化各个处理算法,此次例化的是rgb转ycbcr的模块以及中值滤波模块:
module vip_top(
input clk ,
input rst_n ,
input pre_frame_vsync ,
input pre_frame_hsync ,
input pre_frame_de ,
input [4:0] img_red ,
input [5:0] img_green ,
input [4:0] img_blue ,
output pos_frame_vsync ,
output pos_frame_href ,
output pos_frame_clken ,
output [7:0] pos_img
);
//wire define
wire pe1_frame_vsync;
wire pe1_frame_hsync;
wire pe1_frame_de;
wire [7:0] pe1_img_y;
wire [7:0] pe1_img_Cb;
wire [7:0] pe1_img_Cr;
rgb2ycbcr u_rgb2ycbcr(
//module clock
.clk (clk), // 模块驱动时钟
.rst_n (rst_n), // 复位信号
//图像处理前的数据接口
.pre_frame_vsync (pre_frame_vsync), // vsync信号
.pre_frame_hsync (pre_frame_hsync), // hsync信号
.pre_frame_de (pre_frame_de), // data enable信号
.img_red (img_red), // 输入图像数据R
.img_green (img_green), // 输入图像数据G
.img_blue (img_blue), // 输入图像数据B
//图像处理后的数据接口
.post_frame_vsync(pe1_frame_vsync), // vsync信号
.post_frame_hsync(pe1_frame_hsync), // hsync信号
.post_frame_de (pe1_frame_de), // data enable信号
.img_y (pe1_img_y), // 输出图像Y数据
.img_cb (pe1_img_Cb), // 输出图像Cb数据
.img_cr (pe1_img_Cr) // 输出图像Cr数据
);
gray_median_filter u_gray_median_filter(
//时钟
.clk (clk), // 模块驱动时钟
.rst_n (rst_n), // 复位信号
//处理前图像数据
.pe_frame_vsync (pe1_frame_vsync), //处理前图像数据场信号
.pe_frame_href (pe1_frame_hsync), //处理前图像数据行信号
.pe_frame_clken (pe1_frame_de), //处理前图像数据输入使能效信号
.pe_img (pe1_img_y), //灰度数据
//处理后的图像数据
.pos_frame_vsync (pos_frame_vsync), //处理后的图像数据场信号
.pos_frame_href (pos_frame_href), //处理后的图像数据行信号
.pos_frame_clken (pos_frame_clken), //处理后的图像数据输出使能效信号
.pos_img (pos_img) //处理后的灰度数据
);
endmodule
至此,图像中值滤波已在 FPGA 上实现,下面是FPGA的资源占用率:
可见,使用 SF1 FPGA 部分进行图像的处理加速,其资源是绰绰有余的,感兴趣的读者可以继续研究并在FPGA上实现其他算法,例如均值滤波,其与中值滤波的区别只是在所求数据上(均值)不同,模块层次基本一致,当然还有腐蚀、膨胀、二值化、sobel边缘检测等图像处理常见的操作。安路的SF1同时具备FPGA 与 MCU两部分资源,FPGA在进行算法加速的方面优势突出,而MCU则适合进行一些控制操作,将二者结合起来使用会大大减少开发难度,当然前提是需要同时了解MCU与FPGA的相关知识与运用。
遗憾的是,SF1 DEMO板上使用的均是mipi接口的摄像头与屏幕,目前身边没有这样的摄像头与屏幕,所以上板测试只能缓缓,有机会一定测试一下。
- 2023-04-03
-
回复了主题帖:
国产FPGA安路SF1系列测评【使用SF1 FPGA 进行6自由度机械臂控制】
这是工程源码
- 2023-04-02
-
回复了主题帖:
国产FPGA安路SF1系列测评【使用SF1 FPGA 进行6自由度机械臂控制】
秦天qintian0303 发表于 2023-4-2 08:25
6自由度机械臂控制重点还是算法吧
是的,驱动机械臂需要进行运动的分析建模,抓取物体还需用到传感器定位物体坐标。FPGA端可以用来完成舵机控制、接口扩展、信号处理等,MCU部分可以用来完成轨迹控制,运动分析等。这篇文章中只是进行了较简单的转动角度转换计算与控制。
- 2023-04-01
-
发表了主题帖:
国产FPGA安路SF1系列测评【使用SF1 FPGA 进行6自由度机械臂控制】
本期测评内容是使用 SF1 FPGA 控制六自由度机械臂进行物体的简单抓取,使用到的软件为安路的 TangDynasty 。
六自由度机械臂顾名思义拥有六个转动关节,本次测评使用的是六个转动角度为 0°-270°的舵机,控制周期为20ms,脉冲范围为0.5ms-2.5ms,对应的转动角度为 0°-270°,且二者呈线性关系,公式为 y=x/135+0.5 ,(x为角度,y为脉冲时间ms)。舵机转动的原理就是输出PWM脉冲信号,通过控制脉冲范围(0.5ms-2.5ms),进而控制舵机转动角度。
本次使用时钟是25MHz。这样20ms的控制周期对应的计数值应为 19'd500_000;将前面脉冲范围与转动角度的关系式(y=x/135+0.5)转换为计数个数的关系式,则为 TIME_CNT = Degree *185+12500,由于 FPGA 无法对小数进行计算且除法运算所占用的逻辑资源过大,我们将公式做如下转换:
TIME_CNT = Degree<<7 + Degree<<6 + Degree - Degree<<3 + 12500
这样 FPGA 就可以快速计算结果了,采用两级流水线即可得出角度对应的脉冲计数值,当cnt计数值小于计算所得的脉冲计数值时,输出高电平,否则输出低电平,这样就完成了6个舵机转动角度的控制了,这部分为在 pwm 模块中完成:
module pwm(
input clk,
input rst_n,
input en_in,
input change_flag,
input [8:0] degree0,
input [8:0] degree1,
input [8:0] degree2,
input [8:0] degree3,
input [8:0] degree4,
input [8:0] degree5,
output reg pwm_out0,
output reg pwm_out1,
output reg pwm_out2,
output reg pwm_out3,
output reg pwm_out4,
output reg pwm_out5
);
//parameter define
parameter TIME_20MS = 19'd500_000;
//reg define
reg [16:0] pwm_cnt0;
reg [16:0] pwm_cnt1;
reg [16:0] pwm_cnt2;
reg [16:0] pwm_cnt3;
reg [16:0] pwm_cnt4;
reg [16:0] pwm_cnt5;
reg [19:0] cnt;
reg [16:0] pwm_cnt0_m0,pwm_cnt0_m1,pwm_cnt0_m2,pwm_cnt0_m3;
reg [16:0] pwm_cnt1_m0,pwm_cnt1_m1,pwm_cnt1_m2,pwm_cnt1_m3;
reg [16:0] pwm_cnt2_m0,pwm_cnt2_m1,pwm_cnt2_m2,pwm_cnt2_m3;
reg [16:0] pwm_cnt3_m0,pwm_cnt3_m1,pwm_cnt3_m2,pwm_cnt3_m3;
reg [16:0] pwm_cnt4_m0,pwm_cnt4_m1,pwm_cnt4_m2,pwm_cnt4_m3;
reg [16:0] pwm_cnt5_m0,pwm_cnt5_m1,pwm_cnt5_m2,pwm_cnt5_m3;
//pipeline 1
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
pwm_cnt0_m0 <= 17'd0;pwm_cnt0_m1 <= 17'd0;pwm_cnt0_m2 <= 17'd0;pwm_cnt0_m3 <= 17'd0;
pwm_cnt1_m0 <= 17'd0;pwm_cnt1_m1 <= 17'd0;pwm_cnt1_m2 <= 17'd0;pwm_cnt1_m3 <= 17'd0;
pwm_cnt2_m0 <= 17'd0;pwm_cnt2_m1 <= 17'd0;pwm_cnt2_m2 <= 17'd0;pwm_cnt2_m3 <= 17'd0;
pwm_cnt3_m0 <= 17'd0;pwm_cnt3_m1 <= 17'd0;pwm_cnt3_m2 <= 17'd0;pwm_cnt3_m3 <= 17'd0;
pwm_cnt4_m0 <= 17'd0;pwm_cnt4_m1 <= 17'd0;pwm_cnt4_m2 <= 17'd0;pwm_cnt4_m3 <= 17'd0;
pwm_cnt5_m0 <= 17'd0;pwm_cnt5_m1 <= 17'd0;pwm_cnt5_m2 <= 17'd0;pwm_cnt5_m3 <= 17'd0;
end
else begin
pwm_cnt0_m0 <= degree0<<7;pwm_cnt0_m1 <= degree0<<6;pwm_cnt0_m2 <= degree0<<0;pwm_cnt0_m3 <= degree0<<3;
pwm_cnt1_m0 <= degree1<<7;pwm_cnt1_m1 <= degree1<<6;pwm_cnt1_m2 <= degree1<<0;pwm_cnt1_m3 <= degree1<<3;
pwm_cnt2_m0 <= degree2<<7;pwm_cnt2_m1 <= degree2<<6;pwm_cnt2_m2 <= degree2<<0;pwm_cnt2_m3 <= degree2<<3;
pwm_cnt3_m0 <= degree3<<7;pwm_cnt3_m1 <= degree3<<6;pwm_cnt3_m2 <= degree3<<0;pwm_cnt3_m3 <= degree3<<3;
pwm_cnt4_m0 <= degree4<<7;pwm_cnt4_m1 <= degree4<<6;pwm_cnt4_m2 <= degree4<<0;pwm_cnt4_m3 <= degree4<<3;
pwm_cnt5_m0 <= degree5<<7;pwm_cnt5_m1 <= degree5<<6;pwm_cnt5_m2 <= degree5<<0;pwm_cnt5_m3 <= degree5<<3;
end
end
//pipeline 2
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
pwm_cnt0 <= 17'd37475;
pwm_cnt1 <= 17'd37475;
pwm_cnt2 <= 17'd37475;
pwm_cnt3 <= 17'd37475;
pwm_cnt4 <= 17'd37475;
pwm_cnt5 <= 17'd37475;
end
else if(!en_in)begin
pwm_cnt0 <= pwm_cnt0_m0 + pwm_cnt0_m1 + pwm_cnt0_m2 - pwm_cnt0_m3 + 14'd12500;
pwm_cnt1 <= pwm_cnt1_m0 + pwm_cnt1_m1 + pwm_cnt1_m2 - pwm_cnt1_m3 + 14'd12500;
pwm_cnt2 <= pwm_cnt2_m0 + pwm_cnt2_m1 + pwm_cnt2_m2 - pwm_cnt2_m3 + 14'd12500;
pwm_cnt3 <= pwm_cnt3_m0 + pwm_cnt3_m1 + pwm_cnt3_m2 - pwm_cnt3_m3 + 14'd12500;
pwm_cnt4 <= pwm_cnt4_m0 + pwm_cnt4_m1 + pwm_cnt4_m2 - pwm_cnt4_m3 + 14'd12500;
pwm_cnt5 <= pwm_cnt5_m0 + pwm_cnt5_m1 + pwm_cnt5_m2 - pwm_cnt5_m3 + 14'd12500;
end
else begin
pwm_cnt0 <= 17'd37475;
pwm_cnt1 <= 17'd37475;
pwm_cnt2 <= 17'd37475;
pwm_cnt3 <= 17'd37475;
pwm_cnt4 <= 17'd37475;
pwm_cnt5 <= 17'd37475;
end
end
//20ms cnt
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt <= 20'd0;
else if(!en_in) begin
if(end_cnt)
cnt <= 20'd0;
else if(change_flag)
cnt <= 20'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 20'd0;
end
assign end_cnt = (cnt == (TIME_20MS-1'b1));
//pwm_out0
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
pwm_out0 <= 1'b0;
else begin
if(cnt<pwm_cnt0)
pwm_out0 <= 1'b1;
else
pwm_out0 <= 1'b0;
end
end
//pwm_out1
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
pwm_out1 <= 1'b0;
else begin
if(cnt<pwm_cnt1)
pwm_out1 <= 1'b1;
else
pwm_out1 <= 1'b0;
end
end
//pwm_out2
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
pwm_out2 <= 1'b0;
else begin
if(cnt<pwm_cnt2)
pwm_out2 <= 1'b1;
else
pwm_out2 <= 1'b0;
end
end
//pwm_out3
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
pwm_out3 <= 1'b0;
else begin
if(cnt<pwm_cnt3)
pwm_out3 <= 1'b1;
else
pwm_out3 <= 1'b0;
end
end
//pwm_out4
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
pwm_out4 <= 1'b0;
else begin
if(cnt<pwm_cnt4)
pwm_out4 <= 1'b1;
else
pwm_out4 <= 1'b0;
end
end
//pwm_out5
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
pwm_out5 <= 1'b0;
else begin
if(cnt<pwm_cnt5)
pwm_out5 <= 1'b1;
else
pwm_out5 <= 1'b0;
end
end
endmodule
之后新建 pwm_top 顶层模块,例化 pwm 模块,同时简单控制机械臂抓取物体,机械臂抓取的动作采用三段式状态机控制,分为四个动作:初始-转动-抓取-返回。该控制总共用时20s,每5s更新一次动作,使用按键 H3 (en_in,低电平有效)控制机械臂是否执行抓取:
module pwm_top(
input clk,
input rst_n,
input en_in,
output pwm_out0,
output pwm_out1,
output pwm_out2,
output pwm_out3,
output pwm_out4,
output pwm_out5
);
//parameter define
parameter CNT_1S = 26'd25_000_000;
parameter IDLE = 3'd0;
parameter STEP1 = 3'd1;
parameter STEP2 = 3'd2;
parameter STEP3 = 3'd3;
parameter STEP4 = 3'd4;
//reg define
reg [2:0] cur_state;
reg [2:0] nex_state;
reg [25:0] cnt;
reg [4:0] cnt_s;
reg [8:0] degree0;
reg [8:0] degree1;
reg [8:0] degree2;
reg [8:0] degree3;
reg [8:0] degree4;
reg [8:0] degree5;
reg change_flag;
pwm u_pwm(
.clk (clk ),
.rst_n (rst_n),
.en_in (en_in),
.change_flag(change_flag),
.degree0 (degree0),
.degree1 (degree1),
.degree2 (degree2),
.degree3 (degree3),
.degree4 (degree4),
.degree5 (degree5),
.pwm_out0 (pwm_out0),
.pwm_out1 (pwm_out1),
.pwm_out2 (pwm_out2),
.pwm_out3 (pwm_out3),
.pwm_out4 (pwm_out4),
.pwm_out5 (pwm_out5)
);
//FSM1
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cur_state <= IDLE;
else
cur_state <= nex_state;
end
//FSM2
always @(*) begin
if(!rst_n)
nex_state = IDLE;
else begin
case(cur_state)
IDLE:if(!en_in)
nex_state = STEP1;
else
nex_state = IDLE;
STEP1:begin
if(!en_in)
if((cnt_s == 5'd4) && (cnt == CNT_1S - 1'b1))
nex_state = STEP2;
else
nex_state = STEP1;
else
nex_state = IDLE;
end
STEP2:begin
if(!en_in)
if((cnt_s == 5'd9) && (cnt == CNT_1S - 1'b1))
nex_state = STEP3;
else
nex_state = STEP2;
else
nex_state = IDLE;
end
STEP3:begin
if(!en_in)
if((cnt_s == 5'd14) && (cnt == CNT_1S - 1'b1))
nex_state = STEP4;
else
nex_state = STEP3;
else
nex_state = IDLE;
end
STEP4:begin
if(!en_in)
if((cnt_s == 5'd19) && (cnt == CNT_1S - 1'b1))
nex_state = IDLE;
else
nex_state = STEP4;
else
nex_state = IDLE;
end
default:nex_state = IDLE;
endcase
end
end
//FSM3
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
degree0 <= 9'd135;
degree1 <= 9'd135;
degree2 <= 9'd135;
degree3 <= 9'd135;
degree4 <= 9'd135;
degree5 <= 9'd135;
end
else begin
case(cur_state)
IDLE :begin
degree0 <= 9'd135;
degree1 <= 9'd135;
degree2 <= 9'd135;
degree3 <= 9'd135;
degree4 <= 9'd135;
degree5 <= 9'd135;
end
STEP1:begin
degree0 <= 9'd225;
degree1 <= 9'd180;
degree2 <= 9'd45 ;
degree3 <= 9'd90 ;
degree4 <= 9'd135;
degree5 <= 9'd135;
end
STEP2:begin
degree0 <= 9'd225;
degree1 <= 9'd180;
degree2 <= 9'd45 ;
degree3 <= 9'd90 ;
degree4 <= 9'd135;
degree5 <= 9'd0 ;
end
STEP3:begin
degree0 <= 9'd135;
degree1 <= 9'd135;
degree2 <= 9'd135;
degree3 <= 9'd135;
degree4 <= 9'd135;
degree5 <= 9'd0 ;
end
STEP4:begin
degree0 <= 9'd135;
degree1 <= 9'd135;
degree2 <= 9'd135;
degree3 <= 9'd135;
degree4 <= 9'd135;
degree5 <= 9'd135;
end
default:begin
degree0 <= 9'd135;
degree1 <= 9'd135;
degree2 <= 9'd135;
degree3 <= 9'd135;
degree4 <= 9'd135;
degree5 <= 9'd135;
end
endcase
end
end
//1s
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt <= 26'd0;
else if(!en_in) begin
if(cnt == CNT_1S - 1'b1)
cnt <= 26'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 26'd0;
end
//20s
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_s <= 5'd0;
else if(!en_in) begin
if(cnt == CNT_1S - 1'b1) begin
if(cnt_s == 5'd20 - 1'b1)
cnt_s <= 5'd0;
else
cnt_s <= cnt_s + 1'b1;
end
else
cnt_s <= cnt_s;
end
else
cnt_s <= 5'd0;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
change_flag <= 1'b0;
else if(((cnt_s == 5'd4) && (cnt == CNT_1S - 1'b1)) || ((cnt_s == 5'd9) && (cnt == CNT_1S - 1'b1))
|| ((cnt_s == 5'd14) && (cnt == CNT_1S - 1'b1)) || ((cnt_s == 5'd19) && (cnt == CNT_1S - 1'b1)))
change_flag <= 1'b1;
else
change_flag <= 1'b0;
end
endmodule
进行引脚约束,pwm_out0~pwm_out5 分别对应舵机0~5 ,添加时钟约束:
create_clock -name clk -period 40 -waveform {0 20} [get_nets {clk}]
编译无误后将 bit 文件下载至开发板,机械臂中有三个舵机能正确执行操作,另外三个舵机出现抖动和无响应的现象,经过各种控制变量法查找原因均未能解决(机械臂无问题),然而相同代码在其他fpga上跑,机械臂能够正确执行。个人能力不足,原因尚待查找。
- 2023-03-25
-
发表了主题帖:
国产FPGA安路 高集成低功耗SF1系列FPSoC新品测评【使用SF1 FPGA 进行ADC转换】
本帖最后由 Grayson__ 于 2023-3-25 17:40 编辑
本期SF1测评使用 AD9280芯片与 SF1的 FPGA 部分进行模数转换,并将转换的数据通过uart IP核传输到电脑上。使用到的工具是安路的TD软件。
首先介绍一下 AD9280 ,它是 ADI 公司生产的 8 bit 、最高采样率为 32MHz的 A/D 转换器,A/D 转换器输入信号电压范围是 0~2V 。本次实验中我们将输入 -5V~+5V 的模拟电压,通过电压衰减电路后转换为 0~2V 即可进行 A/D 转换。A/D 的工作频率采用晶振输入的 25MHz。
本次实验中用到的 AD9280 转换器的转换图对应如下,这样8bit数据与输入电压的转换公式则为 y=25.5*x+127.5 (x为电压,y为量化后的8bit数据):
下面进行工程的设计。首先新建工程,配置 uart ip 核,如下,uart ip核配置的注意事项可在前几期中查看:
新建 AD 转换模块,代码如下:
module AD_rec(
input clk, //FPGA输入25MHz时钟
input rst_n, //低有效复位
input [7:0] ad_data, //AD输入数据
output ad_clk //AD驱动时钟
);
assign ad_clk = clk;
endmodule
新建AD_top顶层文件,例化 uart 与 AD 两个模块,设置 AD_top 为顶层模块,代码如下:
module AD_top(
//SYSTEM
input clk,
input rst_n,
//AD
input [7:0] ad_data,
output ad_clk,
//uart
output txd
);
AD_rec AD_rec_u1(
.clk(clk), //FPGA输入25MHz时钟
.rst_n(rst_n), //低有效复位
.ad_data(ad_data), //AD输入数据
.ad_clk(ad_clk) //AD驱动时钟
);
uart_interface uart_interface_u1(
.clk(clk),
.rst_n(rst_n),
.tx_data(ad_data),
.tx_en(1'b1),
.txd(txd)
);
endmodule
之后进行管脚约束:
set_pin_assignment { ad_clk } { LOCATION = B9; }
set_pin_assignment { ad_data[0] } { LOCATION = A10; }
set_pin_assignment { ad_data[1] } { LOCATION = A11; }
set_pin_assignment { ad_data[2] } { LOCATION = B10; }
set_pin_assignment { ad_data[3] } { LOCATION = B11; }
set_pin_assignment { ad_data[4] } { LOCATION = C10; }
set_pin_assignment { ad_data[5] } { LOCATION = C11; }
set_pin_assignment { ad_data[6] } { LOCATION = D10; }
set_pin_assignment { ad_data[7] } { LOCATION = D11; }
set_pin_assignment { clk } { LOCATION = D7; }
set_pin_assignment { rst_n } { LOCATION = H3; }
set_pin_assignment { txd } { LOCATION = A4; }
编译无误后,进行AD转换器与FPGA的管脚连接,进行bit文件的下载。使用 TI 的 HPI-1000 口袋仪器发出范围为 -5V~+5V 的直流电。打开串口显示工具,当输入电压为0时,串口显示接收到的数据为 0x81 , 与前面公式转换一致; 当输入电压为 2V 时,串口接收到0xB4,符合公式;当输入电压为 3V 时,串口接收到0xCE,符合公式。
DEMO板上可使用的 IO 较少,且只有一个uart串口,因此工程实现的功能无法做得大一些,测评结果验证的方法也比较局限。本想使用RISCV核进行AD读取,并在DEMO板上的OLED 显示所测电压值,但是RISCV可配置的功能引脚有限,无法支持实现。感兴趣的朋友可以测量正弦波,将串口输出的数据保存并在 matlab 上绘制图像。
本次测评的工程如下:
- 2023-03-19
-
回复了主题帖:
国产FPGA安路 高集成低功耗SF1系列FPSoC新品测评【使用SF1 RISCV IP 核驱动 oled】
错误提示:在 IVAL、 IEN、 OEN、 OVAL寄存器部分的表格中,OEN 和 OVAL 处的名称标错了。
-
回复了主题帖:
国产FPGA安路 高集成低功耗SF1系列FPSoC新品测评【使用SF1 RISCV IP 核驱动 oled】
本次测评的完整工程。
-
发表了主题帖:
国产FPGA安路 高集成低功耗SF1系列FPSoC新品测评【使用SF1 RISCV IP 核驱动 oled】
本帖最后由 Grayson__ 于 2023-3-19 11:14 编辑
在本期测评中,我们使用安路 SF1 的 RISCV IP 核控制 oled 的显示功能,参考资料有安路 SF1 的 DS800、SF1 DEMO板的原理图、TN816、TN817、TN818。本次测评分为两个部分:FPGA与MCU,分别使用 TD 与 FD 软件进行编写。
首先是 TD 部分,打开 TD 软件,新建名为 oled 的工程。首先调用 RISCV IP核,具体设置如下图:
调用 PLL IP 核,具体设置如下图,DEMO外部晶振为25MHz,所以PLL输入时钟为25MHz,生成的100MHz用于驱动MCU:
新建 gpio_ctrl.v 文件,用于控制gpio的输入或输出:
module gpio_ctrl(
input gpio_dir, //1'b0:input 1'b1:output
input gpio_out,
inout gpio, //对应fpga端口
output gpio_in
);
assign gpio = gpio_dir ? gpio_out:1'bz;
assign gpio_in = gpio; //当作为输入时,直接将输入值赋值给gpio_in
endmodule
新建 ps_wrapper.v 用于例化 gpio_ctrl 与 MCU 模块:
module ps_wrapper(
input wire clk, //MCU的100MHz时钟
input wire rst,
input wire timer_clk, //fpga的25MHz时钟
input wire jtag_tck,
output wire jtag_tdo,
input wire jtag_tms,
input wire jtag_tdi,
input wire uart_rx,
output wire uart_tx,
inout wire gpio_0,
inout wire gpio_1,
inout wire gpio_2,
inout wire gpio_3,
inout wire gpio_4
);
wire gpio0_out;
wire gpio1_out;
wire gpio2_out;
wire gpio3_out;
wire gpio4_out;
wire gpio0_dir;
wire gpio1_dir;
wire gpio2_dir;
wire gpio3_dir;
wire gpio4_dir;
wire gpio0_in;
wire gpio1_in;
wire gpio2_in;
wire gpio3_in;
wire gpio4_in;
gpio_ctrl u0_gpio_ctrl(
.gpio_dir(gpio0_dir),
.gpio_out(gpio0_out),
.gpio(gpio_0),
.gpio_in(gpio0_in)
);
gpio_ctrl u1_gpio_ctrl(
.gpio_dir(gpio1_dir),
.gpio_out(gpio1_out),
.gpio(gpio_1),
.gpio_in(gpio1_in)
);
gpio_ctrl u2_gpio_ctrl(
.gpio_dir(gpio2_dir),
.gpio_out(gpio2_out),
.gpio(gpio_2),
.gpio_in(gpio2_in)
);
gpio_ctrl u3_gpio_ctrl(
.gpio_dir(gpio3_dir),
.gpio_out(gpio3_out),
.gpio(gpio_3),
.gpio_in(gpio3_in)
);
gpio_ctrl u4_gpio_ctrl(
.gpio_dir(gpio4_dir),
.gpio_out(gpio4_out),
.gpio(gpio_4),
.gpio_in(gpio4_in)
);
MCU u_MCU(
.soft_ip_apbm_en (1'b0),
.qspi0cfg1_mode (1'b1),
.qspi0cfg2_mode (1'b1),
.jtag_tck (jtag_tck),
.jtag_tdo (jtag_tdo),
.jtag_tms (jtag_tms),
.jtag_tdi (jtag_tdi),
.apb_clk ( ),
.apb_paddr ( ),
.apb_pwrite ( ),
.apb_penable ( ),
.apb_pprot ( ),
.apb_pstrobe ( ),
.apb_psel ( ),
.apb_pwdata ( ),
.apb_prdata ( ),
.apb_pready ( ),
.apb_pslverr ( ),
.uart_tx (uart_tx),
.uart_rx (uart_rx),
.gpio0_out (gpio0_out),
.gpio0_dir (gpio0_dir),
.gpio0_in (gpio0_in ),
.gpio1_out (gpio1_out),
.gpio1_dir (gpio1_dir),
.gpio1_in (gpio1_in ),
.gpio2_out (gpio2_out),
.gpio2_dir (gpio2_dir),
.gpio2_in (gpio2_in ),
.gpio3_out (gpio3_out),
.gpio3_dir (gpio3_dir),
.gpio3_in (gpio3_in ),
.gpio4_out (gpio4_out),
.gpio4_dir (gpio4_dir),
.gpio4_in (gpio4_in ),
.core_clk (clk),
.timer_clk (timer_clk),
.core_reset (rst),
.mtip ( ),
.nmi ( ),
.clic_irq ( ),
.sysrstreq ( ),
.apb_clk_down ( ),
.apb_paddr_down ( ),
.apb_penable_down ( ),
.apb_pprot_down ( ),
.apb_prdata_down ( ),
.apb_pready_down ( ),
.apb_pslverr_down ( ),
.apb_pstrobe_down ( ),
.apb_pwdata_down ( ),
.apb_pwrite_down ( ),
.apb_psel0_down ( ),
.apb_psel1_down ( ),
.apb_psel2_down ( )
);
endmodule
新建 top.v ,用于例化 PLL 与 ps_wrapper 模块,需要注意的是,为了简化代码,我们只使用了4个GPIO:
module top(
input clk_25m,
input rst_n,
input jtag_tck,
input jtag_tms,
input jtag_tdi,
output jtag_tdo,
input uart_rx,
output uart_tx,
output oled_scl,
inout oled_sda,
output oled_rst_n,
output led
);
wire clk_100m;
wire rst;
assign rst = ~rst_n;
pll u_pll(
.refclk(clk_25m),
.reset(rst),
.clk0_out(clk_100m)
);
ps_wrapper u_ps_wrapper(
.clk(clk_100m), //MCU的100MHz时钟
.rst(rst),
.timer_clk(clk_25m), //fpga的25MHz时钟
.jtag_tck(jtag_tck),
.jtag_tdo(jtag_tdo),
.jtag_tms(jtag_tms),
.jtag_tdi(jtag_tdi),
.uart_rx(uart_rx),
.uart_tx(uart_tx),
.gpio_0(oled_scl),
.gpio_1(oled_sda),
.gpio_2(oled_rst_n),
.gpio_3(led)
);
endmodule
编译无误后添加管脚约束,之后重新编译综合,无误后 FPGA 部分的设置即完成:
set_pin_assignment { clk_25m } { LOCATION = D7; }
set_pin_assignment { jtag_tck } { LOCATION = C7; }
set_pin_assignment { jtag_tdi } { LOCATION = D5; }
set_pin_assignment { jtag_tdo } { LOCATION = C6; }
set_pin_assignment { jtag_tms } { LOCATION = D6; }
set_pin_assignment { led } { LOCATION = J5; }
set_pin_assignment { oled_rst_n } { LOCATION = H2; }
set_pin_assignment { oled_scl } { LOCATION = G4; }
set_pin_assignment { oled_sda } { LOCATION = J2; }
set_pin_assignment { rst_n } { LOCATION = H3; }
set_pin_assignment { uart_rx } { LOCATION = E4; }
set_pin_assignment { uart_tx } { LOCATION = A4; }
oled 的 scl 与 sda 引脚约束参考TN816:
下面进行 MCU 部分的设置,DEMO板上使用的是0.91寸 128*32 的 oled ,官方资料中没有讲述 oled 的具体型号,不过从官方例程来看,应该与0.91寸的SSD1306相同。程序编写步骤为:
主函数如下,需要注意的是,oled 的复位功能我们使用的是 DEMO上的 KEY1,但是main函数中我们将anlogic_log_display函数放在while循环外面,所以复位功能并未使用到,感兴趣的可以修改程序:
#include "stdio.h"
#include "nuclei_sdk_hal.h"
#include "iic.h"
int main(void)
{
anlogic_log_display();
while(1)
{
LED_High();
display_horizontal_scroll_enable();
delay_1ms(2270);
LED_Low();
display_horizontal_scroll_disable();
delay_1ms(4000);
}
return 0;
}
iic.c如下:
#include "iic.h"
#include "nuclei.h"
#include "gpio_ctrl.h"
//========================IIC协议操作===========================
//拉高SCL,GPIO 0
void SCL_High(void)
{
gpio_wr(0,1);
}
//拉低SCL,GPIO 0
void SCL_Low(void)
{
gpio_wr(0,0);
}
//SDA拉低,GPIO 1
void SDA_Low(void)
{
gpio_wr(1,0);
}
//SDA拉高,GPIO 1
void SDA_High(void)
{
gpio_wr(1,1);
}
//SDA读取数据,GPIO 1
int SDA_read(void)
{
int temp;
temp = gpio_rd(1);
if(temp == 1)
return 1;
else
return 0;
}
//LED输出高,GPIO 3
void LED_High(void)
{
gpio_wr(3,1);
}
//LED输出低,GPIO 3
void LED_Low(void)
{
gpio_wr(3,0);
}
//延时1us
void delay1us(void)
{
delay_1us(1);
}
void IIC_START(void)
{
SCL_Low(); // SCL拉低 防止可能出现的各种误动作
delay1us();
SDA_High(); // SDA拉高
SCL_High(); // SCL拉高 准备发出起始信号
delay1us();
SDA_Low(); // SDA拉低 发出起始信号
SCL_Low(); // SCL拉低 开始传输
}
void IIC_STOP(void)
{
SCL_Low(); // SCL拉低 防止可能出现的各种误动作
SDA_Low(); // SDA拉低
delay1us();
SCL_High(); // SCL拉高 准备发出结束信号
delay1us();
SDA_High(); // SDA拉高 发出结束信号
}
int IIC_WaitACK(void)
{
int s;
SCL_Low(); // 拉低SCL
delay1us();
SDA_High(); // 拉高SDA 主机释放总线
delay1us();
SCL_High(); // 拉高SCL
delay1us();
s = SDA_read(); // 采集SDA信号线状态
delay1us();
SCL_Low(); // 拉低SCL 结束询问ACK
if(s)
return 0; // 无应答(ACK)
else
return 1; // 有应答(ACK)
}
void IIC_Write(int dat)
{
int i;
int temp;
for(i=0; i<8; i++)
{
temp = dat & 0x80;
if(temp == 0x80)
SDA_High();
else
SDA_Low();
dat <<= 1; // 数据格式:高位在前
delay1us();
SCL_High(); // 拉高SCL 发送数据
delay1us();
SCL_Low(); // 拉低SCL 结束发送
}
}
//=============================OLED驱动设置=============================
void oled_display_reset() //显示复位
{
gpio_wr(2,0); //GPIO 2,即 oled_rst_n
delay_1us(10000);
gpio_wr(2,1);
delay_1us(10000);
}
void OLED_Write_cmd(int cmd) //写入命令
{
IIC_START();
IIC_Write(0X78); // 写从机地址'0111 100' 读写符号'0'
IIC_WaitACK();
IIC_Write(0X00); // 写命令
IIC_WaitACK();
IIC_Write(cmd);
IIC_WaitACK();
}
void OLED_Write_dat(int dat) //写数据
{
IIC_START(); // 通信开始
IIC_Write(0X78); // 写从机地址'0111 100' 读写符号'0'
IIC_WaitACK();
IIC_Write(0X40); // 写数据
IIC_WaitACK();
IIC_Write(dat);
IIC_WaitACK();
}
void OLED_Init(void) //OLED初始化
{
oled_display_reset();
OLED_Write_cmd(0XAE); // 关OLED显示
// 基础设置
OLED_Write_cmd(0XA4); // 输出GDDRAM内容
OLED_Write_cmd(0XA6); // 正常显示(1亮0灭)
OLED_Write_cmd(0X81); // 设置对比度
OLED_Write_cmd(0X7F); // 第127级对比度
// COM和SEG输出设置
OLED_Write_cmd(0XD3); // 设置垂直显示偏移(向上)
OLED_Write_cmd(0X00); // 偏移0行
OLED_Write_cmd(0X40); // 设置GDDRAM起始行 0
OLED_Write_cmd(0XA8); // 设置MUX数 (显示行数)
OLED_Write_cmd(0X3F); // MUX=63 (显示63行)
OLED_Write_cmd(0XA1); // 左右反置关(段重映射)
OLED_Write_cmd(0XC8); // 上下反置关(行重映射)
OLED_Write_cmd(0XDA); // 设置COM引脚配置
OLED_Write_cmd(0X02); // 序列COM配置,禁用左右反置
// 时钟设置
OLED_Write_cmd(0XD5); // 设置DCLK分频和OSC频率
OLED_Write_cmd(0X80); // 无分频,第8级OSC频率
// 开OLED
OLED_Write_cmd(0X8D); // 启用电荷泵
OLED_Write_cmd(0X14); // 启用电荷泵
OLED_Write_cmd(0XAF); // 开OLED显示
IIC_STOP();
}
void OLED_Clear(void) //清屏
{
int i,j;
OLED_Write_cmd(0X00); // 水平寻址模式
OLED_Write_cmd(0X21); // 设置列起始和结束地址
OLED_Write_cmd(0X00); // 列起始地址 0
OLED_Write_cmd(0X7F); // 列终止地址 127
OLED_Write_cmd(0X22); // 设置页起始和结束地址
OLED_Write_cmd(0X00); // 页起始地址 0
OLED_Write_cmd(0X07); // 页终止地址 7
for(i=0; i<8; i++) // 写入一帧'0'
for(j=0; j<128; j++)
OLED_Write_dat(0X00);
IIC_STOP();
}
void OLED_Frame(int P[8][128]) //写入图像数据
{
int i,j;
OLED_Write_cmd(0X20); // 设置GDDRAM模式
OLED_Write_cmd(0X00); // 水平寻址模式
OLED_Write_cmd(0X21); // 设置列起始和结束地址
OLED_Write_cmd(0X00); // 列起始地址 0
OLED_Write_cmd(0X7F); // 列终止地址 127
OLED_Write_cmd(0X22); // 设置页起始和结束地址
OLED_Write_cmd(0X00); // 页起始地址 0
OLED_Write_cmd(0X07); // 页终止地址 7
for(i=0; i<8; i++) // 写入一帧数据
{
for(j=0; j<128; j++)
{
OLED_Write_dat(P[i][j]);
IIC_STOP();
}
}
IIC_STOP();
}
void display_horizontal_scroll_enable()
{
OLED_Write_cmd(0x2f);//开启滚动
}
void display_horizontal_scroll_disable()
{
OLED_Write_cmd(0x2e);//关闭滚动
}
void anlogic_log_display(void)
{
int index=0;
int oled_display_data[8][128];
int anlogic_log_data[512] = {0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00,
0x0f, 0x80, 0x00, 0x00, 0x0f, 0x80, 0x00, 0xf0, 0x0f, 0x80, 0x00, 0xf8, 0x0f, 0x80, 0x00, 0x78,
0x07, 0x80, 0x00, 0x78, 0x07, 0xc0, 0x00, 0x78, 0x07, 0xc0, 0x00, 0x78, 0x07, 0xc0, 0x00, 0x78,
0x03, 0xe0, 0x00, 0x78, 0x03, 0xf0, 0x00, 0xf8, 0x01, 0xf8, 0x00, 0xf8, 0x00, 0xfc, 0x01, 0xf8,
0x00, 0x7f, 0xc7, 0xf0, 0x00, 0x3f, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xc0,
0x00, 0x03, 0xff, 0x80, 0x07, 0x00, 0x18, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00,
0x07, 0xff, 0xf8, 0x00, 0x07, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xf8,
0x00, 0x00, 0x7f, 0xf8, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x00, 0x00, 0x38, 0x07, 0x00, 0x00, 0x00,
0x07, 0xc1, 0xe0, 0x00, 0x07, 0xc0, 0xff, 0x00, 0x07, 0x80, 0xff, 0xf0, 0x07, 0x80, 0xff, 0xf0,
0x07, 0x80, 0x7f, 0xf0, 0x07, 0x80, 0x71, 0xf8, 0x07, 0x80, 0x20, 0xf8, 0x07, 0x80, 0x20, 0x78,
0x07, 0x80, 0x00, 0x78, 0x07, 0xc0, 0x00, 0x78, 0x03, 0xc0, 0x00, 0x78, 0x03, 0xe0, 0x00, 0xf8,
0x03, 0xf0, 0x00, 0xf8, 0x01, 0xf8, 0x00, 0xf8, 0x00, 0xfc, 0x01, 0xf8, 0x00, 0x7f, 0x87, 0xf0,
0x00, 0x3f, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xc0, 0x00, 0x03, 0xff, 0x80,
0x00, 0x00, 0x7f, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x7f, 0xe0, 0x00, 0x00, 0xff, 0xf8, 0x00,
0x01, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0x00, 0x07, 0xff, 0xff, 0xc0, 0x07, 0xf0, 0x3f, 0xe0,
0x0f, 0xc0, 0x0f, 0xe0, 0x0f, 0x80, 0x03, 0xf0, 0x0f, 0x80, 0x01, 0xf0, 0x0f, 0x80, 0x00, 0xf8,
0x0f, 0x80, 0x00, 0x78, 0x07, 0x80, 0x00, 0x78, 0x07, 0x80, 0x00, 0x78, 0x07, 0xc0, 0x00, 0x78,
0x03, 0xe0, 0x00, 0x78, 0x03, 0xf8, 0x00, 0xf8, 0x01, 0xfe, 0x01, 0xf8, 0x00, 0xff, 0x07, 0xf8,
0x00, 0x7f, 0xff, 0xf8, 0x00, 0x3f, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xe0, 0x00, 0x07, 0xff, 0xc0,
0x00, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x78,
0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x78, 0x07, 0x00, 0x00, 0x78,
0x07, 0xf0, 0x00, 0x78, 0x07, 0xff, 0x80, 0x78, 0x07, 0xff, 0xf8, 0x78, 0x07, 0xff, 0xff, 0xf8,
0x07, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xf8,
0x08, 0x00, 0x00, 0x78, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x20, 0x00, 0x00,
0x09, 0x24, 0x00, 0x00, 0x09, 0x24, 0x00, 0x00, 0x09, 0x24, 0x80, 0x00, 0x09, 0x24, 0x90, 0x00,
0x09, 0x24, 0x90, 0x00, 0x09, 0x24, 0x94, 0x00, 0x09, 0x24, 0x94, 0x80, 0x09, 0x24, 0x94, 0x90,
0x09, 0x24, 0x94, 0x80, 0x09, 0x24, 0x94, 0x30, 0x09, 0x24, 0x90, 0xf0, 0x09, 0x24, 0x93, 0xf0,
0x09, 0x24, 0x8f, 0xf0, 0x09, 0x24, 0x3f, 0xf0, 0x09, 0x24, 0xff, 0xe0, 0x09, 0x23, 0xff, 0x80,
0x09, 0x0f, 0xfe, 0x00, 0x08, 0x3f, 0xf8, 0x10, 0x08, 0xff, 0xe0, 0x70, 0x03, 0xff, 0x80, 0xf0,
0x07, 0xff, 0x00, 0xf0, 0x03, 0xff, 0xc0, 0xf0, 0x00, 0x7f, 0xf0, 0xf0, 0x00, 0x1f, 0xfc, 0xf0,
0x00, 0x07, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xf0, 0x00, 0x00, 0x7f, 0xf0, 0x00, 0x00, 0x1f, 0xf0,
0x00, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x10};
//图像数据映射为SSD1306的显示数据
for(int j=0; j<128; j++)
{
for(int i=7; i>=0; i--)
{
if(i > 3)
{
oled_display_data[i][j] = anlogic_log_data[index];
index++;
}
else
oled_display_data[i][j] = 0x00;
}
}
OLED_Init(); //初始化
OLED_Clear(); //清屏
OLED_Write_cmd(0x2e);//关闭滚动
OLED_Frame(oled_display_data); //写入图像
OLED_Write_cmd(0x27); //水平向左或者右滚动 26/27
OLED_Write_cmd(0x00); //虚拟字节
OLED_Write_cmd(0x00); //起始页 0
OLED_Write_cmd(0x07); //滚动时间间隔
OLED_Write_cmd(0x07); //终止页 7
OLED_Write_cmd(0x00); //虚拟字节
OLED_Write_cmd(0xff); //虚拟字节
OLED_Write_cmd(0x2f); //开启滚动
}
需要注意的是,在iic.c文件中使用到了delay_1ms与delay_1us函数,头文件nuclei.h中只存在delay_1ms,我们需进入nuclei_common.c中添加delay_1us函数,并在nuclei.h声明,delay_1us函数参考的是官方例程。
gpio_ctrl.c用于控制4个gpio输入输出:
#include "gpio_ctrl.h"
#include "global.h"
void gpio_wr(int index,int wr_data) //GPIO输出
{
switch(index)
{
case 0: //GPIO 0,即 scl 控制
gpio_in_en = gpio_in_en & 0xfffffffe; //IEN寄存器值共32位,GPIO 0为第0位,输入不使能
if(wr_data == 1)
gpio_out = gpio_out | 0x00000001; //OVAL寄存器值共32位,GPIO 0为第0位,输出1
else
gpio_out = gpio_out & 0xfffffffe; //输出0
gpio_out_en = gpio_out_en | 0x00000001; //OEN寄存器值共32位,GPIO 0为第0位,输出使能
break;
case 1: //GPIO 1,即 sda 控制
gpio_in_en = gpio_in_en & 0xfffffffd; //IEN寄存器值共32位,GPIO 1为第1位,输入不使能
if(wr_data == 1)
gpio_out = gpio_out | 0x00000002; //OVAL寄存器值共32位,GPIO 1为第1位,输出1
else
gpio_out = gpio_out & 0xfffffffd; //输出0
gpio_out_en = gpio_out_en | 0x00000002; //OEN寄存器值共32位,GPIO 1为第1位,输出使能
break;
case 2: //GPIO 2,即 oled_rst_n 控制
gpio_in_en = gpio_in_en & 0xfffffffb;
if(wr_data == 1)
gpio_out = gpio_out | 0x00000004;
else
gpio_out = gpio_out & 0xfffffffb;
gpio_out_en = gpio_out_en | 0x00000004;
break;
case 3: //GPIO 3,即 led 控制
gpio_in_en = gpio_in_en & 0xfffffff7;
if(wr_data == 1)
gpio_out = gpio_out | 0x00000008;
else
gpio_out = gpio_out & 0xfffffff7;
gpio_out_en = gpio_out_en | 0x00000008;
break;
case 4: //GPIO 4,未使用
gpio_in_en = gpio_in_en & 0xffffffef;
if(wr_data == 1)
gpio_out = gpio_out | 0x00000010;
else
gpio_out = gpio_out & 0xffffffef;
gpio_out_en = gpio_out_en | 0x00000010;
break;
case 5: //GPIO 5,未使用
gpio_in_en = gpio_in_en & 0xffffffdf;
if(wr_data == 1)
gpio_out = gpio_out | 0x00000020;
else
gpio_out = gpio_out & 0xffffffdf;
gpio_out_en = gpio_out_en | 0x00000020;
break;
case 6: //GPIO 6,未使用
gpio_in_en = gpio_in_en & 0xffffffbf;
if(wr_data == 1)
gpio_out = gpio_out | 0x00000040;
else
gpio_out = gpio_out & 0xffffffbf;
gpio_out_en = gpio_out_en | 0x00000040;
break;
case 7: //GPIO 7,未使用
gpio_in_en = gpio_in_en & 0xffffff7f;
if(wr_data == 1)
gpio_out = gpio_out | 0x00000080;
else
gpio_out = gpio_out & 0xffffff7f;
gpio_out_en = gpio_out_en | 0x00000080;
break;
default:
break;
}
}
int gpio_rd(int index) //GPIO输入
{
int temp;
switch(index)
{
case 0: //GPIO 0,即 scl 控制
gpio_in_en = gpio_in_en | 0x00000001; //IEN寄存器值共32位,GPIO 0为第0位,输入使能
gpio_out_en = gpio_out_en & 0xfffffffe; //OEN寄存器值共32位,GPIO 0为第0位,输出不使能
temp = gpio_in;
temp = temp & 0x00000001;
if(temp == 0x00000001)
return 1;
else
return 0;
break;
case 1: //GPIO 1,即 sda 控制
gpio_in_en = gpio_in_en | 0x00000002;
gpio_out_en = gpio_out_en & 0xfffffffd;
temp = gpio_in;
temp = temp & 0x00000002;
if(temp == 0x00000002)
return 1;
else
return 0;
break;
case 2: //GPIO 2,即 oled_rst_n 控制
gpio_in_en = gpio_in_en | 0x00000004;
gpio_out_en = gpio_out_en & 0xfffffffb;
temp = gpio_in;
temp = temp & 0x00000004;
if(temp == 0x00000004)
return 1;
else
return 0;
break;
case 3: //GPIO 1,即 led 控制
gpio_in_en = gpio_in_en | 0x00000008;
gpio_out_en = gpio_out_en & 0xfffffff7;
temp = gpio_in;
temp = temp & 0x00000008;
if(temp == 0x00000008)
return 1;
else
return 0;
break;
case 4:
gpio_in_en = gpio_in_en | 0x00000010;
gpio_out_en = gpio_out_en & 0xffffffef;
temp = gpio_in;
temp = temp & 0x00000010;
if(temp == 0x00000010)
return 1;
else
return 0;
break;
case 5:
gpio_in_en = gpio_in_en | 0x00000020;
gpio_out_en = gpio_out_en & 0xffffffdf;
temp = gpio_in;
temp = temp & 0x00000020;
if(temp == 0x00000020)
return 1;
else
return 0;
break;
case 6:
gpio_in_en = gpio_in_en | 0x00000040;
gpio_out_en = gpio_out_en & 0xffffffbf;
temp = gpio_in;
temp = temp & 0x00000040;
if(temp == 0x00000040)
return 1;
else
return 0;
break;
case 7:
gpio_in_en = gpio_in_en | 0x00000080;
gpio_out_en = gpio_out_en & 0xffffff7f;
temp = gpio_in;
temp = temp & 0x00000080;
if(temp == 0x00000080)
return 1;
else
return 0;
break;
}
return 0;
}
其中global.h文件中声明了对 IVAL、 IEN、 OEN、 OVAL 四个寄存器的地址,将其单独拿出来声明是为了防止出现多重定义的警告,GPIO器件地址在官方资料中没有找到,在官方给的例程中可以找到,IVAL、 IEN、 OEN、 OVAL 四个寄存器的地址及使用方法在TN817中有介绍:
于是global.h文件中定义如下:
#ifndef APPLICATION_GLOBAL_H_
#define APPLICATION_GLOBAL_H_
#define GPIO_BASE_ADDRESS 0xE0020000
long unsigned int *GPIO_IVAL = GPIO_BASE_ADDRESS + 0x00000000;
long unsigned int *GPIO_IEN = GPIO_BASE_ADDRESS + 0x00000004;
long unsigned int *GPIO_OEN = GPIO_BASE_ADDRESS + 0x00000008;
long unsigned int *GPIO_OVAL = GPIO_BASE_ADDRESS + 0x0000000c;
#define gpio_in *GPIO_IVAL
#define gpio_in_en *GPIO_IEN
#define gpio_out_en *GPIO_OEN
#define gpio_out *GPIO_OVAL
#endif /* APPLICATION_GLOBAL_H_ */
编译无误后 FD 部分完成,我们打开 TD 下载 bit 与 hex 文件至芯片中,下载方法在前几期中均已讲述,本期测评的下载方式与之前一致:
显示效果是安路logo按一定时间滚动,并且在滚动时LEDB亮,不滚动时LEDB熄灭。
源码在上文中均已给出,相关注释也已加上,需要的可以参考。
对于 oled 驱动不熟悉的可以参考0.91寸的SSD1306 ,需要注意的是 DEMO 板上标示的 oled SDA是J4,而资料中给的是J2,这里是板子上标错了。
与同行测评的一篇 oled 对比来看,本测评中并未添加时钟约束,而 FPGA JTAG 与 MCU JTAG 均能连接,产生区别的原因尚未找到。
- 2023-03-12
-
加入了学习《TI-RSLK 模块 12 - 直流电机》,观看 实验视频 12.2 - 演示机器人以预设模式移动
-
回复了主题帖:
国产FPGA安路 高集成低功耗SF1系列FPSoC新品测评【使用SF1 uart IP 核通信】
本帖最后由 Grayson__ 于 2023-3-12 11:36 编辑
fxyc87 发表于 2023-3-12 09:22 示波器看下,官方主的IP核应该不会这么菜吧? 还有就是误差太大,超过了3%
在ip核中,重新把波特率设置小一点,比如57600、38400这些,电脑接收到的fpga发送的数据是正确的。
-
回复了主题帖:
国产FPGA安路 高集成低功耗SF1系列FPSoC新品测评【使用SF1 uart IP 核通信】
Jacktang 发表于 2023-3-12 09:22
用SF1的uart ip核进行电脑与fpga的通信实验的主要注意事项有哪些
Incoming clock rate 输入频率: 频率范围为<= 200Mhz,默认为 50 000 000Hz。对于不同的 波特率的具体限制如下:
当波特率为 9600 时, 输入时钟要求 >=2MHz;
当波特率为 19200 时, 输入时钟要求 >=4MHz;
当波特率为 38400 时, 输入时钟要求 >=8MHz;
当波特率为 57600 时, 输入时钟要求 >=12MHz;
当波特率为 115200 时,输入时钟要求 >=24MHz。
- 2023-03-11
-
发表了主题帖:
国产FPGA安路 高集成低功耗SF1系列FPSoC新品测评【使用SF1 uart IP 核通信】
上期我们使用了mcu中的uart通信,本期我们使用SF1的uart ip核进行电脑与fpga的通信实验。
首先新建TD工程,在ip generator中选择uart,修改输入时钟频率为25MHz,波特率115200,数据位8位,停止位1位,如下图所示
顶层代码如下,功能是当fpga接收的数据为8‘h32时,向电脑串口发送8’hf0:
module uart_top(
input clk,
input rst_n,
input rxd,
output txd
);
wire [7:0] tx_data;
wire [7:0] rx_data;
wire rx_vld;
wire tx_en;
uart_interface uart_interface_u1(
.clk(clk),
.rst_n(rst_n),
.rxd(rxd),
.tx_data(tx_data),
.tx_en(tx_en),
.rx_data(rx_data),
.rx_err(),
.rx_vld(),
.tx_rdy(),
.txd(txd)
);
assign tx_data = 8'hf0;
assign tx_en = rx_data==8'h32 ? 1'b1 : 1'b0;
endmodule
结果如下:
但是在实验中遇到一个问题,就是不管fpga发送的8位数据的最高位是0还是1,在电脑接收到的8位数据的最高位总是1,比如发送8‘h01,电脑显示接收到的数据为 81(16进制)。
希望得到同行点播。
- 2023-03-05
-
回复了主题帖:
国产FPGA安路 高集成低功耗SF1系列FPSoC新品测评【使用SF1 RISCV ip核控制uart】
修改标题为【使用SF1 RISCV ip核控制uart】
- 2023-02-27
-
发表了主题帖:
国产FPGA安路 高集成低功耗SF1系列FPSoC新品测评【使用SF1 RISCV ip核控制led】
本期进行“SF1 RISCV ip核控制led”的测试,此次测试同时使用到了安路的TD与FD软件,在FD软件部分,我们将进行debug的调试测试。
首先是TD的fpga部分编写,工程新建步骤不再叙述,需要的请查看之前一期的文章。
使用Tool-->ip generator,生成pll ip核以及SF1 RISCV ip核,如下
顶层模块如下所示,其中clk_25m是fpga系统时钟25MHz,pll生成的100MHz是用于mcu的工作时钟:
module top(
input clk_25m,
input rst_n,
input jtag_tck,
input jtag_tms,
input jtag_tdi,
output jtag_tdo,
output led0,
output led1,
output led2
);
wire clk_100m;
wire rst;
assign rst = ~rst_n;
PLL u_PLL(
.refclk(clk_25m),
.reset(rst),
.clk0_out(clk_100m)
);
SF1_SoC u_SF1_SoC(
.clk(clk_100m), //MCU的100MHz时钟
.rst(rst),
.timer_clk(clk_25m), //fpga的25MHz时钟
.jtag_tck(jtag_tck),
.jtag_tdo(jtag_tdo),
.jtag_tms(jtag_tms),
.jtag_tdi(jtag_tdi),
.gpio_0(led0),
.gpio_1(led1),
.gpio_2(led2)
);
endmodule
SF1_SoC模块是控制mcu的模块:
module SF1_SoC(
input clk, //MCU的100MHz时钟
input rst,
input timer_clk, //fpga的25MHz时钟
input jtag_tck,
output jtag_tdo,
input jtag_tms,
input jtag_tdi,
inout gpio_0,
inout gpio_1,
inout gpio_2
);
wire gpio0_out;
wire gpio1_out;
wire gpio2_out;
wire gpio0_dir;
wire gpio1_dir;
wire gpio2_dir;
wire gpio0_in;
wire gpio1_in;
wire gpio2_in;
gpio_ctrl u0_gpio_ctrl(
.gpio_dir(gpio0_dir), //1'b0:input 1'b1:output
.gpio_out(gpio0_out),
.gpio(gpio_0), //对应fpga端口
.gpio_in(gpio0_in)
);
gpio_ctrl u1_gpio_ctrl(
.gpio_dir(gpio1_dir), //1'b0:input 1'b1:output
.gpio_out(gpio1_out),
.gpio(gpio_1), //对应fpga端口
.gpio_in(gpio1_in)
);
gpio_ctrl u2_gpio_ctrl(
.gpio_dir(gpio2_dir), //1'b0:input 1'b1:output
.gpio_out(gpio2_out),
.gpio(gpio_2), //对应fpga端口
.gpio_in(gpio2_in)
);
SF1_MCU u0_SF1_MCU(
.soft_ip_apbm_en (1'b0),
.qspi0cfg1_mode (1'b1),
.qspi0cfg2_mode (1'b1),
.jtag_tck (jtag_tck),
.jtag_tdo (jtag_tdo),
.jtag_tms (jtag_tms),
.jtag_tdi (jtag_tdi),
.apb_clk ( ),
.apb_paddr ( ),
.apb_pwrite ( ),
.apb_penable ( ),
.apb_pprot ( ),
.apb_pstrobe ( ),
.apb_psel ( ),
.apb_pwdata ( ),
.apb_prdata ( ),
.apb_pready ( ),
.apb_pslverr ( ),
.uart_tx ( ),
.uart_rx ( ),
.gpio0_out (gpio0_out),
.gpio0_dir (gpio0_dir),
.gpio0_in (gpio0_in ),
.gpio1_out (gpio1_out),
.gpio1_dir (gpio1_dir),
.gpio1_in (gpio1_in ),
.gpio2_out (gpio2_out),
.gpio2_dir (gpio2_dir),
.gpio2_in (gpio2_in ),
.core_clk (clk),
.timer_clk (timer_clk),
.core_reset (rst),
.mtip ( ),
.nmi ( ),
.clic_irq ( ),
.sysrstreq ( ),
.apb_clk_down ( ),
.apb_paddr_down ( ),
.apb_penable_down ( ),
.apb_pprot_down ( ),
.apb_prdata_down ( ),
.apb_pready_down ( ),
.apb_pslverr_down ( ),
.apb_pstrobe_down ( ),
.apb_pwdata_down ( ),
.apb_pwrite_down ( ),
.apb_psel0_down ( ),
.apb_psel1_down ( ),
.apb_psel2_down ( )
);
endmodule
gpio_ctrl用于控制gpio的输入输出模式:
module gpio_ctrl(
input gpio_dir, //1'b0:input 1'b1:output
input gpio_out,
inout gpio, //对应fpga端口
output gpio_in
);
assign gpio = gpio_dir ? gpio_out:1'bz;
assign gpio_in = gpio; //当作为输入时,直接将输入值赋值给gpio_in
endmodule
引脚分配通过查找DEMO板的原理图得知:
编译综合无误后,TD部分即可完成。
FD部分,打开FD软件,file-->New-->Project,选择C Project,选择For sf1 SoC,点击next,选择work3模式(各模式的区别请查看FD使用手册),next:
例程使用gpio_demo, 点击finish,即可完成工程新建:
在官方例程中比较复杂,我们如下修改,使得三个led每隔0.5s交替闪烁:
#include <stdio.h>
#include "nuclei_sdk_hal.h"
#include "anl_printf.h"
#define LEDn 3
static uint32_t LED_CLORK[] = {SOC_LED_RED_GPIO_MASK, SOC_LED_GREEN_GPIO_MASK, SOC_LED_BLUE_GPIO_MASK};
void all_io_config(void)
{
//for output
for(int i=0; i<LEDn; i++)
{
gpio_enable_output(GPIO,LED_CLORK[i]);
gpio_write(GPIO,LED_CLORK[i],1);
}
}
int main(void)
{
all_io_config();
while(1)
{
for(int i=0; i<LEDn; i++)
{
gpio_toggle(GPIO,LED_CLORK[i]);
delay_1ms(500);
}
}
return 0;
}
点击编译,无误后会在Debug目录下生成.hex文件,FD部分完成:
接下来我们回到TD,将TD生成的bit文件与FD生成的hex文件导入:
将下载模式改成PROGRAM RISC IMG并选择hex文件,之后点击run即可将程序固化到fpga中(注意,只有当bit文件被选中时才可进行Run):
开发板的三个led每隔0.5s交替闪烁,当断电后重新上电,效果依旧,由此得出程序被固化到fpga中。
下面进行mcu部分的调试,此部分只需将risc调试器接入电脑即可:
首先在led闪烁的代码处添加断点:
检查Run-->debug configurations相关设置是否正确:
无误后点击debug:
点击运行至断点处,同时观察变量窗口中的i,以及开发板led灯的状况:
发现每按一次运行至断点处,变量 i 自增1 ,同时开发板的 led 变换一次,调试完成,点击红点结束。
FD部分的编译调试方法和其他编译器相似,入手难度不大。需要注意的是,当进行工程的导入时会报路径错误,具体解决方法可查看测评:
【国产FPGA安路 高集成低功耗SF1系列FPSoC新品】RISC-V开发环境搭建保姆级教程 http://bbs.eeworld.com.cn/thread-1234278-1-1.html
本次测评的工程如下:
- 2023-02-26
-
回复了主题帖:
国产FPGA安路 高集成低功耗SF1系列FPSoC新品测评【开发板资源介绍+流水灯】
火辣西米秀 发表于 2023-2-26 08:40
下载连接线用的什么接口?
安路的fpga下载器,type B + 10pin
- 2023-02-23
-
发表了主题帖:
国产FPGA安路 高集成低功耗SF1系列FPSoC新品测评【使用SF1 RISCV ip核控制uart】
本次我们测评的实验是用SF1的DEMO板设计 uart 通信实验:SF1的 fpga 部分调用 eMCU ip核中的uart接口,此部分用 TD 设计,在 FD 中使用 risc-v MCU 编写uart通信的c语言代码。最终实现DEMO板与电脑的串口通信。
1. 首先新建一个文件夹,再在里面新建 TD 、FD 的文件夹,分别用来保存 TD 软件和 FD 软件生成的文件。如下图。
2.打开 TD,New Project ,如下图
点击Tool里的 IP Generator ,选择 SF1 RISC-V ,勾选 UART Enable ,勾选三个gpio,勾选内部时钟使能,点击OK , 勾选位置后点击Yes,如下图
在软件左边的Project中将如下显示,双击uart_interface 即可打开生成的 ip 核代码,如下图
下面首先需要进行gpio的控制,主要就是输入输出方向的设置。新建 gpio_ctrl.v ,代码如下:
module gpio_ctrl(
input gpio_dir, //1'b0:input 1'b1:output
input gpio_out,
inout gpio, //对应fpga端口
output gpio_in
);
assign gpio = gpio_dir ? gpio_out:1'bz;
assign gpio_in = gpio; //当作为输入时,直接将输入值赋值给gpio_in
endmodule
接下来进行riscv ip核的控制,我们需要例化uart_interface.v 以及 gpio_ctrl.v 两个模块,在例化uart_interface.v中,core_clk 接口为MCU的输入时钟100MHz,timer_clk 为FPGA的时钟25MHz,需要注意的是riscv ip核的复位为高电平有效,例化代码如下:
module SF1_SoC(
input clk, //MCU的100MHz时钟
input rst, //active high
input timer_clk, //fpga的25MHz时钟
input jtag_tck,
output jtag_tdo,
input jtag_tms,
input jtag_tdi,
input uart_rx,
output uart_tx,
inout gpio_0,
inout gpio_1,
inout gpio_2
);
wire gpio0_out;
wire gpio1_out;
wire gpio2_out;
wire gpio0_dir;
wire gpio1_dir;
wire gpio2_dir;
wire gpio0_in;
wire gpio1_in;
wire gpio2_in;
gpio_ctrl u0_gpio_ctrl(
.gpio_dir(gpio0_dir), //1'b0:input 1'b1:output
.gpio_out(gpio0_out),
.gpio(gpio_0), //对应fpga端口
.gpio_in(gpio0_in)
);
gpio_ctrl u1_gpio_ctrl(
.gpio_dir(gpio1_dir), //1'b0:input 1'b1:output
.gpio_out(gpio1_out),
.gpio(gpio_1), //对应fpga端口
.gpio_in(gpio1_in)
);
gpio_ctrl u2_gpio_ctrl(
.gpio_dir(gpio2_dir), //1'b0:input 1'b1:output
.gpio_out(gpio2_out),
.gpio(gpio_2), //对应fpga端口
.gpio_in(gpio2_in)
);
uart_interface u0_uart_interface(
.soft_ip_apbm_en (1'b0),
.qspi0cfg1_mode (1'b1),
.qspi0cfg2_mode (1'b1),
.jtag_tck (jtag_tck),
.jtag_tdo (jtag_tdo),
.jtag_tms (jtag_tms),
.jtag_tdi (jtag_tdi),
.apb_clk ( ),
.apb_paddr ( ),
.apb_pwrite ( ),
.apb_penable ( ),
.apb_pprot ( ),
.apb_pstrobe ( ),
.apb_psel ( ),
.apb_pwdata ( ),
.apb_prdata ( ),
.apb_pready ( ),
.apb_pslverr ( ),
.uart_tx (uart_tx),
.uart_rx (uart_rx),
.gpio0_out (gpio0_out),
.gpio0_dir (gpio0_dir),
.gpio0_in (gpio0_in ),
.gpio1_out (gpio1_out),
.gpio1_dir (gpio1_dir),
.gpio1_in (gpio1_in ),
.gpio2_out (gpio2_out),
.gpio2_dir (gpio2_dir),
.gpio2_in (gpio2_in ),
.core_clk (clk ),
.timer_clk (timer_clk),
.core_reset (rst ),
.mtip ( ),
.nmi ( ),
.clic_irq ( ),
.sysrstreq ( ),
.apb_clk_down ( ),
.apb_paddr_down ( ),
.apb_penable_down ( ),
.apb_pprot_down ( ),
.apb_prdata_down ( ),
.apb_pready_down ( ),
.apb_pslverr_down ( ),
.apb_pstrobe_down ( ),
.apb_pwdata_down ( ),
.apb_pwrite_down ( ),
.apb_psel0_down ( ),
.apb_psel1_down ( ),
.apb_psel2_down ( )
);
endmodule
对于MCU的100MHz时钟,我们需要使用PLL ip核生成,其复位方式也是高电平复位。
之后新建工程的顶层文件 top.v,在里面例化各个子模块,需要注意的是需要手动设置 top.v 为顶层文件,代码如下:
module top(
input clk_25m,
input rst_n,
input jtag_tck,
input jtag_tms,
input jtag_tdi,
output jtag_tdo,
input uart_rx,
output uart_tx,
output led0,
output led1,
output led2
);
wire clk_100m;
wire rst;
assign rst = ~rst_n;
PLL u_PLL(
.refclk(clk_25m),
.reset(rst),
.clk0_out(clk_100m)
);
SF1_SoC u_SF1_SoC(
.clk(clk_100m), //MCU的100MHz时钟
.rst(rst),
.timer_clk(clk_25m), //fpga的25MHz时钟
.jtag_tck(jtag_tck),
.jtag_tdo(jtag_tdo),
.jtag_tms(jtag_tms),
.jtag_tdi(jtag_tdi),
.uart_rx(uart_rx),
.uart_tx(uart_tx),
.gpio_0(led0),
.gpio_1(led1),
.gpio_2(led2)
);
endmodule
之后进行引脚分配:
set_pin_assignment { clk_25m } { LOCATION = D7; }
set_pin_assignment { jtag_tck } { LOCATION = C7; }
set_pin_assignment { jtag_tdi } { LOCATION = D5; }
set_pin_assignment { jtag_tdo } { LOCATION = C6; }
set_pin_assignment { jtag_tms } { LOCATION = D6; }
set_pin_assignment { led0 } { LOCATION = J4; }
set_pin_assignment { led1 } { LOCATION = H5; }
set_pin_assignment { led2 } { LOCATION = J5; }
set_pin_assignment { rst_n } { LOCATION = H3; }
set_pin_assignment { uart_rx } { LOCATION = E4; }
set_pin_assignment { uart_tx } { LOCATION = A4; }
编译综合无误后TD部分完成。
下面进行FD的代码编写:
首先新建FD工程,选择uart_demo官方例程:
在例程中,我们做如下修改,程序的功能是在串口给板子发送 a 时,led0电平翻转,发送 b 时,led1电平翻转,发送 c时,led2电平翻转:
#include <stdio.h>
#include "nuclei_sdk_hal.h"
#include "nuclei_libopt.h"
void main(void)
{
char t;
uart_init(UART0, 115200,UART_BIT_LENGTH_8); //uart初始化,波特率115200,有效位八位
uart_config_stopbit(UART0,UART_STOP_BIT_1); //使用UART0,一位停止位
gpio_enable_output(GPIO,1 << 0); //使能三个gpio为输出模式
gpio_enable_output(GPIO,1 << 1);
gpio_enable_output(GPIO,1 << 2);
gpio_write(GPIO,1 << 0,1);
gpio_write(GPIO,1 << 1,1);
gpio_write(GPIO,1 << 2,1);
while(1)
{
t = uart_read(UART0);
if(t == 'a')
{
gpio_toggle(GPIO,1 << 0); //led0反转
}
if(t == 'b')
{
gpio_toggle(GPIO,1 << 1); //led1反转
}
if(t == 'c')
{
gpio_toggle(GPIO,1 << 2); //led2反转
}
}
}
在官方例程中默认的系统时钟是80MHz,我们需要将其修改为100MHz,如下,在system_nuclei.c中:
需要注意的是,官方的例程中uart波特率设置的时钟还在系统时钟的基础上进行了2分频,我们需在startup_anlogic.S 将0x83(2分频)修改为0x81(1分频)。
具体的寄存器配置方法请参考官方手册 TN817。
烧录程序之后显示CPU频率为100MHz,在分别发送 a b c 后,板子呈现的效果与预期相同。
对于一些寄存器以及时钟的配置还是需要仔细查阅手册,需要多一点耐心。
本次工程如下:
- 2023-02-20
-
发表了主题帖:
国产FPGA安路 高集成低功耗SF1系列FPSoC新品测评【开发板资源介绍+流水灯】
上期我们进行了开发环境的搭建,本期我们将进行开发板资源的介绍,并且运用Verilog语言进行简单的流水灯设计。
开发板资源介绍:
1.SF1S60CG121I器件特点
LUT4/LUT5混合结构,等效LUT4逻辑规模为5824;
最大支持45.5Kb分布式和234Kb嵌入式存储器;
源同步输入/输出接口;
支持 LVTTL33,LVCMOS(3.3/2.5/1.8/1.5/1.2V), LVDS( 3.3/2.5/1.8) 电平标准;
2.2Gbps per line MIPI 硬核控制器;
2x64Mb 200MHz 存储器+硬核控制器;
RISC-V MCU 硬核最大主频 160Mhz;
丰富的片上时钟资源;
支持多种配置模式;
增强了安全设计保护,拥有唯一的64 位 DNA。
2.SF102 DEMO 板可验证 FPGA+RISC-V 硬核, 也可通过对应的 MIPI 拓展口, 满足各种 MIPI 测试需求。开发板外接25MHz有源晶振,从 SF1S60CG121I 全局时钟管脚(IO_R1P_0,GCLK3)输入,由 Type-C 接口提供 5V 供电,
3.RISC_V MCU 硬核
支持RV32IMCA指令集,采用三级流水线设计,支持机器模式和用户模式,支持PMP,拥有通用外设 SPIM、IICM、UART、GPIO 等,配有专用的JTAG调试接口。MCU模块结构如下图所示:
4. FPGA配置。SF1 器件支持 4 种配置方式, 分别是从动串行, 从动并行, 内部 SPI 模式和 JTAG 配置模式。 内部 SPI 模式支持 x1/x2/x4 位宽。 配置模式由内部 feature 寄存器决定, 默认为内部 SPI 模式 x1。具体配置如下图所示。
5. SF1 系列 FPGA 内嵌 2 个多功能锁相环, 可实现高性能时钟管理功能。 可以实现时钟分频、 倍频、占空比调整、 输入和反馈时钟对准、 多相位时钟输出等功能。其特性表如下所示,PLL支持四种反馈模式:源同步模式、无补偿模式、普通模式、零延迟缓冲模式。PLL 的时钟源头可以来自于芯片引脚或者芯片内部的不同其他资源。
6. SF1的DSP模块安路科技在 SF1 芯片上为实现数字信号处理而做的一个专用乘法模块,可以配置成一个 18x18 乘法器, 或者配置成两个 9x9 乘法器。根据乘法器的操作模式, 可以用 18 bit 或 36 bit 的形式来使用输出寄存器对嵌入式乘法器的输出进行寄存。乘法器模块的体系结构如下图所示。
7. 输入输出逻辑单元IOL。SF1的输入输出寄存器逻辑都可以配置三种模式:普通输入输出模式、SDR输入输出模式,DDR输入输出模式。
8. 输入输出缓冲器IOB。SF1 的 IOB 均为增强型 IOBE, 统称为 IOB, 每个 IOB 包含输入、 输出和三态驱动器。 这些驱动器可以按照各种 I/O 标准配置。
以上信息参考安路 DS800_SF1_Datasheet 资料,可在安路官网查找。
流水灯设计:
本次实验使用到开发板的三个LED,使用 Verilog 语言进行流水灯模块的编写,流水模式为三个LED间隔100ms以此亮灭。首先打开TangDynasty(简称TD),在上栏Project中点击New Project,进行流水灯工程的新建。Device Family选择SF1,Device Name选择SF1S60CG121I,完成后点击OK,如下图所示。
新建工程完成后就可以进行.V文件的编写了。点击上栏Source中的New Source,勾选 Add To Project,点击OK。如下图所示。之后就可进行代码编写。代码见附件。
代码编写完成并保存后,双击IO Constraints,进行管脚约束,如下图,设置完成后点击左上角保存,选择Single Line,点击OK。
之后后依次双击Read Design,进行文件读入;Optimize RTL,进行RTL级优化;Optimize Gate,进行门级优化。也可直接右击Syn Opt然后点击Run。代码若无问题则如下图所示,本次均采用默认方式优化,具体优化方式不再叙述,可查看使用手册。对于布局布线以及生成bit流文件,都在Phy Opt中,具体操作与上方一样。
以上操作均无误后即可点击Download进行板级验证,将bit流文件下载到板子上。现象是3个LED呈现流水闪烁,间隔是100ms。
点击add,选择bit文件,之后点击Run即可下载。注意,在这之前需开启开发板电源。
演示视频
[localvideo]49c07ba5003f0a04f2fb4efd3b39e241[/localvideo]
工程代码
-
发表了主题帖:
国产FPGA安路 高集成低功耗SF1系列FPSoC新品测评【开箱及环境搭建篇】
本系列测评的是来自安路的高集成低功耗SF1系列FPSoC新品,具体为基于工业级器件 SF1S60CG121I 芯片设计的SF102_V2.0 DEMO( 以下简称 SF102 DEMO) 板。
开箱:
对比来看,安路的包装可谓即安全又充实:包括了一块DEMO板,一个ANFPGA-LINK下载器,以及数据线和转接器;板载资源除了SF1芯片等电子器件之外,主要还有按键、LED、蜂鸣器、TF卡插槽以及一块128*32的LCD屏幕等外设,当然还预留一些扩展接口;板子规格约为100*60mm。拿到板子之后给我的第一印象就是小巧而充实,看到用于初学的一些外设都具备,感觉非常适合学习使用。
板子上电后正常运行内部固化程序:
开发环境搭建:
SF1系列内部集成了 RISC-V MCU,针对此部分的设计我们需要使用到安路的 FutureDynasty 软件,软件发布路径: https://www.anlogic.com/product/software/5.html ,此软件免安装,下载软件包后进入软件目录直接点击 FutureDynasty.exe 即可启动软件,需要注意的是,安装路径不可带有中文
针对FPGA部分的开发需要使用安路的 TangDynasty 软件,此软件可于安路官网下载 TangDynasty(TD)-开发软件-国产FPGA创新者 - 安路科技 (anlogic.com) ,安装好后需要在官网申请license,需要将 .lic 文件放入安装目录下的license文件夹里
该软件具体使用教程可在上方栏中的Help里点击 Software User Guide 查看,教程很详细,包括许多实例模块教程;当然若是想要快速入门使用也可在安路官网查看视频教学。
本期测评准备好开发工具,下期我们将进行开发板资源的介绍以及简单程序的写入。