Hi, I've been reading through various links and tutorials on the PWM function, and I've a question nobody else seems to have asked.
Is it possible to lower the pwm frequency to about 3 to 5 hz? Yes, I know that's low, but this is to control a mechanical solenoid.
This is a project for a closed loop fuel controller for a propane carburetor on a vehicle, and the solenoid is not really capable of much higher than about 5 hz, and I could use anywhere from 2.5 to 6, and probably be fine.
I was going to use a long loop of logic conditions to check the air fuel ratio sensor voltage and use it to adjust each cycle using the digitalWrite() function and wait() using variables created using analogRead() to compare against the desired sensor readings and adjust the on and off times accordingly. However, I would much prefer to use the PWM function and have my sensor read more like 20 to 50 times a second and change the pwm duty cycle every so often, using much more sensible logic.
However, as I read through the stuff online, I noticed everyone had much higher frequency needs than I, and it appears to me from the examples you may not be able to set it that low.
Anything useful appreciated.
Thanks.
Edit: This is my first attempt at anything even remotely like this. I can muddle through the C - like language I think, but I'm a network administrator and auto mechanic, this stuff is a bit new to me.
Timer 1 can be set up to run really slowly. Its clock can be set to C, C/8, C/64, C/256 or C/1024 where C is the system clock (normally 16 MHz). Its a 16 bit counter so the PWM frequency can go down to 1/65536 times its clock rate. So for C/1024 it clocks at 15.625kHz and can PWM down to 0.238Hz (period about 4.2 seconds).
Normally it is configured as C/64 with a cycle of 510 clocks (phase correct 8 bit mode) - 490Hz approx.
Change this to C/1024 (or even C/256) and use a longer period (use modes 10 or 14 and set ICR1). You'll have to replace calls to analogWrite with direct setting of OCR1A and OCR1B (pins 9 and 10 respectively).
mdkoskenmaki:
I was going to use a long loop of logic conditions to check the air fuel ratio sensor voltage and use it to adjust each cycle using the digitalWrite() function and wait() using variables created using analogRead() to compare against the desired sensor readings and adjust the on and off times accordingly. However, I would much prefer to use the PWM function and have my sensor read more like 20 to 50 times a second and change the pwm duty cycle every so often, using much more sensible logic.
I think your idea of using the existing PWM outputs would probably give you the best result, but if you prefer to avoid playing with the timer configuration then what you're trying to achieve could be achieved quite simply within the sketch itself.
The idea would be that instead of having a loop() function that completes a full PWM cycle, you write a loop function that completes very quickly and just does a couple of things each time through:
If it is time to read the EGO sensor then read and store the sensor value and do any processing necessary to recalculate the PWM settings.
If it is time to change the PWM output state then change the output state.
Both these functions would be controlled using the technique demonstrated in the 'blink without delay' sample sketch i.e. have a variable that holds the time you last did , and compare this against current time to see whether it's time to do again.
MarkT:
Timer 1 can be set up to run really slowly. Its clock can be set to C, C/8, C/64, C/256 or C/1024 where C is the system clock (normally 16 MHz). Its a 16 bit counter so the PWM frequency can go down to 1/65536 times its clock rate. So for C/1024 it clocks at 15.625kHz and can PWM down to 0.238Hz (period about 4.2 seconds).
Normally it is configured as C/64 with a cycle of 510 clocks (phase correct 8 bit mode) - 490Hz approx.
Change this to C/1024 (or even C/256) and use a longer period (use modes 10 or 14 and set ICR1). You'll have to replace calls to analogWrite with direct setting of OCR1A and OCR1B (pins 9 and 10 respectively).
Note only timer1 is 16 bit and thus able to go slow enough, and it only controls PWM pins 9 and 10.
The alternative is software PWM, note.
Wow, for me this is a straight vertical learning curve. I have no, and I mean, NO experience with digital electronics, much less have any signficant programming skills, so this will take me a bit to see if I can work it out. ( I expected as much )
I'm going to have to find and see this in action, before I can make heads or tails of how to do it. I understand the logic and why it works, but how to implement's a bit beyond me at the moment .
I very much appreciate the input. This is very helpful.
PeterH:
I think your idea of using the existing PWM outputs would probably give you the best result, but if you prefer to avoid playing with the timer configuration then what you're trying to achieve could be achieved quite simply within the sketch itself.
The idea would be that instead of having a loop() function that completes a full PWM cycle, you write a loop function that completes very quickly and just does a couple of things each time through:
If it is time to read the EGO sensor then read and store the sensor value and do any processing necessary to recalculate the PWM settings.
If it is time to change the PWM output state then change the output state.
Both these functions would be controlled using the technique demonstrated in the 'blink without delay' sample sketch i.e. have a variable that holds the time you last did , and compare this against current time to see whether it's time to do again.
The logic loop is definitely good, but how to make use of this without using timer change would be rather complex.
How often to "do" something is useful, I appreciate the heads up, but how to use that loop to change the ratio of on vs off time, while keeping the total of on + off the same is not something that's readily apparent to me. I"ll let that stir around in the brain a while and see if I get inspired.
MarkT:
Change this to C/1024 (or even C/256) and use a longer period (use modes 10 or 14 and set ICR1). You'll have to replace calls to analogWrite with direct setting of OCR1A and OCR1B (pins 9 and 10 respectively).
Well, I comprehend pretty much everything else you wrote, but the last sentence above is where I just get lost. I can find code examples of how to set the pre-scaler, but what else to do has got me completely lost. I can't seem to find anything that clears it up for me.
PeterH:
I think your idea of using the existing PWM outputs would probably give you the best result, but if you prefer to avoid playing with the timer configuration then what you're trying to achieve could be achieved quite simply within the sketch itself.
The idea would be that instead of having a loop() function that completes a full PWM cycle, you write a loop function that completes very quickly and just does a couple of things each time through:
If it is time to read the EGO sensor then read and store the sensor value and do any processing necessary to recalculate the PWM settings.
If it is time to change the PWM output state then change the output state.
Both these functions would be controlled using the technique demonstrated in the 'blink without delay' sample sketch i.e. have a variable that holds the time you last did , and compare this against current time to see whether it's time to do again.
The logic loop is definitely good, but how to make use of this without using timer change would be rather complex.
How often to "do" something is useful, I appreciate the heads up, but how to use that loop to change the ratio of on vs off time, while keeping the total of on + off the same is not something that's readily apparent to me. I"ll let that stir around in the brain a while and see if I get inspired.
Thanks.
It's very simple.
-One variable holds the number of milliseconds per pulse. 5 Hz would be 200 milliseconds. More to slow it down, less to speed it up. Or if you open and close 5 times per second then 100 milliseconds between changes, right?
I'll call it changeValveMillis.
-One variable holds the last time the valve changed.
I'll call that lastChangeMillis and make a companion variable called nowMillis.
All of those will be unsigned longs as that's what the millis() function (milliseconds since startup) returns, and there's a great 'feature' where the result is always right even when the counter 'rolls over' in about 50 days.
-Each time loop() runs you set nowMillis = millis(). Then you check if nowMillis - lastChangeMillis >= changeValveMillis. If it is then change the valve state (open to close or vice-versa). If it isn't then do the next thing like check the EGR and possibly change valveChangeMillis to adjust your engine.
You see, the loop() may run many 100's of times between changing that valve once.
-One variable holds the number of milliseconds per pulse. 5 Hz would be 200 milliseconds. More to slow it down, less to speed it up. Or if you open and close 5 times per second then 100 milliseconds between changes, right?
I'll call it changeValveMillis.
-One variable holds the last time the valve changed.
I'll call that lastChangeMillis and make a companion variable called nowMillis.
All of those will be unsigned longs as that's what the millis() function (milliseconds since startup) returns, and there's a great 'feature' where the result is always right even when the counter 'rolls over' in about 50 days.
-Each time loop() runs you set nowMillis = millis(). Then you check if nowMillis - lastChangeMillis >= changeValveMillis. If it is then change the valve state (open to close or vice-versa). If it isn't then do the next thing like check the EGR and possibly change valveChangeMillis to adjust your engine.
You see, the loop() may run many 100's of times between changing that valve once.
I can see how this gets me a timer that happens every x number of miliseconds, but I don't see how I can vary the percentage of on vs off, while not changing the frequency. I haven't conjured up the process yet.
readOxy = analogRead(sensorPin); // check O2 sensor status
error = readOxy - targetO2; // find whether too rich or lean
// now, look at error and adjust duty cycle
if ( error > 50 ){
off = off - 12;
}
if ( error > 20 ) {
off = off - 5;
}
if ( error < -20 ) {
off = off + 12;
}
if ( error < -80 ) {
off = off + 70;
}
// make sure that error doesn't accumulate, leave tiny on.
if ( off > 150 ) {
off = 145 ;
}
// don't allow less than 20 off.
if (off < 20) {
off = 20 ;
}
// warning for continuous rich or lean status going on.
if ( error > 80 ) {
richwarn = richwarn + 10;
} // if pegged rich, ratchet warning up
if ( error < 80 ) {
richwarn = richwarn - 12;
} // if not pegged rich, move away from warning status
if ( error > -80 ) {
leanwarn = leanwarn - 20;
} // if not pegged lean, reduce warning count FAST
if (error < -80 ) {
leanwarn = leanwarn + 10;
} // if pegged lean, raise warning count.
// now make sure that we don't go below 0 warning count
if (leanwarn < 0 ) {
leanwarn = 0;
}
if (richwarn < 0 ) {
richwarn = 0 ;
}
// ok, now don't let rich or lean accumulate
if (leanwarn > 1200 ) {
leanwarn = 1200;
}
if (richwarn > 3000 ) {
richwarn = 3000 ;
}
// ok, now flip on warning lights if prolonged rich or lean
if (leanwarn > 1200) {
digitalWrite(11,HIGH);
}
// turn it off if needed
if (leanwarn < 1200) {
digitalWrite(11,LOW);
}
// do the same for rich as above
if (richwarn > 3000) {
digitalWrite(10,HIGH);
}
// turn it off if needed
if (richwarn < 3000) {
digitalWrite(10,LOW);
}
It uses timer1 and supports PWM periods up to around 4 seconds.
Jim
Thanks, I grabbed the PWM16 files and now will have to relearn how to use them... (sigh). I once took a class in "c" at a local CC. I think I learned how to write the "hello world" program by its end. Ok, that was a lame attempt at humor, it did cover a slight bit more, but I think I spent most of my time trying to make sure that half of the other students didn't fail or drop out so the class wouldn't be cancelled. That was like 9 or 10 years ago, so I'm worse than "rusty".
I thought that the valve was only good for 5 Hz (5 open-close cycles per second) which would be a minimum of 100 milliseconds (1/10th second) between start of one movement and start of the next, yet you have minimum off time at 20 milliseconds. Sure, the instruction will run but that doesn't guarantee the valve will move that fast.
You can use else if to reduce the number of if's the code has to process.
example:
if ( error > 50 ) {
off = off - 12;
}
else if ( error > 20 ) {
off = off - 5;
}
else if ( error < -20 ) {
off = off + 12;
}
else if ( error < -80 ) {
off = off + 70;
}
BTW, the IDE copy code for forum button gets the html tags wrong and the code won't all always show up right. Instead of quote and /quote inside the brackets you want to use code and /code. There's the # button in the forum post editor that makes them, next to the word balloon button that makes quote tags.
And here's something you might like. There's leds with 3 leads that can be red, green, or amber (just using full power, with PWM there's a full range of red/green mix), and don't cost much. More expensive are 4 lead RGB leds that can be pretty much any color you want. So your two warning lights can be one light that too hot is red, too cold is green and just right is amber or even tell you just how much hot/cold you're running. With RGB led you could get real creative.
GoForSmoke:
If what you have works at all then it's not bad.
I thought that the valve was only good for 5 Hz (5 open-close cycles per second) which would be a minimum of 100 milliseconds (1/10th second) between start of one movement and start of the next, yet you have minimum off time at 20 milliseconds. Sure, the instruction will run but that doesn't guarantee the valve will move that fast.
You can use else if to reduce the number of if's the code has to process.
example:
if ( error > 50 ) {
off = off - 12;
}
else if ( error > 20 ) {
off = off - 5;
}
else if ( error < -20 ) {
off = off + 12;
}
else if ( error < -80 ) {
off = off + 70;
}
Thanks for the help.
Yeah, I put in a min 20 so I could see if the LED blinks
My code runs so slow that it appears with only 150 total wait, I appear to get a little more than 2 cycles per second. So, back to the drawing board.
But yes, it does run, and it starts out with minimal flash and in a few cycles makes it to full "on" which is apparently correct, as there's no input on the right pin now.
I have to set up the device in a box, use a darlington as a "relay" to fire the solenoid, add in some inductive kick protection, and noise suppression for the O2 sensor input, etc.
but I think I'm going to try out the pwm16 library first and see what I can do with it. Are "if" statements slow to execute? Are math operations fast?
What little code you have there runs in about no time flat -compared to the delays-.
The next longest thing are the analog reads which take about 100 microseconds (1/10th of a millisecond) each.
At 16 MHz the Arduino runs 16000 cycles per millisecond with many instructions taking 1 or few cycles but some you write one word and many things happen for that. To make an if happen, numbers must be loaded into registers on the CPU and then it takes a cycle to compare them. Of course if one number is already still in a register from last compare it won't take as long to do the next. Still, to do a few extra compares that the first has made unnecessary does not help but it's no more than a scratch next to a 1 millisecond delay -- but the scratches can add up like nickels and dimes.
One thing to look at and improve is that every time through loop() you have a long cycle of this-this-this-this ending with delays. But except for the delays it's not really loaded down yet.
A 20 millisecond delay is so short a led turned on and off that fast flickers. On for 20 ms, off for 130... maybe you should step back and make a variable time led blink just to see and know what speeds you are using.
Perhaps also to find a way to know the real speed your engine runs at, does it have a tachometer and is it 2 stroke or 4 stroke or something else? Is it a 1-cylinder engine? Some of what you write, I think you have documentation or that kind of knowledge or some numbers you believe. Just to say the valve runs at certain speeds, you must have done some tests or have documents there.
At 10 cycles a second, a single cycle is a long time to an Arduino running short code.
Me, I would arrange it so the analog reads are done in between waiting to open or close the valve and take averages of those reads or even watch the rise and fall of O2 during the periods that the vale is open or closed. I would do this instead of waiting for the valve to cycle before taking a single read then basing knowledge of how the engine is running on that.
There is no need to do everything every time that loop() runs. In fact, it is counter-productive to run that way. To do one thing in one execution of loop() and something different the next with priority of time critical tasks is a better way that keeps doing-nothing tasks from holding up doing-something tasks, which is one form of 'blocking code'.
You can unblock simply by watching the time and operating the valve as needed or you can run the PWM library... either way will get rid of those delays that block your code from doing anything more than 99% of the time it runs. You already compute the on and off time and have to either way but it's a mystery to me why you'd want to run the rather large software PWM library when you can check time and do a digital write in a very few lines of code at all. But hey, it's your project!
GoForSmoke:
Perhaps also to find a way to know the real speed your engine runs at, does it have a tachometer and is it 2 stroke or 4 stroke or something else? Is it a 1-cylinder engine? Some of what you write, I think you have documentation or that kind of knowledge or some numbers you believe. Just to say the valve runs at certain speeds, you must have done some tests or have documents there.
At 10 cycles a second, a single cycle is a long time to an Arduino running short code.
Me, I would arrange it so the analog reads are done in between waiting to open or close the valve and take averages of those reads or even watch the rise and fall of O2 during the periods that the vale is open or closed. I would do this instead of waiting for the valve to cycle before taking a single read then basing knowledge of how the engine is running on that.
There is no need to do everything every time that loop() runs. In fact, it is counter-productive to run that way. To do one thing in one execution of loop() and something different the next with priority of time critical tasks is a better way that keeps doing-nothing tasks from holding up doing-something tasks, which is one form of 'blocking code'.
You can unblock simply by watching the time and operating the valve as needed or you can run the PWM library... either way will get rid of those delays that block your code from doing anything more than 99% of the time it runs. You already compute the on and off time and have to either way but it's a mystery to me why you'd want to run the rather large software PWM library when you can check time and do a digital write in a very few lines of code at all. But hey, it's your project!
One of the reasons I wanted to use the PWM output is that I have another task or two I'd like to add, which are unrelated to the O2 readings and are sensor reads with no pwm, just on-off operation. Mixing all this into loops designed for other tasks gets some rather nasty nesting and for me, with no programming experience, it just gets really, REALLY hard to keep straight. I'm sure I can do better after I fiddle with this. As it is, I wrote what I have in less than 2 hours of installing the IDE and by looking at the reference pages and some examples. If I could see an example of someone doing something like this, it would help ( mean, the code created duty cycle and adjustments).
I will confess that I did take a BASIC class back in the early 80's. And it was not exactly inspiring, what we accomplished. I do appreciate the input, as it shows me what experienced people think like... and that helps a lot.