From 1ef275a52093ec08eb02477a172b229b56b3ce8c Mon Sep 17 00:00:00 2001 From: Rahix Date: Sun, 3 Nov 2024 13:54:30 +0100 Subject: [PATCH] Implement async fieldbus driver Update the fieldbus in the background so the bus cycle is not affected by the application cycle. --- src/fieldbus.rs | 211 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 130 ++++------------------------- 2 files changed, 225 insertions(+), 116 deletions(-) create mode 100644 src/fieldbus.rs diff --git a/src/fieldbus.rs b/src/fieldbus.rs new file mode 100644 index 0000000..5f71ce2 --- /dev/null +++ b/src/fieldbus.rs @@ -0,0 +1,211 @@ +use std::sync::{Arc, Mutex}; + +use profirust::dp; +use profirust::fdl; +use profirust::phy; + +pub use dp::OperatingState; + +// Bus Parameters +const MASTER_ADDRESS: u8 = 3; +const BUS_DEVICE: &'static str = "/dev/ttyAMA0"; +const BAUDRATE: profirust::Baudrate = profirust::Baudrate::B500000; + +// Peripheral Addresses +const RIO_ADDRESS: u8 = 42; + +pub const PIQ_SIZE: usize = 256; +pub const PII_SIZE: usize = 256; + +#[derive(Debug)] +pub struct Fieldbus { + inner: Arc>, +} + +#[derive(Debug)] +struct FieldbusInner { + state: OperatingState, + is_online: bool, + pii: [u8; PII_SIZE], + piq: [u8; PIQ_SIZE], +} + +impl Default for FieldbusInner { + fn default() -> Self { + Self { + state: OperatingState::Stop, + is_online: false, + pii: [0u8; PII_SIZE], + piq: [0u8; PIQ_SIZE], + } + } +} + +impl Fieldbus { + pub fn new() -> Self { + let inner: Arc> = Default::default(); + std::thread::spawn({ + let inner = inner.clone(); + move || { + fieldbus_task(inner); + } + }); + Self { inner } + } + + pub fn enter_state(&mut self, state: OperatingState) { + self.inner.lock().unwrap().state = state; + } + + pub fn is_online(&self) -> bool { + self.inner.lock().unwrap().is_online + } + + pub fn update_process_images(&mut self, pii: &mut [u8; PII_SIZE], piq: &[u8; PIQ_SIZE]) { + let mut data = self.inner.lock().unwrap(); + pii.copy_from_slice(&data.pii); + data.piq.copy_from_slice(piq); + } +} + +struct PeripheralInfo { + pub handle: dp::PeripheralHandle, + pub pii_offset: usize, + pub piq_offset: usize, + pub liveness_bit: (usize, u8), +} + +fn fieldbus_task(fieldbus_data: Arc>) { + let mut dp_master = dp::DpMaster::new(vec![]); + let mut peripherals: Vec = Default::default(); + + // Options generated by `gsdtool` using "B751_P39.GSD" + let options = profirust::dp::PeripheralOptions { + // "WAGO 750-303 (FW: 01 ... 06) PRO" by "WAGO Kontakttechnik GmbH" + ident_number: 0xb751, + + // Global Parameters: + // - Register-Interface.............: is not used + // - RESET on terminalbus failure...: POWER ON RESET + // - Terminalbus diagnostics........: disabled + // - Evaluation of Clear_Data.......: enabled + // - Diagnostics of binary modules..: is not mapped into PI + // - Extended PI-Update.............: according to PI-Update + // - Evaluation of complex modules..: process data only + // - Data format....................: MOTOROLA + // - Process image update...........: free running + // - Response to PROFIBUS failure...: Output image is cleared + // - Response to terminalbus failure: PROFIBUS communication stops + // - Maximum length of diagnostics..: 16 Byte + // + // Selected Modules: + // [0] 8 Bit binary inputs + // [1] 8 Bit binary outputs + user_parameters: Some(&[ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x0a, + 0x00, + ]), + config: Some(&[0x10, 0x20]), + + // Set max_tsdr depending on baudrate and assert + // that a supported baudrate is used. + max_tsdr: match BAUDRATE { + profirust::Baudrate::B9600 => 60, + profirust::Baudrate::B19200 => 60, + profirust::Baudrate::B93750 => 60, + profirust::Baudrate::B187500 => 60, + profirust::Baudrate::B500000 => 100, + profirust::Baudrate::B1500000 => 150, + profirust::Baudrate::B3000000 => 250, + profirust::Baudrate::B6000000 => 350, + profirust::Baudrate::B12000000 => 550, + b => panic!( + "Peripheral \"WAGO 750-303 (FW: 01 ... 06) PRO\" does not support baudrate {b:?}!" + ), + }, + + fail_safe: false, + ..Default::default() + }; + let mut buffer_inputs = [0u8; 1]; + let mut buffer_outputs = [0u8; 1]; + let mut buffer_diagnostics = [0u8; 64]; + + let handle = dp_master.add( + dp::Peripheral::new( + RIO_ADDRESS, + options, + &mut buffer_inputs, + &mut buffer_outputs, + ) + .with_diag_buffer(&mut buffer_diagnostics), + ); + + peripherals.push(PeripheralInfo { + handle, + pii_offset: 0, + piq_offset: 0, + liveness_bit: (64, 0), + }); + + let mut fdl = fdl::FdlActiveStation::new( + fdl::ParametersBuilder::new(MASTER_ADDRESS, BAUDRATE) + // We use a rather large T_slot time because USB-RS485 converters + // can induce large delays at times. + .slot_bits(2500) + .watchdog_timeout(profirust::time::Duration::from_secs(2)) + .build_verified(&dp_master), + ); + + // We must not poll() too often or to little. T_slot / 2 seems to be a good compromise. + let sleep_time: std::time::Duration = (fdl.parameters().slot_time() / 2).into(); + + let mut phy = phy::LinuxRs485Phy::new(BUS_DEVICE, fdl.parameters().baudrate); + + fdl.set_online(); + + loop { + let now = profirust::time::Instant::now(); + let events = fdl.poll(now, &mut phy, &mut dp_master); + + { + let mut data = fieldbus_data.lock().unwrap(); + + if dp_master.operating_state() != data.state { + dp_master.enter_state(data.state); + } + + data.is_online = fdl.is_in_ring(); + + if events.cycle_completed { + for peripheral_info in peripherals.iter() { + let peripheral = dp_master.get_mut(peripheral_info.handle); + + if peripheral.is_running() { + data.pii[peripheral_info.liveness_bit.0] |= + 1 << peripheral_info.liveness_bit.1; + } else { + data.pii[peripheral_info.liveness_bit.0] &= + !(1 << peripheral_info.liveness_bit.1); + } + + if peripheral.is_running() { + { + let pii = peripheral.pi_i(); + data.pii[peripheral_info.pii_offset..][..pii.len()] + .copy_from_slice(pii); + } + { + let piq = peripheral.pi_q_mut(); + piq.copy_from_slice( + &data.piq[peripheral_info.piq_offset..][..piq.len()], + ); + } + } + } + } + } + + std::thread::sleep(sleep_time); + } +} diff --git a/src/main.rs b/src/main.rs index 89c44c7..773ae77 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,13 @@ use process_image as pi; -use profirust::dp; -use profirust::fdl; -use profirust::phy; - mod display; - -// Bus Parameters -const MASTER_ADDRESS: u8 = 3; -const BUS_DEVICE: &'static str = "/dev/ttyAMA0"; -const BAUDRATE: profirust::Baudrate = profirust::Baudrate::B500000; - -// Device Addresses -const RIO_ADDRESS: u8 = 42; +mod fieldbus; // Parameters const CYCLE_TIME: u64 = 5000; pi::process_image! { - struct Pii: 1024 { + struct Pii: 256 { pub bgb1: (X, 0, 0), // Left Limit Switch pub bgb2: (X, 0, 1), // Right Limit Switch pub bpb1: (X, 0, 2), // Pressure > 1.5 barg @@ -31,7 +20,7 @@ pi::process_image! { } pi::process_image! { - struct mut PiqMut: 1024 { + struct mut PiqMut: 256 { pub mbb1: (X, 0, 0), // Coil for turning cylinder to the left pub mbb2: (X, 0, 1), // Coil for turning cylinder to the right pub pza1_white: (X, 0, 2), // White status indicator light @@ -112,114 +101,23 @@ fn main() { let mut display = display::Display::new(); display.update_line(0, &format!("{:^20}", "torque tester")); - let mut dp_master = dp::DpMaster::new(vec![]); + let mut fieldbus = fieldbus::Fieldbus::new(); - // Options generated by `gsdtool` using "B751_P39.GSD" - let options = profirust::dp::PeripheralOptions { - // "WAGO 750-303 (FW: 01 ... 06) PRO" by "WAGO Kontakttechnik GmbH" - ident_number: 0xb751, + let mut pii = [0u8; fieldbus::PII_SIZE]; + let mut piq = [0u8; fieldbus::PIQ_SIZE]; - // Global Parameters: - // - Register-Interface.............: is not used - // - RESET on terminalbus failure...: POWER ON RESET - // - Terminalbus diagnostics........: disabled - // - Evaluation of Clear_Data.......: enabled - // - Diagnostics of binary modules..: is not mapped into PI - // - Extended PI-Update.............: according to PI-Update - // - Evaluation of complex modules..: process data only - // - Data format....................: MOTOROLA - // - Process image update...........: free running - // - Response to PROFIBUS failure...: Output image is cleared - // - Response to terminalbus failure: PROFIBUS communication stops - // - Maximum length of diagnostics..: 16 Byte - // - // Selected Modules: - // [0] 8 Bit binary inputs - // [1] 8 Bit binary outputs - user_parameters: Some(&[ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x0a, - 0x00, - ]), - config: Some(&[0x10, 0x20]), + fieldbus.enter_state(fieldbus::OperatingState::Operate); - // Set max_tsdr depending on baudrate and assert - // that a supported baudrate is used. - max_tsdr: match BAUDRATE { - profirust::Baudrate::B9600 => 60, - profirust::Baudrate::B19200 => 60, - profirust::Baudrate::B93750 => 60, - profirust::Baudrate::B187500 => 60, - profirust::Baudrate::B500000 => 100, - profirust::Baudrate::B1500000 => 150, - profirust::Baudrate::B3000000 => 250, - profirust::Baudrate::B6000000 => 350, - profirust::Baudrate::B12000000 => 550, - b => panic!( - "Peripheral \"WAGO 750-303 (FW: 01 ... 06) PRO\" does not support baudrate {b:?}!" - ), - }, - - fail_safe: false, - ..Default::default() - }; - let mut buffer_inputs = [0u8; 1]; - let mut buffer_outputs = [0u8; 1]; - let mut buffer_diagnostics = [0u8; 64]; - - let rio_handle = dp_master.add( - dp::Peripheral::new( - RIO_ADDRESS, - options, - &mut buffer_inputs, - &mut buffer_outputs, - ) - .with_diag_buffer(&mut buffer_diagnostics), - ); - - let mut fdl = fdl::FdlActiveStation::new( - fdl::ParametersBuilder::new(MASTER_ADDRESS, BAUDRATE) - // We use a rather large T_slot time because USB-RS485 converters - // can induce large delays at times. - .slot_bits(2500) - .watchdog_timeout(profirust::time::Duration::from_secs(2)) - .build_verified(&dp_master), - ); - // We must not poll() too often or to little. T_slot / 2 seems to be a good compromise. - let sleep_time: std::time::Duration = (fdl.parameters().slot_time() / 2).into(); - - let mut phy = phy::LinuxRs485Phy::new(BUS_DEVICE, fdl.parameters().baudrate); - - let _start = profirust::time::Instant::now(); - let mut pii = [0u8; 1024]; - let mut piq = [0u8; 1024]; - - fdl.set_online(); - dp_master.enter_operate(); loop { - let now = profirust::time::Instant::now(); - let events = fdl.poll(now, &mut phy, &mut dp_master); + fieldbus.update_process_images(&mut pii, &piq); + let pii = Pii::from(&pii); + let piq = PiqMut::from(&mut piq); - if events.cycle_completed { - let remoteio = dp_master.get_mut(rio_handle); - let rio_running = remoteio.is_running(); - let rio_pii = remoteio.pi_i(); - pii[0..rio_pii.len()].copy_from_slice(rio_pii); + display.update_line(2, &format!("Switch: {}", pii.want_run())); + display.update_line(3, &format!("Button: {}", pii.want_run_once())); - *pi::tag_mut!(&mut pii, X, 64, 0) = rio_running; + program(pii, piq, profirust::time::Instant::now()); - { - let pii = Pii::from(&pii); - display.update_line(2, &format!("Switch: {}", pii.want_run())); - display.update_line(3, &format!("Button: {}", pii.want_run_once())); - } - - program(Pii::from(&pii), PiqMut::from(&mut piq), now); - - let remoteio = dp_master.get_mut(rio_handle); - let rio_piq = remoteio.pi_q_mut(); - rio_piq.copy_from_slice(&piq[0..rio_piq.len()]); - } - - std::thread::sleep(sleep_time); + std::thread::sleep(std::time::Duration::from_millis(20)); } }