diff --git a/boards/nucleo_u545re_q/src/main.rs b/boards/nucleo_u545re_q/src/main.rs index 7f8403b6b2..3a3c8bdd42 100644 --- a/boards/nucleo_u545re_q/src/main.rs +++ b/boards/nucleo_u545re_q/src/main.rs @@ -52,6 +52,7 @@ struct NucleoU545RE { stm32u545::tim::Tim2<'static>, >, >, + dac: &'static capsules_extra::dac::Dac<'static>, } impl SyscallDriverLookup for NucleoU545RE { @@ -64,6 +65,7 @@ impl SyscallDriverLookup for NucleoU545RE { capsules_core::led::DRIVER_NUM => f(Some(self.led)), capsules_core::button::DRIVER_NUM => f(Some(self.button)), capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_extra::dac::DRIVER_NUM => f(Some(self.dac)), _ => f(None), } } @@ -122,6 +124,12 @@ unsafe fn set_pin_primary_functions(periphs: &stm32u545::chip::Stm32u5xxDefaultP let btn = periphs.gpio_c.pin(PinId::Pin13); btn.make_input(); btn.set_floating_state(kernel::hil::gpio::FloatingState::PullDown); + + //DAC pin (PA4) A2 on the board + periphs + .gpio_a + .pin(PinId::Pin04) + .set_mode(stm32u545::gpio::Mode::Analog); } #[inline(never)] @@ -233,6 +241,9 @@ unsafe fn start() -> ( ) .finalize(components::button_component_static!(stm32u545::gpio::Pin)); + let dac = components::dac::DacComponent::new(&periphs.dac) + .finalize(components::dac_component_static!()); + // Platform and Interrupts let platform = static_init!( NucleoU545RE, @@ -244,6 +255,7 @@ unsafe fn start() -> ( led, button, alarm, + dac, } ); diff --git a/chips/stm32u5xx/src/chip.rs b/chips/stm32u5xx/src/chip.rs index 1b079ec031..55c56f3f28 100644 --- a/chips/stm32u5xx/src/chip.rs +++ b/chips/stm32u5xx/src/chip.rs @@ -3,6 +3,7 @@ // Copyright Tock Contributors 2024. // Copyright OxidOS Automotive 2026. +use crate::dac; use crate::dma::{ChannelId, Dma}; use crate::exti; use crate::gpio; @@ -34,6 +35,7 @@ pub struct Stm32u5xxDefaultPeripherals<'a> { pub dma1: &'a Dma, pub gpio_a: gpio::Port<'a>, pub gpio_c: gpio::Port<'a>, + pub dac: dac::Dac, } fn enable_tim2_clock() { @@ -41,6 +43,11 @@ fn enable_tim2_clock() { rcc.enable_tim2(); } +fn enable_dac1_clock() { + let rcc = rcc::Rcc::new(rcc::RCC_BASE); + rcc.enable_dac1(); +} + impl<'a> Stm32u5xxDefaultPeripherals<'a> { pub fn new(usart1: &'a usart::Usart<'a>, exti: &'a exti::Exti<'a>, dma1: &'a Dma) -> Self { Self { @@ -51,6 +58,7 @@ impl<'a> Stm32u5xxDefaultPeripherals<'a> { dma1, gpio_a: gpio::Port::new(gpio::GPIO_A_BASE, exti, gpio::GpioPort::PortA), gpio_c: gpio::Port::new(gpio::GPIO_C_BASE, exti, gpio::GpioPort::PortC), + dac: dac::Dac::new(dac::DAC_BASE, enable_dac1_clock), } } @@ -62,6 +70,7 @@ impl<'a> Stm32u5xxDefaultPeripherals<'a> { self.rcc.enable_usart1(); self.rcc.enable_syscfg(); self.rcc.set_usart1_source_pclk(); + self.rcc.enable_dac1(); // Link DMA to USART1 let usart1_channel_tx = self.dma1.request_channel(); let usart1_channel_rx = self.dma1.request_channel(); diff --git a/chips/stm32u5xx/src/dac.rs b/chips/stm32u5xx/src/dac.rs new file mode 100644 index 0000000000..556d7b488d --- /dev/null +++ b/chips/stm32u5xx/src/dac.rs @@ -0,0 +1,268 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2026. + +use core::cell::Cell; +use kernel::hil::dac::DacChannel; +use kernel::utilities::registers::interfaces::{ReadWriteable, Writeable}; +use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; +use kernel::utilities::StaticRef; +use kernel::ErrorCode; + +register_structs! { + pub DacRegisters { + /// control register + (0x000 => cr: ReadWrite), + /// software trigger register + (0x004 => swtrgr: ReadWrite), + /// channel1 12-bit right-aligned data holding register + (0x008 => dhr12r1: ReadWrite), + /// channel1 12-bit left-aligned data holding register + (0x00C => dhr12l1: ReadWrite), + /// channel1 8-bit right-aligned data holding register + (0x010 => dhr8r1: ReadWrite), + /// channel2 12-bit right-aligned data holding register + (0x014 => dhr12r2: ReadWrite), + /// channel2 12-bit left-aligned data holding register + (0x018 => dhr12l2: ReadWrite), + /// channel2 8-bit right-aligned data holding register + (0x01C => dhr8r2: ReadWrite), + /// dual dac 12-bit right-aligned data holding register + (0x020 => dhr12rd: ReadWrite), + /// dual dac 12-bit left-aligned data holding register + (0x024 => dhr12ld: ReadWrite), + /// dual dac 8-bit right-aligned data holding register + (0x028 => dhr8rd: ReadWrite), + /// channel1 data output register + (0x02C => dor1: ReadWrite), + /// channel2 data output register + (0x030 => dor2: ReadWrite), + /// status register + (0x034 => sr: ReadWrite), + /// calibration control register + (0x038 => ccr: ReadWrite), + /// mode control register + (0x03C => mcr: ReadWrite), + /// channel1 sample and hold sample time register + (0x040 => shsr1: ReadWrite), + /// channel2 sample and hold sample time register + (0x044 => shsr2: ReadWrite), + /// sample and hold time register + (0x048 => shhr: ReadWrite), + /// sample and hold refresh time register + (0x04C => shrr: ReadWrite), + /// 0x050 reserved + (0x050 => _reserved0), + /// autonomous mode control register + (0x054 => autocr: ReadWrite), + (0x058 => @END), + } +} + +register_bitfields! [u32, + pub CR [ + /// channel1 enable + EN1 OFFSET(0) NUMBITS(1) [], + /// channel1 trigger enable + TEN1 OFFSET(1) NUMBITS(1) [], + /// channel1 trigger selection + TSEL1 OFFSET(2) NUMBITS(4) [ + SWTRIG1 = 0, + dac_ch1_trg1 = 1, + dac_ch1_trg2 = 2, + dac_ch1_trg3 = 3, + dac_ch1_trg4 = 4, + dac_ch1_trg5 = 5, + dac_ch1_trg6 = 6, + dac_ch1_trg7 = 7, + dac_ch1_trg8 = 8, + dac_ch1_trg9 = 9, + dac_ch1_trg10 = 10, + dac_ch1_trg11 = 11, + dac_ch1_trg12 = 12, + dac_ch1_trg13 = 13, + dac_ch1_trg14 = 14, + dac_ch1_trg15 = 15, + ], + /// channel1 noise/triangle wave generation + WAVE1 OFFSET(6) NUMBITS(2) [ + disabled = 0, + noise_wave = 1, + triangle_wave = 2 + ], + /// mask in noise wave generation mode or ampl in triangle gen mode + MAMP1 OFFSET(8) NUMBITS(4) [ + /// unmask bit0 of LFSR / triangle amplitude 1 + AMP1 = 0, + /// unmask bits[1:0] of LFSR / triangle amplitude 3 + AMP3 = 1, + /// unmask bits[2:0] of LFSR / triangle amplitude 7 + AMP7 = 2, + /// unmask bits[3:0] of LFSR / triangle amplitude 15 + AMP15 = 3, + /// unmask bits[4:0] of LFSR / triangle amplitude 31 + AMP31 = 4, + /// unmask bits[5:0] of LFSR / triangle amplitude 63 + AMP63 = 5, + /// unmask bits[6:0] of LFSR / triangle amplitude 127 + AMP127 = 6, + /// unmask bits[7:0] of LFSR / triangle amplitude 255 + AMP255 = 7, + /// unmask bits[8:0] of LFSR / triangle amplitude 511 + AMP511 = 8, + /// unmask bits[9:0] of LFSR / triangle amplitude 1023 + AMP1023 = 9, + /// unmask bits[10:0] of LFSR / triangle amplitude 2047 + AMP2047 = 10, + /// unmask bits[11:0] of LFSR / triangle amplitude 4095 (>= 1011) + AMP4095 = 11, + ], + /// channel 1 dma enable + DMAEN1 OFFSET(12) NUMBITS(1) [], + /// channel1 DMA underrun interrupt enable + DMAUDRIE1 OFFSET(13) NUMBITS(1) [], + /// channel1 calibration enable + CEN1 OFFSET(14) NUMBITS(1) [], + /// channel2 enable + EN2 OFFSET(16) NUMBITS(1) [], + /// channel2 trigger enable + TEN2 OFFSET(17) NUMBITS(1) [], + /// channel2 trigger selection + TSEL2 OFFSET(18) NUMBITS(4) [ + SWTRIG2 = 0, + dac_ch2_trg1=1, + dac_ch2_trg2=2, + dac_ch2_trg3=3, + dac_ch2_trg4=4, + dac_ch2_trg5=5, + dac_ch2_trg6=6, + dac_ch2_trg7=7, + dac_ch2_trg8=8, + dac_ch2_trg9=9, + dac_ch2_trg10=10, + dac_ch2_trg11=11, + dac_ch2_trg12=12, + dac_ch2_trg13=13, + dac_ch2_trg14=14, + dac_ch2_trg15=15, + ], + /// channel2 noise/triangle wave generation + WAVE2 OFFSET(22) NUMBITS(2) [ + disabled=0, + noise_wave=1, + triangle_wave=2 + ], + /// mask in noise wave generation mode or ampl in triangle gen mode + MAMP2 OFFSET(24) NUMBITS(4) [ + /// unmask bit0 of LFSR / triangle amplitude 1 + AMP1 = 0, + /// unmask bits[1:0] of LFSR / triangle amplitude 3 + AMP3 = 1, + /// unmask bits[2:0] of LFSR / triangle amplitude 7 + AMP7 = 2, + /// unmask bits[3:0] of LFSR / triangle amplitude 15 + AMP15 = 3, + /// unmask bits[4:0] of LFSR / triangle amplitude 31 + AMP31 = 4, + /// unmask bits[5:0] of LFSR / triangle amplitude 63 + AMP63 = 5, + /// unmask bits[6:0] of LFSR / triangle amplitude 127 + AMP127 = 6, + /// unmask bits[7:0] of LFSR / triangle amplitude 255 + AMP255 = 7, + /// unmask bits[8:0] of LFSR / triangle amplitude 511 + AMP511 = 8, + /// unmask bits[9:0] of LFSR / triangle amplitude 1023 + AMP1023 = 9, + /// unmask bits[10:0] of LFSR / triangle amplitude 2047 + AMP2047 = 10, + /// unmask bits[11:0] of LFSR / triangle amplitude 4095 (>= 1011) + AMP4095 = 11, + ], + /// dma channel2 enable + DMAEN2 OFFSET(28) NUMBITS(1) [], + /// ch2 dma underrun interrupt enable + DMAUDRIE2 OFFSET(29) NUMBITS(1) [], + /// ch2 calibration enable + CEN2 OFFSET(30) NUMBITS(1) [] + ], + /// mode control register + pub MCR [ + /// dac ch1 mode + MODE1 OFFSET(0) NUMBITS(3) [ + /// CH1 in normal mode + EXT_PIN_BUF_EN = 0, + EXT_PIN_PERI_BUF_EN = 1, + EXT_PIN_BUF_NEN = 2, + PERI_BUF_NEN = 3, + /// CH1 in sample and hold + SH_EXT_PIN_BUF_EN = 4, + SH_EXT_PIN_PERI_BUF_EN = 5, + SH_EXT_PIN_PERI_BUF_NEN = 6, + SH_PERI_BUF_NEN = 7 + ], + /// ch1 dma double mode + DMADOUBLE1 OFFSET(8) NUMBITS(1) [], + /// signed formar for ch1 + SINFORMAT1 OFFSET(9) NUMBITS(1) [], + HFSET OFFSET(14) NUMBITS(2) [ + disabled = 0, + enabled_ahb_80 = 1, + enabled_ahb_160 = 2 + ] + ] +]; + +pub const DAC_BASE: StaticRef = + unsafe { StaticRef::new(0x4602_1800 as *const DacRegisters) }; + +/// The DAC takes in an arbitrary 12 bit number and outputs a voltage proportional to it. +pub struct Dac { + registers: StaticRef, + enable_clock: fn(), + initialized: Cell, +} + +impl Dac { + /// Creates a new instance of the driver. + /// + /// - `base`: The StaticRef pointing to the MMIO base address of the peripheral. + /// - `enable_clock`: A callback function to provide the peripheral clock via RCC. + /// - `initialized``: Bool cell that tracks whether the DAC has been initialized yet. + pub fn new(base: StaticRef, enable_clock: fn()) -> Self { + Self { + registers: base, + enable_clock, + initialized: Cell::new(false), + } + } + + fn enable_clock(&self) { + (self.enable_clock)(); + } + + /// Initialization function that only gets called on first set_value write. + /// Because the HIL only exposes the set_value function, we have to store the initialization state as a bool to make sure we only initialize once. + /// Configures the MODE1 register and then enables CH1 of the DAC. MODE1 is set for clarity as it set to 0 regardless and that's what we need. + fn initialize(&self) { + self.enable_clock(); + self.registers.mcr.modify(MCR::MODE1::EXT_PIN_BUF_EN); + self.registers.cr.modify(CR::EN1::SET); + self.initialized.set(true); + } +} + +impl DacChannel for Dac { + /// Bound checks the value and if ok, writes it to the 12-bit right aligned DAC register after padding. + fn set_value(&self, value: usize) -> Result<(), ErrorCode> { + if !self.initialized.get() { + self.initialize(); + } + if value > 0xFFF { + Err(ErrorCode::FAIL) + } else { + self.registers.dhr12r1.set((value as u32) & 0xFFF); + Ok(()) + } + } +} diff --git a/chips/stm32u5xx/src/lib.rs b/chips/stm32u5xx/src/lib.rs index f9bc0e4651..5714292828 100644 --- a/chips/stm32u5xx/src/lib.rs +++ b/chips/stm32u5xx/src/lib.rs @@ -5,6 +5,7 @@ #![no_std] pub mod chip; +pub mod dac; pub mod dma; pub mod exti; pub mod gpio; diff --git a/chips/stm32u5xx/src/rcc.rs b/chips/stm32u5xx/src/rcc.rs index aec1859559..8fc4eab964 100644 --- a/chips/stm32u5xx/src/rcc.rs +++ b/chips/stm32u5xx/src/rcc.rs @@ -16,18 +16,22 @@ register_structs! { (0x088 => ahb1enr: ReadWrite), /// AHB2 peripheral clock enable register 1 (0x08C => ahb2enr1: ReadWrite), - (0x090 => _reserved1: [u32; 3]), + (0x090 => _reserved1: [u32; 1]), //this would be AHB2ENR2, but unused for now + (0x94 => ahb3enr: ReadWrite), + (0x98 => _reserved4: [u32; 1]), //just padding /// APB1 peripheral clock enable register 1 (0x09C => apb1enr1: ReadWrite), - (0x0A0 => _reserved2: [u32; 1]), + (0x0A0 => _reserved2: [u32; 1]), //this would be APB1ENR2, but unused for now /// APB2 peripheral clock enable register (0x0A4 => apb2enr: ReadWrite), /// APB3 peripheral clock enable register (0x0A8 => apb3enr: ReadWrite), - (0x0AC => _reserved3: [u32; 13]), + (0x0AC => _reserved3: [u32; 13]), //this is for padding /// Peripherals independent clock configuration register 1 (0x0E0 => ccipr1: ReadWrite), - (0x0E4 => @END), + (0x0E4 => ccipr2: ReadWrite), + (0x0E8 => ccipr3: ReadWrite), + (0x0EC => @END), } } @@ -63,7 +67,24 @@ register_bitfields![u32, HSI16 = 2, LSE = 3 ] - ] + ], + pub CCIPR3 [ + ADCDACSEL OFFSET(12) NUMBITS(3) [ + HCLK = 0, + SYSCLK = 1, + PLL2_R_CK = 2, + HSE = 3, + HSI16 = 4, + MSIK = 5 + ], + DAC1SEL OFFSET(15) NUMBITS(1) [ + LSE = 0, + LSI = 1 + ] + ], + pub AHB3ENR [ + DAC1EN OFFSET(6) NUMBITS(1) [] + ], ]; /// Base address for RCC in Secure mode. @@ -106,4 +127,8 @@ impl Rcc { pub fn set_usart1_source_pclk(&self) { self.registers.ccipr1.modify(CCIPR1::USART1SEL::PCLK); } + + pub fn enable_dac1(&self) { + self.registers.ahb3enr.modify(AHB3ENR::DAC1EN::SET); + } }