STM32 正弦波输出

要输出正弦波,需要好几个外设配合:Timer、DAC、DMA。TImer用来设置正弦波的频率的;DAC顾名思义将数字量转换成模拟量,在这里就是转化成电压信号;DMA直接控制DAC输出,而不用麻烦芯片内核。

下面讲讲它们之间如何配合工作。首先要配置定时器的频率,并设置定时器为输出触发。然后配置DAC的触发源为定时器触发,并打开DAC的MDA功能。接下去轮到DMA的工作了,设置DMA的操作对象为DAC。按上面配置好后,三个外设就可以正常工作了:定时器每次计数值递增,就触发DAC工作,然后DMA就控制DAC输出相对应的电压值,在一个定时周期内,DAC输出电压值输出按正弦波的变化,这样就产生了正弦波!

下面开始讲讲STM32的 代码,仍然还是在我自己的规范工程做修改。

1、工程的改动

1)代码中需要用到定时器,所以添加stm32f10x_tim.c到STM32F10x_StdPeriod_Driver工作组中。

2)除了定时器,还需要用到DAC,故添加stm32f10x_dac.c到STM32F10x_StdPeriod_Driver工作组中。

3)最后还需要添加stm32f10x_dma.c到STM32F10x_StdPeriod_Driver工作组中

4)打开stm32f10x_conf.h文件,把stm32f10x_tim.h、stm32f10x_dac.h、stm32f10x_dma.h包含进来,也就是将原先屏蔽的包含这些文件的语句去掉屏蔽。

5)新建SineWave.c与sineWave.h这两个文件分别保存在BSP文件夹中的src与inc中,并将SineWave.c添加进工程的BSP中。

2、SineWave.c与SineWave.h的程序编写

在代码中,我设置两路正弦波输出,一路输出频率为800Hz的正弦波,另一路输出频率为1600Hz的正弦波,他们分别对应的DAC通道1的PA4引脚,与DAC通道2的PA5引脚。所以代码中首先初始化这两个引脚:

/*************************************************************

Function : SineWave_GPIO_Config

Deion: 引脚配置

Input : none

return : none

*************************************************************/

static void SineWave_GPIO_Config(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //初始化引脚时钟

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;//DAC channel1和channel2对应的引脚

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOA, &GPIO_InitStructure);

}

接下去要配置定时器了,定时器的作用就是设置正弦波的频率,由于要输出两路频率不相同的定时器,所以这里需要配置两路的定时器,一路设置频率为800Hz,另一路设置频率为1600Hz,代码如下:

#define _800Hz (u16)(72000000/sizeof(Sine12bit)*2/800) //800Hz

#define _1600Hz (u16)(72000000/sizeof(Sine12bit)*2/1600) //1600Hz

const u16 Sine12bit[256] = { //正弦波描点

2048, 2098, 2148, 2198, 2248, 2298, 2348, 2398, 2447, 2496,

2545, 2594, 2642, 2690, 2737, 2785, 2831, 2877, 2923, 2968,

3013, 3057, 3100, 3143, 3185, 3227, 3267, 3307, 3347, 3385,

3423, 3460, 3496, 3531, 3565, 3598, 3631, 3662, 3692, 3722,

3750, 3778, 3804, 3829, 3854, 3877, 3899, 3920, 3940, 3958,

3976, 3992, 4007, 4021, 4034, 4046, 4056, 4065, 4073, 4080,

4086, 4090, 4093, 4095, 4095, 4095, 4093, 4090, 4086, 4080,

4073, 4065, 4056, 4046, 4034, 4021, 4007, 3992, 3976, 3958,

3940, 3920, 3899, 3877, 3854, 3829, 3804, 3778, 3750, 3722,

3692, 3662, 3631, 3598, 3565, 3531, 3496, 3460, 3423, 3385,

3347, 3307, 3267, 3227, 3185, 3143, 3100, 3057, 3013, 2968,

2923, 2877, 2831, 2785, 2737, 2690, 2642, 2594, 2545, 2496,

2447, 2398, 2348, 2298, 2248, 2198, 2148, 2098, 2047, 1997,

1947, 1897, 1847, 1797, 1747, 1697, 1648, 1599, 1550, 1501,

1453, 1405, 1358, 1310, 1264, 1218, 1172, 1127, 1082, 1038,

995, 952, 910, 868, 828, 788, 748, 710, 672, 635,

599, 564, 530, 497, 464, 433, 403, 373, 345, 317,

291, 266, 241, 218, 196, 175, 155, 137, 119, 103,

88, 74, 61, 49, 39, 30, 22, 15, 9, 5,

2, 0, 0, 0, 2, 5, 9, 15, 22, 30,

39, 49, 61, 74, 88, 103, 119, 137, 155, 175,

196, 218, 241, 266, 291, 317, 345, 373, 403, 433,

464, 497, 530, 564, 599, 635, 672, 710, 748, 788,

828, 868, 910, 952, 995, 1038, 1082, 1127, 1172, 1218,

1264, 1310, 1358, 1405, 1453, 1501, 1550, 1599, 1648, 1697,

1747, 1797, 1847, 1897, 1947, 1997 };

