Uno running PID w/PWM frequency change

Hello,
This is my first time experimenting with an Arduino. I have very minimal experience in electronic, and much less in writing any sort of programming. With much help from google and an acquaintance that has a lot of experience I managed to load a PID program and get it working with my setup. Now I need to adjust the output PWM frequency to work with the valve I am using. I have tried many different methods and suggestions that I have found through searches with no success.

The setup:
I have a closed loop pressure system using the Uno to read a 5v pressure sensor, a potentiometer to adjust pressure, and a pilot operated valve to regulate the pressure. The pressure is to be adjustable from 0-4000psi. This valve is from an automotive application and I do not have technical data on the valve, from what I gather the valve was designed to run on a 3300Hz frequency and a 15-85% duty cycle

The Uno output is fed to a SSR with a 12v power supply providing power to the valve. Currently the frequency is so low that the pressure pulses extremely hard, and becomes worse as the pressure is increased.

Question is - how can I incorporate a function to adjust the PWM frequency within my current sketch?

The sketch I am using thus far without any PWM changes is as follows:

#include <PID_v1.h>


const int ICP = A0; // ICP Sensor input
const int pot = A1; // Potentiometer input
const int IPR = 6; // IPR valve
double ICPLevel; // variable that stores the incoming ICP level

// Tuning parameters
float Kp=0; //Initial Proportional Gain
float Ki=5; //Initial Integral Gain
float Kd=0; //Initial Differential Gain
double Setpoint, Input, Output; //These are just variables for storingvalues
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
// This sets up our PDID Loop
//Input is our PV
//Output is our u(t)
//Setpoint is our SP
const int sampleRate = 1; // Variable that determines how fast our PID loop
// runs
// Communication setup
const long serialPing = 500; //This determines how often we ping our loop
// Serial pingback interval in milliseconds
unsigned long now = 0; //This variable is used to keep track of time
// placehodler for current timestamp
unsigned long lastMessage = 0; //This keeps track of when our loop last
// spoke to serial
// last message timestamp.
void setup(){
 ICPLevel = analogRead(ICP); //Read pressure level
 Input = map(ICPLevel, 0, 1024, 0, 255); //Change read scale to analog
// out scale
 Setpoint = map(analogRead(pot), 0, 1024, 0, 255);
//get our setpoint from our pot
 Serial.begin(9600); //Start a serial session
 myPID.SetMode(AUTOMATIC); //Turn on the PID loop
 myPID.SetSampleTime(sampleRate); //Sets the sample rate

 Serial.println("Begin"); // Hello World!
 lastMessage = millis(); // timestamp
}
void loop(){
 Setpoint = map(analogRead(pot), 0, 1024, 0, 255); //Read our setpoint
 ICPLevel = analogRead(ICP); //Get the pressure level
 Input = map(ICPLevel, 0, 1024, 0, 255); //Map it to the right scale
 myPID.Compute(); //Run the PID loop
 analogWrite(IPR, Output); //Write out the output from the PID loop to our LED pin

 now = millis(); //Keep track of time
 if(now - lastMessage > serialPing) { //If it has been long enough give us
// some info on serial
 // this should execute less frequently
 // send a message back to the mother ship
 Serial.print("Setpoint = ");
 Serial.print(Setpoint);
 Serial.print(" Input = ");
 Serial.print(Input);
 Serial.print(" Output = ");
 Serial.print(Output);
 Serial.print("\n");
 if (Serial.available() > 0) { //If we sent the program a command deal
 // with it
 for (int x = 0; x < 4; x++) {
 switch (x) {
 case 0:
 Kp = Serial.parseFloat();
 break;
 case 1:
 Ki = Serial.parseFloat();
 break;
 case 2:
 Kd = Serial.parseFloat();
 break;
 case 3:
 for (int y = Serial.available(); y == 0; y--) {
 Serial.read(); //Clear out any residual junk
 }
 break;
 }
 }
 Serial.print(" Kp,Ki,Kd = ");
 Serial.print(Kp);
 Serial.print(",");
 Serial.print(Ki);
 Serial.print(",");
 Serial.println(Kd); //Let us know what we just received
 myPID.SetTunings(Kp, Ki, Kd); //Set the PID gain constants and start
 // running
 }

 lastMessage = now;
 //update the time stamp.
 }

}

Thanks everyone!
-Jon

I would first avoid using map
Input = map ( ICPLevel, 0, 1024, 0, 255 ) ; //Map it to the right scale

Input is a floating point number and map only returns an Integer
consider
**Input = ( ( float ) **__ICPLevel ) * 0.249 ; //this will keep your precision at 1024 steps __

