USB ISP(ICSP) Open Programmer < PWM ADC HV PID >
http://sourceforge.net/projects/openprogrammer/?source=navbar
Open Programmer
http://openprog.altervista.org/OP_eng.html#Quick
Open Programmer v0.8.x
Quick facts
- Completely free and Open Source (including firmware)
- Programs PIC10-12-16-18-24, dsPIC30-33,
EEPROMs type 24xxxx (I2C), 25xxx (SPI), 93xx6 (MicroWire),
DS24xx (OneWire), 11xxx (UNIO),
some ATMEL micros, communicates with generic
I2C & SPI devices (see supported devices) - Can work as ICD debugger
- USB 2.0 Full Speed interface, HID class (same as keyboards, mice, etc.)
- Self powered
- Doesn't need drivers
- Built from easy to find components (estimated cost ~10€)
- Hardware generated timings for maximum speed and reliability
(writes a 18F2550 in 15s, 8s under Linux) - Doesn't saturate your CPU and doesn't suffer
when other programs are running - Open source control programs for Linux and Windows
- It's not another PicKit clone
The circuit (v1.7)
The project is based on a 28 pin 18F2550,
but only about a third of the memory and 0% of eeprom was used,
so it will fit confortably into the smaller 2455.
The 2458 and 2553 have a 12 bit ADC, so only recompilation is required.
With some modifications I adapted the code to the 2450;
since this model lacks the MSSP module I used a software implementation of I2C and SPI;
it also lacks the second PWM channel, therefore it can't generate the clock for Atmel chips
(for those that are configured with external clock);
in this case RB3 can be used to turn on an external oscillator
(which would be inserted in a modified Atmel expansion board).
The use of the corresponding 40 pin devices (4450, 4455, 4458, 4550, 4553)
requires modification of the PCB.
In order to implement an USB pheripheral with a PIC micro we need very few components:
the main microcontroller, a quartz, some capacitors, and a USB type B receptacle,
exactly as written in application notes from Microchip.
To be able to program PIC devices we need two digital lines for clock and data and
two supply voltages, VCC and VPP, which are controlled using three transistors;
VPP comes from a switching voltage regulator formed by Q4, L1, D3 which is described later.
In order to use SPI Flash memories it's necessary to modify the EEPROM adapter
to lower the supply voltage to 3.3V; below is the schematic diagram:
The ICSP-IN connector is used to program the main micro without extracting it,
by means of another programmer.
http://ww1.microchip.com/downloads/en/appnotes/00258a.pdf
Low Cost USB Microcontroller Programmer
Switching voltage regulator
18F2550
Timer2 (if DCDC on): 90kHz, no prescaler, no postscaler
Timer3 (if DCDC on): synchronous counter, no prescaler, source for CCP2
(after CLOCK_GEN command): 16 bit, source for CCP1 & CCP2, no prescaler
CCP1 (if DCDC on): PWM mode, clock from timer2, 90 kHz, to DCDC converter
(after CLOCK_GEN command): compare mode, reset timer3 on match (RC2)
CCP2 (if DCDC on): compare mode, trigger ADC every 250us
(after CLOCK_GEN command): compare mode, toggle on match, clock to external devices
ADC: acquires Vreg*12k/34k on AN0(FB), FOSC/64, triggered by CCP2, generates interrupt
If compiled for 18F2450:
Timer1 (if DCDC on): interrupt every 250us, ADC started by interrupt routine;
no CCP2
18F2458
#define ADC12//12 bit ADC requires a different algorithm for DCDC control
#define Vcost 72.282 //(12/(12+22)/5*1024) //Voltage feedback constant
#define ADC12 //12 bit ADC requires a different algorithm for DCDC control #define Vcost 72.282 //(12/(12+22)/5*1024) //Voltage feedback constant int pwm = 0; byte pwm_maxH = 100, T1 = 1; int volatile err = 0, errz = 0, vreg = 13.0 * Vcost; void init( void ) { err = errz = pwm = 0; #if !defined(NO_CCP2) CCP1CON = CCP2CON = 0; #else CCP1CON=0; #endif } /****************************************************************************** * Function: void TXins(byte) * PreCondition: None * Input: None * Output: None * Side Effects: None * Overview: Write to output queue *****************************************************************************/ void TXins( byte x ) { } void loop( void ) { switch ( receive_buffer[ RXptr ] ) { case CLOCK_GEN: // TXins( CLOCK_GEN ); if ( RXptr + 1 < number_of_bytes_read ) { i = receive_buffer[ ++RXptr ]; #if !defined(NO_CCP2) //use CCP1-1-timer3 if ( i == 0xff ) { CCP1CON = CCP2CON = 0; LATBbits.LATB3 = 0; T3CON = 0; } else { TRISCbits.TRISC2 = 1; //PWM1 disable output TRISBbits.TRISB3 = 0; //PWM2 enable output TMR3L = TMR3H = 0; T3CON = 0b11000001; //16 bit, source for CCP1 & CCP2, no prescaler CCPR1H = CCPR2H = 0x00; //CCPR1=N clock=12MHz/2(N+1) if ( i == 0 ) CCPR1L = 0x3B; //100KHz else if ( i == 1 ) CCPR1L = 0x1D; //200KHz else if ( i == 2 ) CCPR1L = 0xB; //500KHz else if ( i == 3 ) CCPR1L = 0x5; //1 MHz else if ( i == 4 ) CCPR1L = 0x2; //2 MHz else if ( i == 5 ) CCPR1L = 0x1; //3 MHz else if ( i == 6 ) CCPR1L = 0x0; //6 MHz CCPR2L = 1; CCP1CON = 0x0B; //reset timer3 on match CCP2CON = 0x02; //toggle on timer3 match } #else //18F2450 (software mode) if (i==0xff) { LATBbits.LATB3=0; } else { TRISBbits.TRISB3=0; LATBbits.LATB3=1; } #endif } else { TXins( RX_ERR ); receive_buffer[ RXptr + 1 ] = FLUSH; } break; case VREG_EN: //enable DCDC [5us] TXins( VREG_EN ); err = errz = pwm = 0; PR2 = 0x84; //PWM period=11us f=90kHz T2CON = 4; //timer2 ON #if defined(NO_CCP2) //use timer1 if CCP2 is not present PIR1bits.TMR1IF=0; PIE1bits.TMR1IE=1;//enable Timer1 interrupt TMR1H=0xF4;//64K-3000 @48MHz = 250 us TMR1L=0x48; T1CON=0b10000001;//timer1, 16 bit, no prescaler #else //else use CCP2 CCPR2H = 0x0B; //CCPR2=3000 (0xBB8) 12MHz/3000= 4KHz = 250us CCPR2L = 0xB8; PIR1bits.ADIF = 0; PIE1bits.ADIE = 1; //enable ADC interrupt TMR3L = TMR3H = 0; T3CON = 0b10001001; //timer3, 16 bit, linked to CCP2, no prescaler CCP2CON = 0x0B; //enable compare mode with ADC trigger #endif CCPR1L = 0; //SetDCPWM1(0); CCP1CON = 0x0C; //enable pwm TRISCbits.TRISC2 = 0; //PWM1 out INTCON = 0b11000000; //enable interrupt HLVDCON = 0b00011110; //enable power supply comparator @ 4.5V break; case VREG_DIS: //disable DCDC TXins( VREG_DIS ); #if defined(NO_CCP2) PIE1bits.TMR1IE=0; //disable Timer1 interrupt CCP1CON=T1CON=0;//timer1 off, pwm off #else //others use CCP2 PIE1bits.ADIE = 0; //ADC interrupt CCP1CON = CCP2CON = 0; //pwm off #endif HLVDCON = 0b00001110; //diable power supply comparator break; case SET_VPP: //set vpp (X x 0.1V) and wait for it to stabilize to +- 0.2V //return X if successful, else INS_ERR TXins( SET_VPP ); if ( RXptr + 1 < number_of_bytes_read ) { #if !defined(NO_CCP2) if ( !PIE1bits.ADIE || !HLVDCONbits.IVRST ) TXins( INS_ERR ); //Check interrupt and HLVD #else if(!PIE1bits.TMR1IE||!HLVDCONbits.IVRST) TXins(INS_ERR); //Check interrupt and HLVD #endif else { PIR2bits.HLVDIF = 0; d = (unsigned char) receive_buffer[ ++RXptr ]; vreg = d * 72 / 10; //7; i = 15; //15 ms do { for ( d = 1000; d; d-- ) { //to update err Nop( ); Nop( ); Nop( ); Nop( ); } i--; }while ( ( err > 14 || err < -14 ) && i ); if ( PIR2bits.HLVDIF ) TXins( 0 ); else if ( err > 14 || err < -14 ) TXins( INS_ERR ); else TXins( receive_buffer[ RXptr ] ); } } else { TXins( RX_ERR ); receive_buffer[ RXptr + 1 ] = FLUSH; } break; case EN_VPP_VCC: //controls VPP and VCC, bit 0 VCC, bit 2 VPP TXins( EN_VPP_VCC ); if ( RXptr + 1 < number_of_bytes_read ) { i = receive_buffer[ ++RXptr ]; vcc_bit = ( i & 0x01 ) ? 0 : 1; vcc_dir = ( i & 0x02 ) ? 1 : 0; vpp_bit = ( i & 0x04 ) ? 1 : 0; vpp_dir = ( i & 0x08 ) ? 1 : 0; } else { TXins( RX_ERR ); receive_buffer[ RXptr + 1 ] = FLUSH; } break; } } /****************************************************************************** * Function: void timer_isr(void) * PreCondition: None * Input: None * Output: None * Side Effects: None * Overview: DCDC control function * Note: None *****************************************************************************/ #pragma interruptlow timer_isr void timer_isr (void) { #if defined(NO_CCP2) //no autostart by CCP2 ADCON0bits.GO=1; //start conversion TMR1H=0xF4; //64K-3000 @48MHz = 250 us TMR1L=0x48; while(ADCON0bits.GO); PIR1bits.TMR1IF = 0; #else while(ADCON0bits.GO); //in case ADC is not finished PIR1bits.ADIF = 0; #endif errz=err; HIBYTE(err)=ADRESH; LOBYTE(err)=ADRESL; #if defined(ADC12) //must shift if 12 bit result err>>=2; #endif err-=vreg; //pwm+=errz*225 //pwm-=err*228 #define F 1 #define W 0 _asm //arg2=errz arg1=225 MOVLW 225 MULWF errz,0 // ARG1L * ARG2L -> PRODH:PRODL MOVFF PRODH, RES1 MOVFF PRODL, RES0 MOVLW 225 MULWF errz+1,0 // ARG1L * ARG2H -> PRODH:PRODL MOVF PRODL, W,0 ADDWF RES1, F,0 // Add cross MOVF RES0,W,0 //pwm+=errz*225 ADDWF pwm,F,0 MOVF RES1,W,0 ADDWFC pwm+1,F,0 MOVLW 228 MULWF err,0 // ARG1L * ARG2L -> PRODH:PRODL MOVFF PRODH, RES1 MOVFF PRODL, RES0 MOVLW 228 MULWF err+1,0 // ARG1L * ARG2H -> PRODH:PRODL MOVF PRODL, W,0 ADDWF RES1, F,0 // Add cross MOVF RES0,W,0 //pwm-=err*228 SUBWF pwm,F,0 MOVF RES1,W,0 SUBWFB pwm+1,F,0 _endasm if(HIBYTE(pwm)<0) pwm=0; if(HIBYTE(pwm)>pwm_maxH) HIBYTE(pwm)=pwm_maxH; CCPR1L=HIBYTE(pwm); CCP1CON = (CCP1CON & 0xCF) | ((LOBYTE(pwm) >> 2) & 0x30); } #pragma code
In order to generate a voltage higher than 5V we need a boost switching converter.
On the market there are thousands of single chip solutions,
but I used instead the microcontroller itself and a few external components.
The width of output pulses will vary to keep the output voltage stable over all operating conditions.
In practice this is a digitally controlled regulator, as shown in the following diagram:
he ADC converter presently uses the 5V supply as a reference,
so the output voltage will follow it; it is possible to connect
an external reference to RA3 to improve the overall precision.
Switching frequency is 90 kHz, which is well over
the cutoff frequency of the output LC filter (~2,3 kHz).
The performance is limited by losses due to the transistor, diode, inductor,
but since the load is very low we can use low-cost (even recycled) components;
to improve load capability switch to a better transistor,
a Shottky diode, a higher rated inductor.
Anyways, in order to design a suitable regulator (block C above)
it's necessary to work in s domain and model the converter itself;
this has fortunately been done already, some info is available for example
http://www.ti.com/lit/an/slva061/slva061.pdf
With present component values the boost converter operates in dicontinuous mode;
critical current is:
Icrit=Vu*T/(16*L)=86 mA
well over expected load, supposed to be 1 mA.
Other parameters:
Vi=5
Vu=12.5
D=(Vu-Vi)/Vo
L=100e-6
C=47e-6
I=1e-3
R=12/I
Rl=1.6 (inductor series resistance)
T=1/90e3
vu 1 vu M-1 Vu 2M -1 --- = Gdo ---------- where Gdo = 2 --- ------- , M = ---, wp = ---------- D 1 + s/wp D 2M -1 Vi (M-1) RC
Transfer function results to be:
vu 127.58 -- = ------------- D 0.22031 s + 1
It seems that the system, in closed loop, would be stable even by itself;
however it would have a steady state error of 1/DCgain.
It's better to use a controller with a pole on the origin
and a zero to stabilize everything, for example the following controller:
D 0.25 (s + 50) C = --- = ------------- err s
Overall transfer function would be:
vu 144.77 s + 7238.4 -- = ----------------- vi s2 + 4.539 s
The system is stable, with a phase margin of ~75º.
Since we operate in the digital domain we must choose the sampling frequency.
It can't be too high because of execution speed;
if too low it limits the regulator bandwidth; a period of 250 us was a good compromise.
The various transfer functions are converted to z domain using bilinear transformation:
vu 0.018199 z2+ 0.00022607 z - 0.017973 -- = ------------------------------------ vi z2 - 1.9989 z + 0.99887 The controller is: D 0.25156*z - 0.24844 C1 - C2 z-1
C = --- = ------------------- = -----------
err z - 1 1 -z-1
Remember that z-1 represents a delay of one clock cycle.
Next we must deal with quantization and calculation errors.
A/D converter is 10 bits wide, and is triggered by timer2;
at the end of conversion an interrupt calls the regulation routine,
which calculates the new duty cycle for the PWM peripheral, also 10 bits wide.
On the feedback path it's necessary to include a voltage divider in order
to limit ADC input voltage to [0,5V]; R1 and R2 do this.
So the block diagram is modified as follows:
a=12/34 Vu=C'H(Vi-aVu) Vu C'H -- = ------ Vi 1+aC'H
To compare with the previous model we can multiply both terms by a;
simply remembering to change the set point we can decide that
the new input is Vi/a, and equate with the previous expression:
Vu aC'H CH ---- = ------ = ---- Vi/a 1+aC'H 1+CH aC'=C aC1' - aC2' z-1 C1 - C2 z-1 aC'= --------------- = C = ------------ 1 - z-1 1 - z-1 aC1'=C1 aC2'=C2
Since the hardware works with 10 bit digital data we can go from D/err to pwm/[err]:
[err]=err*1024/5 pwm=D*1024 D pwm/1024 pwm C1' - C2'z-1 C'= --- = ------------ = ------- = ------------ err [err]/1024*5 [err]*5 1 -z-1 pwm(1 - z-1)=[err](5*C1/a - 5*C2/a z-1)=[err](3.564 - 3.52 z-1)
It's clear that integer multiplications can't be used with these coefficients;
the easiest solution is to work with fractional values
(i.e. divide output by 2N and multiply coefficients accordingly);
considering that pwm output is 10 bits wide and left-aligned,
we can easily work with values divided by 64.
pwm(1 - z-1 )=[err]( k1 - k2 z-1 )/64 k1=5C1/a*64=228.12 ~ 228 k2=5C2/a*64=225.25 ~ 225
Following are step responses of continuous-time system (blue),
discrete-time system (red), discrete-time system with approximate cefficients (green);
As you can see they're almost coincident.
For all calculations I used Octave, an open source mathematical modeling tool; version 3 has just been released,
and it can be used also under the famous windows (almost)operating system.
If someone is interested these are the modeling scripts I used.
The real code for the control function was written in assembly; this is necessary for performance reasons.
In fact our C compiler calls a library function to perform multiplications,
so it has to save many variables on the stack causing a delay;
in this case the resulting execution time had reached 50 us,
which is a significant fraction of the sampling period.
Instead, by avoiding function calls and manually coding the 16x8 bit multiplication
(see k1 & k2), the execution time is down to 12 us.
Some real waveforms:
3
Power-up transient, 50 ms/div
Step response to load change (load on top trace,
AC coupled output on bottom tr.), 50ms/div
Step response to set point change (from 11,5 to 12,5 V), 50 ms/div
+5v to +13v Converter
http://www.romanblack.com/smps/conv.htm
A cheap circuit to make +13v from a +5v supply
Roman Black - orig Sep 2002 - updated Aug 2006
This is a simple smps voltage converter,
it makes an output voltage of +13.3v from a "standard" +5v supply as used with logic chips.
I was contacted by hobby PIC programmer legend Myke Predko about designing a simple and
cheap circuit to produce the 13v Vpp voltage needed to program PIC chips
from a standard 5v regulated supply. This would eliminate the need for an smps 13v converter chip.
The circuit I settled on worked out very cheap, using a cheap 470uH "RF choke" type inductor
and a very cheap 1N4148 small signal diode as the rectifier.
The two transistor types are not critical.
Measured efficiency was 72% which is quite reasonable from such a simple low power circuit.
The inductor can be a cheap small "RF choke" type.
- Tiny cheap RF type inductor
- Very cheap circuit (under 50 cents in parts!)
- Reliable self-starting
- 72% efficiency
- Input: 5v regulated
- Output: 13.3v @ 12mA maximum
How it works
The circuit regulates around peak inductor current,
and since input voltage remains constant it also regulates input current and hence input power.
The regulation mechanism is R2, by ensuring that if R2 volts
exceeds 0.6v main switch Q1 will turn off. The input current path is via L1, Q1 and R2.
Oscillation duty cycle is close to 50:50 so with an R2 of 6.8 ohms,
peak inductor current is about 90mA and the average input current is about 46mA.
Total current requirements can be reduced by increasing R2.
Notes
DO NOT run the circuit with no zener, and do not remove the zener when the circuit is running!
Like any boost converter it may produce high voltages without the zener output clamp,
and may damage transistors or the diode etc.
The circuit oscillates mainly based on inductor characteristics,
and the cap C1 is not really a tuning cap, instead it provides some stability of oscillation waveform
at a frequency determined mainly by inductance and the total load.
This booster is a CONSTANT POWER regulator.
It always draws 46mA from the 5v supply (230mW),
and delivers this power to the load at about 72% efficiency.
So the load always gets about 13.3v and 12mA.
If the output is overloaded at currents greater than 12mA the output voltage drops,
providing a crude current regulation.
If the output voltage is further overloaded to the point where it drops below 5v
the diode starts passing current directly from the 5v supply and input current rises a LOT.
Overloads like this should be avoided!
Important Notes!
The 13.3v output can be turned on or off by attaching the R1 resistor to +5v (as shown)
or to 0v to turn it off. R1 can be attached to a PIC output pin to control the circuit.
When the circuit is OFF the output is about 4.3v and total current consumption is about 0.4mA.
When the circuit is ON the output is 13.3v and total current consumption is about 46mA.
For a PIC programmer application; the MCLR must rise very quickly to 13v to initiate programming.
Use a 3k3 resistor in series with the output, going to the MCLR pin.
Then use an additional NPN transistor to hold MCLR low as an open-collector pull-down.
This transistor can then be switched and will switch the output voltage very quickly
from 0v to 13v. (This is necessary with the smps 13v generator chips also.)
High Power / High Voltage?
Note!
This circuit can be quite usable for higher power applications,
and will oscillate reliably with a larger (high power) version of Q1
like a TO-220 darlington type. Remember it regulates INPUT current,
and will provide a fairly regulated amount of power to the high voltage output load.
It can be used as a simple 4 watt fluoro tube driver with a 12 volt input (for example).
If you try this, try these values;
- Q1 = TIP122 darlington (see note)
- L1 = 470uH 500mA ferrite core inductor
- D1 = 1N4937 600v 1A fast rectifier
- R1 = 4k7
- R2 = 3.3 ohm
- R4 = 56k
- CLOAD = 0.1uF (not critical) but must be 630 volt type
With a 12v input this will run about 2 watts and make a usable light from the fluoro tube.
Remove the zener and connect the output to a 4 watt (5 inch) fluoro tube like a F4T5 type.
You may need a heatsink for Q1. I used a TIP-122 as it was lying around,
but a high voltage darlington (>300v) is better.
Remember this produces high voltages and you MUST always have the tube connected.
Have fun and work safely with high voltages!
Low Cost USB Microcontroller Programmer
http://ww1.microchip.com/downloads/en/appnotes/00258a.pdf
THE BOOST POWER SUPPLY
A boost power supply is needed on the PICkit 1 programmer board in order
to create the programming voltage (VPP) called for in the PIC12F629/675
programming specification. The boost power supply is a combination of hardware and firmware design.
Implementation: Hardware
The hardware developed for the PICkit 1 FLASH Starter Kit intentionally
uses as few components as necessary to keep the cost low.
Most of the components on the PICkit 1 programmer board make up the boost power supply.
The boost power supply, shown in Figure 3, creates the 13V voltage needed
for programming Microchip's low pin count FLASH devices.
CCP1 runs in PWM mode to create the input to the boost circuit.
Timer2 sets the period of the PWM.
An A/D channel (RA1) provides feedback to the PIC16C745.
VPP is switched on and off by pin RA0.
Similarly, RB7 switches power to VDD.
Finally, RC6 and RC7 bit bang programming commands to the device.
Details on how a boost circuit works are discussed in Technical Brief TB053.
http://ww1.microchip.com/downloads/en/appnotes/91053b.pdf
What happens briefly is that when Q2 is turned on, inductor L1 charges.
When Q2 is switched off, the energy stored in L1 flows through D13 to the storage capacitor (C4) and load.
Q3 and Q4 make up a switch for turning VPP on and off to the FLASH device.
The switch is toggled on and off by RA0.
Assuming the resistive load is the held constant,VPP will change with respect to the energy released from L1.
L1 stores energy during the high phase of the PWM and releases energy during the low phase of the PWM.
This boost circuit runs in Discontinuous mode, meaning L1 will always discharge fully during the low phase of the PWM.
VPP increases as the duty cycle of the PWM increases. The stability of VPP is achieved with a firmware PID.
Implementation: Firmware
The boost circuit routines are located in two linked files:
pid.asm and VPPCntl.asm.
VPPCntl.asm contains the functions for initializing the PIC16C745 peripherals used
to generate the boost circuit, turning VPP and VDD on and off, and clamping VPP high or low.
This file also contains the DoSwitcher function which is called from the ISR when Timer2 interrupts.
Timer2 is set up to overflow at a frequency of 93.75 kHz.
The Timer2 postscaler is 1:16, therefore the Timer2 interrupt occurs at a frequency of 5.86 kHz.
DoSwitcher reads the A/D value generated on the boost circuit feedback pin.
DoSwitcher then transfers this value to the PID function where a correction factor is calculated.
Several parameters are defined at the head of VPPCntl.asm.
They are described in Table 1.
Any increase or decrease in the duty cycle causes a respective rise or fall in VPP.
The variable governing the duty cycle is the control variable (CV).
The control variable is calculated for the next cycle based on the proportional,
integral and differential analysis of the feedback. Equation 1 shows this relation.
pid.asm
extern doPID extern InitPID extern SetTarget extern K1 extern K2 extern K3 ; File : PID.asm ; ; Proportional, Integral, Differential control loop utility. GLOBAL doPID, InitPID, SetTarget GLOBAL K1,K2,K3,Target,DeltaV #include <P16C745.inc> errorlevel -302 ; suppress message 302 from list file #define MaximumTarget H'C2' #define TargetADC MaximumTarget #define subwl sublw ; Ward's Secret Macro Kp equ D'230' ; Proportional error multiplier Ki equ D'26' ; Integral error multiplier Kd equ D'128' ; Differential error multiplier ;***************************************************************************************** ;* ;* Define RAM variables ;* bank0 udata multcnd res 1 ; 8 bit multiplicand multplr res 1 ; 8 bit multiplier xCount res 1 ; counter workspace used by Multiply and Tacq H_Byte res 1 ; High byte of the 16 bit multiply result L_Byte res 1 ; Low byte of the 16 bit multiply result K1 res 1 ; P multiplier K2 res 1 ; I multiplier K3 res 1 ; D multiplier Target res 1 ; Target boost voltage ADC MaxTarget res 1 ; Maximum target boost voltage DeltaV res 1 ; difference between target and measured output voltage LastDeltaV res 1 ; previous DeltaV H_Integral res 1 ; ms byte of 16 bit error integrator L_Integral res 1 ; ls byte of 16 bit error integrator H_PIDSum res 1 ; ms byte of 16 bit PID sum L_PIDSum res 1 ; ls byte of 16 bit PID sum PIDTemp res 1 ; temporary work space global K1 global K2 global K3 PROG1 code doPID ; use value passed through W as latest input for PID movwf PIDTemp CtrlLoop banksel DeltaV movf DeltaV,w ; get most recent error voltage movwf LastDeltaV ; save as last bcf STATUS,C ; assure zero in msb after rotate rrf PIDTemp,w ; get latest input (7 bit signed) subwf Target,w ; compute error voltage movwf DeltaV ; save ; Proportional error movf K1,w ; prepare for multiply by pre-placing... movwf multplr ; ...Proportional multiplier and... movf DeltaV,w ; ...error voltage... movwf multcnd ; ...in multiply workspace call Multiply ; multiply Proportional error by K1 movf H_Byte,w ; accumulate first of PID offsets movwf H_PIDSum ; movf L_Byte,w ; movwf L_PIDSum ; ; Integral error movf K2,w ; prepare for multiply by pre-placing... movwf multplr ; ...Integral multiplier movf DeltaV,w ; compute Integral Deltas movwf multcnd ; save integral in multiply workspace call Multiply ; multiply Integral error by K2 call Integrate ; integrate and accumulate ; Differential error movf K3,w ; prepare for multiply by pre-placing... movwf multplr ; ...Differential multiplier movf LastDeltaV,w ; compute Differential voltage... subwf DeltaV,w ; ...Differential = Delta - LastDelta movwf multcnd ; save Differential in multiply workspace call Multiply ; multiply Diffential error by K3 call Accumulate ; add to previous result ; return corrected control byte movf H_PIDSum,w ; recover control byte into W return ; return the byte Multiply ;***************************************************************** ; ; 8 x 8 multiplier routine ; enter with two 8 bit numbers in multcnd and multplr ; 16 bit result is returned in H_Byte and L_Byte ; multplr is altered in the process. ; This is a semi-signed multiply which means that if the ; multcnd is negative the result will be negative. The ; multplr is always unsigned. ; ;***************************************************************** ; clrf H_Byte ; clear workspace (L_Byte is self clearing) movlw 8 ; multiply is 8 accumulate/rotates movwf xCount ; set accumulate/rotate counter movf multcnd,W ; restore multicand btfsc multcnd,7 ; is multicand negative? subwl D'0' ; yes - make it positive by two's complimenting MU10 rrf multplr,f ; test multiplier LSB skpnc ; skip accumulate if LSB was zero addwf H_Byte,f ; not zero - add multicand rrf H_Byte,f ; right shift 16 bit result 1 place rrf L_Byte,f ; decfsz xCount,f ; all 8 accumulate/rotates performed? goto MU10 ; no - repeat for next bit btfss multcnd,7 ; was multicand negative? return ; no - no further processing comf L_Byte,f ; make positive result negative by two's complimenting comf H_Byte,f ; invert all 16 bits... movlw 1 ; ...and... addwf L_Byte,f ; ...add 1 skpnc ; adjust MS byte if carry from LS byte incf H_Byte,f ; return Integrate ;***************************************************************** ; ; Sum the 16 bit result of K2 multiply result with ; all previous results and add this integration to the PIDSum. ; ; The integral is always positive but other addends are signed. ; Determine overflow/underfow by comparing the msb's of the ; integral, addend, and addition result as follows: ; ; Integral, Addend, Result - Conclusion - Action ; 1 0 0 Overflow Force Max ; 0 1 1 Underflow Force Min ; ;***************************************************************** movf H_Integral,w ; get ms result movwf PIDTemp ; save movf H_Byte,w ; restore ms multiplication result addwf H_Integral,f ; 16 bit accumulate movf L_Byte,w ; get ls result addwf L_Integral,f ; 16 bit accumulate btfsc STATUS,C ; lower byte addition carry? incf H_Integral,f ; yes - adjust ms byte ; test for addition overflow btfss PIDTemp,7 ; test for pre-addition integral msb goto IntegUnder ; zero - check for underflow btfsc H_Byte,7 ; is addend negative? goto IGAccum ; yes - no overflow btfsc H_Integral,7 ; is result positive? goto IGAccum ; no - no overflow movlw 0xff ; overflow detected... movwf H_Integral ; ... force maximum movwf L_Integral goto IGAccum ; Test for addition underflow IntegUnder btfss H_Byte,7 ; is addend positive? goto IGAccum ; yes - no underflow btfss H_Integral,7 ; is result negative? goto IGAccum ; no - no underflow clrf H_Integral ; underflow detected... clrf L_Integral ; ... force minimum IGAccum ; the integral is always positive ; accumulate integral and sum then check for over/under flow ; the accumulation result must be positive movf H_PIDSum,w ; get ms addend movwf PIDTemp ; save movf H_Integral,w ; restore ms integral addwf H_PIDSum,f ; accumulate with sum movf L_Integral,w ; restore ls byte of integral addwf L_PIDSum,f ; sum with ls PIDSum btfsc STATUS,C ; lower byte addition carry? incf H_PIDSum,f ; yes - adjust ms byte ; test for addition overflow btfss H_Integral,7 ; test integral msb goto IAUnder ; zero - check for underflow btfsc PIDTemp,7 ; is addend negative? goto IntegExit ; yes - no overflow btfsc H_PIDSum,7 ; is result positive? goto IntegExit ; no - no overflow movlw 0xff ; overflow detected... movwf H_PIDSum ; ... force maximum movwf L_PIDSum goto IntegExit ; Test for addition underflow IAUnder btfss PIDTemp,7 ; is addend positive? goto IntegExit ; yes - no underflow btfss H_PIDSum,7 ; is result negative? goto IntegExit ; no - no underflow clrf H_PIDSum ; underflow detected... clrf L_PIDSum ; ... force minimum IntegExit return Accumulate ;***************************************************************** ; ; Accumulate the 16 bit result of K3 PID multiply result ; ; The sum accumulator (PIDSum) is assumed always positive ; ;***************************************************************** movf H_PIDSum,w ; get pre-addition sum ms byte movwf PIDTemp ; save movf H_Byte,w ; get ms multiplication result addwf H_PIDSum,f ; 16 bit accumulate movf L_Byte,w ; get ls result addwf L_PIDSum,f ; 16 bit accumulate btfsc STATUS,C ; lower byte addition carry? incf H_PIDSum,f ; yes - adjust ms byte ; test for accumulation overflow btfss PIDTemp,7 ; test pre-addition msb goto ACUnder ; zero - check for underflow btfsc H_Byte,7 ; is addend negative? goto AccumExit ; yes - no overflow btfsc H_PIDSum,7 ; is result positive? goto AccumExit ; no - no overflow movlw 0xff ; overflow detected... movwf H_PIDSum ; ... force maximum movwf L_PIDSum goto AccumExit ; Test for addition underflow ACUnder btfss H_Byte,7 ; is addend positive? goto AccumExit ; yes - no underflow btfss H_PIDSum,7 ; is result negative? goto AccumExit ; no - no underflow clrf H_PIDSum ; underflow detected... clrf L_PIDSum ; ... force minimum AccumExit return InitPID ;************************************************************************************ ;* ;* Initialize PID registers ;* movlw Kp ; initialize PID multipliers movwf K1 movlw Ki movwf K2 movlw Kd movwf K3 clrf DeltaV ; clear accumulators clrf H_Integral clrf L_Integral clrf H_PIDSum clrf L_PIDSum return SetTarget ;************************************************************************ ;* ;* take value passed in W and configure the target ;* movwf Target ; set target bcf STATUS,C ; clear carry for rotate rrf Target,f ; convert to 7 bit signed return end
vppcntrl.asm
;############################################################################### ; filename: VppCntrl.inc ; Vpp Control functions ; ; This file implements a basic interrupt service routine and shows how the ; USB interrupt would be serviced, and also how InitUSB and PutUSB ; should be called. It may be used as a reference, or as a starting point ; from which to build an application. ; ;############################################################################### extern VppInit extern VppHigh extern VppLow extern VppOff extern VppOn extern VddOn extern VddOff extern DoSwitcher extern PWMOn extern PWMOff ;############################################################################### ; filename: VppCntrl.ASM ; Vpp Control functions ; ; This file implements a regulated boost power supply. ; The following resources are used by this module: ; CCP ; ADC channel ; Timer ; ;############################################################################### GLOBAL lastADRES #include <P16C745.inc> #include "pid.inc" errorlevel -302 ; suppress message 302 from list file bank0 udata PowerState res 1 ; Power Status Flag pwm res 1 ; temporary value for CCP loader Result res 2 ; final calculation skip res 1 lastADRES res 1 #define PowerGood PowerState,0 #define PowerHigh PowerState,1 #define PowerLow PowerState,2 #define PowerON PowerState,7 ; Set Points for voltage regulator. V5V equ .92 ; counts for 5v from Regulator VCHECK equ .220 ; regulator setpoint (11.7v ideal) ;VCHECK equ .230 ; regulator setpoint (12v) maybe too high PWM_MAX equ .130 ; 75% PWM Maximum PWM_MIN equ .13 ; 5% PWM Minimum skip_reload equ .10 PROG1 code VppInit global VppInit call InitPID movlw VCHECK call SetTarget clrf STATUS ; Bank0 return VppLow global VppLow ; cause Vpp to be 5v banksel PowerState bcf PowerON banksel CCP1CON clrf CCP1CON ; turn off CCP clrf STATUS ; Bank0 return VppHigh global VppHigh ; cause Vpp to be 13v ; does not return until regulator is running ; setup ADC banksel ADCON1 movlw 0x04 movwf ADCON1 banksel ADCON0 movlw 0x89 ; configure ADC for Fint/32 and AN1. movwf ADCON0 ; Turn ADC on banksel PORTC clrf PORTC banksel TRISC bcf TRISC,2 ; set RC2 as output bsf TRISA,1 ; setup CCP banksel PR2 movlw .63 ; 93.75khz Timer 2 movwf PR2 banksel T2CON movlw 0x7C ; T2 postscale 1/16 movwf T2CON movlw PWM_MAX pagesel updateCCP call updateCCP banksel PowerState bcf PowerGood bcf INTCON,GIE ; Disable Global IRQ for a little bit banksel TMR2 clrf TMR2 ; Clear T2 Counter banksel PIR1 bcf PIR1,TMR2IF ; Clear T2 Pending IRQ banksel PIE1 bsf PIE1,TMR2IE ; Enable T2 IRQ banksel INTCON bsf INTCON,PEIE ; Enable Peripheral IRQ bsf INTCON,GIE ; Enable Global IRQ bsf ADCON0,GO banksel PowerState bsf PowerON clrf STATUS ; Bank0 return VddOff global VddOff banksel PORTB movf PORTB,W iorlw 0x80 movwf PORTB ; turns on the Vdd transistor clrf STATUS ; Bank0 return VddOn global VddOn ; turns off the Vdd transistor. banksel PORTB movf PORTB,w andlw 0x7F movwf PORTB clrf STATUS ; Bank0 return VppOn global VppOn banksel PORTA ; make RA0 high movf PORTA,W iorlw 0x01 movwf PORTA clrf STATUS ; Bank0 return VppOff global VppOff ; make RA0 low banksel PORTA movf PORTA,W andlw 0xFE movwf PORTA clrf STATUS ; Bank0 return updateCCP ; place W value in CCPL & CCPCON<5:4> banksel pwm movwf pwm sublw PWM_MAX btfsc STATUS,C goto uc movlw PWM_MAX movwf pwm uc swapf pwm,w andlw 0x30 iorlw 0x0F banksel CCP1CON movwf CCP1CON banksel pwm rrf pwm,f rrf pwm,w andlw 0x3F banksel CCPR1L movwf CCPR1L return DoSwitcher global DoSwitcher banksel skip decfsz skip,f goto switcherdone movlw skip_reload movwf skip banksel ADRES movf ADRES,w ; start next ADC bsf ADCON0,GO banksel lastADRES movwf lastADRES banksel PowerState btfss PowerON goto switcherdone pagesel doPID call doPID pagesel updateCCP call updateCCP switcherdone banksel PIR1 bcf PIR1,TMR2IF banksel PORTB return PWMOn global PWMOn bsf STATUS,RP0 ; Bank1 bcf TRISC,1 ; make RC1 an output movlw .74 movwf PR2 bcf STATUS,RP0 ; Bank0 return PWMOff global PWMOff bsf STATUS,RP0 ; Bank1 bsf TRISC,1 ; make RC1 an input movlw .63 movwf PR2 bcf STATUS,RP0 ; Bank0 return end