АЦП в STM32. Часть 2.

Насто время сделать что-то практическое с АЦП, для начала пусть это будет несложный, пример который считывает значение с какого либо канала, а потом исходя из этого значения будем менять частоту мигания тех самых многострадальных светодиодов стоящих на платке STM32VL Discovery. Для данного практического эксперимента нам потребуется сама платка дискавери и плюс переменный резистор номиналом примерно от 1К до 200К. Короче ставьте любой какой найдете, скорее всего он подойдет :) Главное чтоб сопротивление было не слишком уж маленьким, а то он начнет греться, да и стабилизатор на плате может вспотеть от такой нагрузки. Соединить резистор с платой нужно так, как я показал на схеме в прерыдущей статье про регистры АЦП. Кстати, если у вас вообще нет этой платы, то просто посмотрите на ту же схему и подсоедините всю обвязку как там нарисовано. Ну а счастливым обладателям дискавери я нарисовал вот такую простую картинку: 

potentiometer stm32vl discovery

 

Теперь когда железка собрана, тех. задание составлено можно писать код. В этом примере я не буду использовать библиотек поставляемх вместе с CooCox'ом их мы рассмотрим в следующей части цикла статей про АЦП. В данном примере мы будем использовать инжектированные каналы из соображений простоты и наглядности. Для того чтоб прочитать данные с канала ADC1 мы должны сделать следующее: 
 
1) Включить тактирование порта А
2) Настроить ногу PA1 как вход без подтяжки (по умолчанию она уже в таком состоянии)
3) Включить тактирование АЦП
4) Начать калибровку и дождаться её завершения
5) Добавить канал ADC1 в состав инжектированной группы
6) Выбрать источником запуска бит JSWSTART
7) Активировать режим непрерывного преобразования
8) Включить АЦП
9) Запустить преобразование 
10) Дождаться завершения первого преобразования
11) Прочитать результат
 
Конечно выглядит оно жутковато, но если хорошо разобраться то ничего особо страшного нет. Одно могу сказать точно - в AVR с АЦП всё куда проще. Посмотрим на код который я написал (и самое главное на комментарии) 
 
#include<stm32f10x_rcc.h>
#include<stm32f10x_gpio.h>
#include "stm32f10x.h"
void delay(uint32_t i) {
 volatile uint32_t j;
 for (j=0; j!= i * 1000; j++)
  ;
}
 
int main(void)
{
  GPIO_InitTypeDef PORT;
  //Включаем порты А и С 
  RCC_APB2PeriphClockCmd((RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOA) , ENABLE);
  //Настраиваем ноги PC8 и PC9 на выход. Там у нас висят светодиоды
  PORT.GPIO_Pin = (GPIO_Pin_9 | GPIO_Pin_8);
  PORT.GPIO_Mode = GPIO_Mode_Out_PP;
  PORT.GPIO_Speed = GPIO_Speed_2MHz;
  GPIO_Init( GPIOC , &PORT);
  //Порт A настраивать смысла нет, все его ноги по умолчанию входы что нам и нужно
  RCC_APB2PeriphClockCmd(RCC_APB2ENR_ADC1EN, ENABLE); //Включаем тактирование АЦП
  ADC1->CR2 |= ADC_CR2_CAL; //Запуск калибровки АЦП
  while (!(ADC1->CR2 & ADC_CR2_CAL))
    ; //Ожидаем окончания калибровки
  ADC1->SMPR2 |= (ADC_SMPR2_SMP1_2 | ADC_SMPR2_SMP1_1 | ADC_SMPR2_SMP1_0); //Задаем
                                                                                            // длительность выборки
  ADC1->CR2 |= ADC_CR2_JEXTSEL; //Преобразование инжектированной группы
                                                       //запустится установкой бита JSWSTART
  ADC1->CR2 |= ADC_CR2_JEXTTRIG; //Разрешаем внешний запуск инжектированной группы
  ADC1->CR2 |= ADC_CR2_CONT; //Преобразования запускаются одно за другим
  ADC1->CR1 |= ADC_CR1_JAUTO; //Разрешить преобразование инжектированной группы
                                     //после регулярной. Не понятно зачем, но без этого не работает
  ADC1->JSQR |= (1<<15); //Задаем номер канала (выбран ADC1)
  ADC1->CR2 |= ADC_CR2_ADON;//Теперь включаем АЦП
  ADC1->CR2 |= ADC_CR2_JSWSTART; //Запуск преобразований
  while (!(ADC1->SR & ADC_SR_JEOC)) //ждем пока первое преобразование завершится
    ;
  //Теперь можно читать результат из JDR1
  uint32_t adc_res; //Использовал переменную для отладки. Можно и без неё
  while(1)
  {
    adc_res=ADC1->JDR1;
    delay(adc_res); //Исходя из значения АЦП делаем задержку
    GPIO_WriteBit(GPIOC,GPIO_Pin_8,Bit_RESET); //Гасим диод...
    GPIO_WriteBit(GPIOC,GPIO_Pin_9,Bit_SET); // Зажигаем диод...
    adc_res=ADC1->JDR1;
    delay(adc_res); //Всё повторяется...
    GPIO_WriteBit(GPIOC,GPIO_Pin_9,Bit_RESET);
    GPIO_WriteBit(GPIOC,GPIO_Pin_8,Bit_SET);
  }
}

Тут есть один (или не один?) весьма странный и не очевидный момент: Калибровку надо запускать до включения АЦП установкой бита ADC_CR2_ADON. Почему так сделано не совсем понятно. Кстати калибровка должна производиться всякий раз когда АЦП включается. В остальном всё вполне очевидно: Настраиваем порты и АЦП, в бесконечном цикле забираем результат преобразования и передаем его в функцию задержки. Таким образом задержка будет зависеть от напряжения на ноге PA1. Вращая ручку резистора мы меняем напряжение. Попробуем залить эту прошивку и покрутить резистор. Если всё нормально, то светодиоды будут переключаться с разной частотой в зависимости от положения движка переменного резистора. Конечно практической пользы маловато, но в качестве обучающего примера пригодно :) Есть есть вопросы - велкам в комментарии.