Another observation:

  if(myPID.Compute()){ //Run the PID loop (your PID loop only calculates every 1ms which is fast! it is skipped otherwise and returns false
    analogWrite(IPR, Output); //Write out the output from the PID loop to our LED pin
    now = millis(); //Keep track of time
  }

with such a high sample rate any noise will cause Kd to be unusable and mess with Ki which should be your primary control method.

const int sampleRate = 1; // Variable that determines how fast our PID loop

Consider setting sampleRate to 10 ~ 100 milliseconds.
Your PID will become easier to control.
My balancing bot maintains its balance with a 10 millisecond sample rate Balancing Bot
Start with Ki and have Kp and Kd at zero
Ki is the ramping part of the PID it will discover the best position for the valve to be at to achieve setpoint. Ki is slow and we will need to add Kp next to speed the process up. but before we do find a good Ki that will maintain setpoint without oscillation this will take time to settle. minutes possible. Kp allows for quick adjustments it is literally instantaneous! a change to the input is multiplied by Ki and added to the output. Too much Kp and you will oscillate. Kd is the breaks and monitors rate of change if no change is occurring Kd is Zero so Kd is not what maintains Setpoint it just helps you get there. adding Kd in the end will speed up achieving setpoint. Too much Kd and your control will become Jittery. Noise will affect Kd so smoothing your input will help you increase the effectiveness of Kd.
Z

Zhomeslice,
Thanks for the input. This pressure system is HIGHLY responsive, and I have found that if i set the sample rate to anything other than 1, it becomes VERY unstable. I speculate that it does so because the valve is being run at 1/3 of the frequency that it should be. I also figure that before I can really go tweaking and fine tuning the PID values I should have the frequency properly set so that the valve will respond appropriately.

I have tried inserting both of the following code into the void setup and tried both pins 6 & 9 with no success:

TCCR1B = (TCCR1B & 0xF8) | 0x02
TCCR0B = (TCCR0B & 0xF8) | 0x02

I have also tried the following example from Arduino Playground - PwmFrequency

setPwmFrequency(9, 8);

which when used in my void setup does not verify and gives the following error:
Arduino: 1.8.3 (Windows 10), Board: "Arduino/Genuino Uno"

" setPwmFrequency(9, 8);

^

exit status 1
'setPwmFrequency' was not declared in this scope"

What am I missing? Do i need to add a PWM library? do I need to set an int for the PWM?

Thanks,
Jon

I forgot to mention that it is the Arduino Uno - R3 SMD
If that makes any difference.

TheReelMuhcoy:
Zhomeslice,
Thanks for the input. This pressure system is HIGHLY responsive, and I have found that if i set the sample rate to anything other than 1, it becomes VERY unstable. I speculate that it does so because the valve is being run at 1/3 of the frequency that it should be. I also figure that before I can really go tweaking and fine tuning the PID values I should have the frequency properly set so that the valve will respond appropriately.

I have tried inserting both of the following code into the void setup and tried both pins 6 & 9 with no success:

TCCR1B = (TCCR1B & 0xF8) | 0x02

TCCR0B = (TCCR0B & 0xF8) | 0x02




I have also tried the following example from https://playground.arduino.cc/Code/PwmFrequency


setPwmFrequency(9, 8);



which when used in my void setup does not verify and gives the following error:
Arduino: 1.8.3 (Windows 10), Board: "Arduino/Genuino Uno"


" setPwmFrequency(9, 8);

^

exit status 1
'setPwmFrequency' was not declared in this scope"

What am I missing? Do i need to add a PWM library? do I need to set an int for the PWM?

Thanks,
Jon

PWM frequencies are based on clock ticks the higher the frequency the lower the resolution. the pins are either 8 bit 0-255 or 32 bit. in your case the 8 bit will be just as fast as the 32 bit because you will need to use a smaller count to get the max rates
the counter in the PWM starts counting from zero each clock tick. to get the max resolution of 8 bit you will want it to count to 255 if you need to have a higher frequency you would tell it to reset at a lower number say 128 and your PWM frequency will double. but your resolution is cut in half only having 128 steps now.
now with that insight as to how pwm works you will want to dig into and get familiar with the the timer portion of the datasheet for the atmega328p. without your interest in this portion of the data sheet the future posts will make little sense and we will not achieve your goals for higher frequency PWM.

this uses timer 1 and will set the PWM period

