-
Notifications
You must be signed in to change notification settings - Fork 69
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
Changes from all commits
92067dc
81d8255
0a2d3d3
2f765fa
c5d7c56
f1dc02a
f61c361
3579804
174a94b
676a621
b8e6f56
1d6943a
2eb0773
85e8c5d
fec90c2
5b7b3d4
5da9e77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @strom-und-spiele Where is that voltage value coming from? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. I suggest
|
||
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); | ||
} | ||
} |
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 | ||
teskje marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#[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, | ||
} | ||
David-OConnor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe there is a better, robust and explicit version (e.g. |
||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we need a " There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()) | ||
} | ||
David-OConnor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); | ||
} | ||
|
||
/// 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(()) | ||
} | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was not intended.