STM32F DAC настройка и использование с помощью SPL а также пример генератора сигналов.
Ссылка на видео в YouTubeDAC если кто не знает это преобразователь цифрового значения в напряжение или противоположность ADC. В статье я буду использовать более простой вариант DAC который находится в контроллере STM32F051C8, есть более сложные например в контроллерах STM32F07x или STM32F09x которые могут самостоятельно генерировать некоторые сигналы.
Для создания простых сигналов таких как прямоугольный, синусный, треугольный, левая пила и правая пила я написал программу которую можете использовать для создания массива значений, она находится в разделе Программы называется «Генератор сигналов для DAC».
Схема DAC.
- VDDA и VSSA
- положительное и отрицательное напряжение относительно которого будет формироваться выходной сигнал.
- DAC_OUT
- вывод DAC для аналогового сигнала, выводы DAC у контроллеров являются аналоговыми и перенаправляется не могут.
- EXTI_9, SWTRIGx, TIM6_TRGO, TIM3_TRGO, TIM15_TRGO, TIM2_TRGO
- входы триггера для старта передачи нового значения из регистра DHRx(регистр удержания) в DORx(регистр преобразования цифрового значения в аналоговое).
- DAC control register
- регистр управления DAC.
- DHRx
- регистр удерживания нового значения для преобразование, условное обозначение трёх регистров DAC_DHR12R1(двенадцати битное значение со смещение вправо), DAC_DHR12L1 (двенадцати битное значение со смещение влево) и DAC_DHR8R1(восьми битное значение со смещение влево).
- DORx
- регистр преобразования, из DHRx в него копируется результат самостоятельно или по событию триггера.
- Control logic
- блок управление передачей результата из DHRx в DORx.
- Digital-to-analog-converterx
- конвертер цифрового сигнала в цифровой, цепочка резисторов подключённая к DORx регистру.
- BOFF
- выходной буфер или операционный повторитель, предназначен для обеспечения необходимым током нагрузку.
Пример из кода ниже включения и выключения выходного буфера.
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
Повторюсь данный DAC самый простой, есть более сложные с автоматической генерацией сигналов(треугольных и шумов) и также регулировкой уровня смещения напряжения, что будет описано другой статье.
Особенности использования DAC.
Прежде чем начинать работать с DAC вам нужно ознакомится с этой вырезкой из datasheet.
В таблице приведены минимальные и максимальные уровни напряжений при включенном и выключенном выходном буфере. Если буфер включен то уровень минимального выходного значения будет 0.2В и максимального Vdda-0.2В, то есть если нормально воспроизвести сигнал вы значение преобразования должны сместить на этот эквивалент напряжения.
Пример, если у нас Vssa=0В и Vdda=3.3В то значение минимальное будет при двенадцати битном значении(4095) DAC 0.2*4095/3.3=248 и максимальное 4095-248=3847, ниже и выше этих значений уровень выходных напряжений будет искажаться.
При выключенном выходном буфере минимально значение 0.0005В и максимальное Vdda-1LSB(значение младшего бита) или 3.3-3.3/4095=3.29В, здесь выходной сигнал лучше но если подключить нагрузку сопротивлением в 5.1кОм уровень напряжения упадёт с 1.65В до 0.6В.
Практика настройки и использования DAC или примеры.
Для примера будем использовать контроллер STM32F051C8 и его единственный вывод DAC_OUT1, не забываем подключить выводы Vdda(верхнее напряжение, не больше питания контроллера) и Vssa(общий контроллера или земля).
В примерах будет использоваться для преобразования двенадцати битовый результат DAC с правым смещением, который записывается в регистр DHRx с помощью функции.
- DAC_SetChannel1Data( DAC_Align, Data)
- возможны варианты для записи значения Data.
- DAC_Align
- варианты:
- DAC_Align_12b_R-двенадцати битовое значение с правым смещением;
- DAC_Align_12b_L-двенадцати битовое значение с левым смещением;
- DAC_Align_8b_R-восьми битное значение с правым смещением.
Первый пример простой генератор пяти разных сигналов(квадратурный, синусоидальный, треугольный, левая и правая пила), которые будут генерироваться по очереди, задержка перед записью нового значения в DHRx реализована в виде обычного цикла.
#include "stm32f0xx.h"
/*Квадратурный сигнал.*/
const uint16_t arr_square[]={3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 3847, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248};
/*Синусоидальный сигнал.*/
const uint16_t arr_sin[]={2048, 2236, 2422, 2604, 2779, 2947, 3105, 3252, 3385, 3503, 3606, 3691, 3759, 3808, 3837, 3847, 3837, 3808, 3759, 3691, 3606, 3503, 3385, 3252, 3105, 2947, 2779, 2604, 2422, 2236, 2048, 1859, 1673, 1491, 1316, 1148, 990, 843, 710, 592, 489, 404, 336, 287, 258, 248, 258, 287, 336, 404, 489, 592, 710, 843, 990, 1148, 1316, 1491, 1673, 1859};
/*Треугольный сигнал.*/
const uint16_t arr_triangle[]={248, 368, 488, 608, 728, 848, 968, 1088, 1208, 1328, 1448, 1568, 1688, 1808, 1928, 2048, 2167, 2287, 2407, 2527, 2647, 2767, 2887, 3007, 3127, 3247, 3367, 3487, 3607, 3727, 3847, 3727, 3607, 3487, 3367, 3247, 3127, 3007, 2887, 2767, 2647, 2527, 2407, 2287, 2167, 2048, 1928, 1808, 1688, 1568, 1448, 1328, 1208, 1088, 968, 848, 728, 608, 488, 368};
/*Левая пила сигнал.*/
const uint16_t arr_left_saw[]={248, 309, 370, 431, 492, 553, 614, 675, 736, 797, 858, 919, 980, 1041, 1102, 1163, 1224, 1285, 1346, 1407, 1468, 1529, 1590, 1651, 1712, 1773, 1834, 1895, 1956, 2017, 2078, 2139, 2200, 2261, 2322, 2383, 2444, 2505, 2566, 2627, 2688, 2749, 2810, 2871, 2932, 2993, 3054, 3115, 3176, 3237, 3298, 3359, 3420, 3481, 3542, 3603, 3664, 3725, 3786, 3847};
/*Правая пила сигнал.*/
const uint16_t arr_right_saw[]={3847, 3786, 3725, 3664, 3603, 3542, 3481, 3420, 3359, 3298, 3237, 3176, 3115, 3054, 2993, 2932, 2871, 2810, 2749, 2688, 2627, 2566, 2505, 2444, 2383, 2322, 2261, 2200, 2139, 2078, 2017, 1956, 1895, 1834, 1773, 1712, 1651, 1590, 1529, 1468, 1407, 1346, 1285, 1224, 1163, 1102, 1041, 980, 919, 858, 797, 736, 675, 614, 553, 492, 431, 370, 309, 248};
int main(){
RCC_DeInit( );
/*Настройка вывода DAC_OUT1.*/
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOA, ENABLE);
GPIO_InitTypeDef gpio_dac;
gpio_dac.GPIO_Mode = GPIO_Mode_AN;
gpio_dac.GPIO_Pin = GPIO_Pin_4;
gpio_dac.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init( GPIOA, &gpio_dac);
/*Настройка DAC для самостоятельной перезаписи нового значения.*/
RCC_APB1PeriphClockCmd( RCC_APB1Periph_DAC, ENABLE);
DAC_InitTypeDef DAC_InitStructure;
DAC_StructInit( &DAC_InitStructure);
DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
DAC_Init( DAC_Channel_1, &DAC_InitStructure);
DAC_Cmd( DAC_Channel_1, ENABLE);
while(1){
/*Генерация прямоугольного сигнала.*/
for( uint16_t count=0; count<1000; count++){
for( uint32_t i=0; i<60; i++){
DAC_SetChannel1Data( DAC_Align_12b_R, arr_square[i]);
for( uint32_t w=0; w<10; w++);//Задержка перед следующим преобразованием.
}
}
/*Генерация синусоидального сигнала.*/
for( uint16_t count=0; count<1000; count++){
for( uint32_t i=0; i<60; i++){
DAC_SetChannel1Data( DAC_Align_12b_R, arr_sin[i]);
for( uint32_t w=0; w<10; w++);//Задержка перед следующим преобразованием.
}
}
/*Генерация треугольного сигнала.*/
for( uint16_t count=0; count<1000; count++){
for( uint32_t i=0; i<60; i++){
DAC_SetChannel1Data( DAC_Align_12b_R, arr_triangle[i]);
for( uint32_t w=0; w<10; w++);//Задержка перед следующим преобразованием.
}
}
/*Генерация левая-пила сигнал.*/
for( uint16_t count=0; count<1000; count++){
for( uint32_t i=0; i<60; i++){
DAC_SetChannel1Data( DAC_Align_12b_R, arr_left_saw[i]);
for( uint32_t w=0; w<10; w++);//Задержка перед следующим преобразованием.
}
}
/*Генерация правая-пила сигнал.*/
for( uint16_t count=0; count<1000; count++){
for( uint32_t i=0; i<60; i++){
DAC_SetChannel1Data( DAC_Align_12b_R, arr_right_saw[i]);
for( uint32_t w=0; w<10; w++);//Задержка перед следующим преобразованием.
}
}
}
return 0;
}
И более сложный пример но более точный с использование для перезаписи нового значения функцию триггера, для этого будем использовать триггер переполнения таймера(TIM2_TRGO).
После отсчёта периода TIM2 значение будет передано из DHRx в DORx но новое значение запишется в DHRx в прерывании от переполнении TIM2. Прерывание требует определённое количество тактов для выполнения и поэтому значение периода таймера не должно быть меньше 114(получено опытным путём), увеличивая значение периода получится плавное уменьшение частоты генерируемого сигнала.
#include "stm32f0xx.h"
/*Синусоидальный сигнал, 60 значений.*/
const uint16_t arr_sin[]={2048, 2236, 2422, 2604, 2779, 2947, 3105, 3252, 3385, 3503, 3606, 3691, 3759, 3808, 3837, 3847, 3837, 3808, 3759, 3691, 3606, 3503, 3385, 3252, 3105, 2947, 2779, 2604, 2422, 2236, 2048, 1859, 1673, 1491, 1316, 1148, 990, 843, 710, 592, 489, 404, 336, 287, 258, 248, 258, 287, 336, 404, 489, 592, 710, 843, 990, 1148, 1316, 1491, 1673, 1859};
/*Счётчик значений.*/
uint16_t count=0;
int main(){
RCC_DeInit( );
/*Настройка TIM2 для генерации сигнала.*/
RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseInitTypeDef TIM2_InitStructure;
TIM2_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM2_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM2_InitStructure.TIM_Period = 114;
TIM2_InitStructure.TIM_Prescaler = 0;
TIM2_InitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit( TIM2, &TIM2_InitStructure);
/*После переполнения будет срабатывать выходной триггер.*/
TIM_SelectOutputTrigger( TIM2, TIM_TRGOSource_Update);
TIM_ClearFlag( TIM2, TIM_FLAG_Update);
TIM_ITConfig( TIM2, TIM_IT_Update, ENABLE);
NVIC_EnableIRQ( TIM2_IRQn);
TIM_Cmd( TIM2, ENABLE);
/*Настройка вывода DAC.*/
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOA, ENABLE);
GPIO_InitTypeDef gpio_dac;
gpio_dac.GPIO_Mode = GPIO_Mode_AN;
gpio_dac.GPIO_Pin = GPIO_Pin_4;
gpio_dac.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init( GPIOA, &gpio_dac);
/*Настройка DAC для перезаписи нового значения от входного триггера.*/
RCC_APB1PeriphClockCmd( RCC_APB1Periph_DAC, ENABLE);
DAC_InitTypeDef DAC_InitStructure;
DAC_StructInit(&DAC_InitStructure);
/*Подключаем входной триггер.*/
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
DAC_Init(DAC_Channel_1, &DAC_InitStructure);
DAC_Cmd(DAC_Channel_1, ENABLE);
while(1){}
return 0;
}
void TIM2_IRQHandler(void){
TIM_ClearFlag( TIM2, TIM_FLAG_Update);
DAC_SetChannel1Data( DAC_Align_12b_R, arr_sin[count]);
count++;
if( count==60 ) count=0;
}
И последний пример с использование DMA, самый быстрый способ генерации сигнала и сложный.
#include "stm32f0xx.h"
/*Адрес в памяти нужного DHRx, высчитывается самостоятельно.*/
#define DAC_DHR12R_Address 0x40007408
//Синусоидальный сигнал, 60 значений.
const uint16_t arr_sin[]={2048, 2236, 2422, 2604, 2779, 2947, 3105, 3252, 3385, 3503, 3606, 3691, 3759, 3808, 3837, 3847, 3837, 3808, 3759, 3691, 3606, 3503, 3385, 3252, 3105, 2947, 2779, 2604, 2422, 2236, 2048, 1859, 1673, 1491, 1316, 1148, 990, 843, 710, 592, 489, 404, 336, 287, 258, 248, 258, 287, 336, 404, 489, 592, 710, 843, 990, 1148, 1316, 1491, 1673, 1859};
/*Счётчик данных массива сигнала для DMA.*/
uint16_t count=60;
/*Задержка для перезаписи нового значения сигнала в DHRx, должна быть больше нуля.*/
uint16_t signal_speed=50;
int main(){
RCC_DeInit( );
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOA, ENABLE);
GPIO_InitTypeDef gpio_dac;
gpio_dac.GPIO_Mode = GPIO_Mode_AN;
gpio_dac.GPIO_Pin = GPIO_Pin_4;
gpio_dac.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init( GPIOA, &gpio_dac);
RCC_APB1PeriphClockCmd( RCC_APB1Periph_DAC, ENABLE);
DAC_InitTypeDef DAC_InitStructure;
DAC_StructInit(&DAC_InitStructure);
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
DAC_Init(DAC_Channel_1, &DAC_InitStructure);
DAC_Cmd(DAC_Channel_1, ENABLE);
DAC_DMACmd(DAC_Channel_1, ENABLE);
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel3);
DMA_InitTypeDef DMA_Settings_DMA_DAC;
DMA_Settings_DMA_DAC.DMA_PeripheralBaseAddr = DAC_DHR12R_Address;
DMA_Settings_DMA_DAC.DMA_MemoryBaseAddr = (uint32_t)&arr_sin;
DMA_Settings_DMA_DAC.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_Settings_DMA_DAC.DMA_BufferSize = count;
DMA_Settings_DMA_DAC.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_Settings_DMA_DAC.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_Settings_DMA_DAC.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_Settings_DMA_DAC.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_Settings_DMA_DAC.DMA_Mode = DMA_Mode_Circular;
DMA_Settings_DMA_DAC.DMA_Priority = DMA_Priority_VeryHigh;
DMA_Settings_DMA_DAC.DMA_M2M = DMA_M2M_Disable;
DMA_Init( DMA1_Channel3, &DMA_Settings_DMA_DAC);
DMA_Cmd( DMA1_Channel3, ENABLE);
RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseInitTypeDef TIM2_InitStructure;
TIM2_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM2_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM2_InitStructure.TIM_Period = signal_speed;
TIM2_InitStructure.TIM_Prescaler = 0;
TIM2_InitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit( TIM2, &TIM2_InitStructure);
TIM_SelectOutputTrigger( TIM2, TIM_TRGOSource_Update);
TIM_Cmd( TIM2, ENABLE);
while(1){}
return 0;
}