4-wire PWM DC fan tacho rpm c code ATmega328pb

Go To Last Post
31 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I need to monitor the rpm speed of the tacho wire of a 4-wire dc fan using an ATmega328pb programmed in C. 
Any idea on how to write a c code to get the rpm feedback from the tach wire ? All I know is that I need to use one of the External interrupts PCINT[22:21] and the timer1 T1.
I am a beginner in microcontroller programmation, this is my first project, any advice is welcomed.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I need to monitor the rpm speed of the tacho wire

Welcome to AVRFreaks, where many questions are asked and answered

 

Monitor how?  Show that it is pulsing and light up an LED?  Send out a message on the uart?  Send out a time value or speed on the uart?  Display RPM on an LED display?  There are many ways to monitor, so consider you options.  Have you worked the timers at all? You should first give them a general tryout, so you know their basic operation.  Can you use INT0 instead?  It is easier if you can.  The PC irqs are "addons"  and are a few steps more cumbersome.  In either case look at the datasheet carefully, it is your guide (for instance search it for INT0).   You can simply set up a timer to free run (count) and each IRQ, grab the time (count).  Since you also grabbed the last time the last time, their difference gives the time between pulses (and you need to think about rollover).  If last time your wall clock read 11 am, and now it reads  3 am what is the difference?   What if it said 5 pm and now says 8 pm?  Give that some thought.

Once you have the difference, you could display it, or send it in a message.  You could average it, so it is less jumpy.    

 

