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