cube2rust 0.0.1

A tool for generating a rust project from a STM32CubeMX ioc file
Documentation
use crate::db::*;
use crate::gpio::*;
use crate::i2c::*;
use crate::rcc::*;
use crate::spi::*;
use crate::usart::*;
use crate::utils::*;
use crate::{Config, MCUFamily};

pub fn generate_main(config: &Config) -> anyhow::Result<String> {
    let hal = match config.mcu_family {
        MCUFamily::STM32F0 => "stm32f0xx_hal",
        _ => todo!("Only STM32F0 supported for now"),
    };

    let mut imports = GeneratedString::new();

    imports.line("#![no_std]");
    imports.line("#![no_main]");
    imports.empty_line();
    imports.line("use crate::hal::{prelude::*, stm32};");
    imports.line("use cortex_m::interrupt;");
    imports.line("use cortex_m_rt::entry;");
    imports.line("use panic_halt as _;");
    imports.line(f!("use {hal} as hal;"));
    imports.empty_line();

    let mut main_func = GeneratedString::new();

    main_func.line("#[entry]");
    main_func.line("fn main() -> ! {");
    main_func.indent_right();

    main_func.line("let mut p = stm32::Peripherals::take().unwrap();");

    add_rcc(&mut main_func, &config);

    add_ports(&mut main_func, &config);

    add_gpios(&mut main_func, &config);

    for spi in config.spis.iter() {
        add_spi(&mut main_func, &mut imports, spi);
    }

    for usart in config.usarts.iter() {
        add_usart(&mut main_func, &mut imports, usart);
    }

    for i2c in config.i2cs.iter() {
        add_i2c(&mut main_func, &mut imports, i2c);
    }

    main_func.line("loop {}");

    main_func.indent_left();
    main_func.line("}");

    Ok(imports.string + "\n" + &main_func.string)
}

fn add_rcc(string: &mut GeneratedString, config: &Config) {
    string.line("let mut rcc = p");
    string.indent_right();
    string.line(".RCC");
    string.line(".configure()");
    match config.rcc.clock_source {
        ClockSource::HSI => {}
        ClockSource::HSI48 => string.line(".hsi48()"),
        ClockSource::HSE(HSEMode::NotBypassed(freq)) => string.line(f!(
            ".hse({freq}.hz(), hal::rcc::HSEBypassMode::NotBypassed)"
        )),
        ClockSource::HSE(HSEMode::Bypassed(freq)) => {
            string.line(f!(".hse({freq}.hz(), hal::rcc::HSEBypassMode::Bypassed)"))
        }
    }

    if let Some(sysclk_freq) = config.rcc.sysclk_freq {
        string.line(f!(".sysclk({sysclk_freq}.hz())"));
    }
    if let Some(hclk_freq) = config.rcc.hclk_freq {
        string.line(f!(".hclk({hclk_freq}.hz())"));
    }
    if let Some(apb1_freq) = config.rcc.apb1_freq {
        string.line(f!(".pclk({apb1_freq}.hz())"));
    }
    string.line(".freeze(&mut p.FLASH);");
    string.indent_left();
    string.empty_line();
}

fn add_ports(string: &mut GeneratedString, config: &Config) {
    for port in config.ports.iter() {
        let port_lower = port.to_ascii_lowercase();

        let gpio_names: Vec<_> = config
            .gpios
            .iter()
            .filter(|gpio| gpio.port.ends_with(*port))
            .map(|gpio| gpio.register.clone())
            .collect();

        let registers: Vec<_> = gpio_names
            .iter()
            .map(|name| f!("_{port_lower}.{name}"))
            .collect();

        let gpio_names = gpio_names.join(", ");
        let registers = registers.join(", ");

        string.line(f!("let _{port_lower} = p.GPIO{port}.split(&mut rcc);"));
        string.line(f!("let ({gpio_names}) = ({registers});"));
    }
    string.empty_line();
}