setPeriod(long microseconds)    // AR modified for atomic access
{
	long cycles = (F_CPU / 2000000) * microseconds;                                // the counter runs backwards after TOP, interrupt is at BOTTOM so divide microseconds by 2
	if (cycles < RESOLUTION)              clockSelectBits = _BV(CS10);             // no prescale, full xtal
	else if ((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11);             // prescale by /8
	else if ((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11) | _BV(CS10); // prescale by /64
	else if ((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12);             // prescale by /256
	else if ((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12) | _BV(CS10); // prescale by /1024
	else      cycles = RESOLUTION - 1,    clockSelectBits = _BV(CS12) | _BV(CS10); // request was out of bounds, set as maximum

	char oldSREG = SREG;
	cli();								// Disable interrupts for 16 bit register access
	ICR1 = pwmPeriod = cycles;			// ICR1 is TOP in p & f correct pwm mode
	SREG = oldSREG;
	TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));
	TCCR1B |= clockSelectBits;			// reset clock select register, and starts the clock
	return *this;
}

I hope this helps.
What are you using for your PID values?
how fast can your system pressurize?
Z

I will copy your code into IDE and study it to see what sense I can make of it. Although I am not educated in this area, I do obsess over things till i understand them (or at least to a point that gets me what I need).

At first I found both 'p' and 'd' values to be too sensitive to use and focused on the 'i' value. At first setting it to 5-10 and leaving p&d at zero, gave me the smoothest increase in pressure with the least pulsing. The issue is though that I need to produce up to 4200psi and currently am only able to steadily produce 1000psi at best before it starts slamming the valve.

I am now running p=10, i=75, and d=0.001 with probably the smoothest operation yet, but still only able to reach just over 1000psi before the valve starts slamming and the pressure fluctuates massively. Below is a link to a short video of the pressure banging and the machine that this pressure system is used in.

As you can see in the video, the end result is firing a HEUI diesel injector. HEUI injectors use an intensifier piston (using oil pressure) to mechanically increase the fuel pressure by 7 times. The intensifier piston has 7 times greater surface area than the fuel plunger. So at full oil pressure (4200psi) fuel pressure at the nozzle is approximately 29,400psi before losses.

To answer your question of how fast can it build pressure; I do not have a means to measure it, but I can say the pilot valve responds so fast I can peg out the 5000psi gauge instantly with the wrong PID settings. The oil pump (hydraulic pump) moves 0.37 In^3 per revolution and we are running at approximately 1440RPM, moving roughly 532 in^3 or 2.3 gallons per minute. When the injector fires it obviously consumes a small amount of oil, depending on the size of injector it can consume as much as 0.35in^3 per injection which the maximum injection event is 2.3 miliseconds. Our goal is to fire 4 injectors in sequence.

Hopefully I explained everything well enough, if anyone needs clarification, just ask.

-Jon

TheReelMuhcoy:
I will copy your code into IDE and study it to see what sense I can make of it. Although I am not educated in this area, I do obsess over things till i understand them (or at least to a point that gets me what I need).

At first I found both 'p' and 'd' values to be too sensitive to use and focused on the 'i' value. At first setting it to 5-10 and leaving p&d at zero, gave me the smoothest increase in pressure with the least pulsing. The issue is though that I need to produce up to 4200psi and currently am only able to steadily produce 1000psi at best before it starts slamming the valve.

I am now running p=10, i=75, and d=0.001 with probably the smoothest operation yet, but still only able to reach just over 1000psi before the valve starts slamming and the pressure fluctuates massively. Below is a link to a short video of the pressure banging and the machine that this pressure system is used in.

https://www.youtube.com/watch?v=UnL8NOVcCsc

As you can see in the video, the end result is firing a HEUI diesel injector. HEUI injectors use an intensifier piston (using oil pressure) to mechanically increase the fuel pressure by 7 times. The intensifier piston has 7 times greater surface area than the fuel plunger. So at full oil pressure (4200psi) fuel pressure at the nozzle is approximately 29,400psi before losses.

To answer your question of how fast can it build pressure; I do not have a means to measure it, but I can say the pilot valve responds so fast I can peg out the 5000psi gauge instantly with the wrong PID settings. The oil pump (hydraulic pump) moves 0.37 In^3 per revolution and we are running at approximately 1440RPM, moving roughly 532 in^3 or 2.3 gallons per minute. When the injector fires it obviously consumes a small amount of oil, depending on the size of injector it can consume as much as 0.35in^3 per injection which the maximum injection event is 2.3 miliseconds. Our goal is to fire 4 injectors in sequence.

Hopefully I explained everything well enough, if anyone needs clarification, just ask.

-Jon

Wow Nice your project is great! you have a fast reaction to change and so we should be able to speed things up. what seems to be happening is that the higher the pressure the larger the pressure sensor steps become.
If you haven't done so with my first suggestion lets not convert anything before we input it into the PID
keeping it at full resolution will help

  static unsigned long _ATimer;
  if ((millis() - _ATimer) >= (100)) { // decrease the amount of samples of setpoint to remove noise from this input
    _ATimer = millis();
    Setpoint = (float)analogRead(pot); // Setpoint is a value from 0 to 1024
  } 

 Input = (float) analogRead(ICP); //Get the pressure level is a value from 0 to 1024
 if (myPID.Compute()){ //Run the PID loop
  analogWrite(IPR, Output); //Write out the output from the PID loop to our LED pin
}

now your pid loop will have more to work with and since your setpoint is in the same range you will not need any conversions. also your setpoint is checked only 10 times a second
Kp Ki and Kd may need some adjustments. they may be about 2.4 times too big now.
also note that compute() only triggers once every 1ms the loop will trigger 100 times more often so changing analogWrite when compute doesn't trigger does no good.

Z

I inserted the 'input = (float)' code that you provided. It worked just fine, I was not able to notice any difference in operation just with changing the code yet. Tomorrow I will reduce the PID values by your suggested 2.4 times and adjust accordingly to find the best values.

I saved your code in a new sketch to review & understand it to the best of my ability, but that didn't get me very far... I believe I have a good understanding of the prescale divisors and how they produce the final frequencies in either Fast PWM or Phase Corrected PWM. Looking at the PWM code snippet you provided is the line below the line that actually dictates the frequency result?

TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));