Or you could instead reset the timer each IRQ to zero, like a stopwatch.  Then the "difference" is just reading the timer each IRQ (note for easiest, the timer must be set slow enough so it doesn't not normally rollover).  Then you have your time values.  Also, you can set up timer overflow IRQ, so if NO PULSES ever occur, then the count overlow IRQ will eventually happen, saying  ...we are getting no pulses.  Maybe send a message, sound an alarm, etc. 

 

Note the time difference values are the scaled reciprocal of the RPM (so to a certain extent, reporting either represents RPM).

 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


Thank you for your reply. 

First, the goal is to read the fan speed values connecting the atmega with an arduino board and uart to see if the program works, then add a LED that will light up only if there is no pulses to know that the fan is off. 

Unfortunately, I can't use INT0 it's already taking, I searched a little about it and yes it would be much more easier since I can configure the interrupt on falling edge, rising edge... which is not the case with PCINTn. 

I attached the tach wire signal, as you said I need to count the time period to obtain the rpm.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0



I attached the tach wire signal, as you said I need to count the time period to obtain the rpm.

 

Are you 100% sure you can't use INT0--make life easy, it is worth the effort.

 

Anyhow you don't want to measure and display EACH  time, that would be way too jumpy...you prob want to measure over many pulses, maybe a few hundred.   You don't want to get a cumulative error, either, due to time slicing. 

Just like a ruler---you want to measure 36 inches.  If you mark off 1 inch, then 1 inch, then one inch with your ruler 36 times...well you might get 36 inches...or maybe 35.15 or 37.2 inches.  

 

So you start at time zero...then count irqs...say 75 of them. at IRQ #75 grab the time  (say 21356 counts)...now that can be much better than grabbing the time 75 little times and trying to add them up (note that grabbing 75 times might give an averaging effect so it might not be completely bad).  in order to grab #75 , each IRQ before then you may have to watch for count rollover (depending on how you scale the timer counting rate and whether you use 75, or 25 or 488 irqs). 

 

Say you had a 16 bit timer (0-65535) counting at 16e6/1024   that's 15625 counts/sec (will overflow in about 4.2 sec)

Say your motor was running 600rpm  that's 10rev/sec, or 40 rising edges/sec (390.625 counts/edge)

 

Say you measure over 80 rising edges

 

The numerator (based on N=80)  will be 18750000.  Integer divide by the final count total (here 31250) to get 600

Note that at 600 rpm, this is an average speed over about 2 sec (80/40)...which might be somewhat slow

 

Here the final count will be 34090 (or 34091)  the integer divide 18750000/34090 will give 550.  Integer divide is fast and easy.

 

Below 286 rpm, the count will exceed 65535, so you will need to handle the count wraparound.  This will always eventually happen at a slow enough speed.

 

for example at 200rpm the count will be (after your extension) 93750 counts

 

A completely opposite approach can also be used.  You can have the timer generate an periodic IRQ (say every 0.1 second)

During this time you count how many edge IRQ's you get (say rising or both).  However this is a low number, unless the time span is long.  

in 0.1 sec there are only about 13 rising edges at 200rpm, so a 1 sec time would be much better (130 edges)...The timer can't interrupt that slowly, so you have to do some tricks to extend that. 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Timer1 also has a timer capture register, that is made for measuring pulse timings like this, read the data sheet on this and see if it fits your needs, no IRQ's required.

Good luck with your project.

Jim

 

 

FF = PI > S.E.T

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you so much for taking the time to respond. 
I am limited in the choice of pins, so I can't use INT0.
The second approach seems doable, I can use 2 ISR, the first one will count every rising edge, then in the second ISR the CPU jumps there every 1 second and calculate the fan rpm as the following : rpm = count*60 after that the counter is reset to 0. What do you think of this approach ? 

"Timer1 also has a timer capture register, that is made for measuring pulse timings like this, read the data sheet on this and see if it fits your needs, no IRQ's required." Jim, if I'm not mistaking the input capture pin (ICP1) is located in the pin PB0, I can't use it either so I guess I'll go with the interrupts. Let's say I use the PIN PB1 or PB2 so PCINT1/2 how do you normally configure the timer1 and interrupt in c programming? 

Regards, 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For the most part, you look at the timer your want to use and the couple of registers that control it...then set them as you desire

Of course that is a bit oversimplified & doesn't help to get the ball rolling! 

When looking at the datasheet (you should!)...as you read along in the timers section, make note of things... I could use this, I could maybe use that, this looks interesting, this will be of no use...so you kinda filter down to the "tools" for the job at hand. 

It sounds like you have already done some of that--excellent.

 

This will get you started:

https://hekilledmywire.wordpress...

 

This is also a top notch timer tutorial...enjoy

http://www.fourwalledcubicle.com...

 

Keep in mind you always end up with a count of variation  ...was that 456 or 455?  It will flicker back and forth, just like your multimeter.  If the number is small it more of an % error (is that 8 or 7), so you want to have larger values (counts) to work with. You can improve the situation by averaging 7 8 7 7 8 = 7.4 avg, but some uncertainties may not merely average out.

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you so much for the links, it will be really helpful. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

esalma757 wrote:
the input capture pin (ICP1) is located in the pin PB0, I can't use it either

So the h/w was designed before all the requirements for success were known?!!  Get your Exacto knife out and make the necessary pcb changes so you can use the timers the way they are intended! 

Next time get ALL the design requirements before the project starts! 

Good luck with your project.

Jim

 

 

FF = PI > S.E.T

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Actually, the PCB was already designed when the project was assigned to me. The PWM wire was connected in order to control the fan speed but the tach wire wasn't, so now we want to add it to know if the fan is properly working.
 

I started configuring the timer1 I'll be using : 
 

       TCCR1A|= (1<<COM1A1);

       TCCR1B|= ((1<<CS10)|(1<<CS12)|(1<<WGM12));

 

       //Prescaler Value= 1024
      //Compare value= 976

      OCR1A= 976;

      TIMSK0|=(1<<OCIE0B);  //Output compare 1A interrupt enable

     //Enable interrupts globaly
      sei();

 

Am I not forgetting any bit inside the registers TCCR1A/TCCR1B ?

 

Also, according to the data sheet, in order to use OC1A, the pin has to be configured as an output but in this case the tacho wire will be connected to that specific pin (PB1) so as an input. Am I missing something ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

DO NOT use or to set the config, use equals...Why are you using or????  Set all 8 bits exactly the way you need them TCCR1A what you need 

Who told you to use or?

Also, according to the data sheet, in order to use OC1A, the pin has to be configured as an output

That doesn't sound correct, unless you want to control a pin based on it (I'd need to pull up the datasheet)...I'd think you'd still be able to gen a number match IRQ regardless 

edit:

 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Thu. Apr 21, 2022 - 06:47 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Okay I wasn't aware, I just saw how they did in one of the website you send me. I am a newbie so thank you for your indulgence.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I just saw how they did in one of the website you send me. 

Unfortunately, such bad things tend to spread faster than covid.  If someone hooked their AVR pins to 15V on a site, probably everyone would soon follow along. 

What if some site says pi equals 3.23 (there I said it)...now geometry will be forever changed, the world over.

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Anyone can help with the c code of the 2 ISR?  

 

So I want to count the time of the 2 rising/falling edges (1 revolution) and then jump every 1 second to display the rpm, knowing that the max fan speed is about 2830 rpm which makes it 169800 revolutions per second so in total we have 339600 rising edges per second. How do I make my program count just 1 revolution (2 rising edges and 2 falling edges) every one second and not mixed it all and count endless rising/falling edges which won't calculate the accurate speed ?

 

And what if the speed changes constantly due to the PWM ? 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

 2830 rpm which makes it 169800 revolutions per second so in total we have 339600 rising edges per second

What???  Did you even consider that you said hundreds of thousands of edges a second  (from something giving 90 degree pulses) .  Always check that numbers makes sense, as a rough guide.

 

you have 47.17 rev/sec or 189 rising or 377 edges per sec

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This chip does have other options for counting. Check if you can access one of T0...T4 inputs.

 

Count for one /or more/ seconds exactly and calculate.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@avrcandies my bad I forgot dividing by 60, I was too finding those numbers too high.

@grohote I can use T1, how would you do it in that case ? 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

T1 means a pin for counting, it is PD5, and does have associated 16b Timer1.
Select CS12/11/10 of TCCR1B value, that is all- you need not activate Timer1 Overflow interrupt, because you said "2830 rpm".

It is the same as 50 rps or 100 Tacho signals in one sec.

Each /precise/ 1s or 2s you will read TCNT1 and then reset it, calculate and show RPM.

Last Edited: Fri. Apr 22, 2022 - 07:55 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for your reply, but what if we have multiple fans that we want to control and read their speeds ? PD5 is only one PIN and the other PIN are just regular ones.  

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How many of them? Is it a requirement, to have many fans? Do you realize that each fan needs the own access (Interrupt) pin.

 

Start drawing a schematic!

 

My solution in this "multifan" case will be: for 8 fans= one data selector external chip (total of 3 AVR pins for selection) plus ICP.

With ICP you are not counting pulses, but measure period, and in one second you can measure many periods you need for any fan.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

but what if we have multiple fans that we want to control and read their speeds

Are you asking a hypothetical question?  Nowhere do you mention multiple fans?

 

the pin change IRQs will be for you.

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Fri. Apr 22, 2022 - 08:59 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, it's not actually, first I need to start with just one fan, then the next step is trying to control multiple fans with one MCU  

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Note also that you need to assume each edge may not be an edge but possibly several (essentially bounce), so take that into account with your interrupt.  The sensor doesn't trip exactly, and the motor may be vibrating. So your method must be tolerant of this condition.

The slight change of timing is likely ignorable, but you don't want one of these possible narrow pulses to be counted as a full interval.

For instance, you might say after you get an irq (the very beginning, or beginning of the end portion), you will ignore irq's for 200us (your normal min pulse width is about 3000us, so this is minor blanking).  These points are marked in red.  Or you could just throw wildly out of range results in the trashcan. While the trashcan is simple, you might not get enough good values to keep.

 

 

Do not say, well the wave looks good to me, I scoped it...because it will, until the system has dirt and wear & tear in the field & now you have 5000 units acting up two years from now.

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Fri. Apr 22, 2022 - 09:03 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That is not honest, you should know that any AVR does not have unlimited resources of pins, and some requirements are towards a specific pin which then can not be used elsewhere, as it was discussed T1.

 

You may avoid a herd of external chips of type HC4040, but you still need one 74HC151 (or HC4051). And ICP, of course.

 

Edit: INT or PCINT can do it instead of ICP, but result will be less precise.

Last Edited: Fri. Apr 22, 2022 - 09:21 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@grohote It's ok, I don't need to have very precise results
So using PCINT, we should have something like that : 

uint8_t current_pinb; 
uint8_t tach_timeout; 

void timer1_reload();
void tacho_timer1_init(void);

 

volatile uint16_t tach_count=0;    //Main revolution counter

volatile uint16_t tach_rpm=0;   //Revolution per minute

 