//const u16 Sine12bit[32] = { //正弦描点

// 2047, 2447, 2831, 3185, 3498, 3750, 3939, 4056, 4095, 4056,

// 3939, 3750, 3495, 3185, 2831, 2447, 2047, 1647, 1263, 909,

// 599, 344, 155, 38, 0, 38, 155, 344, 599, 909, 1263, 1647};

/*************************************************************

Function : SineWave_TIM_Config

Deion: 定时器配置

Input : none

return : none

*************************************************************/

static void SineWave_TIM_Config(void)

{

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2|RCC_APB1Periph_TIM6, ENABLE);//初始化与6的时钟

TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);

TIM_TimeBaseStructure.TIM_Period = _800Hz; //正弦波1频率设置

TIM_TimeBaseStructure.TIM_Prescaler = 0x0; //没有预分频

TIM_TimeBaseStructure.TIM_ClockDivision = 0x0; //时钟不分频

TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//增计数

TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

TIM_TimeBaseStructure.TIM_Period = _1600Hz; //正弦波2频率设置

TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);

TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);//更新TIM2输出触发

TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);//更新TIM6输出触发

}

上面代码中宏定义中的sizeof(Sine12bit)是输出正弦波的数组的大小,它的类型是u16,所以在要求出数组的元素的个数sizeof(Sine12bit)要除以2。之所以要用sizeof(Sine12bit)/2这张种方式而不是直接用常量256,是因为在上面代码中,我定义了两个Sine12bit数组,其中一个被我屏蔽掉了,如果想要使用被屏蔽的那个Sine12bit数组,在解除它的屏蔽与屏蔽另一个Sine12bit数组后,_800Hz和_1600Hz的值就会自定改变,以满足定时器的正确频率输出。上面两个Sine12bit[]数组中,一个数组为Sine12bit[256]共256个元素,另一个屏蔽的是数组是Sine12bit[32]共32个元素。这两个数组的元素如果在坐标上描点出来的话,就是一个周期的正弦波,只不过具有256个元素的数组描出来的曲线比只有32元素的数组描出来的曲线精确的多。这两个数组,我都给出了,以便以后根据场合选择。在这个工程里我选着选择的是Sine12bit[256],画出精确的正弦波。

接下去配置DAC,DAC总共2路通道,由于要输出正弦波,所以这两个通道都需要配置。代码如下:

/*************************************************************

Function : SineWave_DAC_Config

Deion: DAC配置

Input : none

return : none

*************************************************************/

static void SineWave_DAC_Config(void)

{

DAC_InitTypeDef DAC_InitStructure;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);//初始化DAC的时钟

DAC_StructInit(&DAC_InitStructure);

DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO;//指定DAC1的触发定时器TIM2

DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;//无波形产生

DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable; //不是能DAC输出缓冲

DAC_Init(DAC_Channel_1, &DAC_InitStructure);//初始化DAC channel1

DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;//指定DAC2的触发定时器TIM6

DAC_Init(DAC_Channel_2, &DAC_InitStructure);//初始化DAC channel2

DAC_Cmd(DAC_Channel_1, ENABLE); //使能DAC channel1

DAC_Cmd(DAC_Channel_2, ENABLE); //使能DAC channel2

DAC_DMACmd(DAC_Channel_1, ENABLE); //使能DAC Channel1的DMA

DAC_DMACmd(DAC_Channel_2, ENABLE); //使能DAC Channel2的DMA

}

