======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
*/
}
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=====
{{2015:stm32-dds:p1030965.jpg}}
{{2015:stm32-dds:p1030966.jpg}}
{{2015:stm32-dds:p1030967.jpg}}
=====Kompletní zdrojový kód=====
{{2015:stm32-dds:Main.c.doc}}