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))
    }
}