Menu Close

RISC-V C语言编程2

1. RISCV FII-PRX100 (ARTIX-7, XC7A100T) Xilinx FPGA 板简介

 

相关参考文章:

RISC-V教学教案

 

RISCV FII-PRX100 Xilinx FPGA板有两个版本,一个配有SRAM,表示为PRX100-S,另一个配有DDR而不是SRAM,表示为PRX100-D(https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w5003-22702892501.2.7f0056012gj77C&id=615296285488&scene=taobao_shop)。 有关PRX100的不同版本,参见图一和图二。 通过FPGA板上红色显示的区域,可以发现两个版本的差异。 DDR版本的正面有一个明显的IC,标记为“ DDR”。

%title插图%num

图一 PRX100-D,DDR版本

以PRX100-S为例,图二中突出显示了两个JTAG (内部和外部)和UART接口。注意,连接跳线J6 1-2以使用J4或J5作为外部下载接口,如图三所示。 JTAG接口主要用于FPGA编程。 UART接口用于输出到计算机。 在使用Freedom Studio运行C程序之前,需要使用RISCV verilog工程对FPGA板进行编程。

%title插图%num

图二 PRX100-S,SRAM版本

%title插图%num

 

图三 外部JTAG接口

 

 

2. 原理图分析

LED,按钮和数码管显示在以下工程中会被使用,因此对原理图的分析有助于理解C工程里的相关变量赋值。

  • LED

从图四可以看出,要点亮LED,电流必须从左侧流向右侧。 由于左侧是恒定的高电压,右侧是FPGA布线。 仅当FPGA布线处于低电平时,左侧电压才高于右侧电压,这样电流才会流过LED,并且将LED点亮。 相反,当FPGA接线处于高电平时,LED的两侧都处于高电压,因此没有电流流过,LED将保持熄灭状态。

%title插图%num

图四 LED原理图接线

 

  • 按键

这里,按键被当作GPIO输入。 按下按键时,按键左侧和右侧接线连通,否则,可以将其视为开路。 当按下按键也就是连通时,VCC电压将通过一个电阻降压,然后接地。 这样,FPGA布线将直接接地,从而处于低压状态。 如果按键是开路,则VCC无法降压,因此不会产生电流流经电阻,FPGA布线的电压将与VCC相同,为高电平。

%title插图%num

图五 按键原理图接线

  • 数码管显示

数码管显示有两种类型的FPGA接线:位选和段选。 由于位选连接到P沟道场效应晶体管(FET),因此栅极必须比源极小典型值0.78V ,以便VCC可以流经数码管。 因此,要点亮数码管,位选必须处于低电平状态。 同样,如果段选也处于低压状态(假设相应的位选处于低压状态,则VCC可以流经数码管),那么段选和VCC之间存在电压差,VCC可以从数码管的右侧降压到左侧。 相反,如果段选择为高电压,则数码管的左右两侧之间没有电压差,数码管也因此没有显示。

%title插图%num

图六 数码管原理图接线

 

 

3. 地址图

图七截取了部分RISCV地址分配,主要用于编写C语言工程时的地址赋值查询。

%title插图%num

图七 地址分配

 

 

4. RISCV_seg_cnt工程

如图八所示,首先,在Freedom Studio下选择File> Import,导入窗口将弹出。 在General 页面下,选择Existing Projects into Workspace,然后单击Next。 在图九中,点击Browse浏览找到相应的工程,勾选Copy projects into workspace项,然后点击Finish完成。 RISCV_seg_cnt工程会出现在Project Explorer选项下,如图十所示。

%title插图%num

图八 Import窗口(1)

%title插图%num

图九 Import窗口(2)

%title插图%num

图十 RISCV_seg_cnt工程在Project Explorer选项下出现

将项目添加到工作区后,右键点击Project Explorer下的工程文件夹,点击Clean Project(每次在实际构建项目之前都尽量执行此操作)。 然后点击Build Project。 控制台Console显示Build Finished后,点击Refresh刷新界面,如图十一和图十二所示。调试需要右键点击该项目,然后选择Debug As> Debug Configurations

 

%title插图%num

图十一 Clean、build、refresh工程

%title插图%num

图十二 调试

%title插图%num

图十三 调试配置

在图十三所示的弹出窗口中,按照C语言编程1所述设置调试配置。在GDB OpenOCD Debugging下,通过点击左上角的图标添加新的启动配置。 通常在Build项目和Refresh后,C / C ++ Application栏将自动填充相应的* .elf文件。 如果没有,点击下面的Search Project来搜索。 否则使用Browse,在调试文件夹下浏览搜索(绝对路径)。 完成所有这些操作后,单击ApplyDebug。 如果使用FII-RISCV3.01对RISCV FII-PRX100 XILINX FPGA板进行编程,则可以观察到数码管持续计数的实验现象。

