2895|1

3

帖子

0

TA的资源

一粒金砂(中级)

楼主
 

[安路科技 SF1 系列 FPGA Rust 编程探索] - UART 初探 [复制链接]

 
本帖最后由 musiclee 于 2023-3-7 15:17 编辑

上一篇 [Rust on SF102] 安路科技 SF1 FPGA 系列评测 (一)- Hello rust! 尝试了针对安路科技 SF1 系列 FPGA 配置对应的 Rust 交叉编译环境,并编写了一个简单的 LED Blink 例程,并顺利地在SF102 DEMO 板上运行成功。接下来我们将正式创建一个 FPGA SOC 项目,用于评估后续的外设驱动。

FPGA SOC 项目

1. 创建项目 

 

2. 配置时钟,点选菜单 Tools -> IP Generator 生成锁相环:

 

点击 OK,并填写生成的目标文件名:

 

点击 OK,选择锁相环对应的 IP:

 

开发板输入时钟为 25MHz,将 PLL 配置为简单倍频到 100MHz 输出:

 

确认即可生成对应的 PLL IP 代码,软件会询问是否加入项目,确认选择加入。

 

3. 点选菜单 Tools -> IP Generator 生成 RISC-V 硬核 IP 代码

步骤和 PLL 相同,填写目标代码文件名:

 

IP 选择 SF1 RISCV:

 

当前我们仅验证 UART 编程,所以勾选最简配置:

确认生成并加入项目。至此完成必要的 IP 文件生成。

 

4. 编写主模块

主模块代码例化了 PLL 和 RISC-V 硬核,实现了支持 UART 的最小硬件系统:

