Hi guys,
For my final project for microcontrollers, I'm designing a PWM controller for a LTC4440 MOSFET driver. (datasheet:http://cds.linear.com/docs/en/datasheet/4440fb.pdf) I'm writing the code (it has to be in assembly for the project) for either an atmega2560 or an atmega328p (I have the arduino uno, arduino mega, and a 328p DIP to work with).
Here is my schematic (the attiny85 is a placeholder for the atmega since it has less pins):
The circuit takes US wall voltage, and (hopefully) outputs ~20Vdc at ~2-3Amps. I'm starting with just the P controller to make it easy, then will add the ID parts later.
The code, as I see it, is split into three parts. The ADC control, the PWM control, and the PID calculations.
1. The PWM Control;Definitions
.DEF init1 = r16 ;Variables used to initialize registers
.DEF init2 = r17
.DEF init3 = r18
.DEF kp = r19
.org 0x0000 ;memory (PC) location of reset handler
rjmp Reset
.org 0x0020 ;memory location for T1 Overflow handler
RJMP TIM1_CAPT
.org 0x003A ;memory location for adc ready handler
RJMP ADC_RDY
.org 0x0040 ; memory location for T3 compare A handler
RJMP TIM3_COMPA
;============
Reset:
;initialize system clock to 16MHz
CLI
LDI init1, 0b1000_0000 ;enable CLKPR to allow to change frequency
LDI init2, 0b0000_0001 ;set main prescaler to 8 so it can be seen on cheap oscilliscope
STS CLKPR, init1
STS CLKPR, init2
;Initial setup of the PWM signal (timer 3)
LDI init1, 0b1000_0010 ;Fast PWM mode 14 TCCR3A
LDI init2, 0b0001_1001 ;Set scaler to 1 TCCR3B
STS TCCR3A, init1
STS TCCR3B, init2
LDI init1, 0b0000_0010 ;turn on Interrupt for TIMSK3
LDI init2, 0x00 ;Max value of PWM (can use 1+2 to make 16-bit)
LDI init3, 0xA0 ; A0 = 160 = 16MHz/100Khz
STS TIMSK3, init1
STS ICR3H, init2
STS ICR3L, init3
LDI init2, 100 ;starting value of OCR1A (Duty Cycle OCR1A/ICR1L)
STS OCR3AL, init2
LDI init1, 0xFF
OUT DDRE, init1
CLR init1
STS TCNT1H, init1
STS TCNT1L, init1
STS TCNT3H, init1
STS TCNT3L, init1
The PWM needs to output at 100KHz for the LTC4440. So using PWM mode 14, it counts till the value in OCR3AL then turns on the output, then counts to ICR3H/L and resets back to zero and turns off the signal.
Running at 16MHz, the atmega2560 only gives me 160 values for the control of the duty cycle (16Mhz/100Khz). I can run the 328p at up to 20MHz, which would give me 200 values, but that's still not a full 8 bits, so would there be any advantage to using the 328p, since I already started writing for the 2560?
The ADC:;Set up ADC
LDI init1, 0b0000_0001 ;should set ADC port to input
STS DIDR0, init1
LDI init1, 0b0000_0000 ;should disable all other adc ports
STS DIDR2, init1
LDI init1, 0b0100_0000 ;ADMUX Vcc reference, ADC input on ADC0 -A0
LDI init2, 0b0000_0110 ;ADCSRB Auto trigger on timer1 overflow
LDI init3, 0b1110_1111 ;ADCSRA interrupt enable, prescaler 2
STS ADMUX, init1
STS ADCSRB, init2
STS ADCSRA, init3
;Set up ADC timer (timer 1)
LDI init1, 0b0100_0000 ;TCCR1A CTC mode max = OCR1A
LDI init2, 0b0001_1001 ;TCCR1B prescaler 1
LDI init3, 0b0000_0001 ;TIMSK1 enable overflow interrupt
STS TCCR1A, init1
STS TCCR1B, init2
STS TIMSK1, init3
LDI init1, 0x0F ;ICR1AH countermax = 4000.
LDI init2, 0xA0 ;ICR1AL should trigger every 500us (16Mhz/2/4000)^-1
STS ICR1H, init1
STS ICR1L, init2
The ADC is the easy part. I already have it working. it starts it's conversion when timer 1 overflows automatically, which is ~ once every 500us, and returns a 10bit value that is relative to the internal 5V Vcc.
The PID ControlThis is where I could really use some help
So according to the voltage divider on Vout, I want the reference value for the PID to be 20V * 120k/(120K+620K) = ~3.24V which is an ADC value of (3.24V/5V)*1024 = ~664, correct?
I understand that e(t) = ref - y(t) where y(t) is the value read by the adc, so e(t) will give me any value from -360 (664-1024) to 664 (664- 0), then I multiply that by some value kp to get the u(t) that gets fed back into the system. So far so good.
What I'm stuck on is what that means for the PWM signal.
I think that if the value is over 664, it should shorten the duty cycle to drop the voltage, and if it's below 664, it should increase the duty cycle to raise the voltage. Is that correct?
I think I am supposed to set a default duty cycle of 50% and then add u(t) to it. That way if it's negative, it will reduce the duty cycle, and if it's positive it will increase the duty cycle, thereby varying the voltage.
How do I jam my 10bit u(t) into my 160 values that I can use for my duty cycle? Do I just rotate right until I get rid of the extra digits?
The avr code has fractional multiply, but I have no idea how it works, and I'm not sure if it's useful. I might have bitten off more than I can chew, since he let us pick a project, and I was already trying to design the buck converter circuit with another professor, I chose to do this. Plus we did the whole class on the MSP430, so I made things way harder for myself by switching to AVR, but I don't think I could have done this on the MSP430 since it doesn't have built in multiplication.
I have attached a zip file with my schematic and my code. The code under the ADC_READY interrupt is just what I was using to test the ADC to make sure it was working. it lights up 1 - 5 LEDs on port C depending on how many volts the ADC reads. It won't be in the final version of my code.