esp-hal/esp-hal/MIGRATING-0.20.md
Scott Mabin 0142703112
I2c rename, small docs clean up (#2320)
* cleanup prelude docs

* fixups and i2c rename

* changelog and migration

* fixup docs and examples

* fix lint
2024-10-10 07:26:13 +00:00

10 KiB

Migration Guide from 0.20.x to vNext

Cargo Features

A number of trait implementations which were previously feature-gated are now implemented by default. The following Cargo features have been removed:

  • async
  • embedded-hal-02
  • embedded-hal
  • embedded-io
  • embedded-io-async
  • ufmt

If your project enables any of these features, simply remove them from your Cargo manifest and things should continue to work as expected.

HAL Initialisation

Instead of manually grabbing peripherals and setting up clocks, you should now call esp_hal::init.

 use esp_hal::{
-    clock::ClockControl,
-    peripherals::Peripherals,
     prelude::*,
-    system::SystemControl,
 };

 #[entry]
 fn main() -> ! {
-    let peripherals = Peripherals::take();
-    let system = SystemControl::new(peripherals.SYSTEM);
-    let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
+    let peripherals = esp_hal::init(esp_hal::Config::default());

     // ...
 }

GPIO changes

  • The GpioN type aliasses are no longer available. You can use GpioPin<N> instead.
  • The AnyInputOnlyPin has been removed. Replace any use with AnyPin.
  • The NoPinType has been removed. You can use DummyPin in its place.

Type-erased GPIO drivers

The AnyInput, AnyOutput, AnyOutputOpenDrain and AnyFlex structures have been removed. Instead, you can use the non-Any counterparts. By default, the structures will use AnyPin in their type names, which you don't have to specify.

-let pin = AnyInput::new(io.gpio0, Pull::Up);
+let pin = Input::new(io.gpio0, Pull::Up); // pin will have the type `Input<'some>` (or `Input<'some, AnyPin>` if you want to be explicit about it)

You can use new_typed if you want to keep using the typed form:

let pin = Input::new_typed(io.gpio0, Pull::Up); // pin will have the type `Input<'some, GpioPin<0>>`

Creating an AnyPin

Instead of AnyPin::new, you can now use the Pin trait's degrade method to obtain an AnyPin. You can now create an AnyPin out of input only pins (like ESP32's IO34), but trying to use such pins as output (or, generally, trying to use pins in ways they don't support) will panic.

+ use esp_hal::gpio::Pin;

-let pin = AnyPin::new(io.gpio0);
+let pin = io.gpio0.degrade();

Wakeup using pin drivers

You can now use pin drivers as wakeup sources.

use esp_hal::peripheral::Peripheral; // needed for `into_ref`

let pin2 = Input::new(io.pins.gpio2, Pull::None);
let mut pin3 = Input::new(io.pins.gpio3, Pull::None);
// ... use pin2 as an input ..

// Ext0
let ext0 = Ext0WakeupSource::new(pin2, WakeupLevel::High);

// Ext1
let mut wakeup_pins: [&mut dyn RtcPin; 2] = [
    &mut *pin_0.into_ref(),
    &mut io.pins.gpio4, // unconfigured pins continue to work, too!
];
let ext1 = Ext1WakeupSource::new(&mut wakeup_pins, WakeupLevel::High);

RX/TX Order

Previously, our API was pretty inconsistent with the RX/TX ordering, and different peripherals had different order. Now, all the peripherals use rx-tx. Make sure your methods are expecting the right RX/TX order, for example an SPI DMA app should be updated to:

- let (tx_buffer, tx_descriptors, rx_buffer, rx_descriptors) = dma_buffers!(4);
+ let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(4);
let mut dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();

...

 let transfer = spi
-    .dma_transfer(dma_tx_buf, dma_rx_buf)
+    .dma_transfer(dma_rx_buf, dma_tx_buf)
    .map_err(|e| e.0)
    .unwrap();

When using the asymmetric variant of the macro to create DMA buffers and descriptors make sure to swap the order of parameters

- let (tx_buffer, tx_descriptors, _, _) = dma_buffers!(32000, 0);
+ let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, 32000);

Removed constructors

UART

The Uart::new_with_default_pins and Uart::new_async_with_default_pins constructors have been removed. Use new or new_async instead.

I2S1

The I2s::new_i2s1 constructor has been removed. Use I2s::new instead.

Timer changes

ErasedTimer rename

The ErasedTimer has been renamed to AnyTimer.

esp_hal::time::current_time rename

To avoid confusion with the Rtc::current_time wall clock time APIs, we've renamed esp_hal::time::current_time to esp_hal::time::now().

RTC Wall Clock APIs

Instead of the get_time_ms, get_time_us, and get_time_raw functions, the Rtc struct now provides the current_time function, using chrono's NaiveDateTime struct.

let rtc = Rtc::new(peripherals.LPWR);
- let current_time_ms = rtc.get_time_ms();
+ let current_time_ms = rtc.current_time().and_utc().timestamp_millis(); // assuming UTC

PCNT input config

The PcntSource and PcntInputConfig have been removed. You can use Input or Flex instead to configure an input pin, and pass it to set_edge_signal or set_ctrl_signal.

-   let mut pin_a = io.pins.gpio4;
-   ch0.set_ctrl_signal(PcntSource::from_pin(
-       &mut pin_a,
-       PcntInputConfig { pull: Pull::Up },
-   ));
+   ch0.set_ctrl_signal(Input::new(io.pins.gpio4, Pull::Up));

