易灵思FPGA的RISC-V核操作函数

本文主要针对西安电子科技大学电子工程学院的FPGA实验,使用的是易灵思的T20F256开发板。

主要补充一些RISC-V的操作函数。

1.设置指定GPIO的电平

复制代码
/**
 * @brief  设置指定 GPIO 引脚的输出电平(高或低)
 * @note   此函数执行"读-改-写"操作,不会影响其他引脚。
 * 它假设 gpio_getOutput 和 gpio_setOutput 函数已经定义。
 *
 * @param  baseAddress   GPIO 端口的基地址 (例如: GPIO0)
 * @param  pinNumber     要设置的引脚编号 (0 到 31 之间)
 * @param  level         要设置的电平 (1 = 高电平, 0 = 低电平)
 *
 * @return None
 */
void gpio_setPinLevel(u32 baseAddress, u32 pinNumber, u32 level)
{
    // 1. 读取 (Read):获取当前所有 32 个引脚的输出状态
    u32 current_outputs = gpio_getOutput(baseAddress);

    // 2. 修改 (Modify):
    if (level == 1) {
        // --- 设置为高电平 ---
        // 使用 "按位或" (|) 运算
        // (1 << pinNumber) 会创建一个掩码,只有目标位是 1 (例如 pinNumber=5, 掩码=0b100000)
        // current_outputs | 0b100000 会强制第 5 位置 1,其他位不变
        current_outputs = current_outputs | (1 << pinNumber);
    } else {
        // --- 设置为低电平 ---
        // 使用 "按位与" (&) 和 "按位取反" (~) 运算
        // (1 << pinNumber)       -> 掩码, 目标位是 1 (例如 0b100000)
        // ~(1 << pinNumber)      -> 反向掩码, 目标位是 0 (例如 0b111...11011111)
        // current_outputs & 0b11..0..11 会强制第 5 位置 0,其他位不变
        current_outputs = current_outputs & ~(1 << pinNumber);
    }

    // 3. 写入 (Write):将修改后的新值写回 GPIO 输出寄存器
    gpio_setOutput(baseAddress, current_outputs);
}

示例:

复制代码
gpio_setPinLevel(GPIO0,0,1);//设置GPIO0的第0个引脚为高电平

gpio_setPinLevel(GPIO0,0,0);//设置GPIO0的第0个引脚为低电平

2.设置连续4个GPIO的电平

复制代码
/**
 * @brief  设置指定 GPIO 端口上从 pinNumber 开始的 4 个连续引脚的电平
 * @note   此函数执行"读-改-写"操作,不会影响其他引脚。
 * 它假设 gpio_getOutput 和 gpio_setOutput 函数已经定义。
 *
 * @param  baseAddress   GPIO 端口的基地址 (例如: GPIO0)
 * @param  pinNumber     要设置的 4 个引脚中的 *起始* 引脚编号 (0 到 28)
 * @param  level         要写入的 4 位数据 (例如: 0b1011)。只有 level 的
 * 低 4 位会被使用。
 *
 * @return None
 */
void gpio_set4PinLevel(u32 baseAddress, u32 pinNumber, u32 level)
{
    // 1. 创建一个 4 位宽的掩码 (0b1111 = 0xF)
    u32 mask = 0xF;

    // 2. 将掩码移到目标位置
    // 例如, pinNumber = 8, shifted_mask = 0b1111 0000 0000
    u32 shifted_mask = (mask << pinNumber);

    // 3. 读取 (Read):获取当前所有 32 个引脚的输出状态
    u32 current_outputs = gpio_getOutput(baseAddress);

    // 4. 修改 (Modify)

    // 4a. 清除:首先将目标 4 位清零
    //     使用 (~) 将掩码反转 (例如 0b...1111 0000 1111...)
    //     然后使用 (&) 将目标位置 0,其他位保持不变
    u32 cleared_value = current_outputs & (~shifted_mask);

    // 4b. 写入新值:
    //     (level & mask) 确保 level 只有 4 位有效
    //     (<< pinNumber) 将这 4 位数据移到正确的位置
    //     使用 (|) 将新数据写入被清零的区域
    u32 new_value = cleared_value | ((level & mask) << pinNumber);

    // 5. 写入 (Write):将修改后的新值写回 GPIO 输出寄存器
    gpio_setOutput(baseAddress, new_value);
}