If so, how would I read that to understand what my end result would be?

Is there any difference for my application which PWM mode I use? If I am understanding things correctly, if I use Phase Corrected PWM at a prescale of 8 I would end up with roughly 3900hz? This frequency is closest to my preferred frequency.

Here is another really short video of a test I ran yesterday:

Ive attached a photo of the manifold I made for the valve, pressure sensor and all the lines. The valve is the top device and as we call it the ICP or Injector Control Pressure sensor is on the bottom, just a typical automotive 5v reference sensor i believe.

And lastly the reason for all this hard and confusing work... This is one of the reasons we are building and testing these injectors for, this is a truck that I have been working on for a few years, at the time of the video it was pushing around 550-600HP. Looking to close to double that soon.

TheReelMuhcoy:
I inserted the 'input = (float)' code that you provided. It worked just fine, I was not able to notice any difference in operation just with changing the code yet. Tomorrow I will reduce the PID values by your suggested 2.4 times and adjust accordingly to find the best values.

I saved your code in a new sketch to review & understand it to the best of my ability, but that didn't get me very far... I believe I have a good understanding of the prescale divisors and how they produce the final frequencies in either Fast PWM or Phase Corrected PWM. Looking at the PWM code snippet you provided is the line below the line that actually dictates the frequency result?

TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));

If so, how would I read that to understand what my end result would be?

Is there any difference for my application which PWM mode I use? If I am understanding things correctly, if I use Phase Corrected PWM at a prescale of 8 I would end up with roughly 3900hz? This frequency is closest to my preferred frequency.

Here is another really short video of a test I ran yesterday:
https://www.youtube.com/watch?v=0PJlVQuuvgo

Ive attached a photo of the manifold I made for the valve, pressure sensor and all the lines. The valve is the top device and as we call it the ICP or Injector Control Pressure sensor is on the bottom, just a typical automotive 5v reference sensor i believe.

And lastly the reason for all this hard and confusing work... This is one of the reasons we are building and testing these injectors for, this is a truck that I have been working on for a few years, at the time of the video it was pushing around 550-600HP. Looking to close to double that soon.
https://www.youtube.com/watch?v=1sMGq_asF6M

ICR1 is also needed to get to your exact frequency

  long cycles = (F_CPU / 2000000) * microseconds;                                // the counter runs backwards after TOP, interrupt is at BOTTOM so divide microseconds by 2
  if(cycles < RESOLUTION)              clockSelectBits = _BV(CS10);              // no prescale, full xtal
  TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12)); // Clears the clock select register to TCCR1B = ?????000
  TCCR1B |= clockSelectBits;			  // reset clock select register, and starts the clock
// since we want as fast as possible we will only set  the Bit Value of CS10 which is B00000001
// TCCR1B now equals B?????001  Question marks are for other register values that we don't want to change.

// Now we need to trigger when the timer reverses direction this will shorten the amount of time before the PWN starts over
  ICR1 = cycles;			// ICR1 is TOP in p & f correct pwm mode

