DE1-SoC HPS CAN通信(中断方式)调试
实验目的: 利用Altera Cyclone V SoPC HPS集成的CAN总线控制器,采用中断方式,实现裸机下的CAN报文数据的收发。 实验环境: 硬件:DE1-SoC开发板, 软件:Quartus II 15.0和DS-5 18.1,Preloader,HPS裸机固件驱动库(18.1)。 实验流程: 1.硬件设计 2.软件设计 a. Preloader部分 b. HPS引导流程 c. CAN裸机中断收发程序流程 1. 主程序框架 2. 关键硬件资源的初始化 3. 中断收发服务函数实现及触发流程
1.硬件设计 HPS 中包含CAN 总线控制器,所以不需要添加 IP 核,只需从 HPS 中引出 CAN 的 tx、rx 的引脚,使其能够通过这两个引脚连接片外驱动电路并和外部进行通信。
具体步骤: 在 Qsys 系统组件 HPS 参数选项标签页设置 CAN 的外设引脚 注:CAN pin选择FPGA ,因为 HPS I/O Set 没有可以连接外部进行 CAN 通信的引脚 双击红色区域引出 CAN 的引脚 用 verilog 进行端口声明: 经过综合与分析后,在 Pin Planner 中就会看到上面声明的 CAN 端口 双击 Location进行引脚分配: 分配的引脚是从板上2个40pin GPIO中随意挑选的,但需要注意的是,由于 CAN 通信是差分信号输入到 CAN 总线上,所以挑选引脚复用时要选择 DIFFIO_RX DIFFIO_TX 电路连接如下: 需要注意的是,需要加一个电平转换器件,因为 TTL 电平标准和 CAN 的电平标准不同。 最终编译生成的.sof文件经由USB-Blaster下载到开发板中。 3.软件设计
硬件部分设计完成,但HPS 的配置信息还不知道,软件程序运行的环境没准备好,裸机程序还需加载,所以需要准备一个完成软硬件交互的文件,也就是preloader部分。
a.Preloader部分 最终需要生成 u-boot-spl 二进制文件进行相关硬件初始化,并且引导加载裸机程序 那么怎么得到u-boot-spl ? 首先,我们需要知道配置 HPS 的信息,如使能的外设,管脚的复用等 对此,可以编译上面说明的硬件设计,得到.sof文件(经由USB Blaster下载到板子中)和handoff文件夹,handoff文件夹中包含了这些信息 然后,经由 preloader 生成器,也就是bsp(板级支持包) editor 将 handoff 文件夹里的信息转换成源代码、 另外生成 preloader 设置文件、 preloader 调试脚本以及编译 preloader 所用的 makefile。 由此我们得到了源文件,但是和我们最终想要的二进制文件存在差异,需要借助 makefile进行编译。 Bsp editor支持多种型号板子的 preloader 配置信息的生成 generated文件夹可以看到许多头文件与C语言文件,这些文件是用来更新uboot中相关文件的,主要涉及到系统配置,IO复用,DDR初始化等 Makefile 制定了编译规则,生成最终的二进制文件,执行以下步骤: 1.把 generated文件夹下的相应文件拷贝到uboot-socfpga/board/altera/socfpga与下面sdram子目录 2.通过下面的变量设置
PRELOADER_UPDATE_DIR := $(PRELOADER_SRC_DIR)/board/altera/socfpga
SOCFPGA_BOARD_CONFIG := socfpga_$(DEVICE_FAMILY)_config
DEVICE_FAMILY := cyclone5
以及执行make $(board)_config 对工程进行配置,以确定特定于目标板的各个子目录和头文件。生成配置文件config.h(知道取用U-Boot源代码中的哪些相应文件) 3.配置完成后,就要进行编译 spl 得到 u-boot-spl.bin uboot-socfpga目录下顶层文件Makefile有下面的语句
ALL-$(CONFIG_SPL) += $(obj)spl/u-boot-spl.bin
打开spl目录下的文件Makefile 在这里我们可以看到编译 spl 所需要用的各种配置文件、汇编文件和用到的链接器文件,下面是其中的一部分
LIBS-$(CONFIG_SPL_FRAMEWORK) += common/spl/libspl.o
LIBS-$(CONFIG_SPL_LIBCOMMON_SUPPORT) += common/libcommon.o
LIBS-$(CONFIG_SPL_LIBDISK_SUPPORT) += disk/libdisk.o
LIBS-$(CONFIG_SPL_I2C_SUPPORT) += drivers/i2c/libi2c.o
LIBS-$(CONFIG_SPL_GPIO_SUPPORT) += drivers/gpio/libgpio.o
LIBS-$(CONFIG_SPL_MMC_SUPPORT) += drivers/mmc/libmmc.o
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-spl.lds
链接文件决定一个可执行程序的各个段的存储(加载)地址,以及运行(链接)地址。
b.HPS引导流程 上电 reset 后,位于 HPS 中的 ROM 运行封装好的一段代码Boot ROM Boot ROM代码的作用是确定所选的boot源,重置后初始化HPS,将 preloader 映像从 flash 加载到OCRAM然后跳转到 preloader 。 所选的 Flash 类型 SDMMC 在 bsp-editor spl boot 选项中设置 OCRAM 由地址 0xFFFF0000知 Preloader 在完成Clock Reconfiguration、初始化SDRAM接口、配置HPS I/O引脚及复用、初始化加载下一阶段程序的接口后,将裸机程序引导映像从引导设备复制到SDRAM。并执行 SDRAM 地址0x00100000–0xC0000000 c. CAN裸机中断收发程序流程 1. 主程序框架 初始化:1.通过协议组寄存器模块(如CCTRL寄存器、CBT寄存器等)进行软件初始化(如设置比特率,设置通信模式等) 2.通过IF接口寄存器初始化Message RAM中的接收邮箱和发送邮箱(分别初始化)。 消息对象结构:掩码位、仲裁位、控制位、数据位 通过IF接口寄存器配置消息对象的掩码位、仲裁位、控制位、数据位,存储到Message RAM对应邮箱中请求发送。 Message Handler(状态机)接收到了请求,处理消息对象发送。 邮箱中的消息对象通过IF寄存器传输到CAN core中的移位寄存器,经过封装然后到达CAN 总线接口,进行数据帧的传送。
中断相关: GIC–中断控制器连接所有能够产生中断的I/O外围设备的IRQ中断信号。CAN0 控制器有4个中断信号连接到全局中断控制器 (GIC), GIC 由分配器和 CPU 接口组成,分配器从I/O外设接收IRQ中断信号 CPU接口将分配器收到的IRQ请求发送给Cortex-A9处理器。 中断信号可以分为3种 1.SPI(shared peripheral interrupts):该中断由中断控制器可以路由到多个内核的外设生成.中断号32-211 2.PPI(private peripherals interrupts):专用于单个CPU .中断号27-30 3.SGI(software generated interrupts):由软件通过写入专用分配器寄存器生成.中断号0-15
中断过程: 当发生中断时,CAN0 控制器发送一个消息中断信号 ALT_INT_INTERRUPT_CAN0_MO_IRQ 给 GIC ,由分配器接收,然后CPU接口将中断请求发送给Cortex-A9处理器让处理器进行处理,进入到中断模式。 在程序中,跳转到中断向量表 irq 入口,通过跳转指令,跳转到中断服务函数。执行完中断服务函数后,回到原程序继续运行
2.关键硬件资源的初始化代码
进行 CAN 通信,先要对 CAN controller 初始化,其中包括比特率的设置,Message Ram的初始化以及接收邮箱和发送邮箱的初始化等
alt_can_init( ALT_CAN_CAN0, candev);
alt_can_mailbox_init(candev,0x1, 0x16, 0x5,ALT_CAN_TMOD_TX, ALT_CAN_FIFO_MODE_SINGLE_MSG);
alt_can_mailbox_init(candev,0x2, 0x0101, 0x3,ALT_CAN_TMOD_RX, ALT_CAN_FIFO_MODE_SINGLE_MSG);
alt_can_mailbox_init(candev,0x3, 0x0102, 0x3,ALT_CAN_TMOD_RX, ALT_CAN_FIFO_MODE_SINGLE_MSG);
alt_can_mailbox_init(candev,0x4,0x0103,0x3,ALT_CAN_TMOD_RX, ALT_CAN_FIFO_MODE_SINGLE_MSG);
注:当接收多个报文/数据帧时,为避免数据错乱,需设置邮箱的ID 标识符以及掩码位
- 中断收发服务函数实现及触发流程
要以中断方式实现数据收发,需要配置 GIC(包括分配器和 CPU 接口),并配置 CAN 控制器使其能发送中断信号 配置 GIC
ALT_STATUS_CODE status = ALT_E_SUCCESS;
if (status == ALT_E_SUCCESS)
{
status = alt_int_global_init();
}
if (status == ALT_E_SUCCESS)
{
status = alt_int_cpu_init();
}
if (status == ALT_E_SUCCESS)
{
status = alt_int_isr_register(ALT_INT_INTERRUPT_CAN0_MO_IRQ, can_int_callback,NULL);
}
if (status == ALT_E_SUCCESS)
{
int target = 0x3;
status = alt_int_dist_target_set(ALT_INT_INTERRUPT_CAN0_MO_IRQ, target);
}
if (status == ALT_E_SUCCESS)
{
status = alt_int_dist_trigger_set(ALT_INT_INTERRUPT_CAN0_MO_IRQ,ALT_INT_TRIGGER_LEVEL);
}
if (status == ALT_E_SUCCESS)
{
status = alt_int_dist_enable(ALT_INT_INTERRUPT_CAN0_MO_IRQ);
}
if (status == ALT_E_SUCCESS)
{
status = alt_int_cpu_enable();
}
if (status == ALT_E_SUCCESS)
{
status = alt_int_global_enable();
}
配置I/O外围设备 CAN 控制器,使其可以发送IRQ中断请求到GIC。
alt_can_int_enable(candev, 0xf);
设置消息对象接收中断。消息对象/邮箱的中断是在消息对象的控制位中设置的。设置成功后,一旦接收到报文,就触发中断
ALT_CAN_MSG_IFMCTR_t ctrl0 = {0};
ctrl0.data_len=0x8;
ctrl0.block_end=true;
ctrl2.rx_int=true;
alt_can_if_msg_ctrl_set( candev, ALT_CAN_INTERFACE_WRITE,&ctrl0);
ALT_CAN_MSG_PARAM_t msgparam1 = {0};
msgparam1.control=true;
alt_can_message_put(candev, ALT_CAN_INTERFACE_WRITE, 0x2,&msgparam1);
触发中断后,跳转指令到中断服务函数 中断服务函数:实现点亮FPGA灯,以及将接收到的数据帧发送出去
void can_int_callback()
{
alt_write_word(0xff200000,0x1);}
然后通过读取寄存器 MOIPA 的值判断哪一个消息对象的中断标志位 Inpnd 置1,知道哪个邮箱触发中断
ALT_CAN_MSG_PARAM_t cmd_params0 = {0};
cmd_params0.arbitration=true;
cmd_params0.control=true;
cmd_params0.data_A=true;
cmd_params0.data_B=true;
alt_can_message_get(candev,ALT_CAN_INTERFACE_READ, i, &cmd_params0);
uint32_t a = alt_read_word(0xFFC00108);
uint32_t b = alt_read_word(0xFFC00110);
uint32_t c = alt_read_word(0xFFC00114);
uint32_t d = a & 0x1fffffff;
arb_param0.direction=true;
arb_param0.id=d;
arb_param0.extended=false;
arb_param0.valid=true;
alt_can_if_arb_set( candev,ALT_CAN_INTERFACE_WRITE, &arb_param0);
ALT_CAN_MSG_IFMCTR_t ctrl0 = {0};
ctrl0.data_len=0x8;
ctrl0.block_end=true;
alt_can_if_msg_ctrl_set( candev, ALT_CAN_INTERFACE_WRITE,&ctrl0);
alt_can_if_data_set( candev, ALT_CAN_INTERFACE_WRITE, b, c);
ALT_CAN_MSG_PARAM_t msgparam = {0};
msgparam.data_B=true;
msgparam.data_A=true;
msgparam.tx_rqst_new_dat=true;
msgparam.control=true;
msgparam.arbitration=true;
alt_can_message_put(candev, ALT_CAN_INTERFACE_WRITE, 0x1,&msgparam);
ALT_CAN_MSG_IFMCTR_t ctrl2 = {0};
ctrl2.int_pending=false;
ctrl2.data_len=0x8;
ctrl2.block_end=true;
ctrl2.rx_int=true;
alt_can_if_msg_ctrl_set( candev, ALT_CAN_INTERFACE_WRITE,&ctrl2);
ALT_CAN_MSG_PARAM_t msgparam4 = {0};
msgparam4.control=true;
alt_can_message_put(candev,ALT_CAN_INTERFACE_WRITE,i,&msgparam4);
为了程序能正常运行而不是一直进入中断,要清除中断位,在对应邮箱/消息对象的 Intpnd 位置0即可。 程序设计完成后,经过编译、汇编、链接最终生成.axf二进制文件
实验结果:
每当USB-CAN Tool 发送一个消息对象,程序触发中断,进入中断服务函数,将接收到的消息对象再返回发送到USB-CAN Tool中,如下图所示。
|