示例:

复制代码
gpio_set4PinLevel(GPIO0,0,0xf);
//0xf是十六进制数,其二进制数是0b1111
//这个是设置从GPIO0的0号引脚开始,0,1,2,3号引脚都设置为高电平。

gpio_set4PinLevel(GPIO0,0,0xc);
//0xc是十六进制数,其二进制数是0b1100
//这个是设置从GPIO0的0号引脚开始,0,1号,这两个低二位引脚设置为低电平
//2,3号这两个高二位引脚设置为高电平

3.设置连续8个GPIO的电平

复制代码
/**
 * @brief  设置指定 GPIO 端口上从 pinNumber 开始的 8 个连续引脚的电平
 * @note   此函数执行"读-改-写"操作,不会影响其他引脚。
 *
 * @param  baseAddress   GPIO 端口的基地址 (例如: GPIO0)
 * @param  pinNumber     要设置的 8 个引脚中的 *起始* 引脚编号 (0 到 24)
 * @param  level         要写入的 8 位数据 (例如: 0b11001010)。只有 level 的
 * 低 8 位会被使用。
 *
 * @return None
 */
void gpio_set8PinLevel(u32 baseAddress, u32 pinNumber, u32 level)
{
    // 1. 创建一个 8 位宽的掩码 (0b11111111 = 0xFF)
    u32 mask = 0xFF;

    // 2. 将掩码移到目标位置
    u32 shifted_mask = (mask << pinNumber);

    // 3. 读取 (Read)
    u32 current_outputs = gpio_getOutput(baseAddress);

    // 4. 修改 (Modify)

    // 4a. 清除:将目标 8 位清零
    u32 cleared_value = current_outputs & (~shifted_mask);

    // 4b. 写入新值:将 8 位数据移到正确位置
    u32 new_value = cleared_value | ((level & mask) << pinNumber);

    // 5. 写入 (Write)
    gpio_setOutput(baseAddress, new_value);
}

示例:

复制代码
gpio_set8PinLevel(GPIO0,0,0x3f);
//0x3f是十六进制数,转化为二进制为0011 1111
//设置GPIO0的从0-5这低六位是高电平
//设置6-7这高二位为低电平

4.数码管显示函数

复制代码
/**
 * @brief  设置数码管的段选(segment)引脚以显示特定数字。
 * @note   此函数用于驱动 8 个段选引脚(a,b,c,d,e,f,g,dp)。
 * - 假设为 "共阴极" 数码管 (Common Cathode),即高电平 (1) 点亮笔画。
 * - 假设 8 个段选引脚连接到 GPIO0 的 pin 8 到 pin 15。
 * - 假设的段码映射为 [pin15 ... pin8] -> {dp, g, f, e, d, c, b, a}。
 * - 例如 "0" (0x3F = 0b00111111) 表示 g 和 dp 熄灭,a,b,c,d,e,f 点亮。
 *
 * @param  num  要显示的数字 (0-9)。如果输入其他值,数码管将熄灭。
 *
 * @return None
 */
void set_Num(u8 num)
{
	if (num == 0)// "0" -> 0x3F (a,b,c,d,e,f)
	{
		gpio_set8PinLevel(GPIO0,8,0x3F);
	}
	else if (num == 1)// "1" -> 0x06 (b,c)
	{
		gpio_set8PinLevel(GPIO0,8,0x06);
	}
	else if (num == 2)// "2" -> 0x5B (a,b,d,e,g)
	{
		gpio_set8PinLevel(GPIO0,8,0x5B);
	}
	else if (num == 3)// "3" -> 0x4F (a,b,c,d,g)
	{
		gpio_set8PinLevel(GPIO0,8,0x4F);
	}
	else if (num == 4)// "4" -> 0x66 (b,c,f,g)
	{
		gpio_set8PinLevel(GPIO0,8,0x66);
	}
	else if (num == 5)// "5" -> 0x6D (a,c,d,f,g)
	{
		gpio_set8PinLevel(GPIO0,8,0x6D);
	}
	else if (num == 6)// "6" -> 0x7D (a,c,d,e,f,g)
	{
		gpio_set8PinLevel(GPIO0,8,0x7D);
	}
	else if (num == 7)// "7" -> 0x07 (a,b,c)
	{
		gpio_set8PinLevel(GPIO0,8,0x07);
	}
	else if (num == 8)// "8" -> 0x7F (a,b,c,d,e,f,g)
	{
		gpio_set8PinLevel(GPIO0,8,0x7F);
	}
	else if (num == 9)// "9" -> 0x6F (a,b,c,d,f,g)
	{
		gpio_set8PinLevel(GPIO0,8,0x6F);
	}
	else // 其他情况,熄灭
	{
		gpio_set8PinLevel(GPIO0,8,0x00);
	}
}