接下来,将在每个代码块中注释点亮数码管软件代码的详细说明。 如下:

  • Platform.h

#ifndef __PLATFORM_H 

#define __PLATFORM_H

    #ifdef __cplusplus

     extern “C” {

    #endif 

    #include “fii_types.h”                         //数据类型定义

    #include “fii_gpio.h”                           //gpio定义

    #include “fii_uart.h”                           //uart定义

    #include “fii_irq.h”                            //中断定义

    #include “encoding.h”                      //参数定义

    

    #define RAM_ADDR   0x90000000    //定义DTCM地址

    

    //定义访问地址的便捷方式

    #define  GPIO_REG(offset)   (*(volatile unsigned int *)(GPIO_ADDR + offset))

    #define  RAM_REG(offset)    (*(volatile unsigned int *)(RAM_ADDR + offset))

    #define  UART_REG(offset)   (*(volatile unsigned int *)(UART_BASE + offset))

    #define  TIME_REG(offset)   (*(volatile unsigned int *)(TIME_ADDR + offset))

    

    #ifdef __cplusplus

    }

    #endif 

#endif  // __PLATFORM_H

 

  • Fii_types.h

/*

C 数据类型     字节大小(RV32)   字节大小(RV64)

C type      | Bytes in RV32  |  Bytes in RV64

char        |       1        |        1

short       |       2        |        2

int           |       4         |        4

long        |       4        |        8

long long   |       8        |        8

void*       |       4        |        8

float        |       4        |        4

double     |       8        |        8

long double |       16       |        16

*/

 

//声明数据类型, 旨在消除与他人合作时的代码冲突

#ifndef __FII_TYPES_H 

#define __FII_TYPES_H

    #ifdef __cplusplus

     extern “C” {

    #endif 

    

    #define  RV_TYPE  RV32                        //定义RISCV CPU是32位

    typedef unsigned int        uintptr_t;

    typedef unsigned long long  u64_t;

    typedef unsigned int        u32_t;

    typedef unsigned short      u16_t;

    typedef unsigned char       u8_t;

    typedef long long           s64_t;

    typedef int                 s32_t;

    typedef short               s16_t;

    typedef char                s8_t;

#if 0

    

    #if (RV_TYPE == RV32)

    typedef unsigned long       u32_t;

    typedef long                s32_t;

    #elif (RV_TYPE == RV64)

    typedef unsigned long       u64_t;

    typedef long                s64_t;

    #endif

    

#endif

    #ifdef __cplusplus

    }

    #endif 

#endif                           // __FII_TYPES_H

 

  • Fii_gpio.h

#ifndef __FII_GPIO_H

#define __FII_GPIO_H

 

#ifdef __cplusplus

 extern “C” {

#endif 

//声明数码管字模

//实际的赋值与RISCV GPIO定义有关

//示例:

//SEG_C:0X39

//DP G F E D C B A

//0  0 1 1 1 0 0 1

#define  SEG_S   0x6D

#define  SEG_A   0x77              

#define  SEG_B   0x7C              

#define  SEG_C   0x39              

#define  SEG_D   0x5E              

#define  SEG_E   0x79              

#define  SEG_F   0x71              

#define  SEG_0   0x3F              

#define  SEG_1   0x06              

#define  SEG_2   0x5B              

#define  SEG_3   0x4F              

#define  SEG_4   0x66              

#define  SEG_5   0x6D              

#define  SEG_6   0x7D              

#define  SEG_7   0x07              

#define  SEG_8   0x7F              

#define  SEG_9   0x67              

#define  SEG_j   0x40        

#define  SEG_p   0x80              

#define  SEG__   0x00              

#define BYTE_DELAY 0x00200000                    //宏定义

#define GPIO_ADDR  0xf0000000                      //定义GPIO基地址

//定义GPIO基地址的偏移量

#define LED_VAL  0x00                                      //LED数值

#define LED_DIR  0x04                                     //LED 方向(1输入/0输出)

#define SEAT_VAL 0x08                                    //数码管位选数值

#define SEAT_DIR 0x0C                                  //数码管位选方向(1输入/0输出)

#define SEG_VAL  0x10                                  //数码管段选数值(A,B,C,D,E,F,G,DP)

#define SEG_DIR  0x14                                   //数码管段选方向(1输入/0输出)

#define BUT_VAL  0x18                                //按键数值

#define BUT_DIR  0x1C                              //按键方向(1输入/0输出)

//===============================================

//===============================================

#define SEG_POS   0x07

#ifdef __cplusplus

}

#endif

#endif /* end __FII_GPIO_H */

 

  • Fii_uart.h

#ifndef __FII_UART_H 

#define __FII_UART_H

 

