Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

Averaging / integration in VHDL

Status
Not open for further replies.

kureigu

Member level 2
Member level 2
Joined
Jan 14, 2013
Messages
49
Helped
0
Reputation
0
Reaction score
0
Trophy points
1,286
Location
Scotland
Visit site
Activity points
1,779
Hey all, I have a problem and I'm not sure of the best way to approach it, so I was hoping someone might be able to point me in the right direction.

I have an angular rate sensor that's going to be used to determine the change in angle on the horizontal plane. I have it all set up to and ADC and I'm able to read it via SPI using an SPI master component I developed. Now the problem is, the rate sensor outputs a rate of change in angle rather than an actual change in angle. So I figure the best way to get the actual angle out, is by integration (i.e. time averaging over a set number of samples).

This is where I begin to struggle a little since I'm not sure of the best way to go about implementing this in VHDL would be. I've done it before with embedded C, using a circular buffer and continual subtractions and additions to get a moving average, but I can't figure out how I'd do that in VHDL since the whole approach is different.

TL;DR; When a value from the rate sensor is read in, I want to integrate over n samples to determine the angular change, how do?


Edit: I attempted to write a component to do the averaging, but I don't think I've done it the best way. Ideally I want to use a generic to set up the number of samples to average over, but I'm unsure of how to make an array of 12 bit signals.

Code:
library ieee;
use std_logic_1164.all;
use std_logic_arith.all;
use std_logic_unsigned.all;

entity time_avg is
	generic(	rate: integer := 64
				);
	port(	sample: in unsigned(12 downto 0);
			avg: out unsigned(12 downto 0)
			);
end time_avg;

architecture integrate of time_avg is
	
	signal sum: unsigned (13 downto 0);
	signal running_avg: unsigned(12 downto 0) := "000000000000";
	signal count: unsigned(1 downto 0) := 0;
	signal s0, s1, s2, s3 := unsigned(12 downto 0) := "000000000000";
	
begin

	process(sample)
	
		case count is
			when "00" =>
				s0 <= sample;
				
			when "01" =>
				s1 <= sample;
			
			when "10" =>
				s2 <= sample;
			
			when "11" =>
				s3 <= sample;		
		end case;
		
		sum <= s0 + s1 + s2 + s3;
		running_avg <= sum(13 downto 2);
                count <= count + 1;
	end process;

end integrate;
 
Last edited:

The easier way to do this is just calculate a running sum, no need for those intermediate values. Initialize your counter and your running sum, and then add your new value to your existing sum at each sample time (This is called an accumulator, by the way) When your counter reaches its terminal value, you've got your answer in the running sum.

Make sure your running sum is large enough to avoid overflow.

No, wait. I missed something. You wanted a running average.

Anyway, to set up an array:

type array_type is array (0 to number_of_signals-1) of std_logic_vector(11 downto 0);

signal my_array:array_type;
 
Last edited:
Well the first problem is you have no clock. Without a clock, nothing is going to work reliably. For a start, your counter will not count (at the moment, it just adds one all the time in a combinatorial feedback way). Sensitivity lists are ignored in synthesis, so all you will be getting is combinatorial logic.
 

Well the first problem is you have no clock. Without a clock, nothing is going to work reliably. For a start, your counter will not count (at the moment, it just adds one all the time in a combinatorial feedback way). Sensitivity lists are ignored in synthesis, so all you will be getting is combinatorial logic.

I thought the whole point in a sensitivty list was that the process activated when anything in the sensitivity list changed?
I'm not really sure if/how clocking this will work, since I want it to happen only after the rate has been read from SPI, but there are also 2 other SPI reads to do before this one...

No, wait. I missed something. You wanted a running average.

Anyway, to set up an array:

type array_type is array (0 to number_of_signals-1) of std_logic_vector(11 downto 0);

signal my_array:array_type;

I'm glad you realised, because I was thoroughly confused by your first stab at it. Thanks for the info on the arrays though.
 

I thought the whole point in a sensitivty list was that the process activated when anything in the sensitivity list changed?

In VHDL as a language yes, in real hardware no. This can be useful for testbenches, but you have to remember that your code needs to map to real hardware, and for synthesis sensitivity lists are ignored, and logic generated from the specified code behaviour (without a sensitivity list). So without a clock, your counter isnt going to work.