fn add_gpios(string: &mut GeneratedString, config: &Config) {
    for gpio in config.gpios.iter() {
        let pin_name = gpio.get_name();
        let pin_configuration = configure_gpio(gpio, config.mcu_family);

        let mutable = if !matches!(&gpio.signal, SignalType::Peripheral(_)) {
            "mut "
        } else {
            ""
        };

        string.line(f!("let {mutable}{pin_name} = {pin_configuration};"));
    }

    string.empty_line();
}

fn configure_gpio(gpio: &GpioPin, mcu_family: MCUFamily) -> String {
    let name = gpio.get_name();

    let func = match gpio.signal {
        SignalType::AdcInput => f!("into_analog"),
        SignalType::GpioInput => match gpio.pu_pd.unwrap_or_default() {
            PullType::GPIO_NOPULL => f!("into_floating_input"),
            PullType::GPIO_PULLUP => f!("into_pull_up_input"),
            PullType::GPIO_PULLDOWN => f!("into_pull_down_input"),
        },
        SignalType::GpioOutput => match gpio.mode_default_output_pp.unwrap_or_default() {
            ModeOutputType::GPIO_MODE_OUTPUT_OD => match gpio.speed.unwrap_or_default() {
                SpeedType::GPIO_SPEED_FREQ_LOW => f!("into_open_drain_output"),
                _ => todo!("{} higher speeds for", name),
            },
            ModeOutputType::GPIO_MODE_OUTPUT_PP => match gpio.speed.unwrap_or_default() {
                SpeedType::GPIO_SPEED_FREQ_LOW => f!("into_push_pull_output"),
                SpeedType::GPIO_SPEED_FREQ_MEDIUM => f!("into_push_pull_output_hs"),

                _ => todo!("{} higher speeds for", name),
            },
        },
        SignalType::Peripheral(ref name) => {
            let af = get_alternate_function(mcu_family, gpio, name);
            f!("into_alternate_af{af}")
        }
    };
    f!("interrupt::free(|cs| {gpio.register}.{func}(cs))")
}

fn add_spi(main_func: &mut GeneratedString, imports: &mut GeneratedString, spi: &SPI) {
    let polarity = match spi.polarity.unwrap_or_default() {
        CLKPolarity::SPI_POLARITY_LOW => "IdleLow",
        CLKPolarity::SPI_POLARITY_HIGH => "IdleHigh",
    };

    let phase = match spi.phase.unwrap_or_default() {
        CLKPhase::SPI_PHASE_1EDGE => "CaptureOnFirstTransition",
        CLKPhase::SPI_PHASE_2EDGE => "CaptureOnSecondTransition",
    };

    imports.line("use hal::spi::{Spi, Mode, Phase, Polarity};");

    main_func.line(f!("let mut {spi.name_lower} = Spi::{spi.name_lower}("));
    main_func.indent_right();
    main_func.line(f!("p.{spi.name_upper},"));
    main_func.line(f!(
        "({spi.name_lower}_sck, {spi.name_lower}_miso, {spi.name_lower}_mosi),"
    ));
    main_func.line("Mode {");
    main_func.indent_right();
    main_func.line(f!("polarity: Polarity::{polarity},"));
    main_func.line(f!("phase: Phase::{phase},"));
    main_func.indent_left();
    main_func.line("},");
    main_func.line(f!("{spi.baudrate.0}.hz(),"));
    main_func.line("&mut rcc");
    main_func.indent_left();
    main_func.line(");");
    main_func.empty_line();
}

fn add_usart(main_func: &mut GeneratedString, imports: &mut GeneratedString, usart: &USART) {
    let baudrate = usart.baudrate.unwrap_or(38400);

    imports.line("use hal::serial::Serial;");

    main_func.line(f!(
        "let mut {usart.name_lower} = Serial::{usart.name_lower}("
    ));
    main_func.indent_right();
    main_func.line(f!("p.{usart.name_upper},"));
    main_func.line(f!("({usart.name_lower}_tx, {usart.name_lower}_rx),"));
    main_func.line(f!("{baudrate}.bps(),"));
    main_func.line("&mut rcc");
    main_func.indent_left();
    main_func.line(");");
    main_func.empty_line();
}

