本帖最后由 瓜弟 于 2023-3-1 00:39 编辑
在上篇文章中,介绍了SF1系列FPGA的工程建立、下载调试等方法。本文将继续在上一篇的基础上介绍串口通信的使用和使用AHB总线进行LED点灯。
1、串口通信的使用
实际上,在创建的软件工程里,system_nuclei.c文件里,函数void _init(void)已经进行了串口的初始化,但是会发现在使用中,上位机出现乱码。需要在system_nuclei.c文件中,更改SYSTEM_CLOCK宏定义,将默认的80MHz更改为实际中PLL输出的频率100MHz。
在main.c中添加如下串口输出代码。理想情况下,串口会每秒输出一次。但是我们在串口上位机中设为波特率为115200后,会发现,接收到的仍是乱码,如果我们将波特率更改为57600(115200的一半)后,会发现能正常接收不乱码了,这是为何?
#include <stdio.h>
#include "nuclei_sdk_hal.h"
#include "anl_printf.h"
#include "nuclei_uart.h"
int main(void)
{
gpio_enable_output(GPIO, 1);
gpio_enable_input(GPIO, 2);
while(1)
{
if(gpio_read(GPIO, 2) != 0)
{
gpio_toggle(GPIO, 1);
delay_1ms(1000);
printf("anlogic\n");
}
}
}
那就是串口外设的工作频率不是100MHz,在《TN817_SF1 MCU用户指南》中4.3章节中,可知,串口外设的工作频率与CPU工作频率一致,那么到底是多少?且为什么delay_1ms(1000)函数又能准确执行呢?因为delay_1ms使用滴答定时器实现,而滴答定时器的时钟输入又是单独的25MHz,在上篇文章中,RISCV核的例化使用了两个时钟,一个为100MHz的主时钟,另一个则是由晶振时钟引脚输入而来的25MHz。那么CPU到底实际工作频率是多少呢?在startup_anlogic.S文件中,有如下代码
其中CLK_REG_ADDR的定义在riscv_encoding.h文件中,值为0xE0000010,在《TN817_SF1 MCU用户指南》中,可以查到该地址的寄存器如下
对比代码可知,此处进行了二分频,CPU、串口工作的频率实际上只有50MHz。那么将启动代码中的0x83改为0x81即可正常使用。
2、使用AHB总线读写寄存器进行点灯
AHB是属于AMBA下的一个片内总线协议,为多主多从结构,通过将在FPGA侧的硬件代码依据AHB接口协议作为从机挂载在AHB总线上,并在硬件代码中指定地址,即可实现由RISCV-V硬核的软件代码通过指针对目标硬件代码功能的访问与控制。常用的片内总线还有AXI、APB、wishbone等,其中常见的是AXI、AHB、APB,wishbone常见于开源项目,在opencores.org可见许多项目使用wishbone。
SF1器件的AHB总线的可用地址范围未在数据手册中找到,根据官方的参考例程mcu_ahb_to_fpga,以及《TN817_SF1 MCU用户指南》猜测AHB的可用地址范围为0x4000_0000 ~ 0x5FFF_FFFF。可能由于可用空间较小,RISCV的AHB接口未提供HSELx片选信号,所以在实现过程中,不能使用HSELx信号作为状态机启动标志。且当有多个模块连接值AHB总线时,HREADY、HRESP、HRDATA等输出信号需要注意三态处理,本文仅有有一个模块使用AHB总线,故常规处理。
关于AHB总线的时许本文不再讲解,各种资料太多,但仍推荐阅读AMBA标准文档。本文在上一篇的基础上,使用一个reg型变量存储来自AHB写入的控制量,该变量将连接至LED的引脚(H5),地址为0x43214320,通过对该寄存器写0或1,实现对LED的点灯控制。代码如下
module AHB_LED
#(
parameter LED_ADDR = 32'h43214320
)
(
input wire HCLK,
input wire HRSTn,
input wire[1:0] HTRANS,
input wire[31:0] HADDR,
input wire HWRITE,
input wire[2:0] HSIEZ,
input wire[2:0] HBURST,
input wire[3:0] HPROT,
input wire[31:0] HWDATA,
output reg[31:0] HRDATA,
output reg HREADY,
output reg[1:0] HRESP,
output reg LED
);
parameter HTRANS_IDLE = 2'b00; //Slave忽略掉此时的传输
parameter HTRANS_BUSY = 2'b01; //表示master正在处理数据,slave需要忽略掉此时的传输
parameter HTRANS_NONSEQ = 2'b10; //表明当前是单笔的数据,或者是Burst的第一笔数据
parameter HTRANS_SEQ = 2'b11; //是Burst传输的剩余数据
parameter HBURST_SINGLE = 3'b000; //单笔数据传输
parameter HBURST_INCR = 3'b000; //不定长递增方式批量传输
parameter HBURST_WRAP4 = 3'b000; //4个数据回绕方式批量传输
parameter HBURST_INCR4 = 3'b000; //4个数据递增方式批量传输
parameter HBURST_WRAP8 = 3'b000; //8个数据回绕方式批量传输
parameter HBURST_INCR8 = 3'b000; //8个数据递增方式批量传输
parameter HBURST_WRAP16 = 3'b000; //16个数据回绕方式批量传输
parameter HBURST_INCR16 = 3'b000; //16个数据递增方式批量传输
parameter HREADY_REDY = 1'b1;
parameter HREADY_BUSY = 1'b0;
parameter HRESP_OKEY = 2'b00; //传输完成
parameter HRESP_ERROR = 2'b01; //传输错误
parameter HRESP_RETRY = 2'b10; //传输未完成,请求主设备重新开始一个传输,
//arbiter会继续使用通常的优先级
parameter HRESP_SPLIT = 2'b11; //传输未完成,请求主设备分离一次传输,
//arbiter会调整优先级方案以便其他请求总线的主设备可以访问总线
parameter HSIZE_8b = 3'b000;
parameter HSIZE_16b = 3'b001;
parameter HSIZE_32b = 3'b010;
parameter HSIZE_64b = 3'b011;
parameter HSIZE_128b = 3'b100;
parameter HSIZE_256b = 3'b101;
parameter HSIZE_512b = 3'b110;
parameter HSIZE_1024b = 3'b111;
parameter HWRITE_WR = 1'b1;
parameter HWRITE_RD = 1'b0;
parameter FSM_IDLE = 4'h0;
//parameter FSM_ADDR = 4'h1;
parameter FSM_WR = 4'h2;
parameter FSM_RD = 4'h4;
reg[3:0] FSM_Current;
reg[3:0] FSM_Next;
always@(posedge HCLK or negedge HRSTn)
begin
if(HRSTn == 0)
begin
FSM_Current <= FSM_IDLE;
end
else
begin
FSM_Current <= FSM_Next;
end
end
always @(*) begin
if(HRSTn == 0)
begin
FSM_Next <= FSM_IDLE;
end
else
begin
case(FSM_Current)
FSM_IDLE:
begin
if( (HTRANS == HTRANS_NONSEQ) && (HADDR == LED_ADDR) )
begin
if(HWRITE == HWRITE_WR)
begin
FSM_Next <= FSM_WR;
end
else
begin
FSM_Next <= FSM_RD;
end
end
else
begin
FSM_Next <= FSM_IDLE;
end
end
FSM_WR:
begin
FSM_Next <= FSM_IDLE;
end
FSM_RD:
begin
FSM_Next <= FSM_IDLE;
end
default:
begin
FSM_Next <= FSM_IDLE;
end
endcase
end
end
always@(posedge HCLK or negedge HRSTn)
begin
if(HRSTn == 0)
begin
LED <= 1'b0;
HRDATA <= 32'hZ;
HREADY <= HREADY_REDY;
HRESP <= HRESP_OKEY;
end
else
begin
case(FSM_Current)
FSM_IDLE:
begin
if( (HTRANS == HTRANS_NONSEQ) && (HADDR == LED_ADDR) )
begin
LED <= LED;
HRDATA <= 32'hZZZZ_ZZZZ;
HREADY <= HREADY_BUSY;
HRESP <= HRESP_OKEY;
end
else
begin
LED <= LED;
HRDATA <= 32'h0;
HREADY <= HREADY_REDY;
HRESP <= HRESP_OKEY;
end
end
FSM_WR:
begin
LED <= HWDATA[0:0];
HRDATA <= 32'h0;
HREADY <= HREADY_REDY;
HRESP <= HRESP_OKEY;
end
FSM_RD:
begin
LED <= LED;
HRDATA <= {31'h0, LED};
HREADY <= HREADY_REDY;
HRESP <= HRESP_OKEY;
end
default:
begin
LED <= 1'b0;
HRDATA <= 32'hZ;
HREADY <= HREADY_REDY;
HRESP <= HRESP_OKEY;
end
endcase
end
end
endmodule
在原有的代码的基础上,例化RISCV硬核时,将AHB总线信号例化,代码如下
module SOC(
input wire clk,
input wire clk_low,
input wire rstn,
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,
output wire[2:0] htrans,
output wire hwrite,
output wire[31:0] haddr,
output wire[2:0] hsize,
output wire [2:0] hburst,
output wire[3:0] hprot,
output wire hmastlock,
output wire[31:0] hwdata,
input wire hclk,
input wire[31:0] hrdata,
input wire[1:0] hresp,
input wire hready
);
wire ps_gpio0_out;
wire ps_gpio0_dir;
wire ps_gpio0_in ;
wire ps_gpio1_out;
wire ps_gpio1_dir;
wire ps_gpio1_in ;
gpio u1(
.gpio_dir(ps_gpio0_dir),
.gpio_out(ps_gpio0_out),
.gpio_in(ps_gpio0_in),
.pin(gpio_0)
);
gpio u2(
.gpio_dir(ps_gpio1_dir),
.gpio_out(ps_gpio1_out),
.gpio_in(ps_gpio1_in),
.pin(gpio_1)
);
RISCV u3(
.core_clk(clk),
.core_reset(rstn),
.timer_clk ( clk_low ),
.jtag_tck ( jtag_tck ),
.jtag_tdo ( jtag_tdo ),
.jtag_tms ( jtag_tms ),
.jtag_tdi ( jtag_tdi ),
.soft_ip_apbm_en ( 1'b0 ),
.qspi0cfg1_mode ( 1'b1 ),
.qspi0cfg2_mode ( 1'b1 ),
.uart_tx ( uart_tx ),
.uart_rx ( uart_rx ),
.gpio0_out ( ps_gpio0_out ),
.gpio0_dir ( ps_gpio0_dir ),
.gpio0_in ( ps_gpio0_in ),
.gpio1_out ( ps_gpio1_out ),
.gpio1_dir ( ps_gpio1_dir ),
.gpio1_in ( ps_gpio1_in ),
.htrans ( htrans ),
.hwrite ( hwrite ),
.haddr ( haddr ),
.hsize ( hsize ),
.hburst ( hburst ),
.hprot ( hprot ),
.hmastlock ( hmastlock ),
.hwdata ( hwdata ),
.hclk ( hclk ),
.hrdata ( hrdata ),
.hresp ( hresp ),
.hready ( hready )
);
endmodule
配置PLL,使其输出第二路时钟信号50MHz,作为AHB总线的时钟频率
在TOP层,将各模块组装起来
module TOP(
input wire clk_25MHz,
input wire rstn,
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,
output wire LED
);
wire clk_100MHz;
wire hclk;
pll U1(
.refclk(clk_25MHz),
.reset(~rstn),
.clk0_out(clk_100MHz),
.clk1_out(hclk)
);
wire[2:0] htrans;
wire hwrite;
wire[31:0] haddr;
wire[2:0] hsize;
wire [2:0] hburst;
wire[3:0] hprot;
wire hmastlock;
wire[31:0] hwdata;
wire[31:0] hrdata;
wire[1:0] hresp;
wire hready;
SOC U2(
.clk(clk_100MHz),
.clk_low(clk_25MHz),
.rstn(~rstn),
.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(gpio_0),
.gpio_1(gpio_1),
.htrans ( htrans ),
.hwrite ( hwrite ),
.haddr ( haddr ),
.hsize ( hsize ),
.hburst ( hburst ),
.hprot ( hprot ),
.hmastlock ( hmastlock ),
.hwdata ( hwdata ),
.hclk ( hclk ),
.hrdata ( hrdata ),
.hresp ( hresp ),
.hready ( hready )
);
AHB_LED U3(
//ports
.HCLK ( hclk ),
.HRSTn ( rstn ),
.HTRANS ( htrans ),
.HADDR ( haddr ),
.HWRITE ( hwrite ),
.HSIEZ ( hsize ),
.HBURST ( hburst ),
.HPROT ( hprot ),
.HWDATA ( hwdata ),
.HRDATA ( hrdata ),
.HREADY ( hready ),
.HRESP ( hresp ),
.LED ( LED )
);
endmodule
经过综合后,补充LED引脚的约束
set_pin_assignment { LED } { LOCATION = H5; }
set_pin_assignment { clk_25MHz } { LOCATION = D7; }
set_pin_assignment { gpio_0 } { LOCATION = J5; }
set_pin_assignment { gpio_1 } { LOCATION = H3; }
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 { rstn } { LOCATION = H2; }
set_pin_assignment { uart_rx } { LOCATION = E4; }
set_pin_assignment { uart_tx } { LOCATION = A4; }
以上就完成了硬件设计,软件设计很简单,对0x43214320地址进行写操作即可,代码如下
#include <stdio.h>
#include "nuclei_sdk_hal.h"
#include "anl_printf.h"
#include "nuclei_uart.h"
int main(void)
{
gpio_enable_output(GPIO, 1);
gpio_enable_input(GPIO, 2);
unsigned char st = 1;
while(1)
{
if(gpio_read(GPIO, 2) != 0)
{
gpio_toggle(GPIO, 1);
delay_1ms(1000);
*((unsigned long *)0x43214320) = st;
st = ~st;
printf("anlogic\n");
}
}
}
代码编译完成后,按照上一篇文章的教程,下载完成后,就可见H5引脚对应的LED的闪烁
47c7511287fa21b9cfdd3c8e1e1c9e72
附件为硬件与软件两个完整工程: