Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2015:stm32-dds

Generátor signálu s přímou číslicovou syntézou

Hardware

Pro ovládání generátoru dle stávající verze firmwaru je třeba připojit pět tlačítek a externími pull-up rezistory, interní pull-upy nejsou dostatečně tvrdé a způsobují falešné detekce při generování. Vzhledem k dosažené vzorkovací frekvenci je výstup signálu paralelní, pro plné rozlišení 16b je třeba specializovaný převodník.

Program

Středobodem programu je smyčka generování vzorků, k jejímu vytvoření byly použity techniky loop unrollingu a specifika práce programového řadiče procesorů ARM při násobné práci s pamětí současně s ALU operacemi. Proti původní realizaci smyčky v projektu AVR DDS 2.0 je pro její opuštění použit přímo argument frekvence, smyčka je tedy připravena na FM modulaci.

Proti původnímu očekávání nebyl použit řadič DMA, toto rozhodnutí vyplynulo z použití 16b výstupního vzorku. Pozice vzorku v poli je získána posunem registru o patřičný počet bitů vpravo, avšak pro zisk adresy je třeba opětovně posunout o jeden bit vlevo. Instrukce načtení z paměti integruje tento posuv v sobě, nepřináší proto žádné navýšení počtu potřebných taktů, Naopak dochází k úspoře při ostatních operacích s pamětí.

Po všech optimalizacích bylo možno dosáhnout výstupního vzorku každých 5 taktů, s čipem STM32F401RET6 taktovaným na garantovaném kmitočtu 84 MHz tak bylo dosaženo 16,8 MSa/s. Při závěrečném testování se žel ukázalo, že provádění programu nezáleží jen na posloupnosti instrukcí, ale také na použitých registrech. Korektní funkce smyčky psané v C nebo assembleru bez konkrétního přiřazení registrů tak nezávisí jen na smyčce samé, ale i na okolí místa, ze kterého je funkce volaná. Pro replikovatelnost výstelku je tedy nutné přepsat smyčku do assembleru s vyhrazenými registry.

Zdrojový kód smyčky

uint32_t dds_gen(volatile uint32_t *step_ptr, uint32_t init_phase)  //returns phase of next (not generated) sample
{
    register uint32_t acc1;
    register uint32_t acc2;
    register uint32_t acc3;
    register uint32_t acc4;
 
    register uint32_t step;
 
    register uint32_t wf_offset1;
    register uint16_t wf_sample1;
    register uint32_t wf_offset2;
    register uint16_t wf_sample2;
    register uint32_t wf_offset3;
    register uint16_t wf_sample3;
    register uint32_t wf_offset4;
    register uint16_t wf_sample4;
    //most of these will be optimized out, still needed to describe exact instruction sequence
 
    acc1 = init_phase;
    step = *step_ptr;
 
    acc2 = acc1 + step;
    wf_offset1 = (acc1>>(32-WF_DEPTH));
    acc3 = acc2 + (step);
    wf_offset2 = (acc2>>(32-WF_DEPTH));
    acc4 = acc3 + (step);
 
    wf_sample1 = wf[wf_offset1];
    //reset timer for correct DAC latch sync
    //not implemented yet
 
    while (step)
    {                                       //action    taken clocks
        acc1 = acc4 + (step);               //1 add         0   //ALU before memory operation not messing with its address/data is free
        asm volatile("":::"memory");
        GPIO_Write(DAC_PORT, wf_sample1);   //1 write       2
 
        asm volatile("":::"memory");
 
        acc2 = acc1 + step;                 //2 add         1
        wf_offset3 = (acc3>>(32-WF_DEPTH)); //3 shift       1
        acc3 = acc2 + (step);               //3 add         0
        asm volatile("":::"memory");
        wf_sample2 = wf[wf_offset2];        //2 read        2
        GPIO_Write(DAC_PORT, wf_sample2);   //2 write       1
 
        asm volatile("":::"memory");
 
 
        wf_offset4 = (acc4>>(32-WF_DEPTH)); //4 shift       1
        acc4 = acc3 + (step);               //4 add         0
        asm volatile("":::"memory");
        step = *step_ptr;                   //step read     2
        wf_sample3 = wf[wf_offset3];        //3 read        1
        GPIO_Write(DAC_PORT, wf_sample3);   //3 write       1
 
        asm volatile("":::"memory");
 
        wf_offset1 = (acc1>>(32-WF_DEPTH)); //1 shift       0
        wf_offset2 = (acc2>>(32-WF_DEPTH)); //2 shift       1
        asm volatile("":::"memory");
        wf_sample4 = wf[wf_offset4];        //4 read        2
        wf_sample1 = wf[wf_offset1];        //1 read        1
        GPIO_Write(DAC_PORT, wf_sample4);   //4 write       1
 
                                            //  compare     1
                                            //  branch      2
        /* assembly equivalent(addresses, registers & constants may vary):
        adds	r0, r2, r6
        str.w	lr, [r1, #20]
 
        add.w	lr, r0, r2
        mov.w	r8, r5, lsr #29
        add.w	r5, lr, r2
        ldrh.w	r7, [r3, r7, lsl #1]
        str	r7, [r1, #20]
 
        mov.w	r12, r6, lsr #29
        adds	r6, r5, r2
        ldrh.w	r7, [r3, r8, lsl #1]
        ldr	r2, [r4, #0]
        str	r7, [r1, #20]
 
        mov.w	r7, lr, lsr #29
        mov.w	lr, r0, lsr #29
        ldrh.w	r0, [r3, r12, lsl #1]
        ldrh.w	lr, [r3, lr, lsl #1]
        str	r0, [r1, #20]
 
        cmp	r2, #0
        bne.n	0x80003fa <main+174>
        */
    }
    return acc1;
}

