Keskiviikkona 30.11. saatiin viimein Nucleo F303 ajamaan digitaalisuotoa reaaliajassa. Suotimena oli klassinen tyypin 1 FIR-alipäästö jonka kerroinjono oli laskettu Iowa Hillsin FIR-suotimen suunnittelusovelluksella (kiitos ojousimalle linkkivinkistä) http://iowahills.com/
Suotimen kulmataajuus oli asetettu 0,1 x näytteenottotaajuudelle ja kun näytteitä napsittiin 1kHz taajuudella niin kulman piti osua 100 Hz kohdalle. Ihan napakymppiin ei osuttu ja säädin myöhemmin koodia nostamalla näytteenoton 10kHz ja generoimalla kerroinjonon uudelleen.
Ohessa mittaustuloksia modatusta toteutuksesta:
Ekassa kuvassa havainnollistettu suotimen laskennan kestoaikaa. A/D-muunnin tuottaa näytteitä 100 us välein ja jokaisen keskeytyksen alussa asetetaan GPIO-pinni 1-tilaan ja lopussa pudotetaan alas. Skoopilla näkee sitten suoraan kauanko filtterin ajo kestää ja paljonko aikaa jää muuhun iteraatioiden välillä. Ohessa lopputulos. Huomaa kuvan alareunan mitta-arvot.
Tokassa kuvassa haettu ensimmäinen -3dB piste taajuusvasteesta ja se osuu noin 196 Hz kohdalle. Se nyt ei ihan vastaa suunniteltua 1 kHz taajuutta joten jossain joku mättää lievästi.
Ensimmäinen nollakohta osuu 306 Hz kohdalle (huomaa kanavan 2 skaalaus vaihdettu 200mV -> 10 mV per jako-osa). Skoopin maajohdon sieppaamat I/O:n kytkentäpiikit näkyvät selvästi ja tekevät amplitudin automaattisen mittauksen merkityksettömäksi. Silmällä arvioiden lähtöamplitudi on n. 10mVpp tai vähän yli.
Seuraava maksimi osuu n.526 Hz taajuudelle ja senkin amplitudi on n. -3dB mikä ei nyt oikein natsaa oppikirjan mukaisten suotimien toistokäyrän kulkuun. Eli koodin osalta jää vielä kotiläksyä vaikka rakenteen toki pitäisi olla oikein…
Tässä vielä asiasta kiinnostuneille käytetyn suotimen koodi ja kerroinjono:
// Low pass filter with 16 taps. Corner @ 0.1 x sampling
#define NUM_TAPS 16
float LP1kcoeffs[NUM_TAPS] = {
-0.028156774387097804,
-0.041622036924535436,
-0.035698168970811295,
-0.003403302593968809,
0.053154605145074939,
0.121878244119803664,
0.184063001487414751,
0.220978933758780416,
0.220978933758780416,
0.184063001487414751,
0.121878244119803664,
0.053154605145074939,
-0.003403302593968809,
-0.035698168970811295,
-0.041622036924535436,
-0.028156774387097804 };
typedef struct realTimeFIR_t {
uint16_t *signalBuffer;
uint16_t signalLength;
uint16_t signalIndex;
float *FIRCoeff;
uint16_t coeffLength;
} realTimeFIR_t;
void realTimeFIR_Init(realTimeFIR_t *FIR, uint16_t *buf, uint16_t buflen, float *coeff, uint16_t coefflen) {
FIR->signalBuffer = buf;
FIR->signalLength = buflen;
FIR->signalIndex = 0;
FIR->FIRCoeff = coeff;
FIR->coeffLength = coefflen;
}
uint16_t realTimeFIR(uint16_t signal, realTimeFIR_t *FIR) {
uint16_t convCnt;
volatile uint16_t filteredSignal;
*(FIR->signalBuffer + FIR->signalIndex++) = signal;
if (FIR->signalIndex >= FIR->signalLength ) FIR->signalIndex =0;
filteredSignal = 0;
for ( convCnt = 0; convCnt < FIR->coeffLength; convCnt++ ) {
filteredSignal += *(FIR->signalBuffer + FIR->signalIndex++) * *(FIR->FIRCoeff + (FIR->coeffLength - convCnt));
if (FIR->signalIndex >= FIR->signalLength ) FIR->signalIndex =0;
}
return filteredSignal;
}
// Suodattimen signaalipuskuri johon kerätään A/D-muunnettu signaali
#define ADCBUFSIZE 32
volatile uint16_t adcbuf[ADCBUFSIZE];
realTimeFIR_t LPFilter1; // suodattimen koontistruktuuri jossa koottu signaalipuskuri ja kerroinjono sekä osoittimet niihin
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
MX_DAC1_Init();
MX_TIM2_Init();
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
HAL_ADC_Start_IT(&hadc1);
HAL_TIM_OC_Start(&htim2,TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
// Alustetaan suodattimen puskurit ja osoittimet
realTimeFIR_Init( &LPFilter1, &adcbuf, ADCBUFSIZE, &LP1kcoeffs, NUM_TAPS);
while (1)
{
}
}
/ A/D-muunnoksen keskeytyspalveluohjelman callback jossa ajetaan suodatin
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
volatile uint16_t adcvalue;
// -Haetaan A/D-muuntimelta viimeisin muunnosarvo
// -Suodatetaan muunnosjono
// -viedään suodatuksen tulos D/A-muuntimelle uudeksi lähtöarvoksi
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);
HAL_DAC_SetValue( &hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, realTimeFIR( HAL_ADC_GetValue( AdcHandle ), &LPFilter1 ) );
HAL_DAC_Start( &hdac1, DAC_CHANNEL_1 );
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET);
}