STM32 HAL Nucleo F446RE Quadrature Encoder - stm32

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).

Related

Frequency Adjusting with STM32 DAC

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).

stm32F4 pwm input capture of high frequency signal approx. 2MHz?

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.

Transmitting data from Arduino UNO into MATLAB but getting a lot of NaNs and numbers with missing digits

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

Flicker frequencies in PTB

I'm trying to present a 4Hz flickering stimuli in PsychToolbox for 5 seconds followed by a 500Hz tone. Does anyone have an idea of how to do this? I've been using the vbl or screen refresh rate to calculate the flicker frequency but I'm not sure if I'm on the right track at all. I also have no idea how to present an auditory stimuli in PTB (I tried the sound function already). Any help is greatly appreciated!
I'm not sure about sound presentation in PTB (I've never done it), but you seem to be on the right track for the flicker frequency.
The way I do it is to determine the screen refresh rate, divide the total length of time you want the stimulus presented by this refresh rate (this will give you the number of frames that will be drawn during this time), and then have a frame counter that increases by 1 after every flip. You can then use this frame counter to switch commands on or off.
A minimal example (randomly changes the background colour at 4Hz for 5 seconds):
[w, wRect]=Screen('OpenWindow', 0);
MaxTime = 5; %Set maximum time for all stimuli to be presented in seconds
Hz = 4; %Set Hz for stimulus flicker
Screen('Flip',w);
Frametime=Screen('GetFlipInterval',w); %Find refresh rate in seconds
FramesPerFull = round(5/Frametime); % Number of frames for all stimuli
FramesPerStim = round((1/Hz)/Frametime); %Number of frames for each stimulus
StartT = GetSecs; %Measure start time of session
Framecounter = 0; %Frame counter begins at 0
while 1
if Framecounter==FramesPerFull
break; %End session
end
if ~mod(Framecounter,FramesPerStim)
randomcolour = rand(1, 3)*255; %Change background stimulus colour
end
Screen('FillRect', w, randomcolour, wRect);
Screen('Flip',w);
Framecounter = Framecounter + 1; %Increase frame counter
end
EndT = GetSecs; %Measure end time of session
Screen('CloseAll');
EndT - StartT %Shows full length of time all stimuli were presented
The timing precision will depend on your particular refresh rate.
Hope this helps!

Unable to acquire *precisely* timed videos in Matlab (using ImgAcq toolbox)

When I try to record using the code below, the resulting videos have the correct number of frames and file lengths, but the recorded time is always slightly longer (as measured by filming a digital clock), a 60min recordings capture ~61min, and 5min recordings have extra 3-5sec (so time is skipped during recordings). Occasionally, the camera clearly show time skipping when it records for some time, then pauses for up to hour or so, and then resumes.
I am using a Basler GigE acA1300-60gm (http://www.baslerweb.com/en/products/area-scan-cameras/ace/aca1300-60gm) camera set to continuous triggering for several hours, and I need the acquired videos to have millisecond resolution. I am not sure why the recording times are so varied, am I using a wrong script for the job or does it have something to do with the hardware settings?
(Matlab R2014a on Windows 7)
vid = videoinput(adapter, deviceIDVar{1,1}, formatVar);
vid.FramesPerTrigger = NoOfFramesPerFile;
src = getselectedsource(vid);
src.FrameStartTriggerMode = 'On';
src.FrameStartTriggerSource = 'Line1';
src.FrameStartTriggerActivation = 'RisingEdge';
src.FrameStartTriggerMode = 'Off';
src.PacketSize = 8000;
triggerconfig(vid, 'hardware', 'DeviceSpecific', 'DeviceSpecific');
vid.TriggerRepeat = 0;
vid.LoggingMode = 'disk';
for i=1:FileLimit
%file path and format settings
diskLogger = VideoWriter(filenameWithExt, 'MPEG-4');
diskLogger.FrameRate = 25;
vid.DiskLogger = diskLogger;
start(vid)
wait(vid, Inf);
end
stop(vid)
delete(vid)
clear
Are here any better ways to acquire precisely timed videos (at 25FPS)? Thanks in advance!