#ifdef __cplusplus

 extern “C” {

#endif 

//===============================================

#define UART_BASE  0xe0000000                             //定义UART基地址

//定义UART基地址的偏移量

#define UART_VER  0x00                                             //定义UART版本寄存器

#define UART_RUN  0x04                                           //定义UART使能寄存器

#define UART_DATA 0x08                                          //定义UART发送寄存器

#define UART_RDY  0x0C                                         //定义UART状态寄存器

//===============================================

int send_to_uart(const void* ptr, int len);            //声明UART发送函数

//===============================================

#ifdef __cplusplus

}

#endif 

#endif /* __FII_UART_H */

 

  • Main.c

#include <stdio.h>                                                     //引用标准的I / O库,主要用于声明printf函数

#include “platform.h”                                                  //用户自定义的函数库,包括GPIO配置,内存空间以及相关的子函数和参数的定义

 

#define  NOP_DELAY  0x100                                 //定义一个宏

const unsigned char font[] =                                    //索引显示数码管的数组,这里使用十六进制(0〜f)

{

    SEG_0, SEG_1, SEG_2, SEG_3,

    SEG_4, SEG_5, SEG_6, SEG_7,

    SEG_8, SEG_9, SEG_A, SEG_B,

    SEG_C, SEG_D, SEG_E, SEG_F

};

//延迟函数

void delay_cnt (int cnt)

{

    u32_t  i;

    for(i = 0; i < cnt ; i ++ )

        asm(“nop”);

    return;

};

int main(void)                                                    //定义主函数

{

    //数码管初始化

    unsigned int  curr_seat = 0x01;               //初始化数码管初始位置

    unsigned int  char_num = 0;                      //数码管显示的值

    unsigned int  curr_num = 0;                     //数码管点亮停留时间

    unsigned int  char_pos = 0;                        //引索数码管字模

    //UART输出字符串

    printf(“\r\nRiscV Program : Display segment number and print it. \r\n”);

    //GPIO初始化

    (*(volatile unsigned int *)(GPIO_ADDR + LED_VAL)) = ~0L;                         //led_value数值为0,取反参见原理图接线

    (*(volatile unsigned int *)(GPIO_ADDR + LED_DIR)) = 0;                              //LED方向为输出

    (*(volatile unsigned int *)(GPIO_ADDR + SEAT_DIR)) = 0;                          //数码管位选方向为输出

    (*(volatile unsigned int *)(GPIO_ADDR + SEG_DIR)) = 0;                           //数码管段选方向为输出

    while ( 1 )//主循环

    {

        (*(volatile unsigned int *)(GPIO_ADDR + SEG_VAL)) = ~font[char_pos & 0xf];                  //设置数码管为字模中的一个值,取反参见原理图接线

        (*(volatile unsigned int *)(GPIO_ADDR + SEAT_VAL)) = ~curr_seat;                                  //指定数码管位置,取反参见原理图接线

        delay_cnt (NOP_DELAY);                                                        //时间延迟

        curr_seat = curr_seat << 1;                                                   //点亮的数码管左移一位

        if(curr_seat == 0x40)                                                              //决策判断数码管是否显示到最高位

        {

            curr_seat = 0x01;                                                              //重新设置数码管显示为最低位

            curr_num ++ ;                                                                 //计时器加一

            

            if(curr_num % 1000 == 9)                                          //避免多次输出,每一千次循环输出一次

            {

                char_num ++ ;                                                        //数码管显示的内容加一

                printf(“seg cnt num = 0x%06x \r\n”, char_num);

            }

            

            char_pos = char_num;                                            //将数码管显示的内容赋值给char_pos,字模索引

        }

        else char_pos = char_pos >> 4;                                  //当数码管显示位置不是最高位,当前显示的内容右移4位(16进制右移一位)

    }

}

 

5. RISCV_button 工程

与前面的步骤相同,将目标工程导入Freedom Studio,如图十四所示。实验现象是,按下按键(此处仅实现了三个按钮:menu,up和return),相应的数码管显示将从“1”更改为“0”。

%title插图%num

图十四 RISCV_button工程

除了RISCV_button的主程序与RISCV_seg_cnt的主程序不同之外,其他头文件均相同。 因此,这里只会列出main.c文件。 详细信息将在代码模块中注释说明。

  • Main.c

#include <stdio.h>                                                     //引用标准的I / O库,主要用于声明printf函数

#include “platform.h”                                             //用户自定义的函数库,包括GPIO配置,内存空间以及相关的子函数和参数的定义

#define NOP_DELAY 0x00000200                      //定义一个宏,该值不同于RISCV_seg_cnt,可根据需要修改

int main(void)                                                          //按键工程主函数

