From 0cab1563a4537b604e763438db355c6b743ab794 Mon Sep 17 00:00:00 2001 From: Andrei-Tudor Tanase Date: Tue, 30 Jun 2026 16:32:01 +0300 Subject: [PATCH] basic ch1 12bit driver done control register done basic ch1 driver complete basic ch1 driver complete(again) basic ch1 driver complete(again 2) basic ch1 dac driver with ci errors fixed basic ch1 dac driver with ci errors fixed fr this time squashed all intermediary commits Signed-off-by: Andrei-Tudor Tanase added bounds check and documentation Signed-off-by: Andrei-Tudor Tanase fixed clippy Signed-off-by: Andrei-Tudor Tanase Update chips/stm32u5xx/src/dac.rs Co-authored-by: Omer Genan <81963672+genan2003@users.noreply.github.com> Signed-off-by: Andrei-Tudor Tanase added pin comment Signed-off-by: Andrei-Tudor Tanase changed register comments Signed-off-by: Andrei-Tudor Tanase register reserved offsets explained Signed-off-by: Andrei-Tudor Tanase Update chips/stm32u5xx/src/dac.rs changed copyright code Co-authored-by: Omer Genan <81963672+genan2003@users.noreply.github.com> Signed-off-by: Andrei-Tudor Tanase --- boards/nucleo_u545re_q/src/main.rs | 12 ++ chips/stm32u5xx/src/chip.rs | 9 + chips/stm32u5xx/src/dac.rs | 268 +++++++++++++++++++++++++++++ chips/stm32u5xx/src/lib.rs | 1 + chips/stm32u5xx/src/rcc.rs | 35 +++- 5 files changed, 320 insertions(+), 5 deletions(-) create mode 100644 chips/stm32u5xx/src/dac.rs 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); + } }