PID Code with a PIC16F877A, MCP4921-DAC

General discussion on mikroPascal.
Post Reply
Author
Message
isaacn
Posts: 13
Joined: 06 Mar 2005 07:46
Location: WA

PID Code with a PIC16F877A, MCP4921-DAC

#1 Post by isaacn » 30 Jan 2007 06:54

Hello Everyone. I'm new at programming in Pascal so I may have made some mistakes; I was wondering if my PID code is close to the right formula. Also, do you need to have a limit on the accumulated error wind-up? My goal is to measure the temperature of a still with boiling mash, display temp in F, and control the temperature using the MCP4921 with a mass flow controller using the 0-5V output of the MCP4921. My setpoint is 180F. Thanks for any help in advance.

Code: Select all

// Project uses a PIC16F877A, MCP4921-DAC, EasyPIC AND 2x16 LCD Display
program TemperatureController;
const CHIP_SELECT                : byte = 0;
var i, highByte, lowByte         : byte;
    counter, temp, tmp           : byte;
    kd, ki, kp                   : integer;
    T, SP                        : integer;
    PV                           : longint;
    errA, errD, errN, errP       : integer;
    p_Term, i_Term, d_Term       : integer;
    pid_Out                      : integer;
    txt1                         : array[10] of char;
    txt2                         : array[11] of char;
    txt3                         : array[6] of char;
    txt4                         : array[6] of char;
//******************************************************************************
// TMR0 Interrupt
//******************************************************************************
Procedure interrupt;
begin
  inc(counter);              // Increment value of counter on every interrupt
  TMR0   := $00;             // Load TMR0 with $00 for a new count
  clearbit(INTCON, TMR0IF);  // Clear overflow flag bit for TMR0
end;
//******************************************************************************
// Send pid_Out to DAC
//******************************************************************************
Procedure dacOut;
begin
ClearBit(PORTC,CHIP_SELECT);  //  Prepare for data transfer
temp := hi(pid_Out) and $0F;  //  Prepare hi-byte for transfer
temp := temp or $30;          //  It's a 12-bit number, so only
SPI_write(temp);              //  lower nibble of high byte is used
temp := lo(pid_Out);          //  Prepare lo-byte for transfer
SPI_write(temp);
SetBit(PORTC,CHIP_SELECT);
end;
//******************************************************************************
// Print temperature and DAC output to LCD
//******************************************************************************
Procedure printTempPID;
begin
intToStr(T, txt3);                   // Print temperature in fahrenheit to LCD
Lcd_Chr(1,11, txt3[2]);
Lcd_Chr(1,12, txt3[3]);
Lcd_Chr(1,13, txt3[4]);
Lcd_Chr(1,14, txt3[5]);
intToStr(pid_Out, txt4);             // Print PID_out to LCD
Lcd_Chr(2,12, txt4[1]);
Lcd_Chr(2,13, txt4[2]);
Lcd_Chr(2,14, txt4[3]);
Lcd_Chr(2,15, txt4[4]);
Lcd_Chr(2,16, txt4[5]);
delay_ms(50);
end;
//******************************************************************************
// Calculate PID output
//******************************************************************************
Procedure PID;                      // Setpoint calculations
begin                               // 4095 / 250 = 16.38
PV   := (tmp * 1638)/100;           // 100 * 250 = 1638
errN := SP - PV;                    // (250 * 1638) / 100 = 4095
errA := errA + errP;                // (180F - 32) / 1.8 = 82.22 C
errD := errN - errP;                // 2 * 82.22 C = 164.44 C
errP := errN;                       // 16.38 * 164.44 = 2693.53
// if errA > X then errA := X; // wind-up limits ???
// if errA < X then errA := X; // wind-up limits ???
p_Term := (kp * errN);
i_Term := (ki * errA);
d_Term := (kd * errD);
pid_Out := p_Term + i_Term + d_Term;
if pid_Out > 4095 then pid_Out := 4095;   // High limit
if pid_Out < 0 then pid_Out := 0;         // Low limit
end;
//******************************************************************************
// Get temperature and convert from C to F
//******************************************************************************
Procedure temperature;
begin
Ow_Reset(PORTA, 5);                // onewire reset signal
Ow_Write(PORTA, 5, $CC);           // issue SKIP ROM command to DS1820
Ow_Write(PORTA, 5, $44);           // issue CONVERT T command to DS1820
Delay_us(120);
i := Ow_Reset(PORTA, 5);
Ow_Write(PORTA,5, $CC);            // issue SKIP ROM command to DS1820
Ow_Write(PORTA,5, $BE);            // issue READ SCRATCHPAD command to DS1820
lowByte  := Ow_Read(PORTA, 5);     // get low byte 0000 0000 XXXX XXXX
highByte := Ow_Read(PORTA, 5);     // get high byte XXXX XXXX 0000 0000
T := lowByte;                      // using range 0 to 127 C
tmp := T;                          // tmp for PV
T := T shr 1;                      // convert to whole number
T := ((T * 18) + 320)/10;          // convert from C to F
end;
//******************************************************************************
// Initiate
//******************************************************************************
Procedure init;
begin
ADCON1     :=   6;         // Configure PortA, All Digital I/O
INTCON     := $A0;         // Enable Enable GIE and TMR0IF
OPTION_REG := $87;         // Enable TMR0, 256-bit prescaler = 1:256
TMR0       := $00;         // Load TMR0 with 0000 0000
TRISA      := $FF;         // Designate porta as inputs
PORTA      := $FF;         // Initialize porta to $FF
TRISB      := $00;         // Designate portb as output
TRISC      := $00;         // Designate portc as output
Lcd_Init(PORTB);                       // Get ready
Lcd_Cmd(LCD_CURSOR_OFF);               // Turn off grey display
txt1   := 'Still Temp';                // Still Temp
txt2   := 'PIDOUTPUT = ';              // PIDOUTPUT
Lcd_Out(1,  1, txt1);                  // Print Still Temp
Lcd_Chr(1, 15, 223);                   // The little circle character
Lcd_Chr(1, 16, 'F');                   // Fahrenheit
Lcd_Out(2,  1, txt2);                  // Print PIDOUTPUT =
ClearBit(TRISC,CHIP_SELECT);           // Get DAC ready
SPI_init;                              // Get DAC ready
counter := 0;
PV      := 0;
SP      := 2694;                       // 16.38 * 164.44 = 2693.53
PID_Out := 0;
T       := 0;
tmp     := 0;
kp      := 1;
ki      := 0;
kd      := 0;
errA    := 0;
errD    := 0;
errN    := 0;
errP    := 0;
end;
//******************************************************************************
// Main  Program
//******************************************************************************
begin
init;
   repeat
   begin
   if counter = 30 then
      begin
       temperature;
       PID;
       printTempPID;
       dacOut;
       counter := 0;
      end;
   end;
   until 1 = 0;
