mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-28 04:40:52 +00:00
Add basic interrupt support for ESP32C3 and ESP32
This commit is contained in:
parent
ef5d1ac7f4
commit
cac30b7544
@ -17,6 +17,7 @@ paste = "1.0"
|
||||
riscv = { version = "0.7", optional = true }
|
||||
void = { version = "1.0", default-features = false }
|
||||
xtensa-lx = { version = "0.4", optional = true }
|
||||
xtensa-lx-rt = { version = "0.7", features = ["lx6"], optional = true }
|
||||
procmacros = { path = "../esp-hal-procmacros", package = "esp-hal-procmacros" }
|
||||
# IMPORTANT:
|
||||
# Each supported device MUST have its PAC included below along with a
|
||||
@ -27,7 +28,10 @@ esp32s2_pac = { package = "esp32s2", git = "https://github.com/jessebraham/esp32
|
||||
esp32s3_pac = { package = "esp32s3", git = "https://github.com/jessebraham/esp32s3.git", branch = "develop", optional = true }
|
||||
|
||||
[features]
|
||||
esp32 = ["esp32_pac", "esp32_pac/rt", "xtensa-lx/lx6", "procmacros/rtc_slow"]
|
||||
esp32c3 = ["esp32c3_pac", "esp32c3_pac/rt", "riscv"]
|
||||
esp32s2 = ["esp32s2_pac", "esp32s2_pac/rt", "xtensa-lx/lx6", "procmacros/rtc_slow"] # FIXME
|
||||
esp32s3 = ["esp32s3_pac", "esp32s3_pac/rt", "xtensa-lx/lx6", "procmacros/rtc_slow"] # FIXME
|
||||
esp32 = ["esp32_pac", "esp32_pac/rt", "xtensa-lx/lx6", "xtensa-lx-rt/lx6", "procmacros/rtc_slow", "dual_core"]
|
||||
esp32c3 = ["esp32c3_pac", "esp32c3_pac/rt", "riscv", "single_core"]
|
||||
esp32s2 = ["esp32s2_pac", "esp32s2_pac/rt", "xtensa-lx/lx6", "xtensa-lx-rt/lx6", "procmacros/rtc_slow", "single_core"] # FIXME
|
||||
esp32s3 = ["esp32s3_pac", "esp32s3_pac/rt", "xtensa-lx/lx6", "xtensa-lx-rt/lx6", "procmacros/rtc_slow", "dual_core"] # FIXME
|
||||
|
||||
single_core = []
|
||||
dual_core = []
|
||||
|
@ -330,10 +330,12 @@ macro_rules! impl_input {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// a crate using this macro needs to provide gpio_intr_enable
|
||||
unsafe {
|
||||
(&*GPIO::ptr()).pin[$pin_num].modify(|_, w|
|
||||
w
|
||||
.pin_int_ena().bits(int_enable as u8 | ((nmi_enable as u8) << 1))
|
||||
.pin_int_ena().bits(crate::gpio_intr_enable(int_enable, nmi_enable))
|
||||
.pin_int_type().bits(event as u8)
|
||||
.pin_wakeup_enable().bit(wake_up_from_light_sleep)
|
||||
);
|
||||
|
295
esp-hal-common/src/interrupt/riscv.rs
Normal file
295
esp-hal-common/src/interrupt/riscv.rs
Normal file
@ -0,0 +1,295 @@
|
||||
use riscv::register::mcause;
|
||||
|
||||
use crate::{pac::Interrupt, Cpu};
|
||||
|
||||
// User code shouldn't usually take the mutable TrapFrame or the TrapFrame in
|
||||
// general. However this makes things like preemtive multitasking easier in
|
||||
// future
|
||||
extern "C" {
|
||||
fn interrupt1(frame: &mut TrapFrame);
|
||||
fn interrupt2(frame: &mut TrapFrame);
|
||||
fn interrupt3(frame: &mut TrapFrame);
|
||||
fn interrupt4(frame: &mut TrapFrame);
|
||||
fn interrupt5(frame: &mut TrapFrame);
|
||||
fn interrupt6(frame: &mut TrapFrame);
|
||||
fn interrupt7(frame: &mut TrapFrame);
|
||||
fn interrupt8(frame: &mut TrapFrame);
|
||||
fn interrupt9(frame: &mut TrapFrame);
|
||||
fn interrupt10(frame: &mut TrapFrame);
|
||||
fn interrupt11(frame: &mut TrapFrame);
|
||||
fn interrupt12(frame: &mut TrapFrame);
|
||||
fn interrupt13(frame: &mut TrapFrame);
|
||||
fn interrupt14(frame: &mut TrapFrame);
|
||||
fn interrupt15(frame: &mut TrapFrame);
|
||||
fn interrupt16(frame: &mut TrapFrame);
|
||||
fn interrupt17(frame: &mut TrapFrame);
|
||||
fn interrupt18(frame: &mut TrapFrame);
|
||||
fn interrupt19(frame: &mut TrapFrame);
|
||||
fn interrupt20(frame: &mut TrapFrame);
|
||||
fn interrupt21(frame: &mut TrapFrame);
|
||||
fn interrupt22(frame: &mut TrapFrame);
|
||||
fn interrupt23(frame: &mut TrapFrame);
|
||||
fn interrupt24(frame: &mut TrapFrame);
|
||||
fn interrupt25(frame: &mut TrapFrame);
|
||||
fn interrupt26(frame: &mut TrapFrame);
|
||||
fn interrupt27(frame: &mut TrapFrame);
|
||||
fn interrupt28(frame: &mut TrapFrame);
|
||||
fn interrupt29(frame: &mut TrapFrame);
|
||||
fn interrupt30(frame: &mut TrapFrame);
|
||||
fn interrupt31(frame: &mut TrapFrame);
|
||||
}
|
||||
|
||||
/// Interrupt kind
|
||||
pub enum InterruptKind {
|
||||
/// Level interrupt
|
||||
Level,
|
||||
/// Edge interrupt
|
||||
Edge,
|
||||
}
|
||||
|
||||
/// Enumeration of available CPU interrupts.
|
||||
/// It is possible to create a handler for each of the interrupts. (e.g. `interrupt3`)
|
||||
pub enum CpuInterrupt {
|
||||
Interrupt1 = 1,
|
||||
Interrupt2,
|
||||
Interrupt3,
|
||||
Interrupt4,
|
||||
Interrupt5,
|
||||
Interrupt6,
|
||||
Interrupt7,
|
||||
Interrupt8,
|
||||
Interrupt9,
|
||||
Interrupt10,
|
||||
Interrupt11,
|
||||
Interrupt12,
|
||||
Interrupt13,
|
||||
Interrupt14,
|
||||
Interrupt15,
|
||||
Interrupt16,
|
||||
Interrupt17,
|
||||
Interrupt18,
|
||||
Interrupt19,
|
||||
Interrupt20,
|
||||
Interrupt21,
|
||||
Interrupt22,
|
||||
Interrupt23,
|
||||
Interrupt24,
|
||||
Interrupt25,
|
||||
Interrupt26,
|
||||
Interrupt27,
|
||||
Interrupt28,
|
||||
Interrupt29,
|
||||
Interrupt30,
|
||||
Interrupt31,
|
||||
}
|
||||
|
||||
/// Interrupt priority levels.
|
||||
pub enum Priority {
|
||||
None,
|
||||
Priority1,
|
||||
Priority2,
|
||||
Priority3,
|
||||
Priority4,
|
||||
Priority5,
|
||||
Priority6,
|
||||
Priority7,
|
||||
Priority8,
|
||||
Priority9,
|
||||
Priority10,
|
||||
Priority11,
|
||||
Priority12,
|
||||
Priority13,
|
||||
Priority14,
|
||||
Priority15,
|
||||
}
|
||||
|
||||
/// Enable and assign a peripheral interrupt to an CPU interrupt.
|
||||
pub fn enable(_core: Cpu, interrupt: Interrupt, which: CpuInterrupt) {
|
||||
unsafe {
|
||||
let interrupt_number = interrupt as isize;
|
||||
let cpu_interrupt_number = which as isize;
|
||||
let intr = &*crate::pac::INTERRUPT_CORE0::ptr();
|
||||
let intr_map_base = intr.mac_intr_map.as_ptr();
|
||||
intr_map_base
|
||||
.offset(interrupt_number)
|
||||
.write_volatile(cpu_interrupt_number as u32);
|
||||
|
||||
// enable interrupt
|
||||
intr.cpu_int_enable
|
||||
.write(|w| w.bits(1 << cpu_interrupt_number));
|
||||
}
|
||||
}
|
||||
|
||||
/// Disable the given peripheral interrupt.
|
||||
pub fn disable(_core: Cpu, interrupt: Interrupt) {
|
||||
unsafe {
|
||||
let interrupt_number = interrupt as isize;
|
||||
let intr = &*crate::pac::INTERRUPT_CORE0::ptr();
|
||||
let intr_map_base = intr.mac_intr_map.as_ptr();
|
||||
intr_map_base.offset(interrupt_number).write_volatile(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the interrupt kind (i.e. level or edge) of an CPU interrupt
|
||||
pub fn set_kind(_core: Cpu, which: CpuInterrupt, kind: InterruptKind) {
|
||||
unsafe {
|
||||
let intr = &*crate::pac::INTERRUPT_CORE0::ptr();
|
||||
let cpu_interrupt_number = which as isize;
|
||||
|
||||
let interrupt_type = match kind {
|
||||
InterruptKind::Level => 0,
|
||||
InterruptKind::Edge => 1,
|
||||
};
|
||||
intr.cpu_int_type.modify(|r, w| {
|
||||
w.bits(
|
||||
r.bits() & !(1 << cpu_interrupt_number) | (interrupt_type << cpu_interrupt_number),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the priority level of an CPU interrupt
|
||||
pub fn set_priority(_core: Cpu, which: CpuInterrupt, priority: Priority) {
|
||||
unsafe {
|
||||
let intr = &*crate::pac::INTERRUPT_CORE0::ptr();
|
||||
let cpu_interrupt_number = which as isize;
|
||||
let intr_prio_base = intr.cpu_int_pri_0.as_ptr();
|
||||
|
||||
intr_prio_base
|
||||
.offset(cpu_interrupt_number as isize)
|
||||
.write_volatile(priority as u32);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear a CPU interrupt
|
||||
pub fn clear(_core: Cpu, which: CpuInterrupt) {
|
||||
unsafe {
|
||||
let cpu_interrupt_number = which as isize;
|
||||
let intr = &*crate::pac::INTERRUPT_CORE0::ptr();
|
||||
intr.cpu_int_clear
|
||||
.write(|w| w.bits(1 << cpu_interrupt_number));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get status of peripheral interrupts
|
||||
pub fn get_status(_core: Cpu) -> u128 {
|
||||
unsafe {
|
||||
((*crate::pac::INTERRUPT_CORE0::ptr())
|
||||
.intr_status_reg_0
|
||||
.read()
|
||||
.bits() as u128)
|
||||
| ((*crate::pac::INTERRUPT_CORE0::ptr())
|
||||
.intr_status_reg_1
|
||||
.read()
|
||||
.bits() as u128)
|
||||
<< 32
|
||||
}
|
||||
}
|
||||
|
||||
// TODO should this be aligned with Atomic Emulation Trap Handler in future?
|
||||
/// Registers saved in trap handler
|
||||
#[doc(hidden)]
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct TrapFrame {
|
||||
pub ra: usize,
|
||||
pub t0: usize,
|
||||
pub t1: usize,
|
||||
pub t2: usize,
|
||||
pub t3: usize,
|
||||
pub t4: usize,
|
||||
pub t5: usize,
|
||||
pub t6: usize,
|
||||
pub a0: usize,
|
||||
pub a1: usize,
|
||||
pub a2: usize,
|
||||
pub a3: usize,
|
||||
pub a4: usize,
|
||||
pub a5: usize,
|
||||
pub a6: usize,
|
||||
pub a7: usize,
|
||||
pub s0: usize,
|
||||
pub s1: usize,
|
||||
pub s2: usize,
|
||||
pub s3: usize,
|
||||
pub s4: usize,
|
||||
pub s5: usize,
|
||||
pub s6: usize,
|
||||
pub s7: usize,
|
||||
pub s8: usize,
|
||||
pub s9: usize,
|
||||
pub s10: usize,
|
||||
pub s11: usize,
|
||||
pub gp: usize,
|
||||
pub tp: usize,
|
||||
pub sp: usize,
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is called from an assembly trap handler.
|
||||
#[doc(hidden)]
|
||||
#[link_section = ".trap.rust"]
|
||||
#[export_name = "_start_trap_rust_hal"]
|
||||
pub unsafe extern "C" fn start_trap_rust_hal(trap_frame: *mut TrapFrame) {
|
||||
extern "C" {
|
||||
pub fn _start_trap_rust(trap_frame: *const TrapFrame);
|
||||
|
||||
pub fn DefaultHandler();
|
||||
}
|
||||
|
||||
let cause = mcause::read();
|
||||
if cause.is_exception() {
|
||||
_start_trap_rust(trap_frame);
|
||||
} else {
|
||||
let code = riscv::register::mcause::read().code();
|
||||
match code {
|
||||
1 => interrupt1(trap_frame.as_mut().unwrap()),
|
||||
2 => interrupt2(trap_frame.as_mut().unwrap()),
|
||||
3 => interrupt3(trap_frame.as_mut().unwrap()),
|
||||
4 => interrupt4(trap_frame.as_mut().unwrap()),
|
||||
5 => interrupt5(trap_frame.as_mut().unwrap()),
|
||||
6 => interrupt6(trap_frame.as_mut().unwrap()),
|
||||
7 => interrupt7(trap_frame.as_mut().unwrap()),
|
||||
8 => interrupt8(trap_frame.as_mut().unwrap()),
|
||||
9 => interrupt9(trap_frame.as_mut().unwrap()),
|
||||
10 => interrupt10(trap_frame.as_mut().unwrap()),
|
||||
11 => interrupt11(trap_frame.as_mut().unwrap()),
|
||||
12 => interrupt12(trap_frame.as_mut().unwrap()),
|
||||
13 => interrupt13(trap_frame.as_mut().unwrap()),
|
||||
14 => interrupt14(trap_frame.as_mut().unwrap()),
|
||||
16 => interrupt16(trap_frame.as_mut().unwrap()),
|
||||
15 => interrupt15(trap_frame.as_mut().unwrap()),
|
||||
17 => interrupt17(trap_frame.as_mut().unwrap()),
|
||||
18 => interrupt18(trap_frame.as_mut().unwrap()),
|
||||
19 => interrupt19(trap_frame.as_mut().unwrap()),
|
||||
20 => interrupt20(trap_frame.as_mut().unwrap()),
|
||||
21 => interrupt21(trap_frame.as_mut().unwrap()),
|
||||
22 => interrupt22(trap_frame.as_mut().unwrap()),
|
||||
23 => interrupt23(trap_frame.as_mut().unwrap()),
|
||||
24 => interrupt24(trap_frame.as_mut().unwrap()),
|
||||
25 => interrupt25(trap_frame.as_mut().unwrap()),
|
||||
26 => interrupt26(trap_frame.as_mut().unwrap()),
|
||||
27 => interrupt27(trap_frame.as_mut().unwrap()),
|
||||
28 => interrupt28(trap_frame.as_mut().unwrap()),
|
||||
29 => interrupt29(trap_frame.as_mut().unwrap()),
|
||||
30 => interrupt30(trap_frame.as_mut().unwrap()),
|
||||
31 => interrupt31(trap_frame.as_mut().unwrap()),
|
||||
_ => DefaultHandler(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[no_mangle]
|
||||
pub fn _setup_interrupts() {
|
||||
extern "C" {
|
||||
static _vector_table: *const u32;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let vec_table = &_vector_table as *const _ as usize;
|
||||
riscv::register::mtvec::write(vec_table, riscv::register::mtvec::TrapMode::Vectored);
|
||||
};
|
||||
}
|
144
esp-hal-common/src/interrupt/xtensa.rs
Normal file
144
esp-hal-common/src/interrupt/xtensa.rs
Normal file
@ -0,0 +1,144 @@
|
||||
use crate::{pac::Interrupt, Cpu};
|
||||
|
||||
extern "C" {
|
||||
fn level1_interrupt();
|
||||
fn level2_interrupt();
|
||||
fn level3_interrupt();
|
||||
fn level4_interrupt();
|
||||
fn level5_interrupt();
|
||||
fn level6_interrupt();
|
||||
fn level7_interrupt();
|
||||
}
|
||||
|
||||
/// Enumeration of available CPU interrupts
|
||||
/// It's possible to create one handler per priority level. (e.g `level1_interrupt`)
|
||||
#[allow(unused)]
|
||||
pub enum CpuInterrupt {
|
||||
Interrupt0LevelPriority1 = 0,
|
||||
Interrupt1LevelPriority1,
|
||||
Interrupt2LevelPriority1,
|
||||
Interrupt3LevelPriority1,
|
||||
Interrupt4LevelPriority1,
|
||||
Interrupt5LevelPriority1,
|
||||
Interrupt6Timer0Priority1,
|
||||
Interrupt7SoftwarePriority1,
|
||||
Interrupt8LevelPriority1,
|
||||
Interrupt9LevelPriority1,
|
||||
Interrupt10EdgePriority1,
|
||||
Interrupt11ProfilingPriority3,
|
||||
Interrupt12LevelPriority1,
|
||||
Interrupt13LevelPriority1,
|
||||
Interrupt14NmiPriority7,
|
||||
Interrupt15Timer1Priority3,
|
||||
Interrupt16Timer2Priority3,
|
||||
Interrupt17LevelPriority1,
|
||||
Interrupt18LevelPriority1,
|
||||
Interrupt19LevelPriority2,
|
||||
Interrupt20LevelPriority2,
|
||||
Interrupt21LevelPriority2,
|
||||
Interrupt22EdgePriority3,
|
||||
Interrupt23LevelPriority3,
|
||||
Interrupt24LevelPriority4,
|
||||
Interrupt25LevelPriority4,
|
||||
Interrupt26LevelPriority5,
|
||||
Interrupt27LevelPriority3,
|
||||
Interrupt28EdgePriority4,
|
||||
Interrupt29SoftwarePriority3,
|
||||
Interrupt30EdgePriority4,
|
||||
Interrupt31EdgePriority5,
|
||||
}
|
||||
|
||||
/// Enable and assign a peripheral interrupt to an CPU interrupt.
|
||||
pub fn enable(core: Cpu, interrupt: Interrupt, which: CpuInterrupt) {
|
||||
unsafe {
|
||||
let interrupt_number = interrupt as isize;
|
||||
let cpu_interrupt_number = which as isize;
|
||||
let intr = &*crate::pac::DPORT::ptr();
|
||||
let intr_map_base = match core {
|
||||
Cpu::ProCpu => intr.pro_mac_intr_map.as_ptr(),
|
||||
#[cfg(feature = "dual_core")]
|
||||
Cpu::AppCpu => intr.app_mac_intr_map.as_ptr(),
|
||||
#[cfg(feature = "single_core")]
|
||||
Cpu::AppCpu => intr.pro_mac_intr_map.as_ptr(),
|
||||
};
|
||||
intr_map_base
|
||||
.offset(interrupt_number)
|
||||
.write_volatile(cpu_interrupt_number as u32);
|
||||
}
|
||||
}
|
||||
|
||||
/// Disable the given peripheral interrupt.
|
||||
pub fn disable(core: Cpu, interrupt: Interrupt) {
|
||||
unsafe {
|
||||
let interrupt_number = interrupt as isize;
|
||||
let intr = &*crate::pac::DPORT::ptr();
|
||||
let intr_map_base = match core {
|
||||
Cpu::ProCpu => intr.pro_mac_intr_map.as_ptr(),
|
||||
#[cfg(feature = "dual_core")]
|
||||
Cpu::AppCpu => intr.app_mac_intr_map.as_ptr(),
|
||||
#[cfg(feature = "single_core")]
|
||||
Cpu::AppCpu => intr.pro_mac_intr_map.as_ptr(),
|
||||
};
|
||||
intr_map_base.offset(interrupt_number).write_volatile(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear the given CPU interrupt
|
||||
pub fn clear(_core: Cpu, which: CpuInterrupt) {
|
||||
unsafe {
|
||||
xtensa_lx::interrupt::clear(1 << which as u32);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get status of peripheral interrupts
|
||||
pub fn get_status(core: Cpu) -> u128 {
|
||||
unsafe {
|
||||
match core {
|
||||
Cpu::ProCpu => {
|
||||
((*crate::pac::DPORT::ptr()).pro_intr_status_0.read().bits() as u128)
|
||||
| ((*crate::pac::DPORT::ptr()).pro_intr_status_1.read().bits() as u128) << 32
|
||||
| ((*crate::pac::DPORT::ptr()).pro_intr_status_2.read().bits() as u128) << 64
|
||||
}
|
||||
Cpu::AppCpu => {
|
||||
((*crate::pac::DPORT::ptr()).app_intr_status_0.read().bits() as u128)
|
||||
| ((*crate::pac::DPORT::ptr()).app_intr_status_1.read().bits() as u128) << 32
|
||||
| ((*crate::pac::DPORT::ptr()).app_intr_status_2.read().bits() as u128) << 64
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[xtensa_lx_rt::interrupt(1)]
|
||||
fn _level1_interrupt() {
|
||||
unsafe { level1_interrupt() };
|
||||
}
|
||||
|
||||
#[xtensa_lx_rt::interrupt(2)]
|
||||
fn _level2_interrupt() {
|
||||
unsafe { level2_interrupt() };
|
||||
}
|
||||
|
||||
#[xtensa_lx_rt::interrupt(3)]
|
||||
fn _level3_interrupt() {
|
||||
unsafe { level3_interrupt() };
|
||||
}
|
||||
|
||||
#[xtensa_lx_rt::interrupt(4)]
|
||||
fn _level4_interrupt() {
|
||||
unsafe { level4_interrupt() };
|
||||
}
|
||||
|
||||
#[xtensa_lx_rt::interrupt(5)]
|
||||
fn _level5_interrupt() {
|
||||
unsafe { level5_interrupt() };
|
||||
}
|
||||
|
||||
#[xtensa_lx_rt::interrupt(6)]
|
||||
fn _level6_interrupt() {
|
||||
unsafe { level6_interrupt() };
|
||||
}
|
||||
|
||||
#[xtensa_lx_rt::interrupt(7)]
|
||||
fn _level7_interrupt() {
|
||||
unsafe { level7_interrupt() };
|
||||
}
|
@ -29,14 +29,26 @@ pub use esp32s3_pac as pac;
|
||||
|
||||
pub mod delay;
|
||||
pub mod gpio;
|
||||
#[cfg_attr(feature = "esp32", path = "interrupt/xtensa.rs")]
|
||||
#[cfg_attr(feature = "esp32c3", path = "interrupt/riscv.rs")]
|
||||
pub mod interrupt;
|
||||
pub mod prelude;
|
||||
pub mod serial;
|
||||
pub mod timer;
|
||||
|
||||
pub use delay::Delay;
|
||||
pub use gpio::*;
|
||||
pub use interrupt::*;
|
||||
use procmacros;
|
||||
pub use procmacros::ram;
|
||||
pub use serial::Serial;
|
||||
pub use timer::Timer;
|
||||
|
||||
use procmacros;
|
||||
pub use procmacros::ram;
|
||||
/// Enumeration of CPU cores
|
||||
/// The actual number of available cores depends on the target.
|
||||
pub enum Cpu {
|
||||
/// The fist core
|
||||
ProCpu = 0,
|
||||
/// The second core
|
||||
AppCpu,
|
||||
}
|
||||
|
16
esp32-hal/.vscode/settings.json
vendored
Normal file
16
esp32-hal/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"rust-analyzer.cargo.features": [],
|
||||
"rust-analyzer.cargo.allFeatures": false,
|
||||
"editor.formatOnSave": true,
|
||||
"rust-analyzer.checkOnSave.allTargets": false,
|
||||
"rust-analyzer.checkOnSave.allFeatures": false,
|
||||
"rust-analyzer.cargo.runBuildScripts": false,
|
||||
"rust-analyzer.checkOnSave.overrideCommand": [
|
||||
"cargo",
|
||||
"check",
|
||||
"--message-format=json",
|
||||
"-Z",
|
||||
"build-std=core",
|
||||
"--examples"
|
||||
]
|
||||
}
|
@ -13,6 +13,13 @@ fn main() {
|
||||
.write_all(include_bytes!("rom.x"))
|
||||
.unwrap();
|
||||
|
||||
File::create(out.join("hal-defaults.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("hal-defaults.x"))
|
||||
.unwrap();
|
||||
|
||||
println!("cargo:rustc-link-arg=-Thal-defaults.x");
|
||||
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
// Only re-run the build script when memory.x is changed,
|
||||
|
95
esp32-hal/examples/gpio_interrupt.rs
Normal file
95
esp32-hal/examples/gpio_interrupt.rs
Normal file
@ -0,0 +1,95 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::{cell::RefCell, fmt::Write};
|
||||
|
||||
use esp32_hal::{
|
||||
gpio::{Gpio0, IO},
|
||||
pac::{self, Peripherals, UART0},
|
||||
prelude::*,
|
||||
Delay,
|
||||
Serial,
|
||||
Timer,
|
||||
};
|
||||
use esp_hal_common::{
|
||||
gpio::{Event, Pin},
|
||||
interrupt,
|
||||
Cpu,
|
||||
Input,
|
||||
PullDown,
|
||||
};
|
||||
use panic_halt as _;
|
||||
use xtensa_lx::mutex::{Mutex, SpinLockMutex};
|
||||
use xtensa_lx_rt::entry;
|
||||
|
||||
static mut SERIAL: SpinLockMutex<RefCell<Option<Serial<UART0>>>> =
|
||||
SpinLockMutex::new(RefCell::new(None));
|
||||
static mut BUTTON: SpinLockMutex<RefCell<Option<Gpio0<Input<PullDown>>>>> =
|
||||
SpinLockMutex::new(RefCell::new(None));
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let peripherals = Peripherals::take().unwrap();
|
||||
|
||||
// Disable the TIMG watchdog timer.
|
||||
let mut timer0 = Timer::new(peripherals.TIMG0);
|
||||
let serial0 = Serial::new(peripherals.UART0).unwrap();
|
||||
|
||||
timer0.disable();
|
||||
|
||||
// Set GPIO15 as an output, and set its state high initially.
|
||||
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
|
||||
let mut led = io.pins.gpio15.into_push_pull_output();
|
||||
let mut button = io.pins.gpio0.into_pull_down_input();
|
||||
button.listen(Event::FallingEdge);
|
||||
|
||||
unsafe {
|
||||
(&SERIAL).lock(|data| (*data).replace(Some(serial0)));
|
||||
(&BUTTON).lock(|data| (*data).replace(Some(button)));
|
||||
}
|
||||
|
||||
interrupt::enable(
|
||||
Cpu::ProCpu,
|
||||
pac::Interrupt::GPIO,
|
||||
interrupt::CpuInterrupt::Interrupt1LevelPriority1,
|
||||
);
|
||||
|
||||
led.set_high().unwrap();
|
||||
|
||||
// Initialize the Delay peripheral, and use it to toggle the LED state in a
|
||||
// loop.
|
||||
let mut delay = Delay::new();
|
||||
|
||||
unsafe {
|
||||
xtensa_lx::interrupt::enable();
|
||||
}
|
||||
|
||||
loop {
|
||||
led.toggle().unwrap();
|
||||
delay.delay_ms(500u32);
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn level1_interrupt() {
|
||||
unsafe {
|
||||
(&SERIAL).lock(|data| {
|
||||
let mut serial = data.borrow_mut();
|
||||
let serial = serial.as_mut().unwrap();
|
||||
writeln!(serial, "Interrupt").ok();
|
||||
});
|
||||
}
|
||||
|
||||
interrupt::clear(
|
||||
Cpu::ProCpu,
|
||||
interrupt::CpuInterrupt::Interrupt1LevelPriority1,
|
||||
);
|
||||
|
||||
unsafe {
|
||||
(&BUTTON).lock(|data| {
|
||||
let mut button = data.borrow_mut();
|
||||
let button = button.as_mut().unwrap();
|
||||
button.clear_interrupt();
|
||||
});
|
||||
}
|
||||
}
|
7
esp32-hal/hal-defaults.x
Normal file
7
esp32-hal/hal-defaults.x
Normal file
@ -0,0 +1,7 @@
|
||||
PROVIDE(level1_interrupt = DefaultHandler);
|
||||
PROVIDE(level2_interrupt = DefaultHandler);
|
||||
PROVIDE(level3_interrupt = DefaultHandler);
|
||||
PROVIDE(level4_interrupt = DefaultHandler);
|
||||
PROVIDE(level5_interrupt = DefaultHandler);
|
||||
PROVIDE(level6_interrupt = DefaultHandler);
|
||||
PROVIDE(level7_interrupt = DefaultHandler);
|
@ -7,7 +7,7 @@ pub use self::gpio::IO;
|
||||
|
||||
pub mod gpio;
|
||||
|
||||
pub use esp_hal_common::ram;
|
||||
pub use esp_hal_common::{interrupt, ram, Cpu};
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn DefaultHandler(_level: u32, _interrupt: pac::Interrupt) {}
|
||||
@ -55,3 +55,10 @@ pub unsafe extern "C" fn ESP32Reset() -> ! {
|
||||
pub extern "Rust" fn __init_data() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn gpio_intr_enable(int_enable: bool, nmi_enable: bool) -> u8 {
|
||||
int_enable as u8
|
||||
| ((nmi_enable as u8) << 1)
|
||||
| (int_enable as u8) << 2
|
||||
| ((nmi_enable as u8) << 3)
|
||||
}
|
||||
|
@ -4,3 +4,6 @@ rustflags = [
|
||||
|
||||
[build]
|
||||
target = "riscv32imc-unknown-none-elf"
|
||||
|
||||
[unstable]
|
||||
build-std = [ "core" ]
|
||||
|
16
esp32c3-hal/.vscode/settings.json
vendored
Normal file
16
esp32c3-hal/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"rust-analyzer.cargo.features": [],
|
||||
"rust-analyzer.cargo.allFeatures": false,
|
||||
"editor.formatOnSave": true,
|
||||
"rust-analyzer.checkOnSave.allTargets": false,
|
||||
"rust-analyzer.checkOnSave.allFeatures": false,
|
||||
"rust-analyzer.cargo.runBuildScripts": false,
|
||||
"rust-analyzer.checkOnSave.overrideCommand": [
|
||||
"cargo",
|
||||
"check",
|
||||
"--message-format=json",
|
||||
"-Z",
|
||||
"build-std=core",
|
||||
"--examples"
|
||||
]
|
||||
}
|
@ -43,6 +43,9 @@ git = "https://github.com/MabezDev/riscv-rt"
|
||||
rev = "6b55e4aa3895924e31bcd151f2f0ab840836fa07"
|
||||
optional = true
|
||||
|
||||
[build-dependencies]
|
||||
riscv-target = "0.1.2"
|
||||
|
||||
[dev-dependencies]
|
||||
panic-halt = "0.2"
|
||||
|
||||
|
101
esp32c3-hal/asm.S
Normal file
101
esp32c3-hal/asm.S
Normal file
@ -0,0 +1,101 @@
|
||||
#define STORE sw
|
||||
#define LOAD lw
|
||||
#define LOG_REGBYTES 2
|
||||
#define REGBYTES (1 << LOG_REGBYTES)
|
||||
|
||||
/*
|
||||
Trap entry point (_start_trap)
|
||||
Saves registers and calls _start_trap_rust_hal,
|
||||
restores registers and then returns.
|
||||
*/
|
||||
.section .trap, "ax"
|
||||
.global _start_trap_hal
|
||||
.option norelax
|
||||
.align 6
|
||||
|
||||
_start_trap_hal:
|
||||
addi sp, sp, -32*REGBYTES
|
||||
|
||||
STORE ra, 0*REGBYTES(sp)
|
||||
STORE t0, 1*REGBYTES(sp)
|
||||
STORE t1, 2*REGBYTES(sp)
|
||||
STORE t2, 3*REGBYTES(sp)
|
||||
STORE t3, 4*REGBYTES(sp)
|
||||
STORE t4, 5*REGBYTES(sp)
|
||||
STORE t5, 6*REGBYTES(sp)
|
||||
STORE t6, 7*REGBYTES(sp)
|
||||
STORE a0, 8*REGBYTES(sp)
|
||||
STORE a1, 9*REGBYTES(sp)
|
||||
STORE a2, 10*REGBYTES(sp)
|
||||
STORE a3, 11*REGBYTES(sp)
|
||||
STORE a4, 12*REGBYTES(sp)
|
||||
STORE a5, 13*REGBYTES(sp)
|
||||
STORE a6, 14*REGBYTES(sp)
|
||||
STORE a7, 15*REGBYTES(sp)
|
||||
STORE s0, 16*REGBYTES(sp)
|
||||
STORE s1, 17*REGBYTES(sp)
|
||||
STORE s2, 18*REGBYTES(sp)
|
||||
STORE s3, 19*REGBYTES(sp)
|
||||
STORE s4, 20*REGBYTES(sp)
|
||||
STORE s5, 21*REGBYTES(sp)
|
||||
STORE s6, 22*REGBYTES(sp)
|
||||
STORE s7, 23*REGBYTES(sp)
|
||||
STORE s8, 24*REGBYTES(sp)
|
||||
STORE s9, 25*REGBYTES(sp)
|
||||
STORE s10, 26*REGBYTES(sp)
|
||||
STORE s11, 27*REGBYTES(sp)
|
||||
STORE gp, 28*REGBYTES(sp)
|
||||
STORE tp, 29*REGBYTES(sp)
|
||||
|
||||
addi s0, sp, 32*REGBYTES
|
||||
STORE s0, 30*REGBYTES(sp)
|
||||
|
||||
add a0, sp, zero
|
||||
jal ra, _start_trap_rust_hal
|
||||
|
||||
LOAD ra, 0*REGBYTES(sp)
|
||||
LOAD t0, 1*REGBYTES(sp)
|
||||
LOAD t1, 2*REGBYTES(sp)
|
||||
LOAD t2, 3*REGBYTES(sp)
|
||||
LOAD t3, 4*REGBYTES(sp)
|
||||
LOAD t4, 5*REGBYTES(sp)
|
||||
LOAD t5, 6*REGBYTES(sp)
|
||||
LOAD t6, 7*REGBYTES(sp)
|
||||
LOAD a0, 8*REGBYTES(sp)
|
||||
LOAD a1, 9*REGBYTES(sp)
|
||||
LOAD a2, 10*REGBYTES(sp)
|
||||
LOAD a3, 11*REGBYTES(sp)
|
||||
LOAD a4, 12*REGBYTES(sp)
|
||||
LOAD a5, 13*REGBYTES(sp)
|
||||
LOAD a6, 14*REGBYTES(sp)
|
||||
LOAD a7, 15*REGBYTES(sp)
|
||||
LOAD s0, 16*REGBYTES(sp)
|
||||
LOAD s1, 17*REGBYTES(sp)
|
||||
LOAD s2, 18*REGBYTES(sp)
|
||||
LOAD s3, 19*REGBYTES(sp)
|
||||
LOAD s4, 20*REGBYTES(sp)
|
||||
LOAD s5, 21*REGBYTES(sp)
|
||||
LOAD s6, 22*REGBYTES(sp)
|
||||
LOAD s7, 23*REGBYTES(sp)
|
||||
LOAD s8, 24*REGBYTES(sp)
|
||||
LOAD s9, 25*REGBYTES(sp)
|
||||
LOAD s10, 26*REGBYTES(sp)
|
||||
LOAD s11, 27*REGBYTES(sp)
|
||||
LOAD gp, 28*REGBYTES(sp)
|
||||
LOAD tp, 29*REGBYTES(sp)
|
||||
LOAD sp, 30*REGBYTES(sp)
|
||||
|
||||
# SP was restored from the original SP
|
||||
mret
|
||||
|
||||
.section .trap, "ax"
|
||||
.balign 0x100
|
||||
.global _vector_table
|
||||
.type _vector_table, @function
|
||||
|
||||
_vector_table:
|
||||
.option push
|
||||
.option norvc
|
||||
.rept 31
|
||||
j _start_trap_hal
|
||||
.endr
|
3
esp32c3-hal/asm.bat
Normal file
3
esp32c3-hal/asm.bat
Normal file
@ -0,0 +1,3 @@
|
||||
riscv32-esp-elf-gcc -ggdb3 -c -mabi=ilp32 -march=rv32i asm.S -o bin/esp32c3asm.o
|
||||
riscv32-esp-elf-ar crs bin/asm_riscv32i-unknown-none-elf.a bin/esp32c3asm.o
|
||||
del bin\esp32c3asm.o
|
BIN
esp32c3-hal/bin/asm_riscv32i-unknown-none-elf.a
Normal file
BIN
esp32c3-hal/bin/asm_riscv32i-unknown-none-elf.a
Normal file
Binary file not shown.
@ -1,4 +1,11 @@
|
||||
use std::{env, fs::File, io::Write, path::PathBuf};
|
||||
use std::{
|
||||
env,
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use riscv_target::Target;
|
||||
|
||||
#[cfg(not(feature = "normalboot"))]
|
||||
fn main() {
|
||||
@ -26,6 +33,9 @@ fn main() {
|
||||
// instead of when any part of the source code changes.
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
println!("cargo:rustc-link-arg=-Tesp32c3-link.x");
|
||||
|
||||
add_defaults();
|
||||
prepare_trap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "normalboot")]
|
||||
@ -49,4 +59,40 @@ fn main() {
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
println!("cargo:rustc-link-arg=-Tmemory.x");
|
||||
println!("cargo:rustc-link-arg=-Tbl-riscv-link.x");
|
||||
|
||||
add_defaults();
|
||||
prepare_trap();
|
||||
}
|
||||
|
||||
fn add_defaults() {
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("hal-defaults.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("hal-defaults.x"))
|
||||
.unwrap();
|
||||
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
println!("cargo:rustc-link-arg=-Thal-defaults.x");
|
||||
}
|
||||
|
||||
fn prepare_trap() {
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
let name = env::var("CARGO_PKG_NAME").unwrap();
|
||||
let target = env::var("TARGET").unwrap();
|
||||
|
||||
if target.starts_with("riscv") {
|
||||
let mut target = Target::from_target_str(&target);
|
||||
target.retain_extensions("if");
|
||||
|
||||
let target = target.to_string();
|
||||
|
||||
fs::copy(
|
||||
format!("bin/asm_{}.a", target),
|
||||
out.join(format!("lib{}.a", name)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
println!("cargo:rustc-link-lib=static={}", name);
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
}
|
||||
}
|
||||
|
99
esp32c3-hal/examples/gpio_interrupt.rs
Normal file
99
esp32c3-hal/examples/gpio_interrupt.rs
Normal file
@ -0,0 +1,99 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::{cell::RefCell, fmt::Write};
|
||||
|
||||
use bare_metal::Mutex;
|
||||
use esp32c3_hal::{
|
||||
gpio::{Gpio9, IO},
|
||||
pac::{self, Peripherals, UART0},
|
||||
prelude::*,
|
||||
Delay,
|
||||
RtcCntl,
|
||||
Serial,
|
||||
Timer,
|
||||
};
|
||||
use esp_hal_common::{
|
||||
interrupt::{self},
|
||||
Cpu,
|
||||
Event,
|
||||
Input,
|
||||
Pin,
|
||||
PullDown,
|
||||
};
|
||||
use panic_halt as _;
|
||||
use riscv_rt::entry;
|
||||
|
||||
static mut SERIAL: Mutex<RefCell<Option<Serial<UART0>>>> = Mutex::new(RefCell::new(None));
|
||||
static mut BUTTON: Mutex<RefCell<Option<Gpio9<Input<PullDown>>>>> = Mutex::new(RefCell::new(None));
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let peripherals = Peripherals::take().unwrap();
|
||||
|
||||
// Disable the watchdog timers. For the ESP32-C3, this includes the Super WDT,
|
||||
// the RTC WDT, and the TIMG WDTs.
|
||||
let rtc_cntl = RtcCntl::new(peripherals.RTC_CNTL);
|
||||
let mut timer0 = Timer::new(peripherals.TIMG0);
|
||||
let mut timer1 = Timer::new(peripherals.TIMG1);
|
||||
let serial0 = Serial::new(peripherals.UART0).unwrap();
|
||||
|
||||
rtc_cntl.set_super_wdt_enable(false);
|
||||
rtc_cntl.set_wdt_enable(false);
|
||||
timer0.disable();
|
||||
timer1.disable();
|
||||
|
||||
// Set GPIO5 as an output
|
||||
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
|
||||
let mut led = io.pins.gpio5.into_push_pull_output();
|
||||
|
||||
// Set GPIO9 as an input
|
||||
let mut button = io.pins.gpio9.into_pull_down_input();
|
||||
button.listen(Event::FallingEdge);
|
||||
|
||||
riscv::interrupt::free(|_cs| unsafe {
|
||||
SERIAL.get_mut().replace(Some(serial0));
|
||||
BUTTON.get_mut().replace(Some(button));
|
||||
});
|
||||
|
||||
interrupt::enable(
|
||||
Cpu::ProCpu,
|
||||
pac::Interrupt::GPIO,
|
||||
interrupt::CpuInterrupt::Interrupt3,
|
||||
);
|
||||
interrupt::set_kind(
|
||||
Cpu::ProCpu,
|
||||
interrupt::CpuInterrupt::Interrupt3,
|
||||
interrupt::InterruptKind::Level,
|
||||
);
|
||||
interrupt::set_priority(
|
||||
Cpu::ProCpu,
|
||||
interrupt::CpuInterrupt::Interrupt3,
|
||||
interrupt::Priority::Priority1,
|
||||
);
|
||||
|
||||
unsafe {
|
||||
riscv::interrupt::enable();
|
||||
}
|
||||
|
||||
let mut delay = Delay::new(peripherals.SYSTIMER);
|
||||
loop {
|
||||
led.toggle().unwrap();
|
||||
delay.delay_ms(500u32);
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn interrupt3() {
|
||||
riscv::interrupt::free(|cs| unsafe {
|
||||
let mut serial = SERIAL.borrow(*cs).borrow_mut();
|
||||
let serial = serial.as_mut().unwrap();
|
||||
let mut button = BUTTON.borrow(*cs).borrow_mut();
|
||||
let button = button.as_mut().unwrap();
|
||||
|
||||
writeln!(serial, "Interrupt").ok();
|
||||
|
||||
interrupt::clear(Cpu::ProCpu, interrupt::CpuInterrupt::Interrupt3);
|
||||
button.clear_interrupt();
|
||||
});
|
||||
}
|
31
esp32c3-hal/hal-defaults.x
Normal file
31
esp32c3-hal/hal-defaults.x
Normal file
@ -0,0 +1,31 @@
|
||||
PROVIDE(interrupt1 = DefaultHandler);
|
||||
PROVIDE(interrupt2 = DefaultHandler);
|
||||
PROVIDE(interrupt3 = DefaultHandler);
|
||||
PROVIDE(interrupt4 = DefaultHandler);
|
||||
PROVIDE(interrupt5 = DefaultHandler);
|
||||
PROVIDE(interrupt6 = DefaultHandler);
|
||||
PROVIDE(interrupt7 = DefaultHandler);
|
||||
PROVIDE(interrupt8 = DefaultHandler);
|
||||
PROVIDE(interrupt9 = DefaultHandler);
|
||||
PROVIDE(interrupt10 = DefaultHandler);
|
||||
PROVIDE(interrupt11 = DefaultHandler);
|
||||
PROVIDE(interrupt12 = DefaultHandler);
|
||||
PROVIDE(interrupt13 = DefaultHandler);
|
||||
PROVIDE(interrupt14 = DefaultHandler);
|
||||
PROVIDE(interrupt15 = DefaultHandler);
|
||||
PROVIDE(interrupt16 = DefaultHandler);
|
||||
PROVIDE(interrupt17 = DefaultHandler);
|
||||
PROVIDE(interrupt18 = DefaultHandler);
|
||||
PROVIDE(interrupt19 = DefaultHandler);
|
||||
PROVIDE(interrupt20 = DefaultHandler);
|
||||
PROVIDE(interrupt21 = DefaultHandler);
|
||||
PROVIDE(interrupt22 = DefaultHandler);
|
||||
PROVIDE(interrupt23 = DefaultHandler);
|
||||
PROVIDE(interrupt24 = DefaultHandler);
|
||||
PROVIDE(interrupt25 = DefaultHandler);
|
||||
PROVIDE(interrupt26 = DefaultHandler);
|
||||
PROVIDE(interrupt27 = DefaultHandler);
|
||||
PROVIDE(interrupt28 = DefaultHandler);
|
||||
PROVIDE(interrupt29 = DefaultHandler);
|
||||
PROVIDE(interrupt30 = DefaultHandler);
|
||||
PROVIDE(interrupt31 = DefaultHandler);
|
@ -8,7 +8,7 @@ use riscv_rt::pre_init;
|
||||
pub mod gpio;
|
||||
pub mod rtc_cntl;
|
||||
|
||||
pub use esp_hal_common::ram;
|
||||
pub use esp_hal_common::{interrupt, ram, Cpu};
|
||||
|
||||
pub use self::{gpio::IO, rtc_cntl::RtcCntl};
|
||||
|
||||
@ -40,6 +40,7 @@ extern "C" {
|
||||
#[cfg(not(feature = "normalboot"))]
|
||||
#[pre_init]
|
||||
#[cfg(not(feature = "normalboot"))]
|
||||
#[doc(hidden)]
|
||||
unsafe fn init() {
|
||||
r0::init_data(&mut _srwtext, &mut _erwtext, &_irwtext);
|
||||
|
||||
@ -54,6 +55,7 @@ unsafe fn init() {
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
#[export_name = "_mp_hook"]
|
||||
#[doc(hidden)]
|
||||
pub fn mp_hook() -> bool {
|
||||
unsafe {
|
||||
r0::zero_bss(&mut _rtc_fast_bss_start, &mut _rtc_fast_bss_end);
|
||||
@ -69,3 +71,7 @@ pub fn mp_hook() -> bool {
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn gpio_intr_enable(int_enable: bool, nmi_enable: bool) -> u8 {
|
||||
int_enable as u8 | ((nmi_enable as u8) << 1)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user