So as I understand it (Please someone correct me) in Phase corrected mode the counter counts at a fixed frequency (in the above case at full xtal speed) till it matches ICR1 then reverses till it reaches Zero this is the PWN frequency
The duty cycle is determined simply by any counter value above OCR1x produces a HIGH on the pin and values below produce LOW

OCR1x The Output Compare Registers contain a 16-bit value that is continuously compared with the counter value (TCNT1). A match can be used to generate an Output Compare interrupt, or to generate a waveform output on the OC1x pin.

here are some other code snips that will help:

SetMode(int mode)
{
	//Clear all timer mode bits
	TCCR1A &= ~(_BV(WGM10) | _BV(WGM11));
	TCCR1B &= ~(_BV(WGM13) | _BV(WGM12));
	switch(mode){
		case 1:
		TCCR1A |= _BV(WGM10);				// PWM, Phase Correct, 8-bit 0x00FF TOP BOTTOM
		break;
		case 2:
		TCCR1A |= _BV(WGM11);				// PWM, Phase Correct, 9-bit 0x01FF TOP BOTTOM
		break;
		case 3:
		TCCR1A |= _BV(WGM10) | _BV(WGM11);	// PWM, Phase Correct, 10-bit 0x03FF TOP BOTTOM
		break;
		case 4:
		TCCR1B |= _BV(WGM12) ;				// CTC OCR1A Immediate MAX
		break;
		case 5:
		TCCR1B |= _BV(WGM12) ;				// Fast PWM, 8-bit 0x00FF BOTTOM TOP
		TCCR1A |= _BV(WGM10);				// ditto
		break;
		case 6:
		TCCR1B |= _BV(WGM12) ;				// Fast PWM, 9-bit 0x01FF BOTTOM TOP
		TCCR1A |= _BV(WGM11);				// ditto
		break;
		case 7:
		TCCR1B |= _BV(WGM12) ;				// Fast PWM, 10-bit 0x03FF BOTTOM TOP
		TCCR1A |= _BV(WGM10) | _BV(WGM11);	// ditto
		break;
		case 8:
		TCCR1B |= _BV(WGM13) ;				// PWM, Phase and Frequency Correct ICR1 BOTTOM BOTTOM
		break;
		case 9:
		TCCR1B |= _BV(WGM13) ;				// PWM, Phase and Frequency Correct OCR1A BOTTOM BOTTOM
		TCCR1A |= _BV(WGM10);				// ditto
		break;
		case 10:
		TCCR1B |= _BV(WGM13) ;				// PWM, Phase Correct ICR1 TOP BOTTOM
		TCCR1A |= _BV(WGM11);				// ditto
		break;
		case 11:
		TCCR1B |= _BV(WGM13) ;				// PWM, Phase Correct OCR1A TOP BOTTOM
		TCCR1A |= _BV(WGM10) | _BV(WGM11);	// ditto
		break;
		case 12:
		TCCR1B |= _BV(WGM12) |_BV(WGM13) ;	// CTC ICR1 Immediate MAX
		break;
		case 14:
		TCCR1B |= _BV(WGM12) |_BV(WGM13) ;	// Fast PWM ICR1 BOTTOM TOP
		TCCR1A |=  _BV(WGM11);				// ditto
		break;
		case 15:
		TCCR1B |= _BV(WGM12) |_BV(WGM13) ;	// Fast PWM OCR1A BOTTOM TOP
		TCCR1A |= _BV(WGM10) | _BV(WGM11);	// ditto
		break;				
	}
	return *this;
}
void setPwmDuty(char pin, int duty)
{
  unsigned long dutyCycle = pwmPeriod;
  
  dutyCycle *= duty;
  dutyCycle >>= 10;
  
  oldSREG = SREG;
  cli();
  if(pin == 1 || pin == 9)       OCR1A = dutyCycle;
  else if(pin == 2 || pin == 10) OCR1B = dutyCycle;
  SREG = oldSREG;
}

Ive attached a photo of the manifold I made for the valve, pressure sensor and all the lines. The valve is the top device and as we call it the ICP or Injector Control Pressure sensor is on the bottom, just a typical automotive 5v reference sensor i believe.

Thinking about the oscillations at higher pressures how hard would it be to create pressure vessel or accumulator to provide a larger volume of liquid to manage. This could be an air bladder above or possibly a liquid container. My though is that if you had a compressible item like air (Air may not be good around fuel lol) or a larger volume of fuel in the accumulator to compress, your ability to stabilize under higher pressures would increase.

Z

I am just in way over my head.... lol

Where is the timer code to be inserted? Up top with the variables? or is it to go into the void setup()?

