易灵思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 0000u32 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);
//设置四位数码管的最高位(千位)为45.读取指定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为06.按键中断
按键中断主要学习gpioDemo

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

在下面有中断函数,需要修改红框框住的部分,修改为自己设定的中断源。
然后就可以在这个函数里面写按键中断操作了。

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

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

