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