summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Buesch <mb@bu3sch.de>2008-10-18 23:47:05 +0200
committerMichael Buesch <mb@bu3sch.de>2008-10-18 23:47:05 +0200
commit4b0babc1b1daaece0d8de672f98adaf6cff5cdac (patch)
tree58c4bbac00ca8a76af06b006a392279ae6729907
parent4a914e8c559cd48173bed088e2122d1d35c8de9b (diff)
downloadcnc-4b0babc1b1daaece0d8de672f98adaf6cff5cdac.tar.xz
cnc-4b0babc1b1daaece0d8de672f98adaf6cff5cdac.zip
Add a loooot of stuff to pressure_control
Signed-off-by: Michael Buesch <mb@bu3sch.de>
-rw-r--r--pressure_control/firmware/Makefile16
-rw-r--r--pressure_control/firmware/main.c195
-rw-r--r--pressure_control/firmware/main.h36
-rw-r--r--pressure_control/firmware/remote.c243
-rw-r--r--pressure_control/firmware/remote.h68
-rw-r--r--pressure_control/firmware/sensor.c55
-rw-r--r--pressure_control/firmware/sensor.h4
-rw-r--r--pressure_control/firmware/util.c37
-rw-r--r--pressure_control/firmware/util.h3
-rwxr-xr-xpressure_control/remote/pctl-remote337
10 files changed, 916 insertions, 78 deletions
diff --git a/pressure_control/firmware/Makefile b/pressure_control/firmware/Makefile
index 4024f02..a725d24 100644
--- a/pressure_control/firmware/Makefile
+++ b/pressure_control/firmware/Makefile
@@ -17,7 +17,7 @@ CFLAGS += "-Dinline=inline __attribute__((__always_inline__))"
LFUSE = 0xE0
HFUSE = 0xD9
-OBJECTS = main.o util.o valves.o sensor.o
+OBJECTS = main.o util.o valves.o sensor.o remote.o
NAME = pressure_control
BIN = $(NAME).bin
@@ -26,13 +26,15 @@ EEP = $(NAME).eep.hex
all: $(HEX)
-main.o: util.h calibration.h valves.h sensor.h
+main.o: util.h calibration.h valves.h sensor.h remote.h main.h
util.o: util.h calibration.h
-valves.o: valves.h
+valves.o: util.h valves.h
-sensor.o: sensor.h
+sensor.o: util.h sensor.h
+
+remote.o: util.h remote.h calibration.h main.h
%.s: %.c
$(CC) $(CFLAGS) -S $*.c
@@ -42,8 +44,8 @@ $(BIN): $(OBJECTS)
$(HEX): $(BIN)
$(OBJCOPY) -R.eeprom -O ihex $(BIN) $(HEX)
-# $(OBJCOPY) -j.eeprom --set-section-flags=.eeprom="alloc,load" \
-# --change-section-lma .eeprom=0 -O ihex $(BIN) $(EEP)
+ $(OBJCOPY) -j.eeprom --set-section-flags=.eeprom="alloc,load" \
+ --change-section-lma .eeprom=0 -O ihex $(BIN) $(EEP)
$(SIZE) $(BIN)
avrdude:
@@ -58,7 +60,7 @@ install_eeprom:
$(AVRDUDE) -B $(AVRDUDE_SPEED) -p $(AVRDUDE_ARCH) \
-c $(PROGRAMMER) -P $(PROGPORT) -U eeprom:w:$(EEP)
-install: all install_flash
+install: all install_flash install_eeprom
# Reset the microcontroller through avrdude
reset:
diff --git a/pressure_control/firmware/main.c b/pressure_control/firmware/main.c
index 02272ef..3dab38e 100644
--- a/pressure_control/firmware/main.c
+++ b/pressure_control/firmware/main.c
@@ -17,87 +17,180 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "main.h"
#include "util.h"
#include "calibration.h"
#include "sensor.h"
#include "valves.h"
+#include "remote.h"
+#include <stdint.h>
+#include <string.h>
-static inline void usart_tx(uint8_t data)
+#include <avr/eeprom.h>
+
+
+struct eeprom_data {
+ struct pressure_config cfg;
+};
+
+/* The pressure configuration data. */
+struct pressure_config cfg;
+/* The pressure state data. */
+struct pressure_state state;
+
+/* EEPROM contents */
+static struct eeprom_data EEMEM eeprom = {
+ .cfg = {
+ .desired = 4000, /* 4 Bar */
+ .hysteresis = 300, /* 0.3 Bar */
+ .autoadjust_enable = 1,
+ },
+};
+
+
+void get_pressure_config(struct pressure_config *ret)
{
- while (!(UCSRA & (1 << UDRE)))
- ;
- UDR = data;
+ uint8_t sreg;
+
+ sreg = irq_disable_save();
+ memcpy(ret, &cfg, sizeof(*ret));
+ irq_restore(sreg);
}
-static void __print(const prog_char *msg)
+void get_pressure_state(struct pressure_state *ret)
{
- uint8_t c;
+ uint8_t sreg;
- for ( ; ; msg++) {
- c = pgm_read_byte(msg);
- if (c == '\0')
- break;
- usart_tx(c);
- }
+ sreg = irq_disable_save();
+ memcpy(ret, &state, sizeof(*ret));
+ irq_restore(sreg);
}
-#define print(msg) __print(PSTR(msg))
-#define ERXFE 1 /* USART RX frame error */
-#define ERXPE 2 /* USART RX parity error */
-#define ERXOV 3 /* USART RX hardware buffer overflow */
-#define ENODATA 4 /* No data available */
+/* Load the configuration from the EEPROM. */
+static void eeprom_load_config(void)
+{
+ eeprom_busy_wait();
+ eeprom_read_block(&cfg, &eeprom.cfg, sizeof(cfg));
+ eeprom_busy_wait();
+}
-static inline int8_t usart_rx(uint8_t *data)
+/* Store the configuration to the EEPROM. */
+static void eeprom_store_config(void)
{
- uint8_t status;
-
- status = UCSRA;
- if (!(status & (1 << RXC)))
- return -ENODATA;
- if (unlikely(status & ((1 << FE) | (1 << PE) | (1 << DOR)))) {
- if (status & (1 << FE))
- return -ERXFE;
- if (status & (1 << PE))
- return -ERXPE;
- if (status & (1 << DOR))
- return -ERXOV;
- }
- *data = UDR;
+ eeprom_busy_wait();
+ eeprom_write_block(&cfg, &eeprom.cfg, sizeof(cfg));
+ eeprom_busy_wait();
+}
- return 0;
+/* Sensor measurement completed.
+ * Called in IRQ context. */
+void sensor_result(uint16_t mbar)
+{
+ /* Defer processing of the value to the mainloop, so we can do it with
+ * interrupts enabled. */
+ state.mbar = mbar;
+ mb();
+ state.needs_checking = 1;
}
-#define BAUDRATE 9600
+/* 1kHz system timer. */
+ISR(TIMER1_COMPA_vect)
+{
+ if (state.sensor_trigger_cnt > 0)
+ state.sensor_trigger_cnt--;
+}
-static void usart_init(void)
+void system_timer_init(void)
{
- uint8_t dummy;
-
- /* Set baud rate */
- UBRRL = lo8((CPU_HZ / 16 / BAUDRATE) * 2);
- UBRRH = hi8((CPU_HZ / 16 / BAUDRATE) * 2) & ~(1 << URSEL);
- UCSRA = (1 << U2X);
- /* 8 Data bits, 2 Stop bits, Even parity */
- UCSRC = (1 << URSEL) | (1 << UCSZ0) | (1 << UCSZ1) | (1 << UPM1) | (1 << USBS);
- /* Enable transceiver and RX IRQs */
- UCSRB = (1 << RXEN) | (1 << TXEN);// | (1 << RXCIE);
- /* Drain the RX buffer */
- while (usart_rx(&dummy) != -ENODATA)
- mb();
+ TCCR1B = (1 << WGM12) | (1 << CS10) | (1 << CS11); /* prescaler 64 */
+ OCR1A = 250; /* 1kHz timer at 16MHz crystal */
+ TIMSK |= (1 << OCIE1A);
+}
+
+static void valves_force_state(uint8_t new_state)
+{
+ if (state.valves == new_state)
+ return;
+ valves_global_switch(new_state);
+ state.valves = new_state;
+}
+
+static void adjust_pressure(uint16_t abs_offset, bool raise_pressure)
+{
+ if (0) {
+ //TODO if offset<value do only a short valve-open time.
+ valves_force_state(VALVES_IDLE);
+
+ } else {
+ /* Open the valve. It's closed again next time we check
+ * the pressure and it's OK. */
+ if (raise_pressure)
+ valves_force_state(VALVES_FLOW_IN);
+ else
+ valves_force_state(VALVES_FLOW_OUT);
+ }
+}
+
+/* Check the current pressure value against the desired value and
+ * adjust the pressure if needed. */
+static void check_pressure(void)
+{
+ int32_t offset;
+ uint16_t abs_offset;
+ bool is_too_big;
+
+ if (!cfg.autoadjust_enable)
+ return;
+
+ offset = (int32_t)state.mbar - (int32_t)cfg.desired;
+ abs_offset = abs(offset);
+ is_too_big = (offset >= 0);
+
+ if (abs_offset > cfg.hysteresis) {
+ /* Adjust the pressure */
+ adjust_pressure(abs_offset, !is_too_big);
+ } else {
+ /* The pressure is OK. Make sure the valves are
+ * all idle. */
+ valves_force_state(VALVES_IDLE);
+ }
}
int main(void)
{
cli();
+ /* It's OK to init the remote interface that early, as we
+ * have IRQs disabled throughout the init process. So we can't
+ * receive any remote commands, yet. But early init allows us
+ * to send error messages early. */
+ remote_init();
+ print("Pressure control initializing...\n");
+
valves_init();
- usart_init();
+ state.valves = VALVES_IDLE;
+ sensor_init();
+ eeprom_load_config();
+ system_timer_init();
sei();
+ print("Monitoring...\n");
while (1) {
- print("Hallo!\n");
- //TODO
+ mb();
+ if (state.sensor_trigger_cnt == 0) {
+ /* It's time for triggering another sensor measurement. */
+ state.sensor_trigger_cnt = -1;
+ mb();
+ sensor_trigger_read();
+ }
+ if (state.needs_checking) {
+ check_pressure();
+ /* Trigger another measurement in 50 milliseconds. */
+ state.sensor_trigger_cnt = 50;
+ mb();
+ }
+ remote_work();
}
}
diff --git a/pressure_control/firmware/main.h b/pressure_control/firmware/main.h
new file mode 100644
index 0000000..abdfa8f
--- /dev/null
+++ b/pressure_control/firmware/main.h
@@ -0,0 +1,36 @@
+#ifndef MAIN_H_
+#define MAIN_H_
+
+#include "util.h"
+
+#include <stdint.h>
+
+
+struct pressure_config {
+ /* Desired pressure in mBar */
+ uint16_t desired;
+ /* Pressure hysteresis in mBar */
+ uint16_t hysteresis;
+ /* Auto-adjustment is enabled. */
+ bool autoadjust_enable;
+};
+
+struct pressure_state {
+ /* Current pressure in the tank (in mBar) */
+ uint16_t mbar;
+ /* True, if the current pressure value needs checking against
+ * the desired pressure config. */
+ bool needs_checking;
+ /* Trigger count:
+ * >0 = waiting
+ * 0 = trigger now
+ * -1 = triggered and running. */
+ int8_t sensor_trigger_cnt;
+ /* Current valves state (enum valves_global_state) */
+ uint8_t valves;
+};
+
+void get_pressure_config(struct pressure_config *cfg);
+void get_pressure_state(struct pressure_state *state);
+
+#endif /* MAIN_H_ */
diff --git a/pressure_control/firmware/remote.c b/pressure_control/firmware/remote.c
new file mode 100644
index 0000000..d759e7a
--- /dev/null
+++ b/pressure_control/firmware/remote.c
@@ -0,0 +1,243 @@
+/*
+ * Pneumatic pressure controller.
+ * Remote control.
+ *
+ * Copyright (C) 2008 Michael Buesch <mb@bu3sch.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "remote.h"
+#include "util.h"
+#include "calibration.h"
+#include "main.h"
+
+#include <string.h>
+
+#include <avr/io.h>
+
+
+#define BAUDRATE 9600
+
+
+static struct remote_message rx_msg;
+static uint8_t rx_msg_count;
+static bool rx_msg_valid;
+
+
+static inline void usart_tx(uint8_t data)
+{
+ while (!(UCSRA & (1 << UDRE)))
+ ;
+ UDR = data;
+}
+
+static void usart_tx_buf(const void *_buf, uint8_t size)
+{
+ const uint8_t *buf = _buf;
+
+ while (size) {
+ usart_tx(*buf);
+ buf++;
+ size--;
+ }
+}
+
+#define ERXFE 1 /* USART RX frame error */
+#define ERXPE 2 /* USART RX parity error */
+#define ERXOV 3 /* USART RX hardware buffer overflow */
+#define ENODATA 4 /* No data available */
+
+static inline int8_t usart_rx(uint8_t *data)
+{
+ uint8_t status;
+
+ status = UCSRA;
+ if (!(status & (1 << RXC)))
+ return -ENODATA;
+ if (unlikely(status & ((1 << FE) | (1 << PE) | (1 << DOR)))) {
+ if (status & (1 << FE))
+ return -ERXFE;
+ if (status & (1 << PE))
+ return -ERXPE;
+ if (status & (1 << DOR))
+ return -ERXOV;
+ }
+ *data = UDR;
+
+ return 0;
+}
+
+static void send_message(struct remote_message *msg)
+{
+ /* Calculate the CRC. */
+ msg->crc = crc16_block_update(0xFFFF, msg,
+ sizeof(*msg) - sizeof(msg->crc));
+ msg->crc ^= 0xFFFF;
+ /* And transmit the bits. */
+ usart_tx_buf(msg, sizeof(*msg));
+}
+
+static void send_message_error(uint8_t error_code)
+{
+ struct remote_message msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.id = MSG_ERROR;
+ msg.error.code = error_code;
+
+ send_message(&msg);
+}
+
+static void handle_received_message(void)
+{
+ struct remote_message reply;
+ uint16_t calc_crc;
+
+ calc_crc = crc16_block_update(0xFFFF, &rx_msg,
+ sizeof(rx_msg) - sizeof(rx_msg.crc));
+ calc_crc ^= 0xFFFF;
+ if (calc_crc != rx_msg.crc) {
+ /* CRC mismatch. */
+ send_message_error(MSG_ERR_CHKSUM);
+ return;
+ }
+ memset(&reply, 0, sizeof(reply));
+
+ switch (rx_msg.id) {
+ case MSG_PING:
+ reply.id = MSG_PONG;
+ send_message(&reply);
+ break;
+ case MSG_GET_CURRENT_PRESSURE: {
+ struct pressure_state state;
+
+ get_pressure_state(&state);
+ reply.id = MSG_CURRENT_PRESSURE;
+ reply.pressure.mbar = state.mbar;
+ send_message(&reply);
+ break;
+ }
+ case MSG_GET_DESIRED_PRESSURE: {
+ struct pressure_config conf;
+
+ get_pressure_config(&conf);
+ reply.id = MSG_DESIRED_PRESSURE;
+ reply.pressure.mbar = conf.desired;
+ send_message(&reply);
+ break;
+ }
+ case MSG_GET_HYSTERESIS: {
+ struct pressure_config conf;
+
+ get_pressure_config(&conf);
+ reply.id = MSG_HYSTERESIS;
+ reply.pressure.mbar = conf.hysteresis;
+ send_message(&reply);
+ break;
+ }
+ case MSG_GET_CONFIG_FLAGS: {
+ struct pressure_config conf;
+
+ get_pressure_config(&conf);
+ reply.id = MSG_CONFIG_FLAGS;
+ if (conf.autoadjust_enable)
+ reply.config.flags |= (1 << CFG_FLAG_AUTOADJUST_ENABLE);
+ send_message(&reply);
+ break;
+ } }
+}
+
+/* RX interrupt */
+ISR(USART_RXC_vect)
+{
+ uint8_t *rxbuf = (uint8_t *)&rx_msg;
+ int8_t err;
+ uint8_t data;
+
+ if (rx_msg_valid)
+ return;
+
+ while (1) {
+ err = usart_rx(&data);
+ if (err == -ENODATA)
+ break;
+ if (unlikely(err)) {
+ //TODO other error
+ data = 0;
+ }
+ rxbuf[rx_msg_count++] = data;
+ if (rx_msg_count == sizeof(struct remote_message)) {
+ rx_msg_count = 0;
+ mb();
+ rx_msg_valid = 1;
+ }
+ }
+}
+
+void print_pgm(const prog_char *str)
+{
+ struct remote_message msg;
+ uint8_t c, i;
+
+ do {
+ memset(&msg, 0, sizeof(msg));
+ msg.id = MSG_LOGMESSAGE;
+
+ for (i = 0; i < sizeof(msg.logmessage.str); i++) {
+ c = pgm_read_byte(str);
+ if (c == '\0')
+ break;
+ str++;
+ msg.logmessage.str[i] = c;
+ }
+
+ send_message(&msg);
+ } while (c != '\0');
+}
+
+/* Maintanance work. Called with IRQs enabled. */
+void remote_work(void)
+{
+ if (rx_msg_valid) {
+ handle_received_message();
+ mb();
+ rx_msg_valid = 0;
+ }
+}
+
+static void usart_init(void)
+{
+ uint8_t dummy;
+
+ /* Set baud rate */
+ UBRRL = lo8((CPU_HZ / 16 / BAUDRATE) * 2);
+ UBRRH = hi8((CPU_HZ / 16 / BAUDRATE) * 2) & ~(1 << URSEL);
+ UCSRA = (1 << U2X);
+ /* 8 Data bits, 2 Stop bits, Even parity */
+ UCSRC = (1 << URSEL) | (1 << UCSZ0) | (1 << UCSZ1) | (1 << UPM1) | (1 << USBS);
+ /* Enable transceiver and RX IRQs */
+ UCSRB = (1 << RXEN) | (1 << TXEN) | (1 << RXCIE);
+ /* Drain the RX buffer */
+ while (usart_rx(&dummy) != -ENODATA)
+ mb();
+}
+
+void remote_init(void)
+{
+ /* The remote tool depends on the exact size (and layout). */
+ BUILD_BUG_ON(sizeof(struct remote_message) != 38);
+
+ usart_init();
+}
diff --git a/pressure_control/firmware/remote.h b/pressure_control/firmware/remote.h
new file mode 100644
index 0000000..af18abf
--- /dev/null
+++ b/pressure_control/firmware/remote.h
@@ -0,0 +1,68 @@
+#ifndef REMOTE_H_
+#define REMOTE_H_
+
+#include <stdint.h>
+
+#include <avr/pgmspace.h>
+
+
+enum remote_message_id {
+ MSG_INVALID = 0, /* Discard me */
+ MSG_ERROR,
+ MSG_LOGMESSAGE,
+ MSG_PING,
+ MSG_PONG,
+ MSG_GET_CURRENT_PRESSURE,
+ MSG_CURRENT_PRESSURE,
+ MSG_GET_DESIRED_PRESSURE,
+ MSG_DESIRED_PRESSURE,
+ MSG_SET_DESIRED_PRESSURE,
+ MSG_GET_HYSTERESIS,
+ MSG_HYSTERESIS,
+ MSG_SET_HYSTERESIS,
+ MSG_GET_CONFIG_FLAGS,
+ MSG_CONFIG_FLAGS,
+ MSG_SET_CONFIG_FLAGS,
+};
+
+enum remote_message_error {
+ MSG_ERR_NONE = 0,
+ MSG_ERR_CHKSUM,
+};
+
+enum remote_message_config_flags {
+ CFG_FLAG_AUTOADJUST_ENABLE = 0,
+};
+
+struct remote_message {
+ uint8_t id;
+ uint8_t __padding0[3];
+
+ union {
+ struct {
+ uint8_t code;
+ } __attribute__((packed)) error;
+ struct {
+ char str[32];
+ } __attribute__((packed)) logmessage;
+ struct {
+ uint16_t mbar;
+ } __attribute__((packed)) pressure;
+ struct {
+ uint32_t flags;
+ } __attribute__((packed)) config;
+
+ uint8_t __padding1[32];
+ } __attribute__((packed));
+
+ uint16_t crc;
+} __attribute__((packed));
+
+
+void print_pgm(const prog_char *msg);
+#define print(string_literal) print_pgm(PSTR(string_literal))
+
+void remote_work(void);
+void remote_init(void);
+
+#endif /* REMOTE_H_ */
diff --git a/pressure_control/firmware/sensor.c b/pressure_control/firmware/sensor.c
index e663d7a..2ed36f3 100644
--- a/pressure_control/firmware/sensor.c
+++ b/pressure_control/firmware/sensor.c
@@ -19,10 +19,65 @@
*/
#include "sensor.h"
+#include "util.h"
#include <avr/io.h>
+#include <avr/interrupt.h>
+/*** The sensor enable signal ***/
+#define SENSOR_ENABLE_DDR DDRC
+#define SENSOR_ENABLE_PORT PORTC
+#define SENSOR_ENABLE_BIT 1
+
+
+static inline void sensor_enable(void)
+{
+ SENSOR_ENABLE_PORT |= (1 << SENSOR_ENABLE_BIT);
+}
+
+static inline void sensor_disable(void)
+{
+ SENSOR_ENABLE_PORT &= ~(1 << SENSOR_ENABLE_BIT);
+}
+
+ISR(ADC_vect)
+{
+ uint16_t val;
+
+ val = ADC;
+ sensor_disable();
+ //TODO process value
+ sensor_result(val);
+}
+
+static inline void adc_trigger(bool with_irq)
+{
+ /* Set the multiplexer to ADC-0, AVcc Ref. */
+ ADMUX = (1 << REFS0);
+ /* Start ADC with a prescaler of 128. That's a ADC freq
+ * of 125kHz on a 16MHz crystal. */
+ ADCSRA = (1 << ADEN) | (1 << ADSC) |
+ (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2) |
+ (with_irq ? (1 << ADIE) : 0);
+}
+
+void sensor_trigger_read(void)
+{
+ /* Enable the sensor and wait a dwell time for the
+ * sensor to stabilize. */
+ sensor_enable();
+ udelay(500);
+ /* Finally trigger the ADC conversion. */
+ adc_trigger(1);
+}
+
void sensor_init(void)
{
+ SENSOR_ENABLE_DDR |= (1 << SENSOR_ENABLE_BIT);
+ sensor_disable();
+ /* Discard the first ADC result. */
+ adc_trigger(0);
+ while (ADCSRA & (1 << ADSC))
+ mb();
}
diff --git a/pressure_control/firmware/sensor.h b/pressure_control/firmware/sensor.h
index dd03e29..a6230ad 100644
--- a/pressure_control/firmware/sensor.h
+++ b/pressure_control/firmware/sensor.h
@@ -4,6 +4,10 @@
#include <stdint.h>
+void sensor_trigger_read(void);
void sensor_init(void);
+/* Callback for sensor value reporting. */
+extern void sensor_result(uint16_t millibar_result_value);
+
#endif /* SENSOR_H_ */
diff --git a/pressure_control/firmware/util.c b/pressure_control/firmware/util.c
index 3cecc46..0056cbd 100644
--- a/pressure_control/firmware/util.c
+++ b/pressure_control/firmware/util.c
@@ -1,22 +1,26 @@
/*
- * Utility functions
+ * Utility functions.
*
- * Copyright (C) 2008 Michael Buesch <mb@bu3sch.de>
+ * Copyright (C) 2008 Michael Buesch <mb@bu3sch.de>
*
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
- * This program 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 General Public License for more details.
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "util.h"
#include "calibration.h"
+#include <util/crc16.h>
#include <avr/io.h>
#include <avr/sleep.h>
@@ -76,3 +80,16 @@ void infinite_sleep(void)
while (1)
sleep_mode();
}
+
+uint16_t crc16_block_update(uint16_t crc, const void *_data, uint16_t size)
+{
+ const uint8_t *data = _data;
+
+ while (size) {
+ crc = _crc16_update(crc, *data);
+ data++;
+ size--;
+ }
+
+ return crc;
+}
diff --git a/pressure_control/firmware/util.h b/pressure_control/firmware/util.h
index 11c5f27..0c37990 100644
--- a/pressure_control/firmware/util.h
+++ b/pressure_control/firmware/util.h
@@ -72,4 +72,7 @@ static inline void irq_restore(uint8_t sreg_flags)
#define irqs_disabled() (!(SREG & (1 << SREG_I)))
+
+uint16_t crc16_block_update(uint16_t crc, const void *data, uint16_t size);
+
#endif /* UTIL_H_ */
diff --git a/pressure_control/remote/pctl-remote b/pressure_control/remote/pctl-remote
index bc60515..9dba6f2 100755
--- a/pressure_control/remote/pctl-remote
+++ b/pressure_control/remote/pctl-remote
@@ -19,11 +19,42 @@ import getopt
import sys
from serial.serialposix import *
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+
# Serial communication port configuration
-CONFIG_BAUDRATE = 9600
-CONFIG_BYTESIZE = 8
-CONFIG_PARITY = PARITY_EVEN
-CONFIG_STOPBITS = 2
+CONFIG_BAUDRATE = 9600
+CONFIG_BYTESIZE = 8
+CONFIG_PARITY = PARITY_EVEN
+CONFIG_STOPBITS = 2
+
+# The size of one message
+MSG_SIZE = 38
+MSG_PAYLOAD_SIZE = 32
+# Message IDs
+MSG_INVALID = 0
+MSG_ERROR = 1
+MSG_LOGMESSAGE = 2
+MSG_PING = 3
+MSG_PONG = 4
+MSG_GET_CURRENT_PRESSURE = 5
+MSG_CURRENT_PRESSURE = 6
+MSG_GET_DESIRED_PRESSURE = 7
+MSG_DESIRED_PRESSURE = 8
+MSG_SET_DESIRED_PRESSURE = 9
+MSG_GET_HYSTERESIS = 10
+MSG_HYSTERESIS = 11
+MSG_SET_HYSTERESIS = 12
+MSG_GET_CONFIG_FLAGS = 13
+MSG_CONFIG_FLAGS = 14
+MSG_SET_CONFIG_FLAGS = 15
+# Message error codes
+MSG_ERR_NONE = 0
+MSG_ERR_CHKSUM = 1
+MSG_ERR_NOREPLY = -1 # internal. Not sent over wire.
+# Config flags
+CFG_FLAG_AUTOADJUST_ENABLE = 0
def usage():
@@ -58,16 +89,302 @@ def parseArgs():
usage()
sys.exit(0)
+class RemoteProtocol(QObject):
+ def __init__(self, ttyfile):
+ QObject.__init__(self)
+
+ self.serial = Serial(ttyfile, CONFIG_BAUDRATE,
+ CONFIG_BYTESIZE, CONFIG_PARITY,
+ CONFIG_STOPBITS)
+
+ self.pollTimer = QTimer(self)
+ self.connect(self.pollTimer, SIGNAL("timeout()"), self.poll)
+ self.pollTimer.start(50)
+
+ reply = self.sendMessageSyncReply(MSG_PING, "", MSG_PONG)
+ if not reply:
+ print "Communication with device failed. No reply to PING request."
+ sys.exit(1)
+ mainwnd.centralWidget().log.addText("PING->PONG success. Device is alife.\n")
+
+ def poll(self):
+ if self.serial.inWaiting() < MSG_SIZE:
+ return
+ self.parseMessage(self.serial.read(MSG_SIZE))
+
+ def checksumMessage(self, msg):
+ calc_crc = self.__crc16_update_buffer(0xFFFF, msg[0:-2])
+ calc_crc ^= 0xFFFF
+ want_crc = (ord(msg[-2]) | (ord(msg[-1]) << 8))
+ if calc_crc != want_crc:
+ text = self.tr("ERROR: message CRC mismatch\n")
+ mainwnd.centralWidget().log.addText(text)
+ self.serial.flushInput()
+ return False
+ return True
+
+ def parseMessage(self, msg):
+ if not self.checksumMessage(msg):
+ return
+ id = ord(msg[0])
+ if (id == MSG_LOGMESSAGE):
+ str = self.getPayload(msg).rstrip('\0')
+ mainwnd.centralWidget().log.addText(str)
+ if (id == MSG_PING):
+ sendMessage(MSG_PING, [])
+ if (id == MSG_CURRENT_PRESSURE):
+ mainwnd.centralWidget().parseCurrentPressureMsg(msg)
+
+ def getPayload(self, msg):
+ return msg[4:-2]
+
+ def sendMessage(self, id, payload):
+ """Send a message"""
+ assert(len(payload) <= MSG_PAYLOAD_SIZE)
+ # Create the header
+ msg = "%c\0\0\0" % id
+ # Add the payload
+ msg += payload
+ # Pad the payload up to the constant size
+ i = MSG_PAYLOAD_SIZE - len(payload)
+ while i:
+ msg += '\0'
+ i -= 1
+ # Calculate the CRC
+ crc = self.__crc16_update_buffer(0xFFFF, msg)
+ crc ^= 0xFFFF
+ # Add the CRC to the message
+ msg += "%c%c" % ((crc & 0xFF), ((crc >> 8) & 0xFF))
+ # Send the message
+ assert(len(msg) == MSG_SIZE)
+ self.serial.write(msg)
+
+ def sendMessageSyncReply(self, id, payload, replyId):
+ """Send a message and synchronously wait for the reply."""
+ self.pollTimer.stop()
+ self.sendMessage(id, payload)
+ timeout = QDateTime.currentDateTime().addSecs(2)
+ while True:
+ if QDateTime.currentDateTime() >= timeout:
+ msg = None
+ break
+ if self.serial.inWaiting() < MSG_SIZE:
+ app.processEvents()
+ QThread.msleep(50)
+ continue
+ msg = self.serial.read(MSG_SIZE)
+ if not self.checksumMessage(msg):
+ continue
+ msgid = ord(msg[0])
+ if msgid == replyId:
+ break
+ # This is not a reply to our message.
+ self.parseMessage(msg)
+ self.pollTimer.start()
+ return msg
+
+ def sendMessageSyncError(self, id, payload):
+ """Sends a message and synchronously waits for the MSG_ERROR reply."""
+ reply = self.sendMessageSyncReply(id, payload, MSG_ERROR)
+ if not reply:
+ return MSG_ERR_NOREPLY
+ return ord(self.getPayload(reply)[0])
+
+ def __crc16_update_buffer(self, crc, buf):
+ for c in buf:
+ crc ^= ord(c)
+ for i in range(0, 8):
+ if crc & 1:
+ crc = (crc >> 1) ^ 0xA001
+ else:
+ crc = (crc >> 1)
+ return crc
+
+class StatusBar(QStatusBar):
+ def showMessage(self, msg):
+ QStatusBar.showMessage(self, msg, 10000)
+
+class LogBrowser(QTextEdit):
+ def __init__(self, parent=None):
+ QTextBrowser.__init__(self, parent)
+
+ self.needTimeStamp = True
+ self.setReadOnly(1)
+ self.addText(self.tr("Pressure Control logging started\n"));
+
+ def addText(self, text):
+ if self.needTimeStamp:
+ date = QDateTime.currentDateTime()
+ text = date.toString("[hh:mm:ss] ") + text
+ self.textCursor().setPosition(len(self.toPlainText()))
+ self.insertPlainText(text)
+ self.needTimeStamp = (text[-1] in QString("\r\n"))
+
+class MainWidget(QWidget):
+ def __init__(self, parent=None):
+ QWidget.__init__(self, parent)
+ self.initialized = False
+
+ layout = QVBoxLayout()
+
+ h = QHBoxLayout()
+ label = QLabel(self.tr("Current pressure:"), self)
+ h.addWidget(label)
+ self.curPressure = QLCDNumber(self)
+ self.curPressure.setSegmentStyle(QLCDNumber.Flat)
+ self.curPressure.display(5.1)
+ h.addWidget(self.curPressure)
+ label = QLabel(self.tr("Bar"), self)
+ h.addWidget(label)
+ h.addStretch()
+ layout.addLayout(h)
+
+ self.autoCheckbox = QCheckBox(self.tr("Automatically adjust pressure"), self)
+ layout.addWidget(self.autoCheckbox)
+
+ h = QHBoxLayout()
+ label = QLabel(self.tr("Desired pressure:"), self)
+ h.addStretch()
+ h.addWidget(label)
+ self.pressureSpin = QDoubleSpinBox(self)
+ self.pressureSpin.setMinimum(1)
+ self.pressureSpin.setMaximum(8)
+ self.pressureSpin.setSingleStep(0.1)
+ self.pressureSpin.setSuffix(self.tr(" Bar"))
+ self.connect(self.pressureSpin, SIGNAL("valueChanged(double)"),
+ self.desiredPressureChanged)
+ h.addWidget(self.pressureSpin)
+ layout.addLayout(h)
+
+ h = QHBoxLayout()
+ label = QLabel(self.tr("Hysteresis:"), self)
+ h.addStretch()
+ h.addWidget(label)
+ self.hystSpin = QDoubleSpinBox(self)
+ self.hystSpin.setMinimum(0.1)
+ self.hystSpin.setMaximum(8)
+ self.hystSpin.setSingleStep(0.1)
+ self.hystSpin.setSuffix(self.tr(" Bar"))
+ self.connect(self.hystSpin, SIGNAL("valueChanged(double)"),
+ self.desiredHysteresisChanged)
+ h.addWidget(self.hystSpin)
+ layout.addLayout(h)
+
+ self.log = LogBrowser(self)
+ layout.addWidget(self.log)
+
+ self.setLayout(layout)
+
+ def initializeState(self):
+ # Get the current pressure
+ reply = remote.sendMessageSyncReply(MSG_GET_CURRENT_PRESSURE, "",
+ MSG_CURRENT_PRESSURE)
+ if not reply:
+ print "Failed to fetch current pressure. No reply."
+ sys.exit(1)
+ self.parseCurrentPressureMsg(reply)
+
+ # Get the desired pressure
+ reply = remote.sendMessageSyncReply(MSG_GET_DESIRED_PRESSURE, "",
+ MSG_DESIRED_PRESSURE)
+ if not reply:
+ print "Failed to fetch desired pressure. No reply."
+ sys.exit(1)
+ reply = remote.getPayload(reply)
+ mbar = ord(reply[0]) | (ord(reply[1]) << 8)
+ self.pressureSpin.setValue(float(mbar) / 1000)
+
+ # Get the hysteresis
+ reply = remote.sendMessageSyncReply(MSG_GET_HYSTERESIS, "",
+ MSG_HYSTERESIS)
+ if not reply:
+ print "Failed to fetch hysteresis. No reply."
+ sys.exit(1)
+ reply = remote.getPayload(reply)
+ mbar = ord(reply[0]) | (ord(reply[1]) << 8)
+ self.hystSpin.setValue(float(mbar) / 1000)
+
+ # Get the config flags
+ reply = remote.sendMessageSyncReply(MSG_GET_CONFIG_FLAGS, "",
+ MSG_CONFIG_FLAGS)
+ if not reply:
+ print "Failed to fetch config flags. No reply."
+ sys.exit(1)
+ reply = remote.getPayload(reply)
+ flags = ord(reply[0]) | (ord(reply[1]) << 8) | \
+ (ord(reply[2]) << 16) | (ord(reply[3]) << 24)
+ if flags & (1 << CFG_FLAG_AUTOADJUST_ENABLE):
+ self.autoCheckbox.setCheckState(Qt.Checked)
+ #TODO
+
+ self.initialized = True
+
+ def parseCurrentPressureMsg(self, msg):
+ msg = remote.getPayload(msg)
+ mbar = ord(msg[0]) | (ord(msg[1]) << 8)
+ self.curPressure.display(float(mbar) / 1000)
+
+ def desiredPressureChanged(self):
+ if not self.initialized:
+ return
+ mbar = int(self.pressureSpin.value() * 1000)
+ data = "%c%c" % ((mbar & 0xFF), ((mbar >> 8) & 0xFF))
+ err = remote.sendMessageSyncError(MSG_SET_DESIRED_PRESSURE, data)
+ if err != MSG_ERR_NONE:
+ self.log.addText(self.tr("Failed to change pressure. Error=%u\n" % err))
+
+ def desiredHysteresisChanged(self):
+ if not self.initialized:
+ return
+ mbar = int(self.hystSpin.value() * 1000)
+ data = "%c%c" % ((mbar & 0xFF), ((mbar >> 8) & 0xFF))
+ err = remote.sendMessageSyncError(MSG_SET_HYSTERESIS, data)
+ if err != MSG_ERR_NONE:
+ self.log.addText(self.tr("Failed to change hysteresis. Error=%u\n" % err))
+
+class MainWindow(QMainWindow):
+ def __init__(self, parent=None):
+ QMainWindow.__init__(self, parent)
+ self.setWindowTitle(self.tr("Pneumatic pressure control"))
+
+ mb = QMenuBar(self)
+ ctlmen = QMenu(self.tr("Control"), mb)
+ ctlmen.addAction(self.tr("Exit"), self.close)
+ mb.addMenu(ctlmen)
+ helpmen = QMenu(self.tr("Help"), mb)
+ helpmen.addAction(self.tr("About"), self.about)
+ mb.addMenu(helpmen)
+ self.setMenuBar(mb)
+
+ self.setStatusBar(StatusBar())
+ self.setCentralWidget(MainWidget())
+
+ self.resize(400, 500)
+
+ def initializeState(self):
+ self.centralWidget().initializeState()
+
+ def about(self):
+ QMessageBox.information(self, self.tr("About"),
+ self.tr("Pneumatic pressure control\n"
+ "Copyright (c) 2008 Michael Buesch"))
+
def main():
+ global remote
+ global mainwnd
+ global app
+
+ mainwnd = None
+
+ app = QApplication(sys.argv)
parseArgs()
- fd = Serial(opt_ttyfile, CONFIG_BAUDRATE,
- CONFIG_BYTESIZE, CONFIG_PARITY,
- CONFIG_STOPBITS)
+ mainwnd = MainWindow()
+ remote = RemoteProtocol(opt_ttyfile)
- while True:
- x = fd.readline()
- sys.stdout.write(x)
+ mainwnd.initializeState()
+ mainwnd.show()
+ exit(app.exec_())
if __name__ == "__main__":
try:
bues.ch cgit interface