STM32F3 Discovery and using USART3

Hi there, I am attempting to leverage USART3 on the STM32F3 Discovery board to communicate with an Adafruit FONA GSM board using simple serial communications. I have not been able to get USART3 to work - at least from what I can tell (i.e. even just direct connected it to my laptop with a terminal program). I am using the stm32f3xx_hal crate.

Was able to get things to work (at least using a terminal app on my laptop) communicating between my laptop and STM32F3 over USART1 which is over the USB ST-LINK hardware interface. This validated that my coding strategy at least for USART1. I have since converted the code to leverage USART3 along with updating the DMA channel numbers and other things to enable USART3 versus USART1. However, I have not been able to get this type of test working with USART3. I'm wondering if I might be needing to configure something that was not necessary when I leveraged USART1 over the USB ST-Link interface. I was hopeful that by updating the appropriate pins, (i.e. pc10, pc11), dma channels (C2, C3) and changing everything from USART1 to USART3 would magically work :slight_smile:.

Here's the code (thank you @ kalkyl for the help!) . BTW, I felt this belongs in Embedded versus RTIC since the RTIC part of the design is working just fine.


#[rtic::app(device = stm32f3xx_hal::pac, peripherals = true, dispatchers = [WWDG])]
mod app {
    use stm32f3xx_hal::{
            dma1::{C2, C3},
            Channel, Event, Transfer,
        serial::{Rx, Tx},
    use panic_semihosting as _;

    const BUF_SIZE: usize = 12;

    pub enum TxTransfer {
        Running(Transfer<&'static mut [u8; BUF_SIZE], C2, Tx<USART3>>),
        Idle(&'static mut [u8; BUF_SIZE], C2, Tx<USART3>),

    struct Shared {
        send: Option<TxTransfer>,

    struct Local {
        recv: Option<Transfer<&'static mut [u8; BUF_SIZE], C3, Rx<USART3>>>,
        gpioe: stm32f3xx_hal::pac::GPIOE,

    #[init(local = [tx_buf: [u8; BUF_SIZE] = [0; BUF_SIZE], rx_buf: [u8; BUF_SIZE] = [0; BUF_SIZE]])]
    fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) {
        ctx.device.RCC.ahbenr.modify(|_, w| w.dma1en().enabled());
        ctx.device.RCC.ahbenr.modify(|_, w| w.iopeen().set_bit());

        let mut rcc = ctx.device.RCC.constrain();
        let mut flash = ctx.device.FLASH.constrain();
        let clocks = rcc.cfgr.freeze(&mut flash.acr);
        // for serial port ops
        let mut gpioc = ctx.device.GPIOC.split(&mut rcc.ahb);

        // for relay ops
        let gpioe = ctx.device.GPIOE;
        gpioe.moder.modify(|_, w| {

        // set the led to on
        gpioe.odr.modify(|_, w| {
        // setup for serial communications
        let pins = (
            gpioc.pc10.into_af7(&mut gpioc.moder, &mut gpioc.afrh),
            gpioc.pc11.into_af7(&mut gpioc.moder, &mut gpioc.afrh), 

        let usart3 = stm32f3xx_hal::serial::Serial::usart3(
            &mut rcc.apb1,

        let (tx, rx) = usart3.split();
        let mut dma1 = ctx.device.DMA1.split(&mut rcc.ahb);
            Shared {
                send: Some(TxTransfer::Idle(ctx.local.tx_buf, dma1.ch2, tx)),
            Local {
                recv: Some(rx.read_exact(ctx.local.rx_buf, dma1.ch3)),

    fn idle(_: idle::Context) -> ! {
        loop {}

    // Triggers on RX transfer completed
    #[task(binds = DMA1_CH5, local = [recv,gpioe], priority = 2)]
    fn on_rx(ctx: on_rx::Context) {
        let (rx_buf, rx_channel, rx) = ctx.local.recv.take().unwrap().wait();

        // set the led to off
        ctx.local.gpioe.odr.modify(|r, w| {
            let led = r.odr15().bit();
                if led {
                } else {

        ctx.local.recv.replace(rx.read_exact(rx_buf, rx_channel));

    #[task(shared = [send], priority = 1, capacity = 4)]
    fn echo(ctx: echo::Context, data: [u8; BUF_SIZE]) {
        //defmt::info!("Received {:?}", data);
        let send = ctx.shared.send;
        let (tx_buf, tx_channel, tx) = match send.take().unwrap() {
            TxTransfer::Idle(buf, ch, tx) => (buf, ch, tx),
            TxTransfer::Running(transfer) => transfer.wait(),
        send.replace(TxTransfer::Running(tx.write_all(tx_buf, tx_channel)));

    // Triggers on TX transfer completed
    #[task(binds = DMA1_CH4, shared = [send], priority = 1)]
    fn on_tx(ctx: on_tx::Context) {
        let send = ctx.shared.send;
        let (tx_buf, tx_channel, tx) = match send.take().unwrap() {
            TxTransfer::Idle(buf, ch, tx) => (buf, ch, tx),
            TxTransfer::Running(transfer) => transfer.wait(),
        //defmt::info!("Sent {:?}", tx_buf);
        send.replace(TxTransfer::Idle(tx_buf, tx_channel, tx));

And here's a picture of my makeshift lab setup (haha):

I'm wondering:

  1. Is there something special that I might have to do with the hal configuration to use USART3 (i.e anything special with the clock config that I did not have to do with USART1)?
  2. Does the HAL even support USART3?
  3. Is my design/thought process all wrong?

Thank you for reading my long post!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.