fn add_i2c(main_func: &mut GeneratedString, imports: &mut GeneratedString, i2c: &I2C) {
    imports.line("use hal::i2c::I2c;");

    let speed: u32 = match i2c.mode.unwrap_or_default() {
        Mode::I2C_Standard => 100,
        Mode::I2C_Fast => 400,
        Mode::I2C_Fast_Plus => 1000,
    };

    main_func.line(f!("let mut {i2c.name_lower} = I2c::{i2c.name_lower}("));
    main_func.indent_right();
    main_func.line(f!("p.{i2c.name_upper},"));
    main_func.line(f!("({i2c.name_lower}_scl, {i2c.name_lower}_sda),"));
    main_func.line(f!("{speed}.khz(),"));
    main_func.line("&mut rcc");
    main_func.indent_left();
    main_func.line(");");
    main_func.empty_line();
}

pub fn generate_cargo_config(config: &Config) -> String {
    let mut file_content = String::from(
        r#"[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# uncomment ONE of these three option to make `cargo run` start a GDB session
# which option to pick depends on your system
# runner = "arm-none-eabi-gdb -q -x openocd.gdb"
# runner = "gdb-multiarch -q -x openocd.gdb"
# runner = "gdb -q -x openocd.gdb"

rustflags = [
  # LLD (shipped with the Rust toolchain) is used as the default linker
  "-C", "link-arg=-Tlink.x",

  # if you run into problems with LLD switch to the GNU linker by commenting out
  # this line
  # "-C", "linker=arm-none-eabi-ld",

  # if you need to link to pre-compiled C libraries provided by a C toolchain
  # use GCC as the linker by commenting out both lines above and then
  # uncommenting the three lines below
  # "-C", "linker=arm-none-eabi-gcc",
  # "-C", "link-arg=-Wl,-Tlink.x",
  # "-C", "link-arg=-nostartfiles",
]

[build]
"#,
    );

    let target = match config.mcu_family {
        MCUFamily::STM32F0 | MCUFamily::STM32L0 | MCUFamily::STM32G0 => "thumbv6m-none-eabi",
        MCUFamily::STM32F1 | MCUFamily::STM32F2 | MCUFamily::STM32L1 => "thumbv7m-none-eabi",
        MCUFamily::STM32F3 | MCUFamily::STM32F4 => "thumbv7em-none-eabihf",
        _ => todo!("find out if it has FPU"),
    };

    file_content.push_str(&f!("target = \"{target}\"\n"));
    file_content
}

pub fn generate_dependencies(config: &Config) -> anyhow::Result<String> {
    let hal_crate = match config.mcu_family {
        MCUFamily::STM32F0 => "stm32f0xx-hal",
        _ => todo!("other hal crates"),
    };

    let feature = get_feature(config)?;

    let mut filecontent =
        f!("{hal_crate} = {{version = \"*\", features = [\"{feature}\", \"rt\"]}}");

    filecontent.push_str(
        r#"
cortex-m = "*"
cortex-m-rt = "*"
panic-halt = "*"

[profile.dev.package."*"]
# opt-level = "z"

[profile.release]
# lto = true
"#,
    );

    Ok(filecontent)
}

pub fn generate_memory_x(config: &Config) -> anyhow::Result<String> {
    let mem_size = get_mem_size(config);

    Ok(f!("\
MEMORY
{{
  FLASH : ORIGIN = 0x00000000, LENGTH = {mem_size.flash}K
  RAM : ORIGIN = 0x20000000, LENGTH = {mem_size.ram}K
}}
"))
}