上面代码中,DAC的通道1与通道2的DAC_Trigger分别设置成 定时器2触发DAC_Trigger_T2_TRGO与定时器6触发DAC_Trigger_T6_TRGO。关于DAC的外部触发源,只能是下面的几个:TIM2、TIM4、TIM5、TIM6、TIM7、TIM8、EXIT_Line9、SWTRIG(软件触发),如下图所示:

DAC通道1与通道2配置完后,还要打开DAC的DMA功能:DAC_DMACmd(DAC_Channel_1, ENABLE);DAC_DMACmd(DAC_Channel_2, ENABLE);以便然DMA可以控制DAC输出。

DAC配置后,就临到DAM的配置了。DMA配置代码如下:

#define DAC_DHR12R1 0x40007408 //外设DAC通道1的基地址

#define DAC_DHR12R2 0x40007414 //外设DAC通道2的基地址

/*************************************************************

Function : SineWave_DMA_Config

Deion: DMA配置

Input : none

return : none

*************************************************************/

static void SineWave_DMA_Config(void)

{

DMA_InitTypeDef DMA_InitStructure;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);//初始化DMA2的时钟

DMA_DeInit(DMA2_Channel3); //将DMA配置成默认值

DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12R1;//指定DMA2通道3的目标地址为DAC1_DHR12R1

DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&Sine12bit;//指定DMA的源地址为数组Sine12bit

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//外设作为数据传输的目的地

DMA_InitStructure.DMA_BufferSize = sizeof(Sine12bit)/2;//DMA缓冲区大小

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设机地址存器不变

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设数据宽度为半字

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//内存数据宽度为半字

DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//工作在循环缓存模式,数据传输数为0时,自动恢复配置初值

DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;//非常高优先级

DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//通道未被设置成内存到内存模式,与循环模式相对

DMA_Init(DMA2_Channel3, &DMA_InitStructure);//初始化DMA

DMA_DeInit(DMA2_Channel4);

DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12R2;//指定DMA2通道3的目标地址为DAC2_DHR12R2

DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;

DMA_Init(DMA2_Channel4, &DMA_InitStructure);

DMA_Cmd(DMA2_Channel3, ENABLE); //使能DMA的channel3

DMA_Cmd(DMA2_Channel4, ENABLE); //使能DMA的channel4

}

首先需要说明的是为什么配置是DAM2,而且是DMA2的通道3与通道4。这是因为不同外设都对应不同的DMA与DMA的通道,他们的对应关系如下图所示:

上面的两幅图中看以找到,DAC通道1对应的是DMA2的通道3,DAC通道2对应DMA的通道4。

DMA_PeripheralBaseAddr = DAC_DHR12R1;这句话指定DAC外设的基地址,在宏定义中以已经给出了它的地址。这个外设基地址地址取值可以查询产参考《STM32参考手册》。我以DAC通道1为例:查询《STM32参考手册》的2.3节,可以查询到DAC的寄存器组地址范围为:0x40007400~0x00077FF,如下图所示:

然后可以参考《STM32的11.5.14节》查找DAC寄存器映像表,选择DAC_DHR12R1寄存器,查看其偏移为地址为0x08,如下图所示:

所以上面定义的DAC_DHR12R1=基地址+偏移地址=0x40007400+0x08=0x40007408。下面给出个寄存器的解释,可能过会有助于代码的理解:

DAC_CR:DAC控制寄存器(偏移:0x00)

DAC_SWTRIGR:DAC软件触发寄存器(偏移:0x04)

DAC_DHR12R1:DAC通道 1的 12位右对齐数据保持寄存器(偏移:0x08)

DAC_DHR12L1:DAC通道 1的 12位左对齐数据保持寄存器(偏移:0x0C)

DAC_DHR8R1:DAC通道 1的 8位右对齐数据保持寄存器(偏移:0x10)

DAC_DHR12R2:DAC通道 2的 12位右对齐数据保持寄存器(偏移:0x14)

DAC_DHR12L2:DAC通道 2的 12位左对齐数据保持寄存器(偏移:0x18)

DAC_DHR8R2:DAC通道 2的 8位右对齐数据保持寄存器(偏移:0x1C)

DAC_DHR12LD:双DAC的 12位左对齐数据保持寄存器(偏移:0x20)

DAC_DHR12RD:双DAC的 12位右对齐数据保持寄存器(偏移:0x24)

