I am creating a project in nordic Micro that reads a value from an Analog input termninal an output it into UART. I am now trying to compress the data using GE Proficy Historian Compression so that only changed data are outputted in the UART. But my code is not working. Outputted data are sometime still redundant.
The idea of the program is to generate an interrupt every certain amout of time, read the adc value and if its different than previous value, output it to the UART port.
the algorithm is explained here
http://www.evsystems.net/files/GE_Historian_Compression_Overview.ppt
The main portion of the code that handles the interrupt is as shown below
void ADC_IRQHandler(void)
{
/* Clear dataready event */
NRF_ADC->EVENTS_END = 0;
// write ADC value to UART port
// Compression Algorithm should occur here
uint8_t current_Val= NRF_ADC->RESULT;
//simple_uart_put(current_evaluation);
// Construct error bands around the held point
float U_tolerance = current_Val + error_tolerance;
float L_tolerance = current_Val - error_tolerance; // integer type is selected since lower tolerance could be negative
if( first_Run == false)
{
float slope = ((current_Val - Archived_Val) / (held_Time - Archived_Time)) ;
if (slope > U_Slope || slope > L_Slope)
{
Archived_Val = current_Val; // update the archived value
held_Time = 1; // reset held time
simple_uart_put(current_Val);
first_Run = true;
Archived_Val = current_Val;
}
else
{
float Upper_Slope = (U_tolerance - Archived_Val) /( held_Time - Archived_Time);
float Lower_Slope = (L_tolerance - Archived_Val)/(held_Time- Archived_Time);
if(Upper_Slope < U_Slope) // lowest upper slope is always taken as a blanket boundry
{
U_Slope = Upper_Slope;
}
if(Lower_Slope < L_Slope)
{
L_Slope = Lower_Slope;
}
held_Time += time_increment;
}
}
if (first_Run == true) // first held point always outputted
{
// calculate the slopes of the two lines
float Upper_Slope = (U_tolerance - Start_Up_Val) /( held_Time - Archived_Time);
float Lower_Slope = (L_tolerance - Start_Up_Val)/(held_Time- Archived_Time);
// Update Max and Min slopes
if(Upper_Slope < U_Slope) // lowest upper slope is always taken as a blanket boundry
{
U_Slope = Upper_Slope;
}
if(Lower_Slope < L_Slope)
{
L_Slope = Lower_Slope;
}
held_Time += time_increment;
first_Run = false;
Archived_Val = current_Val;
}
}
The veriables are defined as follow
> uint32_t error_tolerance = 50; // error tolerance value for swining door algorithm
uint8_t Start_Up_Val = 100;
float held_Time = 1;
int Archived_Time = 0;
float U_Slope = 2500;
float L_Slope = 0;
//float slope;
uint8_t Archived_Val;
bool GE_Comp(uint8_t, uint8_t, uint8_t, int);
bool first_Run = true;
float time_increment = 0.1;
Thank you all for your contribution and mostly to #Weather Vane . It was exactly as you and others suggested, the interrupt handler was executing too much coding which prohibited it from proper functionality. I now fixed the problem by diverging the parts of the code to the main function as suggested.
Regareds
Related
I am trying to create a modulated waveform out of 2 sine waves.
To do this I need the modulo(fmodf) to know what amplitude a sine with a specific frequency(lo_frequency) has at that time(t). But I get a hardfault when the following line is executed:
j = fmodf(2 * PI * lo_frequency * t, 2 * PI);
Do you have an idea why this gives me a hardfault ?
Edit 1:
I exchanged fmodf with my_fmodf:
float my_fmodf(float x, float y){
if(y == 0){
return 0;
}
float n = x / y;
return x - n * y;
}
But still the hardfault occurs, and when I debug it it doesn't even jump into this function(my_fmodf).
Heres the whole function in which this error occurs:
int* create_wave(int* message){
/* Mixes the message signal at 10kHz and the carrier at 40kHz.
* When a bit of the message is 0 the amplitude is lowered to 10%.
* When a bit of the message is 1 the amplitude is 100%.
* The output of the STM32 can't be negative, thats why the wave swings between
* 0 and 256 (8bit precision for faster DAC)
*/
static int rf_frequency = 10000;
static int lo_frequency = 40000;
static int sample_rate = 100000;
int output[sample_rate];
int index, mix;
float j, t;
for(int i = 0; i <= sample_rate; i++){
t = i * 0.00000001f; // i * 10^-8
j = my_fmodf(2 * PI * lo_frequency * t, 2 * PI);
if (j < 0){
j += (float) 2 * PI;
}
index = floor((16.0f / (lo_frequency/rf_frequency * 0.0001f)) * t);
if (index < 16) {
if (!message[index]) {
mix = 115 + sin1(j) * 0.1f;
} else {
mix = sin1(j);
}
} else {
break;
}
output[i] = mix;
}
return output;
}
Edit 2:
I fixed the warning: function returns address of local variable [-Wreturn-local-addr] the way "chux - Reinstate Monica" suggested.
int* create_wave(int* message){
static uint16_t rf_frequency = 10000;
static uint32_t lo_frequency = 40000;
static uint32_t sample_rate = 100000;
int *output = malloc(sizeof *output * sample_rate);
uint8_t index, mix;
float j, n, t;
for(int i = 0; i < sample_rate; i++){
t = i * 0.00000001f; // i * 10^-8
j = fmodf(2 * PI * lo_frequency * t, 2 * PI);
if (j < 0){
j += 2 * PI;
}
index = floor((16.0f / (lo_frequency/rf_frequency * 0.0001f)) * t);
if (index < 16) {
if (!message[index]) {
mix = (uint8_t) floor(115 + sin1(j) * 0.1f);
} else {
mix = sin1(j);
}
} else {
break;
}
output[i] = mix;
}
return output;
}
But now I get the hardfault on this line:
output[i] = mix;
EDIT 3:
Because the previous code contained a very large buffer array that did not fit into the 16KB SRAM of the STM32F303K8 I needed to change it.
Now I use a "ping-pong" buffer where I use the callback of the DMA for "first-half-transmitted" and "completly-transmitted":
void HAL_DAC_ConvHalfCpltCallbackCh1(DAC_HandleTypeDef * hdac){
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_SET);
for(uint16_t i = 0; i < 128; i++){
new_value = sin_table[(i * 8) % 256];
if (message[message_index] == 0x0){
dac_buf[i] = new_value * 0.1f + 115;
} else {
dac_buf[i] = new_value;
}
}
}
void HAL_DAC_ConvCpltCallbackCh1 (DAC_HandleTypeDef * hdac){
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_RESET);
for(uint16_t i = 128; i < 256; i++){
new_value = sin_table[(i * 8) % 256];
if (message[message_index] == 0x0){
dac_buf[i] = new_value * 0.1f + 115;
} else {
dac_buf[i] = new_value;
}
}
message_index++;
if (message_index >= 16) {
message_index = 0;
// HAL_DAC_Stop_DMA (&hdac1, DAC_CHANNEL_1);
}
}
And it works the way I wanted:
But the frequency of the created sine is too low.
I cap at around 20kHz but I'd need 40kHz.
I allready increased the clock by a factor of 8 so that one is maxed out:
.
I can still decrease the counter period (it is 50 at the moment), but when I do so the interrupt callback seems to take longer than the period to the next one.
At least it seems so as the output becomes very distorted when I do that.
I also tried to decrease the precision by taking only every 8th sine value but
I cant do this any more because then the output does not look like a sine wave anymore.
Any ideas how I could optimize the callback so that it takes less time ?
Any other ideas ?
Does fmodf() cause a hardfault in stm32?
It is other code problems causing the hard fault here.
Failing to compile with ample warnings
Best code tip: enable all warnings. #KamilCuk
Faster feedback than Stackoverflow.
I'd expect something like below on a well enabled compiler.
return output;
warning: function returns address of local variable [-Wreturn-local-addr]
Returning a local Object
Cannot return a local array. Allocate instead.
// int output[sample_rate];
int *output = malloc(sizeof *output * sample_rate);
return output;
Calling code will need to free() the pointer.
Out of range array access
static int sample_rate = 100000;
int output[sample_rate];
// for(int i = 0; i <= sample_rate; i++){
for(int i = 0; i < sample_rate; i++){
...
output[i] = mix;
}
Stack overflow?
static int sample_rate = 100000; int output[sample_rate]; is a large local variable. Maybe allocate or try something smaller?
Advanced: loss of precision
A good fmodf() does not lose precision. For a more precise answer consider double math for the intermediate results. An even better approach is more involved.
float my_fmodf(float x, float y){
if(y == 0){
return 0;
}
double n = 1.0 * x / y;
return (float) (x - n * y);
}
Can I not use any function within another ?
Yes. Code has other issues.
1 value every 10uS makes only 100kSPS whis is not too much for this macro. In my designs I generate > 5MSPS signals without any problems. Usually I have one buffer and DMA in circular mode. First I fill the buffer and start generation. When the half transmition DMA interrupt is trigerred I fill the first half of the buffer with fresh data. The the transmition complete interrupt is trigerred I fill the second half and this process repeats all over again.
based on some tutorial code I've found I coded a little synthesizer with three oscilators and four different waveform. It works well and I want to add an LFO to module the sounds. Since I didn't coded everything on my own I'm a bit confused of how I could fit the LFO formula on my code. This is more or less what I tried in order to implement the LFO formula on a sinewave.(This formula is something like this: sinewaveFormula + 0.5 * Sinefreq * sin(2pi*1) * time)
double normalize(double phase)
{
double cycles = phase/(2.0*pi);
phase -= trunc(cycles) * 2.0 * pi;
if (phase < 0) phase += 2.0*pi;
return phase;
}
double sine(double phase)
{ phase = normalize(phase); return (sin(phase));}
static void build_sine_table(int16_t *data, int wave_length) {
double phase_increment = (2.0f * pi) / (double)wave_length;
double current_phase = 0;
for(int i = 0; i < wave_length; i++) {
int sample = synthOsc(current_phase, oscNum, selectedWave, selectedWave2, selectedWave3, intensity, intensity2, intensity3) + 0.5 * ((current_phase* wave_length) / (2*pi)) * sin(2*pi*(1.0)) * wave_length;
data[i] = (int16_t)sample;
current_phase += phase_increment;
}
}
static void write_samples(int16_t *s_byteStream, long begin, long end, long length) {
if(note > 0) {
double d_sample_rate = sample_rate;
double d_table_length = table_length;
double d_note = note;
// get correct phase increment for note depending on sample rate and table length.
double phase_increment = (get_pitch(d_note) / d_sample_rate) * d_table_length;
// loop through the buffer and write samples.
for (int i = 0; i < length; i+=2) {
phase_double += phase_increment;
phase_int = (int)phase_double;
if(phase_double >= table_length) {
double diff = phase_double - table_length;
phase_double = diff;
phase_int = (int)diff;
}
if(phase_int < table_length && phase_int > -1) {
if(s_byteStream != NULL) {
int16_t sample = sine_waveform_wave[phase_int];
target_amp = update_envelope();
if(smoothing_enabled) {
// move current amp towards target amp for a smoother transition.
if(current_amp < target_amp) {
current_amp += smoothing_amp_speed;
if(current_amp > target_amp) {
current_amp = target_amp;
}
} else if(current_amp > target_amp) {
current_amp -= smoothing_amp_speed;
if(current_amp < target_amp) {
current_amp = target_amp;
}
}
} else {
current_amp = target_amp;
}
sample *= current_amp; // scale volume.
s_byteStream[i+begin] = sample; // left channel
s_byteStream[i+begin+1] = sample; // right channel
}
}
}
}
}
The code compile but there's no LFO on the sine. I don't understand how I could make this formula work with this code.
It may help to get a basic understanding of how a LFO actually works. It is not that difficult - as an LFO is just another oscillator that is mixed to the waveform you want to modulate.
I would suggest to remove your LFO formular from your call of synthOsc(), then you get a clean oscillator signal again. As a next step, create another oscillator signal for which you can use a very low frequency. Mix both signals together and you are done.
Expresssed in simple math, it is like this:
int the_sample_you_want_to_modulate = synthOsc1(...);
int a_sample_with_very_low_frequency = synthOsc2(...);
Mixing two waveforms is done through addition:
int mixed_sample = the_sample_you_want_to_modulate + a_sample_with_very_low_frequency;
The resulting sample will sweep now based on the frequency you have used for synthOsc2().
As you can see, to implement an LFO you actually do not need a separate formular. You already have the formular when you know how to create an oscillator.
Note that if you add two sine oscillators that have the exact same frequency, the resulting signal will just get louder. But when each has a different frequency, you will get a new waveform. For LFOs (which are in fact just ordinary oscillators - like in your build_sine_table() function) you typically set a very low frequency: 1 - 10 Hz is low enough to get an audible sweep. For higher frequencies you get chords as a result.
In short, I am reading a .wav file in MATLAB for the purposes of sending it to the ESP32 for an FFT analysis. The .wav file in question contains a recording of a Corona effect. My file has 96223 samples when inputted into MATLAB.
For now, I am trying to just get back a checksum so I can know that the data is sent correctly.
I have already tried using the code I've written for smaller sample sizes. For example, I get back the correct checksum when I send 200 samples although the code takes longer than I want it to take which is not good. More than that though, and I never get anything back because of timeouts.
This is my MATLAB code:
esp = serial('COM3');
set(esp, 'DataBits' , 8);
set(esp, 'StopBits', 1);
set(esp, 'BaudRate', 9600);
set(esp, 'Parity', 'none');
set(esp, 'terminator', 'LF');
%filename = 'test100.wav';
%corona = audioread(filename);
load('corona')
fopen(esp);
pause(0.1)
for i = 1:200
fprintf(esp, '%5.9f\n', corona(i,1));
pause(0.1);
end
output = fscanf(esp, '%f\n') %read the checksum
fclose(instrfind);
And this is my Arduino code:
#include <Arduino.h>
float sentData[200]; //initialize data array
int i = 0;
const int ledPin = 26;
float checksum = 0;
int CNT = 0;
void printFloat(float value, int places);
void setup()
{
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
while (Serial.available() < 200)
{
digitalWrite(ledPin, HIGH); //keep the LED on while the data is being sent
}
while (Serial.available() != 0)
{
sentData[i] = Serial.parseFloat(); //parse the data to the array
i++;
}
Serial.flush();
delay(500);
digitalWrite(ledPin, LOW); //turn off the LED when data is fully parsed
for (size_t x = 0; x < 200; ++x)
{
checksum += sentData[x]; //calculate the sum of all elements in the sentData array
}
printFloat(checksum, 10); //send the checksum to the serial port for reading
}
void loop()
{
}
void printFloat(float value, int places)
{
// this is used to cast digits
int digit;
float tens = 0.1;
int tenscount = 0;
int i;
float tempfloat = value;
// if this rounding step isn't here, the value 54.321 prints as 54.3209
// calculate rounding term d: 0.5/pow(10,places)
float d = 0.5;
if (value < 0)
d *= -1.0;
// divide by ten for each decimal place
for (i = 0; i < places; i++)
d /= 10.0;
tempfloat += d;
// first get value tens to be the large power of ten less than value
if (value < 0)
tempfloat *= -1.0;
while ((tens * 10.0) <= tempfloat)
{
tens *= 10.0;
tenscount += 1;
}
// write out the negative if needed
if (value < 0)
Serial.print('-');
if (tenscount == 0)
Serial.print(0, DEC);
for (i = 0; i < tenscount; i++)
{
digit = (int)(tempfloat / tens);
Serial.print(digit, DEC);
tempfloat = tempfloat - ((float)digit * tens);
tens /= 10.0;
}
// if no places after decimal, stop now and return
if (places <= 0)
return;
// otherwise, write the point and continue on
Serial.print('.');
// now write out each decimal place by shifting digits one by one into the ones place and writing the truncated value
for (i = 0; i < places; i++)
{
tempfloat *= 10.0;
digit = (int)tempfloat;
Serial.print(digit, DEC);
// once written, subtract off that digit
tempfloat = tempfloat - (float)digit;
}
}
I expected to get back a checksum but I get a timeout when using very large sample sizes. I should also add that even though the ESP32 should be able to handle my file, I can't just push the whole file into the serial port because I get a buffer overflow error. Is there a solution to this?
First %5.9f doesn't make sense to me.
Thats minimum 5 characters with 9 digit precision. That 5 doesn't make sense as you'll always have at least 11 characters with 9 digit precision
Then let me do some maths for you:
96000 samples, 12 characters each (including \n) is a total of 10368000 bits.
At 9600 baud that's 1080 seconds of transfer time. -> 18 minutes.
As you add 0.1s pause after each sample you add another 9600 seconds to that.
Which leaves you with a total of 178 minutes (3 hours) of transfer time.
What do you expect?
For 200 samples its still 22,25s.
I'm trying to use the Sine Table lookup method to find the tone frequency at different step size, but when I'm converting the floating point to integer and use the oscicopte to view the frequncy, it can't display any things on screen.
Does anyone know what's the solution for this issues. Any help is apperaite.
Below is the code:
// use the formula: StepSize = 360/(Fs/f) Where Fs is the Sample frequency 44.1 kHz and f is the tone frequency.
// example: StepSize = 360/(44100/440) = 3.576, since the STM32 doesn't support the floating point, therefore, we have to use the fixed-point format which multiply it by 1000 to be 3575
int StepSize = 3575;
unsigned int v=0;
signed int sine_table[91] = {
0x800,0x823,0x847,0x86b,
0x88e,0x8b2,0x8d6,0x8f9,
0x91d,0x940,0x963,0x986,
0x9a9,0x9cc,0x9ef,0xa12,
0xa34,0xa56,0xa78,0xa9a,
0xabc,0xadd,0xaff,0xb20,
0xb40,0xb61,0xb81,0xba1,
0xbc1,0xbe0,0xc00,0xc1e,
0xc3d,0xc5b,0xc79,0xc96,
0xcb3,0xcd0,0xcec,0xd08,
0xd24,0xd3f,0xd5a,0xd74,
0xd8e,0xda8,0xdc1,0xdd9,
0xdf1,0xe09,0xe20,0xe37,
0xe4d,0xe63,0xe78,0xe8d,
0xea1,0xeb5,0xec8,0xedb,
0xeed,0xeff,0xf10,0xf20,
0xf30,0xf40,0xf4e,0xf5d,
0xf6a,0xf77,0xf84,0xf90,
0xf9b,0xfa6,0xfb0,0xfba,
0xfc3,0xfcb,0xfd3,0xfda,
0xfe0,0xfe6,0xfec,0xff0,
0xff4,0xff8,0xffb,0xffd,
0xffe,0xfff,0xfff};
unsigned int sin(int x){
x = x % 360;
if(x <= 90)
return sine_table[x];
else if ( x <= 180){
return sine_table[180 - x];
}else if ( x <= 270){
return 4096 - sine_table[x - 180];
}else{
return 4096 - sine_table[360 - x];
}
}
void main(void)
{
while(1){
v+=StepSize; // Don't know why it doesn't work in this way. not display anything on screen.
DAC->DHR12R2 = sin(v/1000); // DAC channel-2 12-bit Right aligned data
if (v >= 360) v = 0;
}
}
But, if I change the StepSize = 3; it shows the frequency:
There are a few issues with your code. But I will start with the one that you asked about.
int StepSize = 3575;
unsigned int v=0;
while(1){
v+=StepSize;
DAC->DHR12R2 = sin(v/1000);
if (v >= 360) v = 0;
}
The reason why this code doesn't work is that v is always set to 0 at the end of the loop because 3575 is greater than 360. So then you always call sin(3) because 3575/1000 is 3 in integer division.
Perhaps, you should rewrite your last line as if ((v/1000) >= 360) v = 0;. Otherwise, I would rewrite your loop like this
while(1){
v+=StepSize;
v/=1000;
DAC->DHR12R2 = sin(v);
if (v >= 360) v = 0;
}
I would also recommend that you declare your lookup table a const. So it would look like
const signed int sine_table[91] = {
Last recommendation is to choose another name for your sin function so as not to confuse with the sin library function. Even though in this case there shouldn't be a problem.
I have an Arduino Due that I'm doing a lot of really neat stuff with in one section of code I'm handeling a flowsensor and getting readings from it. the flowsensor is hooked up to an interrupt as it is a hallsensor device. The problem I'm having is that my math is based off of a single seconds worth of collection data, when I turn on the interrupt and sleep for a second do the calculations for flowrate in gp/m it works great. For My purposes however I can't afford to pause and collect data, so instead I calculate a change in time of greater than a thousand milliseconds. then I take the amount of time passed since the last loop, devide it by 1000 milliseconds or 1 second then I have the percentage of 1 second stored in a variable, I multiply the count by the percentage and should arrive at the number of counts per second. This however results in an ever growing number regardless of the fact that I zero the numbers prior to the next loop. Below I've included the way I formatted the expressions and the loop code for the flowsensor, I'd post the whole code but I don't want to waste space on stackoverflow and I know that portion of code already works with the sleep method.
long currentMillis = 0;
long lastMillis = 0;
int checkMillis = 0;
volatile int NbTopsFan; //measuring the rising edges of the signal
int newNbTopsFan;
float realNbTopsFan;
float realMillis;
float Calc;
float Calcd;
int Calcf;
int hallsensor = 7;
void loop()
{
if (lastMillis = 0) {
lastMillis = millis();
}
currentMillis = millis();
checkMillis = (currentMillis - lastMillis);
if (checkMillis >= 1000) {
realMillis = (checkMillis / 1000);
realNbTopsFan = (NbTopsFan * realMillis);
newNbTopsFan = realNbTopsFan;
Calc = (newNbTopsFan * 60 / 7.5); //(Pulse frequency x 60) / 7.5Q, = flow rate
Calc = (Calc / 60);
Calc = (Calc / 3.78541);
Calc = (Calc * 1.47);
Calcd = (Calc * 100);
Calcf = Calcd;
NbTopsFan = 0;
lastMillis = 0;
checkMillis = 0;
realMillis = 0;
realNbTopsFan = 0;
newNbTopsFan = 0;
}
}
Your first line should read:
if ( lastMillis == 0 ) {
lastMillis = millis();
}
You need the double = to evaluate the variable