I tried to use the following that you provided, I honestly was not able to understand much of this but have been trying to do more research to understand more...

zhomeslice:
this uses timer 1 and will set the PWM period

setPeriod(long microseconds)    // AR modified for atomic access

{
long cycles = (F_CPU / 2000000) * microseconds;                                // the counter runs backwards after TOP, interrupt is at BOTTOM so divide microseconds by 2
if (cycles < RESOLUTION)              clockSelectBits = _BV(CS10);            // no prescale, full xtal
else if ((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11);            // prescale by /8
else if ((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11) | _BV(CS10); // prescale by /64
else if ((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12);            // prescale by /256
else if ((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12) | _BV(CS10); // prescale by /1024
else      cycles = RESOLUTION - 1,    clockSelectBits = _BV(CS12) | _BV(CS10); // request was out of bounds, set as maximum

char oldSREG = SREG;
cli();								// Disable interrupts for 16 bit register access
ICR1 = pwmPeriod = cycles;			// ICR1 is TOP in p & f correct pwm mode
SREG = oldSREG;
TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));
TCCR1B |= clockSelectBits;			// reset clock select register, and starts the clock
return *this;

}

Do you, or anyone, have a suggestion of a Arduino Sketch/C++ for dummies kind of tutorial so that I can try to understand some of the basics? I feel this may be just too complicated for me to try to jump into it neck deep and expect to figure it out.

TheReelMuhcoy:
I am just in way over my head.... lol

Where is the timer code to be inserted? Up top with the variables? or is it to go into the void setup()?

I tried to use the following that you provided, I honestly was not able to understand much of this but have been trying to do more research to understand more...
Do you, or anyone, have a suggestion of a Arduino Sketch/C++ for dummies kind of tutorial so that I can try to understand some of the basics? I feel this may be just too complicated for me to try to jump into it neck deep and expect to figure it out.

changing frequencies is difficult and It was extremely difficult for me to grasp it. for now because this is on the difficulty level of 10 and probably one of the hardest things we may have to deal with lets focus on the PID and how it affects the valve.

Questions:

Do you have more information on how the pilot operated valve works. mabe a part number or spec sheet. I am interested in how this functions.

I think this next question will help me understand how much control your valve has with the current PWM frequencies. your problem may be more in the PID control than the PWM frequency.
If you were to manually ramp up the PWM on the valve could you set the pressure to any pressure and not have it oscillate. for example PWM setting of 50 what happens and PWM of 200 what happens? Are the readings stable? is there other times that the readings are unstable with a fixed PWM value?

An Idea to help stabilize the control:
can we average 100 sensor readings together (as fast as possible) and use this value for a stable input into the pid. having a stable reading is very important. any unwanted fluctuation in the input degrades the effectiveness of the PID enen dampening the input will give you better control overall.

What are the limits of the actual control.
Having the pressure stable seems to be important but may not be critical. how much fluctuation can we have with the pressure?

Z

The valve we are using is actually the exact part from the vehicle this system is used in. I am not sure how much info we will be able to get out of Ford/International on these. Here are the part numbers though:
Ford - 5C3Z9C968CA
Motorcraft - CM5126
International - 1846057C1

I tried to look around on Google to see if I could find any information on it using the part number. I have also tried looking it up by what it is called "IPR valve from a 6.0L powerstroke" here is the google search that provided the most information for me was this 6.0L Powerstroke IPR PWM frequency

As far as maximum fluctuation of pressure, I think we can fluctuate safely +/- 100psi

I tried to apply the same code that we used for the input value to the ICP sensor, thinking maybe I can reduce the sampling rate of the sensor itself while maintaining the PID sample rate.

  static unsigned long _ATimer;
  if ((millis() - _ATimer) >= (100)) { // decrease the amount of samples of setpoint to remove noise from this input
    _ATimer = millis();
    Setpoint = (float)analogRead(pot); // Setpoint is a value from 0 to 1024
  } 

 Input = (float) analogRead(ICP); //Get the pressure level is a value from 0 to 1024
 if (myPID.Compute()){ //Run the PID loop
  analogWrite(IPR, Output); //Write out the output from the PID loop to our LED pin
}

I suppose I could try to use a simple sketch that runs a set PWM signal and manually change it to see what happens.... I will attempt to put together a sketch to do so. I'll see what I can do this evening.

I have also tried the following example from Arduino Playground - HomePage
Code: [Select]

setPwmFrequency(9, 8);

which when used in my void setup does not verify and gives the following error:
Arduino: 1.8.3 (Windows 10), Board: "Arduino/Genuino Uno"

setPwmFrequency(9, 8);

^

exit status 1
'setPwmFrequency' was not declared in this scope"

What am I missing? Do i need to add a PWM library? do I need to set an int for the PWM?

You need to add the functionvoid setPwmFrequency(int pin, int divisor)presented in the tutorial to your code. You are correct to move the IPR output to pin 9 (Timer1) to avoid changing the millis() timing. The divide 8 will give a base frequency of 3920. You can use analogWrite(9,output).

/**
 * Divides a given PWM pin frequency by a divisor.
 * 
 * The resulting frequency is equal to the base frequency divided by
 * the given divisor:
 *   - Base frequencies:
 *      o The base frequency for pins 3, 9, 10, and 11 is 31250 Hz.
 *      o The base frequency for pins 5 and 6 is 62500 Hz.
 *   - Divisors:
 *      o The divisors available on pins 5, 6, 9 and 10 are: 1, 8, 64,
 *        256, and 1024.
 *      o The divisors available on pins 3 and 11 are: 1, 8, 32, 64,
 *        128, 256, and 1024.
 * 
 * PWM frequencies are tied together in pairs of pins. If one in a
 * pair is changed, the other is also changed to match:
 *   - Pins 5 and 6 are paired on timer0
 *   - Pins 9 and 10 are paired on timer1
 *   - Pins 3 and 11 are paired on timer2
 * 
 * Note that this function will have side effects on anything else
 * that uses timers:
 *   - Changes on pins 3, 5, 6, or 11 may cause the delay() and
 *     millis() functions to stop working. Other timing-related
 *     functions may also be affected.
 *   - Changes on pins 9 or 10 will cause the Servo library to function
 *     incorrectly.
 * 
 * Thanks to macegr of the Arduino forums for his documentation of the
 * PWM frequency divisors. His post can be viewed at:
 *   http://forum.arduino.cc/index.php?topic=16612#msg121031
 */
void setPwmFrequency(int pin, int divisor) {
  byte mode;
  if(pin == 5 || pin == 6 || pin == 9 || pin == 10) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 64: mode = 0x03; break;
      case 256: mode = 0x04; break;
      case 1024: mode = 0x05; break;
      default: return;
    }
    if(pin == 5 || pin == 6) {
      TCCR0B = TCCR0B & 0b11111000 | mode;
    } else {
      TCCR1B = TCCR1B & 0b11111000 | mode;
    }
  } else if(pin == 3 || pin == 11) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 32: mode = 0x03; break;
      case 64: mode = 0x04; break;
      case 128: mode = 0x05; break;
      case 256: mode = 0x06; break;
      case 1024: mode = 0x07; break;
      default: return;
    }
    TCCR2B = TCCR2B & 0b11111000 | mode;
  }
}

