This thread has been locked.
If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.
Hello,
I am currently working on a project with TM4C129EXL. I need to use a specifc ADC sequence (See the code below). I wanted to change the timer frequency (400000 Hz) , but it seems to run up to about 100000Hz (compared a counter in the timer with a stopwatch).
When I add some code, it runs up to about 15000Hz.
The datasheet gives the maximum number of sample, wich is 2MSPS.
I suppose there is some kind of limitations here. Can you explain? Maybe my code is wrong?
Thank you.
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <string.h> #include <math.h> #include "inc/hw_ints.h" #include "inc/hw_memmap.h" #include "inc/hw_types.h" #include "inc/hw_gpio.h" #include "driverlib/debug.h" #include "driverlib/fpu.h" #include "driverlib/gpio.h" #include "driverlib/interrupt.h" #include "driverlib/pin_map.h" #include "driverlib/rom.h" #include "driverlib/rom_map.h" #include "driverlib/sysctl.h" #include "driverlib/timer.h" #include "driverlib/uart.h" #include "utils/uartstdio.h" #include "driverlib/adc.h" #include "drivers/pinout.h" #include "drivers/buttons.h" #include "driverlib/fpu.h" #include "driverlib/pwm.h" #include "Custom files/constantes.h" #include "Custom files/maths.h" #include "Custom files/correcteurs.h" // // The error routine that is called if the driver library encounters an error. // #ifdef DEBUG void __error__(char *pcFilename, uint32_t ui32Line) { } #endif // Réglages des paramètres du programme // fréquence d'échantillonnage #define TMR0_FREQ 100000 //Hz // Horloge uint32_t g_ui32SysClock; // Etat de la diode uint8_t LED = 0; // variables analogiques float tension; float temp; float pot; float sinus; float cosinus; float courant[NBECH_COURANT]; float v[NBECH_V]; //vitesse float x[NBECH_X]; //position float *adrTest; float test; int *adrCompteurTemps; uint32_t ADCbuffer[4], thetaSinBuffer[1], thetaCosBuffer[1]; // Initialise le timer 0 void ConfigureTIMER0(void) { // Active le périphérique Timer 0 SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0); // Configure the 32-bit periodic timers. TimerConfigure(TIMER0_BASE, TIMER_CFG_PERIODIC); TimerLoadSet(TIMER0_BASE, TIMER_A, g_ui32SysClock/TMR0_FREQ); // Setup the interrupts for the timer timeouts. IntEnable(INT_TIMER0A); TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT); } // Initialise les ADC et les GPIO associées void ConfigureADC(void) { SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0); SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC1); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE); ADCReferenceSet(ADC0_BASE, ADC_REF_INT); ADCReferenceSet(ADC1_BASE, ADC_REF_INT); GPIOPinTypeADC(GPIO_PORTE_BASE,GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5); while(!SysCtlPeripheralReady(SYSCTL_PERIPH_ADC0) && !SysCtlPeripheralReady(SYSCTL_PERIPH_ADC1) && !SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOE)) {} ////// Désactivation des ADC (sécurité) ADCSequenceDisable(ADC0_BASE,2); ADCSequenceDisable(ADC0_BASE,0); ADCSequenceDisable(ADC1_BASE,0); ////// Configuration des ADC // Séquence courant -> température -> potentiomètre ADCSequenceConfigure(ADC0_BASE,2,ADC_TRIGGER_PROCESSOR,0); //ADC0 Sequence 2 déclenchement processeur priorité 0 (Haute) //positionSin et positionCos sync ADCSequenceConfigure(ADC0_BASE,0,ADC_TRIGGER_PROCESSOR,0); ADCSequenceConfigure(ADC1_BASE,0,ADC_TRIGGER_PROCESSOR,0); ////// Configuration des séquences // Séquence courant -> température -> potentiomètre ADCSequenceStepConfigure(ADC0_BASE,2,0,ADC_CTL_CH1); //Courant ADCSequenceStepConfigure(ADC0_BASE,2,1,ADC_CTL_CH9); //vitesse ADCSequenceStepConfigure(ADC0_BASE,2,2,ADC_CTL_CH2); //Température ADCSequenceStepConfigure(ADC0_BASE,2,3,ADC_CTL_CH3|ADC_CTL_IE|ADC_CTL_END); //Potentiomètre // positionSin et positionCos sync ADCSequenceStepConfigure(ADC0_BASE,0,0,ADC_CTL_CH0|ADC_CTL_IE|ADC_CTL_END); ADCSequenceStepConfigure(ADC1_BASE,0,0,ADC_CTL_CH8|ADC_CTL_IE|ADC_CTL_END); ////// Activations des ADC ADCSequenceEnable(ADC0_BASE,2); ADCSequenceEnable(ADC0_BASE,0); ADCSequenceEnable(ADC1_BASE,0); // Reset des flags d'interuption des ADC ADCIntClear(ADC0_BASE,2); ADCIntClear(ADC0_BASE,0); ADCIntClear(ADC1_BASE,0); } // Fonction d'interuption du timer 0 void Timer0IntHandler(void) { // Reset le flag de l'interuption du timer TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT); static int compteurTemps = 0; adrCompteurTemps = &compteurTemps; ////////////////////////////////////////////////////////////////////////// ////// ADC // initialise le flag de l'interuption de l'ADC ADCIntClear(ADC0_BASE,2); // Déclenche la conversion ADCProcessorTrigger(ADC0_BASE,2); while(!ADCIntStatus(ADC0_BASE,2,false)){} // attend la fin de la conversion // Reset le flag de l'interuption de l'ADC ADCIntClear(ADC0_BASE,2); // Lecture des données ADCSequenceDataGet(ADC0_BASE,2,ADCbuffer); // Calculs et enregistrement actualiseEchantillons(courant,NBECH_COURANT); courant[0] = ADCbuffer[0]*GAIN_COURANT-OFFSET_COURANT; actualiseEchantillons(v,NBECH_V); //v[0] = ADCbuffer[1]*GAIN_VITESSE-OFFSET_VITESSE; v[0] = -1.825101272*((ADCbuffer[1]-1848.246159)*1.510773788/1848.246159); temp = ADCbuffer[2]*GAIN_TEMP+OFFSET_TEMP; pot = ADCbuffer[3]*GAIN_POT+OFFSET_POT; // position, sync ADCIntClear(ADC0_BASE,0); ADCIntClear(ADC1_BASE,0); // déclenche les conversions synchronisées ADCProcessorTrigger(ADC1_BASE,(0|ADC_TRIGGER_WAIT)); // ADC-1 en attente de déclenchement ADCProcessorTrigger(ADC0_BASE,(0|ADC_TRIGGER_SIGNAL)); // ADC-0 en déclenchement global while(!ADCIntStatus(ADC0_BASE,0,false)){} // attend la fin de la conversion // Reset le flag de l'interuption de l'ADC ADCIntClear(ADC0_BASE,0); ADCIntClear(ADC1_BASE,0); // Lecture des données ADCSequenceDataGet(ADC0_BASE,0,thetaSinBuffer); ADCSequenceDataGet(ADC1_BASE,0,thetaCosBuffer); // Calculs et enregitrement //max (erreur capteur + erreur monAtan) = 0.152 mm sinus = (thetaSinBuffer[0]*GAIN_SINCOS-OFFSET_SIN)*GAIN_NL_SIN; cosinus = (thetaCosBuffer[0]*GAIN_SINCOS-OFFSET_COS)*GAIN_NL_COS; actualiseEchantillons(x,NBECH_X); x[0] = GAIN_X*monAtan2(cosinus,sinus,3)+OFFSET_X; compteurTemps++; test = (float)compteurTemps/TMR0_FREQ; adrTest = &test; ////////////////////////////////////////////////////////////////////////// ////// Test de fin de timer HWREGBITW(&LED, 0) ^= 1; // (bit 0 du registre de LED) XOR 1 GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_0, LED); } // Main: Initialisation et boucle infinie int main(void) { ////// Set up // Règle l'horloge sur le cristal à 120MHz g_ui32SysClock = MAP_SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN | SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480), CLK_FREQ); // Permet d'utiliser les virgules flottantes (nombres réels) FPULazyStackingEnable(); FPUEnable(); // Active le port des diodes PinoutSet(false, false); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPION); while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPION)){} GPIOPinTypeGPIOOutput(GPIO_PORTN_BASE, GPIO_PIN_0 | GPIO_PIN_1); GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_0 | GPIO_PIN_1, 0); // Autorise les interuptions IntMasterEnable(); // Configure le timer pour l'échantillonnage ConfigureTIMER0(); // Configure les ADC ConfigureADC(); // initialisation des tableaux de stockage des données initialiseTableau(courant, NBECH_COURANT); initialiseTableau(v, NBECH_V); initialiseTableau(x, NBECH_X); // Lance le timer TimerEnable(TIMER0_BASE, TIMER_A); // Boucle infinie en parallèle du timer while(1) { } } /********************************************* * These functions are defined in another file *********************************************/ void actualiseEchantillons(float tab[], int dim){ float temp; int i; for(i = dim-1; i > 0; i = i--) { temp = tab[i]; tab[i] = tab[i-1]; tab[i-1] = temp; } } void initialiseTableau (float tableau[], int dim){ int i; for(i = 0; i<dim; i++) { tableau[i] = 0.0; } }
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <string.h> #include <math.h> #include "inc/hw_ints.h" #include "inc/hw_memmap.h" #include "inc/hw_types.h" #include "inc/hw_gpio.h" #include "driverlib/debug.h" #include "driverlib/fpu.h" #include "driverlib/gpio.h" #include "driverlib/interrupt.h" #include "driverlib/pin_map.h" #include "driverlib/rom.h" #include "driverlib/rom_map.h" #include "driverlib/sysctl.h" #include "driverlib/timer.h" #include "driverlib/uart.h" #include "utils/uartstdio.h" #include "driverlib/adc.h" #include "drivers/pinout.h" #include "drivers/buttons.h" #include "driverlib/fpu.h" #include "driverlib/pwm.h" #include "Custom files/constantes.h" #include "Custom files/maths.h" #include "Custom files/correcteurs.h" // // The error routine that is called if the driver library encounters an error. // #ifdef DEBUG void __error__(char *pcFilename, uint32_t ui32Line) { } #endif // Réglages des paramètres du programme // fréquence d'échantillonnage #define TMR0_FREQ 100000 //Hz // Horloge uint32_t g_ui32SysClock; // Etat de la diode uint8_t LED = 0; // variables analogiques float tension; float temp; float pot; float sinus; float cosinus; float courant[NBECH_COURANT]; float v[NBECH_V]; //vitesse float x[NBECH_X]; //position float *adrTest; float test; int *adrCompteurTemps; uint32_t ADCbuffer[4], thetaSinBuffer[1], thetaCosBuffer[1]; // Initialise le timer 0 void ConfigureTIMER0(void) { // Active le périphérique Timer 0 SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0); // Configure the 32-bit periodic timers. TimerConfigure(TIMER0_BASE, TIMER_CFG_PERIODIC); TimerLoadSet(TIMER0_BASE, TIMER_A, g_ui32SysClock/TMR0_FREQ); // Setup the interrupts for the timer timeouts. IntEnable(INT_TIMER0A); TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT); } // Initialise les ADC et les GPIO associées void ConfigureADC(void) { SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0); SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC1); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE); ADCReferenceSet(ADC0_BASE, ADC_REF_INT); ADCReferenceSet(ADC1_BASE, ADC_REF_INT); GPIOPinTypeADC(GPIO_PORTE_BASE,GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5); while(!SysCtlPeripheralReady(SYSCTL_PERIPH_ADC0) && !SysCtlPeripheralReady(SYSCTL_PERIPH_ADC1) && !SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOE)) {} ////// Désactivation des ADC (sécurité) ADCSequenceDisable(ADC0_BASE,2); ADCSequenceDisable(ADC0_BASE,0); ADCSequenceDisable(ADC1_BASE,0); ////// Configuration des ADC // Séquence courant -> température -> potentiomètre ADCSequenceConfigure(ADC0_BASE,2,ADC_TRIGGER_PROCESSOR,0); //ADC0 Sequence 2 déclenchement processeur priorité 0 (Haute) //positionSin et positionCos sync ADCSequenceConfigure(ADC0_BASE,0,ADC_TRIGGER_PROCESSOR,0); ADCSequenceConfigure(ADC1_BASE,0,ADC_TRIGGER_PROCESSOR,0); ////// Configuration des séquences // Séquence courant -> température -> potentiomètre ADCSequenceStepConfigure(ADC0_BASE,2,0,ADC_CTL_CH1); //Courant ADCSequenceStepConfigure(ADC0_BASE,2,1,ADC_CTL_CH9); //vitesse ADCSequenceStepConfigure(ADC0_BASE,2,2,ADC_CTL_CH2); //Température ADCSequenceStepConfigure(ADC0_BASE,2,3,ADC_CTL_CH3|ADC_CTL_IE|ADC_CTL_END); //Potentiomètre // positionSin et positionCos sync ADCSequenceStepConfigure(ADC0_BASE,0,0,ADC_CTL_CH0|ADC_CTL_IE|ADC_CTL_END); ADCSequenceStepConfigure(ADC1_BASE,0,0,ADC_CTL_CH8|ADC_CTL_IE|ADC_CTL_END); ////// Activations des ADC ADCSequenceEnable(ADC0_BASE,2); ADCSequenceEnable(ADC0_BASE,0); ADCSequenceEnable(ADC1_BASE,0); // Reset des flags d'interuption des ADC ADCIntClear(ADC0_BASE,2); ADCIntClear(ADC0_BASE,0); ADCIntClear(ADC1_BASE,0); } // Fonction d'interuption du timer 0 void Timer0IntHandler(void) { // Reset le flag de l'interuption du timer TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT); static int compteurTemps = 0; adrCompteurTemps = &compteurTemps; ////////////////////////////////////////////////////////////////////////// ////// ADC // initialise le flag de l'interuption de l'ADC ADCIntClear(ADC0_BASE,2); // Déclenche la conversion ADCProcessorTrigger(ADC0_BASE,2); while(!ADCIntStatus(ADC0_BASE,2,false)){} // attend la fin de la conversion // Reset le flag de l'interuption de l'ADC ADCIntClear(ADC0_BASE,2); // Lecture des données ADCSequenceDataGet(ADC0_BASE,2,ADCbuffer); // Calculs et enregistrement actualiseEchantillons(courant,NBECH_COURANT); courant[0] = ADCbuffer[0]*GAIN_COURANT-OFFSET_COURANT; actualiseEchantillons(v,NBECH_V); //v[0] = ADCbuffer[1]*GAIN_VITESSE-OFFSET_VITESSE; v[0] = -1.825101272*((ADCbuffer[1]-1848.246159)*1.510773788/1848.246159); temp = ADCbuffer[2]*GAIN_TEMP+OFFSET_TEMP; pot = ADCbuffer[3]*GAIN_POT+OFFSET_POT; // position, sync ADCIntClear(ADC0_BASE,0); ADCIntClear(ADC1_BASE,0); // déclenche les conversions synchronisées ADCProcessorTrigger(ADC1_BASE,(0|ADC_TRIGGER_WAIT)); // ADC-1 en attente de déclenchement ADCProcessorTrigger(ADC0_BASE,(0|ADC_TRIGGER_SIGNAL)); // ADC-0 en déclenchement global while(!ADCIntStatus(ADC0_BASE,0,false)){} // attend la fin de la conversion // Reset le flag de l'interuption de l'ADC ADCIntClear(ADC0_BASE,0); ADCIntClear(ADC1_BASE,0); // Lecture des données ADCSequenceDataGet(ADC0_BASE,0,thetaSinBuffer); ADCSequenceDataGet(ADC1_BASE,0,thetaCosBuffer); // Calculs et enregitrement //max (erreur capteur + erreur monAtan) = 0.152 mm sinus = (thetaSinBuffer[0]*GAIN_SINCOS-OFFSET_SIN)*GAIN_NL_SIN; cosinus = (thetaCosBuffer[0]*GAIN_SINCOS-OFFSET_COS)*GAIN_NL_COS; actualiseEchantillons(x,NBECH_X); x[0] = GAIN_X*monAtan2(cosinus,sinus,3)+OFFSET_X; compteurTemps++; test = (float)compteurTemps/TMR0_FREQ; adrTest = &test; ////////////////////////////////////////////////////////////////////////// ////// Test de fin de timer HWREGBITW(&LED, 0) ^= 1; // (bit 0 du registre de LED) XOR 1 GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_0, LED); } // Main: Initialisation et boucle infinie int main(void) { ////// Set up // Règle l'horloge sur le cristal à 120MHz g_ui32SysClock = MAP_SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN | SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480), CLK_FREQ); // Permet d'utiliser les virgules flottantes (nombres réels) FPULazyStackingEnable(); FPUEnable(); // Active le port des diodes PinoutSet(false, false); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPION); while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPION)){} GPIOPinTypeGPIOOutput(GPIO_PORTN_BASE, GPIO_PIN_0 | GPIO_PIN_1); GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_0 | GPIO_PIN_1, 0); // Autorise les interuptions IntMasterEnable(); // Configure le timer pour l'échantillonnage ConfigureTIMER0(); // Configure les ADC ConfigureADC(); // initialisation des tableaux de stockage des données initialiseTableau(courant, NBECH_COURANT); initialiseTableau(v, NBECH_V); initialiseTableau(x, NBECH_X); // Lance le timer TimerEnable(TIMER0_BASE, TIMER_A); // Boucle infinie en parallèle du timer while(1) { } } /********************************************* * These functions are defined in another file *********************************************/ void actualiseEchantillons(float tab[], int dim){ float temp; int i; for(i = dim-1; i > 0; i = i--) { temp = tab[i]; tab[i] = tab[i-1]; tab[i-1] = temp; } } void initialiseTableau (float tableau[], int dim){ int i; for(i = 0; i<dim; i++) { tableau[i] = 0.0; } }
Hello amit, thank you for your reply.
My problem is that I need to use these data to calculate a duty cycle and update the PWM duty cycle. I do all of it in the timer interrupt handler, but do you think I should trigger the ADC in the timer interrupt handler and then in the ADC interupt handler calculate the duty and update the PWM duty cycle? What will be the frequency of this calculus? the duty needs to be updated before the next ADC triggering.
A couple of quick comments
Your structure appears to be
As Amit noted it's probably more productive to trigger that whole conversion sequence in H/W and simply process the result.
This:
Fabien GRZESKOWIAK said:v[0] = -1.825101272*((ADCbuffer[1]-1848.246159)*1.510773788/1848.246159);
is done in double precision. Although there is HW floating point support on the micro (if you have turned it on and enabled the compiler support) this will invoke a S/W library of some sort to provide the double precision support. There's no indication that you need dynamic range since neither you your inputs, outputs have significant dynamic range and nor does it appear that your intermediates need it. That being the case I'd switch to integers and avoid the risk of bringing in double precision library support.
Depending on the compiler and the version of the standard it is supporting it is possible that all floating point operations are done in double precision.
Fabien GRZESKOWIAK said:x[0] = GAIN_X*monAtan2(cosinus,sinus,3)+OFFSET_X;
This could be a real time waster depending on how you implemented it.
Fabien GRZESKOWIAK said:////// Configuration des séquences // Séquence courant -> température -> potentiomètre ADCSequenceStepConfigure(ADC0_BASE,2,0,ADC_CTL_CH1); //Courant ADCSequenceStepConfigure(ADC0_BASE,2,1,ADC_CTL_CH9); //vitesse
ADCSequenceStepConfigure(ADC0_BASE,2,2,ADC_CTL_CH2); //Température ADCSequenceStepConfigure(ADC0_BASE,2,3,ADC_CTL_CH3|ADC_CTL_IE|ADC_CTL_END); //Potentiomètre
Provided my very rusty language skills are working this is speed, current, temperature and potentiometer. While you might need speed and current measured at this frequency, the temperature and pot cannot change that quickly, you can stagger the conversions for those.
I'd very strongly suggest hooking up an oscilloscope or logic analyzer to a spare I/O pin, and toggle it at different places in the code to profile the execution. Measure, measure, measure. You need to know which parts of your code are costing you time.
Robert
Let me give more details about my project.
I need to control a DC actuator that is really fast and have some non-linearity (pattented structure from my lab). It goes from 0mm to 3mm(max position) with a time of 3ms. I have to control it in order to have a "soft landing" profile (without control, the landing speed is about 1m.s-1 wich is dangerous).
Thus, I need a fast control loop (beacause of the non-linearity, I can't have a good mathematical representation, so I have to do an oversampling) with a precise frequency:
Collect datas: Current, speed and position. As Robert said, the potentiometer and the temperature don't need to be mesured at this frequency.
Convert data: the datas need to be converted in order to be used in the loop.
Generate the command signal: It is a simple square signal. It depends on a frequency and a maximal command (min = -max). It is updated in the timer using a counter.
Calculate the duty: knowing the datas, the command, the maximal voltage, I can deduce the duty cycle for my PWM
Update the PWM: I update the 100kHz PWM to control the actuator.
Ready for an other loop.
So if need to update the PWM duty cycle, is it still a good idea to use the ADC interrupt handler?
For the moment, it seems to be "working" with a 10kHz frequency (the frequency is correct, not the full control loop, but that not my problem right now) but I'm still convinced that my way is not the good way, so I would like to know what do you think.
Hello robert, thank you for your reply.
Robert Adsett said:That being the case I'd switch to integers and avoid the risk of bringing in double precision library support.
Robert Adsett said:x[0] = GAIN_X*monAtan2(cosinus,sinus,3)+OFFSET_X;This could be a real time waster depending on how you implemented it.
monAtan2(cos,sin,precision) is a custom Atan2() based on "Taylor's series" (I don't know if it is the same name in english) so I have an Atan2() wich is faster but with less precision. I know it is a time waster but I don't have the choice here to do something else.
Robert Adsett said:Provided my very rusty language skills are working this is speed, current, temperature and potentiometer. While you might need speed and current measured at this frequency, the temperature and pot cannot change that quickly, you can stagger the conversions for those.
I agree. I did this because I thought the micro could handle all of it with 100kHz frequency. For the moment I have removed those ADC.
I really need floating point, but the double precision is maybe too much. There is no way to use floating point operations without the double precision?
Not necessarily. Using integer and scaled math, you can tailor your algorithms to the precision you need.
Or you can restrict yourself to use floating point (single precision, "float") variables and routines only, including all constants (!).
monAtan2(cos,sin,precision) is a custom Atan2() based on "Taylor's series" (I don't know if it is the same name in english) so I have an Atan2() wich is faster but with less precision. I know it is a time waster but I don't have the choice here to do something else.
A table-based approach is the alternative. That comes naturally to older folks that grew up in an age before pocket calculators - one had to use value tables for trigonometric functions (sin, cos, tan, etc.). There is always a tradeoff between code size ("taylor algorithm", clib functions sin(), cos(), atan(), ...) and speed (precalculated tables, perhaps with linear interpolation). You definitely want to favour speed. For an example, just search for integer-based FFT implementations, they use to come with sine and cosine tables.
Even if you go for single precision ("float"), those table will most probably pay off.
Hello f. m.
f. m. said:Not necessarily. Using integer and scaled math, you can tailor your algorithms to the precision you need.
Do you mean for example that if I need a precision of 3 digits, I should multiply everything by 1000 in my algorithms? 12.345 -> 12345
f. m. said:A table-based approach is the alternative.
Of course, I will try this solution soon.
f. m. said:Or you can restrict yourself to use floating point (single precision, "float") variables and routines only, including all constants (!)
Do you mean instead of usings #define MY_CONSTANT I should use float myConstant, and I should declare every constant I use as a float?
Amit Ashara said:Rather use the timer to directly trigger the ADC and the ADC interrupt handler/DMA to transfer the data from the ADC to the SRAM.
I don't know how to use the DMA. I will check the datasheet.
Do you mean for example that if I need a precision of 3 digits, I should multiply everything by 1000 in my algorithms? 12.345 -> 12345
Basically, yes.
That is often the only available approach on less potent MCUs, like Cortex M3, M0 or even 8-bit machines. Used to implement control loops with less than 10ms cycle time on PIC18 MCUs (32MHz clock, 8 "MIPS") with a former employer. That was no fun, yet I learned something.
For a Cortex-M4F, using the FPU and single-precision calculations seems a viable solution.
Fabien GRZESKOWIAK said:f. m.Or you can restrict yourself to use floating point (single precision, "float") variables and routines only, including all constants (!)Do you mean instead of usings #define MY_CONSTANT I should use float myConstant, and I should declare every constant I use as a float?
Yes, conceivably, but you have to be really careful (and sure that the compiler you are using supports it). Any double precision value in an expression is going to cause promotions to double precision and calculations in double precision. I haven't checked the timing to know if the FP accelerator to see how fast it is but that takes me back to my original injunction to measure.
Robert
Fabien GRZESKOWIAK said:Hello robert, thank you for your reply.
Robert AdsettThat being the case I'd switch to integers and avoid the risk of bringing in double precision library support.
I really need floating point, but the double precision is maybe too much. There is no way to use floating point operations without the double precision?
The tradeoffs are
32 bit integer vs 32 bit float
- integer has higher precision
- float has larger dynamic range
- float may require extra library support (slower) or may have slower operands
- float may bet promoted to double precision (slower)
- float is subject to errors due to its representation (most obviously catastrophic subtraction).
- integer is subject to overflow more readily (because of less dynamic range)
From what I see of your work, you could easily accomplish it in a 16bit integer representation, leaving 16 bits to guard against overflow (or 20 and 12, you get the idea)
Robert
Fabien GRZESKOWIAK said:Not necessarily. Using integer and scaled math, you can tailor your algorithms to the precision you need.Do you mean for example that if I need a precision of 3 digits, I should multiply everything by 1000 in my algorithms? 12.345 -> 12345
or use fractions or bits (the advantage of using bits is that scaling is a simple shift).
Robert
Fabien GRZESKOWIAK said:Let me give more details about my project.
I need to control a DC actuator that is really fast and have some non-linearity (pattented structure from my lab). It goes from 0mm to 3mm(max position) with a time of 3ms. I have to control it in order to have a "soft landing" profile (without control, the landing speed is about 1m.s-1 wich is dangerous).
Thus, I need a fast control loop (beacause of the non-linearity, I can't have a good mathematical representation, so I have to do an oversampling) with a precise frequency:
Radical suggestion (and may not be doable) but is there any chance that the load is constant enough that you can just run open loop? Just output a predetermined PWM profile that changes the drive over time to match the optimal landing profile.
Robert
Robert Adsett said:is there any chance that the load is constant enough that you can just run open loop? Just output a predetermined PWM profile that changes the drive over time to match the optimal landing profile.
I was waiting (and hoping) for such a simplification to arrive.
As always - poster states that "Floating Point and only floating point" is required - yet provides little (NO) data in justification. Such (may) prove true - yet "real" science would provide some explanation in support of such claim...
Having experience with high force, high speed Actuators our findings are that such, "Predetermined PWM profile - augmented w/"integer analog positional feedback" often meets design requirements.
Monitoring the actuators current draw, temperature, and demand utilization all play into the requirement for "rapid actuation & soft landing."
Other than the (possible) restriction in dynamic range - the integer-based processing - being far faster - achieves (most always) the most accurate & responsive actuator control...
Note too that, "No one solution" proves "universal" - yet most always this simplified (do we hear KISS) approach yields substantial critical data - which is most quickly & easily harvested.
Actuator's ballistics, attachment method & other factors impact results - thus such "first order" performance acquisition (as outlined herein) well guides to an optimal solution...
Robert Adsett said:Radical suggestion (and may not be doable) but is there any chance that the load is constant enough that you can just run open loop? Just output a predetermined PWM profile that changes the drive over time to match the optimal landing profile.
Actually, I did it. I always have a current control loop, but the open loop is not robust if we change the load or the temperature. That is why I wanted to try a full control loop.
After few tests, I can sample everything I need fast enough (50kHz) using integers only in my algorithms. I am going to try this way.
Thank you for your replies