├── 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 | --------------------------------------------------------------------------------