ãã®è¨äºã¯WMMC Advent Calendar 2024ã®9æ¥ç®ã®è¨äºã§ããæ¨æ¥ã®è¨äºã¯ã¯XFA-27å 輩ã®ããªãããã§ããã
ãã¤ã¯ããã¦ã¹ãä½ã£ã¦ããæ¹ã ã¯STM32ãã¤ã³ã³ã使ç¨ãã¦ããæ¹ãå¤ããæ¸ãè¾¼ã¿ã«STM32CubeProgrammerã使ã£ã¦ããæ¹ãå¤ãã¨æãã¾ããGUIã§ããããããã ãã§æ¸ãè¾¼ã¿ãã§ãã便å©ãªCubeProgrammerã§ããããã£ã¡ã ãã¯ã©ãã·ã¥ãããããã¡ã¤ã«é¸æãé¢åã ã¨æã£ããã¨ã¯ããã¾ãããï¼CubeProgrammerã«ã¯APIããããAPIãéãã¦CubeProgrammerãæä½ããã¢ããªãèªåã§ä½ããã¨ãã§ãããã¨ãã§ãã¾ãããã®è¨äºã§ã¯å®éã«ä½ã£ããã®ã¨ä½ãæ¹ãç´¹ä»ãã¾ãã
ä½ã£ããã®
ä½ã£ãã®ã¯Mbed Studioã¨ããéçºç°å¢ã§ãã«ããããã¤ããªãã¡ã¤ã«ãæ¸ãè¾¼ãããã®ã¢ããªã§ãã
Nucleoã使ç¨ãã¦ããå ´åã¯Mbed Studioä¸ã®æ¸ãè¾¼ã¿ãã¿ã³ã§ç°¡åã«ããã°ã©ã ãæ¸ãè¾¼ããã¨ãã§ãã¾ãããç³åä½ãè¼ããåºæ¿ã«å¥ã§STLinkV3MINIEã®ãããªSTLinkãæ¥ç¶ããã¨Mbed Studioä¸ã§æ¸ãè¾¼ããã¨ãã§ãã¾ãããï¼å¤åï¼
ã¾ããMbed Studioã¯ãã¤ããªãã¡ã¤ã«ã®ãã¹ãããã¸ã§ã¯ãã¾ã§ã®ãã¹/BUILD/ã¿ã¼ã²ããå/ARMC6/ããã¸ã§ã¯ãå.bin
ã®ããã«ãªã£ã¦ãããããããã¸ã§ã¯ãã®ãã¤ããªãã¡ã¤ã«ãæ¸ãè¾¼ãã ãã¨ã«å¥ã®ããã¸ã§ã¯ãã®ãã¤ããªãã¡ã¤ã«ãæ¸ãè¾¼ããã¨ããã¨é層ãä¸ã£ããä¸ãããããå¿
è¦ããããé常ã«é¢åã§ããããã§ããã¸ã§ã¯ãåã¨ã¿ã¼ã²ããåãé¸ã¶ã ãã§æ¸ãè¾¼ããã¢ããªãä½ããã¨ã«ãã¾ããã
ä½ãæ¹
使ç¨ããè¨èªã»ã©ã¤ãã©ãª
ä½ã£ããã®ãæ¥å¸¸çã«ä½¿ããªãã°TUIãªãCUIãªãGUIãªãã§ä½¿ããããã¬ã¯ãå¿ è¦ã«ãªããã¨æãã¾ããèªåã®å ´åã¯ä½¿ãã¨ãã®æ軽ãããããããããä»äººã¸ã®é å¸ã®ãããããéè¦ãã¦GUIã§ä½ããã¨ã«ãã¾ããã 使ç¨ããè¨èªã¯RustãGUIé¨åã«ã¯æµè¡ãã®Tauriã¨Reactã使ç¨ãã¾ãããTauriã¯UIé¨åããã©ã¦ã¶ã®æè¡ã使ç¨ãããã¸ãã¯é¨åãRustã§æ¸ãã¦GUIã¢ããªãä½ããã¨ãã§ããã©ã¤ãã©ãªã§ããé常ãã©ã¦ã¶å´ããã¯PCã®ãã¡ã¤ã«ãä»ã®ããã°ã©ã ã¨ã®éä¿¡ã¸ã®èªç±ãªã¢ã¯ã»ã¹ã¯ããã¾ãããããã®éãRustãã¨ããã¤ãã¨ã§ãªãã§ãã§ããããã«ãªãã¾ããä»åRustã¨Tauriãæ¡ç¨ããçç±ã¯ããããç°¡åã«ããããªè¦ãç®ã§ã¯ãã¹ãã©ãããã©ã¼ã ã»ã·ã³ã°ã«ãã¤ããªãªGUIã¢ããªãä½ããã¨ãã§ããããã§ããåè¨èªã«ã¯GUIç¨ã®ã©ã¤ãã©ãªããããã¨æãã¾ãããOSã®ãã¤ãã£ããªUIã使ããããªã©ã¤ãã©ãªã ã¨ã©ããã¦ãå¤èãè¦ãç®ã«ãªã£ããæã£ããããªé ç½®ã«ããã®ãé£ããã£ããããã¨æãã¾ããããããã¦ã§ãç³»ã®è¨èªã§UIãä½ãã¨ç°¡åã«æã£ããããªè¦ãç®ã«ãããã¨ãã§ãã¾ããè¦ãç®ããããã®ã«ä½¿ç¨ããCSSã¯ã©ã¤ãã©ãªãè±å¯ã§ãã©ã¤ãã©ãªãã¤ã³ãã¼ãããã ãã§è¦ãç®ãããæãã«ãã¦ããããã®ãããããããã¾ããChatGPTãGitHub Copilotã«æ¸ãããæãå ã ãããä¸ã«æ å ±ãå¤ãã®ã§ãã¾ãæ¸ãããããã§ãããï¼å¤åï¼ã
ã¾ããæå¾ã«ä¸çªéè¦ãªCubeProgrammer APIã«ã¢ã¯ã»ã¹ããããã®ã©ã¤ãã©ãªã§ãã wervin/stm32cubeprog-rs: Rust API for STM32CubeProgrammer ãã®ã¾ã¾ã ã¨STLinkãè¦ã¤ãããªãã£ãã¨ãã«panicãèµ·ããã¦ã¢ããªãçµäºãã¦ãã¾ãã®ã§forkãã¦å°ãå¤æ´ãå ãã¦ä½¿ç¨ãã¾ããã Add error handling for no device found in STLink debug connection · Potewo/stm32cubeprog-rs@2dbec0e
ã³ã¼ããæ¸ã
ã©ã¤ãã©ãªãããã£ããå¾ã¯ã©ã¤ãã©ãªã®ãä½æ³ã«æ²¿ã£ã¦ã³ã¼ããæ¸ãã®ã¿ã§ãã
STM32 CubeProgrammer APIã使ç¨ãã¦STLinkããã¤ã¹ããªã¹ãã¢ããããããã¤ããªãã¡ã¤ã«ãæ¸ãè¾¼ãããã®é¢æ°ãä½ãããã¡ã¤ã«ãé¸æããããã®é¢æ°ãä½ãããããã使ã£ãUIå´ã®ã³ã¼ããæ¸ãã°å®æã§ãã
éçºç°å¢ãå
·ä½çãªã³ãã³ãã¯ãã®è¨äºã§ã¯çç¥ãã¾ããTauriã®Getting Startedã®ãã¼ã¸ã®æ¡å
ã«å¾ã£ã¦ã³ãã³ããå®è¡ãã¦ããã¾ããéä¸ã§ä½¿ç¨ããè¨èªãUIãã¬ã¼ã ã¯ã¼ã¯ãèãããã®ã§ã好ã¿ã§çããã°å¤§ä¸å¤«ã§ãã
ããã¸ã§ã¯ããä½ã£ããlib.rsãApp.tsxã以ä¸ã®ããã«ç·¨éãã¾ãã
lib.rs
use tauri_plugin_log::{Target, TargetKind}; // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ #[tauri::command] fn greet(name: &str) -> String { format!("Hello, {}! You've been greeted from Rust!", name) } #[cfg(unix)] fn get_cube_programmer_path() -> String { let home_dir = dirs::home_dir(); let home_dir = match home_dir { Some(home_dir) => home_dir, None => { log::error!("Failed to get home directory"); return String::new(); } }; let binding = home_dir.join("STMicroelectronics/STM32Cube/STM32CubeProgrammer"); let stm32prog_path = binding .to_str(); let stm32prog_path = match stm32prog_path { Some(stm32prog_path) => stm32prog_path, None => { log::error!("Failed to convert STM32CubeProgrammer path to string"); return String::new(); } }; stm32prog_path } #[cfg(windows)] fn get_cube_programmer_path() -> String { return "C:\\Program Files\\STMicroelectronics\\STM32Cube\\STM32CubeProgrammer".to_string(); } #[tauri::command] fn list_stlink_devices() -> Vec<String> { let stm32prog_path = get_cube_programmer_path(); // Load STM32CubeProgmmer API library let stm32prog = match stm32cubeprog_rs::STM32CubeProg::new(stm32prog_path) { Ok(stm32prog) => stm32prog, Err(e) => { log::error!("Failed to load STM32CubeProgrammer API library: {e}"); return Vec::new(); } }; log::info!("STM32CubeProgrammer API library loaded"); // Find connected STLinks let mut stlinks = match stm32prog.discover() { Ok(stlinks) => stlinks, Err(e) => { log::error!("Failed to discover STLinks: {e}"); return Vec::new(); } }; log::info!("STLinks discovered: {}", stlinks.len()); let mut devices = Vec::new(); for stlink in stlinks.iter_mut() { let serial_number = match stlink.serial_number() { Ok(serial_number) => serial_number, Err(e) => { log::error!("Failed to get STLink serial number: {e}"); continue; } }; log::info!("STLink serial number: {}", serial_number); devices.push(serial_number); } log::info!("STLinks found: {:?}", devices); return devices; // let devices = list_stlink_devices(); // devices.iter().map(|d| d.to_string()).collect() } #[tauri::command] fn list_projects() -> Vec<String> { log::info!("Listing projects"); let home_dir = dirs::home_dir(); log::info!("Home directory: {:#?}", home_dir); let home_dir = match home_dir { Some(home_dir) => home_dir, None => { log::error!("Failed to get home directory"); return Vec::new(); } }; log::info!("Home directory: {:#?}", home_dir); let projects_dir = home_dir.join("Mbed Programs"); log::info!("Projects directory: {:#?}", projects_dir); let projects = match std::fs::read_dir(projects_dir) { Ok(projects) => projects, Err(e) => { log::error!("Failed to read projects directory: {}", e); return Vec::new(); } }; log::info!("Projects read"); let mut project_names = Vec::new(); for project in projects { let project = match project { Ok(project) => project, Err(e) => { log::error!("Failed to read project: {}", e); continue; } }; log::info!("Project: {:#?}", project); let project_name = match project.file_name().into_string() { Ok(project_name) => project_name, Err(e) => { log::error!("Failed to convert project name: {:#?}", e); continue; } }; log::info!("Project name: {:#?}", project_name); project_names.push(project_name); } log::info!("Projects found: {:#?}", project_names); project_names } #[tauri::command] fn list_targets(project: &str) -> Vec<String> { let home_dir = dirs::home_dir(); let home_dir = match home_dir { Some(home_dir) => home_dir, None => { log::error!("Failed to get home directory"); return Vec::new(); } }; let targets_dir = home_dir.join("Mbed Programs"); let targets_dir = targets_dir.join(project).join("BUILD"); log::info!("Targets directory: {:#?}", targets_dir); let targets = match std::fs::read_dir(targets_dir) { Ok(targets) => targets, Err(e) => { log::error!("Failed to read targets directory: {}", e); return Vec::new(); } }; let mut target_names = Vec::new(); for target in targets { let target = match target { Ok(target) => target, Err(e) => { log::error!("Failed to read target: {}", e); continue; } }; let target_name = match target.file_name().into_string() { Ok(target_name) => target_name, Err(e) => { log::error!("Failed to convert target name: {:#?}", e); continue; } }; target_names.push(target_name); } target_names } use tauri::ipc::InvokeError; #[derive(Debug, Clone, PartialEq)] enum FlashError { LoadLibraryError(String), ConnectionError(String), DeviceInfoError(String), DownloadError(String), } #[tauri::command] fn write_program(stlink_device: &str, project: &str, target: &str) -> Result<(), FlashError> { let stm32prog_path = get_cube_programmer_path(); // Load STM32CubeProgmmer API library let stm32prog = match stm32cubeprog_rs::STM32CubeProg::new(stm32prog_path) { Ok(stm32prog) => stm32prog, Err(e) => { log::info!("Failed to load STM32CubeProgrammer API library: {}", e); return Err(FlashError::LoadLibraryError(e.to_string())); } }; log::info!("STM32CubeProgrammer API library loaded"); // Find connected STLinks let mut stlinks = match stm32prog.discover() { Ok(stlinks) => stlinks, Err(e) => { let err_msg = format!("Failed to discover STLinks: {}", e); log::error!("{}", err_msg); return Err(FlashError::ConnectionError(err_msg)); } }; log::info!("STLinks discovered: {}", stlinks.len()); let stlink = match stlinks .iter_mut() .find(|stlink| match stlink.serial_number() { Ok(serial) => serial == stlink_device, Err(_) => false, }) { Some(stlink) => stlink, None => { let err_msg = format!("Failed to find STLink with serial number: {}", stlink_device); log::error!("{}", err_msg); return Err(FlashError::ConnectionError(err_msg)); } }; log::info!("STLink found: {}", stlink_device); stlink.set_connection_mode(stm32cubeprog_rs::DebugConnectMode::UnderReset); // Flash target let home_dir = dirs::home_dir(); let home_dir = match home_dir { Some(home_dir) => home_dir, None => { let err_msg = "Failed to get home directory".to_string(); log::error!("{}", err_msg); return Err(FlashError::DownloadError(err_msg)); } }; let target_dir = home_dir.join("Mbed Programs"); let target_dir = target_dir .join(project) .join("BUILD") .join(target) .join("ARMC6"); let target_bin = target_dir.join(format!("{}.bin", project)); log::info!("Target binary: {:#?}", target_bin); let connection_result = stm32prog.connect(stlink); if connection_result.is_err() { if let Err(e) = connection_result { let err_msg = format!("Failed to connect to STLink: {}", e); log::error!("{}", err_msg); return Err(FlashError::ConnectionError(err_msg)); } else { let err_msg = "Failed to connect to STLink".to_string(); log::error!("{}", err_msg); return Err(FlashError::ConnectionError(err_msg)); } } log::info!("Connected to STLink"); let device_info = match stm32prog.device_info() { Ok(device_info) => device_info, Err(e) => { let err_msg = format!("Failed to get device info: {}", e); log::error!("{}", err_msg); return Err(FlashError::DeviceInfoError(err_msg)); } }; log::info!("Device info: {:#?}", device_info); let download_result = stm32prog.download(target_bin, Some(0x08000000), Some(false), Some(true)); if download_result.is_err() { if let Some(e) = download_result.err() { let err_msg = format!("Failed to download binary: {:?}", e); log::error!("{}", err_msg); return Err(FlashError::DownloadError(err_msg)); } else { let err_msg = "Failed to download binary".to_string(); log::error!("{}", err_msg); return Err(FlashError::DownloadError(err_msg)); } } log::info!("Binary downloaded"); let reset_result = stm32prog.reset(stlink); match reset_result { Ok(_) => { log::info!("Reset successful"); }, Err(e) => { let err_msg = format!("Failed to reset: {}", e); log::error!("{}", err_msg); return Err(FlashError::DownloadError(err_msg)); }, }; stm32prog.disconnect(); log::info!("Disconnected from STLink"); return Ok(()); } impl Into<InvokeError> for FlashError { fn into(self) -> InvokeError { InvokeError::from(format!("{:?}", self)) } } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_shell::init()) .plugin( tauri_plugin_log::Builder::new() .target(tauri_plugin_log::Target::new( tauri_plugin_log::TargetKind::LogDir { file_name: Some("logs".to_string()), }, )) .build(), ) .invoke_handler(tauri::generate_handler![ greet, list_stlink_devices, list_projects, list_targets, write_program ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }
App.tsx
import { useState } from "react"; import { invoke } from "@tauri-apps/api/core"; import "./App.css"; type FlashingStatus = "idle" | "flashing" | "success" | "fail"; function FlashingStatusView(props: {status: FlashingStatus, error?: string}) { if (props.status === "idle") { return <span></span> } else if (props.status === "flashing") { return <span>Flashing...</span> } else if (props.status === "success") { return <span>Flashing success</span> } else if (props.status === "fail") { return <> <span>Flashing fail</span> {props.error && <p>{props.error}</p>} </> } else { console.error("Unknown status: ", props.status); return <span>Unknown status: {String(props.status)}</span> } } function App() { const [stlinkDevices, setStlinkDevices] = useState<string[]>([]); const [selectedStlinkDevice, setSelectedStlinkDevice] = useState<string>(""); const [projects, setProjects] = useState<string[]>([]); const [selectedProject, setSelectedProject] = useState<string>(""); const [targets, setTargets] = useState<string[]>([]); const [selectedTarget, setSelectedTarget] = useState<string>(""); const [flashingStatus, setFlashingStatus] = useState<FlashingStatus>("idle"); const [flashingError, setFlashingError] = useState<string>(""); async function listStlinkDevices() { let devices: string[] = await invoke("list_stlink_devices"); setStlinkDevices(devices); if (devices.length > 0 && !devices.includes(selectedStlinkDevice)) { setSelectedStlinkDevice(devices[0]); } } async function listProjects() { let projects: string[] = await invoke("list_projects"); setProjects(projects); if (projects.length > 0 && !projects.includes(selectedProject)) { setSelectedProject(projects[0]); } } async function listTargets() { let targets: string[] = await invoke("list_targets", { project: selectedProject, }); setTargets(targets); if (targets.length > 0 && !targets.includes(selectedTarget)) { setSelectedTarget(targets[0]); } } async function write_program() { setFlashingStatus("flashing"); await invoke("write_program", { stlinkDevice: selectedStlinkDevice, project: selectedProject, target: selectedTarget, }).then(() => { setFlashingError(""); setFlashingStatus("success"); return; }).catch((e) => { console.error(e); setFlashingError(String(e)); setFlashingStatus("fail") return; }); } return ( <main className="container"> <button onClick={listStlinkDevices}>Reload STLinks</button> <label>STLink: <select onChange={ e => setSelectedStlinkDevice(e.target.value) } value={selectedStlinkDevice}> {stlinkDevices.map((device) => ( <option key={device}>{device}</option> ))} </select> </label> <br /> <button onClick={listProjects}>Reload Projects</button> <label>Project: <select onChange={ e => setSelectedProject(e.target.value)} value={selectedProject}> {projects.map((project) => ( <option key={project}>{project}</option> ))} </select> </label> <br /> <button onClick={listTargets}>Reload Targets</button> <label>Target: <select onChange={ e => setSelectedTarget(e.target.value)} value={selectedTarget}> {targets.map((target) => ( <option key={target}>{target}</option> ))} </select> </label> <br /> <button onClick={write_program}>Flash</button> <FlashingStatusView status={flashingStatus} error={flashingError}/> </main> ); } export default App;
ãããã£ã±ã«ã³ã¼ãã解説ããã¨ãrustå´ã®é¢æ°ã«#[tauri::command]
ãã¤ããã¨Tauriãããæãã«ãã£ã¦ããã¦ãã®Rusté¢æ°ãUIãä½ãJavaScriptå´ããå¼ã³åºããããã«ãã¦ããã¾ããRustå´ã¯ã»ã¨ãã©stm32cubeprog-rsã®Exampleã«ã¨ã©ã¼ãã³ããªã³ã°ã追å ãã¦ãããã¤ã¹ã®çºè¦ã¨æ¸ãè¾¼ã¿ã«é¢æ°ãåããæ¸ãè¾¼ããã¤ããªãæ¢ãé¢æ°ã追å ããã ãã§ãã
App.tsx
ã®æ¹ã¯ãããã®é¢æ°ãå¼ã³åºãã¦çµæã表示ããReactã³ã¼ããæ¸ãã ãã§ãã
ã§ãããã®
ãããªè¦ãç®ã®ã¢ããªãã§ãã¾ãã
èµ·åãæ¸ãè¾¼ã¿ãçéã§QoLãä¸ããã¾ãã(STM32CubeProgrammerã£ã¦èµ·åã¡ãã£ã¨é
ãã§ããã...ï¼
Connectã¨Disconnectãèªåã§ããã¾ãã
å¿ç¨
Rustã«ã¯ã¯ãã¹ãã©ãããã©ã¼ã 対å¿ãªã·ãªã¢ã«éä¿¡ãã§ããã©ã¤ãã©ãªãããã®ã§ããããã£ããã®ã使ãã°æ¸ãè¾¼ã¿ã¢ããªã¨ã·ãªã¢ã«ã¢ãã¿ãä¸ä½åããããã¨ãã§ãã¾ãã
æå¾ã«
ããã¾ã§èªãã§ããã ãããããã¨ããããã¾ããæ¥ãã§æ¸ããè¨äºãªã®ã§ã¡ãã£ã¨ãããã«ãããªã£ã¦ãã¾ãã¾ãããå¾ã§æ¸ãç´ããã¨æãã¾ãã
ææ¥ã¯Tanaportå
輩ã®ãã¯ã©ã·ãã¯ã®æ¹è¯æ©ä½ã®è©±ãã§ãã