Generování obrazu funkce

Při předpokladu velké šířky výstupního vzorku je nutné rovněž disponovat obraz signálu s dostatečně jemným odstupňováním, specializované DDS generátory zpravidla obsahují tabulku o 4 bity širší než výstupní vzorek. Je lépe proto vzorky počítat, než nést v programu.

if(menu==Sinus)
            {   for(i=0;(i<(1<<(WF_DEPTH)));i++)
                {
                    float sin_value = sinf( ((float)i *2*PI) / (float)(1<<(WF_DEPTH)) );
                    wf[i]=(0xFFFF/2)*( 1 + sin_value );
                }
            }
            if(menu==Square)
            {
                for(i=0;(i<(1<<(WF_DEPTH)));i++)
                {
                    if(i<(1<<(WF_DEPTH-1)))
                        wf[i]=0xFFFF;
                    else
                        wf[i]=0x0;
                }
                //for extra something on edges
                wf[0]=0x7FFF;
                wf[(1<<(WF_DEPTH-1))]=0x7FFF;
            }

Obsluha tlačítek

Během nastavování parametrů generátoru lze stisk detekovat běžnými nástroji RTOS, při generování je jim přiřazena priorita pomocí přerušení.

//startup section
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource15);
    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource14);
    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource13);
    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource12);
    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource10);
 
    EXTI_InitTypeDef EXTI_InitStruct;
 
    EXTI_InitStruct.EXTI_Line = BTN_UP|BTN_DWN|BTN_RIGHT|BTN_LEFT|BTN_START;
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStruct.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStruct);
 
//interrupt handler    
void EXTI15_10_IRQHandler(void)
{
    btn_state = 0;
    gen_step = 0;
 
    if (EXTI_GetITStatus(BTN_UP) != RESET)
        btn_state |= BTN_UP;
    if (EXTI_GetITStatus(BTN_DWN) != RESET)
        btn_state |= BTN_DWN;
    if (EXTI_GetITStatus(BTN_LEFT) != RESET)
        btn_state |= BTN_LEFT;
    if (EXTI_GetITStatus(BTN_RIGHT) != RESET)
        btn_state |= BTN_RIGHT;
  /*if (EXTI_GetITStatus(BTN_START) != RESET)
        btn_state |= BTN_START;
  */
    NVIC_InitTypeDef NVIC_InitStruct;
 
    NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x02;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x03;
    NVIC_InitStruct.NVIC_IRQChannelCmd = DISABLE;
    NVIC_Init(&NVIC_InitStruct);
}

Fotografie

p1030965.jpg p1030966.jpg p1030967.jpg

Kompletní zdrojový kód

2015/stm32-dds.txt · Poslední úprava: 2016/01/17 19:55 autor: Jindřich Ryšavý