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