Attachment 'LabPSU2R.cpp'
Download 1 /*
2 LabPSU2R, control of 2 switching PSU modules, with remote control
3 Protocol: SCPI 1997.0
4 2015-03-10 Rudolf Reuter DL5FA
5 2016-10-17 RR, OUTPUT->STATe
6
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
21 version for LCD shield by ElecFreaks.com
22 using internal Rotary Encoder (D2, D3) and Joystick (A0)
23
24 Todo: Display Power [W]
25 Display Charge Capacity [mAh]
26
27 Arduino LED: D13
28
29 Encoder: D2, D3, push contact A0 = show encoder value & intRemote = 0
30
31 Joystick: A7 in Analog Voltage Input from Joystick
32 switch "left" = show current limit value I1
33 switch "right" = show current limit value I2
34 switch "up" = No function
35 switch "down" = No function
36 switch "Center Push" = Move active set pointer
37 switch "Encoder Push" = show Encoder value
38
39 LCD: D4 LCD, D4
40 D5 LCD, D5
41 D6 LCD, D6
42 D7 LCD, D7
43 D8 LCD, RS
44 D9 LCD, E
45 D10 LCD, Illumination
46
47 PSU: D11 CL1 (CL = Current Limit)
48 D12 CL2
49 A0 Enable PSU
50 A1 I1
51 A2 U1
52 A3 I2
53 A4 SDA, I2C digital poti
54 A5 SCL, I2C digital poti
55 A6 U2
56
57 Current limit function for encoder: f(x) = 0.0219 + 0.033 A
58 Correction: 0.0213
59 Voltage conversion: encoder = (U[V] - 32.7) * -0.126
60 File: Schaltregler_Current_Limit.ods
61
62 Library SerialCommand, version 3.6 (Stafan Rado)
63 https://github.com/kroimon/Arduino-SerialCommand
64 Patch for SerialCommand.h:
65 l67 char delim[3]; // null-terminated list of character to be used as delimiters for tokenizing (default " ")
66 2015-03-04 RR, 2 -> 3
67 Patch for SerialCommand.cpp:
68 l36 strcpy(delim, " :"); // strtok_r needs a null-terminated string
69 2015-03-04 RR, " " -> " :" for SCPI
70
71 Library for Digital Encoder and LCD
72 http://www.elecfreaks.com/store/download/product/shield/LCD_Key_Shield/CODE/LCD_ENCKEY_TEST.zip
73
74 Library for Digital Potentiometer MCP4461
75 https://github.com/jmgiacalone/Arduino-libraries/tree/master/MCP4461
76
77 The libraries must be copied to folder Arduino/libraries/
78 MCP4461/
79 Rotary/
80 SerialCommand/
81 --------------------------------*/
82
83 //--------- Declarations
84 #include <Rotary.h>
85 #include <LiquidCrystal.h>
86 #include <Wire.h>
87 #include <MCP4461.h>
88
89 #include <SerialCommand.h>
90
91 #define arduinoLED 13 // Arduino LED on board
92
93 SerialCommand sCmd; // The SerialCommand object
94
95 //---------------------
96 // Joystick defines
97 #define btnNone 0
98 #define btnUp 1
99 #define btnRight 2
100 #define btnDown 3
101 #define btnCenter 4
102 #define btnLeft 5
103 #define btnEnc 6
104
105 //set PSU pins
106 #define ENA A0 // Enable PSU module
107 #define CL1 11 // Current Limit 1, high=active
108 #define CL2 12 // Current Limit 2, high=active
109 #define I1 1 // analog Current PSU1
110 #define U1 2 // analog Voltage PSU2
111 #define I2 3 // analog Current PSU2
112 #define U2 6 // analog Voltage PSU2
113 #define VOLT 0
114 #define AMPERE 1
115 #define DP_U1 0 // digital poti U1, Set
116 #define DP_U2 1 // digital poti U2, Set
117 #define DP_I1 2 // digital poti I1, Current Limit
118 #define DP_I2 3 // digital poti I2, Current Limit
119 // target ADC: 1023 = 25 V
120 #define U_CONV1 39.3 // analog to Voltage factor 1
121 #define U_CONV2 39.7 // analog to Voltage factor 2
122 // target ADC: 1023 = 5 A
123 #define I_CONV1 218.9 // analog to Ampere factor 1
124 #define I_CONV2 218.9 // analog to Ampere factor 2
125 #define CL_CONV 0.0213 // encoder to Current Limit Set
126 #define CL_OFST 0.033 // Current Limit Offset
127 #define UR_CONV1 -0.1023 // voltage to encoder factor 1
128 #define UR_OFST1 26.73 // voltage to encoder Offset 1
129 #define UR_CONV2 -0.1035 // voltage to encoder factor 2
130 #define UR_OFST2 26.9 // voltage to encoder Offset 2
131 #define Imin 0.04
132 #define Imax 3.0
133 #define Umin 0.5
134 #define Umax 23.0
135
136 // I2C/TWI for digital potentiometer MCP4461
137
138 // http://www.elecfreaks.com/store/lcd-key-shield-p-688.html
139 // http://www.komputer.de/zen/index.php?main_page=product_info&products_id=290
140 Rotary r = Rotary(2, 3); // sets the pins the rotary encoder uses.
141 LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // settings for EleyFreaks Key Shield
142
143 int intEncoder; // store Encoder movement
144 int intEncoder_old;
145 float fDisp[4]; // float value for LCD Diplay
146 float fVoltDisp[2]; /// Volt for Display
147 int intVoltM[2]; // Volt measured
148 int intSet[4] = {255, 255, 9, 9}; // Volt set, about 0.5 V, 230 mA
149 float fAmpereDisp[2];
150 float fUR_CONV[2] = {UR_CONV1, UR_CONV2};
151 float fUR_OFST[2] = {UR_OFST1, UR_OFST2};
152 int intAmpereM[2];
153 int result;
154 int intValue; // analog value
155 int intActive = DP_U1; // active value
156 int intJoystick;
157 int intPressed;
158 unsigned long time;
159 long previousMs400 = 0;
160 long interval400 = 400;
161 long previousMs200 = 0;
162 long interval200 = 200;
163 int adc_key_in = 0;
164 char charCH1;
165 char charCH2;
166 String strMessage = String(20);
167 char charMessage[20];
168 int intNumChar;
169 int intNumCharRcv;
170 int intOut = 1; // Output
171 int intSel = 0; // PSU number 0 | 1
172 int intRemote;
173
174 // Instantiate objects used in this project
175 MCP4461 myMCP4461;
176
177 //----- Setup ------------------------
178 void setup() {
179 Serial.begin(9600);
180 Serial.println("LabPSU2R V1.1");
181 pinMode(ENA, OUTPUT);
182 pinMode(A1, INPUT);
183 pinMode(A2, INPUT);
184 pinMode(A3, INPUT);
185 pinMode(A6, INPUT);
186 pinMode(A7, INPUT);
187 pinMode(CL1, INPUT_PULLUP); // Diode
188 pinMode(CL2, INPUT_PULLUP); // Diode
189
190 // set Backlight ON
191 pinMode(10, OUTPUT);
192 digitalWrite(10, 1);
193
194 lcd.begin (16, 2); // display init
195 lcd.setCursor(0, 0); // Char 1, Line 1
196 lcd.print(" LabPSU2R ");
197 lcd.setCursor(0, 1); // Char 1, Line 2
198 lcd.print("DL5FA 2016-10-07");
199 delay(3000);
200 lcd.clear();
201
202 Wire.begin();
203 Serial.println("Wire.begin");
204 TWBR = 12; // 400 kHz (maximum)
205 myMCP4461.begin();
206 Serial.println("myMCP4461.begin");
207 result = myMCP4461.testConnection();
208 Serial.print("I2Cinit: ");
209 Serial.print(result);
210 if (result == 1) Serial.println(" OK");
211 else Serial.println(" Error");
212 result = myMCP4461.setVolatileWiper(DP_U1, intSet[DP_U1]); // U1 min.
213 Serial.print("DP0-U1, result:");
214 if (result == 1) Serial.print(" OK");
215 else Serial.print(" Error");
216 Serial.print(", set: ");
217 Serial.println(intSet[DP_U1]);
218 result = myMCP4461.getVolatileWiper(DP_U1); // U1 min.
219 Serial.print("DP0-U1, get: ");
220 Serial.print(result);
221 if (result == intSet[DP_U1]) Serial.println(" OK");
222 else Serial.println(" Error");
223
224 result = myMCP4461.setVolatileWiper(DP_U2, intSet[DP_U2]); // U2 min.
225 result = myMCP4461.setVolatileWiper(DP_I1, intSet[DP_I1]); // Curren Limit I1
226 result = myMCP4461.setVolatileWiper(DP_I2, intSet[DP_I2]); // Curren Limit I2
227
228 analogReference(DEFAULT); // Reference voltage is +5 V
229
230 // digital Encoder interrupt
231 PCICR |= (1 << PCIE2);
232 PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
233 sei();
234
235 digitalWrite(ENA, 1); // Enable PSU Modules
236
237 pinMode(arduinoLED, OUTPUT); // Configure the onboard LED for output
238 digitalWrite(arduinoLED, LOW); // default to LED off
239
240 // Setup callbacks for SerialCommand commands
241 sCmd.addCommand("ON", LED_on); // Turns LED on
242 sCmd.addCommand("OFF", LED_off); // Turns LED off
243 sCmd.addCommand("HELLO", sayHello); // Echos the string argument back
244 sCmd.addCommand("P", processCommand); // Converts two arguments to integers and echos them back
245 sCmd.addCommand("*IDN?", Ident); // Send Identification string
246 sCmd.addCommand("INST", Instrument); // SCPI Instrument
247 sCmd.addCommand("MEAS", Measure); // SCPI Measure
248 sCmd.addCommand("OUTP", Output); // SCPI Output
249 sCmd.addCommand("SOUR", Source); // SCPI Source
250 sCmd.addCommand("*STB?", Status); // Query Status Byte
251 sCmd.addCommand("SYST", System); // SCPI System
252 sCmd.setDefaultHandler(unrecognized); // Handler for command that isn't matched (says "What?")
253 Serial.println("sCmd Ready");
254 }
255
256 //----- main -------------------------
257 void loop() {
258 // update LCD every 400 ms
259 unsigned long currentMillis = millis();
260
261 //---------- refresh LCD every 400 ms
262 if (currentMillis - previousMs400 > interval400) {
263 previousMs400 = currentMillis;
264 lcd.setCursor(0, 0); // Char 1, Line 1
265 if ((intActive == DP_U1) && (intJoystick == btnEnc))
266 show_EncValue(intSet[intActive]);
267 else {
268 intValue = analogRead(U1);
269 fDisp[DP_U1] = intValue / U_CONV1;
270 if (fDisp[DP_U1] < 10.0)
271 lcd.print("0");
272 lcd.print(fDisp[DP_U1], 1);
273 }
274 lcd.print("V1");
275 charCH1 = ' ';
276 if (intActive == DP_U1) charCH1 = '*';
277 if (digitalRead(CL1) > 0) charCH1 = 'L';
278 lcd.print(charCH1);
279 lcd.print(' ');
280 if ((intActive == DP_U2) && (intJoystick == btnEnc))
281 show_EncValue(intSet[intActive]);
282 else {
283 intValue = analogRead(U2);
284 fDisp[DP_U2] = intValue / U_CONV2;
285 if (fDisp[DP_U2] < 10.0)
286 lcd.print("0");
287 lcd.print(fDisp[DP_U2], 1);
288 }
289 lcd.print("V2");
290 charCH2 = ' ';
291 if (intActive == DP_U2) charCH2 = '*';
292 if (digitalRead(CL2) > 0) charCH2 = 'L';
293 lcd.print(charCH2);
294 if (intRemote > 0) lcd.print('R');
295 else lcd.print(' ');
296 lcd.setCursor(0, 1); // Char 1, Line 2
297 if ( intJoystick == btnLeft) show_CurLimit(intSet[DP_I1]);
298 else {
299 if ((intActive == DP_I1) && (intJoystick == btnEnc))
300 show_EncValue(intSet[intActive]);
301 else {
302 intValue = analogRead(I1);
303 fDisp[DP_I1] = intValue / I_CONV1;
304 lcd.print(fDisp[DP_I1], 2);
305 }
306 }
307 lcd.print("A1");
308 if (intActive == DP_I1) {
309 lcd.print('*');
310 } else lcd.print(' ');
311 lcd.print(' ');
312 if (intJoystick == btnRight) show_CurLimit(intSet[DP_I2]);
313 else {
314 if ((intActive == DP_I2) && (intJoystick == btnEnc))
315 show_EncValue(intSet[intActive]);
316 else {
317 intValue = analogRead(I2);
318 fDisp[DP_I2] = intValue / I_CONV2;
319 lcd.print(fDisp[DP_I2], 2);
320 }
321 }
322 lcd.print("A2");
323 if (intActive == DP_I2) {
324 lcd.print('*');
325 } else lcd.print(' ');
326 if (intJoystick == btnEnc) intRemote = 0; // Manual to Local mode
327 }
328
329 //------ readout joystick every 200 ms
330 if (currentMillis - previousMs200 > interval200) {
331 previousMs200 = currentMillis;
332 intJoystick = read_Joystick();
333 if (intJoystick > btnNone) intPressed = intJoystick;
334 if ((intPressed > btnNone) && (intJoystick == btnNone)) {
335 // button just released
336 switch (intPressed) {
337 case btnUp:
338 break;
339 case btnRight:
340 break;
341 case btnDown:
342 break;
343 case btnCenter:
344 intActive += 1;
345 if (intActive > DP_U2) intActive = DP_U1;
346 break;
347 case btnLeft:
348 break;
349 }
350 intPressed = btnNone; // reset button
351 }
352 }
353
354 // process Encoder for Set values (Voltage, Current) via Interrupt
355 int intActive2; // consider Ampere setting switch
356 if (intEncoder != intEncoder_old) {
357 intEncoder_old = intEncoder;
358 if (intRemote == 0) {
359 intActive2 = intActive;
360 if (intJoystick == btnLeft) intActive2 = DP_I1;
361 if (intJoystick == btnRight) intActive2 = DP_I2;
362 intValue = intSet[intActive2];
363 if (intActive2 < 2)
364 intValue = intValue - intEncoder_old; // Set Voltage
365 else
366 intValue = intValue + intEncoder_old; // Set Ampere
367 if (intValue > 255) intValue = 255;
368 if (intValue < 1) intValue = 1;
369 intSet[intActive2] = intValue;
370 result = myMCP4461.setVolatileWiper(intActive2, intValue); // set poti
371 }
372 intEncoder = 0;
373 // for debug
374 //Serial.print("Encoder, Active, Value : ");
375 //Serial.print(intEncoder_old);
376 //Serial.print(", ");
377 //Serial.print(intActive2);
378 //Serial.print(", ");
379 //Serial.println(intValue);
380 intEncoder_old = 0;
381 }
382
383 sCmd.readSerial(); // We don't do much, just process serial commands
384
385 }
386 //-------- Subroutines -------------
387 void LED_on() {
388 Serial.println("LED on");
389 digitalWrite(arduinoLED, HIGH);
390 }
391 void LED_off() {
392 Serial.println("LED off");
393 digitalWrite(arduinoLED, LOW);
394 }
395 void sayHello() {
396 char *arg;
397 arg = sCmd.next(); // Get the next argument from the SerialCommand object buffer
398 if (arg != NULL) { // As long as it existed, take it
399 Serial.print("Hello ");
400 Serial.println(arg);
401 }
402 else {
403 Serial.println("Hello, whoever you are");
404 }
405 }
406 void processCommand() {
407 int aNumber;
408 char *arg;
409 Serial.println("We're in processCommand");
410 arg = sCmd.next();
411 if (arg != NULL) {
412 aNumber = atoi(arg); // Converts a char string to an integer
413 Serial.print("First argument was: ");
414 Serial.println(aNumber);
415 }
416 else {
417 Serial.println("No arguments");
418 }
419 arg = sCmd.next();
420 if (arg != NULL) {
421 aNumber = atol(arg);
422 Serial.print("Second argument was: ");
423 Serial.println(aNumber);
424 }
425 else {
426 Serial.println("No second argument");
427 }
428 }
429 void Ident() {
430 Serial.println("LabPSU2_2015-04-04_DL5FA");
431 }
432
433 void System () { // Version date of SCPI & Remote
434 char *arg;
435 arg = sCmd.next();
436 strMessage = arg; // needed for "substring()"
437 if (arg != NULL) { // As long as it existed, take it
438 if (strMessage.substring(0, 4) == "VERS") Serial.println("1997.0");
439 if (strMessage.substring(0, 3) == "REM") intRemote = 1;
440 if (strMessage.substring(0, 3) == "LOC") intRemote = 0;
441 }
442 }
443
444 void Source () { // Current or Voltage
445 char *arg;
446 float fValue;
447 int intLen;
448 float fCurLimit;
449 arg = sCmd.next();
450 if (arg != NULL) {
451 strMessage = arg; // needed for "substring()"
452 if (strMessage.substring(0, 4) == "VOLT") {
453 intLen = strMessage.length();
454 if (strMessage.charAt(intLen - 1) == '?') { // Query voltage
455 arg = sCmd.next();
456 if (arg != NULL) {
457 strMessage = arg;
458 if (strMessage.substring(0, 3) == "MIN") Serial.println(Umin);
459 if (strMessage.substring(0, 3) == "MAX") Serial.println(Umax);
460 } else {
461 fValue = (intSet[DP_U1 + intSel] * fUR_CONV[intSel] + fUR_OFST[intSel]) ;
462 Serial.println(fValue);
463 }
464 }
465 else { // set voltage
466 arg = sCmd.next();
467 if (arg != NULL) {
468 strMessage = arg;
469 fValue = strMessage.toFloat();
470 if ((fValue >= Umin) && (fValue <= Umax)) { // check for valid
471 intSet[DP_U1 + intSel] = (fValue - fUR_OFST[intSel]) / fUR_CONV[intSel];
472 result = myMCP4461.setVolatileWiper(DP_U1 + intSel, intSet[DP_U1 + intSel]);
473 }
474 }
475 }
476 } else // current
477 if (strMessage.substring(0, 4) == "CURR") {
478 intLen = strMessage.length();
479 if (strMessage.charAt(intLen - 1) == '?') { // Query current
480 arg = sCmd.next();
481 if (arg != NULL) {
482 strMessage = arg;
483 if (strMessage.substring(0, 3) == "MIN") Serial.println(Imin);
484 if (strMessage.substring(0, 3) == "MAX") Serial.println(Imax);
485 } else {
486 fCurLimit = intSet[intSel + 2] * CL_CONV + CL_OFST;
487 Serial.println(fCurLimit);
488 //Serial.print(' ');
489 //Serial.println(intSet[intSel+2]);
490 }
491 }
492 else { // set current limit
493 arg = sCmd.next();
494 if (arg != NULL) {
495 strMessage = arg;
496 fValue = strMessage.toFloat();
497 if ((fValue >= Imin) && (fValue <= Imax)) { // check for valid
498 intSet[DP_I1 + intSel] = (fValue - CL_OFST) / CL_CONV;
499 result = myMCP4461.setVolatileWiper(DP_I1 + intSel, intSet[DP_I1 + intSel]);
500 }
501 }
502 }
503 }
504 }
505 }
506
507 void Measure () { // Current or Voltage
508 char *arg;
509 arg = sCmd.next();
510 if (arg != NULL) {
511 strMessage = arg; // needed for "substring()"
512 if (strMessage.substring(0, 4) == "VOLT") {
513 Serial.println(fDisp[DP_U1 + intSel]);
514 }
515 if (strMessage.substring(0, 4) == "CURR") {
516 Serial.println(fDisp[DP_I1 + intSel]);
517 }
518 }
519 }
520
521 void Instrument () { // PSU 1 or 2
522 char *arg;
523 int intLen;
524 int intParam;
525 arg = sCmd.next();
526 if (arg != NULL) {
527 strMessage = arg; // needed for "substring()"
528 if (strMessage.substring(0, 4) == "NSEL") {
529 intLen = strMessage.length();
530 if (strMessage.charAt(intLen - 1) == '?') { // Query
531 Serial.println(intSel + 1);
532 } else {
533 arg = sCmd.next();
534 if (arg != NULL) {
535 strMessage = arg;
536 intParam = strMessage.toInt();
537 if ((intParam >= 1) && (intParam <= 2)) { // check for valid
538 intSel = intParam - 1;
539 }
540 }
541 }
542 }
543 }
544 }
545
546 void Status() { // show Current Limit
547 char cStatus = 0x30;
548 if (digitalRead(CL1) > 0) cStatus += 0x01;
549 if (digitalRead(CL2) > 0) cStatus += 0x02;
550 if (intRemote > 0) cStatus += 0x04;
551 Serial.println(cStatus);
552 }
553
554 void Output () { // PSU OFF | ON
555 char *arg;
556 int intLen;
557 arg = sCmd.next();
558 if (arg != NULL) {
559 strMessage = arg; // needed for "substring()"
560 if (strMessage.substring(0, 4) == "STAT") {
561 intLen = strMessage.length();
562 if (strMessage.charAt(intLen - 1) == '?') { // Query
563 if (intOut == 0) Serial.println("OFF");
564 else Serial.println("ON");
565 } else {
566 arg = sCmd.next();
567 if (arg != NULL) {
568 strMessage = arg;
569 if (strMessage.substring(0, 3) == "OFF") {
570 //digitalWrite(arduinoLED, HIGH);
571 //delay(500);
572 //digitalWrite(arduinoLED, LOW);
573 intOut = 0;
574 digitalWrite(ENA, 0); // PSU Modules -> OFF
575 } else {
576 intOut = 1;
577 digitalWrite(ENA, 1); // PSU Modules -> ON
578 }
579 }
580 }
581 }
582 }
583 }
584
585 // This gets set as the default handler, and gets called when no other command matches.
586 void unrecognized(const char *command) {
587 Serial.println("What?");
588 }
589
590 //------- show encoder value (4 places) instead of analog value
591 void show_EncValue(int intEncValue) {
592 // intEncoder: 1..255
593 if (intEncValue < 100)
594 lcd.print("0");
595 if (intEncValue < 10)
596 lcd.print("0");
597 lcd.print(intEncValue);
598 lcd.print(' ');
599 }
600
601 //------ show current limit value in A, instead of analog value
602 void show_CurLimit(int intCurLimit) {
603 // intCurLimit: 1..255
604 float fCurLimit;
605 fCurLimit = intCurLimit * CL_CONV + CL_OFST;
606 lcd.print(fCurLimit, 2);
607 }
608
609 //------ read the joystick
610 int read_Joystick() {
611 adc_key_in = analogRead(7); // read the value from the sensor
612 // 4.95 V = 1023
613 // Serial.println(adc_key_in);
614
615 if (adc_key_in > 1000) return btnNone; // 1000 = 4,84 V
616 if (adc_key_in < 50) return btnLeft; // 50 = 0.24 V (0.01 V)
617 if (adc_key_in < 150) return btnUp; // 150 = 0,73 V (0.35 V)
618 if (adc_key_in < 250) return btnRight; // 250 = 1,21 V (1,05 V)
619 if (adc_key_in < 450) return btnCenter;// 450 = 2,18 V (2,00 V)
620 if (adc_key_in < 700) return btnDown; // 700 = 3,39 V (3,18 V)
621 if (adc_key_in < 903) return btnEnc; // 903 = 4,37 V (3,90 V)
622 return btnNone; // when all others fail, return this...
623 }
624
625 //----- digital Encoder Interrupt Service Routine
626 ISR(PCINT2_vect) {
627 unsigned char result = r.process();
628 if (result) {
629 if (result == DIR_CW) {
630 intEncoder = intEncoder + 1;
631 }
632 else {
633 intEncoder = intEncoder - 1;
634 };
635 }
636 }
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.