I have to face that some things just cant be rushed, even though I tend to think otherwise.

I have included the code the way I believe it needs to be but I have not noticed any difference in how the valve functions while leaving all the PID settings as they were. I should also probably mention that the valve operates at 12v so I have pin 9 feeding a SSR with the ground switched by the SSR (as it would be in its intended application. will the SSR affect the frequency and PWM? Or is there still something off in my sketch?

#include <PID_v1.h>


const int ICP = A0; // ICP Sensor input
const int pot = A1; // Potentiometer input
const int IPR = 9; // IPR valve
double ICPLevel; // variable that stores the incoming ICP level

void setPwmFrequency(int pin, int divisor) {
  byte mode;
  if(pin == 5 || pin == 6 || pin == 9 || pin == 10) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 64: mode = 0x03; break;
      case 256: mode = 0x04; break;
      case 1024: mode = 0x05; break;
      default: return;
    }
    if(pin == 5 || pin == 6) {
      TCCR0B = TCCR0B & 0b11111000 | mode;
    } else {
      TCCR1B = TCCR1B & 0b11111000 | mode;
    }
  } else if(pin == 3 || pin == 11) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 32: mode = 0x03; break;
      case 64: mode = 0x04; break;
      case 128: mode = 0x05; break;
      case 256: mode = 0x06; break;
      case 1024: mode = 0x07; break;
      default: return;
    }
    TCCR2B = TCCR2B & 0b11111000 | mode;
  }
}

// Tuning parameters
float Kp=65; //Initial Proportional Gain Last effective 120 but ran IPR hot
float Ki=75; //Initial Integral Gain last effective 100 but ran IPR hot
float Kd=.1; //Initial Differential Gain
double Setpoint, Input, Output; //These are just variables for storingvalues
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
// This sets up our PDID Loop
//Input is our PV
//Output is our u(t)
//Setpoint is our SP
const int sampleRate = 1; // Variable that determines how fast our PID loop
// runs
// Communication setup
const long serialPing = 500; //This determines how often we ping our loop
// Serial pingback interval in milliseconds
unsigned long now = 0; //This variable is used to keep track of time
// placehodler for current timestamp
unsigned long lastMessage = 0; //This keeps track of when our loop last
// spoke to serial
// last message timestamp.

