CubeMX F407 DAC输出正弦波

思路

​ 简单说下思路,STM32自带一个两路独立输出的DAC。要输出正弦波,实质是要控制 DAC 以 v=sin(t)的正弦函数关系输出电压,其中 v 为电压输出,t 为时间。 而由于模拟信号连续而数字信号是离散的,所以使用 DAC 产生正弦波时,只能按一定时间间隔输出正弦曲线上的点,在该时间段内输出相同的电压值,若缩短时间间隔,提高单个周期内的输出点数,可以得到逼近连续正弦波的图形,见下图 37-3,若在外部电路加上适当的电容滤波,可得到更完美的图形。

正弦波

​ 由于正弦曲线是周期函数,所以只需要得到单个周期内的数据后按周期重复即可,而单个周期内取样输出的点数又是有限的,所以为了得到呈 v=sin(t)函数关系电压值的数据通常不会实时计算获取,而是预先计算好函数单个周期内的电压数据表,并且转化成以 DAC 寄存器表示的值。 如 sin 函数值的范围为[-1: +1],而 STM32 的 DAC 输出电压范围为[0~3.3]V,按 12 位 DAC 分辨率表示的方法,可写入寄存器的最大值为 212 = 4096,即范围为[0:4096]。所以,实际输出时,会进行如下处理:

  1. 抬升 sin 函数的输出为正值:v = sin(t)+1 ,此时,v 的输出范围为[0:2];
  2. 扩展输出至 DAC 的全电压范围: v = 3.3*(sin(t)+1)/2 ,此时,v 的输出范围为[0:3.3], 正是 DAC 的电压输出范围,扩展至全电压范围可以充分利用 DAC 的分辨率;
  3. 把电压值以 DAC 寄存器的形式表示:Reg_val = 212/3.3 * v = 211*(sin(t)+1),此时,存储到 DAC 寄存器的值范围为[0:4096];
  4. 实践证明,在 sin(t)的单个周期内,取 32 个点进行电压输出已经能较好地还原正弦波形,所以在 t∈[0:2π]区间内等间距根据上述 Reg_val 公式运算得到 32 个寄存器值,即可得到正弦波表;
  5. 控制 DAC 输出时,每隔一段相同的时间从上述正弦波表中取出一个新数据进行输出,即可输出正弦波。改变间隔时间的单位长度,可以改变正弦波曲线的周期。

所以正弦波表如下

uint16_t Sine12bit[32] = {
    2048    , 2460    , 2856    , 3218    , 3532    , 3786    , 3969    , 4072    ,
    4093    , 4031    , 3887    , 3668    , 3382    , 3042    ,2661    , 2255    , 
    1841    , 1435    , 1054    , 714    , 428    , 209    , 65    , 3        ,
    24        , 127    , 310    , 564    , 878    , 1240    , 1636    , 2048
};

CubeMX以及代码的配置

DAC

DAC
  • OUT1/2 Configuration

    对应着两个输出通道

  • External Trigger

    是否使用外部中断触发 外部中断EXTI9 触发 就是使用外部中断来触发DAC。

  • Output Buffer

    使能DAC输出缓存。

    DAC 集成了 2 个输出缓存,可以用来减少输出阻抗,无需外部运放即可直接驱动外部负载。每个 DAC 通道输出缓存可以通过设置 DAC_CR 寄存器的 BOFFx 位来使能或者关闭。如果带载能力还不行,后面就接一个电压跟随器,选择运放一定要选择电流大的型号。
    使能输出缓冲后,DAC 输出的最小电压为 0.2V,最大电压为 VREF±0.2,而未使能输出缓冲则输出可达到0V。

    DACBUFF

  • Trigger

    选择DAC的触发方式

    Timer 2/4/5/6/7/8 Trigger Out event 定时器触发,利用这种方式可以输出特定的波形。在这里我们选择定时器2。

    Software trigger 软件触发,在本模式下,向 DAC_SWTRIGR 寄存器写入配置即可触发信号进行转换。

  • Wave generation mode

    波形发生器的选择,有三角波,和噪声,由于这里我们要输出正弦波,所以就不选择了

DMA的配置

DACDMA

  • Priority

    当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在 DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通 道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。

  • Mode

    Normal 表示单次传输,传输一次后终止传输。
    Circular 表示循环传输,传输完成后又重新开始继续传输,不断循环永不停止。(还是从内存的首地址)

  • Increment Address

    Peripheral 表示外设地址自增。

    Memory 表示内存地址自增。

  • Data Width

    Byte 一个字节。

    Half Word 半个字,等于两字节。

    Word 一个字,等于四字节。

TIM

TIM
  • Prescaler(时钟预分频数):则驱动计数器的时钟 CK_CNT = CK_INT(即84MHz)/(0+1) = 84MHz 即不分频
  • Counter Mode(计数模式):Up(向上计数模式)
  • Counter Period(自动重装载值):525-1
  • auto-reload-preload(自动重装载):Disable(不使能)(PWM时使用)
  • TRGO Parameters(触发输出):Update Event(更新事件) 在定时器的定时时间到达的时候输出一个信号(如:定时器更新产生TRGO信号来触发ADC的同步转换)

关于DAC输出的频率计算

HZ

参考野火STM32F103手册来计算

STM32F407时钟如图

clock TIMCLOCK

f = 84000000/1 * 525 * 32 = 5000 Hz

代码

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

uint16_t Sine12bit[32] = {
    2048    , 2460    , 2856    , 3218    , 3532    , 3786    , 3969    , 4072    ,
    4093    , 4031    , 3887    , 3668    , 3382    , 3042    ,2661    , 2255    , 
    1841    , 1435    , 1054    , 714    , 428    , 209    , 65    , 3        ,
    24        , 127    , 310    , 564    , 878    , 1240    , 1636    , 2048
};

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  MX_DAC_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */
//    uint8_t HexEnd[] = {0xff, 0xff, 0xff};


//    uint8_t aTxBuffer[100] = "add 1,0,100";

  HAL_TIM_Base_Start(&htim2);//开启DAC
  HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t *)Sine12bit, 32, DAC_ALIGN_12B_R);//使能DAC——DMA方式

  /* USER CODE END 2 */

结果

LOTO

一点小总结

  1. DAC的DMA模式其实就是每当触发源触发一次,DMA就像DAC的DHRX寄存器更新一次数据,DAC也将DHRX中的数据再传输到DORX寄存器中,转换为电压输出

    HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t *)Sine12bit, 32, DAC_ALIGN_12B_R);//使能DAC——DMA方式

    实际上32就是以Sine12bit数组的首地址为首地址,依次向后的32组数据,每次触发源触发一次DMA就传输一组数据,直到32组数据都传输完成,这时再从头开始传输数据,这样循环下去,就可以配合TIM来实现输出正弦波的功能

  2. DAC寄存器
  3. 通过这次实验,以后也可以尝试利用TIM来作为触发其他外设启动的一个触发源(ADC同理)