АЦП и ЦАП могут быть полезны в ряде задач, например, если нужно считывать данные с аналоговых датчиков, или при работе со звуком. Многие МК семейства STM32 имеют встроенный АЦП (даже несколько), а некоторые МК также имеют и встроенный ЦАП. В этой заметке мы рассмотрим простой пример использования обоих устройств. Для экспериментов я использовал плату LimeSTM32 на базе STM32F405, имеющего как АЦП, так и ЦАП.
Итак, создаем новый проект. В STM32CubeMX во вкладке Pinout в дереве слева находим Peripherals и ставим галочки ADC1 → IN0 и DAC → OUT1 Configuration. Далее во вкладке Configuration жмем на ADC1 и меняем значение «Continuous Conversion Mode» на «Enabled», а значение «End of Conversion Selection» — на «EOC flag at the end of all conversions». Несмотря на название, последнее значение эквивалентно «Disabled», о чем несколько подробнее можно прочитать на StackExchange . Что же до DAC, то нам подойдут его настройки по умолчанию.
Для успешной компиляции кода дописываем в Makefile в списке C_SOURCES:
$ ( FIRMWARE ) / Drivers / STM32F4xx_HAL_Driver / Src / stm32f4xx_hal_adc_ex.c
$ ( FIRMWARE ) / Drivers / STM32F4xx_HAL_Driver / Src / stm32f4xx_hal_dac.c
$ ( FIRMWARE ) / Drivers / STM32F4xx_HAL_Driver / Src / stm32f4xx_hal_dac_ex.c
При настройке ЦАП выяснилось, что аналоговый выход есть только на пинах PA4 и PA5. Проблема в том, что в LimeSTM32 эти пины я вывел на гнезда для подключения Arduino-шилда и использовал, как CS и SCK SPI-шины (D10 и D13 в обозначениях Arduino). Другими словами, приходится выбирать, остаться либо без SPI-устройств на шилде, либо без ЦАП. А мне как раз очень хотелось выводить значения, получаемые от АЦП, на шилд с экранчиком на базе ST7735 , который использует SPI. Для решения этой проблемы было решено немного подхачить плату так, чтобы вместо PA4 и PA5 для SPI использовались пины PB5 и PB3. Я сделал себе пометку внести это изменение в будущих ревизиях LimeSTM32.
Основной код прошивки вышел незамысловатым:
ST7735_Init ( ) ;
ST7735_FillScreen ( ST7735_BLACK ) ;
HAL_ADC_Start ( & hadc1 ) ;
HAL_DAC_Start ( & hdac , DAC_CHANNEL_1 ) ;
UART_Printf ( «Ready! r n » ) ;
// Похоже, что АЦП нужно время на инициализацию.
// Без этой задержки в зависимости от тела loop() прошивка
// может повиснуть.
HAL_Delay ( 1 ) ;
}
void loop ( ) {
HAL_Delay ( 100 ) ;
// Без изменения опции «End of Conversion Selection»
// HAL_ADC_GetValue сработает только один раз, а при
// втором вызове повиснет.
HAL_ADC_PollForConversion ( & hadc1 , HAL_MAX_DELAY ) ;
uint32_t adc_val = HAL_ADC_GetValue ( & hadc1 ) ;
HAL_DAC_SetValue ( & hdac , DAC_CHANNEL_1 , DAC_ALIGN_12B_R , adc_val ) ;
char tmp [ 128 ] ;
snprintf ( tmp , sizeof ( tmp ) , «ADC: %04lu» , adc_val ) ; // 0 .. 4095
ST7735_WriteString ( 11 * 0 , 18 * 0 , tmp , Font_11x18 ,
ST7735_WHITE , ST7735_BLACK ) ;
}
Код просто считывает значение с АЦП и передает его как есть в ЦАП. Значения, используемые обоими устройствами, в данном случае 12-и битные. Интересно, что микроконтроллеры STM32 позволяют понижать разрядность сигнала, получая взамен более быструю работу устройств. Также существуют микроконтроллеры с 16-и битным АЦП.
В LimeSTM32 используется микроконтроллер STM32F405RGT6 ( даташит [PDF] ), либо его старший брат STM32F415RGT6. В обоих микроконтроллерах частота дискретизации АЦП составляет 2.4 Msps, а частота дискретизации ЦАП — 1 Msps. Следует учитывать, что это максимальные значения, и в даташите есть немало оговорок касаемо того, когда они достигаются. Помимо прочего, частота дискретизации АЦП зависит от напряжения питания, а частота дискретизации ЦАП — от того, насколько сильно изменяется сигнал.
Fun fact! ЦАП в STM32 основаны на R-2R лестнице. Устройство таких ЦАП ранее была описано в заметке Генерация синусоидального сигнала, а следовательно и звука, на FPGA . Используемый же АЦП является так называемым SAR ADC . Его работа основана на ЦАП и бинарном поиске такого его входного значения, чтобы получаемый в итоге сигнал был максимально близок к входному сигналу АЦП.
Проверить работу кода проще всего с помощью потенциометра и светодиода:
А еще можно убрать задержку в loop()
и воспользоваться для теста генератором сигналов и осциллографом :
На первом канале мы видим вход АЦП, а на втором — выход ЦАП. Искажения сигнала объясняются тем, что мы его пробрасываем в цикле, как бы «в лоб», а стоило бы использовать прерывания или DMA. Однако эти темы уже выходят за рамками сего поста. Заинтересованный читателю стоит обратиться к статье Учимся передавать звук с использованием протокола I2S , в рамках которой был написан простой WAV-проигрыватель. Используя подход, аналогичный описанному в той статье, можно записывать звук при помощи АЦП и затем воспроизводить его при помощи ЦАП.
Исходники к посту вы найдете в этом репозитории на GitHub .
Дополнение: В продолжение темы см заметку Микроконтроллеры STM32: переключение каналов АЦП . Пример использования двух ЦАП одновременно вы найдете в статье Рисуем на осциллографе в режиме X-Y .