end.
//******************************************************************************



FRM
Posts: 381
Joined: 20 May 2005 18:58
Location: UK

#2 Post by FRM » 30 Jan 2007 08:41

I'm no expert, but according to some helpful info from LGR a while back, and according to a very well written article I found on the internet, there MAY ba a few issues here not accounted for...like sampling rate accuracy and maybe precision requirements...also what clock frequency are you driving the pic with?

Here's that very useful article: http://www.embedded.com/2000/0010/0010feat3.htm , written by a very well accredited Tim Wescott.

I recently developed floating point pid for freq/current control of a low speed controller for an instrument, and I found this read invaluable in understanding the algorithm.

Yes, you probably do need to limit wind-up (especially if you are using Integral gain), which also sensitizes the gain settings for Integral term.
All of this can be found in the mentioned article.

I'm sure LGR could add some useful input here if it has not slipped his attentions! :wink:

Hope this helps...

FRM
Posts: 381
Joined: 20 May 2005 18:58
Location: UK

#3 Post by FRM » 30 Jan 2007 08:42

temperature of a still with boiling mash
Not making moonshine are you? :lol:

isaacn
Posts: 13
Joined: 06 Mar 2005 07:46
Location: WA

#4 Post by isaacn » 03 Feb 2007 08:19

Thanks for the information, I think it will help me a lot. My PIC16F877A is running at 4 MHz. I need to purchase a control valve of some sort, so I can tune my code. Also I need to add a start, stop and setpoint change into the code. What do you think about using a proportional control valve for controlling the propane? I want something that will be cheap, that operates from 0 to 5 volts. This project is for my friends 30 gallon still. He wants to make MOONSHINE with out having to manually control the propane valve.

xor
Posts: 5465
Joined: 18 May 2005 00:59
Location: NYC
Contact:

#5 Post by xor » 04 Feb 2007 15:13

Proportional valves and damper motors are typically 0-10V controlled. You can use your 5V DAC coupled with a 10V rail-to-rail opamp ( http://datasheets.maxim-ic.com/en/ds/MA ... AX4164.pdf, http://www.analog.com/UploadedFiles/Dat ... 4_8644.pdf ) and level shift from 0 to 10V.

There is also a single supply SPI DAC that can swing from 0-10V by Linear:
http://www.linear.com/pc/productDetail. ... 1156,P1425
[color=darkred][b]xor[/b][/color]
[url=http://circuit-ed.com]CircuitED -[/url]

piort
Posts: 1379
Joined: 28 Dec 2005 16:42
Location: Laval,Québec,Canada,Earth... :-)
Contact:

#6 Post by piort » 04 Feb 2007 16:19

hi,
im not sure at 100% but some proportionnal valve work with 4-20ma range too. So if you use the 0-5volt with a 10K resistor in parallel , that give you a 4-20ma ;-)

happy coding 8)

isaacn
Posts: 13
Joined: 06 Mar 2005 07:46
Location: WA

#7 Post by isaacn » 08 Feb 2007 07:24

Thanks Guys :D . The best deal I can find on a proportional valve is $395.00 http://www.omega.com/pptst/PV101.html The valve works with 0-5V, 0-10V and 4-20mA. I would like to get one for less money. Piort I'm not understanding the 10K ohm resistor in parallel. Do I have to use an OPAMP with the DAC? How does this work :shock: ?

FRM
Posts: 381
Joined: 20 May 2005 18:58
Location: UK

#8 Post by FRM » 08 Feb 2007 09:44

The op-amp is for conversion to current-loop.
You need a simple op-amp voltage to current circuit (transconductance amplifier), because typically 4-20mA devices would have input voltage ranges that could start above 5v. Thus, you supply (within limits) the op-amp with as much voltage as to make current flow in the device.

You would use the 1-5v range of your dac (less than 1v = less than 4mA)
Using simple ohm's law you need a 250R resistor to GND from the inverting input, with the ouput fed back through the device, to the inverting input.

Your load is across the output of the op-amp and the inverting input, load+ to the op-amp output, load- to the inverting input.
Current flow is from the inverting input to the op-amp output.
Make sure you pick an appropriately rated op-amp.

Schematic: http://www.asrz92.dsl.pipex.com/images/4-20mA.wmf

Hope this helps you...

sonixmigraine2014
Posts: 1
Joined: 26 Oct 2017 06:54

Re: PID Code with a PIC16F877A, MCP4921-DAC

#9 Post by sonixmigraine2014 » 26 Oct 2017 15:41

Thanks! My PIC16F877A at 4 MHZ is doing great!

Post Reply

Return to “mikroPascal General”