{

    unsigned int  curr_seat = 0x01;                       //初始化数码管初始位置

    volatile unsigned int  i;                                       //临时变量,用于延迟循环

    unsigned int  curr_gpio_val;                              //读取当前按键值

    (*(volatile unsigned int *)(GPIO_ADDR + SEAT_DIR)) = 0;                   //数码管位选方向为输出

    (*(volatile unsigned int *)(GPIO_ADDR + SEG_DIR)) = 0;                   //数码管段选方向为输出

    (*(volatile unsigned int *)(GPIO_ADDR + BUT_DIR)) = ~0L;            //按键方向位输入

    (*(volatile unsigned int *)(GPIO_ADDR + SEG_VAL)) = ~SEG__;     //初始化数码管显示为空,取反参见原理图接线

    printf(“\r\nThis is a button test program. \r\n”);

    while (1)                                                               //主循环

    {

        curr_gpio_val = (*(volatile unsigned int *)(GPIO_ADDR + BUT_VAL));               //读取当前按键值

        (*(volatile unsigned int *)(GPIO_ADDR + SEAT_VAL)) = ~curr_seat;                    //设置数码管当前显示位置,取反参见原理图接线

        if(curr_gpio_val & curr_seat)                  //决策判断,如果读取的位置不对应

                                     //按键和对应的数码管数值按位相等操作

                                     //按键被取反,参见原理图接线(按键按下-》低电平,按键松开-》高电平)

            (*(volatile unsigned int *)(GPIO_ADDR + SEG_VAL)) = ~SEG_1;               //显示 ‘1’

        else                              //如果读取的位置对应

            (*(volatile unsigned int *)(GPIO_ADDR + SEG_VAL)) = ~SEG_0;              //显示 ‘0’

        for(i = 0; i < NOP_DELAY; i ++ )                          //数码管显示延迟时间

            asm(“nop”);

        curr_seat = curr_seat << 1;                                  //数码管显示位置左移一位

        if(curr_seat == 0x08) curr_seat = 0x01;              //决策判断数码管是否显示超过第三位(只有三个数码管对应三个按键)

                                              

    }

}

 

6. RISCV_timer_irq 工程

在深入研究工程之前,先简单介绍一下中断。在数字计算机中,中断是处理器对需要软件注意的事件的响应。中断条件将警告处理器,并将其用作对处理器的请求,在允许后中断当前正在执行的代码,以便可以及时处理该事件。 如果请求被接受,则处理器将通过挂起其当前活动,保存其状态并执行称为中断处理程序(interrupt service routine,ISR)的功能来处理该事件来作出响应。 中断是临时的,除非中断指示致命错误,否则在中断处理程序完成后,处理器将恢复正常活动。

更复杂的是,处理器可能会面临处理多个中断的情况。 在这种情况下,应考虑中断优先级(interrupt priority level,IPL)。 通常,一次只允许一个中断,其他中断应等待直到其完成。 有时,在中断中可能会再次发生中断,这称为中断嵌套。

RISCV_timer_irq避免了复杂的中断发生,仅实现机器模式计时器中断。

与以前相同的步骤导入工程。 相应的Freedom Studio Project Explorer如图十五所示。

%title插图%num

图十五RISCV_timer_irq

在此使用新的头文件fii_irq.h。 除此之外,还将介绍entry.S和main.c。 其余的头文件与之前的工程相同。

  • Fii_irq.h

#ifndef __FII_IRQ_H 

#define __FII_IRQ_H

    #ifdef __cplusplus

     extern “C” {

    #endif 

    #include “fii_types.h”

    #include “encoding.h”

    

    #define CLINT_CTRL_ADDR (0x02000000)                          //定义内核中断地址空间

    #define PLIC_CTRL_ADDR  (0x0C000000)                           //定义外部中断地址空间 

    #define TIME_ADDR       CLINT_CTRL_ADDR                         //计时器中断地址    

    

    #define RTC_FREQ        32768                                                 //RTC计时器频率

    

    #define MCAUSE_INT      0x80000000                               //mcause位31屏蔽,决策判断,‘1’为中断,‘0’为异常

    #define MCAUSE_CAUSE    0x7FFFFFFF                           //mcause位30-0屏蔽, 决策判断,异常代码具体数值

                                      //机器模式计时器中断的异常代码是‘7’

    

    //定义内核中断基地址的偏移量

    #define CLINT_CTRL_REG  (0x0000 << 2)                          //0x0000, 内核中断寄存器

    #define TM_CTRL_REG     (0x0001 << 2)                          //0x0004, 计时器控制寄存器

    #define TM_L_REG        (0x0002 << 2)                              //0x0008, timer_val寄存器低32位

    #define TM_H_REG        (0x0003 << 2)                            //0x000c, timer_val寄存器高32位

    #define TMCMP_L_REG     (0x0004 << 2)                       //0x0010, 计时器比较寄存器低32位

    #define TMCMP_H_REG     (0x0005 << 2)                      //0x0014, 计时器比较寄存器高32位

    

    unsigned int handle_trap(unsigned int mcause, unsigned int epc);               //定义中断入口函数

    

    #ifdef __cplusplus

    }

    #endif 