void setup(){

 setPwmFrequency(9, 8);
  
 ICPLevel = analogRead(ICP); //Read pressure level
  
  static unsigned long _ATimer;
  if ((millis() - _ATimer) >= (100)) { // decrease the amount of samples of setpoint to remove noise from this input
    _ATimer = millis();
    Setpoint = (float)analogRead(pot); // Setpoint is a value from 0 to 1024
} 

 Input = (float) analogRead(ICP); //Get the pressure level is a value from 0 to 1024
 if (myPID.Compute()){ //Run the PID loop
  analogWrite(IPR, Output); //Write out the output from the PID loop to our LED pin
}
 Serial.begin(9600); //Start a serial session
 myPID.SetMode(AUTOMATIC); //Turn on the PID loop
 myPID.SetSampleTime(sampleRate); //Sets the sample rate

 Serial.println("Begin"); // Hello World!
 lastMessage = millis(); // timestamp
}
void loop(){
 Setpoint = map(analogRead(pot), 0, 1024, 0, 255); //Read our setpoint
 ICPLevel = analogRead(ICP); //Get the pressure level
 Input = map(ICPLevel, 0, 1024, 0, 255); //Map it to the right scale
 myPID.Compute(); //Run the PID loop
 analogWrite(IPR, Output); //Write out the output from the PID loop to our LED pin

 now = millis(); //Keep track of time
 if(now - lastMessage > serialPing) { //If it has been long enough give us
// some info on serial
 // this should execute less frequently
 // send a message back to the mother ship
 Serial.print("Setpoint = ");
 Serial.print(Setpoint);
 Serial.print(" Input = ");
 Serial.print(Input);
 Serial.print(" Output = ");
 Serial.print(Output);
 Serial.print("\n");
 if (Serial.available() > 0) { //If we sent the program a command deal
 // with it
 for (int x = 0; x < 4; x++) {
 switch (x) {
 case 0:
 Kp = Serial.parseFloat();
 break;
 case 1:
 Ki = Serial.parseFloat();
 break;
 case 2:
 Kd = Serial.parseFloat();
 break;
 case 3:
 for (int y = Serial.available(); y == 0; y--) {
 Serial.read(); //Clear out any residual junk
 }
 break;
 }
 }
 Serial.print(" Kp,Ki,Kd = ");
 Serial.print(Kp);
 Serial.print(",");
 Serial.print(Ki);
 Serial.print(",");
 Serial.println(Kd); //Let us know what we just received
 myPID.SetTunings(Kp, Ki, Kd); //Set the PID gain constants and start
 // running
 }

 lastMessage = now;
 //update the time stamp.
 }

}

I suppose I could try to use a simple sketch that runs a set PWM signal and manually change it to see what happens.

Now that you have your pwm at the higher frequency, did you do this simple test of monitoring the performance at different fixed duty cycles with out any PID control.

12v so I have pin 9 feeding a SSR with the ground switched by the SSR (as it would be in its intended application. will the SSR affect the frequency and PWM? Or is there still something off in my sketch?

Yes SSR usually require AC and not 12v DC do you have a part number for the SSR.

Cattledog,
I am not so sure i have the frequency set. Although my sketch passed the verification, I did not notice any difference in performance of the valve with the PID\PWM sketch. I am going to go give it another shot and try some other frequency settings.

Question... Can I simplify things by having two sketch tabs one for PID and one for PWM? Or does it not work that way?

Another thought... because I am using a SSR does my output need to be HIGH / LOW? I have seen other PID progams that are designed for specific use with relays that used a HIGH / LOW output?

The photo is of the SSR I am using. It is a DC/DC SSR, its a cheap one from ebay, but seems to be doing the job, I just want to be sure it is not limiting me.

The photo is of the SSR I am using. It is a DC/DC SSR, its a cheap one from ebay, but seems to be doing the job, I just want to be sure it is not limiting me.

There is not much to find on the internet about this relay. but this spec indicates that it might be slow, with 10 ms response time. 100Hz operation.

You may need to find another way to control your valve.

https://www.aliexpress.com/store/product/Mager-SSR-10A-DC-DC-Solid-state-relay-Quality-Goods-MGR-1-DD220D10/619076_1468012429.html