/* vim: set sw=8 ts=8 si : */ /************************************************************************* Title: linuxfocus frequency counter Author: guido socher <guido[at]linuxfocus.org> Copyright: GPL **************************************************************************/ #include <io.h> #include <progmem.h> #include <interrupt.h> #include <string-avr.h> #include <stdlib.h> #include <sig-avr.h> #include "lcd.h" /* these are the prototypes and defines for lcd.c */ #include "avr-util.h" #include "uart.h" /* output line to scan for button up/down */ #define BUTTONDOWN PD5 #define BUTTONDOWNDDR DDRD #define BUTTONDOWNPORT PORTD #define BUTTONUP PD6 #define BUTTONUPDDR DDRD #define BUTTONUPPORT PORTD /* Input Push Buttons */ #define SBBUTTON PIND7 #define SBBUTTONDDR DDRD #define SBBUTTONPIN PIND #define SBBUTTONPORT PORTD #define I_BUTTON PINC4 #define I_BUTTONDDR DDRC #define I_BUTTONPIN PINC #define I_BUTTONPORT PORTC #define U_BUTTON PINB3 #define U_BUTTONDDR DDRB #define U_BUTTONPIN PINB #define U_BUTTONPORT PORTB #define UUBUTTON PINB0 #define UUBUTTONDDR DDRB #define UUBUTTONPIN PINB #define UUBUTTONPORT PORTB /* current limit input and led */ #define ILIMIT PIND3 #define ILIMITDDR DDRD #define ILIMITPIN PIND #define ILED PC5 #define ILEDDDR DDRC #define ILEDPORT PORTC /* PWMOUT0= current control, software pwm */ #define PWMOUT0 PB2 #define PWMOUT0DDR DDRB #define PWMOUT0PORT PORTB /* PWMOUT1= voltage control, internal hardware pwm */ #define PWMOUT1 PB1 #define PWMOUT1DDR DDRB #define PWMOUT1PORT PORTB static char uart_outbuf[UART_RX_BUFFER_SIZE+1]; /* max 2.2A output, can be up to 3A (3000), * change, depends on the transformer you have */ #define MAX_I 2200 /* minial stepping for I, set to 50 for MAX_I=3000 * otherwise set to 25, change this dependent on hardware */ #define IMINSTEP 25 static unsigned char ipwm_h; /* time that PWM for I is high */ static unsigned char ipwm_phase; /* flag indicating whether we are now producing a 1 or a zero */ static int ival; /* 16 bit, the current in mA */ /* max 16V output, change dependent on the transformer you have */ #define MAX_U 160 static int uval; /* 16 bit, the Voltage in 0.1 V units, eg. 50=5V */ static unsigned char mode; /* 0=normal operation, 1=I-limit, 2=standby */ static unsigned char oldmode; /* 0=normal operation, 1=I-limit, 2=standby */ static unsigned char tmpval[3]; /* 24 bit, needed for exact math in set_u */ static unsigned char tmpval2[3]; /* 24 bit, needed for exact math in set_u */ #define reply_ok() uart_sendstr_P("ok\n") #define reply_err() uart_sendstr_P("err\n") /* there is only support for 16bit math operations in libc. * to get more accurate math we have to write our own functions */ /* *x is 24 bit, result is in x (x=in/out value), y *256+256 must be < 32768*/ unsigned char divXbyY(unsigned char *x,unsigned char y){ unsigned char loop,rest; unsigned int tmp; // 16 bit loop=3; /* start with highest byte */ rest=0; while(loop>0){ tmp=rest * 256 + x[loop-1]; x[loop-1]=tmp / y; rest=tmp % y; loop--; } return(rest); } /* *x is 24 bit, result is in x (x=in/out value), the bigger * number must go into x, 8bit of x times y must never go over 32768 */ void multiXbyY(unsigned char *x,int y){ unsigned char loop; unsigned int tmp,over; // 16 bit loop=0; over=0; while(loop<3){ tmp= x[loop] * y + over; over=tmp/256; x[loop]=tmp - (over*256); loop++; } } /* convert a 2 time 8 bit interger (16 bit=i[0] and i[1], i[2] is set to zero, * i is 24bit but only 16 bit are used) to decimal ascii and * print it using the printfun. Padd all the digits to 4 (but we can handle full * 16 bit if needed) * If addcomma is set then the number will be printed by 1/10. That is * 35 will be " 3.5" and 3 will be " 0.3" */ /* note: this function modifies *i !! */ void longtoascii(void (*printfun)(char),unsigned char *i,unsigned char addcomma) { char str[7]; unsigned char pos=7; unsigned char j=0; i[2]=0; do{ str[pos-1]=divXbyY(i,10); str[pos-1]+= '0'; pos--; j++; /* write a "," after the last digit from the right */ if (j==1 && addcomma==1){ str[pos-1]=','; pos--; } }while((i[0]|i[1]) && pos > 0); if (str[pos] == ',' && pos > 0){ /* add the missing zero for 3 -> 0.3 */ str[pos-1]='0'; pos--; } /* add spaces to padd up to 4 digits */ j=pos-3; while(j>0){ (*printfun)(' '); j--; } /* now reverse the string and print */ while(pos<7){ (*printfun)(str[pos]); pos++; } } /* this function will be called in if the 8bit timer goes from ff to 00 */ SIGNAL(SIG_OVERFLOW0) { /* stop 8bit timer */ outp(0,TCCR0); if (ipwm_phase==1 && ipwm_h !=0){ /* now we will produce a "1" at the output */ /* time that we should remain "1" is ipwm_h */ outp(0xFF-ipwm_h,TCNT0); /* set start value */ sbi(PWMOUT0PORT,PWMOUT0); /* 1 */ ipwm_phase=0; }else{ /* now we will produce a "0" at the output */ /* time that we should remain "1" is ipwm_h */ outp(ipwm_h,TCNT0); /* set start value */ cbi(PWMOUT0PORT,PWMOUT0); /* 0 */ ipwm_phase=1; } /* start 8bit timer with 62500Hz, clock/64 */ outp(3,TCCR0); } /* set current limit in a non linear way, 0=up, 1=down */ unsigned char step_i(unsigned char direction) { if (ival<200){ /* step in 50mA (or 25) units and round to the next 50 (or 25)*/ ival=ival/IMINSTEP; /* round off values set via rs232 */ ival=ival*IMINSTEP; if (direction){ ival-=IMINSTEP; }else{ ival+=IMINSTEP; } return(0); } if (ival<1000){ /* step in 100mA units and round to the next 100 */ ival=ival/100; /* round off values set via rs232 */ ival=ival*100; if (direction){ ival-=100; }else{ ival+=100; } return(0); } /* more than 1A */ /* step in 200mA units and round to the next 200 */ ival=ival/200; /* round off values set via rs232 */ ival=ival*200; if (direction){ ival-=200; }else{ ival+=200; } return(0); } /* set the current limit , reads ival and writes ipwm_h */ void set_i(void) { int a; if (ival>MAX_I){ ival=MAX_I; } if (ival<50){ /* at least 50 mA, with 8bit this is the lower limit */ ival=50; } if (ival<140){ /* change this to calibrate */ /* y=(a/1000) *x */ //a=65; /* use this if R38=33K */ a=94; /* use this if R38=120K */ }else if (ival<500){ /* change this to calibrate */ /* y=(a/1000) *x */ //a=75; /* use this if R38=33K */ a=107; /* use this if R38=120K */ }else{ /* change this to calibrate */ /* y=(a/1000) *x */ //a=82; /* use this if R38=33K */ a=111; /* use this if R38=120K */ } tmpval[0]=ival & 0xff; tmpval[1]=(ival >> 8); tmpval[2]=0; multiXbyY(tmpval,a); divXbyY(tmpval,100); divXbyY(tmpval,10); ipwm_h=tmpval[0]; /* debug, activate to calibarate */ //longtoascii(uart_sendchar,tmpval,0); //uart_sendchar(' '); } /* set the voltage , reads uval and writes OCR1 */ void set_u(void) { int a; unsigned char c,b; if (uval>MAX_U){ uval=MAX_U; } if (uval<0){ uval=0; } /* below 1V the accuracy is limited because the values are * too much quantisized (the digital resolution gets smaller) * If you draw a curve you find that it is 99% linear */ /* remove the programming cable if you calibrate. It influences * the output values slightly */ if (uval<5){ /* change this to calibrate */ a=250; /* y=(a/(b*c)*x */ b=10; c=10; }else if (uval<25){ /* change this to calibrate */ a=305; /* y=(a/(b*c)*x */ b=10; c=10; /*--- end calibrate ---*/ }else if (uval<120){ /* change this to calibrate */ a=793; /* y=(a/(b*c)*x */ b=10; c=25; /*--- end calibrate ---*/ }else{ /* change this to calibrate */ a=637; /* y=(a/(b*c)*x */ b=20; c=10; /*--- end calibrate ---*/ } /* 24bit math for better accuraty */ tmpval[0]=a & 0xff; tmpval[1]=(a >> 8) & 0xff; tmpval[2]=0; multiXbyY(tmpval,uval); /* uval is smaller than tmpval */ divXbyY(tmpval,b); divXbyY(tmpval,c); if (mode!=2){ /* do not mess up standby state */ outp( tmpval[1],OCR1H); /* set pwm high time*/ outp( tmpval[0],OCR1L); /* set pwm high time*/ } /* debug, activate to calibarate */ //longtoascii(uart_sendchar,tmpval,0); //uart_sendchar(' '); } void toggle_standby(void){ if (mode == 2){ /* set U to zero in standby but do not modify * the displayed value */ outp( 0,OCR1H); /* set pwm high time*/ outp( 0,OCR1L); /* set pwm high time*/ ipwm_h=0; /* current = 0 */ } if (mode ==0){ /* activate voltage output again */ sbi(ILEDPORT,ILED); /* red led off */ set_i(); set_u(); } } /* update rs232*/ void updaters232(void) { uart_sendstr_P("u:"); tmpval[0]=uval & 0xff; tmpval[1]=(uval >> 8); longtoascii(uart_sendchar,tmpval,0); if (mode==2){ uart_sendstr_P(" s:1 i:"); }else{ uart_sendstr_P(" s:0 i:"); } tmpval[0]=ival & 0xff; tmpval[1]=(ival >> 8); longtoascii(uart_sendchar,tmpval,0); if (mode==1){ uart_sendstr_P(" l:1\n"); }else{ uart_sendstr_P(" l:0\n"); } } /* update display */ void updatedisplay(void) { lcd_clrscr(); lcd_puts_P("u: "); tmpval[0]=uval & 0xff; tmpval[1]=(uval >> 8); longtoascii(lcd_putc,tmpval,1); lcd_puts_P(" V "); if (mode==0){ lcd_puts_P("<-"); } if (mode==2){ lcd_puts_P("standby"); } lcd_gotoxy(0,1); lcd_puts_P("i: "); tmpval[0]=ival & 0xff; tmpval[1]=(ival >> 8); longtoascii(lcd_putc,tmpval,0); lcd_puts_P(" mA "); if (mode==1){ lcd_puts_P("<-"); } } int main(void) { unsigned char i,rxresult,status,j,ilimitdebounce; unsigned int ignorebutton; unsigned int blink; /* blink in standby mode */ char cmd; char *val; /* initialize display, cursor off */ lcd_init(LCD_DISP_ON); /* current limit detection as input line */ cbi(ILIMITDDR,ILIMIT); /* red current limit LED as output */ sbi(ILEDDDR,ILED); sbi(ILEDPORT,ILED); /* initialize rs232 */ uart_init(); /* initialize PWM output 0 (current control) as output */ sbi(PWMOUT0DDR,PWMOUT0); /* setup the 8bit timer for current control */ sbi(TIMSK,TOIE0); /* enable T0 overflow0 interrupt */ outp(0,TCNT0); /* start value */ /* start 8bit timer with 62500Hz, clock/64 overflow0 every 64 */ outp(3,TCCR0); ipwm_phase=0; /* initialize */ ival=100; /* initialize default current */ /* initialize PWM output 1 (voltage control) as output */ sbi(PWMOUT1DDR,PWMOUT1); /* setup the 16bit timer for voltage control, in the * 4433 this timer supports hardware pwm therefore we * do not have to implement it in software */ outp(0x83,TCCR1A); /* enable 10bit pwm and clear on compare match */ outp(0x2,TCCR1B); /* run at 1/8 of crystal freq */ /* write high byte first */ outp(0x0,TCNT1H); /* set counter to zero*/ outp(0x0,TCNT1L); /* set counter to zero*/ uval=50; /* initialize default voltage, 50=5.0V */ mode=0; oldmode=mode; ignorebutton=0; ilimitdebounce=0; blink=0; /* button as digital input */ cbi(I_BUTTONDDR,I_BUTTON); cbi(U_BUTTONDDR,U_BUTTON); cbi(UUBUTTONDDR,UUBUTTON); cbi(SBBUTTONDDR,SBBUTTON); /* pull up resistor on */ sbi(I_BUTTONPORT,I_BUTTON); sbi(U_BUTTONPORT,U_BUTTON); sbi(UUBUTTONPORT,UUBUTTON); sbi(SBBUTTONPORT,SBBUTTON); /* the buttons are multiplexed, enable output lines */ sbi(BUTTONDOWNDDR,BUTTONDOWN); sbi(BUTTONUPDDR,BUTTONUP); sei(); /* enable interrupt */ /* now we can use uart/lcd display */ set_i(); set_u(); updatedisplay(); while(1){ /* first check up (++) buttons */ sbi(BUTTONDOWNPORT,BUTTONDOWN); cbi(BUTTONUPPORT,BUTTONUP); j=0; /* ignorebutton is needed for debounce */ while(j<2 && ignorebutton==0){ if (bit_is_clear(I_BUTTONPIN,I_BUTTON)){ step_i(j); set_i(); updatedisplay(); updaters232(); ignorebutton=15000; } if (bit_is_clear(U_BUTTONPIN,U_BUTTON)){ if (j==0){ uval+=1; }else{ uval-=1; } set_u(); updatedisplay(); updaters232(); ignorebutton=15000; } if (bit_is_clear(UUBUTTONPIN,UUBUTTON)){ if (j==0){ uval+=10; }else{ uval-=10; } set_u(); updatedisplay(); updaters232(); ignorebutton=15000; } if (bit_is_clear(SBBUTTONPIN,SBBUTTON)){ if (mode != 2){ mode=2; }else{ mode=0; /* if i-limit is active then this will change below */ } toggle_standby(); ignorebutton=20000; } /* now check down (--) buttons */ cbi(BUTTONDOWNPORT,BUTTONDOWN); sbi(BUTTONUPPORT,BUTTONUP); j++; } if (ignorebutton) ignorebutton--; if (ilimitdebounce) ilimitdebounce--; if (mode!=2){ /* not standby */ if (bit_is_clear(ILIMITPIN,ILIMIT)){ mode=1; ilimitdebounce=200; /* red led on */ cbi(ILEDPORT,ILED); }else{ if(mode==1 && ilimitdebounce==0){ mode=0; /* red led off */ sbi(ILEDPORT,ILED); } } } if (mode!=oldmode){ updatedisplay(); updaters232(); } oldmode=mode; /* flash red led in standby */ if (mode==2){ if (blink > 15000){ blink=0; } if (blink <2000){ cbi(ILEDPORT,ILED); /* red led on */ }else{ sbi(ILEDPORT,ILED); /* red led off */ } blink++; } rxresult=uart_getrxbufnl(uart_outbuf); if (rxresult==0){ continue; } /* valid command are: * i=0-3000 u=0-300 s=0-1 s=? u=? i=? */ /* now parse the comands and act on them */ if (strlen(uart_outbuf) < 3) { reply_err(); continue; } if (uart_outbuf[1] != '='){ reply_err(); continue; } val=&(uart_outbuf[2]); cmd=uart_outbuf[0]; status=0; /* act on the commands */ if (cmd=='i'){ if (*val== '?'){ updaters232(); continue; } ival=(int)atoi(val); set_i(); updatedisplay(); status=1; } /* act on the commands */ if (cmd=='u'){ if (*val== '?'){ updaters232(); continue; } uval=(int)atoi(val); set_u(); updatedisplay(); status=1; } if (cmd=='s'){ i=*val; if (i == '?'){ updaters232(); continue; } if (i == '1'){ mode=2; status=1; } if (i == '0'){ mode=0; /* if i-limit is active then this will change in the next loop */ status=1; } toggle_standby(); } /* command handling done. now return status */ if (status==1){ reply_ok(); }else{ reply_err(); } } }