#endif  // __FII_IRQ_H

 

注意mcause寄存器的实现遵循版本1.9《RISC-V指令集特权体系结构手册》(RISC-V instruction set manual privileged architecture)中的定义。 如图十六所示。

%title插图%num

图十六 mcause寄存器(位31 =中断,位30-0 =异常代码)

中断工程将使用entry.S文件,该文件用于调用中断入口函数。 在主程序的初始化函数中,trap_entry的地址将分配给RISCV CPU控制和状态寄存器(control and status register,CSR)mtvec。 当实际发生中断时,RISCV CPU程序计数器(program counter,PC)将从主要执行程序中挂起,并指向trap_entry函数的地址。 中断处理程序完成后,PC将恢复主要执行程序。

  • Entry.S

.equ REGBYTES,  (1 << 2)                                       #定义REGBYTES为4

#.file “encoding.h”

  .section      .text.entry                                    #定义为代码段

  .align 2                                                              #2^2 = 4字节对齐

  .global trap_entry                                         #声明为global函数

trap_entry:                                                    #函数开始

  ADDI sp, sp, -32*REGBYTES                       # 定义当前所使用的栈深度,将指针指向栈底

                                        #将X1-X31寄存器存入栈

  SW x1, 1*REGBYTES(sp)

  SW x2, 2*REGBYTES(sp)

  SW x3, 3*REGBYTES(sp)

  SW x4, 4*REGBYTES(sp)

  SW x5, 5*REGBYTES(sp)

  SW x6, 6*REGBYTES(sp)

  SW x7, 7*REGBYTES(sp)

  SW x8, 8*REGBYTES(sp)

  SW x9, 9*REGBYTES(sp)

  SW x10, 10*REGBYTES(sp)

  SW x11, 11*REGBYTES(sp)

  SW x12, 12*REGBYTES(sp)

  SW x13, 13*REGBYTES(sp)

  SW x14, 14*REGBYTES(sp)

  SW x15, 15*REGBYTES(sp)

  SW x16, 16*REGBYTES(sp)

  SW x17, 17*REGBYTES(sp)

  SW x18, 18*REGBYTES(sp)

  SW x19, 19*REGBYTES(sp)

  SW x20, 20*REGBYTES(sp)

  SW x21, 21*REGBYTES(sp)

  SW x22, 22*REGBYTES(sp)

  SW x23, 23*REGBYTES(sp)

  SW x24, 24*REGBYTES(sp)

  SW x25, 25*REGBYTES(sp)

  SW x26, 26*REGBYTES(sp)

  SW x27, 27*REGBYTES(sp)

  SW x28, 28*REGBYTES(sp)

  SW x29, 29*REGBYTES(sp)

  SW x30, 30*REGBYTES(sp)

  SW x31, 31*REGBYTES(sp)

  #在进入handle_trap函数时,传递参数(a0,a1,a2),退出函数时,传递return参数(a0)

  csrr a0, mcause                          #x[a0] = CSRs[mcause]

  csrr a1, mepc                             #x[a1] = CSRs[mepc]

  mv a2, sp                                    #x[a2] = x[sp]

  call handle_trap                         #调用handle_trap函数

  csrw mepc, a0#CSRs[mepc] = x[a0]

  #将栈里的内容还原给X1-X31

  LW x1, 1*REGBYTES(sp)

  LW x2, 2*REGBYTES(sp)

  LW x3, 3*REGBYTES(sp)

  LW x4, 4*REGBYTES(sp)

  LW x5, 5*REGBYTES(sp)

  LW x6, 6*REGBYTES(sp)

  LW x7, 7*REGBYTES(sp)

  LW x8, 8*REGBYTES(sp)

  LW x9, 9*REGBYTES(sp)

  LW x10, 10*REGBYTES(sp)

  LW x11, 11*REGBYTES(sp)

  LW x12, 12*REGBYTES(sp)

  LW x13, 13*REGBYTES(sp)

  LW x14, 14*REGBYTES(sp)

  LW x15, 15*REGBYTES(sp)

  LW x16, 16*REGBYTES(sp)

  LW x17, 17*REGBYTES(sp)

  LW x18, 18*REGBYTES(sp)

  LW x19, 19*REGBYTES(sp)

  LW x20, 20*REGBYTES(sp)

  LW x21, 21*REGBYTES(sp)

  LW x22, 22*REGBYTES(sp)

  LW x23, 23*REGBYTES(sp)

  LW x24, 24*REGBYTES(sp)

  LW x25, 25*REGBYTES(sp)

  LW x26, 26*REGBYTES(sp)

  LW x27, 27*REGBYTES(sp)

  LW x28, 28*REGBYTES(sp)

  LW x29, 29*REGBYTES(sp)

  LW x30, 30*REGBYTES(sp)

  LW x31, 31*REGBYTES(sp)

  addi sp, sp, 32*REGBYTES                          #将栈全部释放掉,栈指针复位到进入中断之前的位置

  mret                                                                   #退出中断,恢复到当前系统的PC指针

