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
PWM DMA support #350
Comments
I've not heard about such implementation. |
Did you make any progress with this? I also need DMA to PWM output. |
Oh, yer I have quite a nice little motor controler for the one shot protical but I never made any progress in upstreaming it. |
I'm hoping to DMA to PWM duty cycle so that I can drive WS2812 asynchronously since they're vey time sensitive (synchronous timing is getting messed up by my other interrupts). Does that sound like the same sort of situation you overcame with your motors? Have you got some code I can use as a reference? No worries if not! |
I think this is the main bit to get the DMA going for PWM note that i was creating a pulse chain ie, set it off and then have several different pulses all spat out one after each other let c41 = gpiob.pb6.into_alternate_push_pull(&mut gpiob.crl);
let c42 = gpiob.pb7.into_alternate_push_pull(&mut gpiob.crl);
let c43 = gpiob.pb8.into_alternate_push_pull(&mut gpiob.crh);
let c44 = gpiob.pb9.into_alternate_push_pull(&mut gpiob.crh);
// If you don't want to use all channels, just leave some out
// let c4 = gpioa.pa3.into_alternate_push_pull(&mut gpioa.crl);
let pins4 = (c41, c42, c43, c44);
let mut pwm4 =
Timer::tim4(dp.TIM4, &clocks).pwm::<Tim4NoRemap, _, _, _>(pins4, &mut afio.mapr, 50.hz());
let max4 = pwm4.get_max_duty();
pwm4.set_duty(Channel::C1, 0);
pwm4.enable(Channel::C1);
// TIM2
let c1 = gpioa.pa0.into_alternate_push_pull(&mut gpioa.crl);
let c2 = gpioa.pa1.into_alternate_push_pull(&mut gpioa.crl);
let c3 = gpioa.pa2.into_alternate_push_pull(&mut gpioa.crl);
let c4 = gpioa.pa3.into_alternate_push_pull(&mut gpioa.crl);
// If you don't want to use all channels, just leave some out
// let c4 = gpioa.pa3.into_alternate_push_pull(&mut gpioa.crl);
let pins = (c1, c2, c3, c4);
let mut pwm = Timer::tim2(dp.TIM2, &clocks).pwm::<Tim2NoRemap, _, _, _>(
pins,
&mut afio.mapr,
//1_250.khz(),
625.khz(),
);
let max = pwm.get_max_duty();
hprintln!("max {}", max).unwrap();
pwm.set_duty(Channel::C1, 0);
pwm.enable(Channel::C1);
let tim2 = unsafe { &(*pac::TIM2::ptr()) }; // general timer 2 pointer
let rcc_ptr = unsafe { &*pac::RCC::ptr() }; // RCC pointer
let dma1 = unsafe { &*pac::DMA1::ptr() }; // DMA 1 pointer
rcc_ptr.ahbenr.modify(|_, w| w.dma1en().set_bit()); // enable DMA1 clock: peripheral clock enable register
// timer for DMA configuration
tim2.dier.write(|w| w.tde().set_bit()); // enable DMA trigger
tim2.dier.write(|w| w.ude().set_bit()); // enable update DMA request
let _a = &tim2.ccr1 as *const _ as u32; // very different from 0x4000_0034
// DMA configuration
unsafe {
dma1.ch2.par.write(|w| {
w.pa()
.bits(unsafe { &(*pac::TIM2::ptr()).dcr } as *const _ as u32)
});
};
//dma1.cselr.write(|w| w.c2s().bits(0b0100)); // set CxS[3:0] to 0100 to map the DMA request to timer 2 channel 1
//dma1.cselr.write(|w| w.c2s().bits(0b0100)); // set CxS[3:0] to 0100 to map the DMA request to timer 2 channel 1
//dma1.cpar2.write(|w| w.pa().bits(0x4000_0034)); // set the DMA peripheral address register to the capture/compare 1 of TIM2
unsafe {
dma1.ch2.par.write(|w| w.pa().bits(0x4000_0034));
}
//dma1.cmar2.write(|w| w.ma().bits(buffer.as_ptr() as u32)); // write the buffer to the memory adress
unsafe {
dma1.ch2
.mar
.write(|w| w.ma().bits(unsafe { GLOBAL_BUFF.as_ptr() } as u32));
}
//dma1.cndtr2.write(|w| w.ndt().bits(buffer.len() as u16)); // number of data to transfer register
dma1.ch2.ndtr.write(|w| {
w.ndt()
.bits(u16::try_from(unsafe { GLOBAL_BUFF.len() }).unwrap())
});
//dma1.ccr2.modify(|_, w| w
dma1.ch2.cr.modify(
|_, w| {
w.mem2mem()
.clear_bit() // memory-to-memory disabled
.pl()
.high() // set highest priority
.msize()
.bits16() // size in memory of each transfer: b10 = 32 bits long
.psize()
.bits16() // size of peripheral: b10 = 32 bits long --> 32 or 16 ??
.minc()
.set_bit() // memory increment mode enabled
.pinc()
.clear_bit() // peripheral increment mode disabled
.circ()
.clear_bit() //.set_bit() // circular mode: the dma transfer is repeated automatically when finished
.dir()
.set_bit() // data transfer direction: 1 = read from memory
.teie()
.set_bit() // transfer error interrupt enabled
//.htie().set_bit() // half transfer interrupt enabled
.tcie()
.set_bit() // transfer complete interrupt enabled
.en()
.set_bit()
}, // channel enable
); you need something to fill the buffer back up when the dma gets to the end #[interrupt]
fn DMA1_CHANNEL2() {
unsafe { COUNT += 1 };
let dma1 = unsafe { &*pac::DMA1::ptr() };
dma1.ifcr.write(|w| w.cgif2().set_bit());
//clear the interrupt so this function dose not fire until after the next pulse
write_times(unsafe { next_command }, unsafe { need_telem }, unsafe {
&mut GLOBAL_BUFF
});
unsafe { need_telem = 0 };
dma1.ch2.cr.modify(|_, w| w.en().clear_bit());
dma1.ch2.ndtr.write(|w| {
w.ndt()
.bits(cast::u16(unsafe { GLOBAL_BUFF.len() }).unwrap())
});
dma1.ch2.cr.modify(|_, w| w.en().set_bit());
} You will also need some state that your main loop and interupt can access and a function to change it static mut GLOBAL_BUFF: [u16; 66] = [0; 66];
fn write_times(new_command: u16, telm: u16, buffer: &mut [u16]) {
let max = 76;
let one_duty = (max * 125 / 167) as u16;
let zero_duty = (max * 62 / 167) as u16;
let clean_command = new_command;
let mut data: u16 = ((clean_command & 0b11111111111) << 5) | ((telm & 0b1) << 4);
let mut checksum = (data >> 4) ^ (data >> 8) ^ (data >> 12);
//if (telm & 0b1) == 1 {
// checksum = !checksum;
//}
checksum = checksum & 0x0f;
//}
data = data | checksum;
for iii in 0..16 {
let val: u16 = data & (1 << (15 - iii));
if val != 0 {
buffer[iii] = one_duty;
} else {
buffer[iii] = zero_duty;
}
}
} I think a later version of this was a bit less unsafe but you do have to be careful about memory and lock and avoid dead locks etc you have to be careful cos the interrupt could go off at any point in you main loop so you generally need something to co ordinate, can be as simple as turning off interrupts when you update GLOBAL_BUFF or what ever you use handle state shared between the interrupt and the main loop |
Wow, you went above and beyond here. Thanks! Yes, I'll be using a pulse train too. I think my requirements are very similar. I'm using RTIC, so I'll just lean on that for resource sharing and interrupt free zones. Why do you set TIM4 to 50hz early on? Is that just so you can get access to the PWM channel and then DMA with TIM2 takes over? [EDIT - Never mind. I see you've got separate PWMs on TIM2 and 4.] I'll leave you be now. You've given me more than enough to chew on. Thanks again. |
I notice that this repo dose not allow for DMA transfers to the TimX devices.
I recently followed https://stackoverflow.com/questions/63016570/how-to-make-dma-work-for-changing-the-duty-cycle-of-a-pwm-port-using-rust and i seem to have some thing very similar to that example working but for stm32f1, I have tested on a blue pill and get some cool wave forms out.
I will keep working on my example to create wave forms for my use case and will be trying to significantly reduce the unsafe code.
I would like to write this up, ideally as a addition to this lib. but I'm not sure what API to expose to users. I have not seen any other repos in https://github.com/stm32-rs that have DMA for there pwm.rs files other wise i would have just borrowed from one of them.
Is there any resources I could lean on when creating a PR or is there a reason that this functionality dose not already exist?
I notice that spi and other things use DMA but there API's are quite different so have struggled to use them as inspiration but at this point they are the best thing i have.
Many thanks
The text was updated successfully, but these errors were encountered: