Прямой доступ к памяти (DMA)
DMA — это способ обмена данными между подсистемами микроконтроллера, без участия центрального процессора. Такой способ позволяет снять избыточную нагрузку с ЦП и увеличить скорость передачи данных.
Напишем программу, которая будет анализировать сигнал на контакте PA0, и в зависимости от полученной величины зажигать или гасить штатный светодиод на контакте PC13. АЦП будет осуществлять преобразования каждые 20мс в фоновом режиме, а результат автоматически передавать в особую переменную посредством DMA.
Настройка АЦП
Нажимаем правую кнопку мыши на PA0 и выбираем пункт ADC1_IN0. Выбор этого пункта означает, что мы подключаем данный контакт к каналу №0 аппаратного АЦП №1. Теперь к PA0 можно подключить нужный нам прибор с аналоговым выходом, с которого мы в дальнейшем будем снимать данные.

Далее настроим событие, по которому АЦП будет начинать преобразование. Пусть это будет происходить каждый раз, когда на первом канале таймера №1 возникает событие Capture Compare.
Заходим на вкладку Parameter Settings, и в поле External Trigger Conversion Source выбираем пункт Timer 1 Capture Compare 1 event.

Выбираем пункт ADC1 в левом меню. Открываем вкладку настроек DMA Settings. Жмём кнопку ADD, добавляя тем самым новую инструкцию для передачи данных из АЦП в режиме DMA.
В появившихся полях выбираем режим (Mode) Circular. Размер данных (Data Width) менять не нужно. АЦП у нас 12-разрядное, а значит Half Word (16-разрядов) нам вполне хватит.

С АЦП разделались, переходим к таймеру.
Таймер
Настроим генерацию событий Capture Compare на первом канале таймера №1.
В левом меню жмём на TIM1. В поле Channel1 выбираем Output Compare No Output. Это означает, что таймер будет генерировать нужные нам события, ни на какие физические контакты этот сигнал выдавать не будет.
В поле Clock Source выбираем Internal Clock.
По умолчанию, на в среде CubeIDE тактирование микроконтроллера stm32f103c8t6 осуществляется на частоте 8 МГц, без внешнего резонатора. Так и оставим, высокие скорости нам не требуются.
Пусть таймер будет генерировать событие каждые 20мс (50 Гц). Выставляем предделитель на 800-1, а период счетчика на 200-1. Это и даст нам нужную частоту.

Наконец, там же в параметрах таймера, находим блок Output Compare No Output и выбираем режим (Mode) Toggle on match.

Индикатор
Настроим штатный индикатор, который на отладочных платах обычно привязан к контакту PC13.
Жмём на контакт PC13 и выбираем режим GPIO_Output. Теперь этот контакт будет работать на выход, и мы сможем выставлять на нем логический уровень. Затем в левом меню открываем раздел GPIO, прописываем этому контакту псевдоним LED в поле User Label.

Сохраняем проект и переходим к исходному коду.
Программа
После генерации шаблона, приступаем к написанию своего кода, строго в выделенных для этого разделах.
Будем зажигать светодиод при достижении значения 2100 на потенциометре, и выключать его при снижении до 2000. Разница в 100 единиц сделана для создания гистерезиса — чтобы исключить мерцание светодиода в пограничном состоянии потенциометра.
Чтобы запустить преобразования АЦП с передачей результата в память при помощи DMA, в блоке кода USER CODE 2 пишем нужные вызовы:
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1); // калибровка АЦП
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&pot_adc, 1); // запуск DMA
HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1); // запуск таймера для опроса АЦП
DMA будет записывать показания потенциометра в переменную pot_adc, которую, неплохо бы, тоже объявить в соответствующем блоке кода. Там же объявим переменную flag_adc, отметив её признаком volatile. Она пригодится нам далее.
/* USER CODE BEGIN PV */
uint16_t pot_adc;
volatile uint8_t flag_adc = 0;
Далее, нам потребуется переопределить функцию-обработчик прерывания HAL_ADC_ConvCpltCallback. Эта функция будет вызываться каждый раз, когда АЦП порождает новые данные.
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
if(hadc->Instance == ADC1){
flag_adc = 1;
}
}
/* USER CODE END 4 */
Как мы уже говорили в уроке про прерывания на stm32, в теле обработчика недопустимы сложные вычисления. Мы лишь изменим значение переменной flag_adc на 1.
Далее, отредактируем суперцикл в соответствующем блоке. При использовании переменной pot_adc в каких либо вычислениях, необходимо на время выключить режим DMA. Иначе может возникнуть ситуация, когда переменная изменится прямо в процессе вычисления.
/* USER CODE BEGIN WHILE */
while (1){
if( flag_adc ){
flag_adc = 0;
HAL_ADC_Stop_DMA(&hadc1);
if( pot_adc > 2100 ){
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
} else
if( pot_adc < 2000 ){
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
}
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&pot_adc, 1);
}
/* USER CODE END WHILE */
Готово. Загружаем программу на контроллер. Подключаем потенциометр к контакту PA0. Крутим ручку и смотрим за светодиодом.

нет ли в этом уроке ошибки? функции, которые в нем используются, ведь не были в начале обьявлены?
а при компиляции дает ошибку:
<<undefined reference to `HAL_ADCEx_Calibration_Start'
а перед этим еще и предупреждение было:
<<implicit declaration of function 'HAL_ADCEx_Calibration_Start'; did you mean 'HAL_ADCEx_calibration_Start'? [-Wimplicit-function-declaration]
Эти функции должны были быть объявлены в коде, сгенерированном средой. То есть в редакторе CubeMX включаем ADC, сохраняем проект, и после этого генерируется весь нужный для ADC код.
Надо знать последовательность ваших действий, чтобы понять что пошло не так.