.weak handle_trap

handle_trap:

1:

  j 1b

 

如图十七所示,之前使用的栈原理如下:

1:中断之前堆栈指针的位置

2:在进入中断之前,将堆栈指针移至-32 x 4字节位置

3:存储1-2个之间的31个寄存器值,并且堆栈指针停留在位置2

4:在中断完成之前,将3到4之间的数据加载回31个寄存器,然后将堆栈指针移回4。

注意在整个过程之后,堆栈指针将返回其原始位置。 尽管堆栈为所有32个寄存器留出了空间,但x0硬连线为0,该值没有改变,因此仅操作31个寄存器。

%title插图%num

图十七 栈原理

 

  • Main.c

#include <stdio.h>                                   //引用标准的I / O库,主要用于声明printf函数

#include “platform.h”                                //用户自定义的函数库,包括GPIO配置,内存空间以及相关的子函数和参数的定义

                    

 

extern void trap_entry();                           //声明trap_entry函数是从外部调用的(entry.S)

void set_timer(u64_t msec)                          //定义设置计时器的函数

{

    //用两个32位寄存器组合定义64位计时器

    u64_t now;                                                    //定义64位计时器‘now’

    now   = TIME_REG(TM_H_REG);             //先赋值高32位

    now   = now << 32;                                           //把高32位移到正确的位置

    now   |= TIME_REG(TM_L_REG);                          //把低32位按位或,组成完整的64位计时器

    //在当前计数器的数值上增加时间(转成计数值,毫秒msec), 得到新的64位计数器‘then’

    u64_t then = now + msec*(RTC_FREQ/1000);               //将RTC_FREQ (32768)转化为msec (1000)

    //将‘then’低32位写入计时器比较寄存器

    TIME_REG( TMCMP_L_REG ) = then & 0xffffffff;

    TIME_REG( TMCMP_H_REG ) = then >> 32;                 //将‘then’高32位写入计时器比较寄存器

    

    return;

}

//计时器 计数器(counter)

unsigned int time_cnt = 0;

/*声明计时器中断的入口函数*/

void handle_m_time_interrupt(){

    clear_csr(mie, MIP_MTIP);                     //mie位7是机器模式计时器中断使能

                             //在进入中断处理程序后关闭MIP_MTIP位

                             //避免中断嵌套

    set_timer(250);                                          //重新设置下一次中断在250ms后产生

    //检查进入中断的次数,每隔4次亮一次LED,打印一次信息

    if((time_cnt % 4 ) == 0)                             // 1s, 250ms*4

    {

        GPIO_REG(LED_VAL) = ~((time_cnt >> 2) & 0xff);                      //每一秒亮一次LED,只保留有效的低8位 

                                                      //只有8位LED灯(0xff),取反参见原理图接线

        printf(“time_irq = 0x%08x \r\n”, time_cnt);                         //输出打印

    }

    time_cnt ++;//计时器计数器加一

    //重新启动计时器中断

    set_csr(mie, MIP_MTIP);                                 //mie位7是机器模式计时器中断使能

                           //在退出中断处理程序之前设置MIP_MTIP位

}

//定义总中断入口函数

unsigned int handle_trap(unsigned int mcause, unsigned int epc)

{

    //决策判断

    //中断或是异常(mcause位31)

    //异常代码是否为7(mcause位30-0)

    //同时满足时,确认为计时器中断

    if ((mcause & MCAUSE_INT) && ((mcause & MCAUSE_CAUSE) == IRQ_M_TIMER))

    {

        handle_m_time_interrupt();                    //调用中断处理程序

    }

    else 

    {

        //输出调试信息

        printf(“There is not any handle_trap available \n”);

        printf(“mstatus    = 0x%08x\n” , read_csr(mstatus) );                     //机器状态寄存器mstatus: machine status register

        printf(“mie        = 0x%08x\n” , read_csr(mie) );                            //机器中断使能寄存器mie: machine interrupt-enable register

        printf(“mcause     = 0x%08x\n” , read_csr(mcause) );                        //机器陷阱原因mcause: machine trap cause

        printf(“mtvec      = 0x%08x\n” , read_csr(mtvec));                           //机器自陷向量基址寄存器mtvec: machine trap-handler base address

  

    }

    return epc;

}

//初始化函数

void _init(void)

