1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
/// FDL master parameters
///
/// These parameters configure the behavior of the FDL master.
///
/// You should use the [`ParametersBuilder`] to build the parameters struct. Check its
/// documentation for detailed explanations of the individual parameters.
///
/// # Example
/// ```
/// use profirust::fdl;
/// # let buffer: [profirust::dp::PeripheralStorage; 4] = Default::default();
/// # let dp_master = profirust::dp::DpMaster::new(buffer);
///
/// let master_address = 2;
/// let param = fdl::ParametersBuilder::new(master_address, profirust::Baudrate::B19200)
/// .slot_bits(300)
/// .build_verified(&dp_master);
/// ```
#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub struct Parameters {
/// Station address for this master
pub address: u8,
/// Baudrate
pub baudrate: crate::Baudrate,
/// T<sub>SL</sub>: Slot time in bits
pub slot_bits: u16,
/// Time until the token should have rotated through all masters once.
pub token_rotation_bits: u32,
/// GAP: update factor (how many token rotations to wait before polling the gap again)
pub gap_wait_rotations: u8,
/// HSA: Highest station address
///
/// The HSA is only relevant for finding other masters who want to become part of the token
/// ring. Peripherals are allows to have addresses higher than HSA. Masters must have an
/// address less than HSA.
///
/// **Important**: The live-list service will only detect stations below the HSA as well.
pub highest_station_address: u8,
/// Maximum number of retries when no answer was received
pub max_retry_limit: u8,
/// min T<sub>SDR</sub>: Minimum delay before anyone is allowed to respond to a telegram
pub min_tsdr_bits: u8,
/// Watchdog timeout for peripherals monitoring the DP master
pub watchdog_factors: Option<(u8, u8)>,
}
impl Default for Parameters {
fn default() -> Self {
Parameters {
address: 1,
baudrate: crate::Baudrate::B19200,
// Tied to the baudrate - will usually be adjusted by the ParametersBuilder.
slot_bits: 100,
// TTR default value as found elsewhere.
// TODO: Need to decide how to deal with this value.
token_rotation_bits: 32436,
// GAP update factor, default 10 as found elsewhere.
gap_wait_rotations: 10,
// 125 is the highest possible address - by default all addresses are included.
// (and HSA is highest address + 1)
highest_station_address: 126,
// Defaults to 1 byte time (= 11 bits)
min_tsdr_bits: 11,
// Retry limit defaults to 1, meaning that a telegram will be retried once. This is a
// sane default as retries should not be necessary at all on a bus that is set up
// correctly.
max_retry_limit: 1,
// No watchdog by default.
//
// TODO: Is this what we want? Found 6250 x HSA recommended elsewhere.
watchdog_factors: None,
}
}
}
#[inline]
fn min_slot_bits(baudrate: crate::Baudrate) -> u16 {
match baudrate {
crate::Baudrate::B9600
| crate::Baudrate::B19200
| crate::Baudrate::B31250
| crate::Baudrate::B45450
| crate::Baudrate::B93750
| crate::Baudrate::B187500 => 100,
crate::Baudrate::B500000 => 200,
crate::Baudrate::B1500000 => 300,
crate::Baudrate::B3000000 => 400,
crate::Baudrate::B6000000 => 600,
crate::Baudrate::B12000000 => 1000,
}
}
#[inline]
fn watchdog_factors(dur: crate::time::Duration) -> Option<Result<(u8, u8), ()>> {
// TODO: Support the different watchdog time bases in some way?
Some(dur)
.filter(|dur| *dur != crate::time::Duration::ZERO)
.map(|dur| {
let timeout_10ms: u32 = (dur.total_millis() / 10).try_into().or(Err(()))?;
for f1 in 1..256 {
let f2 = (timeout_10ms + f1 - 1) / f1;
if f2 < 256 {
return Ok((u8::try_from(f1).unwrap(), u8::try_from(f2).unwrap()));
}
}
// Timeout is still too big
Err(())
})
}
/// Builder for the parameters of an FDL master
pub struct ParametersBuilder(Parameters);
impl ParametersBuilder {
/// Start building parameters for an FDL master with the given `address`.
///
/// - `address` must be a valid PROFIBUS address (<= 125).
/// - `baudrate` is the baudrate the is used for this PROFIBUS network.
#[inline]
pub fn new(address: u8, baudrate: crate::Baudrate) -> Self {
assert!(address <= 125);
Self(Parameters {
address,
baudrate,
slot_bits: min_slot_bits(baudrate),
..Default::default()
})
}
/// Configure non-standard T<sub>SL</sub> (slot time in bits)
///
/// The slot time must be larger than the maximum T<sub>SDR</sub> of all peripherals.
/// `build_verified()` will check that this is the case.
///
/// The slot time must be creater than the default slot time:
///
/// | Baudrate | Minimum Slot Time (in Bits) |
/// | ---: | ---: |
/// | <=187500 | 100 |
/// | 500000 | 200 |
/// | 1500000 | 300 |
/// | 3000000 | 400 |
/// | 6000000 | 600 |
/// | 12000000 | 1000 |
#[inline]
pub fn slot_bits(&mut self, slot_bits: u16) -> &mut Self {
self.0.slot_bits = slot_bits;
assert!(slot_bits >= min_slot_bits(self.0.baudrate));
self
}
/// Set the highest projected station address.
///
/// The HSA is used when scanning for other FDL masters who want to participate on the bus.
///
/// The HSA also affects what addresses will appear in the live list recorded by this master.
///
/// By default, all addresses are scanned (HSA = 126). This means all masters will be found
/// but it also means that the time until a master can join the token ring is rather long. It
/// is advisable to choose low addresses for all masters and then set the HSA accordingly to
/// optimize recovery time after a master drops from the bus.
#[inline]
pub fn highest_station_address(&mut self, hsa: u8) -> &mut Self {
assert!(hsa > self.0.address && hsa <= 126);
self.0.highest_station_address = hsa;
// TODO: We probably shouldn't override an explicitly set value here...
self.token_rotation_bits(u32::from(hsa) * 5000);
self
}
/// Set the projected token rotation time (in bits).
///
/// The TTR is used to ensure each FDL master gets a chance to communicate with its peripherals
/// in deterministic time.
///
/// It is important that the TTR is not too small as this unnecessarily slows down
/// communication. Defaults to 32436.
pub fn token_rotation_bits(&mut self, ttr: u32) -> &mut Self {
assert!(ttr >= 256 && ttr <= 16_777_960);
self.0.token_rotation_bits = ttr;
self
}
/// Set how many token rotations to wait before restarting the GAP scan.
///
/// The GAP scan is used to detect other FDL masters who want to communicate on the bus.
///
/// This factor is the wait time between scan cycles. A low value means stations are found
/// very quickly but the tradeoff is a higher average cycle time.
pub fn gap_wait_rotations(&mut self, gap_wait: u8) -> &mut Self {
assert!(gap_wait >= 1 && gap_wait <= 100);
self.0.gap_wait_rotations = gap_wait;
self
}
/// Set the maximum number of retries when communication with a peripheral fails.
///
/// After this amount of retries, the peripheral is considered offline and will need to be
/// reconfigured once it appears again.
///
/// On a bus that is electrically sound, no retries should ever be necessary. When you have to
/// increase the retry count to keep your bus working, it is recommended to check for
/// electrical and/or noise problems.
///
/// Default value is 1, meaning a telegram is retried once when no response was received.
#[inline]
pub fn max_retry_limit(&mut self, max_retry_limit: u8) -> &mut Self {
assert!(max_retry_limit >= 1 && max_retry_limit <= 15);
self.0.max_retry_limit = max_retry_limit;
self
}
/// Set the minimum response time that peripherals should adhere to.
///
/// This value can be increased when peripherals responding after 11 bits is too fast for the
/// bus to settle.
#[inline]
pub fn min_tsdr(&mut self, min_tsdr_bits: u8) -> &mut Self {
assert!(min_tsdr_bits >= 11);
self.0.min_tsdr_bits = min_tsdr_bits;
self
}
/// Set the watchdog timeout that peripherals should use to fail-safe after loosing
/// communication.
#[inline]
pub fn watchdog_timeout(&mut self, wdg: crate::time::Duration) -> &mut Self {
assert!(wdg >= crate::time::Duration::from_millis(10));
assert!(wdg <= crate::time::Duration::from_secs(650));
self.0.watchdog_factors = watchdog_factors(wdg).transpose().unwrap();
self
}
/// Build the parameters struct.
#[inline]
pub fn build(&self) -> Parameters {
self.0.clone()
}
/// Build the parameters struct and verify it against the given DP master.
///
/// This ensures that, for example, the selected T<sub>SL</sub> is greater than the max Tsdr of
/// all peripherals currently tracked by the DP master.
#[inline]
pub fn build_verified(&self, dp_master: &crate::dp::DpMaster) -> Parameters {
for (_, peripheral) in dp_master.iter() {
assert!(
peripheral.options().max_tsdr + 15 <= self.0.slot_bits,
"max Tsdr of peripheral #{} too large for slot time",
peripheral.address(),
);
}
self.0.clone()
}
}
impl Parameters {
pub fn bits_to_time(&self, bits: u32) -> crate::time::Duration {
self.baudrate.bits_to_time(bits)
}
/// T<sub>SL</sub> (slot time) converted to duration
pub fn slot_time(&self) -> crate::time::Duration {
self.bits_to_time(u32::from(self.slot_bits))
}
/// min T<sub>SDR</sub> (minimum time before responding) converted to duration
pub fn min_tsdr_time(&self) -> crate::time::Duration {
self.bits_to_time(u32::from(self.min_tsdr_bits))
}
/// Timeout after which the token is considered lost.
///
/// Calculated as 6 * T<sub>SL</sub> + 2 * Addr * T<sub>SL</sub>.
pub fn token_lost_timeout(&self) -> crate::time::Duration {
let timeout_bits = u32::from(self.slot_bits) * (6 + 2 * u32::from(self.address));
self.bits_to_time(timeout_bits)
}
/// T<sub>TR</sub> (projected token rotation time)
pub fn token_rotation_time(&self) -> crate::time::Duration {
self.bits_to_time(self.token_rotation_bits)
}
/// Watchdog timeout
pub fn watchdog_timeout(&self) -> Option<crate::time::Duration> {
self.watchdog_factors
.map(|(f1, f2)| crate::time::Duration::from_millis(u64::from(f1) * u64::from(f2) * 10))
}
}