It may be worthwhile going through a VHDL for digital design textbook or tutorial.
 

Sensitivity lists are really just for simulation.

Tricky is right, you need a clocked process. And you need some kind of enable to determine when the counter and adder are updated; you probably DON'T want it to occur on every clock event, you'll want it to occur on some (slower) time-based event. And I don't think you want to update every time you get new SPI data unless you're sure that the data is coming in at a known, constant rate, otherwise your calculation will be off.
 

Sensitivity lists are really just for simulation.

Tricky is right, you need a clocked process. And you need some kind of enable to determine when the counter and adder are updated; you probably DON'T want it to occur on every clock event, you'll want it to occur on some (slower) time-based event. And I don't think you want to update every time you get new SPI data unless you're sure that the data is coming in at a known, constant rate, otherwise your calculation will be off.

I actually do know when I'll get the data, since I'm periodically requesting data from the rate sensor's ADC via SPI at a consistent rate. I could maybe throw something into the state machine that handles the SPI comms to trigger the component every time new data is available, and within the accumulating component I could just keep track of the number of samples. Doing this though, I'm never sure if it's the best way because it certainly doesn't seem elegant...

Pretty much, I'm gonna need another state machine for this, right? Seems like with VHDL when I can't figure something out, it needs to be implemented with a state machine..
 

State machines are your friend.

FPGAs have lots and lots of registers, which makes state machines a logical (pardon the pun) way to go. If you end up with a state machine with 100 states, then maybe you should rethink your approach, but there's nothing wrong with using state machines.

- - - Updated - - -

Some other comments:

Rather than doing N additions each time it MIGHT be more efficient to do something like: SUM=SUM-oldest data+newest data. For four samples this might not make sense, but if you have a large number of samples per average, this method would probably be much faster.

If you have a small number of samples/average storing them in registers is fine, but if you are using a large number, then you might want to use memory.
 

Averaging and integration are not identical.

A moving average is a bad way to calculate the angle.
A moving average calculator requires a memory, and that is not necessary here.

In this case, it is better to calculate the average over n samples and then start over again.
Only an accumulator and additions are needed. No subtraction of old values.

However, I think the best way is to do real integration, which is just to accumulate. Only "start over" when you want to calibrate. You will have instant access to the angle, without delay. If you want the accumulator to automatically wrap after 360 degrees, you must multiply all samples with a constant before the accumulator, and the constant depends on the sample rate and other things. It is also possible to use comparators for the wrapping, for example if you want the angle directly in degrees.
 
A moving average is a bad way to calculate the angle.
A moving average calculator requires a memory, and that is not necessary here.

In this case, it is better to calculate the average over n samples and then start over again.
Only an accumulator and additions are needed. No subtraction of old values.

I've realised this since I first started the thread, and it makes things much simpler thankfully. I just subtract the offset value of the rate sensor when it's not moving every time I add a new sample and I can change the 'angle' displayed quite easily.

I will want the angle in degrees eventually, but was planning on just converting that in software. Right now I have to deal with the sensitivity/noise on the thing; the rate is shifting about by one or two bits when it's stationary, even after throwing away the LSB of the data from the rate sensor... and obviously that error itself it fine, but when I integrate continually it introduces somewhat of a drift in the output...

UPDATE:
Not sure I should start a new thread in a more relevant forum for this, but I'll add it here anyway since it's related and you guys might have some thoughts on it.

I added a cap (47uF) I had lying around across the output of the rate sensor to ground, in an attempt to smooth the signal slightly. It's definitely working and that 1 bit of jitter that was causing the integral to drift is mostly gone.
I'm almost certain this isn't the best way to get rid of the problem, and my mind is too frazzled right now to do the maths to decide if it's going to have a detrimental affect on the output accuracy.
As I say, I add this purely in case someone here might have a better solution or some interesting insight.

Also, if anyone's interested, here's the Rate sensor I'm using right now: https://datasheet.octopart.com/MEV-50A-R-Murata-datasheet-12558096.pdf
 
Last edited:

Status
Not open for further replies.

Similar threads

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top