/**
 * @brief  设置 4 位数码管中的某一位显示指定的数字(用于动态扫描)
 * @note   此函数是为 "动态扫描" (Dynamic Scanning) 设计的,用于驱动一帧。
 * 它首先设置 "位选" (digit select) 引脚来选中一个数码管,
 * 然后调用 set_Num() 来设置 "段选" (segment) 引脚显示内容。
 * * 假设:
 * 1. 4 个位选引脚连接到 GPIO0 的 pin 4, 5, 6, 7。
 * 2. 数码管为 "共阴极",因此位选引脚 "低电平 (0)" 有效。
 * 3. 假设引脚映射为:
 * - idx 0 -> G1 (对应 pin 7)
 * - idx 1 -> G2 (对应 pin 6)
 * - idx 2 -> G3 (对应 pin 5)
 * - idx 3 -> G4 (对应 pin 4)
 *
 * @param  idx  要点亮的数码管索引 (0, 1, 2, 或 3)。
 * @param  num  要在该数码管上显示的数字 (0-9)。
 *
 * @return None
 */
void set_digit_number(u8 idx,u8 num)
{
    // --- 1. 设置位选 (Digit Select) ---
    // 通过 gpio_set4PinLevel 设置 pin 4,5,6,7 的电平。
    // 这是 "共阴极" 的 "低电平选通" 逻辑。
    // 4位数据 {pin7, pin6, pin5, pin4}
	if(idx==0)
	{
        // 选中第 1 位 (G1, 假设在 pin 7)
        // 0b0111 -> {pin7=0, pin6=1, pin5=1, pin4=1}
		gpio_set4PinLevel(GPIO0,4,0b0111);
	}
	else if(idx==1) // 注意:这里使用 else if 效率更高
	{
        // 选中第 2 位 (G2, 假设在 pin 6)
        // 0b1011 -> {pin7=1, pin6=0, pin5=1, pin4=1}
		gpio_set4PinLevel(GPIO0,4,0b1011);
	}
	else if(idx==2)
	{
        // 选中第 3 位 (G3, 假设在 pin 5)
        // 0b1101 -> {pin7=1, pin6=1, pin5=0, pin4=1}
		gpio_set4PinLevel(GPIO0,4,0b1101);
	}
	else if(idx==3)
	{
        // 选中第 4 位 (G4, 假设在 pin 4)
        // 0b1110 -> {pin7=1, pin6=1, pin5=1, pin4=0}
		gpio_set4PinLevel(GPIO0,4,0b1110);
	}
    // (如果 idx 不是 0-3,所有位选都将保持上一次的状态,可能导致显示错误或重影)
    // (可以考虑加一个 else 块来关闭所有位选,例如 gpio_set4PinLevel(GPIO0,4,0b1111);)

    // --- 2. 设置段选 (Segment Data) ---
    // 在选定了要点亮哪一位之后,立即设置段选引脚来显示对应的数字
	set_Num(num);
}

设置数码管的函数需要根据自己的引脚约束进行修改或者直接抄我的引脚约束。

GPIO0的0-3是4个LED,4-7是SEL1到SEL4(数码管),8-14是A-G(数码管)

示例:

