zencan_node/
pdo.rs

1//! Implementation of PDO configuration objects and PDO transmission
2//!
3//! ## PDO Default Configuration
4//!
5//! PDO default configuration can be controlled in the device config, so that PDOs may be mapped to
6//! certain object and enabled by default in a device. This is done in the `[pdos]` section of the
7//! config, which is defined by the
8//! [`PdoDefaultConfig`](crate::common::device_config::PdoDefaultConfig) struct.
9//!
10//! The default PDO COB ID may be specified as an absolute value, or it may be offset by the node ID
11//! at runtime.
12//!
13//! Example default PDO config:
14//!
15//! ```toml
16//! [pdos]
17//! num_rpdo = 4
18//! num_tpdo = 4
19//!
20//! # Enable TPDO1 to send on 0x200 + NODE_ID
21//! [pdos.tpdo.1]
22//! enabled = true
23//! cob_id = 0x200
24//! add_node_id = true
25//! transmission_type = 254
26//! mappings = [
27//!     { index=0x2000, sub=1, size=32 },
28//! ]
29//!
30//! # Configure RPDO0 to receive on extended ID 0x5000
31//! [pdos.rpdo.0]
32//! enabled = true
33//! extended = true
34//! rtr_disabled = false
35//! cob_id = 0x5000
36//! add_node_id = false
37//! transmission_type = 254
38//! mappings = [
39//!     { index = 0x2000, sub=2, size=32 },
40//! ]
41//! ```
42
43use crate::object_dict::{
44    find_object_entry, ConstField, ODEntry, ObjectAccess, ProvidesSubObjects, SubObjectAccess,
45};
46use zencan_common::{
47    objects::{AccessType, DataType, ObjectCode, PdoMappable, SubInfo},
48    pdo::PdoMapping,
49    sdo::AbortCode,
50    AtomicCell, CanId, NodeId,
51};
52
53/// Specifies the number of mapping parameters supported per PDO
54///
55/// Since we do not yet support CAN-FD, or sub-byte mapping, it's not possible to map more than 8
56/// objects to a single PDO
57const N_MAPPING_PARAMS: usize = 8;
58
59#[derive(Clone, Copy)]
60/// Data structure for storing a PDO object mapping
61struct MappingEntry {
62    /// A reference to the object which is mapped
63    pub object: &'static ODEntry<'static>,
64    /// The index of the sub object mapped
65    pub sub: u8,
66    /// The length of the mapping in bytes
67    pub length: u8,
68}
69
70#[allow(missing_debug_implementations)]
71/// Initialization values for a PDO
72#[derive(Copy, Clone)]
73pub struct PdoDefaults {
74    cob_id: u32,
75    flags: u8,
76    transmission_type: u8,
77    mappings: &'static [u32],
78}
79
80impl Default for PdoDefaults {
81    fn default() -> Self {
82        Self::DEFAULT
83    }
84}
85
86#[allow(missing_docs)]
87impl PdoDefaults {
88    const ADD_NODE_ID_FLAG: usize = 0;
89    const VALID_FLAG: usize = 1;
90    const RTR_DISABLED_FLAG: usize = 2;
91    const IS_EXTENDED_FLAG: usize = 3;
92
93    /// The PDO defaults used when no other defaults are configured
94    pub const DEFAULT: PdoDefaults = Self {
95        cob_id: 0,
96        flags: 0,
97        transmission_type: 0,
98        mappings: &[],
99    };
100
101    /// Create a new PdoDefaults object
102    pub const fn new(
103        cob_id: u32,
104        extended: bool,
105        add_node_id: bool,
106        valid: bool,
107        rtr_disabled: bool,
108        transmission_type: u8,
109        mappings: &'static [u32],
110    ) -> Self {
111        // Store flags as a single field to save those precious few bytes
112        let mut flags = 0u8;
113        if valid {
114            flags |= 1 << Self::VALID_FLAG;
115        }
116        if rtr_disabled {
117            flags |= 1 << Self::RTR_DISABLED_FLAG;
118        }
119        if add_node_id {
120            flags |= 1 << Self::ADD_NODE_ID_FLAG;
121        }
122        if extended {
123            flags |= 1 << Self::IS_EXTENDED_FLAG;
124        }
125
126        Self {
127            cob_id,
128            flags,
129            transmission_type,
130            mappings,
131        }
132    }
133
134    pub const fn valid(&self) -> bool {
135        self.flags & (1 << Self::VALID_FLAG) != 0
136    }
137
138    pub const fn rtr_disabled(&self) -> bool {
139        self.flags & (1 << Self::RTR_DISABLED_FLAG) != 0
140    }
141
142    pub const fn add_node_id(&self) -> bool {
143        self.flags & (1 << Self::ADD_NODE_ID_FLAG) != 0
144    }
145
146    pub const fn extended(&self) -> bool {
147        self.flags & (1 << Self::IS_EXTENDED_FLAG) != 0
148    }
149
150    pub const fn can_id(&self, node_id: u8) -> CanId {
151        let id = if self.add_node_id() {
152            self.cob_id + node_id as u32
153        } else {
154            self.cob_id
155        };
156        if self.extended() {
157            CanId::Extended(id)
158        } else {
159            CanId::Std(id as u16)
160        }
161    }
162}
163
164/// Represents a single PDO state
165#[allow(missing_debug_implementations)]
166pub struct Pdo {
167    /// The object dictionary
168    ///
169    /// PDOs have to access other objects and use this to do so
170    od: &'static [ODEntry<'static>],
171    /// Configured Node ID for the system
172    node_id: AtomicCell<NodeId>,
173    /// The COB-ID used to send or receive this PDO
174    cob_id: AtomicCell<Option<CanId>>,
175    /// Indicates if the PDO is enabled
176    valid: AtomicCell<bool>,
177    /// If set, this PDO cannot be requested via RTR
178    rtr_disabled: AtomicCell<bool>,
179    /// Transmission type field (subindex 0x2)
180    /// Determines when the PDO is sent/received
181    ///
182    /// 0 (unused): PDO is sent on receipt of SYNC, but only if the event has been triggered
183    /// 1 - 240: PDO is sent on receipt of every Nth SYNC message
184    /// 254: PDO is sent asynchronously on application request
185    transmission_type: AtomicCell<u8>,
186    /// Tracks the number of sync signals since this was last sent or received
187    sync_counter: AtomicCell<u8>,
188    /// The last received data value for an RPDO, or ready to transmit data for a TPDO
189    pub buffered_value: AtomicCell<Option<[u8; 8]>>,
190    /// Indicates how many of the values in mapping_params are valid
191    ///
192    /// This represents sub0 for the mapping object
193    valid_maps: AtomicCell<u8>,
194    /// The mapping parameters
195    ///
196    /// These specify which objects are
197    mapping_params: [AtomicCell<Option<MappingEntry>>; N_MAPPING_PARAMS],
198    /// System default values for this PDO
199    defaults: Option<&'static PdoDefaults>,
200}
201
202impl Pdo {
203    /// Create a new PDO object
204    pub const fn new(od: &'static [ODEntry<'static>]) -> Self {
205        let cob_id = AtomicCell::new(None);
206        let node_id = AtomicCell::new(NodeId::Unconfigured);
207        let valid = AtomicCell::new(false);
208        let rtr_disabled = AtomicCell::new(false);
209        let transmission_type = AtomicCell::new(0);
210        let sync_counter = AtomicCell::new(0);
211        let buffered_value = AtomicCell::new(None);
212        let valid_maps = AtomicCell::new(0);
213        let mapping_params = [const { AtomicCell::new(None) }; N_MAPPING_PARAMS];
214        let defaults = None;
215        Self {
216            od,
217            node_id,
218            cob_id,
219            valid,
220            rtr_disabled,
221            transmission_type,
222            sync_counter,
223            buffered_value,
224            valid_maps,
225            mapping_params,
226            defaults,
227        }
228    }
229
230    /// Create a new PDO object with provided defaults
231    pub const fn new_with_defaults(
232        od: &'static [ODEntry<'static>],
233        defaults: &'static PdoDefaults,
234    ) -> Self {
235        let mut pdo = Pdo::new(od);
236        pdo.defaults = Some(defaults);
237        pdo
238    }
239
240    /// Set the valid bit
241    pub fn set_valid(&self, value: bool) {
242        self.valid.store(value);
243    }
244
245    /// Get the valid bit value
246    pub fn valid(&self) -> bool {
247        self.valid.load()
248    }
249
250    /// Set the transmission type for this PDO
251    pub fn set_transmission_type(&self, value: u8) {
252        self.transmission_type.store(value);
253    }
254
255    /// Get the transmission type for this PDO
256    pub fn transmission_type(&self) -> u8 {
257        self.transmission_type.load()
258    }
259
260    /// Get the COB ID used for transmission of this PDO
261    pub fn cob_id(&self) -> CanId {
262        self.cob_id.load().unwrap_or(self.default_cob_id())
263    }
264
265    /// Get the default COB ID for transmission of this PDO
266    pub fn default_cob_id(&self) -> CanId {
267        if self.defaults.is_none() {
268            return CanId::std(0);
269        }
270        let defaults = self.defaults.unwrap();
271        let node_id = match self.node_id.load() {
272            NodeId::Unconfigured => 0,
273            NodeId::Configured(node_id) => node_id.raw(),
274        };
275        defaults.can_id(node_id)
276    }
277
278    /// This function should be called when a SYNC event occurs
279    ///
280    /// It will return true if the PDO should be sent in response to the SYNC event
281    pub fn sync_update(&self) -> bool {
282        if !self.valid.load() {
283            return false;
284        }
285
286        let transmission_type = self.transmission_type.load();
287        if transmission_type == 0 {
288            // TODO: Figure out how to determine application "event" which triggers the PDO
289            // For now, send every sync
290            true
291        } else if transmission_type <= 240 {
292            let cnt = self.sync_counter.fetch_add(1) + 1;
293            cnt == transmission_type
294        } else {
295            false
296        }
297    }
298
299    /// Check mapped objects for TPDO event flag
300    pub fn read_events(&self) -> bool {
301        if !self.valid.load() {
302            return false;
303        }
304
305        for i in 0..self.mapping_params.len() {
306            let param = self.mapping_params[i].load();
307            if param.is_none() {
308                break;
309            }
310            let param = param.unwrap();
311            if param.object.data.read_event_flag(param.sub) {
312                return true;
313            }
314        }
315        false
316    }
317
318    pub(crate) fn clear_events(&self) {
319        for i in 0..self.mapping_params.len() {
320            let param = self.mapping_params[i].load();
321            if param.is_none() {
322                break;
323            }
324            let param = param.unwrap();
325            param.object.data.clear_events();
326        }
327    }
328
329    pub(crate) fn store_pdo_data(&self, data: &[u8]) {
330        let mut offset = 0;
331        let valid_maps = self.valid_maps.load() as usize;
332        for (i, param) in self.mapping_params.iter().enumerate() {
333            if i >= valid_maps {
334                break;
335            }
336            let param = param.load();
337            if param.is_none() {
338                break;
339            }
340            let param = param.unwrap();
341            let length = param.length as usize;
342            if offset + length > data.len() {
343                break;
344            }
345            let data_to_write = &data[offset..offset + length];
346            // validity of the mappings must be validated during write, so that error here is not
347            // possible
348            param.object.data.write(param.sub, data_to_write).ok();
349            offset += length;
350        }
351    }
352
353    pub(crate) fn send_pdo(&self) {
354        let mut data = [0u8; 8];
355        let mut offset = 0;
356        let valid_maps = self.valid_maps.load() as usize;
357        for (i, param) in self.mapping_params.iter().enumerate() {
358            if i >= valid_maps {
359                break;
360            }
361            let param = param.load();
362            // The first N params will be valid. Can assume if one is None, all remaining will be as
363            // well
364            if param.is_none() {
365                break;
366            }
367            let param = param.unwrap();
368            let length = param.length as usize;
369            if offset + length > data.len() {
370                break;
371            }
372            // validity of the mappings must be validated during write, so that error here is not
373            // possible
374            param
375                .object
376                .data
377                .read(param.sub, 0, &mut data[offset..offset + length])
378                .ok();
379            offset += length;
380        }
381        // If there is an old value here which has not been sent yet, replace it with the latest
382        // Data will be sent by mbox in message handling thread.
383        self.buffered_value.store(Some(data));
384    }
385
386    /// Lookup a PDO mapped object and create a MappingEntry if it is valid
387    ///
388    /// The returned MappingEntry can be stored in the Pdo mappings and includes
389    /// a reference to the mapped object for faster access when
390    /// sending/receiving PDOs.
391    ///
392    /// This function may fail if the mapped object doesn't exist, or if it is
393    /// too short.
394    fn try_create_mapping_entry(&self, mapping: PdoMapping) -> Result<MappingEntry, AbortCode> {
395        let PdoMapping {
396            index,
397            sub,
398            size: length,
399        } = mapping;
400        // length is in bits.
401        if (length % 8) != 0 {
402            // only support byte level access for now
403            return Err(AbortCode::IncompatibleParameter);
404        }
405        let entry = find_object_entry(self.od, index).ok_or(AbortCode::NoSuchObject)?;
406        let sub_info = entry.data.sub_info(sub)?;
407        if sub_info.size < length as usize / 8 {
408            return Err(AbortCode::IncompatibleParameter);
409        }
410        Ok(MappingEntry {
411            object: entry,
412            sub,
413            length: length / 8,
414        })
415    }
416
417    /// Initialize the PDO configuration with its default value
418    pub fn init_defaults(&self, node_id: NodeId) {
419        if self.defaults.is_none() {
420            return;
421        }
422        let defaults = self.defaults.unwrap();
423
424        self.node_id.store(node_id);
425        for (i, m) in defaults.mappings.iter().enumerate() {
426            if i >= self.mapping_params.len() {
427                return;
428            }
429            if let Ok(entry) = self.try_create_mapping_entry(PdoMapping::from_object_value(*m)) {
430                self.mapping_params[i].store(Some(entry));
431            }
432        }
433        self.valid_maps.store(defaults.mappings.len() as u8);
434
435        self.valid.store(defaults.valid());
436        // None means "use the default computed ID"
437        self.cob_id.store(None);
438        self.rtr_disabled.store(defaults.rtr_disabled());
439        self.transmission_type.store(defaults.transmission_type);
440    }
441}
442
443struct PdoCobSubObject {
444    pdo: &'static Pdo,
445}
446
447impl PdoCobSubObject {
448    pub const fn new(pdo: &'static Pdo) -> Self {
449        Self { pdo }
450    }
451
452    /// Should the COB sub object be persisted
453    ///
454    /// The object is only persisted when a non-default COB ID has been assigned.
455    pub fn should_persist(&self) -> bool {
456        self.pdo.cob_id.load().is_some()
457    }
458}
459
460impl SubObjectAccess for PdoCobSubObject {
461    fn read(&self, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode> {
462        let cob_id = self.pdo.cob_id();
463        let mut value = cob_id.raw();
464        if cob_id.is_extended() {
465            value |= 1 << 29;
466        }
467        if self.pdo.rtr_disabled.load() {
468            value |= 1 << 30;
469        }
470        if !self.pdo.valid.load() {
471            value |= 1 << 31;
472        }
473
474        let bytes = value.to_le_bytes();
475        if offset < bytes.len() {
476            let read_len = buf.len().min(bytes.len() - offset);
477            buf[0..read_len].copy_from_slice(&bytes[offset..offset + read_len]);
478            Ok(read_len)
479        } else {
480            Ok(0)
481        }
482    }
483
484    fn read_size(&self) -> usize {
485        4
486    }
487
488    fn write(&self, data: &[u8]) -> Result<(), AbortCode> {
489        if data.len() < 4 {
490            Err(AbortCode::DataTypeMismatchLengthLow)
491        } else if data.len() > 4 {
492            Err(AbortCode::DataTypeMismatchLengthHigh)
493        } else {
494            let value = u32::from_le_bytes(data.try_into().unwrap());
495            let not_valid = (value & (1 << 31)) != 0;
496            let no_rtr = (value & (1 << 30)) != 0;
497            let extended_id = (value & (1 << 29)) != 0;
498
499            let can_id = if extended_id {
500                CanId::Extended(value & 0x1FFFFFFF)
501            } else {
502                CanId::Std((value & 0x7FF) as u16)
503            };
504            self.pdo.cob_id.store(Some(can_id));
505            self.pdo.valid.store(!not_valid);
506            self.pdo.rtr_disabled.store(no_rtr);
507            Ok(())
508        }
509    }
510}
511
512struct PdoTransmissionTypeSubObject {
513    pdo: &'static Pdo,
514}
515
516impl PdoTransmissionTypeSubObject {
517    pub const fn new(pdo: &'static Pdo) -> Self {
518        Self { pdo }
519    }
520}
521
522impl SubObjectAccess for PdoTransmissionTypeSubObject {
523    fn read(&self, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode> {
524        if offset > 1 {
525            return Ok(0);
526        }
527        buf[0] = self.pdo.transmission_type();
528        Ok(1)
529    }
530
531    fn read_size(&self) -> usize {
532        1
533    }
534
535    fn write(&self, data: &[u8]) -> Result<(), AbortCode> {
536        if data.is_empty() {
537            Err(AbortCode::DataTypeMismatchLengthLow)
538        } else {
539            self.pdo.set_transmission_type(data[0]);
540            Ok(())
541        }
542    }
543}
544
545/// Implements a PDO communications config object for both RPDOs and TPDOs
546#[allow(missing_debug_implementations)]
547pub struct PdoCommObject {
548    cob: PdoCobSubObject,
549    transmission_type: PdoTransmissionTypeSubObject,
550}
551
552impl PdoCommObject {
553    /// Create a new PdoCommObject
554    pub const fn new(pdo: &'static Pdo) -> Self {
555        let cob = PdoCobSubObject::new(pdo);
556        let transmission_type = PdoTransmissionTypeSubObject::new(pdo);
557        Self {
558            cob,
559            transmission_type,
560        }
561    }
562}
563
564impl ProvidesSubObjects for PdoCommObject {
565    fn get_sub_object(&self, sub: u8) -> Option<(SubInfo, &dyn SubObjectAccess)> {
566        match sub {
567            0 => Some((
568                SubInfo::MAX_SUB_NUMBER,
569                const { &ConstField::new(2u8.to_le_bytes()) },
570            )),
571            1 => Some((
572                SubInfo::new_u32()
573                    .rw_access()
574                    .persist(self.cob.should_persist()),
575                &self.cob,
576            )),
577            2 => Some((
578                SubInfo::new_u8().rw_access().persist(true),
579                &self.transmission_type,
580            )),
581            _ => None,
582        }
583    }
584
585    fn object_code(&self) -> ObjectCode {
586        ObjectCode::Record
587    }
588}
589
590/// Implements a PDO mapping config object for both TPDOs and RPDOs
591#[allow(missing_debug_implementations)]
592pub struct PdoMappingObject {
593    pdo: &'static Pdo,
594}
595
596impl PdoMappingObject {
597    /// Create a new PdoMappingObject
598    pub const fn new(pdo: &'static Pdo) -> Self {
599        Self { pdo }
600    }
601}
602
603impl ObjectAccess for PdoMappingObject {
604    fn read(&self, sub: u8, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode> {
605        if sub == 0 {
606            if offset < 1 && !buf.is_empty() {
607                buf[0] = self.pdo.valid_maps.load();
608                Ok(1)
609            } else {
610                Ok(0)
611            }
612        } else if sub <= self.pdo.mapping_params.len() as u8 {
613            let value = if let Some(param) = self.pdo.mapping_params[(sub - 1) as usize].load() {
614                ((param.object.index as u32) << 16)
615                    + ((param.sub as u32) << 8)
616                    + param.length as u32 * 8
617            } else {
618                0u32
619            };
620            let bytes = value.to_le_bytes();
621            let read_len = buf.len().min(bytes.len() - offset);
622            buf[..read_len].copy_from_slice(&bytes[offset..offset + read_len]);
623            Ok(read_len)
624        } else {
625            Err(AbortCode::NoSuchSubIndex)
626        }
627    }
628
629    fn read_size(&self, sub: u8) -> Result<usize, AbortCode> {
630        if sub == 0 {
631            Ok(1)
632        } else if sub <= N_MAPPING_PARAMS as u8 {
633            Ok(4)
634        } else {
635            Err(AbortCode::NoSuchSubIndex)
636        }
637    }
638
639    fn write(&self, sub: u8, data: &[u8]) -> Result<(), AbortCode> {
640        if sub == 0 {
641            self.pdo.valid_maps.store(data[0]);
642            Ok(())
643        } else if sub <= self.pdo.mapping_params.len() as u8 {
644            if data.len() != 4 {
645                return Err(AbortCode::DataTypeMismatch);
646            }
647            let value = u32::from_le_bytes(data.try_into().unwrap());
648
649            let mapping = PdoMapping::from_object_value(value);
650
651            self.pdo.mapping_params[(sub - 1) as usize]
652                .store(Some(self.pdo.try_create_mapping_entry(mapping)?));
653            Ok(())
654        } else {
655            Err(AbortCode::NoSuchSubIndex)
656        }
657    }
658
659    fn object_code(&self) -> ObjectCode {
660        ObjectCode::Record
661    }
662
663    fn sub_info(&self, sub: u8) -> Result<SubInfo, AbortCode> {
664        if sub == 0 {
665            Ok(SubInfo {
666                size: 1,
667                data_type: DataType::UInt8,
668                access_type: AccessType::Rw,
669                pdo_mapping: PdoMappable::None,
670                persist: true,
671            })
672        } else if sub <= self.pdo.mapping_params.len() as u8 {
673            Ok(SubInfo {
674                size: 4,
675                data_type: DataType::UInt32,
676                access_type: AccessType::Rw,
677                pdo_mapping: PdoMappable::None,
678                persist: true,
679            })
680        } else {
681            Err(AbortCode::NoSuchSubIndex)
682        }
683    }
684}