`timescale 1ns/1ps

module SF1Simple (
  inout               led1,
  inout               led2,
  inout               led3,
  output              uart_tx,
  input               uart_rx,
  input               clk,
  input               reset
);

  wire                syspll_reset;
  wire                mcu_core_reset;
  wire                syspll_extlock;
  wire                syspll_clk0_out;
  wire       [31:0]   mcu_apb_prdata;
  wire                mcu_apb_pready;
  wire                mcu_apb_pslverr;
  wire                mcu_uart_tx;
  wire                mcu_gpio0_out;
  wire                mcu_gpio0_dir;
  wire                mcu_gpio1_out;
  wire                mcu_gpio1_dir;
  wire                mcu_gpio2_out;
  wire                mcu_gpio2_dir;
  wire                mcu_mtip;
  wire                mcu_sysrstreq;
  wire                mcu_apb_clk_down;
  wire       [31:0]   mcu_apb_pwdata_down;
  wire       [3:0]    mcu_apb_pstrobe_down;
  wire       [2:0]    mcu_apb_pprot_down;
  wire                mcu_apb_penable_down;
  wire                mcu_apb_pwrite_down;
  wire       [11:0]   mcu_apb_paddr_down;
  wire                mcu_apb_psel0_down;
  wire                mcu_apb_psel1_down;
  wire                mcu_apb_psel2_down;
  wire                gpio0_O_gpio_in;
  wire                gpio1_O_gpio_in;
  wire                gpio2_O_gpio_in;

  pll syspll (
    .refclk   (clk            ),
    .reset    (syspll_reset   ),
    .extlock  (syspll_extlock ),
    .clk0_out (syspll_clk0_out) 
  );
  
  rv32 mcu (
    .soft_ip_apbm_en  (1'b0                     ),
    .qspi0cfg1_mode   (1'b1                     ),
    .qspi0cfg2_mode   (1'b1                     ),
    .apb_clk          (1'b0                     ),
    .apb_paddr        (32'h0                    ),
    .apb_pwrite       (1'b0                     ),
    .apb_penable      (1'b0                     ),
    .apb_pprot        (3'b000                   ),
    .apb_pstrobe      (4'b0000                  ),
    .apb_psel         (1'b0                     ),
    .apb_pwdata       (32'h0                    ),
    .apb_prdata       (mcu_apb_prdata[31:0]     ),
    .apb_pready       (mcu_apb_pready           ),
    .apb_pslverr      (mcu_apb_pslverr          ),
    .uart_tx          (mcu_uart_tx              ),
    .uart_rx          (uart_rx                  ),
    .gpio0_in         (gpio0_O_gpio_in          ),
    .gpio0_out        (mcu_gpio0_out            ),
    .gpio0_dir        (mcu_gpio0_dir            ),
    .gpio1_in         (gpio1_O_gpio_in          ),
    .gpio1_out        (mcu_gpio1_out            ),
    .gpio1_dir        (mcu_gpio1_dir            ),
    .gpio2_in         (gpio2_O_gpio_in          ),
    .gpio2_out        (mcu_gpio2_out            ),
    .gpio2_dir        (mcu_gpio2_dir            ),
    .timer_clk        (syspll_clk0_out          ),
    .mtip             (mcu_mtip                 ),
    .core_clk         (syspll_clk0_out          ),
    .core_reset       (mcu_core_reset           ),
    .nmi              (1'b0                     ),
    .clic_irq         (16'h0                    ),
    .sysrstreq        (mcu_sysrstreq            ),
    .apb_clk_down     (mcu_apb_clk_down         ),
    .apb_prdata_down  (32'h0                    ),
    .apb_pwdata_down  (mcu_apb_pwdata_down[31:0]),
    .apb_pstrobe_down (mcu_apb_pstrobe_down[3:0]),
    .apb_pprot_down   (mcu_apb_pprot_down[2:0]  ),
    .apb_penable_down (mcu_apb_penable_down     ),
    .apb_pwrite_down  (mcu_apb_pwrite_down      ),
    .apb_pslverr_down (1'b0                     ),
    .apb_pready_down  (1'b0                     ),
    .apb_paddr_down   (mcu_apb_paddr_down[11:0] ),
    .apb_psel0_down   (mcu_apb_psel0_down       ),
    .apb_psel1_down   (mcu_apb_psel1_down       ),
    .apb_psel2_down   (mcu_apb_psel2_down       ) 
  );

  gpio_controler_2 gpio0 (
    .O_gpio_in  (gpio0_O_gpio_in),
    .I_gpio_dir (mcu_gpio0_dir  ),
    .I_gpio_out (mcu_gpio0_out  ),
    .IO_gpio    (led1           )
  );

  gpio_controler_2 gpio1 (
    .O_gpio_in  (gpio1_O_gpio_in),
    .I_gpio_dir (mcu_gpio1_dir  ),
    .I_gpio_out (mcu_gpio1_out  ),
    .IO_gpio    (led2           )
  );

  gpio_controler_2 gpio2 (
    .O_gpio_in  (gpio2_O_gpio_in),
    .I_gpio_dir (mcu_gpio2_dir  ),
    .I_gpio_out (mcu_gpio2_out  ),
    .IO_gpio    (led3           )
  );

  assign syspll_reset = (! reset);
  assign mcu_core_reset = (! reset);
  assign uart_tx = mcu_uart_tx;

endmodule

module gpio_controler_2 (
  output              O_gpio_in,
  input               I_gpio_dir,
  input               I_gpio_out,
  inout               IO_gpio
);

  assign IO_gpio = I_gpio_dir ? I_gpio_out : 1'bz;
  assign O_gpio_in = IO_gpio;

endmodule

 

5. 分配引脚

在软件界面左侧点击 IO Constraint ,为用到的端口分配引脚编号(参照原理图sf1s60cg121i_demo_board_v21.pdf)

 

 

生成的 .adc 文件内容如下(也可以直接编写 .adc 文件加入项目):

set_pin_assignment	{ clk }	{ LOCATION = D7; IOSTANDARD = LVCMOS33; }
set_pin_assignment	{ reset }	{ LOCATION = H3; IOSTANDARD = LVCMOS33; }
set_pin_assignment	{ uart_rx }	{ LOCATION = E4; IOSTANDARD = LVCMOS33; }
set_pin_assignment	{ uart_tx }	{ LOCATION = A4; IOSTANDARD = LVCMOS33; }
set_pin_assignment	{ led1 }	{ LOCATION = J4; IOSTANDARD = LVCMOS33; }
set_pin_assignment	{ led2 }	{ LOCATION = J5; IOSTANDARD = LVCMOS33; }
set_pin_assignment	{ led3 }	{ LOCATION = H5; IOSTANDARD = LVCMOS33; }

 

6. 综合布线

工具栏点击 run 按钮,正常编译通过:

至此 FPGA 项目设计完成。

编写 Rust 代码

在编写驱动 UART 的 Rust 代码之前,先简要介绍安路科技 SF1内嵌硬核的 UART 外设资源,SF1 支持一个名为 UART 0 的外设,对应的寄存器映射地址空间为 0xE001_0000 ~ 0x1001_FFFF,下面是其寄存器列表概览:

 

要驱动 UART 实现在串口终端输出字符串,最基本的步骤需要配置 UART_DIV、UART_TXCTRL 和 UART_SETUP 这三个寄存器。具体说明详见 TN817_SF1 MCU用户指南.pdf 。

Rust 开发环境以及项目创建指南已经在上一篇  [Rust on SF102] 安路科技 SF1 FPGA 系列评测 (一)- Hello rust! 中详细介绍,因此这里只展示主代码 main.rs:

#![no_std]
#![no_main]

extern crate riscv_rt;
extern crate panic_halt;
use riscv_rt::entry;
use core::ptr::{write_volatile,read_volatile};

// 时钟配置寄存器
const MISC_BASE_ADDRESS: u32 = 0xE0000000;
const CLOCKCTL_ADDRESS: u32 = MISC_BASE_ADDRESS + 0x10;
const CLOCKCTL: *mut u32 = CLOCKCTL_ADDRESS as *mut u32;

// 定义串口寄存器地址
const UART: u32 = 0xE001_0000;
const UART_TXDATA: u32 = UART + 0x00;
// const UART_RXDATA: u32 = UART + 0x04;
const UART_TXCTRL: u32 = UART + 0x08;
const UART_RXCTRL: u32 = UART + 0x0C;
const UART_IE: u32 = UART + 0x10;
// const UART_IP: u32 = UART + 0x14;
const UART_DIV: u32 = UART + 0x18;
// const UART_STATUS: u32 = UART + 0x1C;
const UART_SETUP: u32 = UART + 0x20;

// 定义串口波特率
const BAUD_RATE: u32 = 115200;

// 定义系统时钟频率
const SYSCLK: u32 = 50_000_000;

#[entry]
fn main() -> ! {
    // 初始化串口寄存器
    unsafe {
        write_volatile(CLOCKCTL, 0x83);

        // 设置波特率分频寄存器
        let div = SYSCLK / BAUD_RATE - 1;
        write_volatile(UART_DIV as *mut u32, div);

        // 设置发送控制寄存器,使能发送功能和中断
        let txctrl = read_volatile(UART_TXCTRL as *mut u32) | 0x1;
        write_volatile(UART_TXCTRL as *mut u32, txctrl);

        // 设置接收控制寄存器,使能接收功能和中断
        let rxctrl = read_volatile(UART_RXCTRL as *mut u32) | 0x1;
        write_volatile(UART_RXCTRL as *mut u32, rxctrl);

        write_volatile(UART_SETUP as *mut u32, 0x30);

        // 设置中断使能寄存器,使能发送就绪和接收就绪中断
        let ie = (1 << 0) | (1 << 1);
        write_volatile(UART_IE as *mut u32, ie);
    }

    // 向串口输出 "Hello world." 字符串
    let message = b"Hello world.\r\n";
    for &byte in message.iter() {
        send_byte(byte);
    }

    loop {}
}

// 定义一个函数,用于发送一个字节到串口
fn send_byte(byte: u8) {
    unsafe {
        // 等待发送数据缓冲区为空
        while read_volatile(UART_TXDATA as *const u32) & (1 << 31) != 0 {}
        // 写入数据到发送数据缓冲区
        write_volatile(UART_TXDATA as *mut u32, byte as u32);
    }
}

 

以下是 Cargo 项目的配置文件和链接文件,创建 Rust 项目请参考第一篇 [Rust on SF102] 安路科技 SF1 FPGA 系列评测 (一)- Hello rust!

 

cargo/config 文件

[target.riscv32imac-unknown-none-elf]

rustflags = [
  "-C", "link-arg=-Tmemory.x",
  "-C", "link-arg=-Tlink.x"
]

[build]
target = "riscv32imac-unknown-none-elf"

 

memory.x 文件

MEMORY
{
  flash : ORIGIN = 0x000E0000, LENGTH = 4M 
  ilm : ORIGIN = 0x08000000, LENGTH = 8K  
  ram : ORIGIN = 0x20000000, LENGTH = 8K
}

REGION_ALIAS("REGION_TEXT", flash);
REGION_ALIAS("REGION_RODATA", flash);
REGION_ALIAS("REGION_DATA", ram);
REGION_ALIAS("REGION_BSS", ram);
REGION_ALIAS("REGION_HEAP", ram);
REGION_ALIAS("REGION_STACK", ram);

 

编译并生成 hex 文件

cargo build --release
cargo objcopy --release -- -O ihex oledtest.hex

烧写运行

首先需要连接好主板 USB 和 下载器,如图 Type-C 接口会在电脑上识别为一个USB串口设备

 

打开对应的串口终端,设置波特率为 115200, 具体参数如下:

 

然后在 TD 软件下载 FPGA(具体步骤参考 [Rust on SF102] 安路科技 SF1 FPGA 系列评测 (一)- Hello rust! ):

 

下载成功后,将会在串口终端看到输出信息:

 

至此,UART 驱动成功!

FPGA 项目文件: td2.zip (315.37 KB, 下载次数: 2)
Rust 项目文件: oledtest.zip (17.2 KB, 下载次数: 0)

 

最新回复

学习学习了  详情 回复 发表于 2023-3-7 23:51
点赞 关注
 
 

回复
举报

2

帖子

0

TA的资源

一粒金砂(初级)

沙发
 
学习学习了
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表