From cbbbe97b88d8d6c7f351844bb45ff85f2669e91d Mon Sep 17 00:00:00 2001 From: bjoernQ Date: Tue, 25 Oct 2022 09:36:39 +0200 Subject: [PATCH] Support USB-DEVICE on ESP32-S3 and ESP32-S2 --- esp-hal-common/Cargo.toml | 34 ++++---- esp-hal-common/src/gpio.rs | 6 +- esp-hal-common/src/gpio/esp32.rs | 3 + esp-hal-common/src/gpio/esp32c2.rs | 3 + esp-hal-common/src/gpio/esp32c3.rs | 3 + esp-hal-common/src/gpio/esp32s2.rs | 3 + esp-hal-common/src/gpio/esp32s3.rs | 3 + esp-hal-common/src/lib.rs | 3 + esp-hal-common/src/otg_fs.rs | 123 +++++++++++++++++++++++++++++ esp-hal-common/src/system.rs | 7 ++ esp32s2-hal/Cargo.toml | 2 + esp32s2-hal/examples/usb_serial.rs | 87 ++++++++++++++++++++ esp32s2-hal/src/gpio.rs | 5 ++ esp32s2-hal/src/lib.rs | 1 + esp32s3-hal/Cargo.toml | 2 + esp32s3-hal/examples/usb_serial.rs | 87 ++++++++++++++++++++ esp32s3-hal/src/gpio.rs | 5 ++ esp32s3-hal/src/lib.rs | 1 + 18 files changed, 359 insertions(+), 19 deletions(-) create mode 100644 esp-hal-common/src/otg_fs.rs create mode 100644 esp32s2-hal/examples/usb_serial.rs create mode 100644 esp32s3-hal/examples/usb_serial.rs diff --git a/esp-hal-common/Cargo.toml b/esp-hal-common/Cargo.toml index 76a6fdcaeb1..dd9a9c6a9e5 100644 --- a/esp-hal-common/Cargo.toml +++ b/esp-hal-common/Cargo.toml @@ -12,18 +12,20 @@ repository = "https://github.com/esp-rs/esp-hal" license = "MIT OR Apache-2.0" [dependencies] -cfg-if = "1.0.0" -critical-section = "1.1.0" -embedded-hal = { version = "0.2.7", features = ["unproven"] } -embedded-hal-1 = { version = "=1.0.0-alpha.9", optional = true, package = "embedded-hal" } -embedded-hal-nb = { version = "=1.0.0-alpha.1", optional = true } -fugit = "0.3.6" -lock_api = { version = "0.4.8", optional = true } -nb = "1.0.0" -paste = "=1.0.8" -procmacros = { version = "0.1.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } -void = { version = "1.0.2", default-features = false } -embedded-dma = "0.2.0" +cfg-if = "1.0.0" +critical-section = "1.1.1" +embedded-hal = { version = "0.2.7", features = ["unproven"] } +embedded-hal-1 = { version = "=1.0.0-alpha.9", optional = true, package = "embedded-hal" } +embedded-hal-nb = { version = "=1.0.0-alpha.1", optional = true } +fugit = "0.3.6" +lock_api = { version = "0.4.8", optional = true } +nb = "1.0.0" +paste = "=1.0.8" +procmacros = { version = "0.1.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } +void = { version = "1.0.2", default-features = false } +embedded-dma = "0.2.0" +esp-synopsys-usb-otg = { version = "0.3.1", optional = true, features = ["fs", "esp32sx"] } +usb-device = { version = "0.2.3", optional = true } # RISC-V riscv = { version = "0.9.0", optional = true } @@ -46,15 +48,15 @@ ufmt-write = { version = "0.1.0", optional = true } esp32 = { version = "0.14.0", features = ["critical-section"], optional = true } esp32c2 = { version = "0.3.0", features = ["critical-section"], optional = true } esp32c3 = { version = "0.6.0", features = ["critical-section"], optional = true } -esp32s2 = { version = "0.4.0", features = ["critical-section"], optional = true } -esp32s3 = { version = "0.4.0", features = ["critical-section"], optional = true } +esp32s2 = { version = "0.5.0", features = ["critical-section"], optional = true } +esp32s3 = { version = "0.5.0", features = ["critical-section"], optional = true } [features] esp32 = ["esp32/rt" , "procmacros/xtensa", "xtensa-lx-rt/esp32", "xtensa-lx/esp32", "critical-section/restore-state-u32", "lock_api"] esp32c2 = ["esp32c2/rt", "procmacros/riscv" , "riscv", "riscv-atomic-emulation-trap", "critical-section/restore-state-u8"] esp32c3 = ["esp32c3/rt", "procmacros/riscv" , "riscv", "riscv-atomic-emulation-trap", "critical-section/restore-state-u8"] -esp32s2 = ["esp32s2/rt", "procmacros/xtensa", "xtensa-lx-rt/esp32s2", "xtensa-lx/esp32s2", "critical-section/restore-state-u32"] -esp32s3 = ["esp32s3/rt", "procmacros/xtensa", "xtensa-lx-rt/esp32s3", "xtensa-lx/esp32s3", "critical-section/restore-state-u32", "lock_api"] +esp32s2 = ["esp32s2/rt", "procmacros/xtensa", "xtensa-lx-rt/esp32s2", "xtensa-lx/esp32s2", "critical-section/restore-state-u32", "esp-synopsys-usb-otg", "usb-device"] +esp32s3 = ["esp32s3/rt", "procmacros/xtensa", "xtensa-lx-rt/esp32s3", "xtensa-lx/esp32s3", "critical-section/restore-state-u32", "lock_api", "esp-synopsys-usb-otg", "usb-device"] # Implement the `embedded-hal==1.0.0-alpha.x` traits eh1 = ["embedded-hal-1", "embedded-hal-nb"] diff --git a/esp-hal-common/src/gpio.rs b/esp-hal-common/src/gpio.rs index 430e8c34851..3dea29afe26 100644 --- a/esp-hal-common/src/gpio.rs +++ b/esp-hal-common/src/gpio.rs @@ -418,7 +418,7 @@ pub fn connect_low_to_peripheral(signal: InputSignal) { .in_inv_sel() .bit(false) .in_sel() - .bits(0x1f) + .bits(ZERO_INPUT) }); } @@ -430,7 +430,7 @@ pub fn connect_high_to_peripheral(signal: InputSignal) { .in_inv_sel() .bit(false) .in_sel() - .bits(0x1e) + .bits(ONE_INPUT) }); } @@ -1311,4 +1311,4 @@ pub use impl_interrupt_status_register_access; pub use impl_output; pub use impl_output_wrap; -use self::types::{InputSignal, OutputSignal}; +use self::types::{InputSignal, OutputSignal, ONE_INPUT, ZERO_INPUT}; diff --git a/esp-hal-common/src/gpio/esp32.rs b/esp-hal-common/src/gpio/esp32.rs index 118396ffbd9..00c84cc2b4d 100644 --- a/esp-hal-common/src/gpio/esp32.rs +++ b/esp-hal-common/src/gpio/esp32.rs @@ -2,6 +2,9 @@ pub type OutputSignalType = u16; pub const OUTPUT_SIGNAL_MAX: u16 = 548; pub const INPUT_SIGNAL_MAX: u16 = 539; +pub const ONE_INPUT: u8 = 0x38; +pub const ZERO_INPUT: u8 = 0x30; + /// Peripheral input signals for the GPIO mux #[allow(non_camel_case_types)] #[derive(PartialEq, Copy, Clone)] diff --git a/esp-hal-common/src/gpio/esp32c2.rs b/esp-hal-common/src/gpio/esp32c2.rs index 04ec3e85911..88b262754b8 100644 --- a/esp-hal-common/src/gpio/esp32c2.rs +++ b/esp-hal-common/src/gpio/esp32c2.rs @@ -2,6 +2,9 @@ pub type OutputSignalType = u8; pub const OUTPUT_SIGNAL_MAX: u8 = 128; pub const INPUT_SIGNAL_MAX: u8 = 100; +pub const ONE_INPUT: u8 = 0x1e; +pub const ZERO_INPUT: u8 = 0x1f; + #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq)] pub enum InputSignal { diff --git a/esp-hal-common/src/gpio/esp32c3.rs b/esp-hal-common/src/gpio/esp32c3.rs index 9c6da5d06a3..3dab1965f49 100644 --- a/esp-hal-common/src/gpio/esp32c3.rs +++ b/esp-hal-common/src/gpio/esp32c3.rs @@ -2,6 +2,9 @@ pub type OutputSignalType = u8; pub const OUTPUT_SIGNAL_MAX: u8 = 128; pub const INPUT_SIGNAL_MAX: u8 = 100; +pub const ONE_INPUT: u8 = 0x1e; +pub const ZERO_INPUT: u8 = 0x1f; + #[allow(non_camel_case_types)] #[derive(Clone, Copy, PartialEq)] pub enum InputSignal { diff --git a/esp-hal-common/src/gpio/esp32s2.rs b/esp-hal-common/src/gpio/esp32s2.rs index e4e775c49d3..3465f17e572 100644 --- a/esp-hal-common/src/gpio/esp32s2.rs +++ b/esp-hal-common/src/gpio/esp32s2.rs @@ -2,6 +2,9 @@ pub type OutputSignalType = u16; pub const OUTPUT_SIGNAL_MAX: u16 = 256; pub const INPUT_SIGNAL_MAX: u16 = 204; +pub const ONE_INPUT: u8 = 0x38; +pub const ZERO_INPUT: u8 = 0x3c; + /// Peripheral input signals for the GPIO mux #[allow(non_camel_case_types)] #[derive(PartialEq, Copy, Clone)] diff --git a/esp-hal-common/src/gpio/esp32s3.rs b/esp-hal-common/src/gpio/esp32s3.rs index 151d549c9c8..7e317efc5e6 100644 --- a/esp-hal-common/src/gpio/esp32s3.rs +++ b/esp-hal-common/src/gpio/esp32s3.rs @@ -2,6 +2,9 @@ pub type OutputSignalType = u16; pub const OUTPUT_SIGNAL_MAX: u16 = 256; pub const INPUT_SIGNAL_MAX: u16 = 189; +pub const ONE_INPUT: u8 = 0x38; +pub const ZERO_INPUT: u8 = 0x3c; + /// Peripheral input signals for the GPIO mux #[allow(non_camel_case_types)] #[derive(PartialEq, Copy, Clone)] diff --git a/esp-hal-common/src/lib.rs b/esp-hal-common/src/lib.rs index ff4380a3767..5c55a11afd9 100644 --- a/esp-hal-common/src/lib.rs +++ b/esp-hal-common/src/lib.rs @@ -91,6 +91,9 @@ pub mod efuse; #[cfg_attr(xtensa, path = "interrupt/xtensa.rs")] pub mod interrupt; +#[cfg(any(esp32s3, esp32s2))] +pub mod otg_fs; + /// Enumeration of CPU cores /// The actual number of available cores depends on the target. pub enum Cpu { diff --git a/esp-hal-common/src/otg_fs.rs b/esp-hal-common/src/otg_fs.rs new file mode 100644 index 00000000000..902bbd542bd --- /dev/null +++ b/esp-hal-common/src/otg_fs.rs @@ -0,0 +1,123 @@ +//! USB OTG full-speed peripheral + +pub use esp_synopsys_usb_otg::UsbBus; +use esp_synopsys_usb_otg::UsbPeripheral; + +use crate::{ + pac, + system::{Peripheral, PeripheralClockControl}, + types::InputSignal, +}; + +#[doc(hidden)] +pub trait UsbSel {} + +#[doc(hidden)] +pub trait UsbDp {} + +#[doc(hidden)] +pub trait UsbDm {} + +pub struct USB +where + S: UsbSel + Send + Sync, + P: UsbDp + Send + Sync, + M: UsbDm + Send + Sync, +{ + _usb0: pac::USB0, + _usb_sel: S, + _usb_dp: P, + _usb_dm: M, +} + +impl USB +where + S: UsbSel + Send + Sync, + P: UsbDp + Send + Sync, + M: UsbDm + Send + Sync, +{ + pub fn new( + usb0: pac::USB0, + usb_sel: S, + usb_dp: P, + usb_dm: M, + peripheral_clock_control: &mut PeripheralClockControl, + ) -> Self { + peripheral_clock_control.enable(Peripheral::Usb); + Self { + _usb0: usb0, + _usb_sel: usb_sel, + _usb_dp: usb_dp, + _usb_dm: usb_dm, + } + } +} + +unsafe impl Sync for USB +where + S: UsbSel + Send + Sync, + P: UsbDp + Send + Sync, + M: UsbDm + Send + Sync, +{ +} + +unsafe impl UsbPeripheral for USB +where + S: UsbSel + Send + Sync, + P: UsbDp + Send + Sync, + M: UsbDm + Send + Sync, +{ + const REGISTERS: *const () = pac::USB0::ptr() as *const (); + + const HIGH_SPEED: bool = false; + const FIFO_DEPTH_WORDS: usize = 256; + const ENDPOINT_COUNT: usize = 5; + + fn enable() { + unsafe { + let usb_wrap = &*pac::USB_WRAP::PTR; + usb_wrap.otg_conf.modify(|_, w| { + w.usb_pad_enable() + .set_bit() + .phy_sel() + .clear_bit() + .clk_en() + .set_bit() + .ahb_clk_force_on() + .set_bit() + .phy_clk_force_on() + .set_bit() + }); + + #[cfg(esp32s3)] + { + let rtc = &*pac::RTC_CNTL::PTR; + rtc.usb_conf + .modify(|_, w| w.sw_hw_usb_phy_sel().set_bit().sw_usb_phy_sel().set_bit()); + } + + crate::gpio::connect_high_to_peripheral(InputSignal::USB_OTG_IDDIG); // connected connector is mini-B side + crate::gpio::connect_high_to_peripheral(InputSignal::USB_SRP_BVALID); // HIGH to force USB device mode + crate::gpio::connect_high_to_peripheral(InputSignal::USB_OTG_VBUSVALID); // receiving a valid Vbus from device + crate::gpio::connect_low_to_peripheral(InputSignal::USB_OTG_AVALID); + + usb_wrap.otg_conf.modify(|_, w| { + w.pad_pull_override() + .set_bit() + .dp_pullup() + .set_bit() + .dp_pulldown() + .clear_bit() + .dm_pullup() + .clear_bit() + .dm_pulldown() + .clear_bit() + }); + } + } + + fn ahb_frequency_hz(&self) -> u32 { + // unused + 80_000_000 + } +} diff --git a/esp-hal-common/src/system.rs b/esp-hal-common/src/system.rs index cc78e49781b..397578eea67 100644 --- a/esp-hal-common/src/system.rs +++ b/esp-hal-common/src/system.rs @@ -30,6 +30,8 @@ pub enum Peripheral { Gdma, #[cfg(any(esp32, esp32s2))] Dma, + #[cfg(any(esp32s2, esp32s3))] + Usb, } /// Controls the enablement of peripheral clocks. @@ -106,6 +108,11 @@ impl PeripheralClockControl { perip_clk_en0.modify(|_, w| w.spi3_dma_clk_en().set_bit()); perip_rst_en0.modify(|_, w| w.spi3_dma_rst().clear_bit()); } + #[cfg(any(esp32s2, esp32s3))] + Peripheral::Usb => { + perip_clk_en0.modify(|_, w| w.usb_clk_en().set_bit()); + perip_rst_en0.modify(|_, w| w.usb_rst().clear_bit()); + } } } } diff --git a/esp32s2-hal/Cargo.toml b/esp32s2-hal/Cargo.toml index f04b37ab136..bb435707c00 100644 --- a/esp32s2-hal/Cargo.toml +++ b/esp32s2-hal/Cargo.toml @@ -39,6 +39,8 @@ esp-backtrace = { version = "0.2.0", features = ["esp32s2", "panic-handler", esp-println = { version = "0.3.0", features = ["esp32s2"] } smart-leds = "0.3.0" ssd1306 = "0.7.1" +usb-device = { version = "0.2.3" } +usbd-serial = "0.1.1" [features] default = ["rt", "vectored"] diff --git a/esp32s2-hal/examples/usb_serial.rs b/esp32s2-hal/examples/usb_serial.rs new file mode 100644 index 00000000000..70dd0c19d50 --- /dev/null +++ b/esp32s2-hal/examples/usb_serial.rs @@ -0,0 +1,87 @@ +//! CDC-ACM serial port example using polling in a busy loop. +//! +//! This example should be built in release mode. + +#![no_std] +#![no_main] + +use esp32s2_hal::{ + clock::{ClockControl, CpuClock}, + otg_fs::{UsbBus, USB}, + pac::Peripherals, + prelude::*, + timer::TimerGroup, + Rtc, + IO, +}; +use esp_backtrace as _; +use usb_device::prelude::{UsbDeviceBuilder, UsbVidPid}; +use xtensa_lx_rt::entry; + +static mut EP_MEMORY: [u32; 1024] = [0; 1024]; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take().unwrap(); + let mut system = peripherals.SYSTEM.split(); + let clocks = ClockControl::configure(system.clock_control, CpuClock::Clock240MHz).freeze(); + + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt = timer_group0.wdt; + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + + // Disable MWDT and RWDT (Watchdog) flash boot protection + wdt.disable(); + rtc.rwdt.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + + let usb = USB::new( + peripherals.USB0, + io.pins.gpio18, + io.pins.gpio19, + io.pins.gpio20, + &mut system.peripheral_clock_control, + ); + + let usb_bus = UsbBus::new(usb, unsafe { &mut EP_MEMORY }); + + let mut serial = usbd_serial::SerialPort::new(&usb_bus); + + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) + .manufacturer("esp-hal") + .product("esp-hal") + .serial_number("12345678") + .device_class(usbd_serial::USB_CLASS_CDC) + .build(); + + loop { + if !usb_dev.poll(&mut [&mut serial]) { + continue; + } + + let mut buf = [0u8; 64]; + + match serial.read(&mut buf) { + Ok(count) if count > 0 => { + // Echo back in upper case + for c in buf[0..count].iter_mut() { + if 0x61 <= *c && *c <= 0x7a { + *c &= !0x20; + } + } + + let mut write_offset = 0; + while write_offset < count { + match serial.write(&buf[write_offset..count]) { + Ok(len) if len > 0 => { + write_offset += len; + } + _ => {} + } + } + } + _ => {} + } + } +} diff --git a/esp32s2-hal/src/gpio.rs b/esp32s2-hal/src/gpio.rs index b6d2de35c0c..e94d80c1e15 100644 --- a/esp32s2-hal/src/gpio.rs +++ b/esp32s2-hal/src/gpio.rs @@ -83,3 +83,8 @@ analog! { Gpio20: (20, rtc_pad20, rtc_pad20_hold, mux_sel, fun_sel, fun_ie, slp_ie, slp_sel, rue, rde, drv, slp_oe), Gpio21: (21, rtc_pad21, rtc_pad21_hold, mux_sel, fun_sel, fun_ie, slp_ie, slp_sel, rue, rde, drv, slp_oe), } + +// implement marker traits on USB pins +impl esp_hal_common::otg_fs::UsbSel for Gpio18 {} +impl esp_hal_common::otg_fs::UsbDp for Gpio19 {} +impl esp_hal_common::otg_fs::UsbDm for Gpio20 {} diff --git a/esp32s2-hal/src/lib.rs b/esp32s2-hal/src/lib.rs index 592f646fc67..ac0d95602cc 100644 --- a/esp32s2-hal/src/lib.rs +++ b/esp32s2-hal/src/lib.rs @@ -11,6 +11,7 @@ pub use esp_hal_common::{ interrupt, ledc, macros, + otg_fs, pac, prelude, pulse_control, diff --git a/esp32s3-hal/Cargo.toml b/esp32s3-hal/Cargo.toml index a0b100ee1c8..5d5288996fe 100644 --- a/esp32s3-hal/Cargo.toml +++ b/esp32s3-hal/Cargo.toml @@ -41,6 +41,8 @@ esp-backtrace = { version = "0.2.0", features = ["esp32s3", "panic-handler", esp-println = { version = "0.3.0", features = ["esp32s3"] } smart-leds = "0.3.0" ssd1306 = "0.7.1" +usb-device = { version = "0.2.3" } +usbd-serial = "0.1.1" [features] default = ["rt", "vectored"] diff --git a/esp32s3-hal/examples/usb_serial.rs b/esp32s3-hal/examples/usb_serial.rs new file mode 100644 index 00000000000..e2f9f8d9a6b --- /dev/null +++ b/esp32s3-hal/examples/usb_serial.rs @@ -0,0 +1,87 @@ +//! CDC-ACM serial port example using polling in a busy loop. +//! +//! This example should be built in release mode. + +#![no_std] +#![no_main] + +use esp32s3_hal::{ + clock::{ClockControl, CpuClock}, + otg_fs::{UsbBus, USB}, + pac::Peripherals, + prelude::*, + timer::TimerGroup, + Rtc, + IO, +}; +use esp_backtrace as _; +use usb_device::prelude::{UsbDeviceBuilder, UsbVidPid}; +use xtensa_lx_rt::entry; + +static mut EP_MEMORY: [u32; 1024] = [0; 1024]; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take().unwrap(); + let mut system = peripherals.SYSTEM.split(); + let clocks = ClockControl::configure(system.clock_control, CpuClock::Clock240MHz).freeze(); + + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt = timer_group0.wdt; + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + + // Disable MWDT and RWDT (Watchdog) flash boot protection + wdt.disable(); + rtc.rwdt.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + + let usb = USB::new( + peripherals.USB0, + io.pins.gpio18, + io.pins.gpio19, + io.pins.gpio20, + &mut system.peripheral_clock_control, + ); + + let usb_bus = UsbBus::new(usb, unsafe { &mut EP_MEMORY }); + + let mut serial = usbd_serial::SerialPort::new(&usb_bus); + + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) + .manufacturer("esp-hal") + .product("esp-hal") + .serial_number("12345678") + .device_class(usbd_serial::USB_CLASS_CDC) + .build(); + + loop { + if !usb_dev.poll(&mut [&mut serial]) { + continue; + } + + let mut buf = [0u8; 64]; + + match serial.read(&mut buf) { + Ok(count) if count > 0 => { + // Echo back in upper case + for c in buf[0..count].iter_mut() { + if 0x61 <= *c && *c <= 0x7a { + *c &= !0x20; + } + } + + let mut write_offset = 0; + while write_offset < count { + match serial.write(&buf[write_offset..count]) { + Ok(len) if len > 0 => { + write_offset += len; + } + _ => {} + } + } + } + _ => {} + } + } +} diff --git a/esp32s3-hal/src/gpio.rs b/esp32s3-hal/src/gpio.rs index a7cb1a01102..9ca80ac3bed 100644 --- a/esp32s3-hal/src/gpio.rs +++ b/esp32s3-hal/src/gpio.rs @@ -88,3 +88,8 @@ analog! { Gpio20: (20, rtc_pad20, rtc_pad20_hold, mux_sel, fun_sel, fun_ie, slp_ie, slp_sel, rue, rde, drv, slp_oe), Gpio21: (21, rtc_pad21, rtc_pad21_hold, mux_sel, fun_sel, fun_ie, slp_ie, slp_sel, rue, rde, drv, slp_oe), } + +// implement marker traits on USB pins +impl esp_hal_common::otg_fs::UsbSel for Gpio18 {} +impl esp_hal_common::otg_fs::UsbDp for Gpio19 {} +impl esp_hal_common::otg_fs::UsbDm for Gpio20 {} diff --git a/esp32s3-hal/src/lib.rs b/esp32s3-hal/src/lib.rs index 2e219c3c1d5..78762587236 100644 --- a/esp32s3-hal/src/lib.rs +++ b/esp32s3-hal/src/lib.rs @@ -13,6 +13,7 @@ pub use esp_hal_common::{ interrupt, ledc, macros, + otg_fs, pac, prelude, pulse_control,