{

    time_cnt = 0;                                   //初始化计时器计数器

    printf(“Program Initial… \n”);

   

    write_csr(mtvec, &trap_entry);                        //赋值trap_entry函数地址给mtvec

                                  //当中断开始时,PC指向trap_entry函数

    

    return;

}

//LED初始化

static void init_led(void)

{

    GPIO_REG(LED_VAL) = ~0L;                    //初始化LED默认不亮

                              //取反参见原理图接线

    GPIO_REG(LED_DIR) = 0;                          //初始化LED方向为输出

    return;

}

int main(void)                 //主函数

{

    int  i = 0;                     //用于延迟循环

    

    _init();                         //初始函数

    

    printf(“\r\nRiscV Program : Invoke timer IRQ. \r\n”);

    init_led();                                          //LED初始化

    set_timer(250);                                //第一次设置计时器

    set_csr(mie, MIP_MTIP);                   //启动计时器中断

    write_csr(mstatus, MSTATUS_MIE);                        //启动全局中断

    TIME_REG( TM_CTRL_REG ) = 0x80000001;                        //计时器使能,启动计时器计数

    

    while ( 1 )                  //循环

    {

        for(i = 0; i < 0x100000; i ++ )

            asm(“nop”);                       //每0x100000次,运行汇编指令’nop’

    }

}

 

 

7. RISCV_seg_irq工程

接下来,将计时器中断工程和数码管工程组合成RISCV_seg_irq工程。 按照与之前相同的步骤导入工程。 仅main.c文件与RISCV_timer_irq中的main.c文件略有不同。

  • Main.c

#include <stdio.h>                           //引用标准的I / O库,主要用于声明printf函数

#include “platform.h”                       //用户自定义的函数库,包括GPIO配置,内存空间以及相关的子函数和参数的定义

 

#define  NOP_DELAY  0x100                  //定义宏

extern void trap_entry();                                       //声明trap_entry函数是从外部调用的(entry.S)

void set_timer(u64_t msec)                                     //定义设置计时器的函数

{

    //用两个32位寄存器组合定义64位计时器

    u64_t now;                                                           //定义64位计时器‘now’

    now   = TIME_REG(TM_H_REG);                  //先赋值高32位

    now   = now << 32;                                         //把高32位移到正确的位置

    now   |= TIME_REG(TM_L_REG);                //把低32位按位或,组成完整的64位计时器

    //在当前计数器的数值上增加时间(转成计数值,毫秒msec), 得到新的64位计数器‘then’

    u64_t then = now + msec*(RTC_FREQ/1000);                  //将RTC_FREQ (32768)转化为msec (1000)

    //将‘then’低32位写入计时器比较寄存器

    TIME_REG( TMCMP_L_REG ) = then & 0xffffffff;

    TIME_REG( TMCMP_H_REG ) = then >> 32;                      //将‘then’高32位写入计时器比较寄存器

    

    return;

}

//计时器 计数器(counter)

unsigned int time_cnt = 0;

/*声明计时器中断的入口函数*/

void handle_m_time_interrupt(){

    clear_csr(mie, MIP_MTIP);                   //mie位7是机器模式计时器中断使能

                             //在进入中断处理程序后关闭MIP_MTIP位

                             //避免中断嵌套

    set_timer(500);                                      // 重新设置下一次中断在500ms后产生

    //检查进入中断的次数,每隔4次亮一次LED,打印一次信息

    if((time_cnt % 4 ) == 0)                        // 2s, 500ms*4

    {

        GPIO_REG(LED_VAL) = ~((time_cnt >> 2) & 0xff);                        //每两秒亮一次LED,只保留有效的低8位 

                                                      //只有8位LED灯(0xff),取反参见原理图接线

        printf(“time_irq = 0x%08x \r\n”, time_cnt);                                         //输出打印

    }

    time_cnt ++;//计时器计数器加一

    //重新启动计时器中断

    set_csr(mie, MIP_MTIP);                                              //mie位7是机器模式计时器中断使能

                           //在退出中断处理程序之前设置MIP_MTIP位

}

//定义总中断入口函数

unsigned int handle_trap(unsigned int mcause, unsigned int epc)

{

    //决策判断

    //中断或是异常(mcause位31)

    //异常代码是否为7(mcause位30-0)

    //同时满足时,确认为计时器中断

    if ((mcause & MCAUSE_INT) && ((mcause & MCAUSE_CAUSE) == IRQ_M_TIMER))

    {

        handle_m_time_interrupt();                        //调用中断处理程序

    }

    else 

    {

         //输出调试信息

        printf(“There is not any handle_trap available \n”);

        printf(“mstatus    = 0x%08x\n” , read_csr(mstatus) );                     //机器状态寄存器mstatus: machine status register

        printf(“mie        = 0x%08x\n” , read_csr(mie) );                                  //机器中断使能寄存器mie: machine interrupt-enable register

        printf(“mcause     = 0x%08x\n” , read_csr(mcause) );                         //机器陷阱原因mcause: machine trap cause

        printf(“mtvec      = 0x%08x\n” , read_csr(mtvec));                             //机器自陷向量基址寄存器mtvec: machine trap-handler base address

  

    }

    return epc;

}

