Implement async fieldbus driver
Update the fieldbus in the background so the bus cycle is not affected by the application cycle.
This commit is contained in:
parent
ca6098ec20
commit
1ef275a520
2 changed files with 225 additions and 116 deletions
211
src/fieldbus.rs
Normal file
211
src/fieldbus.rs
Normal file
|
|
@ -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<Mutex<FieldbusInner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<Mutex<FieldbusInner>> = 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<Mutex<FieldbusInner>>) {
|
||||||
|
let mut dp_master = dp::DpMaster::new(vec![]);
|
||||||
|
let mut peripherals: Vec<PeripheralInfo> = 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
126
src/main.rs
126
src/main.rs
|
|
@ -1,24 +1,13 @@
|
||||||
use process_image as pi;
|
use process_image as pi;
|
||||||
|
|
||||||
use profirust::dp;
|
|
||||||
use profirust::fdl;
|
|
||||||
use profirust::phy;
|
|
||||||
|
|
||||||
mod display;
|
mod display;
|
||||||
|
mod fieldbus;
|
||||||
// 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;
|
|
||||||
|
|
||||||
// Parameters
|
// Parameters
|
||||||
const CYCLE_TIME: u64 = 5000;
|
const CYCLE_TIME: u64 = 5000;
|
||||||
|
|
||||||
pi::process_image! {
|
pi::process_image! {
|
||||||
struct Pii: 1024 {
|
struct Pii: 256 {
|
||||||
pub bgb1: (X, 0, 0), // Left Limit Switch
|
pub bgb1: (X, 0, 0), // Left Limit Switch
|
||||||
pub bgb2: (X, 0, 1), // Right Limit Switch
|
pub bgb2: (X, 0, 1), // Right Limit Switch
|
||||||
pub bpb1: (X, 0, 2), // Pressure > 1.5 barg
|
pub bpb1: (X, 0, 2), // Pressure > 1.5 barg
|
||||||
|
|
@ -31,7 +20,7 @@ pi::process_image! {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 mbb1: (X, 0, 0), // Coil for turning cylinder to the left
|
||||||
pub mbb2: (X, 0, 1), // Coil for turning cylinder to the right
|
pub mbb2: (X, 0, 1), // Coil for turning cylinder to the right
|
||||||
pub pza1_white: (X, 0, 2), // White status indicator light
|
pub pza1_white: (X, 0, 2), // White status indicator light
|
||||||
|
|
@ -112,114 +101,23 @@ fn main() {
|
||||||
let mut display = display::Display::new();
|
let mut display = display::Display::new();
|
||||||
display.update_line(0, &format!("{:^20}", "torque tester"));
|
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 mut pii = [0u8; fieldbus::PII_SIZE];
|
||||||
let options = profirust::dp::PeripheralOptions {
|
let mut piq = [0u8; fieldbus::PIQ_SIZE];
|
||||||
// "WAGO 750-303 (FW: 01 ... 06) PRO" by "WAGO Kontakttechnik GmbH"
|
|
||||||
ident_number: 0xb751,
|
|
||||||
|
|
||||||
// Global Parameters:
|
fieldbus.enter_state(fieldbus::OperatingState::Operate);
|
||||||
// - 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 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 {
|
loop {
|
||||||
let now = profirust::time::Instant::now();
|
fieldbus.update_process_images(&mut pii, &piq);
|
||||||
let events = fdl.poll(now, &mut phy, &mut dp_master);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
*pi::tag_mut!(&mut pii, X, 64, 0) = rio_running;
|
|
||||||
|
|
||||||
{
|
|
||||||
let pii = Pii::from(&pii);
|
let pii = Pii::from(&pii);
|
||||||
|
let piq = PiqMut::from(&mut piq);
|
||||||
|
|
||||||
display.update_line(2, &format!("Switch: {}", pii.want_run()));
|
display.update_line(2, &format!("Switch: {}", pii.want_run()));
|
||||||
display.update_line(3, &format!("Button: {}", pii.want_run_once()));
|
display.update_line(3, &format!("Button: {}", pii.want_run_once()));
|
||||||
}
|
|
||||||
|
|
||||||
program(Pii::from(&pii), PiqMut::from(&mut piq), now);
|
program(pii, piq, profirust::time::Instant::now());
|
||||||
|
|
||||||
let remoteio = dp_master.get_mut(rio_handle);
|
std::thread::sleep(std::time::Duration::from_millis(20));
|
||||||
let rio_piq = remoteio.pi_q_mut();
|
|
||||||
rio_piq.copy_from_slice(&piq[0..rio_piq.len()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::thread::sleep(sleep_time);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue