Manipulate input TACH Frequencies and output multiple DDS based TACH Frequencies based on single timer

Go To Last Post
60 posts / 0 new

Pages

Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi All,

 

I am working on a project to be able to manipulate FAN speeds. The fans are 4 wire samples and sitting in a nice Dell server.

For office use the server is great and noisy... However I would like to use it at home, so it needs to be great and SILENT.

Since it does not need to be a fridge inside I want to adjust the fan speed. this is not doable by software on the server and I have to create an alternative way.

This alternative way I would like to create in such a way that if for whatever reason the server gets hot it will be able to run at full power fans (airplane mode).

 

At this moment I have already written a routine to catch 5 fans rpm. This RPM is displayed through UART to me.

 

Now I need to do the tricky part, I need to create a TACH signal to the mainboard. As my topic title explains this needs to be of a variable frequency. 

Normally this would be called a DDS (as far as I am aware atm). However this DDS needs to output 5 square waves at the same time.

The form of the square wave needs to be a pulse and the 0 for the remaining period. This is however not directly of my concern (details I will deal later with).

 

For now I am looking for the best way to extract 5 (6 actually if I include the frequency reading in it as well) frequencies from the timer to create different frequency for every fan.

For example 125Hz, 140Hz, etc.

 

I already made a sample code, which I did not test yet. But I would like to have some advice concerning the way forward.

Do I need to go with 6 phase accumulators like "t" in underneath example or do I need to go the modulo division way (from which I have no clue at the moment).

Furthermore all advice concerning my preliminary code is welcome. Normally I am programming Java, SQL and VBA so still learning every time I work with C

 

Removed due to changes made to source -> scroll down for a later post that holds actual code!

 

This topic has a solution.

Last Edited: Wed. Apr 1, 2015 - 05:21 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Rather than talk about techniques, lets talk about what you require.
What are the range of frequencies and what sort of resolution would you like?

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

What I require is a program that is able to simulate tach behavior of a fan, based on the original tach signal.

At the same time it needs to do this simultaneously for 5 fans (5 tach and 5 tach out).

 

Frequency range is between 0RPM and 20000RPM, which is 0Hz and 334Hz, independent of each other, so in principle 1 could run 10Hz while the other is doing 334 Hz.

I would like to use the smallest AVR possible (req. 10 in.out, ISP and ext crystal), still need to research which would fit best.

 

IN Tach is done using a BC547 and the internal pull-up. All IN do works by the INT vector ISR. This part of code I have.

Currently every 1000ms the code is fired to update the frequency received from the IN TACH. This needs to be translated to an out TACH with to be determined rate.

So if incoming is 5000RPM the outgoing to the Mainboard should be 10000. So if the MB wants to have it run at 5000 it will lower the PWM to the fan. This until the 5K is reached, which effectively means the fan runs at 2500 (in case the ration would be 1:2)

 

Furthermore at the moment I do not know if 1 thick whatever frequency will be enough for the MB to detect so it will display/record the RPM.

I was looking for an answer to your question concerning resolution, but I am afraid I cannot give that to you (coz I do not know).

 

I hope this explains enough and gives answer to your question.

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

If you want to create the frequencies by DDS, using seperate phase accumulators is the way to go. As the resolution like does not need to be very hight, there is no need for high resolution. So 16 Bit should be OK.

 

To create a pulse signal there is no need for a full DDS. Likely its enough to make the on phase one ISR period long. Thus the lookup take usually used in DDS is to be replaced with something like checking for an "overflow".

 

The code shown in the initial post does not look like DDS, but more like software PWM. This limits the period to an integer multiple of the ISR period. True DDS code would also allow fractional parts for the period length, so would allow more resolution or a lower timer frequency. However the Signal would have some jitter.  
 

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

I like a good uC project as much as anyone, but don't you have access to the fan controller through BIOS calls?

If so, you write your high level lang program and make it the first to load on boot up, drop an Icon on the task bar, if your OS has a task bar, and you are done.

If the server is running a terminal then just display a message with the current temps and fan speeds, to show that the system is alive and working.

 

I've not worked with your system before, but if you have a tach output from he MB, and a monitoring tach input to the MB, then when not just connect them together?

The MB will always be happy, as it thinks it has perfect control of the requested speed.

Your micro can monitor what the MB I requesting, and independently run the fan.

 

I'd be inclined to run a prototype on one fan, first.  With the slower/quieter fan speed, you are apt to be running the system/microprocessor/video controller/etc., a little bit hotter than the original designers intended.

Depending upon the control method they elected to use, it may keep requesting a faster and faster fan speed, to try and cool the device to within the target window.

So, once again it makes sense to either read the MB's temp sensors through the BIOS, (or BIOS equivalent...), or add your own temp sensors, (tricky, if you don't know what you are doing).

The point being, you would like to insure you are going to get a run away high speed request from the MB's controller when you reset the target window, otherwise your tracking and divide by two system isn't going to work. 

 

JC

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

@DocJC, no I cannot control it by the host system, nor by the BIOS, that is why I have to walk the alternative route. This method is not used before as I am aware of (otherwise I would love to copy paste ;-) ), but it is done more, only in different ways. The problem here is that the host system is checking in firmware closed source the fan speed.

 

@Kleinstein, I am aware that this looks/is software PWM. I have been searching the net a lot, but did not come across real DDS.

Would love to read something about this (even if it is just to get an idea). Basically I was "scared" to dive in Jesper etc, because it is, ASM and that would be harder to me.

 

Concerning 16bit is correct, this would be enough. 

Furthermore. If you talk about 1ISR long, you probably mean 1 timer_tick?

 

This I do not understand: "Thus the lookup take usually used in DDS is to be replaced with something like checking for an "overflow"."

 

But if I understand you correct then underneath code would do the trick:

 

ISR(xxx_vect){
    static uint8_t timer_tick;
    timer_tick++;                                                   //increment the clock tick
    if(timer_tick % freq1 == 0){do_this("do nothing -> 0");}        //do every X ticks
    if(timer_tick % (freq1+1) == 0){do_that("give pulse -> 1");}    //do every X+1 ticks
    
    if(timer_tick % freq2 == 0){do_this("do nothing -> 0");}        //do every Y ticks
    if(timer_tick % (freq2+1) == 0){do_that("give pulse -> 1");}    //do every Y+1 ticks
}

Or would you prefer:

ISR(xxx_vect){
    static uint8_t timer_tick1;
    timer_tick1++;                                                   //increment the clock tick
    if(timer_tick % freq1 == 0){do_this("do nothing -> 0");}        //do every X ticks
    if(timer_tick % (freq1+1) == 0){do_that("give pulse -> 1" & reset clock1 0);}    //do every X+1 ticks
    
    static uint8_t timer_tick2;
    timer_tick2++;      
    if(timer_tick % freq2 == 0){do_this("do nothing -> 0");}        //do every Y ticks
    if(timer_tick % (freq2+1) == 0){do_that("give pulse -> 1" & reset clock2 0);}    //do every Y+1 ticks
}

Forgot to mention, I am looking for a Atmega only solution, so not a specific chip to create the wave.

Last Edited: Mon. Mar 23, 2015 - 09:12 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

*Gentle bumb*

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

I think you mean 'bump'

Dds is magically simple:
uint16_t phase_acc;

phase_acc+=phase_inc;
//copy msb to port bit
If (phase_acc & 0x8000)
{
Set port bit
}
Else
{
Clear port bit
}

Repeat as many times as required.

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

I think I understand. Will post an example later 2day, to see if I really get it.

Last Edited: Wed. Mar 25, 2015 - 08:36 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Perhaps stupid but I could not see it:

 

Is the input a pulse (always same length) or is it a 50% high low signal?

 

I would make this in one timer ISR. @ 2 times the fastest signal.

Last Edited: Wed. Mar 25, 2015 - 10:38 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I adjusted my "DDS" based on your feedback, would you be so kind to give me feedback if this is better?

 

Removed due to changes made to source -> scroll down for a later post that holds actual code!

 

Last Edited: Fri. Mar 27, 2015 - 07:07 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You might want to change your port pin for each of the dds- currently its all PD3.
A tick of 100KHz seems a tad high. You only have 200 clocks to complete your isr. You might want to run the simulator to see how many clocks the isr actually takes.
Your check_ freq function has some potential integer roundup problems. Do multiplies before divides. Again, the simulator can give some insight.

Last Edited: Thu. Mar 26, 2015 - 10:03 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman, correct I did not change the ports, because I was more curious about my implementation. But in the underneath code it is adjusted.

Simulator I have never used, so I will try later today (code underneath is latest status).

 

Concerning interger rounding with devision I found the following:

http://mihalysprojects.weebly.co...

 

Where for integer division the following formula is discussed: (a + b/2) / b

Furthermore I have adjusted to do multiplies before division.

(See attached code)

 

Ps. I will delete the code earlier this post to make the topic readable.

 

Removed due to changes made to source -> scroll down for a later post that holds actual code!

 

Last Edited: Fri. Mar 27, 2015 - 09:03 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

0.5 doesnt mean much in an integer calculation.
You've got a couple of defects methinks. The learning is in finding them, they should be obvious when you run the code.

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

What are methinks?

And I was really in the understanding that you where hinting on the 0.5 for the rounding up part of integers.

 

What kind of defects/mistakes should I be looking for. Will run simulater later 2day.

 

Update 1: I think you are hinting on the phase_inc01 which is incorrect. I am adjusting this. Simulater showed indeed that the value was wrong

Update 2: Changed calculation, wondering btw why you use 0X8000 instead of 0X10000 for an uint16?

Update 3: At the moment the phase_inc counts over the 65535 and adds after 0. I think I made a mistake with the 65536 (0X10000) but 0XFFFF will not run my code

 

Updated code:

Removed due to changes made to source -> scroll down for a later post that holds actual code!

 

Last Edited: Sat. Mar 28, 2015 - 09:27 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't think you understand how to deal with bits, I think you have some reading to do. 

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

Sparrow, could you recommend a page to read?

If I look after bit manipulation I come across site how to set a bit. I presume you mean something different.

 

AVR is quite a challenge and I am very open for new insight on how to get things done, but some terminology is not yet known to me.

Thank you in advance for a link.

Last Edited: Fri. Mar 27, 2015 - 11:08 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What are methinks? Google that one.

 

why did you change this? 

if (phase_acc01 & 0X10000){

bit 16 doesn't exist in a 16 bit variable. bits0..15 do. It was correct to begin with 0x8000

 

 

if (phase_acc00 == 100000){

what happens when this is true? phase_acc0 just keeps on ticking. Why did you call it phase_acc anyway? it's not being used as a phase accumulator like the others are. This only confuses us.

 

 

void checkFreq() {														// Function to be triggered if frequency needs to be adjusted during runtime
	freqCurrent = (((Average_RPM)+(0X3C/2))/0X3C);							// FAN RPM
		if (abs(freqCurrent-frequency)>freqTolerance){					// if new value exceeds tolerance
		frequency = freqCurrent;										// new frequency
		//phase_inc01 = ((samplerate*0x8000)/frequency)+0.5;		
		phase_inc01 = (((0X10000*frequency)+(samplerate/2))/samplerate);	// Calculate 1 period versus complete samplerate
	}
}

does this function work? You need to understand the values that can be held in variables. You exceed 65535 when dealing with unsigned ints.

 

To test the frequency generation, I would load up fixed values in the phase_inc variables and measure the outputs. If you don't have an oscilloscope, you can use your sound card. Google 'sound card oscilloscope'

Once you're happy with the operation of the dds, then you can add features.

 

Last Edited: Fri. Mar 27, 2015 - 11:15 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:

What are methinks? Google that one.

 

why did you change this? 

if (phase_acc01 & 0X10000){

bit 16 doesn't exist in a 16 bit variable. bits0..15 do. It was correct to begin with 0x8000

 

I think this is where I go wrong every time, because when I look up the max value of an 16 bit uint I always come 65535 (2^16-1), when converted to HEX, that becomes 0XFFFF, however this won’t work. But what I do not understand is that I see the counter holding 65k values while it is 16bit uint.

While when you say 15bit available it should be max 32767, but it can hold 65k, where am I going wrong.

 

Kartman wrote:

if (phase_acc00 == 100000){

what happens when this is true? phase_acc0 just keeps on ticking. Why did you call it phase_acc anyway? it's not being used as a phase accumulator like the others are. This only confuses us.

 

Primarily it should do the same as the others, I only could not get it to work as I wanted. In principle this should fire every 1000ms. But I will solve that piece later.

(As you said in your post, this is a feature which can be created later, next time (if not already adjusted) I will remove it)

 

Kartman wrote:

void checkFreq() {														// Function to be triggered if frequency needs to be adjusted during runtime
	freqCurrent = (((Average_RPM)+(0X3C/2))/0X3C);							// FAN RPM
		if (abs(freqCurrent-frequency)>freqTolerance){					// if new value exceeds tolerance
		frequency = freqCurrent;										// new frequency
		//phase_inc01 = ((samplerate*0x8000)/frequency)+0.5;		
		phase_inc01 = (((0X10000*frequency)+(samplerate/2))/samplerate);	// Calculate 1 period versus complete samplerate
	}
}

does this function work? You need to understand the values that can be held in variables. You exceed 65535 when dealing with unsigned ints.

This function indeed give the value back I calculated on paper, though I dont understand why, because of the 16/15bit etc.

I have simulation working and I see the accumulation working.

phase_accX itself is not exceeding 65535, however the calculation will, before it is divided. Is this a problem?

 

Kartman wrote:

To test the frequency generation, I would load up fixed values in the phase_inc variables and measure the outputs. If you don't have an oscilloscope, you can use your sound card. Google 'sound card oscilloscope'

Once you're happy with the operation of the dds, then you can add features.

 

Will try later when I get the 16bit/15bit/ bit operations.

Thanks for your patience.

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

any number to the power of 0 equals one - so bits are numbered from 0. For a 16 bit variable, you have 16 bits numbered 0 to 15. For the DDS we want to copy the most significant bit (bit 15) to our port pin. thus we mask with 0x8000.

if (phase_acc00 == 100000){

you're using phase_acc00 to count ticks - 100 000 of them it seems. You might want to reset it back to 0 when it hits 100 000 otherwise it will keep counting to 4000 000 000  (actually 2^32) then roll over back to 0. There's no dds action happening here.

 

phase_inc01 = (((0X10000*frequency)+(samplerate/2))/samplerate);

Read up on C's casting and promotion rules. Basically you have a 16bit variable = 17bit variable times an 8 bit variable. You can use 'casts' to force the compiler to use larger variables. Personally I'd use a long var to store the intermediate values then copy to the int.

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

I don't know I learned it the hard way 35 years ago. (ASM programming of 6502).

But google should find it for you.

And I see that a C book and 2. complement representations/calculations will be a help as well.  

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

Kartman wrote:

any number to the power of 0 equals one - so bits are numbered from 0. For a 16 bit variable, you have 16 bits numbered 0 to 15. For the DDS we want to copy the most significant bit (bit 15) to our port pin. thus we mask with 0x8000.

 

Thank you, makes it clearer.

 

Kartman wrote:

if (phase_acc00 == 100000){

you're using phase_acc00 to count ticks - 100 000 of them it seems. You might want to reset it back to 0 when it hits 100 000 otherwise it will keep counting to 4000 000 000  (actually 2^32) then roll over back to 0. There's no dds action happening here.

 

Corrected this already. Indeed its just a timer tick. Renamed it to avoid any further confusion. 

 

Kartman wrote:

phase_inc01 = (((0X10000*frequency)+(samplerate/2))/samplerate);

Read up on C's casting and promotion rules. Basically you have a 16bit variable = 17bit variable times an 8 bit variable. You can use 'casts' to force the compiler to use larger variables. Personally I'd use a long var to store the intermediate values then copy to the int.

 

void checkFreq() {														// Function to be triggered if frequency needs to be adjusted during runtime
	freqCurrent = (((Average_RPM)+(0X3C/2))/0X3C);							// FAN RPM
		if (abs(freqCurrent-frequency)>freqTolerance){					// if new value exceeds tolerance
		frequency = freqCurrent;										// new frequency
		
		temp_var = (((0x8000 * (float)frequency)+((float)samplerate/2))/(float)samplerate);
		phase_inc01 = (uint16_t)temp_var;								// Calculate 1 period versus complete samplerate
	}
}

As seen above I have adjusted the calculation. If I used a decimal instead of the 0x8000 then it wars working. If I used a hex it was not. Casting all to float made it working. (Hope this is what you ment?).

 

Furthermore I understand the part most significant bit, what I do not understand is that I see my : phase_acc01+=phase_inc01; reachting a value of 65k before retruning to +0.

So why do I need to do the calculation(in checkFreq) and comparison(in timer) to 0x8000 and not to 0xFFFF if that is what the timer runs to until is starts again at 0.

Or is the purpose to reset manually to 0 like with the 1000ms timer?

 

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

now back to my question at #10

the 0x8000 check will make 0-32767 -> low port

                                32768-65535 -> high port

so a 50% high low signal.

 

if you don't want to read about it then at least write your bits on paper and see what happen!

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

sparrow2 wrote:

Perhaps stupid but I could not see it:

 

Is the input a pulse (always same length) or is it a 50% high low signal?

 

I would make this in one timer ISR. @ 2 times the fastest signal.

 

I am sorry I did not respond to your question, my apologies.

To describe:

My pulse is a Tacho pulse from a fan (the AVR needs to recreate one). The original pulse is created by an internal transistor with the wire which is connected to the AVR as open collector.

The pulse will come every round once like a hall sensor.

 

The pulse I want to create is likewise. So the main part of the frequency is 0 and only 1 pulse needs to be given every period.

Visualized:

______________________-______________________-______________________-

Where the underscore is NO pulse and the dash is a pulse.

 

Quote:

now back to my question at #10

the 0x8000 check will make 0-32767 -> low port

                                32768-65535 -> high port

so a 50% high low signal.

 

if you don't want to read about it then at least write your bits on paper and see what happen!

 

As I just explained I do not want a 50:50 signal. That is why I was focusing on 65535 instead on 32k.

I now know about the bitwise AND.

This is checking is the most significant bit from BOTH sides of the "&" are set, if they are then port high, otherwise in the time-frame clear.

(Hope I finally understood...)

 

 

Last Edited: Fri. Mar 27, 2015 - 02:43 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Numbers of fixed precision will 'roll over'. Eg: for 16 bits, 65535 +1 =0 . Just like in decimal - for two digits 99+1 = 0, the 1 as in 100 drops off as we only have two digits. Just like a car's odometer.
So, you don't need to manipulate the phase_acc apart from adding a value to it. So fix up your code and get it working generating a square wave of variable frequency. Test your check_freq function so you can give it a frequency and it calculates the phase_inc value correctly.
Using a decimal number vs a hex number should make no difference - they are different ways of representing the same value.
I wasn't suggesting you use float - you have only 200 clocks per timer tick - if you do floating point calcs, it will chew up these 200 clocks very quickly as in you'll run out of time. This will cause your output frequency to jump every time you call check freq from your isr.

Visualise the operation of DDS as a circle. Each timer tick you move around this circle. The greater the phase_inc, the faster you go around the circle.
For your pulse, what is the pulse width in relation to the period? Is it a fixed width regardless of frequency or is it a fixed relation to the frequency?

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

Sebas06 wrote:

Kartman wrote:

 

why did you change this? 

if (phase_acc01 & 0X10000){

bit 16 doesn't exist in a 16 bit variable. bits0..15 do. It was correct to begin with 0x8000

 

I think this is where I go wrong every time, because when I look up the max value of an 16 bit uint I always come 65535 (2^16-1), when converted to HEX, that becomes 0XFFFF, however this won’t work. But what I do not understand is that I see the counter holding 65k values while it is 16bit uint.

While when you say 15bit available it should be max 32767, but it can hold 65k, where am I going wrong.

A typo may have confused you here, kartman likely meant to say

A 17th bit doesn't exist in a 16 bit variable. bits0..15 do

ie bits number from 0..15, and  you can test any of those, but testing a (non existing) 17th bit will give a static result.

 

You can get a rough duty pulse (<> 50%) with a > eg (Acc > 65536/3) for a ~33% duty cycle, but keep in mind the step-amount on ACC varies, and so it will pass that 33.33% threshold in varying phases (in both directions, normal INC and 'flyback' wrap around, and give some jitter on both the edges.

Note a > test is going to be slightly slower than a binary-mask test.

 

As others have hinted, with DDS you should run as fast as the CPU can, but with still enough cycles spare to actually do other work.

DDS can easily expand to use ALL CPU cycles, if you are not careful.  90% is ok, if you have modest other work.

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

Well, apparently I needed a good night sleep. I have adjusted the CPU-freq code and it seemed I did not need the float at all. Looking at the numbers and the calculations done indicated that it was not a divide error, but a int promotion error.

Within the calculation the number get a lot more than the uint_16, so I needed to adjust to uint_32. There was no need (as far as I can see) to go to long nor float.

To be precise, this piece was the bugger: 

0xFFFF * (uint32_t)frequency

Now I have as code:

void checkFreq() {														// Function to be triggered if frequency needs to be adjusted during runtime
	freqCurrent = (((Average_RPM)+(0X3C/2))/0X3C);							// FAN RPM
		if (abs(freqCurrent-frequency)>freqTolerance){					// if new value exceeds tolerance
		frequency = freqCurrent;										// new frequency
		phase_inc01 = (((0xFFFF * (uint32_t)frequency)+(samplerate/2))/samplerate);	// Calculate 1 period versus complete samplerate								
	}
}

Furthermore this was looking to me as a simple project but I have had a quick lesson in bits and bytes (refresher, coz it was a very long time ago I had to learn that at school and never used it again).

 

Besides the claculation part I also had an epihanie concerning the pulse wave. And this raised me a question. Since kartman porposed to use the bitwise AND for a square block wave, this is an easy way to say from here low otherwise high (or the other way around). However I willtry if the Mainboard I will be interfacing with will accept it, but if not I will change it a little. My question is, is this impacting the speed?

 

So currently:

phase_acc01+=phase_inc01;
	// copy msb to port bit
	if (phase_acc01 & 0xFFFF){
		// Set port bit
		PORTC |= (1<<PC0);
	}
	else
	{
		// Clear port bit
		PORTC &= ~(1<<PC0);
	}

But if the block (pulse width) is too long, can I adjust to (without speed impact) to:

(Who-me already answered this as being slightly slower than a binary-mask test.)

phase_acc01+=phase_inc01;
	// copy msb to port bit
	if (phase_acc01 =< phase_inc01){
		// Set port bit
		PORTC |= (1<<PC0);
	}
	else
	{
		// Clear port bit
		PORTC &= ~(1<<PC0);
	}

Thank you.

Last Edited: Sat. Mar 28, 2015 - 07:40 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Check the Assembler the compiler creates, to see the relative impact of the choices.

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

 

Sebas, you haven't answered my question regarding the width of the pulse.

 

Why would you do this?

if (phase_acc01 =< phase_inc01){

I'd be doing something like this:

phase_acc01+=phase_inc01;
	// assume an integer part of 4 bits and a fraction of 12 bits. Mask for the integer part and compare
	if ((phase_acc01 & 0xf000) == 0x8000){
		// Set port bit
		PORTC |= (1<<PC0);
	}
	else
	{
		// Clear port bit
		PORTC &= ~(1<<PC0);
	}

This will give you a pulse width 1/16 of the period or 22.5 degrees. If you don't follow what I'm doing - get yourself a pencil and paper. To make it easy, assume a phase_inc of 4096

 

Note: a uint32_t is an unsigned long

 

so the calculation becomes phase_inc = ((uint32_t)frequency<<16) / 100000UL

I would call that calculation from the isr, instead, have the isr set a flag and the main line code can poll that flag and perform the calc. This uncovers another issue - the phase_inc variables now become shared between the sir and the main line code, so they need to be declared volatile. As well, you need to ensure atomicity. Both Clawson and I have written tutorials on these issues. Mine's called 'the traps when using interrupts'

I'd also suggest you use the simulator to measure the execution time of the isr. This will answer your questions regarding performance. You can slow down the timer tick rate - currently, the code will generate frequencies between 6250Hz and 1.5Hz with a resolution of around 2Hz. You could halve or even divide it by 10 the timer tick frequency.

Last Edited: Sat. Mar 28, 2015 - 09:54 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:

Sebas, you haven't answered my question regarding the width of the pulse.

 

Why would you do this?

if (phase_acc01 =< phase_inc01){

I'd be doing something like this:

phase_acc01+=phase_inc01;
	// assume an integer part of 4 bits and a fraction of 12 bits. Mask for the integer part and compare
	if ((phase_acc01 & 0xf000) == 0x8000){
		// Set port bit
		PORTC |= (1<<PC0);
	}
	else
	{
		// Clear port bit
		PORTC &= ~(1<<PC0);
	}

This will give you a pulse width 1/16 of the period or 22.5 degrees. If you don't follow what I'm doing - get yourself a pencil and paper. To make it easy, assume a phase_inc of 4096

 

Simple answer, because I do not know. But I agree your way is better, probably more efficient.

 

Quote:

Note: a uint32_t is an unsigned long

 

Yep had read that somewhere.

 

Quote:

so the calculation becomes phase_inc = ((uint32_t)frequency<<16) / 100000UL

 

Bitwise operations is not my daily job, so just had a quick google and understand what you are doing.

http://stackoverflow.com/questio... :)

Furthermore I also noticed you removed the +0.5. Besides you noted earlier that this was not my problem you did not explain why I should not need it?

Why do you use 100000UL and not the "samplerate"

 

Quote:

I would call that calculation from the isr, instead, have the isr set a flag and the main line code can poll that flag and perform the calc. This uncovers another issue - the phase_inc variables now become shared between the sir and the main line code, so they need to be declared volatile. As well, you need to ensure atomicity. Both Clawson and I have written tutorials on these issues. Mine's called 'the traps when using interrupts'

I will read that tutorial before changing.

 

Quote:

I'd also suggest you use the simulator to measure the execution time of the isr. This will answer your questions regarding performance. You can slow down the timer tick rate - currently, the code will generate frequencies between 6250Hz and 1.5Hz with a resolution of around 2Hz. You could halve or even divide it by 10 the timer tick frequency.

You mentioned that before. Let me first conquer the traps with interrupts.

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

Updated the code today with the volatile (after reading a little more on the why).

Furthermore added the main code execution flag (also after some more reading).

 

Had no time to test the square nor 22,5% wave.

 

Removed due to changes made to source -> scroll down for a later post that holds actual code!

 

Last Edited: Sun. Mar 29, 2015 - 02:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You can measure the pulse width/frequency etc with your PC's sound card.

As for the use of samplerate, i couldn't see where it was loaded. A #define SAMPLERATE 100000UL
Would suffice as the sample rate won't change at run time.

Last Edited: Sat. Mar 28, 2015 - 09:40 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:

 

Why would you do this?

if (phase_acc01 < pwm_threshold_01){

 

Binary masking, and < are both valid, the benefit of a compare is precision control of the PWM duty, (not limited to binary multiples)

and it is easier to read and edit.

The down side is I would expect < to be slightly slower, but the OP should code both and verify the actual speed/Size of each.

Wider masking can sometimes surprise.

 

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

Who-me, the snippet of code seems to have changed from the one i was referring to.

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

Kartman wrote:

Who-me, the snippet of code seems to have changed from the one i was referring to.

I changed the var names to make it clearer it is a PWM threshold test.

The choices for that are a Binary-Mask, or a Compare.

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

Kartman wrote:

You can measure the pulse width/frequency etc with your PC's sound card.

You told me before, but I need to know how the mainboard reacts on a squarewave instead of a pulse. Normally the hall sensor in the fan will just give a short pulse and not a square wave.

If it accepts the square I am fine with it.

 

Kartman wrote:

As for the use of samplerate, i couldn't see where it was loaded. A #define SAMPLERATE 100000UL
Would suffice as the sample rate won't change at run time.

 

Did not define it in your mentioned way, but as a variable. See your point and change it. I only wanted to have a simple place instead I change it, so when I change it, it would change all at once. Instead of accidentally missing a variable.

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

Okay, today I have been experimenting with my code (and new circuit).

I can read the rpm again and have combined and "optimized" my code. I had different projects for all and wanted to combine them because the loose code pieces did not come together.

TACH and USART have been tested. Next step is to read it from a running PC (instead of a loose fan which is powered by an overpowered supply ->13.8 instead of the 12V). 

I need to finetune that piece.

 

Furthermore I have questions about my combined code.

- I used in other code an int array, what would be more advisable -> 5 different variables or 1 array?

- Do I start stop the PCINT interrupt correctly this way. If I stop the ISR with SEI and cli then it would stop my timer, which cannot because it also stops generating the wave to the MB.

- After testing it seemed there should still be a problem concerning the RPM measurement. While I was already in doubt if it was OK, I gambled that it was the high power supply that gave me the high reading. Normally you should see 2 pulses per rotation. For my fan which should run between 9400~12000 rpm this is 630 pulses per 1000ms (1 second). If you do this times 60 and divide by 2 I get 18900. This makes me wonder if my timing is off, or do I interpret the in pulses wrong (coming from an open collector.)

 

Help is much appreciated.

 

Removed due to changes made to source -> scroll down for a later post that holds actual code!

 

Last Edited: Mon. Mar 30, 2015 - 05:22 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'd suggest you measure the execution time of your isrs in the simulator. You have 200 cycles between each timer tick and I'd guess that you're burning most of them in your timer tick. Then add your pcint isr and you most likely have run out of cycles.
1. Measure first - otherwise we're guessing
2. The pcint is probably not needed - you could poll the tach inputs in your timer tick
3. Decrease the timer tick rate

Using arrays and loops would make the code tidier. Whether this would affect performance is a question you can answer by using the simulator.

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

Kartman, I am having difficulties with the simulator. I have researched (google) but I think I have it configured correctly.

By default it is running at 1Mhz, changed it to 20Mhz (which should only affect the stop watch). Code is still damn slow, but as I read here on the forum that should be normal?

https://www.avrfreaks.net/forum/s...

 

I have put a breakpoint on the flag=1, and 1 run from flag=1 to flag=1 costs: 4.000.206,35 µs

IN the chip this should be the 1000ms..

 

Included latest code down here with changes to arrays and 1 loop for the cpufreq calculation.

 

So your point is that my crystal is running 20.000.000 Hz and I use only 100.000, factor 200 difference -> 200 cycles.

I know you already mentioned that it would be wise to adjust the clock speed. But does this improve the speed (you only adjust the CTC value). Or is it the goal to create more cycles between each tick so it has more "power" to do the calculations?

 

Anyway a lot of questions which gives me headache. I did use the timer code on only measuring the RPM (old code) and then it was just as off as in this code.

(Btw frequencies needed to be able to create (with 2 pulses per rotation) are between 0 till 666)

 

Removed due to changes made to source -> scroll down for a later post that holds actual code!

 

 

 

Last Edited: Mon. Mar 30, 2015 - 03:11 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The simulator doesn't simulate in real time. This is of little consequence. You want to measure the execution time of the ISR. You could look at the assembler code and count the number of machine cycles needed or let the simulator do it for you. I'm lazy, so I'd use the simulator. If your ISR takes 200 cycles, then there is no cycles left to do other work. If it takes more, then your timing goes out the window. It might take over 60 cycles just to get into and out of the ISR then you've got a number of 16 bit operations and some 32 bit operations, so 200 cycles is looking rather dangerous. The answer is to measure or manually count. You timer rate is rather high for what you want to achieve - for the pulse generation it is probably 10 times faster than it needs to be. For measuring you fan speed, sampling 100,000 times a second for a fan that is spinning 100 times a second (6000rpm) means you can resolve to 1/3 of a degree of rotation. If the pulse is a few degrees wide, then you've got more than enough resolution - besides, it's a fan - what sort of precision do you need?

If the simulator is correct, it is taking 4 seconds to do what should be 1 seconds - something is amiss.

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

Ok, you've made me use Studio 6 - just waiting 5 minutes for it to start up is time I'll not recover!

I couldn't make sense of the stopwatch, but the cycle counter made sense. You code makes references to portB but you use portA - very confusing. Anyway, I built it for a mega328.

Without taking into account the entry and exit to the ISR the best case was 130 cycles and the worst was 268 cycles. Every second you're going to have a blip in your timing. At a guess, (I could figure out how to step in the assembler view), it's around 56 cycles to get into and out of the ISR. So that's 186 cycles at a minimum - that's 93% of your cpu best case! Factor in your pcint ISR, and you've run out of time. By halving your time tick rate, you've got back nearly 50% of your cpu time for doing processing other than your timer tick ISR. 

 

Anyway, the delayed viewing of the Malaysian F1 is on the telly now, must go.

 

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

Okey, I fully understand what you are saying. 

I need to have max 15k rpm (which is 500Hz)

Furthermore I need 1Hz for the timer (seconds), preferably with 1 decimal in case I need to increase the resolution. So 1,1 second or 0.5 second (500ms) ->10Hz

 

 

Reread the manual:

https://www.avrfreaks.net/forum/t...

 

Timer Resolution = (1 / Input Frequency)
Timer Resolution = (1 / 10) = 0.1 seconds
Timer Resolution = (1 / 500) = 0.002 seconds
Timer Resolution = (1 / 100000) = 0.00001 seconds -> current setup
Timer Resolution = (1 / 1000) = 0.001 seconds -> current setup

Now calculate target timer count:

Target Timer Count = (1 / Target Frequency) / (1 / Timer Clock Frequency) - 1
                   = (1 / 1000) / (1 / 20000000) - 1
                   = 0.001 / 0.00000005 - 1
                   = 20000 - 1
                   = 19999

So without prescale I need 19999 ticks for 1000Hz -> 19999*1000=19999000

For 100000Hz this would be 199. To get 1 second ->   199*100000=19900000

 

Calculate correct prescaler

Target Timer Count = (Input Frequency / Prescale) / Target Frequency - 1

Prescaler Value | Target Timer Count

              1 |  19999
              8 |  2499
             64 |  311.5
            256 |  77.125
           1024 |  18.53

So based on the 1000Hz freq I choose to get a nice 0.001 resolution (which I think is accurate enough to make me 500Hz) I can use a prescaler of 8.

 

   TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode
   OCR1A   = 2499; // Set CTC compare value to 1000Hz at 20MHz AVR clock, with a prescaler of 8

   TCCR1B |= (| (1 << CS11)); // Start timer at Fcpu/8

Does this makes sence?

Have fun while watching F1

 

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

The question is how fast does the computer change speed of the fans, will jitter on the speed confuse it, in that case perhaps you will need it a tad better resulution.

 

One thing that isn't sure for me is should the pulse have always have same length? (your code don't do that)

If same length you should have a "high" counter, where you set the pulse, and a 8 bit counter,  then for each run you -- it if !=0 and when 0 you clear the pulse.

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

Sebas, i think you need to stop and clearly evaluate what is required. Otherwise you are just going around in circles. I say 100KHz was too high and gave evidence that half that would be better. You've now gone the other way and are suggesting 1kHz which i'd hazard to guess is too low. Coupled with that we have some unknowns so giving a concise suggestion is difficult.

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

Kartman wrote:

Sebas, i think you need to stop and clearly evaluate what is required. Otherwise you are just going around in circles. I say 100KHz was too high and gave evidence that half that would be better. You've now gone the other way and are suggesting 1kHz which i'd hazard to guess is too low. Coupled with that we have some unknowns so giving a concise suggestion is difficult.

 

Ow no, don't interpret my calculation as the other way. I am only trying (based on math) to calculate the best way (based on your comments).

I am struggling with getting clear what/how to make visible where my bottleneck is/are.

 

I agree with you 100% that 100khz is high and that it can be lowered. However I would like to see how I can derive "half of that" next time myself.

So do not get this as arguing or doubting whatever, really appreciate you patience.

 

Btw just altered my code to run with 50k Hz and I now get structural 999.999,95 µs ~ 1.000.999,95 µs out of the simulator.

Could you tell me in a little more detail how I can accomplish this next time by myself (how to analyse).

Haven't tested the code in RL yet. That is for the evening, atm working :(

 

Update: Tested the code and I do not understand, when I am running and get output for 1 sec rpm this is 600Hz. even though the fan itself can run 10k according spec.

http://media.digikey.com/pdf/Dat...

With this schematic: http://www.pavouk.org/hw/fan/en_...

(Only TACH part)

 

10k rpm (per minute) = 166Hz (2 periods per rouding so times 2 = 333)

Removed due to changes made to source -> scroll down for a later post that holds actual code!

 

Last Edited: Wed. Apr 1, 2015 - 05:18 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Found my problem (I think). The piece of code which was handling the external interrupts was in the initial code responding to a high and a low, causing it to 2 times count 1 pulse.

After adjusting it with the underneath code it now gives sensible values. My fan reports 293~294~295. Which is something I am happy with. Translated to RPM this is 8850.

 

Now I can continue to test the wave generation to the MB. Very curious if it works... Fingers crossed.

 

	if(changedbits & (1 << PA0))
	{
		/* PCINT0 changed */
		/* LOW to HIGH pin change */
		if( (PINA & (1 << PA0)) == 1 )
		{
			// Only on high add count
			rpmcount[0]++;
		}
	}

Update: tested with above addition and RPM is OK. Next step was to test the wave generation (Square wave 50:50). Amazingly this work directly.

At the moment my AVR is just a sandbox. Next step now is to add the ratio calculation.

Will test that functionality tomorrow. But besides that I am already quite happy with the current result. When tomorrow is a success I can finally finalize my print and get it made.

Order the necessary stuff and then hopefully this weekend I have my server running (nice and smooth AND SILENT).

 

Kartman (and others) if you have any comments on my code please feel free.

Removed due to changes made to source -> scroll down for a later post that holds actual code!

 

Last Edited: Wed. Apr 1, 2015 - 05:18 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sebas06 wrote:

 

Calculate correct prescaler

Target Timer Count = (Input Frequency / Prescale) / Target Frequency - 1

Prescaler Value | Target Timer Count

              1 |  19999
              8 |  2499
             64 |  311.5
            256 |  77.125
           1024 |  18.53

So based on the 1000Hz freq I choose to get a nice 0.001 resolution (which I think is accurate enough to make me 500Hz) I can use a prescaler of 8.

 

   TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode
   OCR1A   = 2499; // Set CTC compare value to 1000Hz at 20MHz AVR clock, with a prescaler of 8

   TCCR1B |= (| (1 << CS11)); // Start timer at Fcpu/8

I'm not quite following this - in DDS you have some fixed-but-high adder-rate.

It seems to have been established that /200 is too tight, and 400+ is better.

The DDS add-rate sets the jitter on the output, so you do not want to make it too slow, at least initially.

Using the jitter-ceiling you can work from the other end for a adder-rate.

eg if 5% jitter at 500Hz is ok (1% at 100Hz) then you can use a Adder-rate as low as 20x 500Hz or  10KHz  (2000 cycles)

 

You can also reduce the jitter in special cases, by choosing adder values that are binary-fitted.

eg assume an adder-rate of

 20M/8/156 = 16.025KHz, then if you add exactly 32 times, you have fa/32 = 500.801 Hz with no jitter.

( to add 32x, you increment by 2048). There is also another jitter free point at 250.4Hz

- if you find that the host fan controller is bothered by jitter, these values may give a way to avoid that, & still fool it.

16.025KHz gives ~ 3% jitter, and if you choose an adder  value close to 2048, but (say) just above, sometimes it will need 32 adds for a full period, and sometimes it will need 33.

The HW outputs either 500.80Hz or 485.625 Hz, with varying weightings to give an average fo that is more precise.

 

Let's say we want closest to the mid-value,  493.2125Hz, of those two :

That is an adder-value of 2016 rounded, which gives an actual fo of 492.976Hz, and on a scope. that will nearly always alternate

500.80Hz & 485.625 at 50:50 ratios.

Another way to look at this, is that edges must always be placed on a 62.4us timing 'grid' but they can dither over multiple cycles to give average times finer than that 62.4us granularity.

 

 

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This will be my last reply in this topic. I have finished my code and it is working as expected. With this code you are able to control (manipulate) the TACH output of 5 different fans and adjust that output by a given ratio. Then that new frequency is used to create a square wave to feed back to the mainboard. The mainboard picks this signal up and you can display/adjust whatever you want.

 

Unfortunately this solution did not solve my initial problem. For this I will start a new project.

I would like to thank especially Kartman for his patience and help during this project.

 

(Code written for an AVR644)

#ifndef F_CPU
#define F_CPU 20000000UL
#endif

# define SAMPLERATE 50000UL
# define USART_BAUDRATE 9600

// INRATIO:OUTRATIO
# define INRATIO 1
# define OUTRATIO 1

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "uart.h"

uint16_t rpmcount[5];						// default start counting at 0 for fan 1
uint16_t rpmcalc[5];
volatile uint8_t portbhistory = 0xFF;		// default is high because the pull-up
char buffer_var[10];						// buffer used for usart translations

volatile uint8_t flag = 0;
volatile uint16_t phase_inc[5];

uint32_t timer_tick=0;
uint16_t phase_acc[5];

// variables for freq monitoring
uint16_t frequency[5];
uint8_t freqTolerance = 2;					//adjust this to increase/decrease stability of frequency measurement


uint16_t phase_inc_var = 0;

ISR (PCINT0_vect)
{
	uint8_t changedbits;

	changedbits = PINA ^ portbhistory;
	portbhistory = PINA;

	if(changedbits & (1 << PA0))
	{
		/* PCINT0 changed */
		/* LOW to HIGH pin change */
		if( (PINA & (1 << PA0)) == 1 )
		{
			// Only on high add count
			rpmcount[0]++;
		}
	}

	if(changedbits & (1 << PA1))
	{
		/* PCINT1 changed */
		/* LOW to HIGH pin change */
		if( (PINA & (1 << PA1)) == 1 )
		{
			// Only on high add count
			rpmcount[1]++;
		}
	}

	if(changedbits & (1 << PA2))
	{
		/* PCINT2 changed */
		/* LOW to HIGH pin change */
		if( (PINA & (1 << PA2)) == 1 )
		{
			// Only on high add count
			rpmcount[2]++;
		}
	}
	if(changedbits & (1 << PA3))
	{
		/* PCINT3 changed */
		/* LOW to HIGH pin change */
		if( (PINA & (1 << PA3)) == 1 )
		{
			// Only on high add count
			rpmcount[3]++;
		}
	}
	if(changedbits & (1 << PA4))
	{
		/* PCINT4 changed */
		/* LOW to HIGH pin change */
		if( (PINA & (1 << PA4)) == 1 )
		{
			// Only on high add count
			rpmcount[4]++;
		}
	}
}

void INPUT_Init()
{
	DDRA &= ~((1 << DDA0) | (1 << DDA1) | (1 << DDA2) | (1 << DDA3) | (1 << DDA4)); // Clear the PA0, PA1, PA2 pin 
	PORTA |= ((1 << PA0) | (1 << PA1) | (1 << PA2) | (1 << PA3) | (1 << PA4));      // turn On the Pull-up
	
	PCICR |= (1 << PCIE0);     // set PCIE0 to enable PCMSK0 scan
	PCMSK0 |= (1 << PCINT0);   // set PCINT0 to trigger an interrupt on state change
}

void OUTPUT_Init()
{
	//set port/pin  mode
	DDRC |= ((1<<DDC0) | (1<<DDC1) | (1<<DDC2) | (1<<DDC3) | (1<<DDC4));
	DDRD |= (1<<DDD3);          // set LED pin PD1 to output
}

ISR (TIMER1_COMPA_vect)
{
	timer_tick ++;
	if (timer_tick  == SAMPLERATE){
		// Trigger frequency calculation
		flag=1;
		memcpy( rpmcalc, rpmcount, sizeof(rpmcount) );	// copy contents of rpmcount so interrupt can stay running							
		memset(rpmcount, 0, sizeof rpmcount);			// Reset array "rpmcount"
		timer_tick = 0;									// Reset timer
	}
	
	// All individual fan accumulators
	phase_acc[0]+=phase_inc[0];
	// copy msb to port bit
	if (phase_acc[0] & 0x8000){
		PORTC |= (1<<PC0);			// Set port bit
	}
	else
	{
		PORTC &= ~(1<<PC0);			// Clear port bit
	}
	
	phase_acc[1]+=phase_inc[1];
	// copy msb to port bit
	if (phase_acc[1] & 0x8000)
	{
		PORTC |= (1<<PC1);			// Set port bit
	}
	else
	{
		PORTC &= ~(1<<PC1);			// Clear port bit
	}
	
	phase_acc[2]+=phase_inc[2];
	// copy msb to port bit
	if (phase_acc[2] & 0x8000)
	{
		PORTC |= (1<<PC2);			// Set port bit
	}
	else
	{
		PORTC &= ~(1<<PC2);			// Clear port bit
	}
	
	phase_acc[3]+=phase_inc[3];
	// copy msb to port bit
	if (phase_acc[3] & 0x8000)
	{
		PORTC |= (1<<PC3);			// Set port bit
	}
	else
	{
		PORTC &= ~(1<<PC3);			// Clear port bit
	}
		
	phase_acc[4]+=phase_inc[4];
	// copy msb to port bit
	if (phase_acc[4] & 0x8000)
	{
		PORTC |= (1<<PC4);			// Set port bit
	}
	else
	{
		PORTC &= ~(1<<PC4);			// Clear port bit
	}

}
void timer_init()
{	
	TCCR1B |= (1 << WGM12);					// configure timer1 for CTC mode
	TIMSK1 |= (1 << OCIE1A);				// enable the CTC interrupt
	//Target Timer Count = (Input Frequency / Prescale) / Target Frequency - 1
	OCR1A = (F_CPU/SAMPLERATE)-1;			// set the CTC compare value
	
	TCCR1B |= (1 << CS10);					// enable timer with NO prescaler
}

void checkFreq() {
	// temporary display current rotations per 1000ms (in pulses)
	for (int y=0; y<=4; y++){
		itoa(rpmcalc[y], buffer_var, 10);
		uart_puts(buffer_var);
		uart_puts("\r\n");
	}

	// Calculation new FAN delta phase (based on "current real rotations")
	for (int y=0; y<=4; y++){
		if (abs(rpmcalc[y]-frequency[y])>freqTolerance){					// if new value exceeds tolerance
			frequency[y] = rpmcalc[y];										// new frequency
			phase_inc[y] = (((uint32_t)(frequency[y])*OUTRATIO)<<16) / (SAMPLERATE*INRATIO);		// Calculate 1 period versus complete sample rate
		}
	}
	memset(rpmcalc, 0, sizeof rpmcalc);
}

int main(void)
{
	// Initialize program parameters/functionalities
	
	cli();													// Disable interrupt
	timer_init();											// Init Timer
	INPUT_Init();											// Init Input ports
	uart_init( UART_BAUD_SELECT(USART_BAUDRATE,F_CPU) );	// Init UART functionality
	OUTPUT_Init();
	
	// Local Variables used
	memset(rpmcount, 0, sizeof rpmcount);					// Initialize first time rpmcount
	checkFreq();											// Make sure all fans have RPM on start	
	
	// Start Program
	sei();													// Enable interrupt
	
	while (1) {
		/* Wait for the ISR to set the 
		* flag; reset it before 
		* taking any action. */ 
		if (flag==1) 
		{ 	
			flag = 0;
			/* Perform the required action here */
			checkFreq();	
		}
	}
}

 

Last Edited: Wed. Apr 1, 2015 - 05:17 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have a quick question concering my above code. It should detect incomming interrupts (which is done). However I want to detect only the rising edge.

I did this with the small if blocks. However this only works for rpmcount0. rpmcount1 etc do not detect. Or better said they do not count inside the if.

If I move the if outside the rising edge detection it works OK. Same signal is applied to PA0 AND PA1.

 

ISR (PCINT0_vect)
{
	uint8_t changedbits;

	changedbits = PINA ^ portbhistory;
	portbhistory = PINA;

	if(changedbits & (1 << PA0))
	{
		/* PCINT0 changed */
		/* LOW to HIGH pin change */
		if ( (PINA & (1 << PA0)) == 1 )
		{
			// Only on high add count
			rpmcount[0]++;
		}
	}

	if(changedbits & (1 << PA1))
	{
		/* PCINT1 changed */
		/* LOW to HIGH pin change */
		
		if ( (PINA & (1 << PA1)) == 1 )
		{
			// Only on high add count
			rpmcount[1]++;
		}
	}
}

 

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

if ( (PINA & (1 << PA1)) == 1 )

This will never equal 1. Since you mask only one bit, test for true or false.

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

Kartman, I tried to figure out so I have written it out, but don;t get my flaw:

 

 

changedbits = PINA ^ portbhistory;
portbhistory = PINA;
changedbits =00000010 // PA1 Changed
if(changedbits & (1 << PA1))
00000010 & 00000010 = 00000010 // Match so execute IF block
if ( (PINA & (1 << PA0)) == 1 )
00000000 & 00000010 // Pin was low so do not execute -> result was 0==1
00000010 & 00000010 // Pin was high so execute if block -> result was 1==1

I do not understand your commend concerning BITMASK 1 bit...

If you mean to test for true (pin is high) or false (pin is low), it is enough to just change the bitwise & to && (this checks only if the PA0 is high in the PINA bit set)

 

00101010 && 00000010 // Pin was high so execute if block -> result was 1==1

I think I got it:

if ( (PINA & (1 << PA1)) == (1 << PA1) )
00100010 & 00000010 == 00000010 // Match so execute IF block

Is my above assumption correct? (I think it my solution (1 and 2) is NOT, so I crossed it...)

 

Furthermore, I am going to use the above project. When I have it running I will explain how and what.

 

Reference source (for myself):

http://en.wikipedia.org/wiki/Mask_%28computing%29

http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B

http://www.cplusplus.com/doc/boolean/

 

 

Last Edited: Sat. May 2, 2015 - 07:19 AM

Pages