├── .gitignore ├── Makefile ├── README.md ├── UNLICENSE ├── adc.c ├── adc.h ├── beep.c ├── beep.h ├── button.c ├── button.h ├── control ├── UNLICENSE ├── aboutdialog.cpp ├── aboutdialog.h ├── aboutdialog.ui ├── app.ico ├── comm.cpp ├── comm.h ├── common.qrc ├── configdialog.cpp ├── configdialog.h ├── configdialog.ui ├── crc.cpp ├── crc.h ├── curvedata.cpp ├── curvedata.h ├── decoder.cpp ├── decoder.h ├── electronic_load.pro ├── flasher.cpp ├── flasher.h ├── flasherworker.cpp ├── flasherworker.h ├── flashprogressdialog.cpp ├── flashprogressdialog.h ├── flashprogressdialog.ui ├── icon.svg ├── icon16.png ├── icon24.png ├── icon256.png ├── icon32.png ├── icon48.png ├── installer │ └── config │ │ └── config.xml ├── logtable.cpp ├── logtable.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── sample.h ├── samplestorage.cpp ├── samplestorage.h ├── settings.h ├── stdio_fix.h ├── tablemodel.cpp ├── tablemodel.h ├── utils.cpp └── utils.h ├── displays.c ├── displays.h ├── docs ├── 1.jpg └── 2.jpg ├── encoder.c ├── encoder.h ├── encoderbutton.c ├── encoderbutton.h ├── fan.c ├── fan.h ├── flash.c ├── flash.h ├── load.c ├── load.h ├── main.c ├── misc ├── default_opt.bin └── eeprom.bin ├── ringbuffer.c ├── ringbuffer.h ├── settings.h ├── stm8.h ├── strings.c ├── strings.h ├── system.c ├── system.h ├── systemtimer.c ├── systemtimer.h ├── uart.c └── uart.h /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | */*.pro.user 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MAIN=main.c 2 | SRC=system.c systemtimer.c strings.c flash.c uart.c displays.c button.c encoder.c encoderbutton.c beep.c fan.c load.c adc.c ringbuffer.c 3 | RELS=$(SRC:.c=.rel) 4 | 5 | all: bin 6 | 7 | bin: $(MAIN) $(RELS) 8 | @mkdir -p bin 9 | /home/dev/local/sdcc-3.6.0/bin/sdcc --std-c11 --opt-code-size -mstm8 -lstm8 $(MAIN) $(wildcard bin/*.rel) -o bin/ 10 | 11 | .c.rel: 12 | @mkdir -p bin 13 | /home/dev/local/sdcc-3.6.0/bin/sdcc -c --std-c11 --opt-code-size -mstm8 -lstm8 $< -o bin/ 14 | 15 | clean: 16 | @rm -rf bin 17 | 18 | flash: bin 19 | /home/dev/local/stm8flash/stm8flash -c stlinkv2 -p stm8s105k4 -w bin/main.ihx 20 | 21 | .SUFFIXES: .c .rel 22 | 23 | .PHONY: clean flash 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Electronic Load 2 | Reinvented firmware for the electronic load 60W. With PC control Software (Windows/Linux). 3 | 4 | Status: **beta**. 5 | 6 | The load can be ordered from China (AliExpress, eBay etc), something like this one: 7 | 8 | ![The electronic load](docs/1.jpg) 9 | 10 | Added features: 11 | * full control via UART 12 | * calibration (via direct EEPROM programming) 13 | * parameters (e.g. minimal current) are changable 14 | * bootloader can be enabled 15 | 16 | I would recommend to not program the chip in the device, because: 17 | * then you cannot return to original firmware if you don't like this one 18 | * it's STM8S**0**05K6T6C, that means you can one program it about 100 times 19 | * you cannot user a simple USB-UART adapter to program it first time 20 | 21 | Take rather a new (empty) STM8S**1**05K4T6C or STM8S**1**05K6T6C and solder it. 22 | 23 | Programmer connection: 24 | 25 | ![Programmer connection](docs/2.jpg) 26 | 27 | [Analog part schematic](http://www.voltlog.com/pub/dummy-load-sch.pdf) (one correction: PB3 is connected to +12V via 20k). 28 | 29 | Calibration values in the code are for my instance, may be you have to adjust them for your one. 30 | 31 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /adc.c: -------------------------------------------------------------------------------- 1 | #include "adc.h" 2 | 3 | #include "stm8.h" 4 | 5 | static ADC_onResult_t _onResult; 6 | static uint8_t _counts[ADC_COUNTS_SIZE]; 7 | static uint8_t _ch; 8 | static uint8_t _n; 9 | static uint8_t _countMax; 10 | static uint16_t _countValue; 11 | 12 | void ADC_init(void) { 13 | ADC1->CR1 |= ADC1_CR1_SPSEL_6; 14 | ADC1->CR1 |= ADC1_CR1_ADON; // wake up 15 | ADC1->CR2 |= ADC1_CR2_ALIGN; // right-align 16 | } 17 | 18 | void ADC_start(uint8_t ch, uint8_t n, ADC_onResult_t onResult) { 19 | _onResult = onResult; 20 | _ch = ch; 21 | _n = n; 22 | _countMax = 0; 23 | _countValue = 0; 24 | 25 | // the same as: 26 | // for(i = 0; i < ADC_COUNTS_SIZE; ++i) _counts[i] = 0; // 1108 us 27 | // but 515 us 28 | __asm 29 | LDW x, #__counts 30 | 00001$: 31 | CLR (x) 32 | INCW x 33 | CPW x, #__counts+ADC_COUNTS_SIZE 34 | JRULT 00001$ 35 | __endasm; 36 | 37 | ADC1->CR3 &= ~ADC1_CR3_OVR; 38 | ADC1->CSR = ADC1_CSR_EOCIE | _ch; 39 | ADC1->CR1 |= ADC1_CR1_CONT; 40 | ADC1->CR3 |= ADC1_CR3_DBUF; 41 | ADC1->CR1 |= ADC1_CR1_ADON; 42 | } 43 | 44 | // ~7 us for non-last one 45 | void ADC_ADC1_eoc(void) __interrupt(IRQN_ADC1_EOC) { 46 | uint16_t v, c; 47 | ADC1->CSR = ADC1_CSR_EOCIE | _ch; 48 | 49 | v = ADC1->DRL | ((uint16_t)ADC1->DRH << 8); 50 | c = _counts[v] + 1; 51 | _counts[v] = c; 52 | if(c > _countMax) { 53 | _countMax = c; 54 | _countValue = v; 55 | } 56 | 57 | --_n; 58 | if(_n == 1) ADC1->CR1 &= ~ADC1_CR1_CONT; 59 | if(_n == 0) _onResult(_counts, _countMax, _countValue); 60 | } 61 | 62 | -------------------------------------------------------------------------------- /adc.h: -------------------------------------------------------------------------------- 1 | #ifndef _ADC_H_ 2 | #define _ADC_H_ 3 | 4 | #include 5 | 6 | #include "stm8.h" 7 | 8 | #define ADC_COUNTS_SIZE 1024 9 | 10 | typedef void (*ADC_onResult_t)(const uint8_t* counts, uint8_t countMax, uint16_t countValue); 11 | 12 | void ADC_init(void); 13 | 14 | void ADC_start(uint8_t ch, uint8_t n, ADC_onResult_t onResult); 15 | 16 | void ADC_ADC1_eoc(void) __interrupt(IRQN_ADC1_EOC); 17 | 18 | #endif // _ADC_H_ 19 | -------------------------------------------------------------------------------- /beep.c: -------------------------------------------------------------------------------- 1 | #include "beep.h" 2 | 3 | #include "stm8.h" 4 | #include "system.h" 5 | #include "systemtimer.h" 6 | #include "flash.h" 7 | #include "uart.h" 8 | 9 | // ==================================================================================================================== 10 | 11 | static uint32_t start; 12 | static uint8_t duration; 13 | 14 | inline void on(void) { 15 | BEEP->CSR |= BEEP_CSR_BEEPEN; 16 | } 17 | 18 | inline void off(void) { 19 | BEEP->CSR &= ~BEEP_CSR_BEEPEN; 20 | } 21 | 22 | void BEEP_init(void) { 23 | #define BEEP_CALIBRATION 14 24 | if(!(OPT->AFR & OPT_AFR_D4_BEEP)) { 25 | UART_write("fix AFR7\r\n"); 26 | FLASH_unlockOpt(); 27 | OPT->AFR |= OPT_AFR_D4_BEEP; 28 | OPT->NAFR &= ~OPT_AFR_D4_BEEP; 29 | FLASH_waitOpt(); 30 | FLASH_lockOpt(); 31 | SYSTEM_reset(); 32 | } 33 | BEEP->CSR = BEEP_CALIBRATION; 34 | } 35 | 36 | // ~250ns (sound off) 37 | void BEEP_process(void) { 38 | if(duration && (SYSTEMTIMER_ms - start > duration)) { 39 | duration = 0; 40 | off(); 41 | } 42 | } 43 | 44 | void BEEP_beep(enum BEEP_freq f, uint8_t ms) { 45 | if(f == BEEP_freq_None) { 46 | off(); 47 | duration = 0; 48 | } 49 | else { 50 | BEEP->CSR = (BEEP->CSR & ~BEEP_CSR_BEEPSEL) | f; 51 | on(); 52 | start = SYSTEMTIMER_ms; 53 | duration = ms; 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /beep.h: -------------------------------------------------------------------------------- 1 | #ifndef _BEEP_H_ 2 | #define _BEEP_H_ 3 | 4 | #include "stm8.h" 5 | 6 | enum BEEP_freq { 7 | BEEP_freq_None = 0x0F, 8 | BEEP_freq_1k = BEEP_CSR_BEEPSEL_1KHZ, 9 | BEEP_freq_2k = BEEP_CSR_BEEPSEL_2KHZ, 10 | BEEP_freq_4k = BEEP_CSR_BEEPSEL_4KHZ 11 | }; 12 | 13 | // Beeper on PD4 14 | void BEEP_init(void); 15 | 16 | void BEEP_process(void); 17 | 18 | void BEEP_beep(enum BEEP_freq f, uint8_t ms); 19 | 20 | #endif // _BEEP_H_ 21 | -------------------------------------------------------------------------------- /button.c: -------------------------------------------------------------------------------- 1 | #include "button.h" 2 | 3 | #include "stm8.h" 4 | #include "systemtimer.h" 5 | 6 | #define BUTTON_BOUNCE_TIME_MS 20 7 | #define BUTTON_LONG_TIME_MS 1000 8 | 9 | enum PressValue { 10 | PressValue_None, 11 | PressValue_Short, 12 | PressValue_Long 13 | }; 14 | static volatile enum PressValue pressValue; 15 | static enum PressValue pressValueCopy; 16 | 17 | static volatile bool pressed; 18 | static volatile bool pressProcessed; 19 | static volatile uint32_t lastCheck; 20 | static volatile uint32_t pressTime; 21 | 22 | void BUTTON_init(void) { 23 | GPIOC->CR1 |= GPIO_CR1_4; // pull-up 24 | } 25 | 26 | // ~3.8 us 27 | void BUTTON_cycle(void) { 28 | bool p; 29 | 30 | if(pressValue != PressValue_None) return; 31 | if((SYSTEMTIMER_ms - lastCheck) < BUTTON_BOUNCE_TIME_MS) return; 32 | 33 | p = BUTTON_isPressed(); 34 | if(p && pressed && (SYSTEMTIMER_ms - pressTime) >= BUTTON_LONG_TIME_MS) { 35 | if(!pressProcessed) { 36 | pressValue = PressValue_Long; 37 | pressProcessed = true; 38 | } 39 | } 40 | else if(p != pressed) { 41 | if(p) { 42 | pressTime = SYSTEMTIMER_ms; 43 | } 44 | else if(!pressProcessed) { 45 | pressValue = PressValue_Short; 46 | } 47 | pressed = p; 48 | lastCheck = SYSTEMTIMER_ms; 49 | pressProcessed = false; 50 | } 51 | } 52 | 53 | void BUTTON_process(void) { 54 | // Atomic: 55 | // pressValueCopy = pressValue; 56 | // pressValue = 0; 57 | __asm 58 | CLR A 59 | EXG A, _pressValue 60 | LD _pressValueCopy, A 61 | __endasm; 62 | 63 | switch(pressValueCopy) { 64 | case PressValue_None: break; 65 | case PressValue_Short: BUTTON_onRelease(false); break; 66 | case PressValue_Long: BUTTON_onRelease(true); break; 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /button.h: -------------------------------------------------------------------------------- 1 | #ifndef _BUTTON_H_ 2 | #define _BUTTON_H_ 3 | 4 | #include 5 | 6 | #include "stm8.h" 7 | 8 | void BUTTON_onRelease(bool longpress); 9 | void BUTTON_init(void); 10 | void BUTTON_cycle(void); 11 | void BUTTON_process(void); 12 | inline bool BUTTON_isPressed(void) { return !(GPIOC->IDR & GPIO_IDR_4); } 13 | 14 | #endif // _BUTTON_H_ 15 | -------------------------------------------------------------------------------- /control/UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /control/aboutdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "aboutdialog.h" 2 | #include "ui_aboutdialog.h" 3 | 4 | AboutDialog::AboutDialog(QWidget *parent) : 5 | QDialog(parent), 6 | ui(new Ui::AboutDialog) 7 | { 8 | ui->setupUi(this); 9 | } 10 | 11 | AboutDialog::~AboutDialog() 12 | { 13 | delete ui; 14 | } 15 | -------------------------------------------------------------------------------- /control/aboutdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef ABOUTDIALOG_H 2 | #define ABOUTDIALOG_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class AboutDialog; 8 | } 9 | 10 | class AboutDialog : public QDialog 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit AboutDialog(QWidget *parent = 0); 16 | ~AboutDialog(); 17 | 18 | private: 19 | Ui::AboutDialog *ui; 20 | }; 21 | 22 | #endif // ABOUTDIALOG_H 23 | -------------------------------------------------------------------------------- /control/aboutdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | AboutDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 599 10 | 300 11 | 12 | 13 | 14 | About 15 | 16 | 17 | 18 | 19 | 290 20 | 240 21 | 281 22 | 32 23 | 24 | 25 | 26 | Qt::Horizontal 27 | 28 | 29 | QDialogButtonBox::Close 30 | 31 | 32 | 33 | 34 | 35 | 290 36 | 30 37 | 281 38 | 201 39 | 40 | 41 | 42 | false 43 | 44 | 45 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 46 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 47 | p, li { white-space: pre-wrap; } 48 | </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> 49 | <p style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600;">Electronic Load Control</span></p> 50 | <p style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Version 1.0.0</span></p> 51 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">This software was created by </span><a href="http://26th.net/public/projects"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">Anatoli Klassen</span></a><span style=" font-size:8pt;"><br /></span><span style=" font-size:10pt;">and put into the </span><a href="http://unlicense.org/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">public domain</span></a></p> 52 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Based on:</span></p> 53 | <ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:10pt;" style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.qt.io"><span style=" text-decoration: underline; color:#0000ff;">Qt</span></a> (LGPL)</li> 54 | <li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://qwt.sf.net"><span style=" text-decoration: underline; color:#0000ff;">Qwt</span></a> (LGPL)</li></ul></body></html> 55 | 56 | 57 | true 58 | 59 | 60 | 61 | 62 | 63 | 20 64 | 20 65 | 256 66 | 256 67 | 68 | 69 | 70 | 71 | 72 | 73 | :/icon256.png 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | buttonBox 83 | rejected() 84 | AboutDialog 85 | close() 86 | 87 | 88 | 248 89 | 254 90 | 91 | 92 | 157 93 | 274 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /control/app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev26th/electronic_load/8eccf2a8dfd2d0567c6b7756a88d2e565bfa988a/control/app.ico -------------------------------------------------------------------------------- /control/comm.cpp: -------------------------------------------------------------------------------- 1 | #include "comm.h" 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "crc.h" 8 | 9 | Comm::Comm() 10 | : state(State::Idle) 11 | { 12 | } 13 | 14 | void Comm::onStart() 15 | { 16 | setState(State::Idle); 17 | ser = new QSerialPort(this); 18 | connect(ser, &QSerialPort::readyRead, this, &Comm::on_readyRead); 19 | } 20 | 21 | void Comm::portConnect(QString portName) 22 | { 23 | if(ser->isOpen()) portDisconnect(); 24 | 25 | ser->setPortName(portName); 26 | ser->setBaudRate(115200); 27 | 28 | bool openSuccess = ser->open(QIODevice::ReadWrite); 29 | if(!openSuccess) 30 | emit error("Cannot connect to the port"); 31 | else 32 | setState(State::Connected); 33 | } 34 | 35 | void Comm::setState(State state) 36 | { 37 | if(this->state != state) { 38 | this->state = state; 39 | resetRx(); 40 | emit stateChanged(state); 41 | } 42 | } 43 | 44 | void Comm::portDisconnect() 45 | { 46 | ser->close(); 47 | setState(State::Idle); 48 | } 49 | 50 | void Comm::send(QByteArray data) 51 | { 52 | if(!ser->isOpen()) return; 53 | 54 | ser->clear(); 55 | 56 | QByteArray buf; 57 | buf.append('S'); 58 | buf.append(data.toHex().toUpper()); 59 | 60 | char crc = std::accumulate(data.begin(), data.end(), 0, crc8); 61 | QByteArray crcBuf; 62 | crcBuf.append(crc); 63 | buf.append(crcBuf.toHex().toUpper()); 64 | 65 | buf.append('\r'); 66 | (void)ser->write(buf); 67 | qDebug() << "<=" << data.toHex(); 68 | } 69 | 70 | void Comm::on_readyRead() 71 | { 72 | while(!ser->atEnd()) { 73 | processRead(); 74 | } 75 | } 76 | 77 | void Comm::resetRx() 78 | { 79 | subState = SubState::Start; 80 | rxBuf.clear(); 81 | } 82 | 83 | void Comm::processRx() 84 | { 85 | char crc = std::accumulate(rxBuf.begin(), rxBuf.end(), 0, crc8); 86 | if(crc == 0) { 87 | qDebug() << "=>" << rxBuf.toHex(); 88 | 89 | QByteArray buf; 90 | buf.append(rxBuf.data(), rxBuf.size()-1); 91 | emit data(buf, rxTimestamp); 92 | } 93 | } 94 | 95 | void Comm::processRead() 96 | { 97 | char c; 98 | if(!ser->getChar(&c)) return; 99 | 100 | switch(state) { 101 | case State::Idle: 102 | break; 103 | 104 | case State::Connected: 105 | if(c == 'S') { 106 | resetRx(); 107 | rxTimestamp = QDateTime::currentMSecsSinceEpoch(); 108 | subState = SubState::H; 109 | } 110 | else { 111 | uint8_t v; 112 | 113 | switch(subState) { 114 | case SubState::H: 115 | case SubState::L: 116 | if(c >= '0' && c <= '9') { 117 | v = c - '0'; 118 | } 119 | else if(c >= 'A' && c <= 'F') { 120 | v = c - 'A' + 10; 121 | } 122 | else if(c == '\n') { // ignore 123 | break; 124 | } 125 | else if(c == '\r') { // stop 126 | if(subState == SubState::L) { // unexpected, reset 127 | } 128 | else { 129 | processRx(); 130 | } 131 | resetRx(); 132 | 133 | break; 134 | } 135 | else { // unexpected symbol, reset 136 | resetRx(); 137 | break; 138 | } 139 | 140 | if(subState == SubState::H) { 141 | rxBuf.append(v << 4); 142 | subState = SubState::L; 143 | } 144 | else { 145 | rxBuf[rxBuf.size()-1] = (rxBuf.at(rxBuf.size()-1) | (char)v); 146 | subState = SubState::H; 147 | if(rxBuf.size() >= RXBUF_SIZE) resetRx(); 148 | } 149 | 150 | break; 151 | 152 | case SubState::Start: 153 | break; // ignore all 154 | } 155 | } 156 | 157 | break; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /control/comm.h: -------------------------------------------------------------------------------- 1 | #ifndef COMM_H 2 | #define COMM_H 3 | 4 | #include 5 | 6 | class Comm : public QObject { 7 | Q_OBJECT 8 | 9 | public: 10 | enum class State { 11 | Idle, 12 | Connected 13 | }; 14 | 15 | public: 16 | Comm(); 17 | 18 | static const int RXBUF_SIZE = 250; 19 | 20 | public slots: 21 | void onStart(); 22 | void portConnect(QString portName); 23 | void portDisconnect(); 24 | void send(QByteArray data); 25 | 26 | signals: 27 | void error(QString msg); 28 | void data(QByteArray d, qint64 timestamp); 29 | void stateChanged(Comm::State state); 30 | 31 | private slots: 32 | void on_readyRead(); 33 | 34 | private: 35 | enum class SubState { 36 | Start, 37 | H, 38 | L 39 | }; 40 | 41 | private: 42 | void setState(State state); 43 | void processRead(); 44 | void resetRx(); 45 | void processRx(); 46 | 47 | private: 48 | QSerialPort *ser; 49 | State state; 50 | SubState subState; 51 | QByteArray rxBuf; 52 | qint64 rxTimestamp; 53 | }; 54 | 55 | #endif // COMM_H 56 | -------------------------------------------------------------------------------- /control/common.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icon256.png 4 | app.ico 5 | 6 | 7 | -------------------------------------------------------------------------------- /control/configdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "configdialog.h" 2 | #include "ui_configdialog.h" 3 | 4 | #include "utils.h" 5 | 6 | ConfigDialog::ConfigDialog(QWidget *parent, CmdConfigData& deviceConfigData_) : 7 | QDialog(parent), 8 | ui(new Ui::ConfigDialog), 9 | deviceConfigData(deviceConfigData_) 10 | { 11 | ui->setupUi(this); 12 | 13 | ui->iSetCoefOffsetBox->setText(QString::number(deviceConfigData.iSetCoef.offset)); 14 | ui->iSetCoefMulBox->setText(QString::number(deviceConfigData.iSetCoef.mul)); 15 | ui->iSetCoefDivBox->setText(QString::number(deviceConfigData.iSetCoef.div)); 16 | 17 | ui->uCurCoefOffsetBox->setText(QString::number(deviceConfigData.uCurCoef.offset)); 18 | ui->uCurCoefMulBox->setText(QString::number(deviceConfigData.uCurCoef.mul)); 19 | ui->uCurCoefDivBox->setText(QString::number(deviceConfigData.uCurCoef.div)); 20 | 21 | ui->uSenseCoefOffsetBox->setText(QString::number(deviceConfigData.uSenseCoef.offset)); 22 | ui->uSenseCoefMulBox->setText(QString::number(deviceConfigData.uSenseCoef.mul)); 23 | ui->uSenseCoefDivBox->setText(QString::number(deviceConfigData.uSenseCoef.div)); 24 | 25 | ui->tempFanLowBox->setText(QString::number(deviceConfigData.tempFanLow)); 26 | ui->tempFanMidBox->setText(QString::number(deviceConfigData.tempFanMid)); 27 | ui->tempFanFullBox->setText(QString::number(deviceConfigData.tempFanFull)); 28 | ui->tempLimitBox->setText(QString::number(deviceConfigData.tempLimit)); 29 | ui->tempDefectBox->setText(QString::number(deviceConfigData.tempDefect)); 30 | ui->tempThresholdBox->setText(QString::number(deviceConfigData.tempThreshold)); 31 | 32 | ui->uSupMinBox->setText(QString::number(deviceConfigData.uSupMin)); 33 | ui->iSetMinBox->setText(QString::number(deviceConfigData.iSetMin)); 34 | ui->iSetMaxBox->setText(QString::number(deviceConfigData.iSetMax)); 35 | ui->uSetMinBox->setText(QString::number(deviceConfigData.uSetMin)); 36 | ui->uSetMaxBox->setText(QString::number(deviceConfigData.uSetMax)); 37 | ui->uSenseMinBox->setText(QString::number(deviceConfigData.uSenseMin)); 38 | ui->uNegativeBox->setText(QString::number(deviceConfigData.uNegative)); 39 | ui->uCurLimitBox->setText(QString::number(deviceConfigData.uMainLimit)); 40 | ui->powLimitBox->setText(QString::number(deviceConfigData.powLimit)); 41 | ui->ahMaxBox->setText(QString::number(deviceConfigData.ahMax)); 42 | ui->whMaxBox->setText(QString::number(deviceConfigData.whMax)); 43 | 44 | ui->funBox->setText(QString::number(deviceConfigData.fun)); 45 | ui->beepOnBox->setText(QString::number(deviceConfigData.beepOn)); 46 | ui->uSetBox->setText(QString::number(deviceConfigData.uSet)); 47 | ui->iSetBox->setText(QString::number(deviceConfigData.iSet)); 48 | ui->curUnitBox->setText(QString::number(deviceConfigData.curUnit)); 49 | } 50 | 51 | ConfigDialog::~ConfigDialog() 52 | { 53 | delete ui; 54 | } 55 | 56 | static int parseInt(const QString& s) 57 | { 58 | bool ok = false; 59 | int res = s.toInt(&ok); 60 | if(!ok) throw s; 61 | return res; 62 | } 63 | 64 | bool ConfigDialog::parseInput() 65 | { 66 | try { 67 | deviceConfigData.iSetCoef.offset = parseInt(ui->iSetCoefOffsetBox->text()); 68 | deviceConfigData.iSetCoef.mul = parseInt(ui->iSetCoefMulBox->text()); 69 | deviceConfigData.iSetCoef.div = parseInt(ui->iSetCoefDivBox->text()); 70 | 71 | deviceConfigData.uCurCoef.offset = parseInt(ui->uCurCoefOffsetBox->text()); 72 | deviceConfigData.uCurCoef.mul = parseInt(ui->uCurCoefMulBox->text()); 73 | deviceConfigData.uCurCoef.div = parseInt(ui->uCurCoefDivBox->text()); 74 | 75 | deviceConfigData.uSenseCoef.offset = parseInt(ui->uSenseCoefOffsetBox->text()); 76 | deviceConfigData.uSenseCoef.mul = parseInt(ui->uSenseCoefMulBox->text()); 77 | deviceConfigData.uSenseCoef.div = parseInt(ui->uSenseCoefDivBox->text()); 78 | 79 | deviceConfigData.tempFanLow = parseInt(ui->tempFanLowBox->text()); 80 | deviceConfigData.tempFanMid = parseInt(ui->tempFanMidBox->text()); 81 | deviceConfigData.tempFanFull = parseInt(ui->tempFanFullBox->text()); 82 | deviceConfigData.tempLimit = parseInt(ui->tempLimitBox->text()); 83 | deviceConfigData.tempDefect = parseInt(ui->tempDefectBox->text()); 84 | deviceConfigData.tempThreshold = parseInt(ui->tempThresholdBox->text()); 85 | 86 | deviceConfigData.uSupMin = parseInt(ui->uSupMinBox->text()); 87 | deviceConfigData.iSetMin = parseInt(ui->iSetMinBox->text()); 88 | deviceConfigData.iSetMax = parseInt(ui->iSetMaxBox->text()); 89 | deviceConfigData.uSetMin = parseInt(ui->uSetMinBox->text()); 90 | deviceConfigData.uSetMax = parseInt(ui->uSetMaxBox->text()); 91 | deviceConfigData.uSenseMin = parseInt(ui->uSenseMinBox->text()); 92 | deviceConfigData.uNegative = parseInt(ui->uNegativeBox->text()); 93 | deviceConfigData.uMainLimit = parseInt(ui->uCurLimitBox->text()); 94 | deviceConfigData.powLimit = parseInt(ui->powLimitBox->text()); 95 | deviceConfigData.ahMax = parseInt(ui->ahMaxBox->text()); 96 | deviceConfigData.whMax = parseInt(ui->whMaxBox->text()); 97 | 98 | deviceConfigData.fun = parseInt(ui->funBox->text()); 99 | deviceConfigData.beepOn = parseInt(ui->beepOnBox->text()); 100 | deviceConfigData.uSet = parseInt(ui->uSetBox->text()); 101 | deviceConfigData.iSet = parseInt(ui->iSetBox->text()); 102 | deviceConfigData.curUnit = parseInt(ui->curUnitBox->text()); 103 | 104 | deviceConfigData.cmd = Cmd::WriteConfig; 105 | deviceConfigData.state = CmdState::Request; 106 | 107 | return true; 108 | } 109 | catch(const QString& ex) { 110 | showError(QString("Cannot parse: %1").arg(ex)); 111 | return false; 112 | } 113 | } 114 | 115 | void ConfigDialog::on_buttonBox_accepted() 116 | { 117 | if(parseInput()) 118 | accept(); 119 | } 120 | 121 | void ConfigDialog::on_buttonBox_clicked(QAbstractButton *button) 122 | { 123 | if(button == (QAbstractButton*)(ui->buttonBox->button(QDialogButtonBox::Apply))) { 124 | if(parseInput()) { 125 | emit send(formCmdData(deviceConfigData)); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /control/configdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIGDIALOG_H 2 | #define CONFIGDIALOG_H 3 | 4 | #include "decoder.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace Ui { 10 | class ConfigDialog; 11 | } 12 | 13 | class ConfigDialog : public QDialog 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | explicit ConfigDialog(QWidget *parent, CmdConfigData& deviceConfigData_); 19 | ~ConfigDialog(); 20 | 21 | private slots: 22 | void on_buttonBox_accepted(); 23 | 24 | void on_buttonBox_clicked(QAbstractButton *button); 25 | 26 | signals: 27 | void send(QByteArray data); 28 | 29 | private: 30 | bool parseInput(); 31 | 32 | private: 33 | Ui::ConfigDialog *ui; 34 | CmdConfigData& deviceConfigData; 35 | }; 36 | 37 | #endif // CONFIGDIALOG_H 38 | -------------------------------------------------------------------------------- /control/configdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ConfigDialog 4 | 5 | 6 | Qt::ApplicationModal 7 | 8 | 9 | 10 | 0 11 | 0 12 | 456 13 | 462 14 | 15 | 16 | 17 | Device Configuration 18 | 19 | 20 | 21 | 22 | 23 | true 24 | 25 | 26 | 27 | 28 | 0 29 | -770 30 | 422 31 | 1179 32 | 33 | 34 | 35 | 36 | 37 | 38 | I-Coefficients 39 | 40 | 41 | 42 | 43 | 44 | Divider 45 | 46 | 47 | 48 | 49 | 50 | 51 | Offset 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | Multiplier 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | I-Set Minimum, mA 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 1 86 | 0 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 0 96 | 0 97 | 98 | 99 | 100 | U-Supply Minimum, raw 101 | 102 | 103 | 104 | 105 | 106 | 107 | U-Sense-Coefficients 108 | 109 | 110 | 111 | 112 | 113 | Divider 114 | 115 | 116 | 117 | 118 | 119 | 120 | Offset 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | Multiplier 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | U-Main-Coefficients 153 | 154 | 155 | 156 | 157 | 158 | Divider 159 | 160 | 161 | 162 | 163 | 164 | 165 | Offset 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | Multiplier 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | U-Limit, mV 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | Power Limit, mW 208 | 209 | 210 | 211 | 212 | 213 | 214 | Wh Maximum, mWh 215 | 216 | 217 | 218 | 219 | 220 | 221 | Function 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | Sound 232 | 233 | 234 | 235 | 236 | 237 | 238 | U-Set, mA 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | Temperature, raw 249 | 250 | 251 | 252 | 253 | 254 | Fan Full 255 | 256 | 257 | 258 | 259 | 260 | 261 | Limit 262 | 263 | 264 | 265 | 266 | 267 | 268 | Sensor Defect 269 | 270 | 271 | 272 | 273 | 274 | 275 | Fan Low 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | Fan Middle 286 | 287 | 288 | 289 | 290 | 291 | 292 | Threshold 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | U-Negative, raw 318 | 319 | 320 | 321 | 322 | 323 | 324 | U-Set Minimum, mV 325 | 326 | 327 | 328 | 329 | 330 | 331 | I-Set Maximum, mA 332 | 333 | 334 | 335 | 336 | 337 | 338 | U-Set Maximum, mV 339 | 340 | 341 | 342 | 343 | 344 | 345 | U-Sense Minimum, mV 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | Ah Maximum, mAh 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | I-Set, mA 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | Current Unit 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | Qt::Horizontal 402 | 403 | 404 | 405 | 40 406 | 20 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 0 416 | 0 417 | 418 | 419 | 420 | Qt::Horizontal 421 | 422 | 423 | QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Save 424 | 425 | 426 | false 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | buttonBox 436 | rejected() 437 | ConfigDialog 438 | close() 439 | 440 | 441 | 403 442 | 441 443 | 444 | 445 | 227 446 | 230 447 | 448 | 449 | 450 | 451 | 452 | -------------------------------------------------------------------------------- /control/crc.cpp: -------------------------------------------------------------------------------- 1 | #include "crc.h" 2 | 3 | char crc8(char crc, char b) { 4 | crc ^= b; 5 | for(char i = 0; i < 8; ++i) 6 | crc = crc & 0x80 ? (crc << 1) ^ 0x07 : crc << 1; 7 | return crc; 8 | } 9 | -------------------------------------------------------------------------------- /control/crc.h: -------------------------------------------------------------------------------- 1 | #ifndef CRC_H 2 | #define CRC_H 3 | 4 | char crc8(char crc, char b); 5 | 6 | #endif // CRC_H 7 | -------------------------------------------------------------------------------- /control/curvedata.cpp: -------------------------------------------------------------------------------- 1 | #include "curvedata.h" 2 | 3 | // ============================================================================================================= 4 | 5 | CurveData::CurveData(SampleStorage& storage_) 6 | : storage(storage_) 7 | { 8 | cleared(); 9 | 10 | connect(&storage, &SampleStorage::afterClear, this, &CurveData::cleared); 11 | connect(&storage, &SampleStorage::afterAppend, this, &CurveData::added); 12 | connect(&storage, &SampleStorage::afterAppendMultiple, this, &CurveData::addedMultiple); 13 | // note: SampleStorage::afterDelete is not connected 14 | // - min/max value will not be recalculated when samples are deleted via limit 15 | } 16 | 17 | CurveData::~CurveData() 18 | { 19 | } 20 | 21 | QPointF CurveData::sample(size_t i) const 22 | { 23 | const Sample& s = storage.sample(i); 24 | return QPointF((qreal)(s.timestamp - begin)/1000.0, s.u); 25 | } 26 | 27 | QRectF CurveData::boundingRect() const 28 | { 29 | if(storage.size() == 0) 30 | return QRectF(); 31 | 32 | const Sample& first = storage.sample(0); 33 | const Sample& last = storage.sample(storage.size()-1); 34 | 35 | return QRectF((qreal)(first.timestamp - begin)/1000.0, minValue, 36 | (qreal)(last.timestamp - first.timestamp)/1000.0, maxValue - minValue); 37 | } 38 | 39 | void CurveData::cleared() 40 | { 41 | begin = 0; 42 | minValue = 0.0; 43 | maxValue = 0.0; 44 | } 45 | 46 | void CurveData::added(const Sample& sample) 47 | { 48 | double value = sample.u; 49 | if(value == std::numeric_limits::infinity()) value = 0.0; 50 | 51 | if(!begin) { 52 | begin = sample.timestamp; 53 | minValue = value; 54 | maxValue = value; 55 | } 56 | else { 57 | if(value < minValue) minValue = value; 58 | if(value > maxValue) maxValue = value; 59 | } 60 | } 61 | 62 | void CurveData::addedMultiple(const QVector &list) 63 | { 64 | for(auto i = list.begin(), e = list.end(); i != e; ++i) 65 | added(*i); 66 | } 67 | -------------------------------------------------------------------------------- /control/curvedata.h: -------------------------------------------------------------------------------- 1 | #ifndef CURVEDATA_H 2 | #define CURVEDATA_H 3 | 4 | #include "samplestorage.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | class CurveData: public QObject, public QwtSeriesData 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | CurveData(SampleStorage& storage_); 16 | ~CurveData(); 17 | 18 | QPointF sample(size_t i) const override; 19 | size_t size() const override { return storage.size(); } 20 | QRectF boundingRect() const override; 21 | 22 | public slots: 23 | void cleared(); 24 | void added(const Sample& sample); 25 | void addedMultiple(const QVector &list); 26 | 27 | private: 28 | SampleStorage& storage; 29 | qint64 begin; 30 | double minValue; 31 | double maxValue; 32 | }; 33 | 34 | 35 | #endif // CURVEDATA_H 36 | 37 | -------------------------------------------------------------------------------- /control/decoder.cpp: -------------------------------------------------------------------------------- 1 | #include "decoder.h" 2 | 3 | #include 4 | #include 5 | 6 | QByteArray formCmdData(const CmdData& data) 7 | { 8 | QByteArray res; 9 | 10 | QDataStream stream(&res, QIODevice::WriteOnly); 11 | stream.setByteOrder(QDataStream::ByteOrder::BigEndian); 12 | 13 | stream << (uint8_t)((uint8_t)data.cmd | (uint8_t)data.state); 14 | 15 | switch(data.cmd) { 16 | case Cmd::WriteConfig: 17 | { 18 | const CmdConfigData& d = static_cast(data); 19 | stream << d.iSetCoef.offset; 20 | stream << d.iSetCoef.mul; 21 | stream << d.iSetCoef.div; 22 | stream << d.uCurCoef.offset; 23 | stream << d.uCurCoef.mul; 24 | stream << d.uCurCoef.div; 25 | stream << d.uSenseCoef.offset; 26 | stream << d.uSenseCoef.mul; 27 | stream << d.uSenseCoef.div; 28 | stream << d.uSupMin; 29 | stream << d.tempThreshold; 30 | stream << d.tempFanLow; 31 | stream << d.tempFanMid; 32 | stream << d.tempFanFull; 33 | stream << d.tempLimit; 34 | stream << d.tempDefect; 35 | stream << d.iSetMin; 36 | stream << d.iSetMax; 37 | stream << d.uSetMin; 38 | stream << d.uSetMax; 39 | stream << d.uSenseMin; 40 | stream << d.uNegative; 41 | stream << d.uMainLimit; 42 | stream << d.powLimit; 43 | stream << d.ahMax; 44 | stream << d.whMax; 45 | stream << d.fun; 46 | stream << d.beepOn; 47 | stream << d.uSet; 48 | stream << d.iSet; 49 | stream << d.curUnit; 50 | } 51 | break; 52 | 53 | case Cmd::WriteSettings: 54 | { 55 | const CmdSettingData& d = static_cast(data); 56 | stream << d.u; 57 | stream << d.i; 58 | } 59 | break; 60 | 61 | case Cmd::FlowState: 62 | { 63 | const CmdFlowStateData& d = static_cast(data); 64 | stream << d.interval; 65 | } 66 | break; 67 | 68 | case Cmd::Bootloader: 69 | { 70 | const CmdBootloaderData& d = static_cast(data); 71 | stream << (uint8_t)(d.enable ? 1 : 0); 72 | } 73 | break; 74 | 75 | case Cmd::ReadConfig: 76 | case Cmd::ReadSettings: 77 | case Cmd::GetVersion: 78 | case Cmd::ResetState: 79 | case Cmd::Reboot: 80 | break; 81 | 82 | default: 83 | assert(false); 84 | } 85 | 86 | return res; 87 | } 88 | 89 | CmdData* parseCmdData(const QByteArray& data) 90 | { 91 | if(data.isEmpty()) return nullptr; 92 | 93 | QDataStream stream(data); 94 | stream.setByteOrder(QDataStream::ByteOrder::BigEndian); 95 | 96 | uint8_t c; 97 | stream >> c; 98 | Cmd cmd = (Cmd)(c & 0x1F); 99 | CmdState state = (CmdState)(c & 0xE0); 100 | 101 | switch(cmd) { 102 | case Cmd::ReadConfig: 103 | { 104 | if(data.size() != 66) return nullptr; 105 | 106 | CmdConfigData* res = new CmdConfigData(cmd, state); 107 | stream >> res->iSetCoef.offset; 108 | stream >> res->iSetCoef.mul; 109 | stream >> res->iSetCoef.div; 110 | stream >> res->uCurCoef.offset; 111 | stream >> res->uCurCoef.mul; 112 | stream >> res->uCurCoef.div; 113 | stream >> res->uSenseCoef.offset; 114 | stream >> res->uSenseCoef.mul; 115 | stream >> res->uSenseCoef.div; 116 | stream >> res->uSupMin; 117 | stream >> res->tempThreshold; 118 | stream >> res->tempFanLow; 119 | stream >> res->tempFanMid; 120 | stream >> res->tempFanFull; 121 | stream >> res->tempLimit; 122 | stream >> res->tempDefect; 123 | stream >> res->iSetMin; 124 | stream >> res->iSetMax; 125 | stream >> res->uSetMin; 126 | stream >> res->uSetMax; 127 | stream >> res->uSenseMin; 128 | stream >> res->uNegative; 129 | stream >> res->uMainLimit; 130 | stream >> res->powLimit; 131 | stream >> res->ahMax; 132 | stream >> res->whMax; 133 | stream >> res->fun; 134 | stream >> res->beepOn; 135 | stream >> res->uSet; 136 | stream >> res->iSet; 137 | stream >> res->curUnit; 138 | return res; 139 | } 140 | 141 | case Cmd::ReadSettings: 142 | { 143 | if(data.size() != 5) return nullptr; 144 | 145 | CmdSettingData* res = new CmdSettingData(cmd, state); 146 | stream >> res->u; 147 | stream >> res->i; 148 | return res; 149 | } 150 | 151 | case Cmd::GetVersion: 152 | { 153 | if(data.size() != 5) return nullptr; 154 | 155 | CmdVersionData* res = new CmdVersionData(cmd, state); 156 | stream >> res->v; 157 | return res; 158 | } 159 | 160 | case Cmd::GetState: 161 | { 162 | if(data.size() != 19) return nullptr; 163 | 164 | CmdStateData* res = new CmdStateData(cmd, state); 165 | uint8_t mode; 166 | stream >> mode; 167 | stream >> res->error; 168 | stream >> res->uMain; 169 | stream >> res->uSense; 170 | stream >> res->tempRaw; 171 | stream >> res->uSupRaw; 172 | stream >> res->ah; 173 | stream >> res->wh; 174 | res->mode = (DeviceMode)mode; 175 | return res; 176 | } 177 | 178 | default: 179 | return nullptr; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /control/decoder.h: -------------------------------------------------------------------------------- 1 | #ifndef DECODER_H 2 | #define DECODER_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | static const uint8_t DEVICE_ERROR_POLARITY = (1 << 0); 9 | static const uint8_t DEVICE_ERROR_SUPPLY = (1 << 1); 10 | static const uint8_t DEVICE_ERROR_OUP = (1 << 2); 11 | static const uint8_t DEVICE_ERROR_OTP = (1 << 3); 12 | static const uint8_t DEVICE_ERROR_ERT = (1 << 4); 13 | 14 | enum class Cmd { 15 | Reboot = 0x01, 16 | GetVersion, 17 | ReadConfig, 18 | WriteConfig, 19 | Display, 20 | Beep, 21 | Fan, 22 | InputDisable, 23 | ReadSettings, 24 | WriteSettings, 25 | SetMode, 26 | GetState, 27 | ResetState, 28 | FlowState, 29 | ReadRaw, 30 | WriteRaw, 31 | Bootloader, 32 | }; 33 | 34 | enum class CmdState { 35 | Request = 0x00, 36 | Response = 0x40, 37 | Event = 0x80, 38 | Error = 0xC0 39 | }; 40 | 41 | enum class DeviceMode { 42 | Booting, 43 | MenuFun, 44 | MenuBeep, 45 | MenuCalV, 46 | MenuCalI, 47 | CalV1, 48 | CalV2, 49 | CalI1r, 50 | CalI1v, 51 | CalI2r, 52 | CalI2v, 53 | Fun1, 54 | Fun1Run, 55 | Fun2, 56 | Fun2Pre, 57 | Fun2Run, 58 | Fun2Warn, 59 | Fun2Res, 60 | }; 61 | 62 | struct ValueCoef { 63 | uint16_t offset; 64 | uint16_t mul; 65 | uint16_t div; 66 | }; 67 | 68 | struct CmdData { 69 | Cmd cmd; 70 | CmdState state; 71 | 72 | CmdData(Cmd cmd_) : cmd(cmd_), state(CmdState::Request) {} 73 | CmdData(Cmd cmd_, CmdState state_) : cmd(cmd_), state(state_) {} 74 | }; 75 | 76 | struct CmdConfigData : public CmdData { 77 | CmdConfigData(Cmd cmd_, CmdState state_) : CmdData(cmd_, state_) {} 78 | 79 | struct ValueCoef iSetCoef; 80 | struct ValueCoef uCurCoef; 81 | struct ValueCoef uSenseCoef; 82 | uint16_t uSupMin; // raw 83 | uint16_t tempThreshold; 84 | uint16_t tempFanLow; 85 | uint16_t tempFanMid; 86 | uint16_t tempFanFull; 87 | uint16_t tempLimit; 88 | uint16_t tempDefect; 89 | uint16_t iSetMin; // mA 90 | uint16_t iSetMax; // mA 91 | uint16_t uSetMin; // mV 92 | uint16_t uSetMax; // mV 93 | uint16_t uSenseMin; // mV 94 | uint16_t uNegative; // raw 95 | uint16_t uMainLimit; // mV 96 | uint32_t powLimit; // mW 97 | uint32_t ahMax; // mAh 98 | uint32_t whMax; // mWh 99 | uint8_t fun; 100 | uint8_t beepOn; 101 | uint16_t uSet; // mV 102 | uint16_t iSet; // mA 103 | uint8_t curUnit; // 0=mA, 1=100uA 104 | }; 105 | 106 | struct CmdSettingData : public CmdData { 107 | CmdSettingData(Cmd cmd_, CmdState state_) : CmdData(cmd_, state_) {} 108 | 109 | uint16_t u; 110 | uint16_t i; 111 | }; 112 | 113 | struct CmdStateData : public CmdData { 114 | CmdStateData(Cmd cmd_, CmdState state_) : CmdData(cmd_, state_) {} 115 | 116 | DeviceMode mode; 117 | uint8_t error; 118 | uint16_t uMain; 119 | uint16_t uSense; 120 | uint16_t tempRaw; 121 | uint16_t uSupRaw; 122 | uint32_t ah; 123 | uint32_t wh; 124 | }; 125 | 126 | struct CmdFlowStateData : public CmdData { 127 | CmdFlowStateData(uint16_t interval_) : CmdData(Cmd::FlowState, CmdState::Request), interval(interval_) {} 128 | 129 | uint16_t interval; 130 | }; 131 | 132 | struct CmdVersionData : public CmdData { 133 | CmdVersionData(Cmd cmd_, CmdState state_) : CmdData(cmd_, state_) {} 134 | 135 | uint32_t v; 136 | }; 137 | 138 | struct CmdBootloaderData : public CmdData { 139 | CmdBootloaderData(bool enable_) : CmdData(Cmd::Bootloader, CmdState::Request), enable(enable_) {} 140 | 141 | bool enable; 142 | }; 143 | 144 | QByteArray formCmdData(const CmdData& data); 145 | 146 | CmdData* parseCmdData(const QByteArray &data); 147 | 148 | #endif // DECODER_H 149 | -------------------------------------------------------------------------------- /control/electronic_load.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2017-10-29T23:16:29 4 | # 5 | #------------------------------------------------- 6 | 7 | QMAKE_CXXFLAGS += -std=c++11 8 | QT += core gui serialport 9 | 10 | QT += widgets 11 | 12 | TARGET = electronic_load 13 | TEMPLATE = app 14 | 15 | SOURCES += main.cpp\ 16 | mainwindow.cpp \ 17 | comm.cpp \ 18 | utils.cpp \ 19 | samplestorage.cpp \ 20 | curvedata.cpp \ 21 | tablemodel.cpp \ 22 | logtable.cpp \ 23 | aboutdialog.cpp \ 24 | decoder.cpp \ 25 | configdialog.cpp \ 26 | flasherworker.cpp \ 27 | flasher.cpp \ 28 | flashprogressdialog.cpp \ 29 | crc.cpp 30 | 31 | HEADERS += mainwindow.h \ 32 | decoder.h \ 33 | comm.h \ 34 | utils.h \ 35 | samplestorage.h \ 36 | curvedata.h \ 37 | tablemodel.h \ 38 | sample.h \ 39 | logtable.h \ 40 | settings.h \ 41 | aboutdialog.h \ 42 | stdio_fix.h \ 43 | configdialog.h \ 44 | flasherworker.h \ 45 | flasher.h \ 46 | flashprogressdialog.h \ 47 | crc.h 48 | 49 | FORMS += mainwindow.ui \ 50 | aboutdialog.ui \ 51 | configdialog.ui \ 52 | flashprogressdialog.ui 53 | 54 | RC_ICONS = app.ico 55 | 56 | DISTFILES += 57 | 58 | RESOURCES += \ 59 | common.qrc 60 | 61 | unix { 62 | CONFIG += qwt-qt5 63 | INCLUDEPATH += /usr/include/qwt 64 | LIBS += -lqwt-qt5 65 | } 66 | 67 | win32 { 68 | include (C:/qwt-6.1.3/features/qwt.prf) 69 | CONFIG += qwt 70 | } 71 | -------------------------------------------------------------------------------- /control/flasher.cpp: -------------------------------------------------------------------------------- 1 | #include "flasher.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | Q_DECLARE_METATYPE(Flasher::State) 9 | 10 | static const int SYNCH_INTERVAL_MS = 250; 11 | 12 | enum class BlCmd : uint8_t { 13 | Synch = 0x7F, 14 | Ack = 0x79, 15 | }; 16 | 17 | Flasher::Flasher() 18 | : worker(nullptr), state(State::Idle), timerId(0) 19 | { 20 | qRegisterMetaType(); 21 | } 22 | 23 | Flasher::~Flasher() { 24 | workerThread.quit(); 25 | workerThread.wait(); 26 | } 27 | 28 | void Flasher::onStart() 29 | { 30 | setState(State::Idle); 31 | ser = new QSerialPort(this); 32 | connect(ser, &QSerialPort::readyRead, this, &Flasher::on_readyRead); 33 | 34 | worker = new FlasherWorker(); 35 | worker->moveToThread(&workerThread); 36 | connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); 37 | connect(this, &Flasher::download, worker, &FlasherWorker::download); 38 | connect(this, &Flasher::cancel, worker, &FlasherWorker::cancel); 39 | connect(worker, &FlasherWorker::send, this, &Flasher::send); 40 | connect(worker, &FlasherWorker::result, this, &Flasher::on_workerResult); 41 | connect(worker, &FlasherWorker::progress, this, &Flasher::progress); 42 | workerThread.start(); 43 | } 44 | 45 | void Flasher::startTheTimer() 46 | { 47 | if(timerId) killTimer(timerId); 48 | timerId = startTimer(SYNCH_INTERVAL_MS, Qt::PreciseTimer); 49 | } 50 | 51 | void Flasher::stopTheTimer() 52 | { 53 | if(timerId) { 54 | killTimer(timerId); 55 | timerId = 0; 56 | } 57 | } 58 | 59 | void Flasher::portConnect(QString portName, QByteArray fileContent) 60 | { 61 | this->fileContent = fileContent; 62 | 63 | if(ser->isOpen()) portDisconnect(); 64 | 65 | ser->setPortName(portName); 66 | ser->setBaudRate(115200); 67 | 68 | bool openSuccess = ser->open(QIODevice::ReadWrite); 69 | if(!openSuccess) { 70 | emit error("Cannot connect to the port"); 71 | } 72 | else { 73 | setState(State::Connected); 74 | startTheTimer(); 75 | } 76 | } 77 | 78 | void Flasher::timerEvent(QTimerEvent *event) 79 | { 80 | if(!ser->isOpen()) return; 81 | 82 | (void)event; 83 | 84 | switch(state) { 85 | case State::Connected: 86 | send(QByteArray(1, (uint8_t)BlCmd::Synch)); 87 | break; 88 | 89 | default: 90 | ; 91 | } 92 | } 93 | 94 | void Flasher::on_workerResult(const QString& state) 95 | { 96 | if("1" == state) { 97 | setState(State::Programming); 98 | } 99 | else if("2" == state) { 100 | setState(State::Verifying); 101 | } 102 | else if("3" == state) { 103 | setState(State::Resetting); 104 | } 105 | else { 106 | setState(State::Ready); 107 | } 108 | } 109 | 110 | void Flasher::setState(State state) 111 | { 112 | if(this->state != state) { 113 | this->state = state; 114 | emit stateChanged(state); 115 | } 116 | } 117 | 118 | void Flasher::portDisconnect() 119 | { 120 | ser->close(); 121 | setState(State::Idle); 122 | emit cancel(); 123 | } 124 | 125 | void Flasher::send(const QByteArray& data) 126 | { 127 | if(!ser->isOpen()) return; 128 | 129 | (void)ser->write(data); 130 | //qDebug() << "<=" << data.toHex(); 131 | } 132 | 133 | void Flasher::on_readyRead() 134 | { 135 | char c; 136 | while(ser->getChar(&c)) { 137 | //qDebug() << "=>" << QByteArray(1, c).toHex(); 138 | switch(state) { 139 | case State::Connected: 140 | if(c == (uint8_t)BlCmd::Ack) { 141 | stopTheTimer(); 142 | send(QByteArray(1, c)); // echo 143 | setState(State::Preparing); 144 | emit download(fileContent); 145 | } 146 | break; 147 | 148 | case State::Preparing: 149 | case State::Programming: 150 | case State::Verifying: 151 | case State::Resetting: 152 | send(QByteArray(1, c)); // echo 153 | worker->addChar(c); 154 | break; 155 | 156 | default: 157 | ; 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /control/flasher.h: -------------------------------------------------------------------------------- 1 | #ifndef FLASHER_H 2 | #define FLASHER_H 3 | 4 | #include 5 | #include "flasherworker.h" 6 | 7 | class Flasher : public QObject { 8 | Q_OBJECT 9 | 10 | public: 11 | enum class State { 12 | Idle, 13 | Connected, 14 | Preparing, 15 | Programming, 16 | Verifying, 17 | Resetting, 18 | Ready, 19 | }; 20 | 21 | public: 22 | Flasher(); 23 | ~Flasher(); 24 | 25 | public slots: 26 | void onStart(); 27 | void portConnect(QString portName, QByteArray fileContent); 28 | void portDisconnect(); 29 | 30 | signals: 31 | void error(QString msg); 32 | void stateChanged(Flasher::State state); 33 | void download(const QByteArray& bin); 34 | void progress(double percent); 35 | void cancel(); 36 | 37 | private slots: 38 | void send(const QByteArray& data); 39 | void on_readyRead(); 40 | void on_workerResult(const QString& res); 41 | 42 | protected: 43 | void timerEvent(QTimerEvent *event) override; 44 | 45 | private: 46 | void setState(State state); 47 | void processRead(); 48 | void startTheTimer(); 49 | void stopTheTimer(); 50 | void restartTheTimer(); 51 | 52 | private: 53 | QThread workerThread; 54 | FlasherWorker* worker; 55 | 56 | QSerialPort *ser; 57 | State state; 58 | int timerId; 59 | QQueue toSend; 60 | QByteArray fileContent; 61 | 62 | QByteArray readBuf; 63 | uint32_t readAddr; 64 | uint32_t readLen; 65 | uint32_t chunkLen; 66 | }; 67 | 68 | #endif // FLASHER_H 69 | -------------------------------------------------------------------------------- /control/flasherworker.cpp: -------------------------------------------------------------------------------- 1 | #include "flasherworker.h" 2 | 3 | #include 4 | 5 | static const uint32_t ADDR_WRITE_ROUTINE = 0x000000A0; 6 | static const uint32_t ADDR_FLASH = 0x00008000; 7 | static const unsigned long RX_TIMEOUT_MS = 2000; 8 | static const int PROGRESS_POINTS_WRITE = 1; 9 | static const int PROGRESS_POINTS_READ = 5; 10 | 11 | enum class BlCmd : uint8_t { 12 | Get = 0x00, 13 | Read = 0x11, 14 | Erase = 0x43, 15 | Write = 0x31, 16 | Speed = 0x03, 17 | Go = 0x21, 18 | 19 | Synch = 0x7F, 20 | Ack = 0x79, 21 | Nack = 0x1F, 22 | Busy = 0xAA, 23 | }; 24 | 25 | // converted from E_W_ROUTINEs_32K_ver_1.2.s19, which is atteched to the UM0560 26 | // © 2017 STMicroelectronics – All rights reserved 27 | static const unsigned char E_W_ROUTINEs_32K_ver_1_3[] = { 28 | 0x5f, 0x3f, 0x90, 0x3f, 0x96, 0x72, 0x09, 0x00, 0x8e, 0x16, 0xcd, 0x60, 29 | 0x65, 0xb6, 0x90, 0xe7, 0x00, 0x5c, 0x4c, 0xb7, 0x90, 0xa1, 0x21, 0x26, 30 | 0xf1, 0xa6, 0x20, 0xb7, 0x88, 0x5f, 0x3f, 0x90, 0xe6, 0x00, 0xa1, 0x20, 31 | 0x26, 0x07, 0x3f, 0x8a, 0xae, 0x40, 0x00, 0x20, 0x0c, 0x3f, 0x8a, 0xae, 32 | 0x00, 0x80, 0x42, 0x58, 0x58, 0x58, 0x1c, 0x80, 0x00, 0x90, 0x5f, 0xcd, 33 | 0x60, 0x65, 0x9e, 0xb7, 0x8b, 0x9f, 0xb7, 0x8c, 0xa6, 0x20, 0xc7, 0x50, 34 | 0x5b, 0x43, 0xc7, 0x50, 0x5c, 0x4f, 0x92, 0xbd, 0x00, 0x8a, 0x5c, 0x9f, 35 | 0xb7, 0x8c, 0x4f, 0x92, 0xbd, 0x00, 0x8a, 0x5c, 0x9f, 0xb7, 0x8c, 0x4f, 36 | 0x92, 0xbd, 0x00, 0x8a, 0x5c, 0x9f, 0xb7, 0x8c, 0x4f, 0x92, 0xbd, 0x00, 37 | 0x8a, 0x72, 0x00, 0x50, 0x5f, 0x07, 0x72, 0x05, 0x50, 0x5f, 0xfb, 0x20, 38 | 0x04, 0x72, 0x10, 0x00, 0x96, 0x90, 0xa3, 0x00, 0x07, 0x27, 0x0a, 0x90, 39 | 0x5c, 0x1d, 0x00, 0x03, 0x1c, 0x00, 0x80, 0x20, 0xae, 0xb6, 0x90, 0xb1, 40 | 0x88, 0x27, 0x1c, 0x5f, 0x3c, 0x90, 0xb6, 0x90, 0x97, 0xcc, 0x00, 0xc0, 41 | 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 42 | 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x81, 0xcd, 0x60, 0x65, 0x5f, 43 | 0x3f, 0x97, 0x72, 0x0d, 0x00, 0x8e, 0x18, 0x72, 0x00, 0x00, 0x94, 0x0b, 44 | 0xa6, 0x01, 0xc7, 0x50, 0x5b, 0x43, 0xc7, 0x50, 0x5c, 0x20, 0x08, 0x35, 45 | 0x81, 0x50, 0x5b, 0x35, 0x7e, 0x50, 0x5c, 0x3f, 0x94, 0xf6, 0x92, 0xa7, 46 | 0x00, 0x8a, 0x72, 0x0c, 0x00, 0x8e, 0x13, 0x72, 0x00, 0x50, 0x5f, 0x07, 47 | 0x72, 0x05, 0x50, 0x5f, 0xfb, 0x20, 0x04, 0x72, 0x10, 0x00, 0x97, 0xcd, 48 | 0x60, 0x65, 0x9f, 0xb1, 0x88, 0x27, 0x03, 0x5c, 0x20, 0xdb, 0x72, 0x0d, 49 | 0x00, 0x8e, 0x10, 0x72, 0x00, 0x50, 0x5f, 0x07, 0x72, 0x05, 0x50, 0x5f, 50 | 0xfb, 0x20, 0x24, 0x72, 0x10, 0x00, 0x97, 0x20, 0x1e, 0x9d, 0x9d, 0x9d, 51 | 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 52 | 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 53 | 0x9d, 0x9d, 0x9d, 0x81 54 | }; 55 | 56 | FlasherWorker::FlasherWorker() 57 | { 58 | } 59 | 60 | FlasherWorker::~FlasherWorker() 61 | { 62 | } 63 | 64 | void FlasherWorker::download(const QByteArray& bin) 65 | { 66 | try { 67 | QByteArray routine((const char*)E_W_ROUTINEs_32K_ver_1_3, sizeof(E_W_ROUTINEs_32K_ver_1_3)); 68 | 69 | pr = 0; 70 | total = routine.size() * PROGRESS_POINTS_WRITE + bin.size() * (PROGRESS_POINTS_WRITE + PROGRESS_POINTS_READ); 71 | canceled = false; 72 | 73 | writeToDevice(ADDR_WRITE_ROUTINE, routine); 74 | 75 | emit result("1"); 76 | writeToDevice(ADDR_FLASH, bin); 77 | 78 | emit result("2"); 79 | QByteArray r = readFromDevice(ADDR_FLASH, bin.size()); 80 | 81 | //qDebug() << "GOT" << r.toHex(); 82 | if(bin == r) { 83 | emit result("3"); 84 | startDevice(ADDR_FLASH); 85 | } 86 | else { 87 | throw "Validation failed"; 88 | } 89 | 90 | emit result(""); 91 | } 92 | catch(char const* msg) { 93 | qDebug() << msg; 94 | emit result(msg); 95 | } 96 | } 97 | 98 | void FlasherWorker::cancel() 99 | { 100 | canceled = true; 101 | } 102 | 103 | void FlasherWorker::addChar(char c) 104 | { 105 | QMutexLocker locker(&mutex); 106 | queue.enqueue(c); 107 | cond.wakeAll(); 108 | } 109 | 110 | char FlasherWorker::getChar() 111 | { 112 | QMutexLocker locker(&mutex); 113 | if(queue.isEmpty()) { 114 | if(!cond.wait(&mutex, RX_TIMEOUT_MS)) 115 | throw "Rx timeout"; 116 | } 117 | char c = queue.dequeue(); 118 | 119 | return c; 120 | } 121 | 122 | static char xorChecksum(char checksum, char b) { 123 | return checksum ^ b; 124 | } 125 | 126 | void FlasherWorker::waitForAck() 127 | { 128 | if(canceled) 129 | throw "Canceled"; 130 | 131 | if(getChar() != (uint8_t)BlCmd::Ack) 132 | throw "Not ack"; 133 | } 134 | 135 | void FlasherWorker::incProgress(int points) 136 | { 137 | pr += points; 138 | emit progress((double)pr / total * 100); 139 | } 140 | 141 | void FlasherWorker::writeToDevice(uint32_t addr, const QByteArray& data) 142 | { 143 | //qDebug() << "writeToDevice" << addr << data.size(); 144 | QElapsedTimer elapsed; 145 | elapsed.start(); 146 | for(int i = 0; i < data.size(); i += 128, addr += 128) { 147 | { 148 | QByteArray buf; 149 | buf.append((char)BlCmd::Write); 150 | 151 | char checksum = std::accumulate(buf.begin(), buf.end(), 0xFF, xorChecksum); 152 | buf.append(checksum); 153 | 154 | emit send(buf); 155 | waitForAck(); 156 | } 157 | 158 | { 159 | QByteArray buf; 160 | buf.append((char)(addr >> 24)); 161 | buf.append((char)(addr >> 16)); 162 | buf.append((char)(addr >> 8)); 163 | buf.append((char)(addr)); 164 | 165 | char checksum = std::accumulate(buf.begin(), buf.end(), 0, xorChecksum); 166 | buf.append(checksum); 167 | 168 | emit send(buf); 169 | waitForAck(); 170 | } 171 | 172 | { 173 | QByteArray buf; 174 | int chunkLen = std::min(128, data.size() - i); 175 | buf.append((char)(chunkLen - 1)); 176 | buf.append(data.mid(i, chunkLen)); 177 | 178 | char checksum = std::accumulate(buf.begin(), buf.end(), 0, xorChecksum); 179 | buf.append(checksum); 180 | 181 | emit send(buf); 182 | waitForAck(); 183 | 184 | incProgress(chunkLen * PROGRESS_POINTS_WRITE); 185 | } 186 | } 187 | //qDebug() << "elapsed" << elapsed.elapsed(); 188 | } 189 | 190 | QByteArray FlasherWorker::readFromDevice(uint32_t addr, int len) 191 | { 192 | //qDebug() << "readFromDevice" << addr << len; 193 | QElapsedTimer elapsed; 194 | elapsed.start(); 195 | 196 | QByteArray res; 197 | 198 | for(; len > 0; len -= 256, addr += 256) { 199 | int chunkLen = std::min(256, len); 200 | 201 | { 202 | QByteArray buf; 203 | buf.append((char)BlCmd::Read); 204 | 205 | char checksum = std::accumulate(buf.begin(), buf.end(), 0xFF, xorChecksum); 206 | buf.append(checksum); 207 | 208 | emit send(buf); 209 | waitForAck(); 210 | } 211 | 212 | { 213 | QByteArray buf; 214 | buf.append((char)(addr >> 24)); 215 | buf.append((char)(addr >> 16)); 216 | buf.append((char)(addr >> 8)); 217 | buf.append((char)(addr)); 218 | 219 | char checksum = std::accumulate(buf.begin(), buf.end(), 0, xorChecksum); 220 | buf.append(checksum); 221 | 222 | emit send(buf); 223 | waitForAck(); 224 | } 225 | 226 | { 227 | QByteArray buf; 228 | buf.append((char)(chunkLen - 1)); 229 | 230 | char checksum = std::accumulate(buf.begin(), buf.end(), 0xFF, xorChecksum); 231 | buf.append(checksum); 232 | 233 | emit send(buf); 234 | waitForAck(); 235 | } 236 | 237 | for(int l = chunkLen; l > 0; --l) 238 | res.append(getChar()); 239 | 240 | incProgress(chunkLen * PROGRESS_POINTS_READ); 241 | } 242 | //qDebug() << "readDone" << res.size() << elapsed.elapsed(); 243 | return res; 244 | } 245 | 246 | void FlasherWorker::startDevice(uint32_t addr) 247 | { 248 | { 249 | QByteArray buf; 250 | buf.append((char)BlCmd::Go); 251 | 252 | char checksum = std::accumulate(buf.begin(), buf.end(), 0xFF, xorChecksum); 253 | buf.append(checksum); 254 | 255 | emit send(buf); 256 | waitForAck(); 257 | } 258 | 259 | { 260 | QByteArray buf; 261 | buf.append((char)(addr >> 24)); 262 | buf.append((char)(addr >> 16)); 263 | buf.append((char)(addr >> 8)); 264 | buf.append((char)(addr)); 265 | 266 | char checksum = std::accumulate(buf.begin(), buf.end(), 0, xorChecksum); 267 | buf.append(checksum); 268 | 269 | emit send(buf); 270 | waitForAck(); 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /control/flasherworker.h: -------------------------------------------------------------------------------- 1 | #ifndef FLASHERWORKER_H 2 | #define FLASHERWORKER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class FlasherWorker : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | FlasherWorker(); 17 | ~FlasherWorker(); 18 | 19 | public slots: 20 | void download(const QByteArray& bin); 21 | void cancel(); 22 | 23 | signals: 24 | void result(const QString& res); 25 | void send(const QByteArray& data); 26 | void progress(double percent); 27 | 28 | public: 29 | void addChar(char c); 30 | 31 | private: 32 | char getChar(); 33 | void waitForAck(); 34 | void writeToDevice(uint32_t addr, const QByteArray &data); 35 | QByteArray readFromDevice(uint32_t addr, int len); 36 | void startDevice(uint32_t addr); 37 | void incProgress(int points); 38 | 39 | private: 40 | QMutex mutex; 41 | QQueue queue; 42 | QWaitCondition cond; 43 | int pr; 44 | int total; 45 | bool canceled; 46 | }; 47 | 48 | #endif // FLASHERWORKER_H 49 | -------------------------------------------------------------------------------- /control/flashprogressdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "flashprogressdialog.h" 2 | #include "ui_flashprogressdialog.h" 3 | 4 | FlashProgressDialog::FlashProgressDialog(QWidget *parent) : 5 | QDialog(parent), 6 | ui(new Ui::FlashProgressDialog) 7 | { 8 | ui->setupUi(this); 9 | } 10 | 11 | FlashProgressDialog::~FlashProgressDialog() 12 | { 13 | delete ui; 14 | } 15 | 16 | void FlashProgressDialog::on_progress(double percent) 17 | { 18 | ui->progressBar->setValue((int)(percent + 0.5)); 19 | } 20 | 21 | void FlashProgressDialog::on_buttonBox_rejected() 22 | { 23 | emit cancel(); 24 | } 25 | 26 | void FlashProgressDialog::on_stateChanged(Flasher::State state) 27 | { 28 | switch(state) { 29 | case Flasher::State::Connected: 30 | ui->stateLabel->setText("Waiting for bootloader..."); 31 | break; 32 | 33 | case Flasher::State::Preparing: 34 | ui->stateLabel->setText("Preparing..."); 35 | break; 36 | 37 | case Flasher::State::Programming: 38 | ui->stateLabel->setText("Programming..."); 39 | break; 40 | 41 | case Flasher::State::Verifying: 42 | ui->stateLabel->setText("Verifying..."); 43 | break; 44 | 45 | case Flasher::State::Resetting: 46 | ui->stateLabel->setText("Resetting..."); 47 | break; 48 | 49 | case Flasher::State::Ready: 50 | ui->stateLabel->setText("Done"); 51 | ui->buttonBox->setStandardButtons(QDialogButtonBox::Close); 52 | break; 53 | 54 | case Flasher::State::Idle: 55 | ui->stateLabel->setText("Idle"); 56 | break; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /control/flashprogressdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef FLASHPROGRESSDIALOG_H 2 | #define FLASHPROGRESSDIALOG_H 3 | 4 | #include 5 | #include "flasher.h" 6 | 7 | namespace Ui { 8 | class FlashProgressDialog; 9 | } 10 | 11 | class FlashProgressDialog : public QDialog 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit FlashProgressDialog(QWidget *parent = 0); 17 | ~FlashProgressDialog(); 18 | 19 | public slots: 20 | void on_progress(double percent); 21 | void on_stateChanged(Flasher::State state); 22 | 23 | signals: 24 | void cancel(); 25 | 26 | private slots: 27 | void on_buttonBox_rejected(); 28 | 29 | private: 30 | Ui::FlashProgressDialog *ui; 31 | }; 32 | 33 | #endif // FLASHPROGRESSDIALOG_H 34 | -------------------------------------------------------------------------------- /control/flashprogressdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | FlashProgressDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 111 11 | 12 | 13 | 14 | Upgrading Device 15 | 16 | 17 | true 18 | 19 | 20 | 21 | 22 | 10 23 | 70 24 | 381 25 | 32 26 | 27 | 28 | 29 | Qt::Horizontal 30 | 31 | 32 | QDialogButtonBox::Cancel 33 | 34 | 35 | 36 | 37 | 38 | 10 39 | 30 40 | 381 41 | 23 42 | 43 | 44 | 45 | 0 46 | 47 | 48 | Qt::AlignJustify|Qt::AlignVCenter 49 | 50 | 51 | 52 | 53 | 54 | 10 55 | 10 56 | 381 57 | 16 58 | 59 | 60 | 61 | Openning port... 62 | 63 | 64 | 65 | 66 | 67 | 68 | buttonBox 69 | accepted() 70 | FlashProgressDialog 71 | accept() 72 | 73 | 74 | 248 75 | 254 76 | 77 | 78 | 157 79 | 274 80 | 81 | 82 | 83 | 84 | buttonBox 85 | rejected() 86 | FlashProgressDialog 87 | reject() 88 | 89 | 90 | 316 91 | 260 92 | 93 | 94 | 286 95 | 274 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /control/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 56 | 62 | 63 | 65 | 66 | 68 | image/svg+xml 69 | 71 | 72 | 73 | 74 | 75 | 80 | 88 | 92 | 100 | 108 | 113 | 118 | 123 | 128 | 133 | 139 | 147 | 156 | 162 | 168 | 177 | 182 | 191 | 192 | 197 | 205 | 213 | 218 | 223 | 228 | 233 | 238 | 244 | 252 | 261 | 267 | 273 | 282 | 287 | 296 | 297 | 302 | 310 | 318 | 323 | 328 | 333 | 338 | 343 | 349 | 357 | 366 | 372 | 378 | 387 | 392 | 401 | 402 | 407 | 415 | 423 | 428 | 433 | 438 | 443 | 448 | 454 | 462 | 471 | 477 | 483 | 492 | 497 | 506 | 507 | 512 | 520 | 528 | 533 | 538 | 543 | 548 | 553 | 559 | 567 | 576 | 582 | 588 | 597 | 602 | 611 | 612 | 613 | 614 | -------------------------------------------------------------------------------- /control/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev26th/electronic_load/8eccf2a8dfd2d0567c6b7756a88d2e565bfa988a/control/icon16.png -------------------------------------------------------------------------------- /control/icon24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev26th/electronic_load/8eccf2a8dfd2d0567c6b7756a88d2e565bfa988a/control/icon24.png -------------------------------------------------------------------------------- /control/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev26th/electronic_load/8eccf2a8dfd2d0567c6b7756a88d2e565bfa988a/control/icon256.png -------------------------------------------------------------------------------- /control/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev26th/electronic_load/8eccf2a8dfd2d0567c6b7756a88d2e565bfa988a/control/icon32.png -------------------------------------------------------------------------------- /control/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev26th/electronic_load/8eccf2a8dfd2d0567c6b7756a88d2e565bfa988a/control/icon48.png -------------------------------------------------------------------------------- /control/installer/config/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Electronic Load Control 4 | 1.0.0 5 | Electronic Load Control 6 | Anatoli Klassen 7 | Electronic Load Control 8 | @ApplicationsDir@/Electronic Load Control 9 | 10 | -------------------------------------------------------------------------------- /control/logtable.cpp: -------------------------------------------------------------------------------- 1 | #include "logtable.h" 2 | 3 | void LogTable::setModel(QAbstractItemModel * model) 4 | { 5 | QAbstractItemModel * oldModel = this->model(); 6 | if(oldModel == model) 7 | return; 8 | 9 | if(oldModel) 10 | disconnect(oldModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogTable::rowsAboutToBeInserted); 11 | 12 | QTableView::setModel(model); 13 | 14 | QAbstractItemModel * newModel = this->model(); 15 | if(newModel) 16 | connect(newModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogTable::rowsAboutToBeInserted); 17 | } 18 | 19 | void LogTable::rowsAboutToBeInserted(const QModelIndex &parent, int first, int last) 20 | { 21 | (void)parent; (void)last; 22 | 23 | int lastVisibleRow = rowAt(height() - rowHeight(first - 1)); 24 | autoScroll = (lastVisibleRow == (int)(first - 1)); // if last row was visible before insert visible 25 | } 26 | 27 | void LogTable::rowsInserted(const QModelIndex & parent, int start, int end) 28 | { 29 | (void)parent; (void)start; (void)end; 30 | 31 | if(autoScroll) 32 | scrollToBottom(); 33 | } 34 | -------------------------------------------------------------------------------- /control/logtable.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGTABLE_H 2 | #define LOGTABLE_H 3 | 4 | #include 5 | 6 | class LogTable: public QTableView 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit LogTable(QWidget *parent = 0) : QTableView(parent) {} 12 | 13 | void setModel(QAbstractItemModel * model) override; 14 | 15 | protected slots: 16 | void rowsAboutToBeInserted(const QModelIndex &parent, int first, int last); 17 | 18 | void rowsInserted(const QModelIndex & parent, int start, int end) override; 19 | 20 | private: 21 | bool autoScroll; 22 | }; 23 | 24 | #endif // LOGTABLE_H 25 | 26 | -------------------------------------------------------------------------------- /control/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | MainWindow w; 8 | w.show(); 9 | 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /control/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | #include "utils.h" 4 | #include "settings.h" 5 | #include "aboutdialog.h" 6 | #include "configdialog.h" 7 | #include "flashprogressdialog.h" 8 | #include "crc.h" 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | // ============================================================================================================= 29 | 30 | #define U_THRESHOLD 5 31 | 32 | Q_DECLARE_METATYPE(Cmd) 33 | Q_DECLARE_METATYPE(Sample) 34 | Q_DECLARE_METATYPE(Comm::State) 35 | 36 | MainWindow::MainWindow(QWidget *parent) : 37 | QMainWindow(parent), 38 | ui(new Ui::MainWindow), 39 | isConnected(false), 40 | deviceConfigData(Cmd::ReadConfig, CmdState::Error), 41 | storage(Settings::maxSamples) 42 | { 43 | ui->setupUi(this); 44 | 45 | qRegisterMetaType(); 46 | qRegisterMetaType(); 47 | qRegisterMetaType(); 48 | 49 | ui->temperatureBox->setOrientation(Qt::Horizontal); 50 | ui->temperatureBox->setFillBrush(Qt::green); 51 | ui->temperatureBox->setScalePosition(QwtThermo::NoScale); 52 | setControlEnabled(false); 53 | 54 | QSettings settings("Anatoli Klassen", "Electronic Load Control"); 55 | 56 | settings.beginGroup("mainwindow"); 57 | restoreGeometry(settings.value("geometry").toByteArray()); 58 | restoreState(settings.value("state").toByteArray()); 59 | settings.endGroup(); 60 | 61 | // serial ports 62 | currentPort = settings.value("port").toString(); 63 | on_refreshPortsButton_clicked(); 64 | 65 | // log enabled 66 | connect(ui->logDataCheckBox, &QCheckBox::stateChanged, [this](int state) { 67 | this->storage.setEnabled(Qt::Checked == state); 68 | } ); 69 | ui->logDataCheckBox->setCheckState((Qt::CheckState)settings.value("logData", Qt::Checked).toInt()); 70 | 71 | // graph 72 | ui->graphPlot->setCanvasBackground(QColor("MidnightBlue")); 73 | 74 | QwtPlotGrid *grid = new QwtPlotGrid(); 75 | grid->enableXMin(true); 76 | grid->setMajorPen(Qt::white, 0, Qt::DotLine); 77 | grid->setMinorPen(Qt::gray, 0, Qt::DotLine); 78 | grid->attach(ui->graphPlot); 79 | 80 | data = new CurveData(storage); 81 | 82 | QwtPlotCurve * curve = new QwtPlotCurve(); 83 | curve->setRenderHint(QwtPlotItem::RenderAntialiased); 84 | curve->setPen(Qt::yellow); 85 | curve->setData(data); 86 | curve->attach(ui->graphPlot); 87 | 88 | // storage 89 | connect(this, &MainWindow::sample, &storage, &SampleStorage::append); 90 | connect(this, &MainWindow::sampleMultiple, &storage, &SampleStorage::appendMultiple); 91 | connect(&storage, &SampleStorage::afterAppend, [this]() { 92 | if(!this->ui->graphDock->isHidden()) this->ui->graphPlot->replot(); 93 | } ); 94 | connect(&storage, &SampleStorage::afterAppendMultiple, [this]() { 95 | if(!this->ui->graphDock->isHidden()) this->ui->graphPlot->replot(); 96 | } ); 97 | connect(&storage, &SampleStorage::afterClear, [this]() { 98 | if(!this->ui->graphDock->isHidden()) this->ui->graphPlot->replot(); 99 | } ); 100 | connect(ui->graphDock, &QDockWidget::visibilityChanged, [this]() { 101 | if(!this->ui->graphDock->isHidden()) this->ui->graphPlot->replot(); 102 | } ); 103 | 104 | // table 105 | tableModel = new TableModel(storage); 106 | ui->tableView->setModel(tableModel); 107 | ui->tableView->setColumnWidth(0, 140); 108 | ui->tableView->setColumnWidth(1, 80); 109 | connect(&storage, &SampleStorage::beforeAppend, tableModel, &TableModel::beforeAppend); 110 | connect(&storage, &SampleStorage::beforeAppendMultiple, tableModel, &TableModel::beforeAppendMultiple); 111 | connect(&storage, &SampleStorage::afterAppend, tableModel, &TableModel::afterAppend); 112 | connect(&storage, &SampleStorage::afterAppendMultiple, tableModel, &TableModel::afterAppendMultiple); 113 | connect(&storage, &SampleStorage::beforeClear, tableModel, &TableModel::beforeClear); 114 | connect(&storage, &SampleStorage::afterClear, tableModel, &TableModel::afterClear); 115 | connect(&storage, &SampleStorage::beforeDelete, tableModel, &TableModel::beforeDelete); 116 | connect(&storage, &SampleStorage::afterDelete, tableModel, &TableModel::afterDelete); 117 | 118 | // comm 119 | comm = new Comm(); 120 | comm->moveToThread(&commThread); 121 | connect(&commThread, &QThread::started, comm, &Comm::onStart); 122 | connect(&commThread, &QThread::finished, comm, &Comm::deleteLater); 123 | connect(this, &MainWindow::portConnect, comm, &Comm::portConnect); 124 | connect(this, &MainWindow::portDisconnect, comm, &Comm::portDisconnect); 125 | connect(this, &MainWindow::send, comm, &Comm::send); 126 | connect(comm, &Comm::error, this, &MainWindow::on_serError); 127 | connect(comm, &Comm::data, this, &MainWindow::on_serData); 128 | connect(comm, &Comm::stateChanged, this, &MainWindow::on_serStateChanged); 129 | commThread.start(); 130 | 131 | // flasher 132 | flasher = new Flasher(); 133 | flasher->moveToThread(&flasherThread); 134 | connect(&flasherThread, &QThread::started, flasher, &Flasher::onStart); 135 | connect(&flasherThread, &QThread::finished, flasher, &Flasher::deleteLater); 136 | connect(this, &MainWindow::upgradeDevice, flasher, &Flasher::portConnect); 137 | connect(this, &MainWindow::cancelUpgradeDevice, flasher, &Flasher::portDisconnect); 138 | connect(flasher, &Flasher::error, this, &MainWindow::on_flasherError); 139 | flasherThread.start(); 140 | 141 | // interval 142 | ui->intervalBox->setValue(settings.value("interval", MIN_INTERVAL_MS).toInt()); 143 | 144 | // status bar 145 | deviceVersionLabel = new QLabel(); 146 | deviceVersionLabel->setToolTip("Device Version"); 147 | deviceVersionLabel->setVisible(false); 148 | ui->statusBar->addWidget(deviceVersionLabel); 149 | deviceMessageLabel = new QLabel(); 150 | deviceMessageLabel->setToolTip("Device Status"); 151 | deviceMessageLabel->setVisible(false); 152 | ui->statusBar->addWidget(deviceMessageLabel); 153 | 154 | clearDeviceInfo(); 155 | } 156 | 157 | MainWindow::~MainWindow() 158 | { 159 | commThread.quit(); 160 | flasherThread.quit(); 161 | 162 | commThread.wait(); 163 | flasherThread.wait(); 164 | 165 | delete ui; 166 | } 167 | 168 | void MainWindow::on_portBox_currentIndexChanged(int index) 169 | { 170 | (void)index; 171 | 172 | QVariant data = ui->portBox->currentData(); 173 | currentPort = data.toString(); 174 | if(isConnected) connectSer(); 175 | } 176 | 177 | void MainWindow::on_actionShowTable_triggered() 178 | { 179 | ui->tableDock->show(); 180 | } 181 | 182 | void MainWindow::on_actionShowGraph_triggered() 183 | { 184 | ui->graphDock->show(); 185 | } 186 | 187 | void MainWindow::on_actionShowControl_triggered() 188 | { 189 | ui->controlDock->show(); 190 | } 191 | 192 | void MainWindow::updateFun() 193 | { 194 | bool showEnergy = (deviceConfigData.fun == 1); 195 | ui->energyLabel->setVisible(showEnergy); 196 | ui->energyBox->setVisible(showEnergy); 197 | ui->energyResetButton->setVisible(showEnergy); 198 | } 199 | 200 | void MainWindow::setControlEnabled(bool state) 201 | { 202 | ui->funFrame->setEnabled(state); 203 | ui->uLimitBox->setEnabled(state); 204 | ui->currentBox->setEnabled(state); 205 | ui->limitUpdateButton->setEnabled(state); 206 | ui->energyResetButton->setEnabled(state); 207 | ui->runButton->setEnabled(state); 208 | ui->actionDeviceConfiguration->setEnabled(state); 209 | 210 | if(state) { 211 | bool showEnergy = (deviceConfigData.fun == 1); 212 | ui->energyLabel->setVisible(showEnergy); 213 | ui->energyBox->setVisible(showEnergy); 214 | ui->energyResetButton->setVisible(showEnergy); 215 | } 216 | else { 217 | ui->energyLabel->setVisible(true); 218 | ui->energyBox->setVisible(true); 219 | ui->energyResetButton->setVisible(true); 220 | 221 | ui->uLimitBox->setValue(0.0); 222 | ui->currentBox->setValue(0.0); 223 | ui->uActualBox->setText(""); 224 | ui->energyBox->setText(""); 225 | ui->temperatureBox->setValue(0); 226 | ui->wireLabel->setVisible(false); 227 | } 228 | 229 | // FIXME 230 | ui->funFrame->setEnabled(false); 231 | ui->runButton->setVisible(false); 232 | } 233 | 234 | void MainWindow::connectSer() 235 | { 236 | emit portConnect(currentPort); 237 | 238 | ui->connectButton->setText("Disconnect"); 239 | isConnected = true; 240 | } 241 | 242 | void MainWindow::disconnectSer() 243 | { 244 | emit portDisconnect(); 245 | 246 | ui->connectButton->setText("Connect"); 247 | isConnected = false; 248 | clearDeviceInfo(); 249 | } 250 | 251 | void MainWindow::on_connectButton_clicked() 252 | { 253 | if(isConnected) disconnectSer(); 254 | else connectSer(); 255 | } 256 | 257 | void MainWindow::on_refreshPortsButton_clicked() 258 | { 259 | QString selPort = currentPort; 260 | ui->portBox->clear(); 261 | 262 | QList serPorts = QSerialPortInfo::availablePorts(); 263 | int count = 0; 264 | for(auto i = serPorts.begin(); i != serPorts.end(); ++i, ++count) { 265 | QString title = QString("%1: (%2) %3").arg(i->portName()).arg(i->description()).arg(i->manufacturer()); 266 | ui->portBox->addItem(title, QVariant(i->portName())); 267 | if(selPort == i->portName()) { 268 | ui->portBox->setCurrentIndex(count); 269 | } 270 | } 271 | } 272 | 273 | void MainWindow::on_clearDataButton_clicked() 274 | { 275 | storage.clear(); 276 | } 277 | 278 | void MainWindow::closeEvent(QCloseEvent *event) 279 | { 280 | // FIXME ask to save log? 281 | 282 | QSettings settings("Anatoli Klassen", "Electronic Load Control"); 283 | 284 | settings.beginGroup("mainwindow"); 285 | settings.setValue("geometry", saveGeometry()); 286 | settings.setValue("state", saveState()); 287 | settings.endGroup(); 288 | 289 | settings.setValue("port", currentPort); 290 | settings.setValue("logData", ui->logDataCheckBox->checkState()); 291 | settings.setValue("interval", ui->intervalBox->value()); 292 | 293 | QMainWindow::closeEvent(event); 294 | } 295 | 296 | void MainWindow::on_actionAbout_triggered() 297 | { 298 | AboutDialog * dialog = new AboutDialog(this); 299 | dialog->exec(); 300 | delete dialog; 301 | } 302 | 303 | static bool saveStorageToCsvFile(const SampleStorage &storage, QFile &file) 304 | { 305 | if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) return false; 306 | 307 | static const QString dateTimeFormat("dd.MM.yyyy hh:mm.ss.zzz"); 308 | QFileDevice::FileError error; 309 | QString header = "\"Timestamp\",\"Time, s\",\"Current, A\",\"Voltage, V\",\"Energy, Ah\",\"Energy, Wh\"\n"; 310 | file.write(header.toLatin1()); 311 | error = file.error(); 312 | size_t len = storage.size(); 313 | for(size_t i = 0; (QFileDevice::NoError == error) && (i < len); ++i) { 314 | const Sample& sample = storage.sample(i); 315 | QString line = QDateTime::fromMSecsSinceEpoch(sample.timestamp).toString(dateTimeFormat); 316 | line.append(",").append(QString("%1").arg(((double)(sample.timestamp - storage.getBegin())/1000.0), 0, 'f', 3)); 317 | line.append(",").append(QString("%1").arg(sample.i, 0, 'f', 3)); 318 | line.append(",").append(QString("%1").arg(sample.u, 0, 'f', 3)); 319 | line.append(",").append(QString("%1").arg(sample.ah, 0, 'f', 3)); 320 | line.append(",").append(QString("%1").arg(sample.wh, 0, 'f', 3)); 321 | line.append("\n"); 322 | file.write(line.toLatin1()); 323 | error = file.error(); 324 | } 325 | if(QFileDevice::NoError == error) { 326 | file.flush(); 327 | error = file.error(); 328 | } 329 | 330 | file.close(); 331 | return (QFileDevice::NoError == error); 332 | } 333 | 334 | void MainWindow::on_actionSaveLog_triggered() 335 | { 336 | QString fileName = QFileDialog::getSaveFileName(this, 337 | "Save Log", QString(), "CSV tables (*.csv);;Test files (*.txt);;All files (*)"); 338 | if(fileName.isEmpty()) return; 339 | 340 | QFile file(fileName); 341 | if(!saveStorageToCsvFile(storage, file)) { 342 | showError("Cannot write into " + fileName + ": " + file.errorString()); 343 | } 344 | } 345 | 346 | void MainWindow::on_runButton_clicked() 347 | { 348 | } 349 | 350 | void MainWindow::on_serError(QString msg) 351 | { 352 | showError(msg); 353 | disconnectSer(); 354 | } 355 | 356 | static void addError(QString& all, const QString& add) 357 | { 358 | if(!all.isEmpty()) all += " / "; 359 | all += add; 360 | } 361 | 362 | void MainWindow::setupTemperatureBox() 363 | { 364 | static QColor cLow(29, 114, 29); 365 | static QColor cMid(207, 207, 46); 366 | static QColor cFull(238, 154, 0); 367 | static QColor cLimit(223, 32, 32); 368 | 369 | double min = 1/(double)deviceConfigData.tempDefect; 370 | double low = 1/(double)deviceConfigData.tempFanLow; 371 | double mid = 1/(double)deviceConfigData.tempFanMid; 372 | double full = 1/(double)deviceConfigData.tempFanFull; 373 | double limit = 1/(double)deviceConfigData.tempLimit; 374 | double w = limit - min; 375 | 376 | ui->temperatureBox->setValue(min); 377 | ui->temperatureBox->setScale(min, limit); 378 | 379 | QwtLinearColorMap* cMap = new QwtLinearColorMap(); 380 | cMap->setMode(QwtLinearColorMap::FixedColors); 381 | cMap->setColorInterval(cLow, cLimit); 382 | cMap->colorStops().clear(); 383 | cMap->addColorStop(min, cLow); 384 | cMap->addColorStop((low-min)/w, cMid); 385 | cMap->addColorStop((mid-min)/w, cFull); 386 | cMap->addColorStop((full-min)/w, cLimit); 387 | ui->temperatureBox->setColorMap(cMap); 388 | } 389 | 390 | void MainWindow::updateDeviceSettings() { 391 | setupTemperatureBox(); 392 | ui->uLimitBox->setMinimum((double)deviceConfigData.uSetMin / 1000.0); 393 | ui->uLimitBox->setMaximum((double)deviceConfigData.uSetMax / 1000.0); 394 | ui->currentBox->setMinimum((double)deviceConfigData.iSetMin / 1000.0); 395 | ui->currentBox->setMaximum((double)deviceConfigData.iSetMax / 1000.0); 396 | } 397 | 398 | Sample MainWindow::parseSample(CmdStateData *c, qint64 timestamp) 399 | { 400 | Sample s; 401 | if(this->interval > 0) 402 | s.timestamp = ((timestamp + this->interval/2) / this->interval) * this->interval; // round up to interval borders 403 | else 404 | s.timestamp = timestamp; 405 | 406 | bool is4Wire = (c->uSense + 100 >= c->uMain); 407 | uint16_t u = (is4Wire ? c->uSense : c->uMain); 408 | if(abs((int)u - (int)deviceLastU) < U_THRESHOLD) 409 | u = deviceLastU; 410 | else 411 | deviceLastU = u; 412 | 413 | s.u = qFloor((double)u / 10.0 + 0.5) / 100.0; // FIXME good? or s.u = (double)u / 1000; 414 | s.i = (double)deviceCurrent / 1000; 415 | s.ah = (double)c->ah / 1000; 416 | s.wh = (double)c->wh / 1000; 417 | 418 | return s; 419 | } 420 | 421 | void MainWindow::on_serData(QByteArray d, qint64 timestamp) 422 | { 423 | CmdData* cmd = parseCmdData(d); 424 | if(cmd != nullptr) { 425 | if(cmd->state == CmdState::Response || cmd->state == CmdState::Event) { 426 | switch(cmd->cmd) { 427 | case Cmd::Reboot: 428 | if(cmd->state == CmdState::Event) // the device was restarted, re-config it 429 | configDevice(); 430 | break; 431 | 432 | case Cmd::ReadConfig: 433 | { 434 | CmdConfigData* c = static_cast(cmd); 435 | deviceConfigData = *c; 436 | 437 | if(deviceConfigData.fun == 0) 438 | ui->fun1Button->setChecked(true); 439 | else 440 | ui->fun2Button->setChecked(true); 441 | 442 | ui->soundBox->setChecked(deviceConfigData.beepOn); 443 | updateFun(); 444 | updateDeviceSettings(); 445 | } 446 | break; 447 | 448 | case Cmd::ReadSettings: 449 | { 450 | CmdSettingData* c = static_cast(cmd); 451 | ui->uLimitBox->setValue((double)c->u / 1000); 452 | ui->currentBox->setValue((double)c->i / 1000); 453 | deviceCurrent = c->i; 454 | } 455 | break; 456 | 457 | case Cmd::GetVersion: 458 | { 459 | CmdVersionData* c = static_cast(cmd); 460 | deviceVersionLabel->setText("0x" + QString("%1").arg(c->v, 8, 16, QChar('0')).toUpper()); 461 | deviceVersionLabel->setVisible(true); 462 | } 463 | break; 464 | 465 | case Cmd::GetState: 466 | { 467 | CmdStateData* c = static_cast(cmd); 468 | Sample s = parseSample(c, timestamp); 469 | if(c->mode == DeviceMode::Fun1Run || c->mode == DeviceMode::Fun2Run) 470 | emit sample(s); 471 | 472 | QString deviceMessage; 473 | if(c->error) { 474 | if(c->error & DEVICE_ERROR_POLARITY) addError(deviceMessage, "Polarity error"); 475 | if(c->error & DEVICE_ERROR_SUPPLY) addError(deviceMessage, "Supply error"); 476 | if(c->error & DEVICE_ERROR_OUP) addError(deviceMessage, "Overvoltage"); 477 | if(c->error & DEVICE_ERROR_OTP) addError(deviceMessage, "Overheat"); 478 | if(c->error & DEVICE_ERROR_ERT) addError(deviceMessage, "Temperature sensor defect"); 479 | } 480 | else { 481 | switch(c->mode) { 482 | case DeviceMode::Booting: deviceMessage = "Booting"; break; 483 | case DeviceMode::MenuFun: deviceMessage = "Menu"; break; 484 | case DeviceMode::MenuBeep: deviceMessage = "Menu"; break; 485 | case DeviceMode::MenuCalV: deviceMessage = "Menu"; break; 486 | case DeviceMode::MenuCalI: deviceMessage = "Menu"; break; 487 | case DeviceMode::CalV1: deviceMessage = "Calibration"; break; 488 | case DeviceMode::CalV2: deviceMessage = "Calibration"; break; 489 | case DeviceMode::CalI1r: deviceMessage = "Calibration"; break; 490 | case DeviceMode::CalI1v: deviceMessage = "Calibration"; break; 491 | case DeviceMode::CalI2r: deviceMessage = "Calibration"; break; 492 | case DeviceMode::CalI2v: deviceMessage = "Calibration"; break; 493 | case DeviceMode::Fun1: deviceMessage = "Idle"; break; 494 | case DeviceMode::Fun1Run: deviceMessage = "Run"; break; 495 | case DeviceMode::Fun2: deviceMessage = "Idle"; break; 496 | case DeviceMode::Fun2Pre: deviceMessage = "Run"; break; 497 | case DeviceMode::Fun2Run: deviceMessage = "Run"; break; 498 | case DeviceMode::Fun2Warn: deviceMessage = "Stop"; break; 499 | case DeviceMode::Fun2Res: deviceMessage = "Stop"; break; 500 | } 501 | } 502 | deviceMessageLabel->setText(deviceMessage); 503 | deviceMessageLabel->setVisible(true); 504 | 505 | bool is4Wire = (c->uSense + 100 >= c->uMain); 506 | ui->uActualBox->setText(QString("%L1 V").arg(s.u, 0, 'f', 2)); 507 | ui->energyBox->setText(QString("%L1 A⋅h (%L2 W⋅h)").arg(s.ah, 0, 'f', 3).arg(s.wh, 0, 'f', 3)); 508 | ui->wireLabel->setVisible(is4Wire); 509 | ui->temperatureBox->setValue(1/(double)c->tempRaw); 510 | } 511 | break; 512 | 513 | default: 514 | ; 515 | } 516 | } 517 | } 518 | executeNext(); 519 | } 520 | 521 | void MainWindow::on_serStateChanged(Comm::State state) 522 | { 523 | switch(state) { 524 | case Comm::State::Connected: 525 | setControlEnabled(true); 526 | configDevice(); 527 | deviceLastU = 0xFFFF; 528 | break; 529 | 530 | case Comm::State::Idle: 531 | setControlEnabled(false); 532 | deviceVersionLabel->setVisible(false); 533 | deviceVersionLabel->clear(); 534 | deviceMessageLabel->setVisible(false); 535 | deviceMessageLabel->clear(); 536 | break; 537 | } 538 | executeNext(); 539 | } 540 | 541 | void MainWindow::configDevice() 542 | { 543 | this->interval = ui->intervalBox->value(); 544 | 545 | toExecute.clear(); 546 | toExecute.enqueue(ToExecute(ToExecute::Action::Send, formCmdData(Cmd::GetVersion))); 547 | toExecute.enqueue(ToExecute(ToExecute::Action::Send, formCmdData(Cmd::ReadConfig))); 548 | toExecute.enqueue(ToExecute(ToExecute::Action::Send, formCmdData(Cmd::ReadSettings))); 549 | toExecute.enqueue(ToExecute(ToExecute::Action::Send, formCmdData(CmdFlowStateData(this->interval)))); 550 | executeNext(); 551 | } 552 | 553 | void MainWindow::executeNext() 554 | { 555 | if(toExecute.isEmpty()) return; 556 | 557 | ToExecute e = toExecute.dequeue(); 558 | switch(e.action) { 559 | case ToExecute::Action::Send: 560 | emit send(e.data); 561 | break; 562 | 563 | case ToExecute::Action::Disconnect: 564 | disconnectSer(); 565 | break; 566 | 567 | 568 | case ToExecute::Action::StartUpgrade: 569 | startUpgrade(e.data); 570 | break; 571 | } 572 | } 573 | 574 | void MainWindow::on_limitUpdateButton_clicked() 575 | { 576 | CmdSettingData d(Cmd::WriteSettings, CmdState::Request); 577 | d.u = (uint16_t)(ui->uLimitBox->value() * 1000 + 0.5); 578 | d.i = (uint16_t)(ui->currentBox->value() * 1000 + 0.5); 579 | toExecute.enqueue(ToExecute(ToExecute::Action::Send, formCmdData(d))); 580 | toExecute.enqueue(ToExecute(ToExecute::Action::Send, formCmdData(Cmd::ReadSettings))); 581 | executeNext(); 582 | } 583 | 584 | void MainWindow::on_uLimitBox_editingFinished() 585 | { 586 | on_limitUpdateButton_clicked(); 587 | } 588 | 589 | void MainWindow::on_currentBox_editingFinished() 590 | { 591 | on_limitUpdateButton_clicked(); 592 | } 593 | 594 | void MainWindow::on_actionDeviceConfiguration_triggered() 595 | { 596 | CmdConfigData d = deviceConfigData; 597 | ConfigDialog * dialog = new ConfigDialog(this, d); 598 | connect(dialog, &ConfigDialog::send, comm, &Comm::send); 599 | if(1 == dialog->exec()) { 600 | toExecute.enqueue(ToExecute(ToExecute::Action::Send, formCmdData(d))); 601 | toExecute.enqueue(ToExecute(ToExecute::Action::Send, formCmdData(Cmd::ReadConfig))); 602 | executeNext(); 603 | } 604 | delete dialog; 605 | } 606 | 607 | void MainWindow::on_energyResetButton_clicked() 608 | { 609 | toExecute.enqueue(ToExecute(ToExecute::Action::Send, formCmdData(Cmd::ResetState))); 610 | executeNext(); 611 | } 612 | 613 | // note: length not checked 614 | static uint8_t parse_hex(const QChar& d1, const QChar& d2) { 615 | uint8_t d[2]; 616 | for(int i = 0; i < 2; ++i) { 617 | char c = (i==0?d1:d2).toLatin1(); 618 | if(c >= '0' && c <= '9') d[i] = c - '0'; 619 | else if(c >= 'A' && c <= 'F') d[i] = c - 'A' + 10; 620 | else if(c >= 'a' && c <= 'f') d[i] = c - 'a' + 10; 621 | else return 0; // error 622 | } 623 | return (d[0] << 4) | (d[1]); 624 | } 625 | 626 | static void parse_ihex(QByteArray& hexFile, uint8_t erased_pattern, QByteArray& out, uint32_t& begin) { 627 | begin = UINT32_MAX; 628 | uint32_t end = 0; 629 | bool eof_found = false; 630 | 631 | for(int scan = 0; scan < 2; ++scan) { // parse file two times - first to find memory range, second - to fill it 632 | if(scan == 1) { 633 | if(!eof_found) 634 | throw "No EoF recond"; 635 | 636 | if(begin >= end) 637 | throw "No data found in file"; 638 | 639 | out.fill(erased_pattern, (end - begin) + 1); 640 | } 641 | 642 | QTextStream stream(&hexFile); 643 | uint32_t lba = 0; 644 | 645 | while(!stream.atEnd()) { 646 | QString line = stream.readLine(); 647 | if(line[0] == '\n' || line[0] == '\r') continue; // skip empty lines 648 | if(line[0] != ':') // no marker - wrong file format 649 | throw "Wrong file format - no marker"; 650 | 651 | int l = line.length(); 652 | while(l > 0 && (line[l-1] == '\n' || line[l-1] == '\r')) --l; // trim EoL 653 | if(l < 11) // line too short - wrong file format 654 | throw "Wrong file format - wrong line length"; 655 | 656 | // check sum 657 | uint8_t chksum = 0; 658 | for(int i = 1; i < l-1; i += 2) { 659 | chksum += parse_hex(line[i], line[i+1]); 660 | } 661 | if(chksum != 0) 662 | throw "Wrong file format - checksum mismatch"; 663 | 664 | uint8_t reclen = parse_hex(line[1], line[2]); 665 | if(((uint32_t)reclen + 5)*2 + 1 != (uint32_t)l) 666 | throw "Wrong file format - record length mismatch"; 667 | 668 | uint16_t offset = ((uint16_t)parse_hex(line[3], line[4]) << 8) | ((uint16_t)parse_hex(line[5], line[6])); 669 | uint8_t rectype = parse_hex(line[7], line[8]); 670 | 671 | switch(rectype) { 672 | case 0: // data 673 | if(scan == 0) { 674 | uint32_t b = lba + offset; 675 | uint32_t e = b + reclen - 1; 676 | if(b < begin) begin = b; 677 | if(e > end) end = e; 678 | } 679 | else { 680 | for(size_t i = 0; i < reclen; ++i) { 681 | uint8_t b = parse_hex(line[(uint)(9 + i*2)], line[(uint)(10 + i*2)]); 682 | uint32_t addr = lba + offset + i; 683 | if(addr >= begin && addr <= end) { 684 | out[addr - begin] = b; 685 | } 686 | } 687 | } 688 | break; 689 | 690 | case 1: // EoF 691 | eof_found = true; 692 | break; 693 | 694 | case 2: // Extended Segment Address, unexpected 695 | throw "Unexpected"; 696 | 697 | case 3: // Start Segment Address, unexpected 698 | throw "Unexpected"; 699 | 700 | case 4: // Extended Linear Address 701 | if(reclen == 2) 702 | lba = ((uint32_t)parse_hex(line[9], line[10]) << 24) | ((uint32_t)parse_hex(line[11], line[12]) << 16); 703 | else 704 | throw "Wrong file format - wrong LBA length"; 705 | break; 706 | 707 | case 5: // Start Linear Address - expected, but ignore 708 | break; 709 | 710 | default: 711 | throw "Wrong file format - unexpected record type"; 712 | } 713 | } 714 | } 715 | } 716 | 717 | void MainWindow::on_actionUpgradeFirmware_triggered() 718 | { 719 | QString fileName = QFileDialog::getOpenFileName(this, 720 | tr("Select firmware file"), "", 721 | tr("Intel-HEX Files (*.ihex *.ihx *.hex);;Binary Files (*.bin);;All Files (*)")); 722 | if(fileName.isEmpty()) return; 723 | 724 | QByteArray fileContent; 725 | QFile inputFile(fileName); 726 | if(!inputFile.open(QFile::ReadOnly)) { 727 | showError(QString("Cannot read from file %1:\n%2.").arg(fileName).arg(inputFile.errorString())); 728 | return; 729 | } 730 | else { 731 | try { 732 | fileContent = inputFile.readAll(); 733 | } 734 | catch(char const* msg) { 735 | showError(QString("Cannot read from file %1:\n%2.").arg(fileName).arg(msg)); 736 | return; 737 | } 738 | } 739 | 740 | QString fileNameLc = fileName.toLower(); 741 | if(fileNameLc.endsWith(".ihex") || fileNameLc.endsWith(".ihx") || fileNameLc.endsWith(".hex")) { 742 | uint32_t begin; 743 | QByteArray bin; 744 | 745 | try { 746 | parse_ihex(fileContent, 0xFF, bin, begin); 747 | } 748 | catch(char const* msg) { 749 | showError(QString("Cannot parse file %1 is Intel-HEX:\n%2.").arg(fileName).arg(msg)); 750 | return; 751 | } 752 | 753 | fileContent = bin; 754 | } 755 | 756 | if(isConnected) { 757 | toExecute.clear(); 758 | toExecute.enqueue(ToExecute(ToExecute::Action::Send, formCmdData(CmdBootloaderData(true)))); 759 | toExecute.enqueue(ToExecute(ToExecute::Action::Send, formCmdData(Cmd::Reboot))); 760 | toExecute.enqueue(ToExecute(ToExecute::Action::Disconnect)); 761 | } 762 | 763 | toExecute.enqueue(ToExecute(ToExecute::Action::StartUpgrade, fileContent)); 764 | executeNext(); 765 | } 766 | 767 | void MainWindow::startUpgrade(const QByteArray &data) 768 | { 769 | FlashProgressDialog * dialog = new FlashProgressDialog(this); 770 | connect(flasher, &Flasher::progress, dialog, &FlashProgressDialog::on_progress); 771 | connect(flasher, &Flasher::stateChanged, dialog, &FlashProgressDialog::on_stateChanged); 772 | 773 | emit upgradeDevice(currentPort, data); 774 | dialog->exec(); 775 | emit cancelUpgradeDevice(); 776 | 777 | delete dialog; 778 | executeNext(); 779 | } 780 | 781 | void MainWindow::clearDeviceInfo() 782 | { 783 | memset(&deviceConfigData, 0, sizeof(deviceConfigData)); 784 | } 785 | 786 | void MainWindow::on_flasherError(QString msg) 787 | { 788 | emit cancelUpgradeDevice(); 789 | showError(msg); 790 | } 791 | 792 | void MainWindow::on_actionLoadRawLog_triggered() 793 | { 794 | QString fileName = QFileDialog::getOpenFileName(this, 795 | tr("Select RAW-log file"), "", 796 | tr("Text Files (*.txt *.raw *.log);;All Files (*)")); 797 | if(fileName.isEmpty()) return; 798 | 799 | QFile inputFile(fileName); 800 | int skippedLines = 0; 801 | QVector list; 802 | if(inputFile.open(QIODevice::ReadOnly)) { 803 | QTextStream in(&inputFile); 804 | for(;;) { 805 | QString line = in.readLine(); 806 | if(line.isNull()) break; 807 | 808 | QStringList tokens = line.split(','); 809 | if(tokens.length() != 2) { 810 | ++skippedLines; 811 | continue; 812 | } 813 | 814 | bool ok; 815 | qint64 timestamp = tokens[0].toLongLong(&ok); 816 | if(!ok) { 817 | ++skippedLines; 818 | continue; 819 | } 820 | 821 | if(!tokens[1].startsWith("s") && !tokens[1].startsWith("S")) { 822 | ++skippedLines; 823 | continue; 824 | } 825 | 826 | QByteArray data = QByteArray::fromHex(tokens[1].toUtf8()); // 'S' will be skipped 827 | char crc = std::accumulate(data.begin(), data.end(), 0, crc8); 828 | if(crc != 0) { 829 | ++skippedLines; 830 | continue; 831 | } 832 | 833 | QByteArray buf; 834 | buf.append(data.data(), data.size()-1); 835 | 836 | CmdData* cmd = parseCmdData(buf); 837 | if(cmd != nullptr && cmd->state == CmdState::Event && cmd->cmd == Cmd::GetState) { 838 | CmdStateData* c = static_cast(cmd); 839 | Sample s = parseSample(c, timestamp * 1000); 840 | if(c->mode == DeviceMode::Fun1Run || c->mode == DeviceMode::Fun2Run) 841 | list.push_back(s); 842 | } 843 | } 844 | inputFile.close(); 845 | 846 | if(!list.isEmpty()) 847 | emit sampleMultiple(list); 848 | 849 | if(skippedLines > 0) 850 | showError(QString("Read successfully, but %1 lines skipped.").arg(skippedLines)); 851 | } 852 | else { 853 | showError(QString("Cannot opene file %1").arg(fileName)); 854 | } 855 | } 856 | -------------------------------------------------------------------------------- /control/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include "decoder.h" 5 | #include "comm.h" 6 | #include "flasher.h" 7 | #include "samplestorage.h" 8 | #include "curvedata.h" 9 | #include "tablemodel.h" 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | namespace Ui { 24 | class MainWindow; 25 | } 26 | 27 | class MainWindow : public QMainWindow 28 | { 29 | Q_OBJECT 30 | 31 | public: 32 | explicit MainWindow(QWidget *parent = 0); 33 | ~MainWindow(); 34 | 35 | public: 36 | static const int MIN_INTERVAL_MS = 250; 37 | 38 | private slots: 39 | void on_portBox_currentIndexChanged(int index); 40 | 41 | void on_actionShowTable_triggered(); 42 | 43 | void on_actionShowGraph_triggered(); 44 | 45 | void on_actionShowControl_triggered(); 46 | 47 | void on_connectButton_clicked(); 48 | 49 | void on_serError(QString msg); 50 | 51 | void on_serData(QByteArray d, qint64 timestamp); 52 | 53 | void on_serStateChanged(Comm::State state); 54 | 55 | void on_refreshPortsButton_clicked(); 56 | 57 | void on_clearDataButton_clicked(); 58 | 59 | void on_actionAbout_triggered(); 60 | 61 | void on_actionSaveLog_triggered(); 62 | 63 | void on_runButton_clicked(); 64 | 65 | void on_limitUpdateButton_clicked(); 66 | 67 | void on_actionDeviceConfiguration_triggered(); 68 | 69 | void on_energyResetButton_clicked(); 70 | 71 | void on_actionUpgradeFirmware_triggered(); 72 | 73 | void on_flasherError(QString msg); 74 | 75 | void on_uLimitBox_editingFinished(); 76 | 77 | void on_currentBox_editingFinished(); 78 | 79 | void on_actionLoadRawLog_triggered(); 80 | 81 | signals: 82 | void portConnect(QString portName); 83 | void portDisconnect(); 84 | void send(QByteArray data); 85 | void sample(Sample s); 86 | void sampleMultiple(const QVector &list); 87 | void upgradeDevice(QString portName, QByteArray fileContent); 88 | void cancelUpgradeDevice(); 89 | 90 | public: 91 | void closeEvent(QCloseEvent *event); 92 | 93 | private: 94 | void setControlEnabled(bool state); 95 | void connectSer(); 96 | void disconnectSer(); 97 | void executeNext(); 98 | void configDevice(); 99 | void updateFun(); 100 | void setupTemperatureBox(); 101 | void startUpgrade(const QByteArray& data); 102 | void clearDeviceInfo(); 103 | void updateDeviceSettings(); 104 | Sample parseSample(CmdStateData* c, qint64 timestamp); 105 | 106 | private: 107 | struct ToExecute { 108 | enum class Action { 109 | Send, 110 | Disconnect, 111 | StartUpgrade, 112 | }; 113 | 114 | Action action; 115 | QByteArray data; 116 | 117 | ToExecute() {} 118 | 119 | ToExecute(const ToExecute& o) : action(o.action), data(o.data) {} 120 | 121 | ToExecute(Action action_) : action(action_) {} 122 | 123 | ToExecute(Action action_, QByteArray data_) : action(action_), data(data_) {} 124 | }; 125 | 126 | private: 127 | Ui::MainWindow *ui; 128 | 129 | Comm *comm; 130 | QThread commThread; 131 | Flasher *flasher; 132 | QThread flasherThread; 133 | bool isConnected; 134 | QString currentPort; 135 | QQueue toExecute; 136 | int interval; 137 | uint16_t deviceCurrent; 138 | uint16_t deviceLastU; 139 | CmdConfigData deviceConfigData; 140 | 141 | SampleStorage storage; 142 | CurveData *data; 143 | TableModel *tableModel; 144 | QLabel* deviceVersionLabel; 145 | QLabel* deviceMessageLabel; 146 | }; 147 | 148 | #endif // MAINWINDOW_H 149 | -------------------------------------------------------------------------------- /control/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1300 10 | 887 11 | 12 | 13 | 14 | Electronic Load Control 15 | 16 | 17 | 18 | :/app.ico:/app.ico 19 | 20 | 21 | QMainWindow::AllowNestedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 0 30 | 0 31 | 32 | 33 | 34 | 35 | 390 36 | 100 37 | 38 | 39 | 40 | Settings 41 | 42 | 43 | 44 | 45 | 46 | Qt::Vertical 47 | 48 | 49 | 50 | 20 51 | 40 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Sampling Interval 60 | 61 | 62 | 63 | 64 | 65 | 66 | ms 67 | 68 | 69 | 250 70 | 71 | 72 | 86400000 73 | 74 | 75 | 50 76 | 77 | 78 | 1000 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 0 87 | 0 88 | 89 | 90 | 91 | Port 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 0 100 | 0 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | Clear Data 109 | 110 | 111 | 112 | 113 | 114 | 115 | Log Data 116 | 117 | 118 | 119 | 120 | 121 | 122 | Connect 123 | 124 | 125 | 126 | 127 | 128 | 129 | Refresh 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 0 142 | 0 143 | 1300 144 | 21 145 | 146 | 147 | 148 | 149 | &File 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | &Help 159 | 160 | 161 | 162 | 163 | 164 | &View 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | true 173 | 174 | 175 | &Service 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | false 190 | 191 | 192 | Table 193 | 194 | 195 | 8 196 | 197 | 198 | 199 | 200 | 201 | 202 | false 203 | 204 | 205 | 30 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | false 215 | 216 | 217 | Graph 218 | 219 | 220 | 8 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 335 234 | 299 235 | 236 | 237 | 238 | Control 239 | 240 | 241 | 2 242 | 243 | 244 | 245 | 246 | 247 | 248 | QFrame::StyledPanel 249 | 250 | 251 | QFrame::Raised 252 | 253 | 254 | 255 | 256 | 257 | 258 | true 259 | 260 | 261 | 262 | 0 263 | 0 264 | 265 | 266 | 267 | 268 | 269 | 270 | true 271 | 272 | 273 | Fun 1 274 | 275 | 276 | true 277 | 278 | 279 | 280 | 281 | 282 | 283 | Fun 2 284 | 285 | 286 | false 287 | 288 | 289 | 290 | 291 | 292 | 293 | Sound 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | false 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 60 312 | 16777215 313 | 314 | 315 | 316 | Update 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 0 325 | 0 326 | 327 | 328 | 329 | false 330 | 331 | 332 | 4-wire 333 | 334 | 335 | 336 | 337 | 338 | 339 | Temperature 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 0.000000000000000 350 | 351 | 352 | false 353 | 354 | 355 | 80.000000000000000 356 | 357 | 358 | 0.000000000000000 359 | 360 | 361 | 362 | 363 | 364 | 365 | Energy 366 | 367 | 368 | 369 | 370 | 371 | 372 | U-Actual 373 | 374 | 375 | 376 | 377 | 378 | 379 | Run 380 | 381 | 382 | 383 | 384 | 385 | 386 | A 387 | 388 | 389 | 0.100000000000000 390 | 391 | 392 | 393 | 394 | 395 | 396 | Qt::Vertical 397 | 398 | 399 | 400 | 20 401 | 0 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | Current 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 0 418 | 0 419 | 420 | 421 | 422 | V 423 | 424 | 425 | 0.100000000000000 426 | 427 | 428 | 429 | 430 | 431 | 432 | U-Limit 433 | 434 | 435 | 436 | 437 | 438 | 439 | false 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 0 448 | 0 449 | 450 | 451 | 452 | 453 | 60 454 | 16777215 455 | 456 | 457 | 458 | Reset 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | &Save Log 468 | 469 | 470 | 471 | 472 | E&xit 473 | 474 | 475 | 476 | 477 | &About 478 | 479 | 480 | 481 | 482 | Show &Log 483 | 484 | 485 | 486 | 487 | Show &Graph 488 | 489 | 490 | 491 | 492 | Show &Control 493 | 494 | 495 | 496 | 497 | false 498 | 499 | 500 | Device &Calibrate 501 | 502 | 503 | false 504 | 505 | 506 | 507 | 508 | true 509 | 510 | 511 | &Upgrade Device Firmware 512 | 513 | 514 | 515 | 516 | Device Con&figuration 517 | 518 | 519 | 520 | 521 | Load Raw Log 522 | 523 | 524 | 525 | 526 | 527 | 528 | QwtPlot 529 | QFrame 530 |
qwt_plot.h
531 | 1 532 |
533 | 534 | QwtThermo 535 | QWidget 536 |
qwt_thermo.h
537 |
538 | 539 | LogTable 540 | QTableView 541 |
logtable.h
542 |
543 |
544 | 545 | 546 | 547 | 548 | 549 | actionExit 550 | triggered() 551 | MainWindow 552 | close() 553 | 554 | 555 | -1 556 | -1 557 | 558 | 559 | 814 560 | 392 561 | 562 | 563 | 564 | 565 |
566 | -------------------------------------------------------------------------------- /control/sample.h: -------------------------------------------------------------------------------- 1 | #ifndef SAMPLE_H 2 | #define SAMPLE_H 3 | 4 | #include "decoder.h" 5 | 6 | #include 7 | 8 | struct Sample { 9 | qint64 timestamp; 10 | double u; 11 | double i; 12 | double ah; 13 | double wh; 14 | }; 15 | 16 | #endif // SAMPLE_H 17 | -------------------------------------------------------------------------------- /control/samplestorage.cpp: -------------------------------------------------------------------------------- 1 | #include "samplestorage.h" 2 | 3 | // ============================================================================================================= 4 | 5 | const Sample& SampleStorage::sample(size_t i) const 6 | { 7 | return samples[i]; 8 | } 9 | 10 | size_t SampleStorage::size() const 11 | { 12 | return samples.size(); 13 | } 14 | 15 | void SampleStorage::append(const Sample & sample) 16 | { 17 | if(!enabled) return; 18 | 19 | if(!begin) begin = sample.timestamp; 20 | 21 | if(samples.size() >= limit) 22 | del(samples.size() - limit + 1); 23 | 24 | emit beforeAppend(sample); 25 | 26 | samples.push_back(sample); 27 | 28 | emit afterAppend(sample); 29 | } 30 | 31 | void SampleStorage::appendMultiple(const QVector &list) 32 | { 33 | if(!enabled) return; 34 | 35 | if(!begin) begin = list.front().timestamp; 36 | 37 | if(samples.size() >= limit) 38 | del(samples.size() - limit + list.size()); 39 | 40 | emit beforeAppendMultiple(list); 41 | 42 | // FIXME list.size() > limit 43 | for(auto i = list.begin(), e = list.end(); i != e; ++i) 44 | samples.push_back(*i); 45 | 46 | emit afterAppendMultiple(list); 47 | } 48 | 49 | void SampleStorage::del(size_t n) 50 | { 51 | if(n > samples.size()) 52 | n = samples.size(); 53 | 54 | emit beforeDelete(n); 55 | 56 | samples.erase(samples.begin(), samples.begin() + n); 57 | 58 | emit afterDelete(n); 59 | } 60 | 61 | void SampleStorage::clear() 62 | { 63 | emit beforeClear(); 64 | 65 | samples.clear(); 66 | begin = 0; 67 | 68 | emit afterClear(); 69 | } 70 | -------------------------------------------------------------------------------- /control/samplestorage.h: -------------------------------------------------------------------------------- 1 | #ifndef SAMPLESTORAGE_H 2 | #define SAMPLESTORAGE_H 3 | 4 | #include "sample.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | class SampleStorage : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | SampleStorage(size_t limit_) : limit(limit_), enabled(false), begin(0) {} 17 | 18 | const Sample &sample(size_t i) const; 19 | size_t size() const; 20 | void clear(); 21 | void del(size_t n); // delete first n samples 22 | bool isEnabled() const { return enabled; } 23 | qint64 getBegin() const { return begin; } 24 | 25 | public slots: 26 | void append(const Sample &sample); 27 | void appendMultiple(const QVector &list); 28 | void setEnabled(bool enabled) { this->enabled = enabled; } 29 | 30 | signals: 31 | void beforeAppend(const Sample &sample); 32 | void beforeAppendMultiple(const QVector &list); 33 | void afterAppend(const Sample &sample); 34 | void afterAppendMultiple(const QVector &list); 35 | void beforeClear(); 36 | void afterClear(); 37 | void beforeDelete(size_t n); // before deleting of first n samples 38 | void afterDelete(size_t n); // after deleting of first n samples 39 | 40 | private: 41 | size_t limit; 42 | std::deque samples; 43 | bool enabled; 44 | qint64 begin; 45 | }; 46 | 47 | #endif // SAMPLESTORAGE_H 48 | -------------------------------------------------------------------------------- /control/settings.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGS_H 2 | #define SETTINGS_H 3 | 4 | struct Settings 5 | { 6 | static const size_t maxSamples = 1000 * 1000; 7 | }; 8 | 9 | 10 | #endif // SETTINGS_H 11 | 12 | -------------------------------------------------------------------------------- /control/stdio_fix.h: -------------------------------------------------------------------------------- 1 | #ifndef STDIO_FIX_H 2 | #define STDIO_FIX_H 3 | 4 | #include 5 | 6 | #ifdef _WIN32 7 | 8 | #define snprintf sprintf_s 9 | #define FMT_SIZE_T "" 10 | 11 | #else 12 | 13 | #define FMT_SIZE_T "z" 14 | 15 | #endif 16 | 17 | #endif // STDIO_FIX_H 18 | -------------------------------------------------------------------------------- /control/tablemodel.cpp: -------------------------------------------------------------------------------- 1 | #include "tablemodel.h" 2 | #include "utils.h" 3 | 4 | #include 5 | 6 | // ============================================================================================================= 7 | 8 | QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const 9 | { 10 | if(Qt::Horizontal == orientation) { 11 | if(Qt::DisplayRole == role) { 12 | switch(section) { 13 | case 0: return QVariant("Timestamp"); 14 | case 1: return QVariant("Time, s"); 15 | case 2: return QVariant("Current, A"); 16 | case 3: return QVariant("Voltage, V"); 17 | case 4: return QVariant("Energy, A⋅h"); 18 | case 5: return QVariant("Energy, W⋅h"); 19 | } 20 | } 21 | } 22 | else { 23 | if(Qt::DisplayRole == role) { 24 | return QVariant(section + 1); 25 | } 26 | } 27 | 28 | return QVariant(); // default 29 | } 30 | 31 | int TableModel::rowCount(const QModelIndex & parent) const 32 | { 33 | (void)parent; 34 | return storage.size(); 35 | } 36 | 37 | int TableModel::columnCount(const QModelIndex & parent) const 38 | { 39 | (void)parent; 40 | return 6; 41 | } 42 | 43 | QVariant TableModel::data(const QModelIndex & index, int role) const 44 | { 45 | static const QString dateTimeFormat("dd.MM.yyyy hh:mm.ss.zzz"); 46 | 47 | if(Qt::DisplayRole == role) { 48 | const Sample& sample = storage.sample(index.row()); 49 | switch(index.column()) { 50 | case 0: // full time stamp 51 | return QVariant(QDateTime::fromMSecsSinceEpoch(sample.timestamp).toString(dateTimeFormat)); 52 | 53 | case 1: // seconds since begin 54 | return QVariant(QString("%L1").arg(((double)(sample.timestamp - storage.getBegin())/1000.0), 0, 'f', 3)); 55 | 56 | case 2: 57 | return QVariant(QString("%L1").arg(sample.i, 0, 'f', 3)); 58 | 59 | case 3: 60 | return QVariant(QString("%L1").arg(sample.u, 0, 'f', 3)); 61 | 62 | case 4: 63 | return QVariant(QString("%L1").arg(sample.ah, 0, 'f', 3)); 64 | 65 | case 5: 66 | return QVariant(QString("%L1").arg(sample.wh, 0, 'f', 3)); 67 | 68 | default: 69 | ; // unexpected 70 | } 71 | } 72 | else if(Qt::TextAlignmentRole == role) { 73 | return QVariant(Qt::AlignRight | Qt::AlignVCenter); 74 | } 75 | 76 | return QVariant(); // default 77 | } 78 | 79 | bool TableModel::insertRows(int row, int count, const QModelIndex & parent) 80 | { 81 | (void)row; (void)count; (void)parent; 82 | 83 | return true; 84 | } 85 | 86 | void TableModel::beforeAppend(const Sample& sample) 87 | { 88 | (void)sample; 89 | 90 | beginInsertRows(QModelIndex(), storage.size(), storage.size()); 91 | } 92 | 93 | void TableModel::beforeAppendMultiple(const QVector &list) 94 | { 95 | beginInsertRows(QModelIndex(), storage.size(), storage.size() + list.size() - 1); 96 | } 97 | 98 | void TableModel::afterAppend() 99 | { 100 | endInsertRows(); 101 | } 102 | 103 | void TableModel::afterAppendMultiple() 104 | { 105 | endInsertRows(); 106 | } 107 | 108 | void TableModel::beforeClear() 109 | { 110 | beginResetModel(); 111 | } 112 | 113 | void TableModel::afterClear() 114 | { 115 | endResetModel(); 116 | } 117 | 118 | void TableModel::beforeDelete(size_t n) 119 | { 120 | beginRemoveRows(QModelIndex(), 0, n-1); 121 | } 122 | 123 | void TableModel::afterDelete() 124 | { 125 | endRemoveRows(); 126 | } 127 | -------------------------------------------------------------------------------- /control/tablemodel.h: -------------------------------------------------------------------------------- 1 | #ifndef TABLEMODEL_H 2 | #define TABLEMODEL_H 3 | 4 | #include "samplestorage.h" 5 | 6 | #include 7 | 8 | class TableModel : public QAbstractTableModel 9 | { 10 | public: 11 | TableModel(SampleStorage& storage_) : storage(storage_) {} 12 | 13 | int rowCount(const QModelIndex & parent = QModelIndex()) const override; 14 | int columnCount(const QModelIndex & parent = QModelIndex()) const override; 15 | QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; 16 | bool insertRows(int row, int count, const QModelIndex & parent = QModelIndex()) override; 17 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; 18 | 19 | void beforeAppend(const Sample &sample); 20 | void beforeAppendMultiple(const QVector &list); 21 | void afterAppend(); 22 | void afterAppendMultiple(); 23 | void beforeClear(); 24 | void afterClear(); 25 | void beforeDelete(size_t n); 26 | void afterDelete(); 27 | 28 | private: 29 | SampleStorage& storage; 30 | }; 31 | 32 | #endif // TABLEMODEL_H 33 | 34 | -------------------------------------------------------------------------------- /control/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | void showError(QString msg) 8 | { 9 | QMessageBox msgBox; 10 | msgBox.setIcon(QMessageBox::Warning); 11 | msgBox.setWindowTitle("Error"); 12 | msgBox.setText(msg); 13 | msgBox.exec(); 14 | } 15 | -------------------------------------------------------------------------------- /control/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | #include 5 | #include 6 | 7 | void showError(QString msg); 8 | 9 | #endif // UTILS_H 10 | 11 | -------------------------------------------------------------------------------- /displays.c: -------------------------------------------------------------------------------- 1 | #include "displays.h" 2 | 3 | #include 4 | 5 | #include "stm8.h" 6 | #include "systemtimer.h" 7 | 8 | inline void clock0(void) { 9 | GPIOC->ODR &= ~GPIO_ODR_5; 10 | } 11 | 12 | inline void clock1(void) { 13 | GPIOC->ODR |= GPIO_ODR_5; 14 | } 15 | 16 | inline void dataToInput(void) { 17 | GPIOC->DDR &= ~GPIO_DDR_6; 18 | GPIOC->DDR &= ~GPIO_DDR_7; 19 | } 20 | 21 | inline void dataToOutput(void) { 22 | GPIOC->DDR |= GPIO_DDR_6; 23 | GPIOC->DDR |= GPIO_DDR_7; 24 | } 25 | 26 | inline void dataA0(void) { 27 | GPIOC->ODR &= ~GPIO_ODR_6; 28 | } 29 | 30 | inline void dataA1(void) { 31 | GPIOC->ODR |= GPIO_ODR_6; 32 | } 33 | 34 | inline void dataB0(void) { 35 | GPIOC->ODR &= ~GPIO_ODR_7; 36 | } 37 | 38 | inline void dataB1(void) { 39 | GPIOC->ODR |= GPIO_ODR_7; 40 | } 41 | 42 | // Note: in this device SCL is always controlled by the master 43 | static void initI2c(void) { 44 | // SCL 45 | GPIOC->DDR |= GPIO_DDR_5; // output 46 | GPIOC->CR1 |= GPIO_CR1_5; // push-pull 47 | GPIOC->CR2 &= ~GPIO_CR2_5; // low-speed 48 | 49 | // SDA-A 50 | GPIOC->CR1 &= ~GPIO_CR1_6; // open-drain 51 | GPIOC->CR2 &= ~GPIO_CR2_6; // low-speed, no interrupt 52 | 53 | // SDA-B 54 | GPIOC->CR1 &= ~GPIO_CR1_7; // open-drain 55 | GPIOC->CR2 &= ~GPIO_CR2_7; // low-speed, no interrupt 56 | } 57 | 58 | // ~1/5 of clock-cycle 59 | inline void delay(void) { 60 | // According to TM1650 datasheet, minimal signal duration is 100 ns, 61 | // which is unreachable with STM8@16MHz. So, don't add artifical delay. 62 | } 63 | 64 | inline void waitBusy(void) { 65 | // Note: in this device SCL is always controlled by the master 66 | } 67 | 68 | static void clockBegin(void) { 69 | waitBusy(); 70 | clock0(); 71 | delay(); 72 | } 73 | 74 | static void clockEnd(void) { 75 | clock1(); 76 | delay(); 77 | delay(); 78 | } 79 | 80 | static void writeBits(bool bitA, bool bitB) { 81 | clockBegin(); 82 | dataToOutput(); 83 | 84 | if(bitA) dataA1(); 85 | else dataA0(); 86 | 87 | if(bitB) dataB1(); 88 | else dataB0(); 89 | 90 | delay(); 91 | clockEnd(); 92 | } 93 | 94 | // we are not interested in result, just generate a clock for the slaves 95 | static void readBits(void) { 96 | clockBegin(); 97 | dataToInput(); 98 | delay(); 99 | clockEnd(); 100 | } 101 | 102 | static void begin(void) { 103 | dataToOutput(); 104 | 105 | dataA1(); 106 | dataB1(); 107 | delay(); 108 | clock1(); 109 | delay(); 110 | delay(); 111 | 112 | waitBusy(); 113 | 114 | dataA0(); 115 | dataB0(); 116 | delay(); 117 | delay(); 118 | // SCL will be set to 0 during next clock 119 | } 120 | 121 | static void end(void) { 122 | waitBusy(); 123 | dataToOutput(); 124 | 125 | // ensure SDA is low 126 | clock0(); 127 | delay(); 128 | dataA0(); 129 | dataB0(); 130 | delay(); 131 | 132 | // SCL to 1, then SDA to 1 133 | clock1(); 134 | delay(); 135 | delay(); 136 | dataA1(); 137 | dataB1(); 138 | } 139 | 140 | static void writeBytes(uint8_t a, uint8_t b) { 141 | uint8_t i; 142 | for(i = 8; i > 0; --i) { 143 | writeBits(a & 0x80, b & 0x80); 144 | a <<= 1; 145 | b <<= 1; 146 | } 147 | 148 | readBits(); // ACK 149 | } 150 | 151 | static void sendBytes(uint8_t addr, uint8_t a, uint8_t b) { 152 | begin(); 153 | writeBytes(addr, addr); 154 | writeBytes(a, b); 155 | end(); 156 | } 157 | 158 | void DISPLAYS_init(void) { 159 | initI2c(); 160 | } 161 | 162 | void DISPLAYS_start(void) { 163 | SYSTEMTIMER_waitMsOnStart(50); // let the TM1650s time to start 164 | sendBytes(0x48, 0x11, 0x11); // min brightness; dot; on 165 | } 166 | 167 | void DISPLAYS_display(const uint8_t* a, const uint8_t* b) { 168 | uint8_t addr; 169 | for(addr = 0x68; addr <= 0x6E; addr += 2, ++a, ++b) 170 | sendBytes(addr, *a, *b); 171 | } 172 | 173 | const uint8_t DISPLAYS_DIGITS[] = { 174 | DISPLAYS_SYM_0, 175 | DISPLAYS_SYM_1, 176 | DISPLAYS_SYM_2, 177 | DISPLAYS_SYM_3, 178 | DISPLAYS_SYM_4, 179 | DISPLAYS_SYM_5, 180 | DISPLAYS_SYM_6, 181 | DISPLAYS_SYM_7, 182 | DISPLAYS_SYM_8, 183 | DISPLAYS_SYM_9, 184 | DISPLAYS_SYM_A, 185 | DISPLAYS_SYM_b, 186 | DISPLAYS_SYM_C, 187 | DISPLAYS_SYM_d, 188 | DISPLAYS_SYM_E, 189 | DISPLAYS_SYM_F 190 | }; 191 | 192 | -------------------------------------------------------------------------------- /displays.h: -------------------------------------------------------------------------------- 1 | #ifndef _DISPLAYS_H_ 2 | #define _DISPLAYS_H_ 3 | 4 | #include "stm8.h" 5 | #include "settings.h" 6 | 7 | #define DISPLAYS_DOT 0x80 8 | #define DISPLAYS_SYM_SPACE 0x00 9 | #define DISPLAYS_SYM_DASH 0x40 10 | #define DISPLAYS_SYM_I 0x06 11 | #define DISPLAYS_SYM_J 0x0e 12 | #define DISPLAYS_SYM_L 0x38 13 | #define DISPLAYS_SYM_O 0x3f 14 | #define DISPLAYS_SYM_P 0x73 15 | #define DISPLAYS_SYM_S 0x6d 16 | #define DISPLAYS_SYM_U 0x3e 17 | #define DISPLAYS_SYM_n 0x54 18 | #define DISPLAYS_SYM_o 0x5c 19 | #define DISPLAYS_SYM_r 0x50 20 | #define DISPLAYS_SYM_t 0x78 21 | #define DISPLAYS_SYM_u 0x1c 22 | #define DISPLAYS_SYM_0 0x3f 23 | #define DISPLAYS_SYM_1 0x06 24 | #define DISPLAYS_SYM_2 0x5b 25 | #define DISPLAYS_SYM_3 0x4f 26 | #define DISPLAYS_SYM_4 0x66 27 | #define DISPLAYS_SYM_5 0x6d 28 | #define DISPLAYS_SYM_6 0x7d 29 | #define DISPLAYS_SYM_7 0x07 30 | #define DISPLAYS_SYM_8 0x7f 31 | #define DISPLAYS_SYM_9 0x6f 32 | #define DISPLAYS_SYM_A 0x77 33 | #define DISPLAYS_SYM_b 0x7c 34 | #define DISPLAYS_SYM_C 0x39 35 | #define DISPLAYS_SYM_d 0x5e 36 | #define DISPLAYS_SYM_E 0x79 37 | #define DISPLAYS_SYM_F 0x71 38 | 39 | #define DISPLAYS_CHAR_E "\0x79" 40 | 41 | extern const uint8_t DISPLAYS_DIGITS[]; 42 | 43 | void DISPLAYS_init(void); 44 | void DISPLAYS_start(void); 45 | void DISPLAYS_display(const uint8_t* a, const uint8_t* b); 46 | 47 | #endif // _DISPLAYS_H_ 48 | -------------------------------------------------------------------------------- /docs/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev26th/electronic_load/8eccf2a8dfd2d0567c6b7756a88d2e565bfa988a/docs/1.jpg -------------------------------------------------------------------------------- /docs/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev26th/electronic_load/8eccf2a8dfd2d0567c6b7756a88d2e565bfa988a/docs/2.jpg -------------------------------------------------------------------------------- /encoder.c: -------------------------------------------------------------------------------- 1 | #include "encoder.h" 2 | 3 | #include 4 | 5 | #include "stm8.h" 6 | #include "systemtimer.h" 7 | 8 | static volatile int8_t encoderValue; 9 | static int8_t encoderValueCopy; 10 | 11 | void ENCODER_init(void) { 12 | GPIOB->CR1 |= GPIO_CR1_5 | GPIO_CR1_4; // pull-up 13 | GPIOB->CR2 |= GPIO_CR2_4; // interrupt 14 | 15 | EXTI->CR1 |= EXTI_CR1_PBIS_FALLING; 16 | } 17 | 18 | void ENCODER_process(void) { 19 | // Atomic: 20 | // encoderValueCopy = encoderValue; 21 | // encoderValue = 0; 22 | __asm 23 | CLR A 24 | EXG A, _encoderValue 25 | LD _encoderValueCopy, A 26 | __endasm; 27 | 28 | if(encoderValueCopy != 0) 29 | ENCODER_onChange(encoderValueCopy); 30 | } 31 | 32 | void ENCODERBUTTON_exti(void) __interrupt(IRQN_EXTI1) { 33 | encoderValue += (GPIOB->IDR & GPIO_IDR_5) ? 1 : -1; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /encoder.h: -------------------------------------------------------------------------------- 1 | #ifndef _ENCODER_H_ 2 | #define _ENCODER_H_ 3 | 4 | #include 5 | #include "stm8.h" 6 | 7 | void ENCODER_onChange(int8_t d); 8 | void ENCODER_init(void); 9 | void ENCODER_process(void); 10 | 11 | void ENCODERBUTTON_exti(void) __interrupt(IRQN_EXTI1); 12 | 13 | #endif // _ENCODER_H_ 14 | -------------------------------------------------------------------------------- /encoderbutton.c: -------------------------------------------------------------------------------- 1 | #include "encoderbutton.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "stm8.h" 9 | #include "systemtimer.h" 10 | 11 | #define ENCODERBUTTON_BOUNCE_TIME_MS 20 12 | #define ENCODERBUTTON_LONG_TIME_MS 1000 13 | 14 | // FIXME merge with button.c 15 | 16 | enum PressValue { 17 | PressValue_None, 18 | PressValue_Short, 19 | PressValue_Long 20 | }; 21 | static volatile enum PressValue pressValue; 22 | static enum PressValue pressValueCopy; 23 | 24 | static volatile bool pressed; 25 | static volatile bool pressProcessed; 26 | static volatile uint32_t lastCheck; 27 | static volatile uint32_t pressTime; 28 | 29 | void ENCODERBUTTON_init(void) { 30 | GPIOC->CR1 |= GPIO_CR1_3; // pull-up 31 | } 32 | 33 | // ~3.8 us 34 | void ENCODERBUTTON_cycle(void) { 35 | bool p; 36 | 37 | if(pressValue != PressValue_None) return; 38 | if((SYSTEMTIMER_ms - lastCheck) < ENCODERBUTTON_BOUNCE_TIME_MS) return; 39 | 40 | p = !(GPIOC->IDR & GPIO_CR1_3); 41 | if(p && pressed && (SYSTEMTIMER_ms - pressTime) >= ENCODERBUTTON_LONG_TIME_MS) { 42 | if(!pressProcessed) { 43 | pressValue = PressValue_Long; 44 | pressProcessed = true; 45 | } 46 | } 47 | else if(p != pressed) { 48 | if(p) { 49 | pressTime = SYSTEMTIMER_ms; 50 | } 51 | else if(!pressProcessed) { 52 | pressValue = PressValue_Short; 53 | } 54 | pressed = p; 55 | lastCheck = SYSTEMTIMER_ms; 56 | pressProcessed = false; 57 | } 58 | } 59 | 60 | void ENCODERBUTTON_process(void) { 61 | // Atomic: 62 | // pressValueCopy = pressValue; 63 | // pressValue = 0; 64 | __asm 65 | CLR A 66 | EXG A, _pressValue 67 | LD _pressValueCopy, A 68 | __endasm; 69 | 70 | switch(pressValueCopy) { 71 | case PressValue_None: break; 72 | case PressValue_Short: ENCODERBUTTON_onRelease(false); break; 73 | case PressValue_Long: ENCODERBUTTON_onRelease(true); break; 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /encoderbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef _ENCODERBUTTON_H_ 2 | #define _ENCODERBUTTON_H_ 3 | 4 | #include 5 | 6 | void ENCODERBUTTON_init(void); 7 | void ENCODERBUTTON_onRelease(bool longpress); 8 | void ENCODERBUTTON_cycle(void); 9 | void ENCODERBUTTON_process(void); 10 | 11 | #endif // _ENCODERBUTTON_H_ 12 | -------------------------------------------------------------------------------- /fan.c: -------------------------------------------------------------------------------- 1 | #include "fan.h" 2 | 3 | void FAN_init(void) { 4 | TIM3->PSCR = 1; 5 | TIM3->ARRH = 0; 6 | TIM3->ARRL = 99; 7 | TIM3->CCR2H = 0; 8 | TIM3->CCR2L = 0; 9 | TIM3->CCMR2 = TIM3_CCMR2_OC2M_PWM1 | TIM3_CCMR2_OC2PE; 10 | TIM3->CCER1 = TIM3_CCER1_CC2E; 11 | TIM3->CR1 |= TIM3_CR1_CEN; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /fan.h: -------------------------------------------------------------------------------- 1 | #ifndef _FAN_H_ 2 | #define _FAN_H_ 3 | 4 | #include 5 | 6 | #include "stm8.h" 7 | 8 | // PWM on PD0/TIM3_CH2 9 | void FAN_init(void); 10 | 11 | inline void FAN_set(uint8_t v) { 12 | TIM3->CCR2L = v; 13 | } 14 | 15 | #endif // _FAN_H_ 16 | -------------------------------------------------------------------------------- /flash.c: -------------------------------------------------------------------------------- 1 | #include "flash.h" 2 | 3 | void FLASH_unlockProg(void) { 4 | FLASH->PUKR = FLASH_KEY1; 5 | FLASH->PUKR = FLASH_KEY2; 6 | while(!(FLASH->IAPSR & FLASH_IAPSR_PUL)); 7 | } 8 | 9 | void FLASH_lockProg(void) { 10 | FLASH->IAPSR &= FLASH_IAPSR_PUL; 11 | } 12 | 13 | void FLASH_unlockData(void) { 14 | FLASH->DUKR = FLASH_KEY2; 15 | FLASH->DUKR = FLASH_KEY1; 16 | while(!(FLASH->IAPSR & FLASH_IAPSR_DUL)); 17 | 18 | // FIXME without this dummy delay first 4 bytes of EEPROM are written as zero sometimes 19 | { 20 | volatile uint16_t i; 21 | for(i = 0; i < 10000; ++i) {} 22 | } 23 | } 24 | 25 | void FLASH_waitData(void) { 26 | while(!(FLASH->IAPSR & FLASH_IAPSR_EOP)); 27 | } 28 | 29 | void FLASH_lockData(void) { 30 | FLASH->IAPSR &= FLASH_IAPSR_DUL; 31 | } 32 | 33 | void FLASH_waitOpt(void) { 34 | FLASH_waitData(); 35 | } 36 | 37 | void FLASH_unlockOpt(void) { 38 | FLASH_unlockData(); 39 | FLASH->CR2 |= FLASH_CR2_OPT; 40 | FLASH->NCR2 &= ~FLASH_NCR2_NOPT; 41 | } 42 | 43 | void FLASH_lockOpt(void) { 44 | FLASH->CR2 &= ~FLASH_CR2_OPT; 45 | FLASH->NCR2 |= FLASH_NCR2_NOPT; 46 | FLASH_lockData(); 47 | } 48 | 49 | -------------------------------------------------------------------------------- /flash.h: -------------------------------------------------------------------------------- 1 | #ifndef _FLASH_H_ 2 | #define _FLASH_H_ 3 | 4 | #include "stm8.h" 5 | 6 | void FLASH_unlockProg(void); 7 | void FLASH_lockProg(void); 8 | 9 | void FLASH_unlockData(void); 10 | void FLASH_waitData(void); 11 | void FLASH_lockData(void); 12 | 13 | void FLASH_unlockOpt(void); 14 | void FLASH_waitOpt(void); 15 | void FLASH_lockOpt(); 16 | 17 | #endif // _FLASH_ 18 | -------------------------------------------------------------------------------- /load.c: -------------------------------------------------------------------------------- 1 | #include "load.h" 2 | 3 | // use PC1/TIM1_CH1, PE5, PC2 4 | void LOAD_init(void) { 5 | TIM1->PSCRH = 0; 6 | TIM1->PSCRL = 0; 7 | TIM1->ARRH = (uint8_t)(LOAD_MAX >> 8); // FIXME increase frequency? don't lose accuracy 8 | TIM1->ARRL = (uint8_t)LOAD_MAX; 9 | TIM1->BKR = TIM1_BKR_MOE; 10 | TIM1->CCMR1 = TIM1_CCMR1_OC1M_PWM1 | TIM1_CCMR1_OC1PE; 11 | TIM1->CCER1 = TIM1_CCER1_CC1E; 12 | TIM1->CCR1H = 0; 13 | TIM1->CCR1L = 0; 14 | TIM1->CR2 |= TIM1_CR2_CCPC; 15 | TIM1->CR1 |= TIM1_CR1_CEN; 16 | 17 | LOAD_stop(); 18 | GPIOE->DDR |= GPIO_DDR_5; // output 19 | GPIOE->CR1 |= GPIO_CR1_5; // pull-push 20 | 21 | GPIOC->CR1 |= GPIO_CR1_2; // pull-up 22 | } 23 | 24 | void LOAD_set(uint16_t v) { 25 | TIM1->CCR1H = v >> 8; 26 | TIM1->CCR1L = v & 0xFF; 27 | TIM1->EGR |= TIM1_EGR_COMG; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /load.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOAD_H_ 2 | #define _LOAD_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "stm8.h" 8 | 9 | #define LOAD_MAX 0x7FFFuL 10 | 11 | // PWM on PC1/TIM1_CH1 12 | void LOAD_init(void); 13 | 14 | void LOAD_set(uint16_t v); 15 | 16 | inline void LOAD_start(void) { 17 | GPIOE->ODR &= ~GPIO_ODR_5; 18 | } 19 | 20 | inline void LOAD_stop(void) { 21 | GPIOE->ODR |= GPIO_ODR_5; 22 | } 23 | 24 | inline bool LOAD_isStable(void) { 25 | return (GPIOC->IDR & GPIO_IDR_2); 26 | } 27 | 28 | #endif // _LOAD_H_ 29 | -------------------------------------------------------------------------------- /misc/default_opt.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev26th/electronic_load/8eccf2a8dfd2d0567c6b7756a88d2e565bfa988a/misc/default_opt.bin -------------------------------------------------------------------------------- /misc/eeprom.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev26th/electronic_load/8eccf2a8dfd2d0567c6b7756a88d2e565bfa988a/misc/eeprom.bin -------------------------------------------------------------------------------- /ringbuffer.c: -------------------------------------------------------------------------------- 1 | #include "ringbuffer.h" 2 | 3 | #include 4 | 5 | #define SIZE 128 6 | 7 | static volatile uint8_t begin; 8 | static volatile uint8_t end; 9 | static uint8_t buf[SIZE]; 10 | 11 | bool RINGBUFFER_addIfNotFull(uint8_t v) { 12 | uint8_t b = begin; 13 | uint8_t e = end; 14 | 15 | if((e - b) >= SIZE) return false; 16 | 17 | buf[e % SIZE] = v; 18 | end = e + 1; 19 | 20 | return true; 21 | } 22 | 23 | bool RINGBUFFER_takeIfNotEmpty(uint8_t* v) { 24 | uint8_t b = begin; 25 | uint8_t e = end; 26 | 27 | if(b == e) return false; 28 | 29 | *v = buf[b % SIZE]; 30 | begin = b + 1; 31 | 32 | return true; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /ringbuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef _RINGBUFFER_H_ 2 | #define _RINGBUFFER_H_ 3 | 4 | #include 5 | #include 6 | 7 | bool RINGBUFFER_addIfNotFull(uint8_t v); 8 | bool RINGBUFFER_takeIfNotEmpty(uint8_t* v); 9 | 10 | #endif // _RINGBUFFER_H_ 11 | -------------------------------------------------------------------------------- /settings.h: -------------------------------------------------------------------------------- 1 | #ifndef _SETTINGS_H_ 2 | #define _SETTINGS_H_ 3 | 4 | #define STM8S105 5 | #define CPU_F 12000000ul 6 | #define BAUD 115200 7 | 8 | #endif // _SETTINGS_H_ 9 | -------------------------------------------------------------------------------- /strings.c: -------------------------------------------------------------------------------- 1 | #include "strings.h" 2 | 3 | const char HEX_DIGITS[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 4 | 5 | -------------------------------------------------------------------------------- /strings.h: -------------------------------------------------------------------------------- 1 | #ifndef _STRINGS_H_ 2 | #define _STRINGS_H_ 3 | 4 | extern const char HEX_DIGITS[16]; 5 | 6 | #endif // _STRINGS_H_ 7 | -------------------------------------------------------------------------------- /system.c: -------------------------------------------------------------------------------- 1 | #include "system.h" 2 | 3 | #include "stm8.h" 4 | 5 | void SYSTEM_toHseClock(void) { 6 | CLK->SWCR |= CLK_SWCR_SWEN; 7 | CLK->SWR = CLK_SWR_SWI_HSE; 8 | while(CLK->SWCR & CLK_SWCR_SWBSY); 9 | } 10 | 11 | void SYSTEM_reset(void) { 12 | // use the WWDG for reset 13 | WWDG->CR = WWDG_CR_WDGA | 0; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /system.h: -------------------------------------------------------------------------------- 1 | #ifndef _SYSTEM_H_ 2 | #define _SYSTEM_H_ 3 | 4 | void SYSTEM_toHseClock(void); 5 | void SYSTEM_reset(void); 6 | 7 | #endif // _SYSTEM_H_ 8 | 9 | -------------------------------------------------------------------------------- /systemtimer.c: -------------------------------------------------------------------------------- 1 | #include "systemtimer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "stm8.h" 9 | #include "settings.h" 10 | 11 | volatile uint32_t SYSTEMTIMER_ms = 0; 12 | 13 | bool tickTack; 14 | 15 | void SYSTEMTIMER_delayMs(uint32_t ms) { 16 | uint32_t b = SYSTEMTIMER_ms; 17 | while(SYSTEMTIMER_ms - b < ms); 18 | } 19 | 20 | void SYSTEMTIMER_waitMsOnStart(uint32_t ms) { 21 | while(SYSTEMTIMER_ms < ms); 22 | } 23 | 24 | void SYSTEMTIMER_init(void) { 25 | 26 | // Note: use TIM2 instead of TIM4 for get exact millisecond 27 | 28 | #define SYSTEM_TIMER_PSC 5 // 2^n FIXME auto 29 | 30 | #if (CPU_F / (1 << SYSTEM_TIMER_PSC) / SYSTEMTIMER_TICKS_PER_S - 1 >= 65536) 31 | #error "SystemTimer: CPU_F = " ##CPU_F "too big for this prescaler" 32 | #endif 33 | #if (CPU_F % ((1 << SYSTEM_TIMER_PSC) * SYSTEMTIMER_TICKS_PER_S) != 0) 34 | #warning "SystemTimer: cannot configure exact tick for this CPU_F" 35 | #endif 36 | 37 | uint16_t arr = (CPU_F / (1 << SYSTEM_TIMER_PSC) / SYSTEMTIMER_TICKS_PER_S / 2 - 1); 38 | TIM2->PSCR = SYSTEM_TIMER_PSC; 39 | TIM2->ARRH = (uint8_t)(arr >> 8); 40 | TIM2->ARRL = (uint8_t)arr; 41 | TIM2->IER = TIM2_IER_UIE; 42 | TIM2->CR1 |= TIM2_CR1_CEN; 43 | } 44 | 45 | void SYSTEMTIMER_TIM2_overflow(void) __interrupt(IRQN_TIM2_UP) { 46 | TIM2->SR1 = (uint8_t)~TIM2_SR1_UIF; 47 | SYSTEMTIMER_ms += SYSTEMTIMER_MS_PER_TICK / 2; 48 | tickTack = !tickTack; 49 | if(tickTack) 50 | SYSTEMTIMER_onTick(); 51 | else 52 | SYSTEMTIMER_onTack(); 53 | } 54 | 55 | -------------------------------------------------------------------------------- /systemtimer.h: -------------------------------------------------------------------------------- 1 | #ifndef _SYSTEM_TIMER_H_ 2 | #define _SYSTEM_TIMER_H_ 3 | 4 | #include 5 | 6 | #include "stm8.h" 7 | 8 | #define SYSTEMTIMER_MS_PER_TICK 2 9 | #define SYSTEMTIMER_TICKS_PER_S (1000/SYSTEMTIMER_MS_PER_TICK) 10 | 11 | extern volatile uint32_t SYSTEMTIMER_ms; // with overflow every ~50 days 12 | 13 | void SYSTEMTIMER_onTick(void); // called every 10 ms 14 | void SYSTEMTIMER_onTack(void); // called every 10 ms 15 | 16 | void SYSTEMTIMER_delayMs(uint32_t ms); 17 | 18 | void SYSTEMTIMER_waitMsOnStart(uint32_t ms); 19 | 20 | void SYSTEMTIMER_init(void); // 1 kHz 21 | 22 | void SYSTEMTIMER_TIM2_overflow(void) __interrupt(IRQN_TIM2_UP); 23 | 24 | #endif // _SYSTEM_TIMER_H_ 25 | -------------------------------------------------------------------------------- /uart.c: -------------------------------------------------------------------------------- 1 | #include "uart.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "stm8.h" 7 | #include "settings.h" 8 | #include "strings.h" 9 | #include "ringbuffer.h" 10 | 11 | enum RxState { 12 | RxState_Start, 13 | RxState_H, // waiting for high byte half 14 | RxState_L, // waiting low byte half 15 | RxState_Stop // '\n' received, but buffer not read yet 16 | }; 17 | static enum RxState rxState = RxState_Start; 18 | 19 | static uint8_t rxBuf[UART_RXBUF_SIZE]; 20 | static uint8_t rxBufPos; 21 | static bool hasChecksum; 22 | 23 | void UART_write(const char *str) { 24 | for(; *str; ++str) 25 | UART_send((uint8_t)*str); 26 | } 27 | 28 | void UART_writeHexU8(uint8_t v) { 29 | UART_send(HEX_DIGITS[v >> 4]); 30 | UART_send(HEX_DIGITS[v & 0x0F]); 31 | } 32 | 33 | void UART_writeHexU16(uint16_t v) { 34 | UART_writeHexU8(v >> 8); 35 | UART_writeHexU8(v & 0xFF); 36 | } 37 | 38 | void UART_writeHexU32(uint32_t v) { 39 | UART_writeHexU8(v >> 24); 40 | UART_writeHexU8(v >> 16); 41 | UART_writeHexU8(v >> 8); 42 | UART_writeHexU8(v & 0xFF); 43 | } 44 | 45 | void UART_writeDecU16(uint16_t v) { 46 | UART_writeDecU64(v, 5); 47 | } 48 | 49 | void UART_writeDecU32(uint32_t v) { 50 | UART_writeDecU64(v, 10); 51 | } 52 | 53 | void UART_writeDecU64(uint64_t v, uint8_t n) { 54 | char buf[22]; 55 | uint8_t i; 56 | for(i = 0; i < n; ++i) buf[i] = ' '; 57 | buf[n] = '\0'; 58 | 59 | i = n; 60 | do { 61 | uint8_t d = v % 10; 62 | v /= 10; 63 | buf[--i] = '0' + d; 64 | } 65 | while(v != 0); 66 | 67 | UART_write(buf); 68 | } 69 | 70 | static inline void resetRx(void) { 71 | rxBufPos = 0; 72 | rxState = RxState_Start; 73 | } 74 | 75 | const uint8_t* UART_getRx(uint8_t* size) { 76 | if(rxState == RxState_Stop) { 77 | *size = rxBufPos; 78 | return rxBuf; 79 | } 80 | else { 81 | return NULL; 82 | } 83 | } 84 | 85 | bool UART_hasChecksum(void) { 86 | return hasChecksum; 87 | } 88 | 89 | void UART_rxDone(void) { 90 | resetRx(); 91 | } 92 | 93 | // 2-5.3 us 94 | void UART_process(void) { 95 | uint8_t v; 96 | uint8_t b; 97 | if(!RINGBUFFER_takeIfNotEmpty(&b)) return; 98 | 99 | if(rxState != RxState_Stop) { 100 | if(b == 'S' || b == 's') { // start 101 | resetRx(); 102 | rxState = RxState_H; 103 | hasChecksum = (b == 'S'); 104 | return; 105 | } 106 | } 107 | 108 | switch(rxState) { 109 | case RxState_H: 110 | case RxState_L: 111 | if(b >= '0' && b <= '9') { 112 | v = b - '0'; 113 | } 114 | else if(b >= 'A' && b <= 'F') { 115 | v = b - 'A' + 10; 116 | } 117 | else if(b == '\n') { // ignore 118 | break; 119 | } 120 | else if(b == '\r') { // stop 121 | if(rxState == RxState_L) // unexpected, reset 122 | resetRx(); 123 | else 124 | rxState = RxState_Stop; 125 | break; 126 | } 127 | else { // unexpected symbol, reset 128 | resetRx(); 129 | break; 130 | } 131 | 132 | if(rxState == RxState_H) { 133 | rxBuf[rxBufPos] = (v << 4); 134 | rxState = RxState_L; 135 | } 136 | else { 137 | rxBuf[rxBufPos] |= v; 138 | rxState = RxState_H; 139 | ++rxBufPos; 140 | if(rxBufPos >= UART_RXBUF_SIZE) resetRx(); 141 | } 142 | 143 | break; 144 | 145 | case RxState_Start: 146 | case RxState_Stop: 147 | nop(); // who are the EVELYN and the DOG? 148 | break; // ignore all 149 | } 150 | } 151 | 152 | // ~ 5.5us 153 | void UART_UART2_rx(void) __interrupt(IRQN_UART2_RX) { 154 | (void)UART2->SR; // reset and ignore Overrun, if any 155 | RINGBUFFER_addIfNotFull(UART2->DR); // reset RXNE 156 | } 157 | 158 | -------------------------------------------------------------------------------- /uart.h: -------------------------------------------------------------------------------- 1 | #ifndef _UART_H_ 2 | #define _UART_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "stm8.h" 8 | #include "settings.h" 9 | 10 | #define UART_RXBUF_SIZE 250 11 | 12 | const uint8_t* UART_getRx(uint8_t* size); 13 | bool UART_hasChecksum(void); 14 | void UART_rxDone(void); 15 | void UART_process(void); 16 | 17 | inline void UART_init(void) { 18 | UART2->BRR2 = (((CPU_F + BAUD/2) / BAUD) & 0x000F) | ((((CPU_F + BAUD/2) / BAUD) & 0xF000) >> 8); 19 | UART2->BRR1 = (((CPU_F + BAUD/2) / BAUD) & 0x0FF0) >> 4; 20 | UART2->CR2 = UART_CR2_TEN | UART_CR2_REN | UART_CR2_RIEN; 21 | } 22 | 23 | inline void UART_send(uint8_t v) { 24 | while(!(UART2->SR & UART_SR_TXE)); 25 | UART2->DR = v; 26 | } 27 | 28 | void UART_write(const char *str); 29 | 30 | void UART_writeHexU8(uint8_t v); 31 | 32 | void UART_writeHexU16(uint16_t v); 33 | 34 | void UART_writeHexU32(uint32_t v); 35 | 36 | void UART_writeDecU16(uint16_t v); 37 | 38 | void UART_writeDecU32(uint32_t v); 39 | 40 | void UART_writeDecU64(uint64_t v, uint8_t n); 41 | 42 | void UART_UART2_rx(void) __interrupt(IRQN_UART2_RX); 43 | 44 | #endif // _UART_H_ 45 | --------------------------------------------------------------------------------