//初始化函数

void _init(void)

{

    time_cnt = 0;                            //初始化计时器计数器

    printf(“RiscV Program : Display segment number and invoke timer IRQ \r\n”);

    write_csr(mtvec, &trap_entry);                        //赋值trap_entry函数地址给mtvec

                                  //当中断开始时,PC指向trap_entry函数

    

    //增加初始化GPIO地址

    (*(volatile unsigned int *)(GPIO_ADDR + SEAT_DIR)) = 0;            //位选

    (*(volatile unsigned int *)(GPIO_ADDR + SEG_DIR)) = 0;             //段选

    return;

}

//声明数组用来索引字模

//这里使用十六进制(0~f)

const unsigned char font[] =

{

    SEG_0, SEG_1, SEG_2, SEG_3,

    SEG_4, SEG_5, SEG_6, SEG_7,

    SEG_8, SEG_9, SEG_A, SEG_B,

    SEG_C, SEG_D, SEG_E, SEG_F

};

//LED初始化

static void init_led(void)

{

    GPIO_REG(LED_VAL) = ~0L;                  //初始化LED默认不亮

                              //取反参见原理图接线

    GPIO_REG(LED_DIR) = 0;                     //初始化LED方向为输出

    return;

}

//增加数码管显示函数

//输入数码管显示字模和位选参数

void display_seg(unsigned char font_idx, unsigned int font_seat)

{

    (*(volatile unsigned int *)(GPIO_ADDR + SEG_VAL)) = ~(font[font_idx]);                  //数码管显示字模,取反参见原理图接线

    (*(volatile unsigned int *)(GPIO_ADDR + SEAT_VAL)) = ~font_seat;                       //数码管位选,取反参见原理图接线

    return;

}

int main(void)                                    //主函数

{

    int  i = 0;                                          //用于延迟循环

    //数码管初始化

    unsigned int  curr_seat = 0x01;                         //初始化数码管初始位置

    

    unsigned int  curr_seg = 0;                               //数码管显示的值

    unsigned char out_char = 0;                                  //引索数码管字模

    _init();                                                                 //初始函数

    

    printf(“\r\nRun Segment Timer IRQ Program \r\n”);

    

    init_led();                                                            //LED初始化

    set_timer(500);                                              //第一次设置计时器

    set_csr(mie, MIP_MTIP);                              //启动计时器中断

    write_csr(mstatus, MSTATUS_MIE);           //启动全局中断

    TIME_REG( TM_CTRL_REG ) = 0x80000001;                  //计时器使能,启动计时器计数

    

    while ( 1 )                       //主循环

    {

        display_seg(out_char, curr_seat);                         //调用数码管显示函数

        for(i = 0; i < NOP_DELAY ; i ++ )

            asm(“nop”);                                     //运行汇编指令’nop’

        curr_seat = curr_seat << 1;            //点亮的数码管左移一位

        if(curr_seat == 0x40)                        //决策判断数码管是否显示到最高位

        {

            curr_seg = time_cnt;                       //将计时器计数器的值赋给数码管显示

            curr_seat = 0x01;                                  //重新设置数码管显示为最低位

        }

        else

            curr_seg = curr_seg >> 4;                    //当数码管显示位置不是最高位,当前显示的内容右移4位(16进制右移一位)

        out_char = curr_seg & 0xf;                       //一次只显示一位数字(十六进制)

    }

}

 

 

8. 习题

  • 创建一个新的C语言工程,输入menu,up,return 三个按键 当menu 键 按下, 数码管显示 1,当up 键 按下, 数码管显示 2,当return 键 按下, 数码管显示 3.
  • 创建一个新的C语言工程 让LED 灯 从右到左 依次点亮,之前的LED 熄灭, 到第八个灯后(LED 7) 返回到LED 0, 如此循环往复 每次灯亮 和移位的时间为1 秒钟左右 (计时器中断)
  • 创建一个新的C语言工程根据PRX100硬件原理图, 重新编译FPGA, 在软件工程中实现:sw0 推上, led 0 亮, sw1 推上, led 1 亮,…  sw7 推上, led 7 亮.
Posted in RISC-V, 应用开发
0 0 投票数
Article Rating
订阅评论
提醒
guest
1 评论
最新
最久 最赞
内联反馈
查看所有评论
tzhuang
管理员
6 月 前

很详细,期待更多的文章和视频
同时,能否把注释语句与运行语句之间隔离一些,拼在一起看起来感觉有点差,把。。。改成 …

相关链接