之前想写个程序能够控制STM32F103VET6的DAC双通道输出不同的电压值,并且能够通过串口控制两个通道电压实时可变化,但是在网上一直没找到资源,就反复的查看野火给的范例和数据手册DAC那块输出的讲解,最后整明白了分享给大家。
跟着我进行对比(野火的范例——双通道输出正弦波)的学习。
1、bsp_dac.h文件的修改
首先看一下野火在bsp_dac.h文件中如何定义的如下图
在这里野火定义了一个双DAC的 12 位右对齐数据保持寄存器(DAC_DHR12RD) ,我的理解是“双”字应该是说明这个寄存器是被双通道(PA4、PA5共用的),所以要想让两个通道输出不同的电压值,就必须重新定义这两个通道(PA4、PA5)的地址——定义成能一对一的模式,即一个单独的寄存器控制单独的一个。查看STM32官方中文参考手册的P194-P197页就会发现这两个通道(PA4、PA5)的确各自有控制自己的寄存器。下图为例。
#ifndef __DAC_H
#define __DAC_H
#include "stm32f10x.h"
//DAC 两个通道的寄存器地址,12位、右对齐、双通道
#define DAC_DHR12R1_ADDRESS (DAC_BASE+0x08)
#define DAC_DHR12R2_ADDRESS (DAC_BASE+0x14)
void DAC_OutVoltage(uint16_t VoltageLeft,uint16_t VoltageRight);
void DAC_Channel2_Config(void);
void DAC_Channel2_TIM_Config(void);
void DAC_Channel1_Config(void);
void DAC_Channel1_TIM_Config(void);
#endif /* __DAC_H */
请主要看我的两个地址的重新定义,其他是功能函数没什么好看的
2、bsp_dac.c文件的修改
在修改这块之前一定要看好这张图,当我们采用软件定时触发的方式更新寄存器内的值和DAC的输出(范例和我的过程都是通过刷新DAC的DMA来更新输出的)时,不是所有的定时触发都可用,仅限一下
接着我们来看一下DMA配置是几个比较重要的参数,先看野火范例
这里面我觉得一般使用时需要修改的,部分就是
①DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12RD_ADDRESS; //外设数据地址
②DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&DualSine12bit ; //内存数据地址 DualSine12bit
③DMA_InitStructure.DMA_BufferSize = POINT_NUM; //缓存大小为POINT_NUM字节
④DMA_Init(DMA2_Channel4, &DMA_InitStructure);
/* 使能DMA2-14通道 */
DMA_Cmd(DMA2_Channel4, ENABLE);
这四个部分,第一个就是我们在头文件中修改的重新定义的地址
第二个是要更新的数据,就是我们DAC要转换的那个数字
第三个是DMA要缓存更新的数据的数量(在野火范例中是要不断更新输出32的正弦波不同时候的值大小,所以范例中采用宏定义的方式数量写成32,如果要实时更新,宏定义把这里数量改成1进行)。
第四个是DMA的通道,DAC的两个输出通道(PA4、PA5)都有属于自己的DMA通道,因此要想实时控制双通道输出不同的值就要使能他们各自的DMA。如下图(这里就需要到手册的DMA那章去看了)
然后定时器配置那块不讲了,你可以照葫芦画瓢在写一个定时器,一个定时器触发一个DAC通道,一对一的触发,我就是这么干的,至于只用一个定时器触发行不行我没尝试。注意的是用两个定时触发两个通道(一对一的方式)的话,定时扫描设置的时间一定要不同,相同的话会出bug。
下面我把我写的,准确说修改的bsp_dac.c文件分享如下
#include "bsp_dac.h"
//正弦波单个周期的点数
#define POINT_NUM_Channel2 1
#define POINT_NUM_Channel1 1
uint32_t DualSine12bit_Channel2[POINT_NUM_Channel2];
uint32_t DualSine12bit_Channel1[POINT_NUM_Channel1];
/**
* @brief 使能DAC的时钟,初始化GPIO,初始化DAC的两个通道DAC_Channel_1、DAC_Channel_2
* @param 无
* @retval 无
*/
void DAC_Channel2_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitStructure;
/* 使能GPIOA时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* 使能DAC时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
/* DAC的GPIO配置,模拟输入 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 配置DAC 通道2 */
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO; //使用TIM2作为触发源
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; //不使用波形发生器
DAC_InitStructure.DAC_OutputBuffer=DAC_OutputBuffer_Enable;
/* 配置DAC 通道2 */
DAC_Init(DAC_Channel_2, &DAC_InitStructure);
/* 使能通道2 由PA5输出 */
DAC_Cmd(DAC_Channel_2, ENABLE);
/* 使能DAC通道2的DMA请求 */
DAC_DMACmd(DAC_Channel_2, ENABLE);
}
/**
* @brief 配置TIM2
* @param 无
* @retval 无
*/
void DAC_Channel2_TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* 使能TIM2时钟,TIM2CLK 为72M */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* TIM2基本定时器配置 */
// TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = (2-1); //定时周期 20
TIM_TimeBaseStructure.TIM_Prescaler = 0x0; //预分频,不分频 72M / (0+1) = 72M
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0; //时钟分频系数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
/* 配置TIM2触发源 */
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
/* 使能TIM2 */
TIM_Cmd(TIM2, ENABLE);
}
/**
* @brief 配置DMA
* @param 无
* @retval 无
*/
static void DAC_Channel2_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
/* 使能DMA2时钟 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
/* 配置DMA2 */
DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12R2_ADDRESS; //外设数据地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&DualSine12bit_Channel2 ; //内存数据地址 DualSine12bit
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向内存至外设
DMA_InitStructure.DMA_BufferSize = POINT_NUM_Channel2; //缓存大小为POINT_NUM字节
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设数据地址固定
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存数据地址自增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; //外设数据以字为单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; //内存数据以字为单位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //高DMA通道优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非内存至内存模式
DMA_Init(DMA2_Channel4, &DMA_InitStructure);
/* 使能DMA2-14通道 */
DMA_Cmd(DMA2_Channel4, ENABLE);
}
/***************************************************************通道1*********************************************/
void DAC_Channel1_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitStructure;
/* 使能GPIOA时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* 使能DAC时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
/* DAC的GPIO配置,模拟输入 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 配置DAC 通道2 */
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T4_TRGO; //使用TIM2作为触发源
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; //不使用波形发生器
DAC_InitStructure.DAC_OutputBuffer=DAC_OutputBuffer_Enable;
/* 配置DAC 通道1 */
DAC_Init(DAC_Channel_1, &DAC_InitStructure);
/* 使能通道2 由PA5输出 */
DAC_Cmd(DAC_Channel_1, ENABLE);
/* 使能DAC通道1的DMA请求 */
DAC_DMACmd(DAC_Channel_1, ENABLE);
}
/**
* @brief 配置TIM4
* @param 无
* @retval 无
*/
void DAC_Channel1_TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* 使能TIM2时钟,TIM2CLK 为72M */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
/* TIM2基本定时器配置 */
// TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = (10-1); //定时周期 20
TIM_TimeBaseStructure.TIM_Prescaler = 0x0; //预分频,不分频 72M / (0+1) = 72M
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0; //时钟分频系数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
/* 配置TIM4触发源 */
TIM_SelectOutputTrigger(TIM4, TIM_TRGOSource_Update);
/* 使能TIM4 */
TIM_Cmd(TIM4, ENABLE);
}
/**
* @brief 配置DMA
* @param 无
* @retval 无
*/
static void DAC_Channel1_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
/* 使能DMA2时钟 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
/* 配置DMA2 */
DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12R1_ADDRESS; //外设数据地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&DualSine12bit_Channel1 ; //内存数据地址 DualSine12bit
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向内存至外设
DMA_InitStructure.DMA_BufferSize = POINT_NUM_Channel1; //缓存大小为POINT_NUM字节
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设数据地址固定
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存数据地址自增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; //外设数据以字为单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; //内存数据以字为单位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //高DMA通道优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非内存至内存模式
DMA_Init(DMA2_Channel3, &DMA_InitStructure);
/* 使能DMA2-13通道 */
DMA_Cmd(DMA2_Channel3, ENABLE);
}
/*************************************************************电压输出函数********************************/
/**
* @brief DAC电压输出函数
* @param Voltage
* @retval 无
*/
void DAC_OutVoltage(uint16_t VoltageLeft,uint16_t VoltageRight)
{
uint32_t Idx = 0,Idy=0;
DualSine12bit_Channel1[Idy] = (VoltageRight << 16) + (VoltageRight);
DualSine12bit_Channel2[Idx] = (VoltageLeft << 16) + (VoltageLeft);
DAC_Channel2_DMA_Config();
DAC_Channel1_DMA_Config();
}
这个.C文件总结来说就是每一个DAC输出通道配一个DMA通道和一个软件定时触发。
至于怎么通过串口实时修改输出的值,代码如下,虽然只是串口接收.C文件的一部分,但我想看过野火WIFI那章节的人一看就明白了,不多说。
else if( ( pCh = strstr ( strEsp8266_Fram_Record .Data_RX_BUF, "Speed_" ) ) != 0 )
{
SpeedTemp1 = * ( pCh + 6 )-0x30;
SpeedTemp2 = * ( pCh + 7 )-0x30;
SpeedTemp3 = * ( pCh + 8 )-0x30;
SpeedTemp4 = * ( pCh + 9 )-0x30;
SpeedTemp5 = * ( pCh + 10 )-0x30;
SpeedTemp6 = * ( pCh + 11 )-0x30;
SpeedTemp7 = * ( pCh + 12 )-0x30;
SpeedTemp8 = * ( pCh + 13 )-0x30;
SpeedLeft = SpeedTemp1*1000+SpeedTemp2*100+SpeedTemp3*10+SpeedTemp4;
SpeedRight = SpeedTemp5*1000+SpeedTemp6*100*SpeedTemp7*10+SpeedTemp8;
DAC_OutVoltage(SpeedLeft,SpeedRight);
}
这样就能通过串口改变STM32F103VET6的两个DAC输出通道的电压输出大小(0-3.3V之间变化),实现实时变化。
个人学习心得,有什么讲的不对的地方请指出。