Releases: esp-rs/esp-hal
1.0.0
This release has an accompanying blog post: https://developer.espressif.com/blog/2025/10/esp-hal-1/.
To view all the documentation for all crates, along with their chip specific APIs, visit https://docs.espressif.com/projects/rust/.
Please note that only changes to the esp-hal package are tracked in these release notes, please check the esp-hal repo for other packages release notes.
Migration notice
There are no migration changes between 1.0.0-rc.1, and 1.0.0. However, if you are updating a project from a previous version please checkout the tag of your version, and work your way through the migration guides between versions.
Note that unstable drivers see can see a lot of churn between versions, it may be better to just try and adapt your code from the current documentation for those drivers.
v1.0.0-rc.1
To view all the documentation for all crates, along with their chip specific APIs, visit https://docs.espressif.com/projects/rust/.
Please note that only changes to the esp-hal package are tracked in these release notes, please check the esp-hal repo for other packages release notes.
Special migration notice
esp-wifi is no more, it's been replaced with esp-radio. The previously builtin scheduler (required for operating the WiFi blobs) has been pulled out and vastly improved in the esp-rtos crate.
esp-hal-embassy is no more, it's been replaced (merged) with esp-rtos. The new crate has OS aware executors for better performance and efficiency in the future.
Please read the rest of the migration guide here for more details.
Migration Guide from 1.0.0-rc.0 to 1.0.0-rc.1
RNG changes
Random number generator objects can now be created anywhere. The RNG peripheral singleton
is only necessary to enable the cryptographically secure random number generator.
Rngcan be constructed without any constraints.Trngcan only be constructed when it can be ensured to generate true random numbers.
A new TrngSource object has been added. Users can use TrngSource::new to create the object,
and as long as it is alive, Trng::try_new will return Ok and will provide a true random number
generator interface.
The previous way to obtain RNG object has changed like so:
-let mut rng = Rng::new(peripherals.RNG);
+let rng = Rng::new();
-let mut trng = Trng::new(peripherals.RNG, peripherals.ADC1);
+// Once:
+let trng_source = TrngSource::new(peripherals.RNG, peripherals.ADC1);
+// As long as `trng_source` is alive:
+let trng = Tnrg::try_new().unrwap();AES changes
The esp_hal::aes::Aes and esp_hal::aes::AesDma drivers has been slightly reworked:
Modehas been replaced byOperation. Operation hasEncryptandDecryptvariants, but the key length is no longer part of the enum. The key length is specified by the key. AesDma now takes thisOperation.Aes::processhas been split intoencryptanddecrypt. These functions no longer take a mode parameter.AesDma::write_blockandAesDma::write_keyhave been removed.AesDma::processnow takesDmaCipherStatewhich includes information to initialize the block cipher mode of operation.
-aes.process(block, Mode::Encrypt128, key);
+aes.encrypt(block, key_16_bytes);
-aes.process(block, Mode::Decrypt256, key);
+aes.decrypt(block, key_32_bytes);+use esp_hal::aes::dma::DmaCipherState;
+use esp_hal::aes::cipher_modes::Ecb;
let transfer = aes_dma
.process(
1,
output,
input,
Operation::Encrypt,
- CipherMode::Ecb,
+ &DmaCipherState::from(Ecb),
key,
)
.map_err(|e| e.0)
.unwrap();
(aes_dma, output, input) = transfer.wait();ISR Callback Changes
Previously callbacks were of type extern "C" fn(), now they are IsrCallback. In most places no changes are needed but when using bind_interrupt directly
you need to adapt the code.
#[esp_hal::ram]
extern "C" fn software3_interrupt() {
// ...
}
esp_hal::interrupt::bind_interrupt(
esp_hal::peripherals::Interrupt::FROM_CPU_INTR3,
- software3_interrupt,
+ IsrCallback::new(software3_interrupt),
);RMT changes
RMT PulseCode changes
PulseCode used to be an extension trait implemented on u32. It is now a
newtype struct, wrapping u32 and providing mostly const methods.
RMT transmit and receive methods accept impl Into<PulseCode> and
impl From<PulseCode>, respectively, and implementations for
PulseCode: From<u32> and u32: From<PulseCode> are provided.
The PulseCode::empty() method has been renamed to PulseCode::end_marker(),
and the same value can also be obtained via PulseCode::default(). Either methods
might be more desirable depending on the context to better communicate the meaning
of this value.
Nevertheless, type annotations will require some changes:
let rmt = Rmt::new(peripherals.RMT, freq).unrwap();
let tx_channel = rmt.channel0.configure_tx(peripherals.GPIO1, TxChannelConfig::default());
let rx_channel = rmt.channel2.configure_rx(peripherals.GPIO2, RxChannelConfig::default());
-let mut tx_data: [u32; 20] = [PulseCode::new(Level::High, 42, Level::Low, 24); 20];
+let mut tx_data: [PulseCode; 20] = [PulseCode::new(Level::High, 42, Level::Low, 24); 20];
-tx_data[tx_data.len() - 1] = PulseCode::empty();
+tx_data[tx_data.len() - 1] = PulseCode::end_marker();
-let mut rx_data: [u32; 20] = [PulseCode::empty(); 20];
+let mut rx_data: [PulseCode; 20] = [PulseCode::default(); 20];
let _ = tx_channel.transmit(&tx_data).wait().unwrap();
let _ = rx_channel.transmit(&mut rx_data).wait().unwrap();PulseCode constructors have also been reworked, now providing
PulseCode::new()which panics for out-of-range signal lengths,PulseCode::new_clamped()which saturates the signal lengths if out of range,PulseCode::try_new()which returnsNoneif any signal length is out of range.
The old behaviour of truncating the passed in u16 to 15 bits is not available
anymore; which method is the best replacement will depend on the use case.
RMT Channel Changes
rmt::Channel used to have a Raw: RawChannelAccess generic parameter,
which could be either ConstChannelAccess<Dir, const CHANNEL: u8> or DynChannelAccess<Dir>.
This generic has been erased, effectively always using DynChannelAccess.
The corresponding parameter of the transaction structs has been removed as well
(SingleShotTxTransaction, ContinuousTxTransaction, RxTransaction).
Transmit and receive methods are now directly implemented by
Channel<Dm: DriverMode, Tx>
and
Channel<Dm: DriverMode, Rx>
respectively and the RxChannel, TxChannel, RxChannelAsync and TxChannelAsync
traits have been removed.
Several related types that were previously exported have been removed from the
API as well.
-use esp_hal::rmt::{ConstChannelAccess, DynChannelAccess, RawChannelAccess};
-let mut tx: Channel<Blocking, ConstChannelAccess<Tx, 0>> = rmt.channel0.configure_tx(NoPin, TxChannelConfig::default());
-let mut rx: Channel<Blocking, ConstChannelAccess<Rx, 2>> = rmt.channel2.configure_rx(NoPin, RxChannelConfig::default());
+let mut tx: Channel<Blocking, Tx> = rmt.channel0.configure_tx(NoPin, TxChannelConfig::default());
+let mut rx: Channel<Blocking, Rx> = rmt.channel2.configure_rx(NoPin, RxChannelConfig::default());
-let mut tx: Channel<Blocking, DynChannelAccess<Tx>> = tx.degrade();
-let mut rx: Channel<Blocking, DynChannelAccess<Rx>> = rx.degrade();
-// same for TxChannelAsync, RxChannelAsync
-use esp_hal::rmt::{TxChannel, RxChannel};
-
-let tx_transaction: SingleShotTxTransaction<'_, DynChannelAccess<Tx>, PulseCode> = tx.transmit(&data);
-let rx_transaction: RxTransaction<'_, DynChannelAccess<Rx>, PulseCode> = rx.transmit(&data);
+let tx_transaction: SingleShotTxTransaction<'_, PulseCode> = tx.transmit(&data);
+let rx_transaction: RxTransaction<'_, PulseCode> = rx.transmit(&data);RMT method changes
The rmt::Channel::transmit_continuously and
rmt::Channel::transmit_continuously_with_loopcount methods have been merged:
-let tx_trans0 = tx_channel0.transmit_continuously(&data);
-let tx_trans1 = tx_channel1.transmit_continuously_with_loopcount(&data, count);
+use core::num::NonZeroU16;
+use esp_hal::rmt::LoopCount;
+let tx_trans0 = tx_channel0.transmit_continuously(&data, LoopCount::Infinite);
+let count = NonZeroU16::new(count).unwrap();
+let tx_trans1 = tx_channel1.transmit_continuously(&data, LoopCount::Finite(count));The receiver methods rmt::Channel::<'_, Blocking>::receive and
rmt::Channel::<'_, Async>::receive now return the actual amount of data read,
which may be shorter than the buffer size if the idle threshold was exceeded:
-let channel = channel.receive(&mut buffer)?.wait().unwrap();
+let (_count, channel) = channel.receive(&mut buffer)?.wait().unwrap();
-let () = async_channel.receive(&mut buffer).await.unwrap();
+let _count = async_channel.receive(&mut buffer).await.unwrap();RMT lifetime changes
The RMT driver didn't use to properly tie together lifetimes of its types and
therefore didn't statically prevent all kinds of concurrent and conflicting
channel re-use.
rmt::Channel and rmt::ChannelCreator now carry a lifetime and can be reborrowed:
let rmt: Rmt<Blocking> = Rmt::new(peripherals.RMT, freq)?;
- let cc: ChannelCreator<Blocking, 0> = rmt.channel0;
- let ch: Channel<Blocking, Tx> = rmt.channel0.configure_tx(pin, config)?;
+ let cc: ChannelCreator<'static, Blocking, 0> = rmt.channel0;
+ let ch: Channel<'static, Blocking, Tx> = rmt.channel0.configure_tx(pin, config)?;Additionally, RMT transaction types
SingleShotTxTransactionContinuousTxTransactionRxTransaction- futures returned by the async API
are marked as#[must_use]to account for the fact that it is in general required to poll them to ensure progress.
Additionally, they now implementDropand stop the ongoing transfer as quickly as possible when dropped,
ensuring that subsequent transactions start from a well-defined state.
RMT continuous transmit changes
The behavior of Channel::transmit_continuously has been clarified to account
for the varying hardware support between devices: It now takes an additional
LoopStop argument.
- let transaction = channel.transmit_continuously(&data, LoopCount::Finite(count))?;
+ let transaction = channel.transmit_continuously(&data, LoopCount::Finite(count), LoopStop::Manual)?;Additionally, some enum variants and methods are only defined when the hardware ...
v1.0.0-rc.0
To view all the documentation for all crates, along with their chip specific APIs, visit https://docs.espressif.com/projects/rust/.
Please note that only changes to the esp-hal package are tracked in these release notes, please check the esp-hal repo for other packages release notes.
Special migration note
As part of the espflash v4 release, we've updated the prebuilt esp-idf bootloaders. These new bootloaders require a small amount of metadata to boot correctly. We've made this easy to do, by depending on the esp-bootloader-esp-idf support crate, and including the esp_app_desc!() macro somewhere in your project. Please note if you were already depending on esp-bootloader-esp-idf, as of v0.2.0 you must enable a chip feature.
Migration Guide from 1.0.0-beta.1 to 1.0.0-rc.0
AnyI2c and AnySpi have been moved to mode-specific submodules
-use esp_hal::i2c::AnyI2c;
+use esp_hal::i2c::master::AnyI2c;AnySpi has been separated into master and slave counterparts.
-use esp_hal::spi::AnySpi;
+use esp_hal::spi::master::AnySpi;
+// or:
+use esp_hal:spi::slave::AnySpi;SPI DataMode has been moved into esp_hal::spi::master
-use esp_hal::spi::DataMode;
+use esp_hal::spi::master::DataMode;ConfigError variants have been changed
SPI master
UnsupportedFrequency->FrequencyOutOfRange
UART
UnachievableBaudrate->BaudrateNotAchievableUnsupportedBaudrate->BaudrateNotSupportedUnsupportedTimeout->TimeoutTooLongUnsupportedRxFifoThreshold->RxFifoThresholdNotSupportedUnsupportedTxFifoThreshold->TxFifoThresholdNotSupported
I2C master
FrequencyInvalid->FrequencyOutOfRangeTimeoutInvalid->TimeoutTooLong
Unstable driver migration guides
RMT Channel generic parameters have changed
Instead of being parameterized by a const CHANNEL: u8, channels now take a generic
parameter Raw: RawChannelAccess<Dir> where Dir=Tx or Dir=Rx that identifies the
channel.
+use esp_hal::rmt::{ConstChannelAccess, Rx, Tx};
+
-let channel: Channel<Blocking, 0> = {
+let channel: Channel<Blocking, ConstChannelAccess<Tx, 0>> = {
use esp_hal::rmt::TxChannelCreator;
rmt.channel0().configure(pin, TxChannelConfig::default())
};
-let channel: Channel<Blocking, 2> = {
+let channel: Channel<Blocking, ConstChannelAccess<Rx, 0>> = {
use esp_hal::rmt::RxChannelCreator;
rmt.channel2().configure(pin, RxChannelConfig::default())
};RMT ChannelCreator traits have been re-arranged
TxChannelCreatorAsync and RxChannelCreatorAsync have been removed.
Instead, TxChannelCreator and RxChannelCreator now carry a Dm: DriverMode
generic parameter. ChannelCreator<Dm, CHANNEL> thus implements TxChannelCreator<Dm>
and RxChannelCreator<Dm>, respectively.
Additionally, the configure methods have been renamed to configure_tx and
configure_rx to avoid trait disambiguation issues.
+use esp_hal::rmt::{RxChannelCreator, TxChannelCreator};
+
let rmt = rmt.into_async();
-let tx_channel = {
- use esp_hal::rmt::TxChannelCreatorAsync;
- rmt.channel0.configure(pin, tx_config)
-};
+ let tx_channel = rmt.channel0.configure_tx(pin, tx_config);
-let rx_channel = {
- use esp_hal::rmt::RxChannelCreatorAsync;
- rmt.channel2.configure(pin, rx_config)
-};
+ let rx_channel = rmt.channel2.configure_rx(pin, rx_config);v1.0.0-beta.1
To view all the documentation for all crates, along with their chip specific APIs, visit https://docs.espressif.com/projects/rust/.
Please note that only changes to the esp-hal package are tracked in these release notes, please check the esp-hal repo for other packages release notes.
Migration Guide from v1.0.0-beta.0 to 1.0.0-beta.1
Peripheral singleton changes
As this is a conceptual change, we'll not list all affected types in this section.
AnyPeripheral
refers to allAny*types at the same time -AnySpi,AnyUart, etc. Similarly,Driverrefers
to any matching peripheral driver.
Peripheral singletons (like SPI2 or GpioPin) no longer implement Peripheral. The Peripheral
trait and PeripheralRef struct have been removed. The peripheral singletons instead have a
lifetime and implement the following methods:
stealandclone_uncheckedto unsafely create them.reborrowto safely create a copy with a shorter lifetime.degradehas been removed in favour ofAnyPeripheral::from.
Application-facing changes
Peripheral drivers no longer accept &mut singleton.
Use reborrow instead:
-let driver = Driver::new(&mut peripheral);
+let driver = Driver::new(peripheral.reborrow());After dropping the driver, peripheral should be accessible again as it used to be previously.
Peripheral driver changes
The Peripheral and PeripheralRef types no longer exist. The driver structs and constructors need
to be updated accordingly:
If the driver works with a single peripheral instance, for example AES:
struct Driver<'d> {
- aes: PeripheralRef<'d, AES>,
+ aes: AES<'d>,
}
// ...
-fn new(aes: impl Peripheral<P = AES> + 'd)
+fn new(aes: AES<'d>)If a driver works with multiple peripheral instances, e.g. SPI:
struct Driver<'d> {
- spi: PeripheralRef<'d, AnySpi>,
+ spi: AnySpi<'d>,
}
// ...
-fn new(spi: impl Peripheral<P = impl Instance> + 'd)
+fn new(spi: impl Instance + Into<AnySpi<'d>>)DMA changes
DMA channel types are now consistent with other peripheral singletons
For compatibility with third party libraries, as well as for consistency with other peripherals,
the DMA channel types (e.g. DmaChannel0/Spi2DmaChannel) have been replaced by
esp_hal::peripherals::DMA_channel<'d> types.
-use esp_hal::gpio::DmaChannel0;
+use esp_hal::peripherals::DMA_CH0;-use esp_hal::gpio::Spi2DmaChannel;
+use esp_hal::peripherals::DMA_SPI2;GPIO changes
GPIO pins are now consistent with other peripheral singletons
For compatibility with third party libraries, as well as for consistency with other peripherals,
the GPIO pin types (GpioPin<N>) have been replaced by separate esp_hal::peripherals::GPION<'d>
types.
-use esp_hal::gpio::GpioPin;
+use esp_hal::peripherals::{GPIO2, GPIO3};
-fn my_function(gpio2: GpioPin<2>, gpio3: GpioPin<3>) {...}
+fn my_function(gpio2: GPIO2<'_>, gpio3: GPIO3<'_>) {...}GPIO matrix (interconnect) has been reworked
The GPIO matrix implementation has been reworked to solve known issues. The following changes have
been made:
InputConnectionandOutputConnectionhave been removed and their functionality merged intoInputSignalandOutputSignal.- The
InputSignalandOutputSignaltypes now have a lifetime. This lifetime prevents them to be live for longer than the GPIO that was used to create them. - Because
InputSignalandOutputSignalcan now represent constant levels, thenumbermethod now returnsOption<u8>. - The way to invert inputs and outputs has been changed:
InputSignal::invertedhas been renamed toInputSignal::with_input_inverter.OutputSignal::invertedhas been renamed toOutputSignal::with_output_inverter.InputSignal::invertandOutputSignal::inverthave been removed.OutputSignalnow has aninverted_inputproperty, which can be changed by usingwith_output_inverter.- The signals have
is_{input, output}_invertedmethods to read the state that will be used when configuring the hardware.
- Users can now force a signal through the GPIO matrix.
- The
enable_inputandenable_outputmethods have been renamed toset_input_enableandset_output_enable. - A new
PeripheralSignaltrait has been added, which allows us to no longer implyPeripheralInputforPeripheralOutputtypes. - Functions that accept
PeripheralInputno longer acceptPeripheralOutputimplementations. - Removed
Input::into_peripheral_outputandOutput::peripheral_inputfunctions. The drivers can be converted intoFlexwhich offers both ways to acquire signal types, and more. - Various "unreasonable" signal conversions have been removed.
OutputSignalcan no longer be converted intoInputSignal. InputSignalandOutputSignalnow have a "frozen" state. If they are created by a pin driver, they start frozen. If they are created by splitting a GPIO pin, they start unfrozen. Frozen signals will not be configured by peripheral drivers.- Splitting GPIO pins into signals is now unsafe.
Flexcan now be (unsafely) split intoInputandOutputdrivers withsplit_into_drivers.- Converting
AnyPininto signals will now reset the pin's configuration. Creating an InputSignal will now enable the pin's input buffer.
Flex API surface has been simplified
The enable_input method has been renamed to set_input_enable.
The Flex driver no longer provides the following functions:
- set_as_input
- set_as_output
- set_drive_strength
- set_as_open_drain
- pull_direction
The individual configurations can be set via apply_input_config and apply_output_config.
The input buffer and output driver can be separately enabled via set_input_enable and
set_output_enable.
Normally you only need to configure your pin once, after which changing modes can be done by calling
set_input_enable and/or set_output_enable.
- flex.set_as_input(pull_direction);
+ flex.apply_input_config(&InputConfig::default().with_pull(pull_direction)); // only if needed
+ flex.set_output_enable(false);
+ flex.set_input_enable(true);
- flex.set_as_output(); // or set_as_open_drain(pull_direction)
+ flex.apply_output_config(&OutputConfig::default().with_drive_mode(open_drain_or_push_pull)); // only if needed
+ flex.set_input_enable(false); // optional
+ flex.set_level(initial_level); // optional
+ flex.set_output_enable(true);Interrupt handling changes
The interrupt status bits are no longer cleared automatically. Depending on your use case, you will
need to either do this yourself, or disable the pin's interrupt.
If you want your interrupt to keep firing, clear the interrupt status. Keep in mind that
this affects is_interrupt_set.
#[handler]
pub fn interrupt_handler() {
critical_section::with(|cs| {
let pin = INPUT_PIN.borrow_ref_mut(cs).as_mut().unwrap();
+ pin.clear_interrupt();
});
}If you want your interrupt to fire once per listen call, disable the interrupt.
#[handler]
pub fn interrupt_handler() {
critical_section::with(|cs| {
let pin = INPUT_PIN.borrow_ref_mut(cs).as_mut().unwrap();
+ pin.unlisten();
});
}I2S driver now takes DmaDescriptors later in construction
let i2s = I2s::new(
peripherals.I2S0,
Standard::Philips,
DataFormat::Data16Channel16,
Rate::from_hz(44100),
dma_channel,
- rx_descriptors,
- tx_descriptors,
);
let i2s_tx = i2s
.i2s_tx
.with_bclk(peripherals.GPIO2)
.with_ws(peripherals.GPIO4)
.with_dout(peripherals.GPIO5)
- .build();
+ .build(tx_descriptors);
let i2s_rx = i2s
.i2s_rx
.with_bclk(peripherals.GPIO2)
.with_ws(peripherals.GPIO4)
.with_din(peripherals.GPIO5)
- .build();
+ .build(rx_descriptors);PARL IO driver changes
ParlIoFullDuplex, ParlIoTxOnly and ParlIoRxOnly have been merged into ParlIo
- let parl_io = ParlIoFullDuplex::new(peripherals.PARL_IO, dma_channel)?;
- let parl_io = ParlIoTxOnly::new(peripherals.PARL_IO, dma_channel)?;
- let parl_io = ParlIoRxOnly::new(peripherals.PARL_IO, dma_channel)?;
+ let parl_io = ParlIo::new(peripherals.PARL_IO, dma_channel)?;Construction no longer asks for references
let mut parl_io_tx = parl_io
.tx
.with_config(
- &mut pin_conf,
- &mut clock_pin,
+ pin_conf,
+ clock_pin,
0,
SampleEdge::Normal,
BitPackOrder::Msb,
)?;Construction options are passed via config
+let config = RxConfig::default()
+ .with_frequency(Rate::from_mhz(20))
+ .with_bit_order(BitPackOrder::Msb);
let mut parl_io_rx = parl_io
.rx
.with_config(
pin_conf,
clock_pin,
- BitPackOrder::Msb,
- None,
+ config,
)?;+let config = TxConfig::default()
+ .with_frequency(Rate::from_mhz(20))
+ .with_sample_edge(SampleEdge::Normal)
+ .with_bit_order(BitPackOrder::Msb);
let mut parl_io_tx = parl_io
.tx
.with_config(
pin_conf,
clock_pin,
- 0,
- SampleEdge::Normal,
- BitPackOrder::Msb,
+ config,
)?;SPI Slave driver now uses the newer DMA APIs
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(32000);
+ let mut dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
+ let mut dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
- let transfer = spi.transfer(...v1.0.0-beta.0
To view all the documentation for all crates, along with their chip specific APIs, visit https://docs.espressif.com/projects/rust/.
Please note that only changes to the esp-hal package are tracked in these release notes.
Migration Guide from v0.23.x to v1.0.0-beta.0
Driver stability
Unstable parts of esp-hal are gated behind the unstable feature. Previously, this feature
was enabled by default, but starting with this release, it is no longer the case.
The unstable feature itself is unstable, we might change the way we hide APIs without notice.
Unstable APIs are not covered by semver guarantees, they may be changed or removed at any time.
Please refer to the documentation to see which APIs are marked as unstable.
esp-hal = { version = "1.0.0-beta.0" , features = [
"esp32c6",
+ "unstable"
]}Async drivers can no longer be sent between cores and executors
To work around this limitation, send the blocking driver, and configure it into Async mode
in the target context.
#[embassy_executor::task]
-async fn interrupt_driven_task(mut spi: Spi<'static, Async>) {
+async fn interrupt_driven_task(spi: Spi<'static, Blocking>) {
+ let mut spi = spi.into_async();
...
}
let spi = Spi::new(...)
.unwrap()
// ...
- .into_async();
send_spawner.spawn(interrupt_driven_task(spi)).unwrap();RMT changes
Configurations structs now support the builder-lite pattern
The TxChannelConfig and RxChannelConfig structs now support the builder-lite pattern.
Thus, explicit initialization of all fields can be replaced by only the necessary setter methods:
let mut channel = rmt
.channel0
.configure(
peripherals.GPIO1,
- TxChannelConfig {
- clk_divider: 1,
- idle_output_level: false,
- idle_output: false,
- carrier_modulation: false,
- carrier_high: 1,
- carrier_low: 1,
- carrier_level: false,
- },
+ TxChannelConfig::default().with_clk_divider(1)
)
.unwrap();Some configuration fields now take gpio::Level instead of bool
Fields related to the carrier level in the channel configuration structs now
take the more descriptive gpio::Level type instead of a plain bool.
let mut tx_channel = rmt
.channel0
.configure(
peripherals.GPIO1,
TxChannelConfig::default()
.with_clk_divider(1)
- .with_idle_output_level(false)
+ .with_idle_output_level(Level::Low)
- .with_carrier_level(true)
+ .with_carrier_level(Level::High)
)
.unwrap();
let mut rx_channel = rmt
.channel1
.configure(
peripherals.GPIO2,
RxChannelConfig::default()
.with_clk_divider(1)
.with_carrier_modulation(true)
.with_carrier_high(1)
.with_carrier_low(1)
- .with_carrier_level(false),
+ .with_carrier_level(Level::Low),
)
.unwrap();PulseCode now uses gpio::Level instead of bool to specify output levels
The more descriptive gpio::Level enum is now used to specify output levels of PulseCode:
+ use esp_hal::gpio::Level;
+
- let code = PulseCode::new(true, 200, false, 50);
+ let code = PulseCode::new(Level::High, 200, Level::Low, 50);Global driver changes
The _bytes postfix of driver methods that take a byte slice have been removed.
- uart0.write_bytes(b"Hello world!")?;
+ uart0.write(b"Hello world!")?;The peripherals::Interrupts enum is no longer available. Users (mostly third party driver
developers) will need to use the PAC crates directly.
UART changes
Uart write is now blocking and return the number of bytes written. read will block until it fills at least one byte into the buffer with received bytes, use read_buffered_bytes to read the available bytes without blocking.
e.g.
- uart.write(0x42).ok();
- let read = block!(ctx.uart.read());
+ let data: [u8; 1] = [0x42];
+ uart.write(&data).unwrap();
+ let mut byte = [0u8; 1];
+ uart.read(&mut byte).unwrap();UART errors have been split into TxError and RxError.
read_* and write_* functions now return different types. In practice this means you no longer
need to check for RX errors that can't be returned by write_*.
The error type used by embedded-io has been updated to reflect this. A new IoError enum has been
added for embedded-io errors associated to the unsplit Uart driver. On Uart (but not UartRx
or UartTx) TX-related trait methods return IoError::Tx(TxError), while RX-related methods return
IoError::Rx(RxError).
UART halves have their configuration split, too
Uart::Config structure now contains separate RxConfig and TxConfig:
- let config = Config::default().with_rx_fifo_full_threshold(30);
+ let config = Config::default()
+ .with_rx(RxConfig::default()
+ .with_fifo_full_threshold(30)
+ );timer::wait is now blocking
periodic.start(100.millis()).unwrap();
- nb::block!(periodic.wait()).unwrap();
+ periodic.wait();SPI Changes
spi::DataMode changed the meaning of DataMode::Single - it now means 3-wire SPI (using one data line).
Use DataMode::SingleTwoDataLines to get the previous behavior.
- DataMode::Single,
+ DataMode::SingleTwoDataLines,Spi now offers both, with_mosi and with_sio0. Consider using with_sio for half-duplex SPI except for [DataMode::SingleTwoDataLines] or for a mixed-bus.
Removed flip-link Feature
The flip-link feature is removed and replaced by the ESP_HAL_CONFIG_FLIP_LINK option.
Cargo.toml
- esp-hal = { version = "0.23.0", features = ["flip-link"]}
+ esp-hal = "0.23.0"config/config.toml
[env]
+ ESP_HAL_CONFIG_FLIP_LINK = "true"Removed psram-quad/prsram-octal Feature
The features psram-quad/prsram-octal are replaced by a single psram feature and an additional config option (ESP_HAL_CONFIG_PSRAM_MODE).
ESP_HAL_CONFIG_PSRAM_MODE defaults to quad and (for ESP32-S3) also allows octal.
Cargo.toml
- esp-hal = { version = "0.23.0", features = ["psram-octal"]}
+ esp-hal = { version = "0.23.0", features = ["psram"]}config/config.toml
[env]
+ ESP_HAL_CONFIG_PSRAM_MODE = "octal"PARL_IO changes
Parallel IO now uses the newer DMA Move API.
Changes on the TX side
let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, 32000);
+ let mut dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
- let transfer = parl_io_tx.write_dma(&tx_buffer).unwrap();
- transfer.wait().unwrap();
+ let transfer = parl_io_tx.write(dma_tx_buf.len(), dma_tx_buf).unwrap();
+ (result, parl_io_tx, dma_tx_buf) = transfer.wait();
+ result.unwrap();Changes on the RX side
let (rx_buffer, rx_descriptors, _, _) = dma_buffers!(32000, 0);
+ let mut dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
- let transfer = parl_io_rx.read_dma(&mut rx_buffer).unwrap();
- transfer.wait().unwrap();
+ let transfer = parl_io_rx.read(Some(dma_rx_buf.len()), dma_rx_buf).unwrap();
+ (_, parl_io_rx, dma_rx_buf) = transfer.wait();On the RX side, the EofMode is now decided at transfer time, rather than config time.
EofMode::ByteLen->Some(<number of bytes to receive>)EofMode::EnableSignal->None
GPIO changes
GPIO drivers now take configuration structs.
- Input::new(peripherals.GPIO0, Pull::Up);
+ Input::new(peripherals.GPIO0, InputConfig::default().with_pull(Pull::Up));
- Output::new(peripherals.GPIO0, Level::Low);
+ Output::new(peripherals.GPIO0, Level::Low, OutputConfig::default());The OutputOpenDrain driver has been removed. You can use Output instead with
DriveMode::OpenDrain. The input-related methods of OutputOpenDrain (level,
is_high, is_low) are available through the (unstable) Flex driver.
- OutputOpenDrain::new(peripherals.GPIO0, Level::Low);
+ Output::new(
peripherals.GPIO0,
Level::Low,
OutputConfig::default()
.with_drive_mode(DriveMode::OpenDrain),
);AES DMA driver changes
AES now uses the newer DMA move API.
let (output, rx_descriptors, input, tx_descriptors) = dma_buffers!(32000);
+ let mut output = DmaRxBuf::new(rx_descriptors, output).unwrap();
+ let mut input = DmaTxBuf::new(tx_descriptors, input).unwrap();
let mut aes = Aes::new(peripherals.AES).with_dma(
dma_channel,
- rx_descriptors,
- tx_descriptors,
);
let transfer = aes
.process(
- &input,
- &mut output,
+ output.len().div_ceil(16), // Number of blocks
+ output,
+ input,
Mode::Encryption128,
CipherMode::Ecb,
keybuf,
)
+ .map_err(|e| e.0)
.unwrap();
transfer.wait();I2C Changes
All async functions now include the _async postfix. Additionally the non-async functions are now available in async-mode.
- let result = i2c.write_read(0x77, &[0xaa], &mut data).await;
+ let result = i2c.write_read_async(0x77, &[0xaa], &mut data).await;ADC Changes
The ADC driver has gained a new Async/Blocking mode parameter.
NOTE: Async support is only supported in ESP32C3 and ESP32C6 for now
- Adc<'d, ADC>
+ Adc<'d, ADC, Blocking>time API changes
ESP-HAL no longer publicly exposes fugit and no longer expos...
v0.23.1
0.23.0
Please note that only changes to the esp-hal package are tracked in these release notes.
Migration Guide
Starting with this release, unstable parts of esp-hal will be gated behind the unstable feature.
The unstable feature itself is unstable, we might change the way we hide APIs without notice.
Unstable APIs are not covered by semver guarantees, they may break even between patch releases.
Please refer to the documentation to see which APIs are marked as unstable.
DMA changes
Accessing channel objects
DMA channels are now available through the Peripherals struct, which is returned
by esp_hal::init(). The channels themselves have been renamed to match other peripheral singletons.
- ESP32-C2, C3, C6, H2 and S3:
channelX -> DMA_CHX - ESP32 and S2:
spiXchannel -> DMA_SPIX,i2sXchannel -> DMA_I2SX
-let dma = Dma::new(peripherals.DMA);
-let channel = dma.channel2;
+let channel = peripherals.DMA_CH2;Channel configuration changes
configure_for_asyncandconfigurehave been removed- PDMA devices (ESP32, ESP32-S2) provide no channel configurability
- GDMA devices provide
set_priorityto change DMA in/out channel priority
let mut spi = Spi::new_with_config(
peripherals.SPI2,
Config::default(),
)
// other setup
-.with_dma(dma_channel.configure(false, DmaPriority::Priority0));
+.with_dma(dma_channel);+dma_channel.set_priority(DmaPriority::Priority1);
let mut spi = Spi::new_with_config(
peripherals.SPI2,
Config::default(),
)
// other setup
-.with_dma(dma_channel.configure(false, DmaPriority::Priority1));
+.with_dma(dma_channel);Burst mode configuration
Burst mode is now a property of buffers, instead of DMA channels. Configuration can be done by
calling set_burst_config on buffers that support it. The configuration options and the
corresponding BurstConfig type are device specfic.
Usability changes affecting applications
Individual channels are no longer wrapped in Channel, but they implement the DmaChannel trait.
This means that if you want to split them into an rx and a tx half (which is only supported on
the H2, C6 and S3 currently), you can't move out of the channel but instead you need to call
the split method.
-let tx = channel.tx;
+use esp_hal::dma::DmaChannel;
+let (rx, tx) = channel.split();The Channel types remain available for use in peripheral drivers.
It is now simpler to work with DMA channels in generic contexts. esp-hal now provides convenience
traits and type aliasses to specify peripheral compatibility. The ChannelCreator types have been
removed, further simplifying use.
For example, previously you may have needed to write something like this to accept a DMA channel
in a generic function:
fn new_foo<'d, T>(
dma_channel: ChannelCreator<2>, // It wasn't possible to accept a generic ChannelCreator.
peripheral: impl Peripheral<P = T> + 'd,
)
where
T: SomePeripheralInstance,
ChannelCreator<2>: DmaChannelConvert<<T as DmaEligible>::Dma>,
{
let dma_channel = dma_channel.configure_for_async(false, DmaPriority::Priority0);
let driver = PeripheralDriver::new(peripheral, config).with_dma(dma_channel);
// ...
}From now on a similar, but more flexible implementation may look like:
fn new_foo<'d, T, CH>(
dma_channel: impl Peripheral<P = CH> + 'd,
peripheral: impl Peripheral<P = T> + 'd,
)
where
T: SomePeripheralInstance,
CH: DmaChannelFor<T>,
{
// Optionally: dma_channel.set_priority(DmaPriority::Priority2);
let driver = PeripheralDriver::new(peripheral, config).with_dma(dma_channel);
// ...
}Usability changes affecting third party peripheral drivers
If you are writing a driver and need to store a channel in a structure, you can use one of the
ChannelFor type aliasses.
struct Aes<'d> {
- channel: ChannelTx<'d, Blocking, <AES as DmaEligible>::Dma>,
+ channel: ChannelTx<'d, Blocking, PeripheralTxChannel<AES>>,
}Timer changes
The low level timers, SystemTimer and TimerGroup are now "dumb". They contain no logic for operating modes or trait implementations (except the low level Timer trait).
Timer drivers - OneShotTimer & PeriodicTimer
Both drivers now have a Mode parameter. Both also type erase the underlying driver by default, call new_typed to retain the type.
- OneShotTimer<'static, systimer::Alarm>;
+ OneShotTimer<'static, Blocking>;
- PeriodicTimer<'static, systimer::Alarm>;
+ PeriodicTimer<'static, Blocking>;SystemTimer
let systimer = SystemTimer::new(peripherals.SYSTIMER);
- static UNIT0: StaticCell<SpecificUnit<'static, 0>> = StaticCell::new();
- let unit0 = UNIT0.init(systimer.unit0);
- let frozen_unit = FrozenUnit::new(unit0);
- let alarm0 = Alarm::new(systimer.comparator0, &frozen_unit);
- alarm0.set_period(1u32.secs());
+ let alarm0 = systimer.alarm0;
+ let mut timer = PeriodicTimer::new(alarm0);
+ timer.start(1u64.secs());TIMG
Timer group timers have been type erased.
- timg::Timer<timg::Timer0<crate::peripherals::TIMG0>, Blocking>
+ timg::TimerETM usage has changed
Timer dependant ETM events should be created prior to initializing the timer with the chosen driver.
let task = ...; // ETM task
let syst = SystemTimer::new(peripherals.SYSTIMER);
let alarm0 = syst.alarm0;
- alarm0.load_value(1u64.millis()).unwrap();
- alarm0.start();
- let event = Event::new(&mut alarm0);
+ let event = Event::new(&alarm0);
+ let timer = OneShotTimer::new(alarm0);
+ timer.schedule(1u64.millis()).unwrap();
let _configured_channel = channel0.setup(&event, &task);PSRAM is now initialized automatically
Calling esp_hal::initialize will now configure PSRAM if either the quad-psram or octal-psram
is enabled. To retrieve the address and size of the initialized external memory, use
esp_hal::psram::psram_raw_parts, which returns a pointer and a length.
-let peripherals = esp_hal::init(esp_hal::Config::default());
-let (start, size) = esp_hal::psram::init_psram(peripherals.PSRAM, psram_config);
+let peripherals = esp_hal::init({
+ let mut config = esp_hal::Config::default();
+ config.psram = psram_config;
+ config
+});
+let (start, size) = esp_hal::psram::psram_raw_parts(&peripherals.PSRAM, psram);The usage of esp_alloc::psram_allocator! remains unchanged.
embedded-hal 0.2.* is not supported anymore.
As per rust-embedded/embedded-hal#640, our driver no longer implements traits from embedded-hal 0.2.x.
Analogs of all traits from the above mentioned version are available in embedded-hal 1.x.x
- use embedded_hal_02::can::Frame;
+ use embedded_can::Frame;- use embedded_hal_02::digital::v2::OutputPin;
- use embedded_hal_02::digital::v2::ToggleableOutputPin;
+ use embedded_hal::digital::OutputPin;
+ use embedded_hal::digital::StatefulOutputPin;- use embedded_hal_02::serial::{Read, Write};
+ use embedded_hal_nb::serial::{Read, Write};You might also want to check the full official embedded-hal migration guide:
https://github.com/rust-embedded/embedded-hal/blob/master/docs/migrating-from-0.2-to-1.0.md
Interrupt related reshuffle
- use esp_hal::InterruptConfigurable;
- use esp_hal::DEFAULT_INTERRUPT_HANDLER;
+ use esp_hal::interrupt::InterruptConfigurable;
+ use esp_hal::interrupt::DEFAULT_INTERRUPT_HANDLER;Driver constructors now take a configuration and are fallible
The old new_with_config constructor have been removed, and new constructors now always take
a configuration structure. They have also been updated to return a ConfigError if the configuration
is not compatible with the hardware.
-let mut spi = Spi::new_with_config(
+let mut spi = Spi::new(
peripherals.SPI2,
Config {
frequency: 100.kHz(),
mode: SpiMode::_0,
..Config::default()
},
-);
+)
+.unwrap(); let mut spi = Spi::new(
peripherals.SPI2,
+ Config::default(),
-);
+)
+.unwrap();Peripheral instance type parameters and new_typed constructors have been removed
Call new instead and remove the type parameters if you've used them.
-let mut spi: Spi<'lt, SPI2> = Spi::new_typed(..).unwrap();
+let mut spi: Spi<'lt> = Spi::new(..).unwrap();LCD_CAM configuration changes
camnow has aConfigstrurct that contains frequency, bit/byte order, VSync filter options.- DPI, I8080:
frequencyhas been moved intoConfig.
+let mut cam_config = cam::Config::default();
+cam_config.frequency = 1u32.MHz();
cam::Camera::new(
lcd_cam.cam,
dma_rx_channel,
pins,
- 1u32.MHz(),
+ cam_config,
)SpiDma now requires you specify the transfer length explicitly
dma_tx_buf.set_length(5 /* or greater */);
- spi_dma.write(dma_tx_buf);
+ spi_dma.write(5, dma_tx_buf); dma_rx_buf.set_length(5 /* or greater */);
- spi_dma.read(dma_rx_buf);
+ spi_dma.read(5, dma_rx_buf); dma_rx_buf.set_length(5 /* or greater */);
dma_tx_buf.set_length(5 /* or greater */);
- spi_dma.transfer(dma_rx_buf, dma_tx_buf);
+ spi_dma.transfer(5, dma_rx_buf, 5, dma_tx_buf);I2C Error changes
To avoid abbreviations and contractions (as per the esp-hal guidelines), some error variants have changed
- Error::ExecIncomplete
+ Error::ExecutionIncomplete
- Error::CommandNrExceeded
+ Error::CommandNumberExceeded
- Error::ExceedingFifo
+ Error::FifoExceeded
- Error::TimeOut
+ Error::Timeout
- Error::InvalidZeroLengt...0.22.0
Please note that only changes to the esp-hal package are tracked in these release notes.
Migration Guide
IO changes
GPIO pins are now accessible via Peripherals
let peripherals = esp_hal::init(Default::default());
-let io = Io::new(peripherals.GPIO, peripherals.IOMUX);
-let pin = io.pins.gpio5;
+let pin = peripherals.GPIO5;Io constructor changes
new_with_priorityandnew_no_bind_interruptshave been removed.
Useset_priorityto configure the GPIO interrupt priority.
We no longer overwrite interrupt handlers set by user code during initialization.newno longer takesperipherals.GPIO
Removed async-specific constructors
The following async-specific constuctors have been removed:
configure_for_asyncDMA channel constructorsTwaiConfiguration::new_asyncandTwaiConfiguration::new_async_no_transceiverI2c::new_asyncLcdCam::new_asyncUsbSerialJtag::new_asyncRsa::new_asyncRmt::new_asyncUart::new_async,Uart::new_async_with_configUartRx::new_async,UartRx::new_async_with_configUartTx::new_async,UartTx::new_async_with_config
You can use the blocking counterparts, then call into_async on the returned peripheral instead.
-let mut config = twai::TwaiConfiguration::new_async(
+let mut config = twai::TwaiConfiguration::new(
peripherals.TWAI0,
loopback_pin.peripheral_input(),
loopback_pin,
twai::BaudRate::B1000K,
TwaiMode::SelfTest,
-);
+).into_async();Some drivers were implicitly configured to the asyncness of the DMA channel used to construct them.
This is no longer the case, and the following drivers will always be created in blocking mode:
i2s::master::I2sspi::master::SpiDmaandspi::master::SpiDmaBus
Peripheral types are now optional
You no longer have to specify the peripheral instance in the driver's type for the following
peripherals:
- SPI (both master and slave)
- I2S
- I2C
- TWAI
- UART
-Spi<'static, SPI2, FullDuplexMode>
+Spi<'static, FullDuplexMode>
-SpiDma<'static, SPI2, HalfDuplexMode, Blocking>
+SpiDma<'static, HalfDuplexMode, Blocking>
-I2sTx<'static, I2S0, Async>
+I2sTx<'static, Async>Note that you may still specify the instance if you need to. To do this, we provide _typed
versions of the constructors (for example: new_typed, new_half_duplex_typed). Please note that
the peripheral instance has been moved to the last generic parameter position.
let spi: Spi<'static, FullDuplexMode, SPI2> = Spi::new_typed(peripherals.SPI2, 1.MHz(), SpiMode::Mode0);I2C changes
The I2C master driver and related types have been moved to esp_hal::i2c::master.
The with_timeout constructors have been removed. new and new_typed now take a Config struct
with the available configuration options.
- The default configuration is now:
- bus frequency: 100 kHz
- timeout: about 10 bus clock cycles
The constructors no longer take pins. Use with_sda and with_scl instead.
-use esp_hal::i2c::I2c;
+use esp_hal::i2c::{Config, I2c};
-let i2c = I2c::new_with_timeout(peripherals.I2C0, io.pins.gpio4, io.pins.gpio5, 100.kHz(), timeout);
+I2c::new_with_config(
+ peripherals.I2C0,
+ {
+ let mut config = Config::default();
+ config.frequency = 100.kHz();
+ config.timeout = timeout;
+ config
+ },
+)
+.with_sda(io.pins.gpio4)
+.with_scl(io.pins.gpio5);The calculation of I2C timeout has changed
Previously, I2C timeouts were counted in increments of I2C peripheral clock cycles. This meant that
the configure value meant different lengths of time depending on the device. With this update, the
I2C configuration now expects the timeout value in number of bus clock cycles, which is consistent
between devices.
ESP32 and ESP32-S2 use an exact number of clock cycles for its timeout. Other MCUs, however, use
the 2^timeout value internally, and the HAL rounds up the timeout to the next appropriate value.
Changes to half-duplex SPI
The HalfDuplexMode and FullDuplexMode type parameters have been removed from SPI master and slave
drivers. It is now possible to execute half-duplex and full-duplex operations on the same SPI bus.
Driver construction
- The
Spi::new_half_duplexconstructor has been removed. Usenew(ornew_typed) instead. - The
with_pinsmethods have been removed. Use the individualwith_*functions instead. - The
with_mosiandwith_misofunctions now take input-output peripheral signals to support half-duplex mode.
- let mut spi = Spi::new_half_duplex(peripherals.SPI2, 100.kHz(), SpiMode::Mode0)
- .with_pins(sck, mosi, miso, sio2, sio3, cs);
+ let mut spi = Spi::new(peripherals.SPI2, 100.kHz(), SpiMode::Mode0)
+ .with_sck(sck)
+ .with_cs(cs)
+ .with_mosi(mosi)
+ .with_miso(miso)
+ .with_sio2(sio2)
+ .with_sio3(sio3);Transfer functions
The Spi<'_, SPI, HalfDuplexMode>::read and Spi<'_, SPI, HalfDuplexMode>::write functions have been replaced by
half_duplex_read and half_duplex_write.
let mut data = [0u8; 2];
let transfer = spi
- .read(
+ .half_duplex_read(
SpiDataMode::Single,
Command::Command8(0x90, SpiDataMode::Single),
Address::Address24(0x000000, SpiDataMode::Single),
0,
&mut data,
)
.unwrap();
let transfer = spi
- .write(
+ .half_duplex_write(
SpiDataMode::Quad,
Command::Command8(write as u16, command_data_mode),
Address::Address24(
write as u32 | (write as u32) << 8 | (write as u32) << 16,
SpiDataMode::Quad,
),
0,
dma_tx_buf,
)
.unwrap();Slave-mode SPI
Driver construction
The constructors no longer accept pins. Use the with_pin_name setters instead.
let mut spi = Spi::new(
peripherals.SPI2,
- sclk,
- mosi,
- miso,
- cs,
SpiMode::Mode0,
-);
+)
+.with_sclk(sclk)
+.with_mosi(mosi)
+.with_miso(miso)
+.with_cs(cs);UART event listening
The following functions have been removed:
listen_at_cmdunlisten_at_cmdlisten_tx_doneunlisten_tx_donelisten_rx_fifo_fullunlisten_rx_fifo_fullat_cmd_interrupt_settx_done_interrupt_setrx_fifo_full_interrupt_setreset_at_cmd_interruptreset_tx_done_interruptreset_rx_fifo_full_interrupt
You can now use the UartInterrupt enum and the corresponding listen, unlisten, interrupts and clear_interrupts functions.
Use interrupts in place of <INTERRUPT>_interrupt_set and clear_interrupts in place of the old reset_ functions.
UartInterrupt:
AtCmdTxDoneRxFifoFull
Checking interrupt bits is now done using APIs provided by enumset. For example, to see if
a particular interrupt bit is set, use contains:
-serial.at_cmd_interrupt_set()
+serial.interupts().contains(UartInterrupt::AtCmd)You can now listen/unlisten multiple interrupt bits at once:
-uart0.listen_at_cmd();
-uart0.listen_rx_fifo_full();
+uart0.listen(UartInterrupt::AtCmd | UartConterrupt::RxFifoFull);I2S changes
The I2S driver has been moved to i2s::master
-use esp_hal::i2s::{DataFormat, I2s, Standard};
+use esp_hal::i2s::master::{DataFormat, I2s, Standard};Removed i2s traits
The following traits have been removed:
I2sWriteI2sWriteDmaI2sReadI2sReadDmaI2sWriteDmaAsyncI2sReadDmaAsync
You no longer have to import these to access their respective APIs. If you used these traits
in your functions as generic parameters, you can use the I2s type directly instead.
For example:
-fn foo(i2s: &mut impl I2sWrite) {
+fn foo(i2s: &mut I2s<'_, I2S0, Blocking>) {
// ...
}DMA related changes
Circular DMA transfer's available returns Result<usize, DmaError> now
In case of any error you should drop the transfer and restart it.
loop {
- let avail = transfer.available();
+ let avail = match transfer.available() {
+ Ok(avail) => avail,
+ Err(_) => {
+ core::mem::drop(transfer);
+ transfer = i2s_tx.write_dma_circular(&tx_buffer).unwrap();
+ continue;
+ },
+ };Channel, ChannelRx and ChannelTx types have changed
Channel'sAsync/Blockingmode has been moved before the channel instance parameter.ChannelRxandChannelTxhave gained a newAsync/Blockingmode parameter.
-Channel<'d, DmaChannel0, Async>
+Channel<'d, Async, DmaChannel0>
-ChannelRx<'d, DmaChannel0>
+ChannelRx<'d, Async, DmaChannel0>
-ChannelTx<'d, DmaChannel0>
+ChannelTx<'d, Async, DmaChannel0>Removed peripheral_input and into_peripheral_output from GPIO pin types
Creating peripheral interconnect signals now consume the GPIO pin used for the connection.
The previous signal function have been replaced by split. This change affects the following APIs:
GpioPinAnyPin
-let input_signal = gpioN.peripheral_input();
-let output_signal = gpioN.into_peripheral_output();
+let (input_signal, output_signal) = gpioN.split();into_peripheral_output, split (for output pins only) and peripheral_input have been added to
the GPIO drivers (Input, Output, OutputOpenDrain and Flex) instead.
ETM changes
- The types are no longer prefixed with
GpioEtm,TimerEtmorSysTimerEtm. You can still use
import aliasses in case you ...
0.21.1
0.21.0
Please note that only changes to the esp-hal package are tracked in these release notes.
Migration Guides
esp-hal: https://github.com/esp-rs/esp-hal/blob/v0.21.0/esp-hal/MIGRATING-0.20.mdesp-hal-embassy: https://github.com/esp-rs/esp-hal/blob/v0.21.0/esp-hal-embassy/MIGRATING-0.3.mdesp-wifi: https://github.com/esp-rs/esp-hal/blob/v0.21.0/esp-wifi/MIGRATING-0.9.md
Changelog
Added
- Introduce traits for the DMA buffer objects (#1976, #2213)
- Implement
embedded-haloutput pin traits forNoPin(#2019, #2133) - Added
esp_hal::initto simplify HAL initialisation (#1970, #1999) - Added GpioPin::degrade to create ErasePins easily. Same for AnyPin by accident. (#2075)
- Added missing functions to
Flex:unlisten,is_interrupt_set,wakeup_enable,wait_for_high,wait_for_low,wait_for_rising_edge,wait_for_falling_edge,wait_for_any_edge. (#2075) Flexnow implementsWait. (#2075)- Added sleep and wakeup support for esp32c2 (#1922)
Input,Output,OutputOpenDrainandFlexnow implementPeripheral. (#2094)- Previously unavailable memory is available via
.dram2_uninitsection (#2079) - You can now use
Input,Output,OutputOpenDrainandFlexpins as EXTI and RTCIO wakeup sources (#2095) - Added
Rtc::set_current_timeto allow setting RTC time, andRtc::current_timeto getting RTC time while taking into account boot time (#1883) - Added APIs to allow connecting signals through the GPIO matrix. (#2128)
- Allow I8080 transfers to be cancelled on the spot (#2191)
- Implement
TryFrom<u32>forledc::timer::config::Duty(#1984) - Expose
RtcClock::get_xtal_freqandRtcClock::get_slow_freqpublically for all chips (#2183) - TWAI support for ESP32-H2 (#2199)
- Make
DmaDescriptormethods public (#2237) - Added a way to configure watchdogs in
esp_hal::init(#2180) - Introduce
DmaRxStreamBuf(#2242) - Implement
embedded_hal_async::delay::DelayNsforTIMGxtimers (#2084) - Added
Efuse::read_bit(#2259) - Limited SPI slave support for ESP32 (Modes 1 and 3 only) (#2278)
- Added
Rtc::disable_rom_message_printing(S3 and H2 only) (#2280) - Added
esp_hal::time::{Duration, Instant}(#2304)
Changed
- Bump MSRV to 1.79.0 (#1971)
- Make saving and restoring SHA digest state an explicit operation (#2049)
- Reordered RX-TX pairs in all APIs to be consistent (#2074)
- Make saving and restoring SHA digest state an explicit operation (#2049)
Delay::new()is now aconstfunction (#1999)Input,Output,OutputOpenDrainandFlexare now type-erased by default. Use the newnew_typedconstructor to keep using the ZST pin types. (#2075)- To avoid confusion with the
Rtc::current_timewall clock time APIs, we've renamedesp_hal::time::current_timetoesp_hal::time::now. (#2091) - Renamed
touch::Continoustotouch::Continuous. (#2094) - Faster SHA (#2112)
- The (previously undocumented)
ErasedPinenum has been replaced with theErasedPinstruct. (#2094) - Renamed and merged
Rtc::get_time_usandRtc::get_time_msintoRtc::time_since_boot(#1883) - ESP32: Added support for touch sensing on GPIO32 and 33 (#2109)
- Removed gpio pin generics from I8080 driver type. (#2171)
- I8080 driver now decides bus width at transfer time rather than construction time. (#2171)
- Migrate the I8080 driver to a move based API (#2191)
- Replaced
AnyPinwithInputSignalandOutputSignaland renamedErasedPintoAnyPin(#2128) - Replaced the
ErasedTimerenum with theAnyTimerstruct. (#2144) CameraandAesDmanow support erasing the DMA channel type (#2258)- Changed the parameters of
Spi::with_pinsto no longer be optional (#2133) - Renamed
DummyPintoNoPinand removed all internal logic from it. (#2133) - The
NO_PINconstant has been removed. (#2133) - Allow handling interrupts while trying to lock critical section on multi-core chips. (#2197)
- Migrate
Camerato a move based API (#2242). - Removed the PS-RAM related features, replaced by
quad-psram/octal-psram,init_psramtakes a configuration parameter, it's now possible to auto-detect PS-RAM size (#2178) EspTwaiFrameconstructors now accept any type that converts intoesp_hal::twai::Id(#2207)- Change
DmaTxBufto support PSRAM onesp32s3(#2161) - I2c
transactionis now also available as a inherent function, lift size limit onwrite,readandwrite_read(#2262) - SPI transactions are now cancelled if the transfer object (or async Future) is dropped. (#2216)
- The DMA channel types have been removed from peripherals (#2261)
I2Cdriver renamed toI2c(#2320)
Fixed
- SHA driver can now be safely used in multiple contexts concurrently (#2049)
- Fixed an issue with DMA transfers potentially not waking up the correct async task (#2065)
- Fixed an issue with LCD_CAM i8080 where it would send double the clocks in 16bit mode (#2085)
- Fix i2c embedded-hal transaction (#2028)
- Fix some inconsistencies in DMA interrupt bits (#2169)
- Fix SPI DMA alternating
writeandreadfor ESP32 and ESP32-S2 (#2131) - Fix I2C ending up in a state when only re-creating the peripheral makes it useable again (#2141)
- Fix
SpiBus::transfertransferring data twice in some cases (#2159) - Fixed UART freezing when using
RcFastclock source on ESP32-C2/C3 (#2170) - I2S: on ESP32 and ESP32-S2 data is now output to the right (WS=1) channel first. (#2194)
- SPI: Fixed an issue where unexpected data was written outside of the read buffer (#2179)
- SPI: Fixed an issue where
waithas returned before the DMA has finished writing the memory (#2179) - SPI: Fixed an issue where repeated calls to
dma_transfermay end up looping indefinitely (#2179) - SPI: Fixed an issue that prevented correctly reading the first byte in a transaction (#2179)
- SPI: ESP32: Send address with correct data mode even when no data is sent. (#2231)
- SPI: ESP32: Allow using QSPI mode on SPI3. (#2245)
- PARL_IO: Fixed an issue that caused garbage to be output at the start of some requests (#2211)
- TWAI on ESP32 (#2207)
- TWAI should no longer panic when receiving a non-compliant frame (#2255)
- OneShotTimer: fixed
delay_nanosbehaviour (#2256) - Fixed unsoundness around
Efuse(#2259)
Removed
- Removed
digest::Digestimplementation from SHA (#2049) - Removed
NoPinTypein favour ofDummyPin. (#2068) - Removed the
async,embedded-hal-02,embedded-hal,embedded-io,embedded-io-async, andufmtfeatures (#2070) - Removed the
GpioNtype aliasses. UseGpioPin<N>instead. (#2073) - Removed
Peripherals::take. Useesp_hal::initto obtainPeripherals(#1999) - Removed
AnyInputOnlyPinin favour ofAnyPin. (#2071) - Removed the following functions from
GpioPin:is_high,is_low,set_high,set_low,set_state,is_set_high,is_set_low,toggle. (#2094) - Removed
Rtc::get_time_raw(#1883) - Removed
_with_default_pinsUART constructors (#2132) - Removed transfer methods
send,send_dmaandsend_dma_asyncfromI8080(#2191) - Removed
uart::{DefaultRxPin, DefaultTxPin}(#2132) - Removed
PcntSourceandPcntInputConfig. (#2134) - Removed the
place-spi-driver-in-ramfeature, this is now enabled via esp-config (#2156) - Removed
esp_hal::spi::slave::prelude(#2260) - Removed
esp_hal::spi::slave::WithDmaSpiNtraits (#2260) - The
WithDmaAestrait has been removed (#2261) - The
I2s::new_i2s1constructor has been removed (#2261)