DAC_DHR8RD:双DAC的 8位右对齐数据保持寄存器(偏移:0x28)

DAC_DOR1:DAC通道 1数据输出寄存器(偏移:0x2C)

DAC_DOR2:DAC通道 2数据输出寄存器(偏移:0x30)

DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&Sine12bit;这句话指定了数据的源地址,也就是说DMA操作期间的数据都到这个地址中取。

DMA_InitStructure.DMA_BufferSize = sizeof(Sine12bit)/2;定义了DMA缓冲区的大小,缓冲区的大小应取与Sine12bit[]数组大小形同的空间。

后面还有其他一些代码就不细讲了,自己看注释。

最后还需要定义一个总函数:SineWave_Init(),它调用上面的这些函数来初始化与正弦波相关的代码:

/*************************************************************

Function : SineWave_Init();

Deion: 正弦波初始化

Input : none

return : none

*************************************************************/

void SineWave_Init(void)

{

SineWave_GPIO_Config(); //配置引脚

SineWave_TIM_Config(); //配置定时器

SineWave_DAC_Config(); //配置DAC

SineWave_DMA_Config(); //配置DMA

TIM_Cmd(TIM2, ENABLE); //打开TIM2

TIM_Cmd(TIM6, ENABLE); //打开TIM6

}

下载讲讲SineWave.h文件,它也仅仅声明了SineWave_Init()函数,以方便其他文件调用。

3、main.c函数编写

main函数需要调用上面定义的SineWave_Init()函数以初始化相关代码:

/*************************************************************

Function : main

Deion: main入口

Input : none

return : none

*************************************************************/

int main(void)

{

BSP_Init();

SineWave_Init();

PRINTF("\nmain() is running!\r\n");

while(1)

{

1_Toggle();

Delay_ms(1000);

}

}

4、测试

用 检查输出波形,可以检测到频率分别为800Hz与1600Hz的正弦波。其实这不是测试的重点,重点是观察使用Sine12bit[32]与Sine12bit[256]输出波形的区别。我采集输出频率为1600Hz的波形,分别使用Sine12bit[32]与Sine12bit[256],他们分别对应下面的第一张图与第二张图:

两张图对比中,可以看出使用Sine12bit[32]作为描点输出的波形中间有点断续,而使用Sine12bit[256]作为描点输出的正弦波则是非常地连续平滑的。

  • 34
    点赞
  • 196
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
这是一道涉及到电路设计和编程的问题,需要具备相关的知识和经验。 首先,我们需要选择合适的芯片来实现DA转换。MCP4725A0T-E/CH是一款12位分辨率I2C接口数字-模拟转换器,可以直接通过I2C总线控制输出电压。因此,我们可以选择该芯片来实现正弦波信号发生器。 接下来,我们需要编写程序来生成正弦信号。正弦信号的公式为:y = A*sin(2*pi*f*t),其中A为幅度,f为频率,t为时间。由于该芯片的分辨率为12位,因此输出电压范围为0~3.3V。为了实现5V的幅度,我们可以通过电路放大器来实现。 以下是代码示例: ``` #include <Wire.h> #include <Adafruit_MCP4725.h> #define VREF 3.3 // 参考电压 #define AMP 2.5 // 幅度 #define FREQ 1000 // 频率 Adafruit_MCP4725 dac; // 创建MCP4725对象 void setup() { Serial.begin(9600); dac.begin(0x62); // 初始化MCP4725 } void loop() { static unsigned long lastTime = 0; unsigned long now = millis(); float t = (now - lastTime) / 1000.0; // 计算时间间隔 lastTime = now; float voltage = AMP * sin(2 * PI * FREQ * t) + VREF; // 计算输出电压 dac.setVoltage(voltage / VREF * 4095, false); // 将电压转换为12位数字量输出 } ``` 在程序中,我们首先定义了参考电压、幅度和频率。然后在setup函数中初始化MCP4725芯片,并在loop函数中生成正弦波信号,将输出电压转换为12位数字量输出。 最后,我们还需要使用示波器测试输出正弦波信号波形,确保其符合要求。 需要注意的是,由于该芯片的输出电压范围为0~3.3V,因此在实现5V的幅度时需要通过电路放大器来实现。此外,由于正弦信号频率的选择与具体应用有关,需要根据实际情况进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值