复制代码
set_digit_number(0,1);
//设置四位数码管的最低位(个位)为1
set_digit_number(1,2);
//设置四位数码管的次低位(十位)为2
set_digit_number(2,3);
//设置四位数码管的次高位(百位)为3
set_digit_number(2,4);
//设置四位数码管的最高位(千位)为4

5.读取指定GPIO的电平

复制代码
/**
 * @brief  读取指定 GPIO 输入引脚的电平(高或低)
 * @note   此函数首先调用 gpio_getInput() 读取整个 32 位的 GPIO 输入寄存器,
 * 然后通过位运算分离出指定引脚的值。
 *
 * @param  baseAddress   GPIO 端口的基地址 (例如: GPIO0)
 * @param  pinNumber     要读取的引脚编号 (0 到 31 之间)
 *
 * @return u32           该引脚的电平 (1 = 高电平, 0 = 低电平)
 */
u32 gpio_getPinLevel(u32 baseAddress, u32 pinNumber)
{
    // 1. 读取 (Read):获取当前所有 32 个引脚的输入状态
    //    假设 gpio_getInput(baseAddress) 返回一个像 0b1010...0010 这样的 32 位值
    u32 all_pins_value = gpio_getInput(baseAddress);

    // 2. 隔离 (Isolate) 并返回 (Return):
    //
    //    步骤 a: (all_pins_value >> pinNumber)
    //    将 32 位的值向右移动 pinNumber 位。
    //    这会将我们感兴趣的目标引脚移动到数据
    //    的最右边(第 0 位,即 LSB)。
    //
    //    例如: all_pins_value = 0b00100000 (pin 5 是 1)
    //          pinNumber = 5
    //    (all_pins_value >> 5) 的结果是 0b00000001
    //
    //    步骤 b: (... & 0x1)
    //    将右移后的结果与 0x1 (即 0b00...0001) 进行 "按位与" 运算。
    //    这会屏蔽掉除第 0 位之外的所有其他位,
    //    确保我们只得到 0 或 1。
    //
    //    结果:如果目标引脚是 1,结果就是 1。
    //          如果目标引脚是 0,结果就是 0。
    return (all_pins_value >> pinNumber) & 0x1;
}

示例:

复制代码
state = gpio_getPinLevel(GPIO1,0);
//读取GPIO1的0号引脚的电平状态并赋值给state
//如果是高电平,state为1
//如果是低电平,state为0

6.按键中断

按键中断主要学习gpioDemo

在gpioDemo的 init() 函数中有对中断的初始化和使能,当自己要修改成指定的引脚的时候,要修改我红框框住的函数的参数即可。

在下面有中断函数,需要修改红框框住的部分,修改为自己设定的中断源。

然后就可以在这个函数里面写按键中断操作了。

7.串口

在设置riscv核的时候勾选UART,并且约束引脚。

看uartEchoDemo这个模版就行,很简单。注意得接上串口的USB线。

相关推荐
风已经起了4 天前
FPGA学习笔记——用Vitis IDE生成工程(串口发送)
笔记·学习·fpga开发·fpga·1024程序员节
ALINX技术博客5 天前
ALINX 携手 PhineDesign 亮相日本 DSF2025,用 FPGA 产品力响应时代技术浪潮挑战!
fpga开发·fpga
讽刺人生Yan5 天前
RFSOC学习记录(六)混频模式分析
学习·fpga·rfsoc
讽刺人生Yan6 天前
RFSOC学习记录(五)带通采样定理
学习·fpga·rfsoc
讽刺人生Yan7 天前
RFSOC学习记录(四)MTS时序分析
学习·fpga·rfsoc
南檐巷上学7 天前
Vivado调用FFT IP核进行数据频谱分析
fpga开发·fpga·vivado·fft·快速傅里叶变化
北城笑笑9 天前
FPGA 49 ,Xilinx Vivado 软件术语解析(Vivado 界面常用英文字段详解,以及实际应用场景和注意事项 )
fpga开发·fpga
XINVRY-FPGA9 天前
XCAU10P-2SBVB484I Xilinx Artix UltraScale+ FPGA
嵌入式硬件·fpga开发·云计算·硬件工程·dsp开发·射频工程·fpga
嵌入式Linux,10 天前
RISC-V 只会越来越好(2)
risc-v