-   let mut pin_b = io.pins.gpio5;
-   ch0.set_edge_signal(PcntSource::from_pin(
-       &mut pin_b,
-       PcntInputConfig { pull: Pull::Down },
-   ));
+   ch0.set_edge_signal(Input::new(io.pins.gpio5, Pull::Down));

SPI pins and NO_PIN

Use NoPin in place of the now-removed NO_PIN constant.

SPI pins, when using the with_pin function, are no longer optional. You can pass NoPin or Level if you don't need a particular pin.

 let spi = Spi::new(peripherals.SPI2, 100.kHz(), SpiMode::Mode0)
-    .with_pins(Some(sclk), Some(mosi), NO_PIN, NO_PIN);
+    .with_pins(sclk, mosi, Level::Low, NoPin);

I8080 type definition

The I8080 driver no longer holds on to pins in its type definition.

- let _: I8080<'a, DmaChannel3, TxEightBits<AnyPin, AnyPin, AnyPin, ....>, Blocking>;
+ let _: I8080<'a, DmaChannel3, Blocking>;

I8080 transfer API changes

  • The I8080 driver now decides bus width at transfer time, which means you don't get type inference anymore.
  • Starting a transfer moves the driver into the transfer object, allowing you to store it in a static or struct.
  • The transfer API no longer works with plain slices, it now works with DmaTxBuffers which allow to bring your own DMA data structure and implement efficient queueing of transfers.
  • The three transfer methods (send, send_dma, send_dma_async) have been merged into one send method.
let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, 32678);
+ let mut dma_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();

let mut i8080 = I8080::new(
    lcd_cam.lcd,
    channel.tx,
-   tx_descriptors,
    tx_pins,
    20.MHz(),
    Config::default(),
)

- i8080.send(0x12, 0, &[0, 1, 2, 3, 4]);
+ dma_buf.fill(&[0, 1, 2, 3, 4]);
+ let transfer = i8080.send(0x12u8, 0, dma_buf).unwrap();
+ // transfer.wait_for_done().await;
+ (_, i8080, dma_buf) = transfer.wait();

Placing drivers in RAM is now done via esp-config

We've replaced some usage of features with esp-config. Please remove any reference to place-spi-driver-in-ram in your Cargo.toml and migrate to the [env] section of .cargo/config.toml.

# feature in Cargo.toml
- esp-hal = { version = "0.20", features = ["place-spi-driver-in-ram"] }
# key in .cargo/config.toml [env] section
+ ESP_HAL_PLACE_SPI_DRIVER_IN_RAM=true

Camera driver now uses DmaRxBuffer and moves the driver into the transfer object.

For one shot transfers.

let (rx_buffer, rx_descriptors, _, _) = dma_buffers!(32678, 0);
+ let dma_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();

let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
let mut camera = Camera::new(
     lcd_cam.cam,
     channel.rx,
-    rx_descriptors,
     data_pins,
     20u32.MHz(),
);

- let transfer = camera.read_dma(rx_buffer).unwrap();
- transfer.wait();
+ let transfer = camera.receive(dma_buf).unwrap();
+ let (_, camera, buf) = transfer.wait();

For circular transfers.

- let (rx_buffer, rx_descriptors, _, _) = dma_buffers!(32678, 0);
+ let dma_buf = dma_rx_stream_buffer!(32678);

let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
let mut camera = Camera::new(
     lcd_cam.cam,
     channel.rx,
-    rx_descriptors,
     data_pins,
     20u32.MHz(),
);

- let mut transfer = camera.read_dma_circular(rx_buffer).unwrap();
+ let mut transfer = camera.receive(dma_buf).unwrap();
transfer.pop(&mut [.....]);
transfer.pop(&mut [.....]);
transfer.pop(&mut [.....]);

PS-RAM

Initializing PS-RAM now takes a chip specific config and returns start of the mapped memory and the size.

Example

let (start, size) = psram::init_psram(peripherals.PSRAM, psram::PsramConfig::default());

If you don't specify the size of PS-RAM via PsramConfig::size the size of PS-RAM is derived from the RAM-chip id (or via probing in case of ESP32).

psram::psram_vaddr_start() and psram::PSRAM_BYTES are removed.

The features psram-Xm and opsram-Xm are removed and replaced by quad-psram/octal-psram. The feature psram-80mhz is removed and replaced by PsramConfig

Diff of the psram_quad.rs example

-//% FEATURES: psram-2m
+//% FEATURES: esp-hal/quad-psram

...

-fn init_psram_heap() {
+fn init_psram_heap(start: *mut u8, size: usize) {
     unsafe {
         esp_alloc::HEAP.add_region(esp_alloc::HeapRegion::new(
-            psram::psram_vaddr_start() as *mut u8,
-            psram::PSRAM_BYTES,
+            start,
+            size,
             esp_alloc::MemoryCapability::External.into(),
         ));
     }

...

-    psram::init_psram(peripherals.PSRAM);
-    init_psram_heap();
+    let (start, size) = psram::init_psram(peripherals.PSRAM, psram::PsramConfig::default());
+    init_psram_heap(start, size);

...

eFuse

Calling Efuse::read_field_le::<bool>() no longer compiles. Use Efuse::read_bit() instead.

DMA

The DMA channel types have been removed from peripherals.

A non-exhausitve list demonstrating this change:

-I2sTx<'static, I2S0, DmaChannel0, Async>
+I2sTx<'static, I2S0, Async>

-SpiDma<'static, esp_hal::peripherals::SPI2, DmaChannel0, HalfDuplexMode, Blocking>
+SpiDma<'static, esp_hal::peripherals::SPI2, HalfDuplexMode, Blocking>

I2C driver renamed to I2c

To firstly match the naming in embedded-hal, but also because types should be UpperCamelCase.