f51534c549de12e283168d2c88f583daf837518b
[digitaldcpower] / main.c
1 /*********************************************
2 * vim: set sw=8 ts=8 si et :
3 * Author: Guido Socher, Copyright: GPL v3
4 * This is the main program for the digital dc power supply
5
6 * See http://www.tuxgraphics.org/electronics/
7
8 * Chip type           : ATMEGA8
9 * Clock frequency     : Internal clock 8 Mhz 
10 *********************************************/
11 #include <avr/io.h>
12 #include <inttypes.h>
13 #include <avr/interrupt.h>
14 #define F_CPU 8000000UL  // 8 MHz
15 #include <util/delay.h>
16 #include <stdlib.h> 
17 #include <string.h> 
18 #include <avr/eeprom.h> 
19 #include "lcd.h"
20 #include "dac.h"
21 #include "kbd.h"
22 #include "uart.h"
23 #include "analog.h"
24 #include "hardware_settings.h"
25
26 // change this version string when you compile:
27 #define SWVERSION "ver: ddcp-0.6.4"
28 //#define DEBUGDISP 1
29
30 //debug LED:
31 // set output to VCC, red LED off
32 #define LEDOFF PORTD|=(1<<PORTD0)
33 // set output to GND, red LED on
34 #define LEDON PORTD&=~(1<<PORTD0)
35 // to test the state of the LED
36 #define LEDISOFF PORTD&(1<<PORTD0)
37 //
38 // the units are display units and work as follows: 100mA=10 5V=50
39 // The function int_to_dispstr is used to convert the intenal values
40 // into strings for the display
41 static int16_t measured_val[2]={0,0};
42 static int16_t set_val[2];
43 // the set values but converted to ADC steps
44 static int16_t set_val_adcUnits[2]; 
45 static uint8_t bpress=0;
46 // comment this out to use a debug LED on PD0 (RXD):
47 #define USE_UART 1
48 //
49 #ifdef USE_UART
50 #define UARTSTRLEN 10
51 static char uartstr[UARTSTRLEN+1];
52 static uint8_t uartstrpos=0;
53 static uint8_t uart_has_one_line=0;
54 #endif
55
56 void delay_ms_uartcheck(uint8_t ms)
57 // delay for a minimum of <ms> 
58 {
59         uint8_t innerloop=1;
60         char c;
61         while(ms){
62 #ifdef USE_UART
63                 if(uart_has_one_line==0 && uart_getchar_isr_noblock(&c)){
64                         if (c=='\n') c='\r'; // Make unix scripting easier. A terminal, even under unix, does not send \n
65                         // ignore any white space and characters we do not use:
66                         if (!(c=='\b'||(c>='0'&&c<='z')||c==0x7f||c=='\r')){
67                                 goto NEXTCHAR;
68                         }
69                         if (c=='\r'){
70                                 uartstr[uartstrpos]='\0';
71                                 uart_sendchar('\r'); // the echo line end
72                                 uart_sendchar('\n'); // the echo line end
73                                 uart_has_one_line=1;
74                                 goto NEXTCHAR;
75                         }
76                         /*
77                                 // debug
78                                 itoa(c,buf,10);
79                                 uart_sendchar('\r'); // the echo line end
80                                 uart_sendchar('\n'); // the echo line end
81                                 uart_sendchar('|');uart_sendstr(buf);uart_sendchar('|');
82                                 uart_sendchar('\r'); // the echo line end
83                                 uart_sendchar('\n'); // the echo line end
84                                 */
85                         if (c=='\b'){ // backspace
86                                 if (uartstrpos>0){
87                                         uartstrpos--;
88                                         uart_sendchar(c); // echo back
89                                         uart_sendchar(' '); // clear char on screen
90                                         uart_sendchar('\b');
91                                 }
92                         }else if (c==0x7f){ // del
93                                 if (uartstrpos>0){
94                                         uartstrpos--;
95                                         uart_sendchar(c); // echo back
96                                 }
97                         }else{
98                                 uart_sendchar(c); // echo back
99                                 uartstr[uartstrpos]=c;
100                                 uartstrpos++;
101                         }
102                         if (uartstrpos>UARTSTRLEN){
103                                 uart_sendstr_P("\r\nERROR\r\n");
104                                 uartstrpos=0; // empty buffer
105                                 uartstr[0]='\0'; // just print prompt
106                                 uart_has_one_line=1; 
107                         }
108                 }
109 #endif
110 NEXTCHAR:
111                 innerloop--;
112                 if (innerloop==0){
113                         innerloop=45;
114                         ms--;
115                 }
116         }
117 }
118
119 // Convert an integer which is representing a float into a string.
120 // Our display is always 4 digits long (including one
121 // decimal point position). decimalpoint_pos defines
122 // after how many positions from the right we set the decimal point.
123 // The resulting string is fixed width and padded with leading space.
124 //
125 // decimalpoint_pos=2 sets the decimal point after 2 pos from the right: 
126 // e.g 74 becomes "0.74"
127 // The integer should not be larger than 999.
128 // The integer must be a positive number.
129 // decimalpoint_pos can be 0, 1 or 2
130 static void int_to_dispstr(uint16_t inum,char *outbuf,int8_t decimalpoint_pos){
131         int8_t i,j;
132         char chbuf[8];
133         itoa(inum,chbuf,10); // convert integer to string
134         i=strlen(chbuf);
135         if (i>3) i=3; //overflow protection
136         strcpy(outbuf,"   0"); //decimalpoint_pos==0
137         if (decimalpoint_pos==1) strcpy(outbuf," 0.0");
138         if (decimalpoint_pos==2) strcpy(outbuf,"0.00");
139         j=4;
140         while(i){
141                 outbuf[j-1]=chbuf[i-1];
142                 i--;
143                 j--;
144                 if (j==4-decimalpoint_pos){
145                         // jump over the pre-set dot
146                         j--;
147                 }
148         }
149 }
150
151 // convert voltage values to adc values, disp=10 is 1.0V
152 // ADC for voltage is 11bit:
153 static int16_t disp_u_to_adc(int16_t disp){
154         return((int16_t)(((float)disp * 204.7) / (ADC_REF * U_DIVIDER )));
155 }
156 // calculate the needed adc offset for voltage drop on the
157 // current measurement shunt (the shunt has about 0.75 Ohm =1/1.33 Ohm)
158 // use 1/1.2 instead of 1/1.3 because cables and connectors have as well
159 // a loss.
160 static int16_t disp_i_to_u_adc_offset(int16_t disp){
161         return(disp_u_to_adc(disp/12));
162 }
163 // convert adc values to voltage values, disp=10 is 1.0V
164 // disp_i_val is needed to calculate the offset for the voltage drop over
165 // the current measurement shunt, voltage measurement is 11bit
166 static int16_t adc_u_to_disp(int16_t adcunits,int16_t disp_i_val){
167         int16_t adcdrop;
168         adcdrop=disp_i_to_u_adc_offset(disp_i_val);
169         if (adcunits < adcdrop){
170                 return(0);
171         }
172         adcunits=adcunits-adcdrop;
173         return((int16_t)((((float)adcunits /204.7)* ADC_REF * U_DIVIDER)+0.5));
174 }
175 // convert adc values to current values, disp=10 needed to be printed
176 // by the printing function as 0.10 A, current measurement is 10bit
177 static int16_t disp_i_to_adc(int16_t disp){
178         return((int16_t) (((disp * 10.23)* I_RESISTOR) / ADC_REF));
179 }
180 // convert adc values to current values, disp=10 needed to be printed
181 // by the printing function as 0.10 A, current measurement is 10bit
182 static int16_t adc_i_to_disp(int16_t adcunits){
183         return((int16_t) (((float)adcunits* ADC_REF)/(10.23 * I_RESISTOR)+0.5));
184 }
185
186 static void store_permanent(void){
187         int16_t tmp;
188         uint8_t changeflag=1;
189         lcd_clrscr();
190         if (eeprom_read_byte((uint8_t *)0x0) == 19){
191                 changeflag=0;
192                 // ok magic number matches accept values
193                 tmp=eeprom_read_word((uint16_t *)0x04);
194                 if (tmp != set_val[1]){
195                         changeflag=1;
196                 }
197                 tmp=eeprom_read_word((uint16_t *)0x02);
198                 if (tmp != set_val[0]){
199                         changeflag=1;
200                 }
201         }
202         delay_ms_uartcheck(1); // check for uart without delay
203         if (changeflag){
204                 lcd_puts_P("setting stored");
205                 eeprom_write_byte((uint8_t *)0x0,19); // magic number
206                 eeprom_write_word((uint16_t *)0x02,set_val[0]);
207                 eeprom_write_word((uint16_t *)0x04,set_val[1]);
208         }else{
209                 if (bpress> 2){
210                         // display software version after long press
211                         lcd_puts_P(SWVERSION);
212                         lcd_gotoxy(0,1);
213                         lcd_puts_P("tuxgraphics.org");
214                 }else{
215                         lcd_puts_P("already stored");
216                 }
217         }
218         delay_ms_uartcheck(200);
219         delay_ms_uartcheck(200);
220         delay_ms_uartcheck(200);
221 }
222
223 // check the keyboard
224 static uint8_t check_buttons(void){
225         uint8_t uartprint_ok=0;
226         uint8_t cmdok=0;
227 #ifdef USE_UART
228         char buf[21];
229 #endif
230         //
231 #ifdef USE_UART
232         if (uart_has_one_line){
233                         if (uartstr[0]=='i' && uartstr[1]=='=' && uartstr[2]!='\0'){
234                                 set_val[0]=atoi(&uartstr[2]);
235                                 if(set_val[0]>I_MAX){
236                                         set_val[0]=I_MAX;
237                                 }
238                                 if(set_val[0]<0){
239                                         set_val[0]=0;
240                                 }
241                                 uartprint_ok=1;
242                         }
243                         // version
244                         if (uartstr[0]=='v' && uartstr[1]=='e'){
245                                 uart_sendstr_p(P("  "));
246                                 uart_sendstr_p(P(SWVERSION));
247                                 uart_sendstr_p(P("\r\n"));
248                                 cmdok=1;
249                         }
250                         // store
251                         if (uartstr[0]=='s' && uartstr[1]=='t'){
252                                 store_permanent();
253                                 uartprint_ok=1;
254                         }
255                         if (uartstr[0]=='u' && uartstr[1]=='=' && uartstr[2]!='\0'){
256                                 set_val[1]=atoi(&uartstr[2]);
257                                 if(set_val[1]>U_MAX){
258                                         set_val[1]=U_MAX;
259                                 }
260                                 if(set_val[1]<0){
261                                         set_val[1]=0;
262                                 }
263                                 uartprint_ok=1;
264                         }
265                         // help
266                         if (uartstr[0]=='h' || uartstr[0]=='H'){
267                                 uart_sendstr_p(P("  Usage: u=V*10|i=mA/10|store|help|version\r\n"));
268                                 uart_sendstr_p(P("  Examples:\r\n"));
269                                 uart_sendstr_p(P("  set 6V: u=60\r\n"));
270                                 uart_sendstr_p(P("  max 200mA: i=20\r\n"));
271                                 cmdok=1;
272                         }
273                         if (uartprint_ok){
274                                 cmdok=1;
275                                 uart_sendstr_p(P("  ok\r\n"));
276                         }
277                         if (uartstr[0]!='\0' && cmdok==0){
278                                 uart_sendstr_p(P("  command unknown\r\n"));
279                         }
280                         uart_sendchar('#'); // marking char for script interface
281                         int_to_dispstr(measured_val[1],buf,1);
282                         uart_sendstr(buf);
283                         uart_sendchar('V');
284                         uart_sendchar(' ');
285                         uart_sendchar('[');
286                         int_to_dispstr(set_val[1],buf,1);
287                         uart_sendstr(buf);
288                         uart_sendchar(']');
289                         uart_sendchar(',');
290                         int_to_dispstr(measured_val[0],buf,2);
291                         uart_sendstr(buf);
292                         uart_sendchar('A');
293                         uart_sendchar(' ');
294                         uart_sendchar('[');
295                         int_to_dispstr(set_val[0],buf,2);
296                         uart_sendstr(buf);
297                         uart_sendchar(']');
298                         if (is_current_limit()){
299                                 uart_sendchar('I');
300                         }else{
301                                 uart_sendchar('U');
302                         }
303                         uart_sendchar('>');
304                 uartstrpos=0;
305                 uart_has_one_line=0;
306         }
307 #endif
308         if (check_u_button(&(set_val[1]))){
309                 if(set_val[1]>U_MAX){
310                         set_val[1]=U_MAX;
311                 }
312                 return(1);
313         }
314         if (check_i_button(&(set_val[0]))){
315                 if(set_val[0]>I_MAX){
316                         set_val[0]=I_MAX;
317                 }
318                 return(1);
319         }
320         if (check_store_button()){
321                 store_permanent();
322                 return(2);
323         };
324         return(0);
325 }
326
327 int main(void)
328 {
329         char out_buf[21];
330         uint8_t i=0;
331         uint8_t ilimit=0;
332
333 #ifndef USE_UART
334         // debug led, you can not have an LED if you use the uart
335         DDRD|= (1<<DDD0); // LED, enable PD0, LED as output
336         LEDOFF;
337 #endif
338
339         init_dac();
340         lcd_init();
341         init_kbd();
342         set_val[0]=15;set_val[1]=50; // 150mA and 5V
343         if (eeprom_read_byte((uint8_t *)0x0) == 19){
344                 // ok magic number matches accept values
345                 set_val[1]=eeprom_read_word((uint16_t *)0x04);
346                 set_val[0]=eeprom_read_word((uint16_t *)0x02);
347                 // sanity check:
348                 if (set_val[0]<0) set_val[0]=0;
349                 if (set_val[1]<0) set_val[1]=0;
350         }
351 #ifdef USE_UART
352         uart_init();
353 #endif
354         sei();
355         init_analog();
356         while (1) {
357                 i++;
358                 // due to electrical interference we can get some
359                 // garbage onto the display especially if the power supply
360                 // source is not stable enough. We can remedy it a bit in
361                 // software with an ocasional reset:
362                 if (i==50){ // not every round to avoid flicker
363                         lcd_reset();
364                         i=0;
365                 }
366                 lcd_home();
367                 // current
368                 measured_val[0]=adc_i_to_disp(getanalogresult(0));
369                 set_val_adcUnits[0]=disp_i_to_adc(set_val[0]);
370                 set_target_adc_val(0,set_val_adcUnits[0]);
371                 // voltage
372                 measured_val[1]=adc_u_to_disp(getanalogresult(1),measured_val[0]);
373                 set_val_adcUnits[1]=disp_u_to_adc(set_val[1])+disp_i_to_u_adc_offset(measured_val[0]);
374                 set_target_adc_val(1,set_val_adcUnits[1]);
375                 ilimit=is_current_limit();
376
377                         
378                 // voltage
379 #ifdef DEBUGDISP
380                 itoa(getanalogresult(1),out_buf,10);
381 #else
382                 int_to_dispstr(measured_val[1],out_buf,1);
383 #endif
384                 lcd_puts(out_buf);
385                 lcd_puts("V [");
386 #ifdef DEBUGDISP
387                 itoa(set_val_adcUnits[1],out_buf,10);
388 #else
389                 int_to_dispstr(set_val[1],out_buf,1);
390 #endif
391                 lcd_puts(out_buf);
392                 lcd_putc(']');
393                 delay_ms_uartcheck(1); // check for uart without delay
394                 if (!ilimit){
395                         // put a marker to show which value is currenlty limiting
396                         lcd_puts("<- ");
397                 }else{
398                         lcd_puts("   ");
399                 }
400
401                 // current
402                 lcd_gotoxy(0,1);
403 #ifdef DEBUGDISP
404                 itoa(getanalogresult(0),out_buf,10);
405 #else
406                 int_to_dispstr(measured_val[0],out_buf,2);
407 #endif
408                 lcd_puts(out_buf);
409                 lcd_puts("A [");
410 #ifdef DEBUGDISP
411                 itoa(set_val_adcUnits[0],out_buf,10);
412 #else
413                 int_to_dispstr(set_val[0],out_buf,2);
414 #endif
415                 lcd_puts(out_buf);
416                 lcd_putc(']');
417                 if (ilimit){
418                         // put a marker to show which value is currenlty limiting
419                         lcd_puts("<- ");
420                 }else{
421                         lcd_puts("   ");
422                 }
423
424                 // the buttons must be responsive but they must not 
425                 // scroll too fast if pressed permanently
426                 if (check_buttons()==0){
427                         // no buttons pressed
428                         delay_ms_uartcheck(80);
429                         bpress=0;
430                         if (check_buttons()==0){
431                                 // no buttons pressed
432                                 delay_ms_uartcheck(80);
433                         }else{
434                                 bpress++;
435                                 delay_ms_uartcheck(180);
436                                 delay_ms_uartcheck(180);
437                                 delay_ms_uartcheck(180);
438                                 delay_ms_uartcheck(180);
439                         }
440                 }else{
441                         // button press
442                         if (bpress > 10){
443                                 // somebody pressed permanetly the button=>scroll fast
444                                 delay_ms_uartcheck(120);
445                         }else{
446                                 bpress++;
447                                 delay_ms_uartcheck(180);
448                                 delay_ms_uartcheck(180);
449                                 delay_ms_uartcheck(180);
450                                 delay_ms_uartcheck(180);
451                         }
452                 }
453         }
454         return(0);
455 }
456