├── README
├── stdint.h
├── modbus-local.h
├── modbus-psoc.c
├── dma_buffer.h
├── dma_buffer.c
├── modbus.h
├── main.c
└── modbus.c
/README:
--------------------------------------------------------------------------------
1 | This is a port of Stéphane Raimbault's Modbus library, libmodbus:
2 | http://libmodbus.org/
3 |
4 | The library has been modified so that it may be used as embedded software on
5 | a microcontroller.
6 |
7 | The port has been extensively tested on a Cypress CY8C3245 device.
8 |
9 | Files:
10 |
11 | main.c - Top level application code.
12 | dma_buffer.c/h - DMA safe ping-pong buffer for interfacing with Cypress USB
13 | modbus-local.h - Per-application modbus constants - customise for each use
14 | modbus-psoc.c - Cypress PSoC specific code - port this to your uC architecture
15 | modbus.c - The modbus stack, based on libmodbus
16 | modbus.c - The modbus stack API
17 | stdint.h - Use this if your build environment doesnt provide uint8_t...
18 |
--------------------------------------------------------------------------------
/stdint.h:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2016 Kim Taylor
2 | *
3 | * This file is part of hbc_mac.
4 | *
5 | * hbc_mac is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * hbc_mac is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with hbc_mac. If not, see .
17 | */
18 |
19 | #define int8_t int8
20 | #define uint8_t uint8
21 | #define int16_t int16
22 | #define uint16_t uint16
23 | #define int32_t int32
24 | #define uint32_t uint32
25 |
--------------------------------------------------------------------------------
/modbus-local.h:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2016 Kim Taylor
2 | *
3 | * Customise this module for your Modbus implementation
4 | *
5 | * hbc_mac is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * hbc_mac is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with hbc_mac. If not, see .
17 | */
18 |
19 | #define MODBUS_SLAVE_STRING "USB-Modbus Bridge"
20 |
21 | #define MODBUS_MAX_PACKET_LENGTH 64
22 |
23 | #define MODBUS_PROCESS_CONFIRMATION 1
24 | #define MODBUS_USE_FUNCTION_POINTERS 0
25 | #define MODBUS_FORWARD_PACKETS 1
26 |
27 | #define MODBUS_WRITE_FUNC USBFS_PutData
28 | #define MODBUS_READ_READY_FUNC usb_read_ready
29 | #define MODBUS_READ_FUNC usb_get_byte
30 | #define MODBUS_PROCESS_FUNC modbus_respond
31 | #define MODBUS_FORWARD_FUNC RS485_PutArray
32 |
33 | uint8_t usb_read_ready(void);
34 | uint8_t usb_get_byte(void);
35 |
36 |
--------------------------------------------------------------------------------
/modbus-psoc.c:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2016 Kim Taylor
2 | *
3 | * This module holds non-portable code for the Modbus library.
4 | *
5 | * hbc_mac is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * hbc_mac is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with hbc_mac. If not, see .
17 | */
18 |
19 | static uint8_t _modbus_timer_finished = 0;
20 |
21 | CY_ISR(modbus_timeout) {
22 | _modbus_timer_finished = 1;
23 | }
24 |
25 | void _modbus_timer_stop_and_reset(void) {
26 | MODBUS_TIMER_Stop();
27 | /* Reset must be configured for pulse operation */
28 | MODBUS_TIMER_RESET_Write(1);
29 | _modbus_timer_finished = 0;
30 | }
31 |
32 | void _modbus_timer_start(void) {
33 | MODBUS_TIMER_Enable();
34 | }
35 |
36 | void _modbus_timer_init(void) {
37 | MODBUS_TIMER_Init();
38 | MODBUS_TIMEOUT_StartEx(modbus_timeout);
39 | }
40 |
--------------------------------------------------------------------------------
/dma_buffer.h:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2016 Kim Taylor
2 | *
3 | * Ping-Pong buffer system.
4 | *
5 | * hbc_mac is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * hbc_mac is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with hbc_mac. If not, see .
17 | */
18 |
19 | #define USB_BUFFER_SIZE 64
20 |
21 | struct buf_s {
22 | uint8_t _data[2][USB_BUFFER_SIZE];
23 | uint8_t index[2];
24 | uint8_t count[2];
25 | uint8_t base;
26 | };
27 |
28 | extern void buf_in(struct buf_s *buffer, uint8_t val);
29 | extern uint8_t buf_out(struct buf_s *buffer);
30 |
31 | /* Must be called with interrupts disabled.
32 | * Switches the buffer that is to be written by buf_in
33 | * Returns the address of a buffer that may be transferred via DMA */
34 | extern uint8_t *buf_switch(struct buf_s *buffer, uint8_t *size);
35 | extern uint8_t *buf_get(struct buf_s *buffer);
36 | extern void buf_update(struct buf_s *buffer, uint8_t *dma_buf,
37 | uint8_t *start, uint8_t size);
38 | extern uint8_t buf_empty(struct buf_s *buffer, uint8_t *dma_buf);
39 |
40 | extern uint8_t buf_full(struct buf_s *buffer);
41 | extern uint8_t buf_empty_switch(struct buf_s *buffer);
42 |
43 | extern void buf_clear(struct buf_s *buffer, uint8_t base);
44 |
--------------------------------------------------------------------------------
/dma_buffer.c:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2016 Kim Taylor
2 | *
3 | * Ping-Pong buffer system.
4 | *
5 | * hbc_mac is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * hbc_mac is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with hbc_mac. If not, see .
17 | */
18 |
19 | #ifdef DEBUG_DMA
20 | #include
21 | #include
22 | #else
23 | #include
24 | #include "stdint.h"
25 | #endif
26 |
27 | #include "dma_buffer.h"
28 |
29 | void buf_in(struct buf_s *buffer, uint8_t val) {
30 | buffer->count[buffer->base]++;
31 | buffer->_data[buffer->base][buffer->index[buffer->base]++] = val;
32 | }
33 |
34 | uint8_t buf_out(struct buf_s *buffer) {
35 | buffer->count[buffer->base]--;
36 | return buffer->_data[buffer->base][buffer->index[buffer->base]++];
37 | }
38 |
39 | void buf_clear(struct buf_s *buffer, uint8_t base) {
40 | buffer->count[base] = 0;
41 | buffer->index[base] = 0;
42 | }
43 |
44 | /* Must be called with interrupts disabled.
45 | * Switches the buffer that is to be written by buf_in
46 | * Returns the address of a buffer that may be transferred via DMA */
47 | uint8_t *buf_switch(struct buf_s *buffer, uint8_t *size) {
48 | uint8_t old_base = buffer->base;
49 |
50 | if (size) *size = buffer->index[old_base];
51 | buf_clear(buffer, old_base);
52 | buffer->base = !buffer->base;
53 |
54 | return buffer->_data[old_base];
55 | }
56 |
57 | /* A race condition exists here. The DMA controller will start writing into the
58 | * returned buffer, while an interrupt might start reading from it. We rely on
59 | * the fact that the DMA controller will be faster than the interrupt reads.
60 | * Ensure that the DMA controller has been fully set up before enabling
61 | * interrupts.
62 | * The PSoC API seems to ignore this as USBUART_ReadOutEP returns immediately */
63 | uint8_t *buf_get(struct buf_s *buffer) {
64 | if (!buffer->count[0]) return buffer->_data[0];
65 | if (!buffer->count[1]) return buffer->_data[1];
66 | return NULL;
67 | }
68 |
69 | static uint8_t which_buf(struct buf_s *buffer, uint8_t *dma_buf) {
70 | return dma_buf == buffer->_data[1] ? 1 : 0;
71 | }
72 |
73 | void buf_update(struct buf_s *buffer, uint8_t *dma_buf,
74 | uint8_t *start, uint8_t size) {
75 | uint8_t base = which_buf(buffer, dma_buf);
76 | buffer->index[base] = start - buffer->_data[base];
77 | buffer->count[base] = size;
78 | }
79 |
80 | uint8_t buf_empty(struct buf_s *buffer, uint8_t *dma_buf) {
81 | uint8_t base = which_buf(buffer, dma_buf);
82 | return !buffer->count[base];
83 | }
84 |
85 | uint8_t buf_full(struct buf_s *buffer) {
86 | return buffer->index[buffer->base] == USB_BUFFER_SIZE;
87 | }
88 |
89 | uint8_t buf_empty_switch(struct buf_s *buffer) {
90 | /* First try and switch buffers */
91 | if (!buffer->count[buffer->base])
92 | buf_switch(buffer, NULL);
93 |
94 | return !buffer->count[buffer->base];
95 | }
96 |
--------------------------------------------------------------------------------
/modbus.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Kim Taylor
3 | * Based on libmodbus by Stéphane Raimbault
4 | *
5 | * This library is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 2.1 of the License, or (at your option) any later version.
9 | *
10 | * This library is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | * Lesser General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Lesser General Public
16 | * License along with this library; if not, write to the Free Software
17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 | */
19 |
20 | /* Function codes */
21 | #define _FC_READ_COILS 0x01
22 | #define _FC_READ_DISCRETE_INPUTS 0x02
23 | #define _FC_READ_HOLDING_REGISTERS 0x03
24 | #define _FC_READ_INPUT_REGISTERS 0x04
25 | #define _FC_WRITE_SINGLE_COIL 0x05
26 | #define _FC_WRITE_SINGLE_REGISTER 0x06
27 | #define _FC_READ_EXCEPTION_STATUS 0x07
28 | #define _FC_WRITE_MULTIPLE_COILS 0x0F
29 | #define _FC_WRITE_MULTIPLE_REGISTERS 0x10
30 | #define _FC_REPORT_SLAVE_ID 0x11
31 | #define _FC_MASK_WRITE_REGISTER 0x16
32 | #define _FC_WRITE_AND_READ_REGISTERS 0x17
33 |
34 | #define MODBUS_EXCEPTION 0x80
35 | enum {
36 | MODBUS_EXCEPTION_ILLEGAL_FUNCTION = 0x01,
37 | MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS,
38 | MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
39 | MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE,
40 | MODBUS_EXCEPTION_ACKNOWLEDGE,
41 | MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY,
42 | MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE,
43 | MODBUS_EXCEPTION_MEMORY_PARITY,
44 | MODBUS_EXCEPTION_NOT_DEFINED,
45 | MODBUS_EXCEPTION_GATEWAY_PATH,
46 | MODBUS_EXCEPTION_GATEWAY_TARGET,
47 | MODBUS_EXCEPTION_MAX,
48 | };
49 |
50 | /* Offsets from the user function perspective */
51 | #define MODBUS_MSG_LEN_OFFSET 0
52 | #define MODBUS_MSG_ADDR_OFFSET 0
53 | #define MODBUS_MSG_NR_REGS_OFFSET 2
54 |
55 | #define MODBUS_WRITE_RESP_SIZE 4
56 |
57 | /* Helper functions */
58 | extern uint16_t mb_address;
59 | extern uint8_t mb_nr_regs;
60 | extern uint8_t mb_resp_bytes;
61 | extern int8_t modbus_slave_id_response(uint8_t *msg);
62 | extern void mb_data_init(uint8_t *msg, uint8_t fn);
63 | extern void mb_data_resp(uint8_t *msg, uint16_t val);
64 | extern uint16_t mb_data_next(uint8_t *msg);
65 |
66 | /* Limits for user function */
67 | #define MAX_NR_REGS(fn) ( fn == _FC_READ_HOLDING_REGISTERS ? \
68 | (MODBUS_MAX_PACKET_LENGTH - 5)/2 : \
69 | fn == _FC_WRITE_MULTIPLE_REGISTERS ? \
70 | (MODBUS_MAX_PACKET_LENGTH - 9)/2 : \
71 | 0)
72 |
73 |
74 | #define MB_UINT16_T(ptr) ((uint16_t) *ptr << 8 | *(ptr + 1))
75 |
76 | static uint16_t mb_buf_to_val(uint8_t *buf) {
77 | return (uint16_t) buf[0] << 8 | buf[1];
78 | }
79 |
80 | static uint8_t mb_val_to_buf(uint8_t *buf, uint16_t val) {
81 | buf[0] = val >> 8;
82 | buf[1] = val;
83 | return 2;
84 | }
85 |
86 | typedef void (*modbus_write_t) (const uint8_t *msg, uint8_t bytes);
87 | typedef uint8_t (*modbus_read_t) (void);
88 | typedef uint8_t (*modbus_read_ready_t) (void);
89 | typedef int8_t (*modbus_process_t) (uint8_t function, uint8_t *msg);
90 | typedef void (*modbus_forward_t) (const uint8_t *msg, uint8_t bytes);
91 |
92 | #if MODBUS_USE_FUNCTION_POINTERS
93 | extern void modbus_init(
94 | uint8_t slave_addr,
95 | modbus_write_t modbus_write,
96 | modbus_read_ready_t modbus_read_ready,
97 | modbus_read_t modbus_read,
98 | modbus_process_t modbus_process,
99 | modbus_forward_t modbus_forward);
100 | #else
101 | extern void modbus_init(uint8_t slave_addr);
102 | #endif
103 |
104 | extern uint8_t modbus_poll(void);
105 |
106 | #define MODBUS_ACTIVE_HIGH 1
107 | #define MODBUS_ACTIVE_LOW 0
108 | extern void modbus_tx_led(void (*led_enable)(uint8_t val), uint8_t val);
109 | extern void modbus_rx_led(void (*led_enable)(uint8_t val), uint8_t val);
110 |
--------------------------------------------------------------------------------
/main.c:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2016 Kim Taylor
2 | *
3 | * Example application acts as a bridge between USB and modbus. Tested using
4 | * Cypress CY8C3245-PVI150
5 | *
6 | * hbc_mac is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * hbc_mac is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with hbc_mac. If not, see .
18 | */
19 |
20 | #include
21 |
22 | #include "stdint.h"
23 | #include "modbus-local.h"
24 | #include "modbus.h"
25 | #include "dma_buffer.h"
26 |
27 | #define MODBUS_SLAVE_ADDR 20
28 |
29 | static struct buf_s wr_buf;
30 | static volatile struct buf_s rd_buf;
31 |
32 | static void usb_poll(void) {
33 | if (USBFS_GetConfiguration()) {
34 | if (!usb_up) USBFS_CDC_Init();
35 | usb_up = 1;
36 | } else {
37 | usb_up = 0;
38 | }
39 | }
40 |
41 | uint8_t usb_get_byte(void) {
42 | return buf_out(&wr_buf);
43 | }
44 |
45 | uint8_t usb_read_ready(void) {
46 | return !buf_empty_switch(&wr_buf);
47 | }
48 |
49 | static uint16_t test_var;
50 |
51 | static int8_t modbus_write_regs(uint8_t *msg, uint8_t function) {
52 | mb_data_init(msg, function);
53 |
54 | if (mb_nr_regs > MAX_NR_REGS(_FC_WRITE_MULTIPLE_REGISTERS)) return -1;
55 |
56 | while (mb_nr_regs) {
57 | switch (mb_address) {
58 | case 0:
59 | test_var = mb_data_next(msg);
60 | break;
61 |
62 | default:
63 | return -1;
64 | }
65 | }
66 |
67 | return MODBUS_WRITE_RESP_SIZE;
68 | }
69 |
70 | static int8_t modbus_read_regs(uint8_t *msg) {
71 | mb_data_init(msg, _FC_READ_HOLDING_REGISTERS);
72 |
73 | if (mb_nr_regs > MAX_NR_REGS(_FC_READ_HOLDING_REGISTERS)) return -1;
74 |
75 | while (mb_nr_regs) {
76 | switch (mb_address) {
77 | case MB_VERSION:
78 | if (mb_nr_regs < MB_VERSION_REGS) return -1;
79 | mb_data_resp(msg, GIT_REVISION >> 16);
80 | mb_data_resp(msg, GIT_REVISION);
81 | break;
82 |
83 | case 0:
84 | mb_data_resp(msg, test_var);
85 | break;
86 |
87 | default:
88 | return -1;
89 | }
90 | }
91 |
92 | return mb_resp_bytes;
93 | }
94 |
95 | int8_t modbus_respond(uint8_t function, uint8_t *msg) {
96 | switch (function) {
97 | case _FC_WRITE_SINGLE_REGISTER:
98 | case _FC_WRITE_MULTIPLE_REGISTERS:
99 | return modbus_write_regs(msg, function);
100 | case _FC_READ_HOLDING_REGISTERS:
101 | return modbus_read_regs(msg);
102 | case _FC_REPORT_SLAVE_ID:
103 | return modbus_slave_id_response(msg);
104 | }
105 | return -1;
106 | }
107 |
108 | CY_ISR(rs485_rx_isr) {
109 | while (RS485_GetRxBufferSize()) {
110 | if (buf_full(&rd_buf)) break;
111 | buf_in(&rd_buf, RS485_ReadRxData());
112 | }
113 | }
114 |
115 | static void usb_to_modbus(void) {
116 | uint8_t bytes;
117 | uint8_t *dma_buf;
118 |
119 | if (usb_up && (bytes = USBFS_GetCount())) {
120 | if ((dma_buf = buf_get(&wr_buf))) {
121 | USBFS_GetData(dma_buf, bytes);
122 | buf_update(&wr_buf, dma_buf, dma_buf, bytes);
123 | /* Packets not destined for this slave are automatically
124 | * forwarded */
125 | }
126 | }
127 | }
128 |
129 | static void rs485_to_usb(void) {
130 | static uint8_t bytes = 0;
131 | static uint8_t *dma_buf;
132 |
133 | if (!bytes) {
134 | RS485_RX_ISR_Disable();
135 |
136 | if (buf_full(&rd_buf)) RS485_RX_ISR_SetPending();
137 | dma_buf = buf_switch(&rd_buf, &bytes);
138 |
139 | RS485_RX_ISR_Enable();
140 | }
141 |
142 | if (bytes) {
143 | if (!USBFS_CDCIsReady()) return;
144 | USBFS_PutData(dma_buf, bytes);
145 | bytes = 0;
146 | }
147 | }
148 |
149 | int main() {
150 | USBFS_Start(1, USBFS_DWR_VDDD_OPERATION);
151 | CyGlobalIntEnable;
152 |
153 | RS485_Start();
154 | RS485_RX_ISR_StartEx(rs485_rx_isr);
155 | BUTTON_ISR_StartEx(button_isr);
156 |
157 | modbus_init(MODBUS_SLAVE_ADDR);
158 | modbus_tx_led(STATUS_LED_0_Write, MODBUS_ACTIVE_HIGH);
159 | modbus_rx_led(STATUS_LED_1_Write, MODBUS_ACTIVE_HIGH);
160 |
161 | while (1) {
162 |
163 | /* Store USB data in buffer for Modbus */
164 | usb_to_modbus();
165 |
166 | /* Forward RS485 bus data to Modbus master */
167 | rs485_to_usb();
168 |
169 | modbus_poll();
170 | usb_poll();
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/modbus.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Kim Taylor
3 | * Based on libmodbus by Stéphane Raimbault
4 | *
5 | * This library is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 2.1 of the License, or (at your option) any later version.
9 | *
10 | * This library is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | * Lesser General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Lesser General Public
16 | * License along with this library; if not, write to the Free Software
17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 | */
19 |
20 | #include
21 |
22 | #include "stdint.h"
23 | #include "modbus-local.h"
24 | #include "modbus.h"
25 |
26 | static uint8_t modbus_msg[MODBUS_MAX_PACKET_LENGTH];
27 | #if MODBUS_USE_FUNCTION_POINTERS
28 | static modbus_write_t _modbus_write;
29 | static modbus_read_ready_t _modbus_read_ready;
30 | static modbus_read_t _modbus_read;
31 | static modbus_process_t _modbus_process;
32 | static modbus_forward_t _modbus_forward;
33 | #else
34 | #define _modbus_write MODBUS_WRITE_FUNC
35 | #define _modbus_read_ready MODBUS_READ_READY_FUNC
36 | #define _modbus_read MODBUS_READ_FUNC
37 | #define _modbus_process MODBUS_PROCESS_FUNC
38 | #define _modbus_forward MODBUS_FORWARD_FUNC
39 | int8_t _modbus_process(uint8_t function, uint8_t *msg);
40 | #endif
41 |
42 | #include "modbus-psoc.c"
43 |
44 | static void (*_tx_led_enable)(uint8_t val) = NULL;
45 | static void (*_rx_led_enable)(uint8_t val) = NULL;
46 | static uint8_t _tx_led_enable_val;
47 | static uint8_t _rx_led_enable_val;
48 | static uint8_t _slave_addr;
49 |
50 | #define CRC16_POLY 0xA001
51 | #define CRC16_INIT 0xFFFF
52 | #define CRC_0(crc) (crc & 0xff)
53 | #define CRC_1(crc) (crc >> 8)
54 |
55 | #define MODBUS_SLAVE_OFFSET 0
56 | #define MODBUS_FUNCTION_OFFSET 1
57 |
58 | static uint16_t crc16_update(uint16_t crc, uint8_t _data) {
59 | uint8_t i;
60 |
61 | crc ^= _data;
62 |
63 | for (i = 0; i < 8; i++) {
64 | if (crc & 1) crc = (crc >> 1) ^ CRC16_POLY;
65 | else crc >>= 1;
66 | }
67 |
68 | return crc;
69 | }
70 |
71 | static uint16_t crc16_bytes(uint8_t *_data, uint8_t bytes) {
72 | uint16_t crc = CRC16_INIT;
73 | while (bytes--) {
74 | crc = crc16_update(crc, *(_data++));
75 | }
76 | return crc;
77 | }
78 |
79 | #define HEADER_FUNCTION_LENGTH 2
80 | #define CRC_LENGTH 2
81 | /* 3 steps are used to parse the query */
82 | typedef enum {
83 | _STEP_FUNCTION,
84 | _STEP_META,
85 | _STEP_DATA,
86 | } _step_t;
87 |
88 | typedef enum {
89 | MSG_INDICATION,
90 | MSG_CONFIRMATION,
91 | } msg_type_t;
92 |
93 | static uint8_t compute_meta_length_after_function(
94 | uint8_t function, msg_type_t msg_type) {
95 | uint8_t length;
96 |
97 | if (msg_type == MSG_INDICATION) {
98 | if (function <= _FC_WRITE_SINGLE_REGISTER) {
99 | length = 4;
100 | } else if (function == _FC_WRITE_MULTIPLE_COILS ||
101 | function == _FC_WRITE_MULTIPLE_REGISTERS) {
102 | length = 5;
103 | } else if (function == _FC_MASK_WRITE_REGISTER) {
104 | length = 6;
105 | } else if (function == _FC_WRITE_AND_READ_REGISTERS) {
106 | length = 9;
107 | } else {
108 | /* _FC_READ_EXCEPTION_STATUS, _FC_REPORT_SLAVE_ID */
109 | length = 0;
110 | }
111 | #if MODBUS_PROCESS_CONFIRMATION
112 | } else {
113 | /* MSG_CONFIRMATION */
114 | switch (function) {
115 | case _FC_WRITE_SINGLE_COIL:
116 | case _FC_WRITE_SINGLE_REGISTER:
117 | case _FC_WRITE_MULTIPLE_COILS:
118 | case _FC_WRITE_MULTIPLE_REGISTERS:
119 | length = 4;
120 | break;
121 | case _FC_MASK_WRITE_REGISTER:
122 | length = 6;
123 | break;
124 | default:
125 | length = 1;
126 | }
127 | #endif
128 | }
129 |
130 | return length;
131 | }
132 |
133 | static uint8_t compute_data_length_after_meta(
134 | uint8_t *modbus_msg, msg_type_t msg_type) {
135 | uint8_t function = modbus_msg[1];
136 | uint8_t length;
137 |
138 | if (msg_type == MSG_INDICATION) {
139 | switch (function) {
140 | case _FC_WRITE_MULTIPLE_COILS:
141 | case _FC_WRITE_MULTIPLE_REGISTERS:
142 | length = modbus_msg[6];
143 | break;
144 | case _FC_WRITE_AND_READ_REGISTERS:
145 | length = modbus_msg[10];
146 | break;
147 | default:
148 | length = 0;
149 | }
150 | #if MODBUS_PROCESS_CONFIRMATION
151 | } else {
152 | /* MSG_CONFIRMATION */
153 | if (function <= _FC_READ_INPUT_REGISTERS ||
154 | function == _FC_REPORT_SLAVE_ID ||
155 | function == _FC_WRITE_AND_READ_REGISTERS) {
156 | length = modbus_msg[2];
157 | } else {
158 | length = 0;
159 | }
160 | #endif
161 | }
162 |
163 | length += CRC_LENGTH;
164 |
165 | return length;
166 | }
167 |
168 | static void modbus_reply(int8_t bytes) {
169 | uint8_t function;
170 | uint16_t crc;
171 |
172 | if (_tx_led_enable) _tx_led_enable(_tx_led_enable_val);
173 | function = modbus_msg[MODBUS_FUNCTION_OFFSET];
174 |
175 | bytes -= HEADER_FUNCTION_LENGTH;
176 | if ((bytes = _modbus_process(function,
177 | modbus_msg + HEADER_FUNCTION_LENGTH)) < 0) {
178 | function |= MODBUS_EXCEPTION;
179 | modbus_msg[HEADER_FUNCTION_LENGTH] = MODBUS_EXCEPTION_ILLEGAL_FUNCTION;
180 | bytes = 1;
181 | }
182 | bytes += HEADER_FUNCTION_LENGTH;
183 |
184 | modbus_msg[MODBUS_FUNCTION_OFFSET] = function;
185 | crc = crc16_bytes(modbus_msg, bytes);
186 | modbus_msg[bytes++] = CRC_0(crc);
187 | modbus_msg[bytes++] = CRC_1(crc);
188 | _modbus_write(modbus_msg, bytes);
189 | if (_tx_led_enable) _tx_led_enable(!_tx_led_enable_val);
190 | }
191 |
192 | uint8_t modbus_poll(void) {
193 | static uint8_t length_to_read = HEADER_FUNCTION_LENGTH;
194 | static uint8_t msg_length = 0;
195 | static _step_t step = _STEP_FUNCTION;
196 | static msg_type_t msg_type = MSG_INDICATION;
197 | uint8_t retval = 0;
198 | uint16_t crc;
199 | uint8_t slave;
200 |
201 | if (msg_length && _modbus_timer_finished) goto reset;
202 |
203 | if (!_modbus_read_ready()) return 0;
204 |
205 | modbus_msg[msg_length++] = _modbus_read();
206 | length_to_read--;
207 |
208 | if (msg_length == 1) {
209 | _modbus_timer_start();
210 | if (_rx_led_enable) _rx_led_enable(_rx_led_enable_val);
211 | }
212 |
213 | if (!length_to_read) {
214 | switch (step) {
215 | case _STEP_FUNCTION:
216 | /* Function code position */
217 | length_to_read = compute_meta_length_after_function(
218 | modbus_msg[1], msg_type);
219 | if (length_to_read != 0) {
220 | step = _STEP_META;
221 | break;
222 | } /* else switches straight to the next step */
223 | case _STEP_META:
224 | length_to_read = compute_data_length_after_meta(modbus_msg,
225 | msg_type);
226 | if ((msg_length + length_to_read) > MODBUS_MAX_PACKET_LENGTH)
227 | goto reset;
228 | step = _STEP_DATA;
229 | break;
230 | default:
231 | break;
232 | }
233 | }
234 |
235 | if (!length_to_read) {
236 | /* All data collected by here */
237 | crc = crc16_bytes(modbus_msg, msg_length);
238 | if (!crc) retval = msg_length - CRC_LENGTH;
239 | else goto reset;
240 |
241 | /* If the slave address does not match this device, expect and ignore a
242 | * confirmation message from another device on the network. */
243 | if (msg_type == MSG_CONFIRMATION) goto reset;
244 |
245 | slave = modbus_msg[MODBUS_SLAVE_OFFSET];
246 | if (slave != _slave_addr) {
247 | #if MODBUS_FORWARD_PACKETS
248 | /* Forward this packet on to the next interface */
249 | _modbus_forward(modbus_msg, msg_length);
250 | goto reset;
251 | #endif
252 | /* Leave the timer running so that a reset will occur if no
253 | * device ever responds */
254 | msg_type = MSG_CONFIRMATION;
255 | goto reset_with_timeout;
256 | }
257 |
258 | modbus_reply(retval);
259 |
260 | reset:
261 | _modbus_timer_stop_and_reset();
262 | msg_type = MSG_INDICATION;
263 | reset_with_timeout:
264 | msg_length = 0;
265 | length_to_read = HEADER_FUNCTION_LENGTH;
266 | step = _STEP_FUNCTION;
267 | if (_rx_led_enable) _rx_led_enable(!_rx_led_enable_val);
268 | }
269 |
270 | return retval;
271 | }
272 |
273 | #if MODBUS_USE_FUNCTION_POINTERS
274 | void modbus_init(
275 | uint8_t slave_addr,
276 | modbus_write_t modbus_write,
277 | modbus_read_ready_t modbus_read_ready,
278 | modbus_read_t modbus_read,
279 | modbus_process_t modbus_process) {
280 | _modbus_write = modbus_write;
281 | _modbus_read_ready = modbus_read_ready;
282 | _modbus_read = modbus_read;
283 | _modbus_process = modbus_process;
284 | #else
285 | void modbus_init(uint8_t slave_addr) {
286 | #endif
287 | _slave_addr = slave_addr;
288 | _modbus_timer_init();
289 | }
290 |
291 | void modbus_tx_led(void (*led_enable)(uint8_t val), uint8_t val) {
292 | _tx_led_enable = led_enable;
293 | _tx_led_enable_val = val;
294 | }
295 |
296 | void modbus_rx_led(void (*led_enable)(uint8_t val), uint8_t val) {
297 | _rx_led_enable = led_enable;
298 | _rx_led_enable_val = val;
299 | }
300 |
301 | /* Helper functions */
302 | int8_t modbus_slave_id_response(uint8_t *msg) {
303 | msg[0] = sizeof(MODBUS_SLAVE_STRING) + 2;
304 | msg[1] = _slave_addr;
305 | msg[2] = 0xff; /* Run indicator status */
306 | memcpy(&msg[3], MODBUS_SLAVE_STRING, sizeof(MODBUS_SLAVE_STRING));
307 | return sizeof(MODBUS_SLAVE_STRING) + 3;
308 | }
309 |
310 | /* Iterators */
311 | uint16_t mb_address;
312 | uint8_t mb_nr_regs;
313 | uint8_t mb_resp_bytes;
314 | static uint8_t _mb_data_offset;
315 |
316 | void mb_data_init(uint8_t *msg, uint8_t fn) {
317 | mb_address = mb_buf_to_val(&msg[MODBUS_MSG_ADDR_OFFSET]);
318 | mb_nr_regs = mb_buf_to_val(&msg[MODBUS_MSG_NR_REGS_OFFSET]);
319 | switch (fn) {
320 | case _FC_READ_HOLDING_REGISTERS:
321 | _mb_data_offset = 1;
322 | mb_resp_bytes = 1 + mb_nr_regs * 2;
323 | msg[MODBUS_MSG_LEN_OFFSET] = mb_resp_bytes - 1;
324 | return;
325 | case _FC_WRITE_SINGLE_REGISTER:
326 | _mb_data_offset = 2;
327 | mb_nr_regs = 1;
328 | return;
329 | case _FC_WRITE_MULTIPLE_REGISTERS:
330 | _mb_data_offset = 5;
331 | return;
332 | }
333 | }
334 |
335 | void mb_data_resp(uint8_t *msg, uint16_t val) {
336 | _mb_data_offset += mb_val_to_buf(&msg[_mb_data_offset], val);
337 | mb_nr_regs--;
338 | mb_address++;
339 | }
340 |
341 | uint16_t mb_data_next(uint8_t *msg) {
342 | uint16_t retval = mb_buf_to_val(&msg[_mb_data_offset]);
343 | mb_nr_regs--;
344 | mb_address++;
345 | _mb_data_offset += 2;
346 | return retval;
347 | }
348 |
--------------------------------------------------------------------------------