Skip to content

Added DAC with basic features #133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cargo/config
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[target.thumbv7em-none-eabihf]
runner = "arm-none-eabi-gdb"
runner = "arm-none-eabi-gdb -q -x openocd.gdb"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@strom-und-spiele Do you think it is a good idea to add a reference to a file that is not part of this repo?
IMO we probably shouldn't even specify GDB as a runner, as there are other options. But not sure what the intentions were when this was added.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was not intended.

rustflags = [
"-C", "link-arg=-Tlink.x",
]
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- SPI4 peripheral for supported
devices. ([#99](https://github.com/stm32-rs/stm32f3xx-hal/pull/99))
- Support for I2C transfer of more than 255 bytes, and 0 byte write ([#154](https://github.com/stm32-rs/stm32f3xx-hal/pull/154))
- DAC support for `stm32f303` devices ([#133](https://github.com/stm32-rs/stm32f3xx-hal/pull/133))
- Support for HSE bypass and CSS ([#156](https://github.com/stm32-rs/stm32f3xx-hal/pull/156))

### Changed
Expand Down
10 changes: 7 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,20 @@ required-features = ["rt", "stm32f303xc", "stm32-usbd"]

[[example]]
name = "spi"
required-features = ["stm32f303"]
required-features = ["stm32f303xc"]

[[example]]
name = "can"
required-features = ["rt", "can", "stm32f303"]

[[example]]
name = "serial_dma"
required-features = ["stm32f303"]
required-features = ["stm32f303xc"]

[[example]]
name = "adc"
required-features = ["stm32f303"]
required-features = ["stm32f303xc"]

[[example]]
name = "dac"
required-features = ["stm32f303xc"]
73 changes: 73 additions & 0 deletions examples/dac.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#![no_main]
#![no_std]

use panic_semihosting as _;

use cortex_m::asm;

use cortex_m_rt::entry;
use stm32f3xx_hal::{
dac::{Dac, DacBits, Trigger},
pac,
prelude::*,
};

#[entry]
fn main() -> ! {
// Set up microcontroller peripherals
let dp = pac::Peripherals::take().unwrap();

// Set up clocks
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();
let _clocks = rcc.cfgr.freeze(&mut flash.acr);

// Set up the DAC output pin.
// The output pin you choose selects the respective DAC channel:
// - PA4: channel 1
// - PA5: channel 2
let mut gpioa = dp.GPIOA.split(&mut rcc.ahb);
let dac_pin = gpioa.pa4.into_analog(&mut gpioa.moder, &mut gpioa.pupdr);

// Set up the DAC
let reference_voltage = 2.915;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@strom-und-spiele Where is that voltage value coming from?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see you got that from experimentation. Is this a value we can expect to be the same on each user of an F3DISCOVERY? If not, I think we should document how a user can find there own correct value. There must be some way to measure/infer the VREF for a given board, no?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't so sure either and checked the schematics of the stm32f3discovery again.

VREF is a stabilized version VDD.
VDD is directly connected via Jumper 3 to 3V
3V is powered by the 5V supply and adjusted by a BAT60JFILM which has a voltage drop that depends on the power being used, so it's roughly 3V. Less if your chip is running something more demanding.

I suggest

  • replacing the 2.9… by a solid 3
  • add to the docs the info that the ref value should be known by the schematics and can be roughly determined by setting set_value to 255 and measuring the output
  • add a hint to the docs in the example

let mut dac = Dac::new(dp.DAC, dac_pin, DacBits::EightR, reference_voltage);
dac.enable(&mut rcc.apb1);

// primitively wait about one second on a default stm32f3discovery
let wait = 8_000_000;
asm::delay(wait);

// directly set the output voltage to 1.5 V (valid values: 0..=reference_voltage)
dac.set_voltage(1.5);
asm::delay(3 * wait);

// equivalently:
dac.set_value(256 / 2);

// set the output off for 3 seconds
dac.set_value(0);
asm::delay(3 * wait);

// setup a trigger. From now on, setting a value or voltage will be stored but not converted
dac.set_trigger(Trigger::SoftwareTrigger);

// this is never converted:
dac.set_voltage(1.5);
asm::delay(wait);

// toggle the dac value every 2 seconds
loop {
dac.set_value(0);
asm::delay(wait);

dac.trigger_software_trigger();
asm::delay(wait);

dac.set_value(255);
asm::delay(wait);

dac.trigger_software_trigger();
asm::delay(wait);
}
}
190 changes: 190 additions & 0 deletions src/dac.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
//! Configure the internal DAC on the stm32f3xx.
//! Incomplete, but includes basic operation.

use crate::{
gpio::{
gpioa::{PA4, PA5},
Analog,
},
pac,
rcc::APB1,
};
use core::convert::Infallible;

/// Trait representing a single-channel digital-to-analog converter (DAC).
pub trait SingleChannelDac<Word> {
/// Error type returned by DAC methods
type Error;

/// Output a constant signal, given a bit word.
fn try_set_value(&mut self, value: Word) -> Result<(), Self::Error>;
}

/// This is an abstraction to select the correct DAC channel for a given
/// (analog) output pin.
pub trait Pin {
const CHANNEL: Channel;
}

impl Pin for PA4<Analog> {
const CHANNEL: Channel = Channel::One;
}

impl Pin for PA5<Analog> {
const CHANNEL: Channel = Channel::Two;
}

/// Select the channel
#[derive(Clone, Copy)]
pub enum Channel {
/// Channel 1
One,
/// Channel 2
Two,
}

/// Three options are available to set DAC precision.
#[derive(Clone, Copy)]
pub enum DacBits {
/// Eight bit precision, right-aligned.
EightR,
/// 12-bit precision, left-aligned.
TwelveL,
/// 12-bit precision, right-aligned.
TwelveR,
}

/// Select an external event to trigger the DAC.
#[derive(Clone, Copy)]
pub enum Trigger {
Timer2,
Timer3Or8,
Timer4,
Timer6,
Timer7,
Timer15,
Exti9,
SoftwareTrigger,
}

impl Trigger {
fn bits(&self) -> u8 {
match self {
Self::Timer6 => 0b000,
Self::Timer3Or8 => 0b001,
Self::Timer7 => 0b010,
Self::Timer15 => 0b011,
Self::Timer2 => 0b100,
Self::Timer4 => 0b101,
Self::Exti9 => 0b110,
Self::SoftwareTrigger => 0b111,
}
}
}

pub struct Dac<P> {
regs: pac::DAC,
pin: P,
bits: DacBits,
vref: f32,
}

impl<P: Pin> Dac<P> {
const CHANNEL: Channel = P::CHANNEL;

/// Create a new DAC instance
pub fn new(regs: pac::DAC, pin: P, bits: DacBits, vref: f32) -> Self {
Self {
regs,
pin,
bits,
vref,
}
}

/// Destruct the DAC abstraction and give back all owned peripherals.
pub fn free(self) -> (pac::DAC, P) {
(self.regs, self.pin)
}

/// Enable the DAC.
pub fn enable(&mut self, apb1: &mut APB1) {
apb1.enr().modify(|_, w| w.dac1en().enabled());
self.regs.cr.modify(|_, w| match Self::CHANNEL {
Channel::One => w.en1().enabled(),
Channel::Two => w.en2().enabled(),
});
}

/// Disable the DAC
pub fn disable(&mut self, apb1: &mut APB1) {
self.regs.cr.modify(|_, w| match Self::CHANNEL {
Channel::One => w.en1().disabled(),
Channel::Two => w.en2().disabled(),
});
apb1.enr().modify(|_, w| w.dac1en().disabled());
}

/// Set the DAC value as an integer.
pub fn set_value(&mut self, val: u32) {
match Self::CHANNEL {
Channel::One => match self.bits {
DacBits::EightR => self.regs.dhr8r1.modify(|_, w| unsafe { w.bits(val) }),
DacBits::TwelveL => self.regs.dhr12l1.modify(|_, w| unsafe { w.bits(val) }),
DacBits::TwelveR => self.regs.dhr12r1.modify(|_, w| unsafe { w.bits(val) }),
},
Channel::Two => match self.bits {
DacBits::EightR => self.regs.dhr8r2.modify(|_, w| unsafe { w.bits(val) }),
DacBits::TwelveL => self.regs.dhr12l2.modify(|_, w| unsafe { w.bits(val) }),
DacBits::TwelveR => self.regs.dhr12r2.modify(|_, w| unsafe { w.bits(val) }),
},
}
}

/// Set the DAC voltage.
pub fn set_voltage(&mut self, volts: f32) {
let val = match self.bits {
DacBits::EightR => ((volts / self.vref) * 255.) as u32,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there is a better, robust and explicit version (e.g. u32::try_from(...)).
I'm not sure it is needed, though.

DacBits::TwelveL => ((volts / self.vref) * 4_095.) as u32,
DacBits::TwelveR => ((volts / self.vref) * 4_095.) as u32,
};

self.set_value(val);
}

// Select and activate a trigger. See f303 Reference manual, section 16.5.4.
pub fn set_trigger(&mut self, trigger: Trigger) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we need a "get_set_trigger" function to check which trigger is set?
Not really needed, more like a nice to have.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless we have a convincing use-case for it now, I would leave it out for simplicity. We can always add it later if it's actually needed.

self.regs.cr.modify(|_, w| match Self::CHANNEL {
Channel::One => {
w.ten1().enabled();
unsafe { w.tsel1().bits(trigger.bits()) }
}
Channel::Two => {
w.ten2().enabled();
w.tsel2().bits(trigger.bits())
}
});
}

/// Takes the value stored via set_value or set_voltage and converts it to
/// output on the Pin.
///
/// For this to have an effect, you need to first enable the software
/// trigger with `set_trigger`.
pub fn trigger_software_trigger(&mut self) {
self.regs.swtrigr.write(|w| match Self::CHANNEL {
Channel::One => w.swtrig1().enabled(),
Channel::Two => w.swtrig2().enabled(),
});
}
}

impl<P: Pin> SingleChannelDac<u32> for Dac<P> {
type Error = Infallible;

/// Set the DAC value as an integer.
fn try_set_value(&mut self, val: u32) -> Result<(), Infallible> {
self.set_value(val);
Ok(())
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ pub use crate::pac::interrupt;

#[cfg(feature = "stm32f303")]
pub mod adc;
#[cfg(feature = "stm32f303")]
pub mod dac;
#[cfg(feature = "device-selected")]
pub mod delay;
#[cfg(any(feature = "stm32f302", feature = "stm32f303"))]
Expand Down