ISR(PCINT1_vect)
{
   if( !(current_pinb & _BV(PB1)) && (PINB & (1 << PINB1)) == 1 )
    {
      tach_count++;  // LOW to HIGH pin change 
    }
    current_pinb = PINB1; 
}

ISR(TIMER1_OVF_vect)
{
    tach_timeout++; 
    if (tach_timeout == 100)
    {
        //CPU Jumps here every 1 sec exactly!
       tach_rpm = tach_count*60;
       tach_count = 0;
       
       tach_timeout = 0;
    }
    timer1_reload; 

Am I doing this correctly ? Is there any way to verify that the code is properly working ? 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

  tach_rpm = tach_count*60;

All IRQ variables shared with main must be declared volatile.

 

Just set your PC irq to use only one pin.  in the PC IRQ check if the pin is high...if so, assume you got a tach tick (later you should add some things to prevent/ignore  false edges).

You may have to reset the PC irqflag to zero by writing a 1 to clear (I did not check whether this is true...some IRQ's you must do so).  Otherwise when you exit the IRQ it will endlessly fire immediately again, since it is still set.

 

 Is there any way to verify that the code is properly working ? 

Are you planning on displaying the result on a 7 seg display or maybe uart?  Apply your signal and do so 

 

Or you could hook up an led & check when tach_rpm is between 30 and 35 (or whatever) , turn on an led, otherwise off, in main.  Then apply your signal and take a look.  As you adjust the signal, it should light up only in the range you set.

 

Also toggle an led here:  //CPU Jumps here every 1 sec exactly!  and use a scope to ensure you are really getting one second per toggle.  Otherwise, your tach measurements will be off from what you think.  Verify this FIRST, since it is easiest (no signal needed).

 

  timer1_reload;  ???   the timer keeps ticking, you are using tach_timeout, leave the timer alone.

 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Wed. May 11, 2022 - 05:39 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

esalma757 wrote:
   if( !(current_pinb & _BV(PB1)) && (PINB & (1 << PINB1)) == 1 )
I somehow doubt that line is going to work.

 

Think about it.

 

"PINB1" is almost certainly:

#define PINB1 1

so "(1 << PINB1)" is going to be (1 << 1)

 

(1 << 1) is 2. So the clause in your conditional says:

&& (PINB & 2) == 1 )

I can tell you for nothing that whether bit 1 in PINB is set or whether it is clear the answer is never going to be 1 !

 

As this is "&&" then this part is always 0, thus the entire conditional is 0. So the code in the braces will never execute

 

(I suspect the optimizer will have come to this conclusion a little quicker than I just did and will not have generated any code for a pointless conditional and code that can never be reached)

 

I never understand this insistence of squeezing an "== 1" into such conditionals anyway. In C 0=false and everything else is true. So if part of a conditional evaluates to a non-0 result it will be considered "true". Thus:

if( !(current_pinb & _BV(PB1)) && (PINB & (1 << PINB1)) )

without the "== 1" would actually work. In fact, for symmetry, you might prefer to write that as:

if( !(current_pinb & _BV(PB1)) && (PINB & _BV(PB1)) )

EDIT: just spotted the next error:

ISR(PCINT1_vect)
{
   if( !(current_pinb & _BV(PB1)) && (PINB & (1 << PINB1)) == 1 )
    {
      tach_count++;  // LOW to HIGH pin change 
    }
    current_pinb = PINB1; 
}

I bet you mean "(1 << PINB1)".

 

Again I never understand this desire to use "PB1" or "PINB1" or whatever - they are all just 1 anyway - so just use 1!

 

I personally would be tempted (if the things must be given symbolic names) to just get rid of all the (1<<n) nonsense once and for all:

#define BIT0 0x01
#define BIT1 0x02
#define BIT2 0x04
#define BIT3 0x08
#define BIT4 0x10
etc

then the code becomes:

ISR(PCINT1_vect)
{
   if( !(current_pinb & BIT1) && (PINB & BIT1))
    {
      tach_count++;  // LOW to HIGH pin change 
    }
    current_pinb = BIT1; 
}

 

Last Edited: Wed. May 11, 2022 - 09:22 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
!(current_pinb & BIT1) 

The nice thing PB1, PD2 etc have going for them is everyone immediately knows them & knows they are correct (hopefully!).

 

If someone sends me BIT3, I don't know exactly what they did (did they do what I think they did, correctly, or is BIT3 set as 3)....so I have to round up and find the definition. Since I have to go look to see what it is, it somewhat defeats the purpose of efficiency.

I suppose that is true of any name that is user-created.

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm sorry, for clarity perhaps I should have called it BITMASK3 ?

 

(or perhaps follow Atmel's convention of "_bp" for bit positions and "_bm" for bit masks?)

 

To be honest, for total clarity to the reader/maintainer what I would actually probably use is something like:

#define TACHO_INPUT 0x08

...
    if (PINB & TACHO_INPUT) {
        ...
    }

I suppose the fact that I didn't mention "3" in that means the maintainer, who is reading the code alongside the schematic, has to work a little harder to convert my 0x08 back to the notion of physical pin PB3 ? Perhaps I'd just:

#define TACHO_INPUT 0x08 // Tacho input is bit 3 of port B 

or similar for the benefit of the maintainer? But what I don't want her to have to do is read:

    if (PINB & (1 << 3))) {
        ...
    }

and have to grub about to find out what is being read - what is bit 3 of B actually connected to. Words like "TACHO_INPUT" mean the code can more immediately be read and understood.

 

(BTW it's a shame that the AVR SBI/CBI/SBIC/CBIC opcodes ever chose to take the bit position as a 0..7 vale as that is the reason that AVR code every is stuffed full of (1 << n) constructs all so that ADSC or whatever can be defined as 7 and not 0x80 for use in Asm as "SBI ADCSRA, ADSC" etc).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm trying to set up the interrupt :
 

//Init PCINT1

 

EICRA = (1 << ISC11); // The rising edge of INT1 generates an interrupt request

PCICR = (1 << PCIE0); // set PCINT1 to enable PCMSK0 scan

PCMSK0 = (1 << PCINT1); // set PCINT1 to trigger an interrupt on state change

 

sei(); //Enable interrupts globally

Am I missing something ? Is EICRA really needed since it's only one pin that I don't even have access to or is the if condition (if( !(current_pinb & _BV(PB1)) && (PINB & _BV(PB1)))) fulfilled this function ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Back in the old days the original AVR just had one or two IO with interrupt sensing - usually "INT0" and "INT1" and while there was only 2 or 3 of these they were fairly "full featured" in that you could configure then to interrupt on rising edge, falling edge, level. Then around the time the mega48/88/168 appeared (2004/5?) Atmel saw an opportunity and made it so that pretty much every GPIO pin (as well as dedicated INT0/INT1) could be used as an interrupt source but they have "lesser funcitonality". For one thing they are effectively grouped in 8's. So all 8 pins of PORTB can generate an interrupt, all 8 pins of PINC can generate another interrupt and so on. Also you don't get to configure rising/falling/level any more. They just interrupt when the pin state changes and that is all. When you use these new interrupts (PCINT) you have to (a) determine which of a possible group of 8 caused the interrupt (though a mask allows you to vary from 1 to 8 pins as the source so if you only used one pin of a given PORT you would know the pin that caused it also (b) you have to keep track of "previous pin state" so that you can tell if it just made a 1->0 or a 0->1 transition.

 

So INT0/INT1 (if you aren't using the pins for other stuff and they can be easily routed to the source you are monitoring) are still the "best" external interrupt pins to use as you have a whole interrupt per pin and you have control over the kind of sensing that is to be performed.

 

History lesson over:

esalma757 wrote:
Am I missing something ?

Given what I have just said then, no, if you are using PCIE0 and PCINT1 (so the first port (B) and pine 1 in it) then EICRA is not involved as it is for controlling the sensing of INT0/INT1

 

But, depending on what you are trying to achieve, could it be that INT0/INT1 could be a "better" choice anyway? Looking at 328PB datasheet it seems that PD2=INT0 and PD3=INT1

 

If you were to use INT0/INT1 you wouldn't need code in the ISR to compare current and previous pin state and so on as you know exactly which pin caused the interrupt and by judicious use of the rising/falling bits in the EICRA you can be sure that the interrupt is either because of a rising or a falling edge.