Skip to main content

modbus_impl/
lib.rs

1#![no_std]
2/// RegisterRead:支持 is_valid(addr) 用于越界检查
3/// 支持03/04寄存器的读取(Hreg/Ireg)
4pub trait RegisterRead {
5    fn get(&self, addr: u16) -> u16;
6    fn is_valid(&self, addr: u16) -> bool;
7}
8
9/// 用于 FC06 写单保持寄存器
10pub trait RegisterWrite {
11    fn set_reg(&mut self, addr: u16, val: u16);
12}
13
14/// 支持 01/02 的位读取(Coil/ISTS)
15pub trait BitRead {
16    fn get(&self, addr: u16) -> bool;
17    fn is_valid(&self, addr: u16) -> bool;
18}
19
20/// 用于 FC05 写单线圈
21pub trait BitWrite {
22    fn set_bit(&mut self, addr: u16, val: bool);
23}
24
25/// Coil 结构体(FC01)
26pub struct Coil<const N: usize> {
27    regs: [bool; N],
28}
29impl<const N: usize> Coil<N> {
30    pub const fn new() -> Self {
31        Self { regs: [false; N] }
32    }
33    pub fn set_bit(&mut self, addr: u16, val: bool) {
34        let i = addr as usize;
35        if i < N {
36            self.regs[i] = val;
37        }
38    }
39    pub fn as_bits(&self) -> &[bool] {
40        &self.regs
41    }
42}
43
44impl<const N: usize> BitRead for Coil<N> {
45    fn get(&self, addr: u16) -> bool {
46        let i = addr as usize;
47        if i < N {
48            self.regs[i]
49        } else {
50            false
51        }
52    }
53    fn is_valid(&self, addr: u16) -> bool {
54        (addr as usize) < N
55    }
56}
57
58impl<const N: usize> BitWrite for Coil<N> {
59    fn set_bit(&mut self, addr: u16, val: bool) {
60        Coil::set_bit(self, addr, val);
61    }
62}
63
64/// ISTS(离散输入)结构体(FC02)
65pub struct Ists<const N: usize> {
66    regs: [bool; N],
67}
68impl<const N: usize> Ists<N> {
69    pub const fn new() -> Self {
70        Self { regs: [false; N] }
71    }
72    pub fn set_bit(&mut self, addr: u16, val: bool) {
73        let i = addr as usize;
74        if i < N {
75            self.regs[i] = val;
76        }
77    }
78}
79impl<const N: usize> BitRead for Ists<N> {
80    fn get(&self, addr: u16) -> bool {
81        let i = addr as usize;
82        if i < N {
83            self.regs[i]
84        } else {
85            false
86        }
87    }
88    fn is_valid(&self, addr: u16) -> bool {
89        (addr as usize) < N
90    }
91}
92
93/// IREG(输入寄存器)结构体(FC04)
94pub struct Ireg<const N: usize> {
95    regs: [u16; N],
96}
97impl<const N: usize> Ireg<N> {
98    pub const fn new() -> Self {
99        Self { regs: [0; N] }
100    }
101    pub fn set(&mut self, addr: u16, val: u16) {
102        let i = addr as usize;
103        if i < N {
104            self.regs[i] = val;
105        }
106    }
107}
108impl<const N: usize> RegisterRead for Ireg<N> {
109    fn get(&self, addr: u16) -> u16 {
110        let i = addr as usize;
111        if i < N {
112            self.regs[i]
113        } else {
114            0
115        }
116    }
117    fn is_valid(&self, addr: u16) -> bool {
118        (addr as usize) < N
119    }
120}
121
122///HREG(保持寄存器)结构体(FC03)
123pub struct Hreg<const N: usize> {
124    regs: [u16; N],
125}
126
127impl<const N: usize> Hreg<N> {
128    pub const fn new() -> Self {
129        Self { regs: [0; N] }
130    }
131
132    pub fn set(&mut self, addr: u16, val: u16) {
133        let i = addr as usize;
134        if i < N {
135            self.regs[i] = val;
136        }
137    }
138
139    pub fn as_slice(&self) -> &[u16] {
140        &self.regs
141    }
142}
143
144// RegisterRead 实现:提供 get + is_valid
145impl<const N: usize> RegisterRead for Hreg<N> {
146    fn get(&self, addr: u16) -> u16 {
147        let i = addr as usize;
148        if i < N {
149            self.regs[i]
150        } else {
151            0
152        }
153    }
154    fn is_valid(&self, addr: u16) -> bool {
155        (addr as usize) < N
156    }
157}
158
159impl<const N: usize> RegisterWrite for Hreg<N> {
160    fn set_reg(&mut self, addr: u16, val: u16) {
161        Hreg::set(self, addr, val);
162    }
163}
164
165///////////////////////////////////////////////////////////////////////////////
166// 2) random(start_val, end_val)  —— no_std 伪随机
167///////////////////////////////////////////////////////////////////////////////
168
169// xorshift32 seed:单线程场景足够用
170static mut SEED: u32 = 0x1234_5678;
171
172#[inline]
173fn xorshift32_next() -> u32 {
174    // SAFETY: 单线程典型嵌入式用法;如需多核/中断并发请告诉我再改为更安全的方案
175    unsafe {
176        let mut x = SEED;
177        x ^= x << 13;
178        x ^= x >> 17;
179        x ^= x << 5;
180        SEED = x;
181        x
182    }
183}
184
185/// 生成闭区间 [min(start_val,end_val), max(...)] 内的随机 u16
186pub fn random(start_val: u16, end_val: u16) -> u16 {
187    let (lo, hi) = if start_val <= end_val {
188        (start_val, end_val)
189    } else {
190        (end_val, start_val)
191    };
192
193    let span = (hi as u32).wrapping_sub(lo as u32).wrapping_add(1);
194    let r = xorshift32_next() % span;
195    (lo as u32 + r) as u16
196}
197
198#[derive(Clone, Copy, Debug)]
199pub struct Req03 {
200    pub unit_id: u8,
201    pub start_addr: u16,
202    pub quantity: u16,
203}
204
205pub mod exc {
206    pub const ILLEGAL_FUNCTION: u8 = 0x01;
207    pub const ILLEGAL_DATA_ADDRESS: u8 = 0x02;
208    pub const ILLEGAL_DATA_VALUE: u8 = 0x03;
209}
210
211/// Modbus RTU CRC16,用于生成CRC16验证码
212pub fn crc16_modbus(data: &[u8]) -> u16 {
213    let mut crc: u16 = 0xFFFF;
214    for &b in data {
215        crc ^= b as u16;
216        for _ in 0..8 {
217            if (crc & 0x0001) != 0 {
218                crc = (crc >> 1) ^ 0xA001;
219            } else {
220                crc >>= 1;
221            }
222        }
223    }
224    crc
225}
226
227/// 组装 FC01 / FC02 响应:
228/*
229  Unit(1) + Func(1) + ByteCount(1) + PackedBits(N) + CRC(2)
230
231  packed bits:Modbus 规定每字节 bit0 是第一个位(LSB-first)
232*/
233pub fn build_resp_bit_reads<const MAX_QTY: usize, B: BitRead>(
234    out: &mut [u8],
235    unit_id: u8,
236    func: u8, // 0x01 or 0x02
237    start_addr: u16,
238    quantity: u16,
239    bits: &B,
240) -> usize {
241    let qty = quantity as usize;
242    let byte_cnt = (qty + 7) / 8;
243
244    out[0] = unit_id;
245    out[1] = func;
246    out[2] = byte_cnt as u8;
247
248    // 清零位区
249    for i in 0..byte_cnt {
250        out[3 + i] = 0;
251    }
252
253    for j in 0..qty {
254        let addr = start_addr.wrapping_add(j as u16);
255        let bit = bits.get(addr);
256        if bit {
257            let byte_i = j / 8;
258            let bit_i = j % 8;
259            out[3 + byte_i] |= 1u8 << bit_i; // LSB-first
260        }
261    }
262
263    let body_len = 3 + byte_cnt;
264    let crc = crc16_modbus(&out[..body_len]);
265    out[body_len] = (crc & 0xFF) as u8;
266    out[body_len + 1] = (crc >> 8) as u8;
267    body_len + 2
268}
269
270/// 组装异常响应:Function=03|0x80 + ExceptionCode(1) + CRC(2)
271pub fn build_exception_resp<const BUF: usize>(
272    out: &mut [u8; BUF],
273    unit_id: u8,
274    function_exception: u8, // 例如 0x83
275    exception_code: u8,
276) -> usize {
277    out[0] = unit_id;
278    out[1] = function_exception;
279    out[2] = exception_code;
280
281    let crc = crc16_modbus(&out[..3]);
282    out[3] = (crc & 0xFF) as u8;
283    out[4] = (crc >> 8) as u8;
284    5
285}
286
287///ModbusCtx,用于沟通上下文
288pub struct ModbusCtx<'a, H, I, C, D> {
289    pub holdings: &'a mut H, // FC03
290    pub inputs: &'a mut I,   // FC04
291    pub coils: &'a mut C,    // FC01
292    pub ists: &'a mut D,     // FC02
293}
294
295impl<'a, H, I, C, D> ModbusCtx<'a, H, I, C, D>
296where
297    H: RegisterRead + RegisterWrite,
298    I: RegisterRead,
299    C: BitRead + BitWrite,
300    D: BitRead,
301{
302    /// pharse_pdu: 解析固定 8 字节 Modbus RTU 请求,并组装响应/异常
303    /// 返回值:
304    /// - 正常响应:返回 out_tx 中的长度
305    /// - 异常响应:写入 out_exc,并返回 5
306    pub fn pharse_pdu<const MAX_QTY: usize>(
307        &mut self,
308        req8: &[u8; 8],
309        out_tx: &mut [u8],
310        out_exc: &mut [u8; 5],
311    ) -> usize {
312        let unit_id = req8[0];
313        let func = req8[1];
314
315        // -------- CRC check --------
316        let expected_crc = u16::from_le_bytes([req8[6], req8[7]]);
317        let calc_crc = crc16_modbus(&req8[..6]);
318        if expected_crc != calc_crc {
319            // CRC 错:返回 ILLEGAL_DATA_VALUE,功能码用请求 func|0x80
320            return build_exception_resp_fixed::<5>(
321                out_exc,
322                unit_id,
323                func | 0x80,
324                exc::ILLEGAL_DATA_VALUE,
325            );
326        }
327
328        // -------- common fields --------
329        let start_addr = u16::from_be_bytes([req8[2], req8[3]]);
330        let quantity = u16::from_be_bytes([req8[4], req8[5]]);
331
332        match func {
333            0x01 => {
334                // quantity 校验
335                if quantity == 0 || (quantity as usize) > MAX_QTY {
336                    return build_exception_resp_fixed::<5>(
337                        out_exc,
338                        unit_id,
339                        func | 0x80,
340                        exc::ILLEGAL_DATA_VALUE,
341                    );
342                }
343                // FC01 Read Coils
344                let end = start_addr.wrapping_add(quantity.saturating_sub(1));
345                if !self.coils.is_valid(start_addr) || !self.coils.is_valid(end) {
346                    return build_exception_resp_fixed::<5>(
347                        out_exc,
348                        unit_id,
349                        func | 0x80,
350                        exc::ILLEGAL_DATA_ADDRESS,
351                    );
352                }
353                build_resp_bit_reads::<MAX_QTY, _>(
354                    out_tx, unit_id, 0x01, start_addr, quantity, self.coils,
355                )
356            }
357
358            0x02 => {
359                // quantity 校验
360                if quantity == 0 || (quantity as usize) > MAX_QTY {
361                    return build_exception_resp_fixed::<5>(
362                        out_exc,
363                        unit_id,
364                        func | 0x80,
365                        exc::ILLEGAL_DATA_VALUE,
366                    );
367                }
368                // FC02 Read Discrete Inputs
369                let end = start_addr.wrapping_add(quantity.saturating_sub(1));
370                if !self.ists.is_valid(start_addr) || !self.ists.is_valid(end) {
371                    return build_exception_resp_fixed::<5>(
372                        out_exc,
373                        unit_id,
374                        func | 0x80,
375                        exc::ILLEGAL_DATA_ADDRESS,
376                    );
377                }
378                build_resp_bit_reads::<MAX_QTY, _>(
379                    out_tx, unit_id, 0x02, start_addr, quantity, self.ists,
380                )
381            }
382
383            0x03 => {
384                // quantity 校验
385                if quantity == 0 || (quantity as usize) > MAX_QTY {
386                    return build_exception_resp_fixed::<5>(
387                        out_exc,
388                        unit_id,
389                        func | 0x80,
390                        exc::ILLEGAL_DATA_VALUE,
391                    );
392                }
393                // FC03 Read Holding Registers
394                let end = start_addr.wrapping_add(quantity.saturating_sub(1));
395                if !self.holdings.is_valid(start_addr) || !self.holdings.is_valid(end) {
396                    return build_exception_resp_fixed::<5>(
397                        out_exc,
398                        unit_id,
399                        func | 0x80,
400                        exc::ILLEGAL_DATA_ADDRESS,
401                    );
402                }
403                build_resp_regs::<MAX_QTY, _>(
404                    out_tx,
405                    unit_id,
406                    0x03,
407                    start_addr,
408                    quantity,
409                    self.holdings,
410                )
411            }
412
413            0x04 => {
414                // quantity 校验
415                if quantity == 0 || (quantity as usize) > MAX_QTY {
416                    return build_exception_resp_fixed::<5>(
417                        out_exc,
418                        unit_id,
419                        func | 0x80,
420                        exc::ILLEGAL_DATA_VALUE,
421                    );
422                }
423                // FC04 Read Input Registers
424                let end = start_addr.wrapping_add(quantity.saturating_sub(1));
425                if !self.inputs.is_valid(start_addr) || !self.inputs.is_valid(end) {
426                    return build_exception_resp_fixed::<5>(
427                        out_exc,
428                        unit_id,
429                        func | 0x80,
430                        exc::ILLEGAL_DATA_ADDRESS,
431                    );
432                }
433                build_resp_regs::<MAX_QTY, _>(
434                    out_tx,
435                    unit_id,
436                    0x04,
437                    start_addr,
438                    quantity,
439                    self.inputs,
440                )
441            }
442
443            0x05 => {
444                // FC05: addr=start_addr, value=quantity(0xFF00/0x0000)
445                let coil_value = quantity;
446
447                let bit = match coil_value {
448                    0xFF00 => true,
449                    0x0000 => false,
450                    _ => {
451                        return build_exception_resp_fixed::<5>(
452                            out_exc,
453                            unit_id,
454                            0x05 | 0x80,
455                            exc::ILLEGAL_DATA_VALUE,
456                        );
457                    }
458                };
459
460                if !self.coils.is_valid(start_addr) {
461                    return build_exception_resp_fixed::<5>(
462                        out_exc,
463                        unit_id,
464                        0x05 | 0x80,
465                        exc::ILLEGAL_DATA_ADDRESS,
466                    );
467                }
468
469                self.coils.set_bit(start_addr, bit);
470
471                // 正常响应:回显请求前6字节 + CRC
472                out_tx[0] = unit_id;
473                out_tx[1] = 0x05;
474                out_tx[2] = (start_addr >> 8) as u8;
475                out_tx[3] = (start_addr & 0xFF) as u8;
476                out_tx[4] = (coil_value >> 8) as u8;
477                out_tx[5] = (coil_value & 0xFF) as u8;
478
479                let crc = crc16_modbus(&out_tx[..6]);
480                out_tx[6] = (crc & 0xFF) as u8;
481                out_tx[7] = (crc >> 8) as u8;
482                8
483            }
484
485            0x06 => {
486                // FC06: 写单保持寄存器:addr=start_addr, value=quantity(任意 u16)
487                let reg_value = quantity;
488
489                if !self.holdings.is_valid(start_addr) {
490                    return build_exception_resp_fixed::<5>(
491                        out_exc,
492                        unit_id,
493                        0x06 | 0x80,
494                        exc::ILLEGAL_DATA_ADDRESS,
495                    );
496                }
497
498                self.holdings.set_reg(start_addr, reg_value);
499
500                // 正常响应:回显请求前6字节 + CRC
501                out_tx[0] = unit_id;
502                out_tx[1] = 0x06;
503                out_tx[2] = (start_addr >> 8) as u8;
504                out_tx[3] = (start_addr & 0xFF) as u8;
505                out_tx[4] = (reg_value >> 8) as u8;
506                out_tx[5] = (reg_value & 0xFF) as u8;
507
508                let crc = crc16_modbus(&out_tx[..6]);
509                out_tx[6] = (crc & 0xFF) as u8;
510                out_tx[7] = (crc >> 8) as u8;
511                8
512            }
513
514            _ => {
515                // 不支持功能码
516                build_exception_resp_fixed::<5>(
517                    out_exc,
518                    unit_id,
519                    func | 0x80,
520                    exc::ILLEGAL_FUNCTION,
521                )
522            }
523        }
524    }
525}
526
527// ---------------- helper:异常帧(固定 5 字节) ----------------
528fn build_exception_resp_fixed<const BUF: usize>(
529    out_exc: &mut [u8; 5],
530    unit_id: u8,
531    function_exception: u8,
532    exception_code: u8,
533) -> usize {
534    out_exc[0] = unit_id;
535    out_exc[1] = function_exception;
536    out_exc[2] = exception_code;
537
538    let crc = crc16_modbus(&out_exc[..3]);
539    out_exc[3] = (crc & 0xFF) as u8;
540    out_exc[4] = (crc >> 8) as u8;
541    5
542}
543// ---------------- helper:reg 读取响应(FC03/FC04) ----------------
544// out_tx 需要至少 >= 3 + MAX_QTY*2 + 2
545fn build_resp_regs<const MAX_QTY: usize, R: RegisterRead>(
546    out_tx: &mut [u8],
547    unit_id: u8,
548    func: u8,
549    start_addr: u16,
550    quantity: u16,
551    regs: &R,
552) -> usize {
553    let qty = quantity as usize;
554
555    out_tx[0] = unit_id;
556    out_tx[1] = func;
557    out_tx[2] = (qty as u8) * 2;
558
559    for i in 0..qty {
560        let addr = start_addr.wrapping_add(i as u16);
561        let v = regs.get(addr);
562        let base = 3 + i * 2;
563        out_tx[base] = (v >> 8) as u8;
564        out_tx[base + 1] = (v & 0xFF) as u8;
565    }
566
567    let body_len = 3 + qty * 2;
568    let crc = crc16_modbus(&out_tx[..body_len]);
569    out_tx[body_len] = (crc & 0xFF) as u8;
570    out_tx[body_len + 1] = (crc >> 8) as u8;
571
572    body_len + 2
573}