-Buildbotics CNC Controller Firmware Change Log
+Buildbotics CNC Controller Firmware Changelog
==============================================
## v0.3.21
- Implemented M70-M73 modal state save/restore.
+ - Added support for modbus VFDs.
## v0.3.20
- Eliminated drift caused by miscounting half microsteps.
- Start Web server eariler in case of Python coding errors
-Change log not maintained in previous versions. See git commit log.
+Changelog not maintained in previous versions. See git commit log.
DRV8711_CTRL_EXSTALL_bm)
-// Huanyang settings
-#define HUANYANG_PORT USARTD1
-#define HUANYANG_DRE_vect USARTD1_DRE_vect
-#define HUANYANG_TXC_vect USARTD1_TXC_vect
-#define HUANYANG_RXC_vect USARTD1_RXC_vect
-#define HUANYANG_TIMEOUT 50 // ms. response timeout
-#define HUANYANG_RETRIES 4 // Number of retries before failure
-#define HUANYANG_ID 1 // Default ID
+// RS485 settings
+#define RS485_PORT USARTD1
+#define RS485_DRE_vect USARTD1_DRE_vect
+#define RS485_TXC_vect USARTD1_TXC_vect
+#define RS485_RXC_vect USARTD1_RXC_vect
+
+
+// Modbus settings
+#define MODBUS_TIMEOUT 50 // ms. response timeout
+#define MODBUS_RETRIES 4 // Number of retries before failure
+#define MODBUS_BUF_SIZE 64 // Max bytes in rx/tx buffers
// Serial settings
#include "huanyang.h"
#include "config.h"
-#include "rtc.h"
+#include "modbus.h"
#include "report.h"
-#include "pgmspace.h"
-#include <avr/io.h>
-#include <avr/interrupt.h>
-#include <util/crc16.h>
-
-#include <stdio.h>
#include <string.h>
#include <math.h>
+
/*
- Huanyang supposedly is not quite Modbus compliant.
+ Huanyang is not quite Modbus compliant.
Message format is:
- [id][cmd][length][data][checksum]
+ [id][func][length][data][checksum]
Where:
id - 1-byte Peer ID
- cmd - 1-byte One of hy_cmd_t
+ func - 1-byte One of hy_func_t
length - 1-byte Length data in bytes
data - length bytes - Command arguments
checksum - 16-bit CRC: x^16 + x^15 + x^2 + 1 (0xa001) initial: 0xffff
// See VFD manual pg56 3.1.3
typedef enum {
- HUANYANG_FUNC_READ = 1, // Use hy_func_addr_t
+ HUANYANG_FUNC_READ = 1, // Use hy_addr_t
HUANYANG_FUNC_WRITE, // ?
HUANYANG_CTRL_WRITE, // Use hy_ctrl_state_t
HUANYANG_CTRL_READ, // Use hy_ctrl_addr_t
HUANYANG_RESERVED_1,
HUANYANG_RESERVED_2,
HUANYANG_LOOP_TEST,
-} hy_cmd_t;
+} hy_func_t;
// See VFD manual pg57 3.1.3.d
} hy_ctrl_status_t;
-typedef bool (*next_command_cb_t)(int index);
+static struct {
+ uint8_t id;
+ uint8_t state;
+ hy_func_t func;
+ uint8_t data[4];
+ uint8_t bytes;
+ uint8_t response;
-typedef struct {
- uint8_t id;
- bool debug;
-
- next_command_cb_t next_command_cb;
- uint8_t command_index;
- uint8_t current_offset;
- uint8_t command[8];
- uint8_t command_length;
- uint8_t response_length;
- uint8_t response[10];
- uint32_t last;
- uint8_t retry;
-
- uint8_t shutdown;
- bool connected;
+ bool shutdown;
bool changed;
float speed;
uint16_t rated_rpm;
uint8_t status;
-} hy_t;
-
-
-static hy_t ha = {0};
-
-
-#define CTRL_STATUS_RESPONSE(R) ((uint16_t)R[4] << 8 | R[5])
-#define FUNC_RESPONSE(R) (R[2] == 2 ? R[4] : ((uint16_t)R[4] << 8 | R[5]))
-
-
-static uint16_t _crc16(const uint8_t *buffer, unsigned length) {
- uint16_t crc = 0xffff;
-
- for (unsigned i = 0; i < length; i++)
- crc = _crc16_update(crc, buffer[i]);
-
- return crc;
-}
-
-
-static void _set_baud(uint16_t bsel, uint8_t bscale) {
- HUANYANG_PORT.BAUDCTRLB = (uint8_t)((bscale << 4) | (bsel >> 8));
- HUANYANG_PORT.BAUDCTRLA = bsel;
-}
-
-
-static void _set_write(bool x) {SET_PIN(RS485_RW_PIN, x);}
-
-
-static void _set_dre_interrupt(bool enable) {
- if (enable) HUANYANG_PORT.CTRLA |= USART_DREINTLVL_MED_gc;
- else HUANYANG_PORT.CTRLA &= ~USART_DREINTLVL_MED_gc;
-}
+} hy = {1}; // Default ID
-static void _set_txc_interrupt(bool enable) {
- if (enable) HUANYANG_PORT.CTRLA |= USART_TXCINTLVL_MED_gc;
- else HUANYANG_PORT.CTRLA &= ~USART_TXCINTLVL_MED_gc;
+static void _ctrl_write(hy_ctrl_state_t state) {
+ hy.func = HUANYANG_CTRL_WRITE;
+ hy.bytes = 2;
+ hy.response = 2;
+ hy.data[0] = 1;
+ hy.data[1] = state;
}
-static void _set_rxc_interrupt(bool enable) {
- if (enable) HUANYANG_PORT.CTRLA |= USART_RXCINTLVL_MED_gc;
- else HUANYANG_PORT.CTRLA &= ~USART_RXCINTLVL_MED_gc;
+static void _ctrl_read(hy_ctrl_addr_t addr) {
+ hy.func = HUANYANG_CTRL_READ;
+ hy.bytes = 2;
+ hy.response = 4;
+ hy.data[0] = 1;
+ hy.data[1] = addr;
}
-static void _set_command1(int code, uint8_t arg0) {
- ha.command_length = 4;
- ha.command[1] = code;
- ha.command[2] = 1;
- ha.command[3] = arg0;
+static void _freq_write(uint16_t freq) {
+ hy.func = HUANYANG_FREQ_WRITE;
+ hy.bytes = 3;
+ hy.response = 2;
+ hy.data[0] = 2;
+ hy.data[1] = freq >> 8;
+ hy.data[2] = freq;
}
-static void _set_command2(int code, uint8_t arg0, uint8_t arg1) {
- ha.command_length = 5;
- ha.command[1] = code;
- ha.command[2] = 2;
- ha.command[3] = arg0;
- ha.command[4] = arg1;
+static void _func_read(uint16_t addr) {
+ hy.func = HUANYANG_FUNC_READ;
+ hy.bytes = 4;
+ hy.response = 4;
+ hy.data[0] = 3;
+ hy.data[1] = addr;
+ hy.data[2] = addr >> 8;
+ hy.data[3] = 0;
}
-static void _set_command3(int code, uint8_t arg0, uint8_t arg1, uint8_t arg2) {
- ha.command_length = 6;
- ha.command[1] = code;
- ha.command[2] = 3;
- ha.command[3] = arg0;
- ha.command[4] = arg1;
- ha.command[5] = arg2;
-}
-
-
-static int _response_length(int code) {
- switch (code) {
- case HUANYANG_FUNC_READ: return 8;
- case HUANYANG_FUNC_WRITE: return 8;
- case HUANYANG_CTRL_WRITE: return 6;
- case HUANYANG_CTRL_READ: return 8;
- case HUANYANG_FREQ_WRITE: return 7;
- default: return -1;
+static void _func_read_response(hy_addr_t addr, uint16_t value) {
+ switch (addr) {
+ case HY_PD005_MAX_FREQUENCY: hy.max_freq = value * 0.01; break;
+ case HY_PD011_FREQUENCY_LOWER_LIMIT: hy.min_freq = value * 0.01; break;
+ case HY_PD144_RATED_MOTOR_RPM: hy.rated_rpm = value; break;
+ default: break;
}
}
-static bool _update(int index) {
- // Read response
- switch (index) {
- case 1: ha.status = ha.response[5]; break;
- case 2: ha.max_freq = FUNC_RESPONSE(ha.response) * 0.01; break;
- case 3: ha.min_freq = FUNC_RESPONSE(ha.response) * 0.01; break;
- case 4: ha.rated_rpm = FUNC_RESPONSE(ha.response); break;
+static void _ctrl_read_response(hy_ctrl_addr_t addr, uint16_t value) {
+ switch (addr) {
+ case HUANYANG_ACTUAL_FREQ: hy.actual_freq = value * 0.01; break;
+ case HUANYANG_ACTUAL_CURRENT: hy.actual_current = value * 0.01; break;
+ case HUANYANG_ACTUAL_RPM: hy.actual_rpm = value; break;
+ case HUANYANG_TEMPERATURE: hy.temperature = value; break;
default: break;
}
+}
- // Setup next command
- uint8_t var;
- switch (index) {
- case 0: { // Update direction
- uint8_t state = HUANYANG_STOP;
- if (!ha.shutdown) {
- if (0 < ha.speed) state = HUANYANG_RUN;
- else if (ha.speed < 0) state = HUANYANG_RUN | HUANYANG_REV_FWD;
- }
-
- _set_command1(HUANYANG_CTRL_WRITE, state);
-
- return true;
- }
-
- case 1: var = HY_PD005_MAX_FREQUENCY; break;
- case 2: var = HY_PD011_FREQUENCY_LOWER_LIMIT; break;
- case 3: var = HY_PD144_RATED_MOTOR_RPM; break;
-
- case 4: { // Update freqency
- // Compute frequency in Hz
- float freq = fabs(ha.speed * 50 / ha.rated_rpm);
-
- // Clamp frequency
- if (ha.max_freq < freq) freq = ha.max_freq;
- if (freq < ha.min_freq) freq = ha.min_freq;
- if (ha.shutdown) freq = 0;
-
- // Frequency write command
- uint16_t f = freq * 100;
- _set_command2(HUANYANG_FREQ_WRITE, f >> 8, f);
- if (1 < ha.shutdown) ha.shutdown--;
+static uint16_t _read_word(const uint8_t *data) {
+ return (uint16_t)data[0] << 8 | data[1];
+}
- return true;
- }
- default:
- report_request();
- return false;
+static void _handle_response(hy_func_t func, const uint8_t *data) {
+ switch (func) {
+ case HUANYANG_FUNC_READ: {
+ _func_read_response((hy_addr_t)_read_word(data), _read_word(data + 2));
+ break;
}
- _set_command3(HUANYANG_FUNC_READ, var, 0, 0);
-
- return true;
-}
-
+ case HUANYANG_FUNC_WRITE: break;
+ case HUANYANG_CTRL_WRITE: hy.status = _read_word(data); break;
-static bool _query_status(int index) {
- // Read response
- switch (index) {
- case 1: ha.actual_freq = CTRL_STATUS_RESPONSE(ha.response) * 0.01; break;
- case 2: ha.actual_current = CTRL_STATUS_RESPONSE(ha.response) * 0.1; break;
- case 3: ha.actual_rpm = CTRL_STATUS_RESPONSE(ha.response); break;
- case 4: ha.temperature = CTRL_STATUS_RESPONSE(ha.response); break;
- default: break;
+ case HUANYANG_CTRL_READ: {
+ _ctrl_read_response((hy_ctrl_addr_t)_read_word(data), _read_word(data + 2));
+ break;
}
- // Setup next command
- uint8_t var;
- switch (index) {
- case 0: var = HUANYANG_ACTUAL_FREQ; break;
- case 1: var = HUANYANG_ACTUAL_CURRENT; break;
- case 2: var = HUANYANG_ACTUAL_RPM; break;
- case 3: var = HUANYANG_TEMPERATURE; break;
- default:
- report_request();
- return false;
+ case HUANYANG_FREQ_WRITE: break;
+ default: break;
}
-
- _set_command1(HUANYANG_CTRL_READ, var);
-
- return true;
}
static void _next_command();
-static void _next_state() {
- if (ha.changed || ha.shutdown) {
- ha.next_command_cb = _update;
- ha.changed = false;
-
- } else ha.next_command_cb = _query_status;
-
- _next_command();
-}
-
-
-static bool _check_response() {
- // Check CRC
- uint16_t computed = _crc16(ha.response, ha.response_length - 2);
- uint16_t expected = (ha.response[ha.response_length - 1] << 8) |
- ha.response[ha.response_length - 2];
-
- if (computed != expected) {
- STATUS_WARNING(STAT_OK, "huanyang: invalid CRC, expected=0x%04u got=0x%04u",
- expected, computed);
- return false;
- }
-
- // Check if response code matches the code we sent
- if (ha.command[1] != ha.response[1]) {
- STATUS_WARNING(STAT_OK,
- "huanyang: invalid function code, expected=%u got=%u",
- ha.command[2], ha.response[2]);
- return false;
- }
-
- return true;
-}
-
-
static void _reset(bool halt) {
- _set_dre_interrupt(false);
- _set_txc_interrupt(false);
- _set_rxc_interrupt(false);
- _set_write(true); // RS485 write mode
-
- // Flush USART
- uint8_t x = HUANYANG_PORT.DATA;
- x = HUANYANG_PORT.STATUS;
- x = x;
-
// Save settings
- uint8_t id = ha.id;
- float speed = ha.speed;
- bool debug = ha.debug;
+ uint8_t id = hy.id;
+ float speed = hy.speed;
// Clear state
- memset(&ha, 0, sizeof(ha));
+ memset(&hy, 0, sizeof(hy));
// Restore settings
- ha.id = id;
- ha.speed = speed;
- ha.debug = debug;
- ha.changed = true;
+ hy.id = id;
+ hy.speed = speed;
+ hy.changed = true;
- if (!halt) _next_state();
+ if (!halt) _next_command();
}
-static void _next_command() {
- if (ha.shutdown == 1) {
- _reset(true);
-
- // Disable USART
- HUANYANG_PORT.CTRLA = 0;
- HUANYANG_PORT.CTRLC = 0;
- HUANYANG_PORT.CTRLB = 0;
-
- // Float write pins
- DIRCLR_PIN(RS485_DI_PIN);
- DIRCLR_PIN(RS485_RW_PIN);
-
- return;
- }
-
- if (ha.next_command_cb && ha.next_command_cb(ha.command_index++)) {
- ha.response_length = _response_length(ha.command[1]);
-
- ha.command[0] = ha.id;
+static void _modbus_cb(uint8_t slave, uint8_t func, uint8_t bytes,
+ const uint8_t *data) {
+ if (data && bytes == *data + 1) {
+ _handle_response((hy_func_t)func, data + 1);
- uint16_t crc = _crc16(ha.command, ha.command_length);
- ha.command[ha.command_length++] = crc;
- ha.command[ha.command_length++] = crc >> 8;
+ if (func == HUANYANG_CTRL_WRITE && hy.shutdown) {
+ _reset(true);
+ modbus_deinit();
+ return;
+ }
- _set_dre_interrupt(true);
+ // Next command
+ if (++hy.state == 9) {
+ hy.state = 5;
+ report_request();
+ }
- return;
+ // Update freq
+ if (hy.shutdown || (hy.changed && 4 < hy.state)) hy.state = 0;
}
- ha.command_index = 0;
- _next_state();
-}
-
-
-static void _retry_command() {
- ha.last = 0;
- ha.current_offset = 0;
- ha.retry++;
-
- _set_write(true); // RS485 write mode
-
- _set_txc_interrupt(false);
- _set_rxc_interrupt(false);
- _set_dre_interrupt(true);
-
- if (ha.debug) STATUS_DEBUG("huanyang: retry %d", ha.retry);
+ _next_command();
}
-// Data register empty interrupt
-ISR(HUANYANG_DRE_vect) {
- HUANYANG_PORT.DATA = ha.command[ha.current_offset++];
+static void _next_command() {
+ switch (hy.state) {
+ case 0: { // Update direction
+ hy_ctrl_state_t state = HUANYANG_STOP;
+ if (!hy.shutdown) {
+ if (0 < hy.speed) state = HUANYANG_RUN;
+ else if (hy.speed < 0)
+ state = (hy_ctrl_state_t)(HUANYANG_RUN | HUANYANG_REV_FWD);
+ }
- if (ha.current_offset == ha.command_length) {
- _set_dre_interrupt(false);
- _set_txc_interrupt(true);
- ha.current_offset = 0;
+ _ctrl_write(state);
+ break;
}
-}
+ case 1: _func_read(HY_PD005_MAX_FREQUENCY); break;
+ case 2: _func_read(HY_PD011_FREQUENCY_LOWER_LIMIT); break;
+ case 3: _func_read(HY_PD144_RATED_MOTOR_RPM); break;
-/// Transmit complete interrupt
-ISR(HUANYANG_TXC_vect) {
- _set_txc_interrupt(false);
- _set_rxc_interrupt(true);
- _set_write(false); // RS485 read mode
- ha.last = rtc_get_time(); // Set timeout timer
-}
-
-
-// Data received interrupt
-ISR(HUANYANG_RXC_vect) {
- ha.response[ha.current_offset++] = HUANYANG_PORT.DATA;
+ case 4: { // Update freqency
+ // Compute frequency in Hz
+ float freq = fabs(hy.speed * 50 / hy.rated_rpm);
- if (ha.current_offset == ha.response_length) {
- _set_rxc_interrupt(false);
- ha.current_offset = 0;
- _set_write(true); // RS485 write mode
+ // Clamp frequency
+ if (hy.max_freq < freq) freq = hy.max_freq;
+ if (freq < hy.min_freq) freq = hy.min_freq;
- if (_check_response()) {
- ha.last = 0; // Clear timeout timer
- ha.retry = 0; // Reset retry counter
- ha.connected = true;
+ // Frequency write command
+ _freq_write(freq * 100);
+ hy.changed = false;
+ break;
+ }
- _next_command();
- }
+ case 5: _ctrl_read(HUANYANG_ACTUAL_FREQ); break;
+ case 6: _ctrl_read(HUANYANG_ACTUAL_CURRENT); break;
+ case 7: _ctrl_read(HUANYANG_ACTUAL_RPM); break;
+ case 8: _ctrl_read(HUANYANG_TEMPERATURE); break;
}
+
+ // Send command
+ modbus_func(hy.id, hy.func, hy.bytes, hy.data, hy.response, _modbus_cb);
}
void hy_init() {
- PR.PRPD &= ~PR_USART1_bm; // Disable power reduction
-
- DIRCLR_PIN(RS485_RO_PIN); // Input
- OUTSET_PIN(RS485_DI_PIN); // High
- DIRSET_PIN(RS485_DI_PIN); // Output
- OUTSET_PIN(RS485_RW_PIN); // High
- DIRSET_PIN(RS485_RW_PIN); // Output
-
- _set_baud(3325, 0b1101); // 9600 @ 32MHz with 2x USART
-
- // No parity, 8 data bits, 1 stop bit
- HUANYANG_PORT.CTRLC = USART_CMODE_ASYNCHRONOUS_gc | USART_PMODE_DISABLED_gc |
- USART_CHSIZE_8BIT_gc;
-
- // Configure receiver and transmitter
- HUANYANG_PORT.CTRLB = USART_RXEN_bm | USART_TXEN_bm | USART_CLK2X_bm;
-
- ha.id = HUANYANG_ID;
- hy_reset();
+ modbus_init();
+ _reset(false);
}
-void hy_deinit() {ha.shutdown = 5;}
+void hy_deinit() {hy.shutdown = true;}
void hy_set(float speed) {
- if (ha.speed != speed) {
- if (ha.debug) STATUS_DEBUG("huanyang: speed=%0.2f", speed);
- ha.speed = speed;
- ha.changed = true;
- }
-}
-
-
-void hy_reset() {_reset(false);}
-
-
-void hy_rtc_callback() {
- if (ha.last && rtc_expired(ha.last + HUANYANG_TIMEOUT)) {
- if (ha.retry < HUANYANG_RETRIES) _retry_command();
- else {
- if (ha.debug) STATUS_DEBUG("huanyang: timedout");
-
- if (ha.debug && ha.current_offset) {
- const uint8_t buf_len = 8 * 2 + 1;
- char sent[buf_len];
- char received[buf_len];
-
- uint8_t i;
- for (i = 0; i < ha.command_length; i++)
- sprintf(sent + i * 2, "%02x", ha.command[i]);
- sent[i * 2] = 0;
-
- for (i = 0; i < ha.current_offset; i++)
- sprintf(received + i * 2, "%02x", ha.response[i]);
- received[i * 2] = 0;
-
- STATUS_DEBUG("huanyang: sent 0x%s received 0x%s expected %u bytes",
- sent, received, ha.response_length);
- }
-
- // Try changing pin polarity
- PINCTRL_PIN(RS485_RO_PIN) ^= PORT_INVEN_bm;
- PINCTRL_PIN(RS485_DI_PIN) ^= PORT_INVEN_bm;
-
- hy_reset();
- }
+ if (hy.speed != speed) {
+ hy.speed = speed;
+ hy.changed = true;
}
}
void hy_stop() {
hy_set(0);
- hy_reset();
+ _reset(false);
}
-uint8_t get_hy_id() {return ha.id;}
-void set_hy_id(uint8_t value) {ha.id = value;}
-bool get_hy_debug() {return ha.debug;}
-void set_hy_debug(bool value) {ha.debug = value;}
-bool get_hy_connected() {return ha.connected;}
-float get_hy_freq() {return ha.actual_freq;}
-float get_hy_current() {return ha.actual_current;}
-uint16_t get_hy_rpm() {return ha.actual_rpm;}
-uint16_t get_hy_temp() {return ha.temperature;}
-float get_hy_max_freq() {return ha.max_freq;}
-float get_hy_min_freq() {return ha.min_freq;}
-uint16_t get_hy_rated_rpm() {return ha.rated_rpm;}
-float get_hy_status() {return ha.status;}
+uint8_t get_hy_id() {return hy.id;}
+void set_hy_id(uint8_t value) {hy.id = value;}
+float get_hy_freq() {return hy.actual_freq;}
+float get_hy_current() {return hy.actual_current;}
+uint16_t get_hy_rpm() {return hy.actual_rpm;}
+uint16_t get_hy_temp() {return hy.temperature;}
+float get_hy_max_freq() {return hy.max_freq;}
+float get_hy_min_freq() {return hy.min_freq;}
+uint16_t get_hy_rated_rpm() {return hy.rated_rpm;}
+float get_hy_status() {return hy.status;}
void hy_init();
void hy_deinit();
void hy_set(float speed);
-void hy_reset();
-void hy_rtc_callback();
void hy_stop();
+
/// See Huanyang VFD user manual
typedef enum {
HY_PD000_PARAMETER_LOCK,
HY_PD181_SOFTWARE_VERSION,
HY_PD182_MANUFACTURE_DATE,
HY_PD183_SERIAL_NO,
-} hy_func_addr_t;
+} hy_addr_t;
--- /dev/null
+/******************************************************************************\
+
+ This file is part of the Buildbotics firmware.
+
+ Copyright (c) 2015 - 2018, Buildbotics LLC
+ All rights reserved.
+
+ This file ("the software") is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License,
+ version 2 as published by the Free Software Foundation. You should
+ have received a copy of the GNU General Public License, version 2
+ along with the software. If not, see <http://www.gnu.org/licenses/>.
+
+ The software is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the software. If not, see
+ <http://www.gnu.org/licenses/>.
+
+ For information regarding this software email:
+ "Joseph Coffland" <joseph@buildbotics.com>
+
+\******************************************************************************/
+
+#include "modbus.h"
+#include "usart.h"
+#include "status.h"
+#include "rtc.h"
+#include "config.h"
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <util/crc16.h>
+
+#include <string.h>
+#include <stdio.h>
+
+
+static struct {
+ bool debug;
+ uint8_t baud;
+
+ uint8_t bytes;
+ uint8_t command[MODBUS_BUF_SIZE];
+ uint8_t command_length;
+ uint8_t response[MODBUS_BUF_SIZE];
+ uint8_t response_length;
+
+ modbus_cb_t receive_cb;
+
+ uint32_t last;
+ uint8_t retry;
+ bool connected;
+ bool busy;
+} mb = {true, USART_BAUD_9600};
+
+
+static uint16_t _crc16(const uint8_t *buffer, unsigned length) {
+ uint16_t crc = 0xffff;
+
+ for (unsigned i = 0; i < length; i++)
+ crc = _crc16_update(crc, buffer[i]);
+
+ return crc;
+}
+
+
+static void _set_write(bool x) {SET_PIN(RS485_RW_PIN, x);}
+
+
+#define INTLVL_SET(REG, NAME, LEVEL) \
+ REG = ((REG) & ~NAME##INTLVL_gm) | NAME##INTLVL_##LEVEL##_gc
+
+
+#define INTLVL_ENABLE(REG, NAME, LEVEL, ENABLE) do { \
+ if (ENABLE) INTLVL_SET(REG, NAME, LEVEL); \
+ else INTLVL_SET(REG, NAME, OFF); \
+ } while (0)
+
+
+static void _set_dre_interrupt(bool enable) {
+ INTLVL_ENABLE(RS485_PORT.CTRLA, USART_DRE, MED, enable);
+}
+
+
+static void _set_txc_interrupt(bool enable) {
+ INTLVL_ENABLE(RS485_PORT.CTRLA, USART_TXC, MED, enable);
+}
+
+
+static void _set_rxc_interrupt(bool enable) {
+ INTLVL_ENABLE(RS485_PORT.CTRLA, USART_RXC, MED, enable);
+}
+
+
+static bool _check_response() {
+ // Check CRC
+ uint16_t computed = _crc16(mb.response, mb.response_length - 2);
+ uint16_t expected = (mb.response[mb.response_length - 1] << 8) |
+ mb.response[mb.response_length - 2];
+
+ if (computed != expected) {
+ STATUS_WARNING(STAT_OK, "modbus: invalid CRC, expected=0x%04u got=0x%04u",
+ expected, computed);
+ return false;
+ }
+
+ // Check that slave id matches
+ if (mb.command[0] != mb.response[0]) {
+ STATUS_WARNING(STAT_OK, "modbus: unexpected slave id, expected=%u got=%u",
+ mb.command[0], mb.response[0]);
+ return false;
+ }
+
+ // Check that function code matches
+ if (mb.command[1] != mb.response[1]) {
+ STATUS_WARNING(STAT_OK, "modbus: invalid function code, expected=%u got=%u",
+ mb.command[1], mb.response[1]);
+ return false;
+ }
+
+ return true;
+}
+
+
+static void _handle_response() {
+ if (!_check_response()) return;
+
+ mb.last = 0; // Clear timeout timer
+ mb.retry = 0; // Reset retry counter
+
+ if (mb.receive_cb)
+ mb.receive_cb(mb.response[0], mb.response[1], mb.response_length - 4,
+ mb.response + 2);
+
+ mb.connected = true;
+ mb.busy = false;
+}
+
+
+/// Data register empty interrupt
+ISR(RS485_DRE_vect) {
+ RS485_PORT.DATA = mb.command[mb.bytes++];
+
+ if (mb.bytes == mb.command_length) {
+ _set_dre_interrupt(false);
+ _set_txc_interrupt(true);
+ mb.bytes = 0;
+ }
+}
+
+
+/// Transmit complete interrupt
+ISR(RS485_TXC_vect) {
+ _set_txc_interrupt(false);
+ _set_rxc_interrupt(true);
+ _set_write(false); // Switch to read mode
+ mb.last = rtc_get_time(); // Set timeout timer
+}
+
+
+/// Data received interrupt
+ISR(RS485_RXC_vect) {
+ mb.response[mb.bytes++] = RS485_PORT.DATA;
+
+ if (mb.bytes == mb.response_length) {
+ _set_rxc_interrupt(false);
+ _set_write(true); // Back to write mode
+ mb.bytes = 0;
+ _handle_response();
+ }
+}
+
+
+static void _reset() {
+ _set_dre_interrupt(false);
+ _set_txc_interrupt(false);
+ _set_rxc_interrupt(false);
+ _set_write(true); // RS485 write mode
+
+ // Flush USART
+ uint8_t x = RS485_PORT.DATA;
+ x = RS485_PORT.STATUS;
+ x = x;
+
+ // Save settings
+ bool debug = mb.debug;
+ uint8_t baud = mb.baud;
+
+ // Clear state
+ memset(&mb, 0, sizeof(mb));
+
+ // Restore settings
+ mb.debug = debug;
+ mb.baud = baud;
+}
+
+
+static void _retry() {
+ mb.last = 0;
+ mb.bytes = 0;
+ mb.retry++;
+
+ _set_write(true); // RS485 write mode
+
+ _set_txc_interrupt(false);
+ _set_rxc_interrupt(false);
+ _set_dre_interrupt(true);
+
+ if (mb.debug) STATUS_DEBUG("modbus: retry %d", mb.retry);
+}
+
+
+static void _timeout() {
+ if (mb.debug) STATUS_DEBUG("modbus: timedout");
+
+ // Try changing pin polarity
+ PINCTRL_PIN(RS485_RO_PIN) ^= PORT_INVEN_bm;
+ PINCTRL_PIN(RS485_DI_PIN) ^= PORT_INVEN_bm;
+
+ mb.retry = -1;
+ _retry();
+}
+
+
+void modbus_init() {
+ PR.PRPD &= ~PR_USART1_bm; // Disable power reduction
+
+ DIRCLR_PIN(RS485_RO_PIN); // Input
+ OUTSET_PIN(RS485_DI_PIN); // High
+ DIRSET_PIN(RS485_DI_PIN); // Output
+ OUTSET_PIN(RS485_RW_PIN); // High
+ DIRSET_PIN(RS485_RW_PIN); // Output
+
+ usart_set_port_baud(&RS485_PORT, mb.baud);
+
+ // No parity, 8 data bits, 1 stop bit
+ RS485_PORT.CTRLC = USART_CMODE_ASYNCHRONOUS_gc | USART_PMODE_DISABLED_gc |
+ USART_CHSIZE_8BIT_gc;
+
+ // Enable receiver and transmitter
+ RS485_PORT.CTRLB |= USART_RXEN_bm | USART_TXEN_bm;
+
+ // TODO remove this
+ PINCTRL_PIN(RS485_RO_PIN) ^= PORT_INVEN_bm;
+ PINCTRL_PIN(RS485_DI_PIN) ^= PORT_INVEN_bm;
+
+ _reset();
+}
+
+
+void modbus_deinit() {
+ _reset();
+
+ // Disable USART
+ RS485_PORT.CTRLB &= ~(USART_RXEN_bm | USART_TXEN_bm);
+
+ // Float write pins
+ DIRCLR_PIN(RS485_DI_PIN);
+ DIRCLR_PIN(RS485_RW_PIN);
+}
+
+
+bool modbus_busy() {return mb.busy;}
+
+
+void modbus_func(uint8_t slave, uint8_t func, uint8_t send, const uint8_t *data,
+ uint8_t receive, modbus_cb_t receive_cb) {
+ ASSERT(send + 4 <= MODBUS_BUF_SIZE);
+ ASSERT(receive + 4 <= MODBUS_BUF_SIZE);
+
+ _reset();
+
+ mb.command[0] = slave;
+ mb.command[1] = func;
+ memcpy(mb.command + 2, data, send);
+ uint16_t crc = _crc16(mb.command, send + 2);
+ mb.command[send + 2] = crc;
+ mb.command[send + 3] = crc >> 8;
+
+ mb.command_length = send + 4;
+ mb.response_length = receive + 4;
+ mb.receive_cb = receive_cb;
+
+ _set_dre_interrupt(true);
+}
+
+
+void modbus_rtc_callback() {
+ if (mb.last && rtc_expired(mb.last + MODBUS_TIMEOUT)) {
+ if (mb.debug && mb.bytes) {
+ const uint8_t buf_len = 8 * 2 + 1;
+ char sent[buf_len];
+ char received[buf_len];
+
+ uint8_t i;
+ for (i = 0; i < mb.command_length; i++)
+ sprintf(sent + i * 2, "%02x", mb.command[i]);
+ sent[i * 2] = 0;
+
+ for (i = 0; i < mb.bytes; i++)
+ sprintf(received + i * 2, "%02x", mb.response[i]);
+ received[i * 2] = 0;
+
+ STATUS_DEBUG("modbus: sent 0x%s received 0x%s expected %u bytes",
+ sent, received, mb.response_length);
+ }
+
+ if (mb.retry < MODBUS_RETRIES) _retry();
+ else _timeout();
+ }
+}
+
+
+// Variable callbacks
+bool get_modbus_debug() {return mb.debug;}
+void set_modbus_debug(bool value) {mb.debug = value;}
+uint8_t get_modbus_baud() {return mb.baud;}
+
+
+void set_modbus_baud(uint8_t baud) {
+ mb.baud = baud;
+ usart_set_port_baud(&RS485_PORT, baud);
+}
+
+
+bool get_modbus_connected() {return mb.connected;}
--- /dev/null
+/******************************************************************************\
+
+ This file is part of the Buildbotics firmware.
+
+ Copyright (c) 2015 - 2018, Buildbotics LLC
+ All rights reserved.
+
+ This file ("the software") is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License,
+ version 2 as published by the Free Software Foundation. You should
+ have received a copy of the GNU General Public License, version 2
+ along with the software. If not, see <http://www.gnu.org/licenses/>.
+
+ The software is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the software. If not, see
+ <http://www.gnu.org/licenses/>.
+
+ For information regarding this software email:
+ "Joseph Coffland" <joseph@buildbotics.com>
+
+\******************************************************************************/
+
+/******************************************************************************\
+
+ Modbus RTU command format is as follows:
+
+ Function 01: Read Coil Status
+ Function 02: Read Input Status
+
+ Send: [id][func][addr][count][crc]
+ Receive: [id][func][bytes][flags][crc]
+
+ Function 03: Read Holding Registers
+ Function 04: Read Input Registers
+
+ Send: [id][func][addr][count][crc]
+ Receive: [id][func][bytes][regs][crc]
+
+ Function 05: Force Single Coil
+ Function 06: Preset Single Register
+
+ Send: [id][func][addr][value][crc]
+ Receive: [id][func][addr][value][crc]
+
+ Function 15: Force Multiple Coils
+
+ Send: [id][func][addr][count][bytes][flags][crc]
+ Receive: [id][func][addr][count][crc]
+
+ Function 16: Preset Multiple Registers
+
+ Send: [id][func][addr][count][bytes][regs][crc]
+ Receive: [id][func][addr][count][crc]
+
+ Where:
+
+ id: 1-byte Slave ID
+ func: 1-byte Function code
+ addr: 2-byte Data address
+ count: 2-byte Address count
+ bytes: 1-byte Number of bytes to follow
+ value: 2-byte Value read or written
+ bits: n-bytes Flags indicating on/off
+ regs: n-bytes register values to write
+ checksum: 16-bit CRC: x^16 + x^15 + x^2 + 1 (0x8005) initial: 0xffff
+
+\******************************************************************************/
+
+#pragma once
+
+
+#include <stdint.h>
+#include <stdbool.h>
+
+
+typedef enum {
+ MODBUS_READ_COILS = 1,
+ MODBUS_READ_CONTACTS = 2,
+ MODBUS_READ_OUTPUT_REG = 3,
+ MODBUS_READ_INPUT_REG = 4,
+ MODBUS_WRITE_COIL = 5,
+ MODBUS_WRITE_OUTPUT_REG = 6,
+ MODBUS_WRITE_COILS = 15,
+ MODBUS_WRITE_OUTPUT_REGS = 16,
+} modbus_function_code_t;
+
+
+typedef enum {
+ MODBUS_COIL_BASE = 0,
+ MODBUS_CONTACT_BASE = 10000,
+ MODBUS_INPUT_REG_BASE = 30000,
+ MODBUS_OUTPUT_REG_BASE = 40000,
+} modbus_base_addrs_t;
+
+
+typedef void (*modbus_cb_t)(uint8_t slave, uint8_t func, uint8_t bytes,
+ const uint8_t *data);
+
+void modbus_init();
+void modbus_deinit();
+bool modbus_busy();
+void modbus_func(uint8_t slave, uint8_t func, uint8_t send, const uint8_t *data,
+ uint8_t receive, modbus_cb_t receive_cb);
+void modbus_rtc_callback();
#pragma once
-#include "spindle.h"
-
void pwm_spindle_init();
void pwm_spindle_deinit();
#include "switch.h"
#include "analog.h"
#include "io.h"
-#include "huanyang.h"
+#include "modbus.h"
#include "motor.h"
#include "lcd.h"
switch_rtc_callback();
analog_rtc_callback();
io_rtc_callback();
- hy_rtc_callback();
+ modbus_rtc_callback();
if (!(ticks & 255)) motor_rtc_callback();
wdt_reset();
}
USART_CHSIZE_8BIT_gc;
// Configure receiver and transmitter
- SERIAL_PORT.CTRLB = USART_RXEN_bm | USART_TXEN_bm | USART_CLK2X_bm;
+ SERIAL_PORT.CTRLB |= USART_RXEN_bm | USART_TXEN_bm;
PMIC.CTRL |= PMIC_HILVLEN_bm; // Interrupt level on
}
-static void _set_baud(uint16_t bsel, uint8_t bscale) {
- SERIAL_PORT.BAUDCTRLB = (uint8_t)((bscale << 4) | (bsel >> 8));
- SERIAL_PORT.BAUDCTRLA = bsel;
+static void _set_baud(USART_t *port, uint16_t bsel, uint8_t bscale) {
+ port->BAUDCTRLB = (uint8_t)((bscale << 4) | (bsel >> 8));
+ port->BAUDCTRLA = bsel;
+ port->CTRLB |= USART_CLK2X_bm;
}
-void usart_set_baud(int baud) {
+void usart_set_port_baud(USART_t *port, int baud) {
// The BSEL / BSCALE values provided below assume a 32 Mhz clock
- // Assumes CTRLB CLK2X bit (0x04) is set
+ // With CTRLB CLK2X is set
// See http://www.avrcalc.elektronik-projekt.de/xmega/baud_rate_calculator
switch (baud) {
- case USART_BAUD_9600: _set_baud(3325, 0b1101); break;
- case USART_BAUD_19200: _set_baud(3317, 0b1100); break;
- case USART_BAUD_38400: _set_baud(3301, 0b1011); break;
- case USART_BAUD_57600: _set_baud(1095, 0b1100); break;
- case USART_BAUD_115200: _set_baud(1079, 0b1011); break;
- case USART_BAUD_230400: _set_baud(1047, 0b1010); break;
- case USART_BAUD_460800: _set_baud(983, 0b1001); break;
- case USART_BAUD_921600: _set_baud(107, 0b1011); break;
- case USART_BAUD_500000: _set_baud(1, 0b0010); break;
- case USART_BAUD_1000000: _set_baud(1, 0b0001); break;
+ case USART_BAUD_9600: _set_baud(port, 3325, 0b1101); break;
+ case USART_BAUD_19200: _set_baud(port, 3317, 0b1100); break;
+ case USART_BAUD_38400: _set_baud(port, 3301, 0b1011); break;
+ case USART_BAUD_57600: _set_baud(port, 1095, 0b1100); break;
+ case USART_BAUD_115200: _set_baud(port, 1079, 0b1011); break;
+ case USART_BAUD_230400: _set_baud(port, 1047, 0b1010); break;
+ case USART_BAUD_460800: _set_baud(port, 983, 0b1001); break;
+ case USART_BAUD_921600: _set_baud(port, 107, 0b1011); break;
+ case USART_BAUD_500000: _set_baud(port, 1, 0b0010); break;
+ case USART_BAUD_1000000: _set_baud(port, 1, 0b0001); break;
}
}
+void usart_set_baud(int baud) {
+ usart_set_port_baud(&SERIAL_PORT, baud);
+}
+
+
void usart_set(int flag, bool enable) {
if (enable) usart_flags |= flag;
else usart_flags &= ~flag;
#pragma once
+#include <avr/io.h>
#include <stdint.h>
#include <stdbool.h>
+
#define USART_TX_RING_BUF_SIZE 256
#define USART_RX_RING_BUF_SIZE 256
};
void usart_init();
+void usart_set_port_baud(USART_t *port, int baud);
void usart_set_baud(int baud);
void usart_set(int flag, bool enable);
bool usart_is_set(int flags);
// PWM spindle
VAR(pwm_invert, pi, b8, 0, 1, 1, "Inverted spindle PWM")
+// Modbus spindle
+VAR(modbus_debug, hb, b8, 0, 1, 1, "Modbus debugging")
+VAR(modbus_baud, mb, u8, 0, 1, 1, "Modbus BAUD rate")
+VAR(modbus_connected, he, b8, 0, 0, 1, "Modbus connected")
+
// Huanyang spindle
-VAR(hy_id, hi, u8, 0, 1, 1, "Huanyang ID")
-VAR(hy_freq, hz, f32, 0, 0, 1, "Huanyang actual freq")
-VAR(hy_current, hc, f32, 0, 0, 1, "Huanyang actual current")
-VAR(hy_rpm, hr, u16, 0, 0, 1, "Huanyang actual RPM")
-VAR(hy_temp, ht, u16, 0, 0, 1, "Huanyang temperature")
-VAR(hy_max_freq, hx, f32, 0, 0, 1, "Huanyang max freq")
-VAR(hy_min_freq, hm, f32, 0, 0, 1, "Huanyang min freq")
-VAR(hy_rated_rpm, hq, u16, 0, 0, 1, "Huanyang rated RPM")
-VAR(hy_status, hs, u8, 0, 0, 1, "Huanyang status flags")
-VAR(hy_debug, hb, b8, 0, 1, 1, "Huanyang debugging")
-VAR(hy_connected, he, b8, 0, 0, 1, "Huanyang connected")
+VAR(hy_id, hi, u8, 0, 1, 1, "Modbus ID")
+VAR(hy_freq, hz, f32, 0, 0, 1, "Modbus actual freq")
+VAR(hy_current, hc, f32, 0, 0, 1, "Modbus actual current")
+VAR(hy_rpm, hr, u16, 0, 0, 1, "Modbus actual RPM")
+VAR(hy_temp, ht, u16, 0, 0, 1, "Modbus temperature")
+VAR(hy_max_freq, hx, f32, 0, 0, 1, "Modbus max freq")
+VAR(hy_min_freq, hm, f32, 0, 0, 1, "Modbus min freq")
+VAR(hy_rated_rpm, hq, u16, 0, 0, 1, "Modbus rated RPM")
+VAR(hy_status, hs, u8, 0, 0, 1, "Modbus status flags")
// Machine state
VAR(id, id, u32, 0, 1, 1, "Last executed command ID")
-VAR(speed, s, f32, 0, 1, 1, "Current spindle speed")
+VAR(speed, s, f32, 0, 1, 1, "Current spindle speed")
VAR(feed_override, fo, f32, 0, 1, 1, "Feed rate override")
VAR(speed_override, so, f32, 0, 1, 1, "Spindle speed override")
VAR(optional_pause, op, b8, 0, 1, 1, "Optional pause state")
// System
-VAR(velocity, v, f32, 0, 0, 1, "Current velocity")
-VAR(acceleration, ax, f32, 0, 0, 1, "Current acceleration")
-VAR(jerk, j, f32, 0, 0, 1, "Current jerk")
-VAR(peak_vel, pv, f32, 0, 1, 1, "Peak velocity, set to clear")
-VAR(peak_accel, pa, f32, 0, 1, 1, "Peak acceleration, set to clear")
+VAR(velocity, v, f32, 0, 0, 1, "Current velocity")
+VAR(acceleration, ax, f32, 0, 0, 1, "Current acceleration")
+VAR(jerk, j, f32, 0, 0, 1, "Current jerk")
+VAR(peak_vel, pv, f32, 0, 1, 1, "Peak velocity, set to clear")
+VAR(peak_accel, pa, f32, 0, 1, 1, "Peak acceleration, set to clear")
VAR(hw_id, hid, str, 0, 0, 1, "Hardware ID")
VAR(estop, es, b8, 0, 1, 1, "Emergency stop")
VAR(estop_reason, er, pstr, 0, 0, 1, "Emergency stop reason")
templated-input(v-for="templ in template.tool", :name="$key",
:model.sync="tool[$key]", :template="templ")
- div(v-if="tool['spindle-type'] == 'PWM'")
+ div(v-if="get_type() == 'PWM'")
h2 PWM Spindle
form.pure-form.pure-form-aligned
fieldset
templated-input(v-for="templ in template['pwm-spindle']",
:name="$key", :model.sync="pwmSpindle[$key]", :template="templ")
- div(v-if="tool['spindle-type'] == 'HUANYANG'")
- h2 Huanyang VFD Spindle
+ div(v-if="get_type() == 'HUANYANG'")
+ h2 Huanyang VFD
form.pure-form.pure-form-aligned
fieldset
templated-input(v-for="templ in template['huanyang-spindle']",
:name="$key", :model.sync="huanyangSpindle[$key]",
:template="templ")
+
+ div(v-if="get_type() == 'MODBUS'")
+ h2 Modbus Compatiable VFD
+ form.pure-form.pure-form-aligned
+ fieldset
+ templated-input(v-for="templ in template['modbus-spindle']",
+ :name="$key", :model.sync="modbusSpindle[$key]",
+ :template="templ")
return {
tool: {},
pwmSpindle: {},
- huanyangSpindle: {}
+ huanyangSpindle: {},
+ modbusSpindle: {}
}
},
methods: {
+ get_type: function () {
+ return (this.tool['spindle-type'] || 'Disabled').toUpperCase();
+ },
+
+
+ update_tool: function (type) {
+ if (this.config.hasOwnProperty(type + '-spindle'))
+ this[type + 'Spindle'] = this.config[type + '-spindle'];
+
+ var template = this.template[type + '-spindle'];
+ for (var key in template)
+ if (!this[type + 'Spindle'].hasOwnProperty(key))
+ this.$set(type + 'Spindle["' + key + '"]', template[key].default);
+ },
+
+
update: function () {
Vue.nextTick(function () {
- // Tool
if (this.config.hasOwnProperty('tool'))
this.tool = this.config.tool;
if (!this.tool.hasOwnProperty(key))
this.$set('tool["' + key + '"]', template[key].default);
- // PWM
- if (this.config.hasOwnProperty('pwm-spindle'))
- this.pwmSpindle = this.config['pwm-spindle'];
-
- template = this.template['pwm-spindle'];
- for (key in template)
- if (!this.pwmSpindle.hasOwnProperty(key))
- this.$set('pwmSpindle["' + key + '"]', template[key].default);
-
- // Huanyang
- if (this.config.hasOwnProperty('huanyang-spindle'))
- this.huanyangSpindle = this.config['huanyang-spindle'];
-
- template = this.template['huanyang-spindle'];
- for (key in template)
- if (!this.huanyangSpindle.hasOwnProperty(key))
- this.$set('huanyangSpindle["' + key + '"]', template[key].default);
- }.bind(this));
+ this.update_tool('pwm');
+ this.update_tool('huanyang');
+ this.update_tool('modbus');
+ }.bind(this));
}
}
}
"tool": {
"spindle-type": {
"type": "enum",
- "values": ["DISABLED", "PWM", "HUANYANG"],
- "default": "DISABLED",
+ "values": ["Disabled", "PWM", "Huanyang", "Modbus"],
+ "default": "Disabled",
"code": "st"
},
"spin-reversed": {
}
},
+ "modbus-spindle": {
+ "modbus-id": {
+ "type": "int",
+ "default": "1",
+ "code": "bi"
+ }
+ },
+
"pwm-spindle": {
"max-spin": {
"type": "float",