I want to measure the frequency of a PWM signal. To do that, I'm using STM-F401RE and its Timer_Input_Capture function.
The problem is:
the input signal has a quite high frequency (approx. 2MHz) and,
the STM-F401RE controller has only a 80MHz clock
Therefore when using an interrupt routine for counting the number of rising edge of the input signal, it misses many rising edges (depending on the frequency of the input signal). When using an Oscilloscope and toggling an I/O-pin, I saw that it can only capture all the rising edge when the frequency is lower than 400kHz.
Question is: How to overcome this problem? or is there another way to measure an input pwm signal with high frequency ?
Thanks
You need to setup your timer as PWM input and not capture input (TIM1 for example can do it). This way, 2 channels are used (but you have only one physical connection). Basically, the first channel will give you the period and the second channel will give you the pulse. The counters are automatically reset.
The init function:
void tim_init()
{
TIM_SlaveConfigTypeDef sSlaveConfig;
TIM_IC_InitTypeDef sConfigIC;
TIM_MasterConfigTypeDef sMasterConfig;
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 65535;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
HAL_TIM_IC_Init(&htim1);
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET;
sSlaveConfig.InputTrigger = TIM_TS_TI2FP2;
sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sSlaveConfig.TriggerPrescaler = TIM_ICPSC_DIV1;
sSlaveConfig.TriggerFilter = 0;
HAL_TIM_SlaveConfigSynchronization(&htim1, &sSlaveConfig);
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&htim1, &sConfigIC, TIM_CHANNEL_1);
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
HAL_TIM_IC_ConfigChannel(&htim1, &sConfigIC, TIM_CHANNEL_2);
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);
//Enable interrupt
HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_2);
HAL_TIM_IC_Start(&htim1, TIM_CHANNEL_1);
}
And the interrupt handler
void tim_irq()
{
period = HAL_TIM_ReadCapturedValue(&htim1, TIM_CHANNEL_2);
pulse = HAL_TIM_ReadCapturedValue(&htim1, TIM_CHANNEL_1);
//First irq to be ignored
}
You can calculate the cycles that your code takes in your interrupt and add the extra cycles for interrupt latency (10-20 ?). Then you can see what your maximum capture frequency will be. I'll bet it will be close to 400Khz.
I think there is no way that this can be done with STM-F401RE running at 80Mhz.
Related
I used STM32F407VG to create a 30 khz sine wave. Timer settings are; Prescaler = 2-1, ARR = 1, also the clock is 84 Mhz(the clock which runs DAC).
I wrote a function called generate_sin();
#define SINE_ARY_SIZE (360)
const int MAX_SINE_DEGERI = 4095; // max_sine_value
const double BASLANGIC_NOKTASI = 2047.5; //starting point
uint32_t sine_ary[SINE_ARY_SIZE];
void generate_sine(){
for (int i = 0; i < SINE_ARY_SIZE; i++){
double deger = (sin(i*M_PI*360/180/SINE_ARY_SIZE) * BASLANGIC_NOKTASI) + BASLANGIC_NOKTASI; //double value
sine_ary[i] = (uint32_t)deger; // value
}
This is the function which creates sine wave. I used HAL DMA to send DAC output variables.
HAL_TIM_Base_Start(&htim2);
generate_sine();
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, sine_ary, SINE_ARY_SIZE, DAC_ALIGN_12B_R);
These are the codes i used to do what i want. But im having a trouble to change frequency without changing prescaler or ARR.
So here is my question. Can i change frequency without changing timer settings ? For example i want to use buttons and whenever i push button i want my frequency to change.
The generate_sine function will give you one period of a sine wave which has SINE_ARY_SIZE of samples.
To increase the frequency you need to make the period shorter (for 2x frequency, you would have half the number of samples per period). So you should calculate the array for smaller SINE_ARY_SIZE (which will fill just part of the original buffer with a shorter sine wave) and also put this smaller value in the HAL_DAC_Start_DMA function.
Decreasing the frequency will require making the array longer.
You should declare the sine_ary with a maximum length that you will need (for lowest frequency). Make sure it fits in RAM.
#define MAXIMUM_ARRAY_LENGTH 360
uint32_t usedArrayLength = 180;
const double amplitude = 2047.5;
uint32_t sine_ary[MAXIMUM_ARRAY_LENGTH];
void generate_sine(){
for (int i = 0; i < usedArrayLength; i++){
double value = (sin(i*M_PI*2/usedArrayLength) * amplitude) + amplitude;
sine_ary[i] = (uint32_t)value; // value
}
This will have two times higher frequency than the original code, because it only has 180 samples per period, compared to 360.
Start it using
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, sine_ary, usedArrayLength, DAC_ALIGN_12B_R);
To change the frequency, stop DAC, change the value of usedArrayLength (smaller value means higher frequency, must be less or equal to MAXIMUM_ARRAY_LENGTH). Then call the generate_sine function and start the DAC again by the same function (that now uses new usedArrayLength).
Frequency will be: Clock/prescaler/ARR/usedArrayLength
Also, you should use uint16_t for the array (values are from 0 to 4095, the DAC is 12bit I suppose) and DMA should be set to Half-word (2 bytes per value).
I have a problem with the quadrature encoder mode on timer TIM3 of my
STM32F446RE /
NUCLEO-F446RE:
TIM3 counts on every rising edge on the first signal.
The CNT register counts up and I read the value with 1 Hz and then
I set the register to 0.
When I look on the
oscilloscope
the frequency is half as high as the value from the
CNT register output (1hz).
Why?
TIM3 counts on both edges on the first signal.
The
CNT register output (1 Hz)
is completely wrong.
My configuration is:
GPIO_InitTypeDef sInitEncoderPin1;
sInitEncoderPin1.Pin = pin1Encoder.pin; // A GPIO_PIN_6
sInitEncoderPin1.Mode = GPIO_MODE_AF_PP;
sInitEncoderPin1.Pull = GPIO_PULLUP;
sInitEncoderPin1.Speed = GPIO_SPEED_HIGH;
sInitEncoderPin1.Alternate = altFunctionEncoder; // GPIO_AF2_TIM3
GPIO_InitTypeDef sInitEncoderPin2;
sInitEncoderPin2.Pin = pin2Encoder.pin; // A GPIO_PIN_7
sInitEncoderPin2.Mode = GPIO_MODE_AF_PP;
sInitEncoderPin2.Pull = GPIO_PULLUP;
sInitEncoderPin2.Speed = GPIO_SPEED_HIGH;
sInitEncoderPin2.Alternate = altFunctionEncoder; // GPIO_AF2_TIM3
HAL_GPIO_Init(GPIOA, &sInitEncoderPin1);
HAL_GPIO_Init(GPIOA, &sInitEncoderPin2);
encoderTimer.Init.Period = 0xffff;
encoderTimer.Init.Prescaler = 0;
encoderTimer.Init.CounterMode = TIM_COUNTERMODE_UP;
encoderTimer.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
encoderTimer.Init.RepetitionCounter = 0;
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 1);
encoder.EncoderMode = TIM_ENCODERMODE_TI1;
encoder.IC1Filter = 0x0f;
encoder.IC1Polarity = TIM_INPUTCHANNELPOLARITY_RISING; // TIM_INPUTCHANNELPOLARITY_BOTHEDGE
encoder.IC1Prescaler = TIM_ICPSC_DIV1;
encoder.IC1Selection = TIM_ICSELECTION_DIRECTTI;
encoder.IC2Filter = 0x0f;
encoder.IC2Polarity = TIM_INPUTCHANNELPOLARITY_RISING;
encoder.IC2Prescaler = TIM_ICPSC_DIV1;
encoder.IC2Selection = TIM_ICSELECTION_DIRECTTI;
HAL_TIM_Encoder_Init(&encoderTimer, &encoder);
HAL_TIM_Encoder_Start_IT(&encoderTimer, TIM_CHANNEL_ALL);
The
oscilloscope screenshot
shows a frequency of about 416 Hz.
The values shown in the
first shell output
are (very roughly!) twice as high (as the question points out already).
This appears (nearly...) correct to me since the shown configuration
encoder.EncoderMode = TIM_ENCODERMODE_TI1;
selects the "X2 resolution encoder mode", which counts 2 CNT increments per signal period.
In an application note on
timer overview,
(Sec. 4.3.4 / Fig. 7) there is an illustrative diagram how the encoder mode works in detail.
The
second screenshot
results from an incorrect TIM3 configuration:
The encoder mode (TIM_ENCODERMODE_TI1) assumes that both channels trigger only upon directed flanks in an alternating way (see the AN link above).
If one of the two channels triggers twice as many events due to
configuration
encoder.IC1Polarity = TIM_INPUTCHANNELPOLARITY_BOTHEDGE,
the counter will only count up one position and then "recognize" a "reversal" event (= change of direction).
Keeping in mind that
65535u = 0xFFFF = -1
the second screenshot only shows values -1, 0, +1 - which fits perfectly with this explanation.
The question remains why the first screenshot shows (reproducible) measurements between 800 and 822.
I assume that
the physical source of the encoder signal runs at a constant pace
the 1 Hz timer that triggered shell output is independent from TIM3, and
it has been started before the encoder timer
(i.e., above the shown code sample).
This may explain why the first two values look like nonsense (0: TIM3 has not been started yet. 545: TIM3 has been started during the shell output timer period).
Discarding the first two measurement samples, the average and standard deviation, resp., of the measured signal frequency are
808,9091 +/- 0,5950 [X2 increments per second]
404,4545 +/- 0,2975 [Hz]
which corresponds to a period of
2,4331 +/- 0,003 [ms].
Hence, the measured frequency is too low by about 11 Hz, i.e., measured period too high by nearly 30 µs, and this error is clearly beyond the statistical noise.
The question gives a hint where this error might come from:
The CNT register counts up and I read the value with 1 Hz and then I set the register to 0.
Whenever the 1 Hz "polling timer" expires, it triggers an interrupt
(or a logical event in polling software).
Processing of this interrupt/event may be delayed a little,
depending on other software (IRQ: deactivation times elsewhere in the software,
Polling: loop duration until event is polled).
Software reads CNT value.
Software resets CNT value to zero,
discarding further increments since the CNT value has been read.
TIM3 continues counting (from zero).
This hints that software needs 30 µs between (3.) and (4.), which would be quite a lot of time on an STM32F4.
Edit: I just re-checked the oscilloscope screenshot. The error is visible, but I believe it is smaller than I originally assumed (from counting flanks in the picture).
I'm collecting degree data from an encoder using an Arduino Uno, and then sending that data into MATLAB using USB. In order to get velocity of rotation as well, I'm generating and sending a timestamp after every degree data point from the Arduino into MATLAB. The data is generated flawlessly on the Arduino side--there's nothing wrong on the Serial Monitor.
In MATLAB, however, my data matrices fill up with quite a few NaNs, as well as the occasional integer with a missing first digit. For example, instead of 216 degree, I get 16 degrees coming through, or instead of 7265900 microseconds, I get 265900 microseconds coming through.
I've been able to solve the issue of losing time data by using Arduino's millis() clock instead of its micros() clock and by adding a 50 millisecond delay after the encoder reading. The problem of degree data loss goes away if I increase the delay after the millis() clock, but then degree data acquisition becomes too slow to be useful.
Any help would be much appreciated!
Code pasted below. First the Arduino code, then the MATLAB code.
Thank you!
David
#include <Adafruit_LEDBackpack.h>
#include <Adafruit_GFX.h>
#include <gfxfont.h>
#include <SPI.h> //Include the SPI library in this sketch.
#include <Wire.h> // Enable this line if using Arduino Uno, Mega, etc.
//#include <TinyWireM.h> // Enable this line if using Adafruit Trinket, Gemma, etc.
#include "Adafruit_LEDBackpack.h"
#include "Adafruit_GFX.h"
Adafruit_7segment matrix = Adafruit_7segment();
//Variables for SPI rotary encoder
boolean knob_detected = false;
byte slave_select = 10; //Slave select pin.
byte mosi = 11; //MOSI pin.
byte miso = 12; //MISO pin.
byte spi_clock = 13; //Clock pin.
byte read_counter = 96;
byte write_mode0 = 137;
byte write_mode1 = 144;
byte count1;
byte count2;
int count;
int val;
int button = 7;
int b = 0;
int degree;
int absolute;
unsigned long time;
// SETUP //
void setup() {
//Connect the 4 communication pins between the Arduino and the quadrature counter.
#ifndef __AVR_ATtiny85__
Serial.begin(9600);
//Serial.println("7 Segment Backpack Test");
#endif
matrix.begin(0x70);
pinMode(slave_select, OUTPUT);
pinMode(mosi, OUTPUT);
pinMode(miso, INPUT);
pinMode(spi_clock, OUTPUT);
// pinMode(button,INPUT);
SPI.begin(); //Begin SPI communication.
SPI.setBitOrder(MSBFIRST); //Bytes read into SPI will be read in MSB style.
SPI.setDataMode(SPI_MODE0); //Set the data to shift on the low clock phase with normal (non-inverted) clock polarity.
digitalWrite(slave_select, HIGH); //Close the SPI communication port.
SPI.setClockDivider(SPI_CLOCK_DIV128); //Set the SPI clock to 125 kHz (This can be changed).
digitalWrite(slave_select, LOW); //Set slave select low to enable communication.
SPI.transfer(write_mode0); //Select mode 0 for modifying.
SPI.transfer(0b01000011); //Write to mode 0: Synchronous Index, disable index, range-limit count mode, x4 quadrature.
digitalWrite(slave_select, HIGH); //Set slave select high to disable communication
digitalWrite(slave_select, LOW); //Set slave select low to enable communication
SPI.transfer(write_mode1); //Select mode 1 for modifying.
SPI.transfer(2); //Write across a 0b01 to mode1 to enable 2 byte data transfers
digitalWrite(slave_select, HIGH); //Set slave select high to disable communication
Serial.begin(9600); //Set the baud rate for serial data transmission.
//Serial.println("READY"); //Print "READY" to the serial line to indicate that initialization is complete.
}
// MAIN LOOP //
void loop() {
digitalWrite(slave_select, LOW); //Set slave_select pin low to begin tranmission .
SPI.transfer(read_counter); //Read from the read_counter register.
count1 = SPI.transfer(0); //Send a null transfer to read byte1 values.
count2 = SPI.transfer(0); //Send a null transfer to read byte2 values.
digitalWrite(slave_select, HIGH); //Set slave_select high to end transmission.
count = word(count1, count2); //Type cast the 2 bytes as a word.
degree = count / 4;
absolute = abs(degree);
//Send back the value of the register over the serial line.
b = digitalRead(button);
if (b == HIGH) {
digitalWrite(absolute,LOW);
}
else {}
if (absolute == LOW)
{
absolute = 0;
}
else {}
Serial.println(absolute); //Send back the value of the register over the serial line.
matrix.print(absolute,DEC);
matrix.writeDisplay();
delay(50); //Pause for 50 milliseconds before re-looping.
time = millis();
Serial.println(time);
delay(10);
}
clear all;
close all;
clc;
delete(instrfind); %delete connected ports
p=getserialport;
[m,n] = listdlg('PromptString','Select Your Bluetooth device:','SelectionMode','single','ListString',p);
com=cell2mat(p(m));
s=serial(com);
fopen(s);
csd = figure;
set(csd,'Name','Plotting Arduino Data on MATLAB','NumberTitle','off'); % open Figure
t=1:100;% total Figure
hAx(1) = subplot(211);
hLine(1) = line('XData',t, 'YData',nan(size(t)), 'Color','b', 'Parent',hAx(1));
xlabel('Samples'), ylabel('Degrees');
hAx(2) = subplot(212);
hLine(2) = line('XData',t, 'YData',nan(size(t)), 'Color','b', 'Parent',hAx(2));
xlabel('Samples'), ylabel('Velocity');
set(hAx, 'Box','on', 'YGrid','on');
axis(hAx(1),[0,100,0,100]);
axis(hAx(2),[0,100,0,100000]);
d=zeros(1,100); %buffer of 100 elements
sm=zeros(1,10); %buffer of 10 elements
t10=zeros(1,10); %buffer of 10 elements
t100=zeros(1,100); %buffer of 100 elements
while(1)
j=1;
while(j<=10)
flushinput(s);
sc=(fscanf(s));
if(length(sc)<6) %1000 degree units = 6 char (was <7 for micros() clock)
sm(j)=str2double(sc);
else %i.e. else, if a timestamp (more than 7 chars)
t10(j)=str2double(sc);
j=j+1;
end
end
t100 = [t100,t10];
t100 = t100(11:end);
d=[d,sm]; %Add new Samples
d=d(11:end); %Remove old Samples
%d(end)
set(hLine(1), 'YData',d); %draw Degrees
set(hLine(2), 'YData',t100); %draw Velocity (currently just timestamps)
drawnow %# force MATLAB to flush any queued displays
end
(getserialport function defined by the following m-file)
function x=getserialport
serialInfo = instrhwinfo('serial');
x=serialInfo.SerialPorts;
end
I have the following code that wires a filter to an oscillator:
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// create Oscillator node
var oscillator = audioCtx.createOscillator();
oscillator.type = 'square';
oscillator.frequency.value = 1000; // value in hertz
oscillator.start();
var filter = audioCtx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 440;
oscillator.connect(filter);
filter.connect(audioCtx.destination);
Problem is that the filter seems to lower the gain of the oscillator rather than filtering out frequencies. What am i doing wrong here?
https://jsfiddle.net/matLkaeb/
I think everything is working. Change the filter frequency to, say, 10000 and listen carefully. Then change the frequency to 1000 and listen. There will be a difference. With a frequency of 440, you'll only get a single tone (mostly) from the oscillator, and the volume will be decreased because the oscillator fundamental frequency is outside the pass band of the filter.
Reverb.m
#define D 1000
OSStatus MusicPlayerCallback(
void* inRefCon,
AudioUnitRenderActionFlags * ioActionFlags,
const AudioTimeStamp * inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames
AudioBufferList * ioData){
MusicPlaybackState *musicPlaybackState = (MusicPlaybackState*) inRefCon;
//Sample Rate 44.1
float a0,a1;
double y0, sampleinp;
//Delay Gain
a0 = 1;
a1 = 0.5;
for (int i = 0; i< ioData->mNumberBuffers; i++){
AudioBuffer buffer = ioData->mBuffers[i];
SIn16 *outSampleBuffer = buffer.mData;
for (int j = 0; j < inNumberFrames*2; j++) {
//Delay Left Channel
sampleinp = *musicPlaybackState->samplePtr++;
/* IIR equation of Comb Filter
y[n] = (a*x[n])+ (b*x[n-D])
*/
y0 = (a0*sampleinp) + (a1*sampleinp-D);
outSample[j] = fmax(fmin(y0, 32767.0), -32768.0);
j++;
//Delay Right Channel
sampleinp = *musicPlaybackState->samplePtr++;
y0 = (a0*sampleinp) + (a1*sampleinp-D);
outSample[j] = fmax(fmin(y0, 32767.0), -32768.0);
}
}
}
Ok, I got a lot of info but I'm having trouble implementing it. Can someone help, it's probably something really easy i'm forgeting. It's just playing back as normal with a little boost but no delays.
Your treatment of the x0[] variables doesn't look right -- the way you have it, the left and right channels will be intermingled. You assign to x0[j] for the left channel, then
overwrite x0[j] with the right channel data. So the delayed signal x0[j-D] will
always correspond to the right channel, with the delayed left channel data being lost.
You didn't say what your sample rate is, but for a typical audio application, a
three-sample delay might not have much of an audible effect. At 44.1 ksamp/sec,
with a 3-sample delay the peaks and troughs of the filter response will be at
multiples of 14,700 Hz. All you'll get is a single peak in the audio frequency
range, in a part of the spectrum where there's hardly any power (assuming the
signal is speech or music).