DAC+DMA+TMR
波形发生器采用的是DAC+DMA+TMR的方案,主要思路:PA4 DAC的通道1,通过DMA把内存dataDAC[N]中的数据传送到DAC,并转换成模拟量输出。
配置直接看代码吧,tmr6 作为触发这个参考手册上都有网上资源也多,我就不废话了。
看代码:
/*-------------------------------------------------------------------------------------
mini示波器
2021.2.18 bySDS
TMR+DMA+DAC实现
DAC 使能PA4
---------------------------------------------------------------------------------------*/
#include "dac.h"
void Dac1_Init(void)
{
DAC_InitTypeDef DACtypeDef;
GPIO_PortClock(GPIOA,ENABLE); //端口A 使能
GPIO_PinConfigure(GPIOA,4,GPIO_IN_ANALOG,GPIO_MODE_INPUT); //端口4的模式配置
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC,ENABLE);
DAC_DeInit(); //
DAC_StructInit(&DACtypeDef);//
DACtypeDef.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;//不使用自带的就不需要
DACtypeDef.DAC_OutputBuffer=DAC_OutputBuffer_Disable;
DACtypeDef.DAC_Trigger=DAC_Trigger_T6_TRGO;
DACtypeDef.DAC_WaveGeneration=DAC_WaveGeneration_None;
DAC_Init(DAC_Channel_1,&DACtypeDef);
DAC_Cmd(DAC_Channel_1,ENABLE);
DAC_DMACmd(DAC_Channel_1,ENABLE);
}
使用基本定时器来触发转换
DMA配置:
void MYDMA2_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
DMA_InitTypeDef DMAtypeDef;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2,ENABLE);
DMA_StructInit(&DMAtypeDef);
DMA_DeInit(DMA_CHx);
//基本配置,都不用咋说了,资料上多
DMAtypeDef.DMA_BufferSize=cndtr;
DMAtypeDef.DMA_DIR=DMA_DIR_PeripheralDST;//外设作为目的,即把内存中的数据传到dac外设
DMAtypeDef.DMA_M2M=DMA_M2M_Disable;
DMAtypeDef.DMA_MemoryBaseAddr=cmar;
DMAtypeDef.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;
DMAtypeDef.DMA_MemoryInc=DMA_MemoryInc_Enable;//内存地址递增,内存有len长度大小
DMAtypeDef.DMA_Mode=DMA_Mode_Circular;//循环模式
DMAtypeDef.DMA_PeripheralBaseAddr=cpar;
DMAtypeDef.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//半字16bit
DMAtypeDef.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
DMAtypeDef.DMA_Priority=DMA_Priority_Low;//优先级设置
DMA_Init(DMA_CHx,&DMAtypeDef);
DMA_Cmd(DMA_CHx,ENABLE);
}
波形函数的写法,这里只用三角函数为例:
for (i = 0; i < len; i++)
{
dataDAC[i] = (u16)(base + sin((double)i / len * 2*pi) * amp);
}
DAC定时器数据 TIM6
rcc:499,pre: 71 Tout=500*72/72 =500us, 默认 2KHZ
256个点每500us
采用的是ADC+DMA+TMR。过程:TMR触发adc转换,dma通过将外设adc上的数据采集到内存里。直接看代码:
/**********************************************************
使用ADC1 CH6 做ADC入口检测
***********************************************************/
#include "adc.h"
void Adc_Init(void)
{
ADC_InitTypeDef ADCtypeDef;
GPIO_PortClock(GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
ADC_DeInit(ADC1);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_13Cycles5);// 1/12 *(12.5+13.5) =2.17us,时间长adc精度高
GPIO_PinConfigure(GPIOA,6,GPIO_IN_ANALOG,GPIO_MODE_INPUT);//72/6=12MHz
ADCtypeDef.ADC_ContinuousConvMode=DISABLE;
ADCtypeDef.ADC_DataAlign=ADC_DataAlign_Right;
ADCtypeDef.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T3_TRGO;//类似
ADCtypeDef.ADC_Mode=ADC_Mode_Independent;
ADCtypeDef.ADC_NbrOfChannel=1;
ADCtypeDef.ADC_ScanConvMode=DISABLE;
ADC_Init(ADC1,&ADCtypeDef);
ADC_Cmd(ADC1,ENABLE);
ADC_DMACmd(ADC1,ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_ExternalTrigConvCmd(ADC1,ENABLE);
}
DMA中设置:基本和上述类似,只不过这个是外设为源。
void MYDMA1_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
DMA_InitTypeDef DMAtypeDef;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_StructInit(&DMAtypeDef);
DMA_DeInit(DMA_CHx);
DMAtypeDef.DMA_BufferSize=cndtr;
DMAtypeDef.DMA_DIR=DMA_DIR_PeripheralSRC;
DMAtypeDef.DMA_M2M=DMA_M2M_Disable;
DMAtypeDef.DMA_MemoryBaseAddr=cmar;
DMAtypeDef.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;
DMAtypeDef.DMA_MemoryInc=DMA_MemoryInc_Enable;
DMAtypeDef.DMA_Mode=DMA_Mode_Circular;
DMAtypeDef.DMA_PeripheralBaseAddr=cpar;
DMAtypeDef.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;
DMAtypeDef.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
DMAtypeDef.DMA_Priority=DMA_Priority_High;
DMA_Init(DMA_CHx,&DMAtypeDef);
DMA_Cmd(DMA_CHx,ENABLE);
}
TMR也类似:
/*
定时器3 关于ADC1 的触发转换
: Tout=((arr+1)*(psc+1))/Ft us.
*/
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_BaseTypeDef;
GPIO_PortClock(GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_DeInit(TIM3);
TIM_BaseTypeDef.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_BaseTypeDef.TIM_CounterMode=TIM_CounterMode_Up;
TIM_BaseTypeDef.TIM_Period=arr;
TIM_BaseTypeDef.TIM_Prescaler=psc;
TIM_BaseTypeDef.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM3,&TIM_BaseTypeDef);
TIM_SelectOutputTrigger(TIM3,TIM_TRGOSource_Update);
}
ADC 转换时间 1/12 *(12.5+13.5)=2.17us
定时器数据 TIM3
rcc:20, pec:1800(ret) Fout =72/20/ret 初始情况 : Fout=2KHz 500us
502.17us 1995 Hz 每隔502.17us 发生ADC转换
分辨率995/256=7.79.
cr4_fft_256_stm32(lBufOutArray,lBufInArray, NTP);//官方的库,用的256点
signed int lBufInArray[NTP];
signed int lBufOutArray[NTP/2];
signed int lBufMagArray[NTP/2];
void GetPowerMag(void)
{
float X,Y,Mag;
unsigned short i;
for(i=0; i<NTP/2; i++) //FFT A0=lBufOutArray[0]/NPT
{ // Ai=lBufOutArray[i]*2/NPT
X = (lBufOutArray[i] << 16) >> 16; //lX = lBufOutArray[i];
Y = (lBufOutArray[i] >> 16);
Mag = sqrt(X * X + Y * Y);
if(i == 0)
lBufMagArray[i] = (Mag); //
else
lBufMagArray[i] = (Mag*2);
}
}
void adc_sample(void)
{
u16 i=0;
memset(lBufMagArray,0,NTP/2);
memset(lBufOutArray,0,NTP/2);
memset(lBufInArray,0,NTP);
for (i = 0; i < NTP; i++)
{
lBufInArray[i] = dataADC[i];
}
cr4_fft_256_stm32(lBufOutArray,lBufInArray, NTP);
GetPowerMag();
}
lBufMagArray:存放幅值的(正)。频率分辨率*最大的幅值对应的坐标
为了做得像样一点,添加了菜单进去,整个程序只用到了3个按键:wkup(确定返回),key0(down),key1(up)精英版上的按键
typedef struct
{
u8 curTask;
u8 up;// /KEY1
u8 down;// //KEY0
u8 enter;// //WKUP
void (*current_operation)();
} key_table;
key_table table[]= //多级菜单
{ // 1 2 3 1
{0, 0, 0, 1,startUI}, //过程 ADC-->DATA-->DAC-->ADC
{1, 3, 2, 4,showMenu1}, //1: ADC
{2, 1, 3, 5,showMenu2}, //2: DATA
{3, 2, 1, 6,showMenu3}, //3:DAC
{4, 4, 4, 7,showADCWaveUI},
{5, 5, 5, 8,showDataUI},
{6, 13, 10, 9,showDACWaveUI},
{7, 7, 7, 1,doADCWave},
{8, 8, 8, 2,doDataPre},
{9, 9, 9, 3,doDACWave},
{10,6,11,9,showDACWaveUI2},
{11,10,12,9,showDACWaveUI3},
{12,11,13,9,showDACWaveUI4},
{13,12,6,9,showDACWaveUI5},
};
主函数:
int main(void)
{
u8 key;
systemInit();
while(1)
{
key=KEY_Scan(0);
if(key&&(OS_TASK==RUN_TASK||OS_TASK==ADC_TASK||OS_TASK==DAC_TASK||OS_TASK==DATA_TASK))
{
LED1=!LED1;
switch(key)
{
case KEY1_PRES: fun_index=table[fun_index].up;break;
case KEY0_PRES: fun_index=table[fun_index].down;break;
case WKUP_PRES: fun_index=table[fun_index].enter;break;
}
current_operation_index=table[fun_index].current_operation;
}
current_operation_index();
}
}
主函数整个过程其实就是,循环执行current_operation_index(),函数指针被不停的重新赋值。
形如:
void showMenu3()
{
OLED_CLS();
OLED_ShowStr(30,24, (u8 *)"DAC",24,1);
OLED_ShowStr(30,0, (u8 *)"UP :DAT",12,1);
OLED_ShowStr(30,48, (u8 *)"DOWN:ADC",12,1);
OLED_Refresh_Gram();
}
其实加了while和状态标志判断,这样可以实现在fun内的循环了,此时wkup才能退出,而key0和key1就可以用作其他用途。
void doADCWave()
{
u8 key;
OS_TASK=ADC_TASK;
TIM_Cmd(TIM3,ENABLE);
while(OS_TASK==ADC_TASK)
{
key = KEY_Scan(0);
if(key)
{
LED1=~LED1;
switch(key)
{
case KEY0_PRES:ret+=100;
ret= ret> 65535 ? 65535:ret;
TIM_PrescalerConfig(TIM3,ret-1,TIM_PSCReloadMode_Update);break; //向下翻
case KEY1_PRES: ret-=100;
ret= ret< 1 ? 1:ret;
TIM_PrescalerConfig(TIM3,ret-1,TIM_PSCReloadMode_Update);break; //向上翻
case WKUP_PRES:OS_TASK=RUN_TASK;//退出循环
fun_index=table[fun_index].enter;
current_operation_index=table[fun_index].current_operation;
break;
}
}
doShowWave(dataADC);
}
TIM_Cmd(TIM3,DISABLE);
}
简易示波器
OLED为IIC模式,传输速率好像有点拉跨,屏幕帧率太低的
本人能力有限,错误的地方非常感谢指出,整个工程在这里:
https://download.csdn.net/download/qq_43530159/15512913