Zde můžete vidět rozdíly mezi vybranou verzí a aktuální verzí dané stránky.
Následující verze | Předchozí verze | ||
2015:stm32-dds [2016/01/17 18:25] Jindřich Ryšavý vytvořeno |
2015:stm32-dds [2016/01/17 19:55] (aktuální) Jindřich Ryšavý [Generování obrazu funkce] |
||
---|---|---|---|
Řádek 2: | Řádek 2: | ||
=====Hardware===== | =====Hardware===== | ||
- | Pro ovládání generátoru dle stávající verze firmwaru je třeba připojit pět tlačítek. Vzhledem k dosažené vzorkovací frekvenci je výstup signálu paralelní, pro plné rozlišení 16b je třeba specializovaný převodník. | + | 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===== | =====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. | + | 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í. | 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á. | + | 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===== | ||
+ | <code c> | ||
+ | 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; | ||
+ | } | ||
+ | </code> | ||
+ | =====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. | ||
+ | <code c> | ||
+ | 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; | ||
+ | } | ||
+ | </code> | ||
+ | =====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í. | ||
+ | <code c> | ||
+ | //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); | ||
+ | } | ||
+ | </code> | ||
+ | =====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}} |