Форум программистов, компьютерный форум, киберфорум
Микроконтроллеры ARM, Cortex, STM32
Войти
Регистрация
Восстановить пароль
 
Рейтинг 4.80/5: Рейтинг темы: голосов - 5, средняя оценка - 4.80
2656 / 1581 / 339
Регистрация: 09.09.2017
Сообщений: 6,405
1

АЦП + DMA

01.10.2019, 14:35. Просмотров 1004. Ответов 10

Начал разбираться с АЦП в контроллерах. Естественно, возможность один раз задать список каналов для оцифровки, пнуть модуль и заниматься своими делами, пока DMA гоняет байты, не оставила равнодушным.
Запустить оба этих модуля удалось, даже удалось их связать друг с другом. Но есть несколько непонятных моментов.
Вот код main, там куча самописных макросов для удобства настройки, но функционал у них простой. Сам код работает.
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
void sleep(uint32_t x){
  volatile uint32_t i = x;
  while ( i-- );
}
 
#define RLED B,8,1,GPIO_PP_VS
#define GLED B,9,1,GPIO_PP_VS
#define LBTN C,13,0,GPIO_INP_VS
#define JBTN B,5,0,GPIO_INP_VS
 
#define AIN A,0,0,GPIO_ANA_LS
 
volatile uint16_t adc_buf[30];
 
int main(){
  clock_HS( CLOCK_HSI ); //для АЦП нужен именно HSI
  
  RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN | RCC_AHBENR_GPIOCEN | RCC_AHBENR_GPIODEN;
  
  GPIO_config(RLED);
  GPIO_config(GLED);
  GPIO_config(LBTN);
  GPIO_config(JBTN);
  
  GPIO_config( AIN );
  
//-----------------------------------------------------
//-- ADC --
//-----------------------------------------------------
  RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; //подаем тактирование на АЦП
  ADC->CCR |= ADC_CCR_TSVREFE; //включаем VREF и температурный датчик
  PM_BITMASK( ADC->CCR, ADC_CCR_ADCPRE, ADC_SPEED_1 ); //делитель частоты: 1/1
  
  ADC_SAMPLING_TIME( ADC_1, ADC_CH_VREF, ADC_SAMPL_TIME_384 ); //время выборки 384 такта
  ADC_SEQ_SET( ADC_1, 1, ADC_CH_VREF ); //список каналов для чтения: на 1-й назначаем VREF
  ADC_SEQ_SET( ADC_1, 2, ADC_CH_VREF ); //на 2-й тоже VREF
  ADC_SEQ_SET( ADC_1, 3, ADC_CH_VREF );
  ADC_SEQ_SET( ADC_1, 4, ADC_CH_VREF );
  
  ADC_SAMPLING_TIME( ADC_1, marg3(AIN), ADC_SAMPL_TIME_384 );
  ADC_SEQ_SET( ADC_1, 5, marg3(AIN) ); //на 5-й назначаем АЦП, соответствующее выводу AIN (3-й аргумент)
  ADC_SEQ_SET( ADC_1, 6, marg3(AIN) );
  ADC_SEQ_SET( ADC_1, 7, marg3(AIN) );
  ADC_SEQ_SET( ADC_1, 8, marg3(AIN) );
 
  
  ADC_SEQ_CNT( ADC_1, 8 ); //количество каналов в списке (под макросом скрывается вычитание единицы)
  ADC1->CR1 |= ADC_CR1_SCAN; //включаем автопереключение каналов по списку (без зацикливания)
  ADC1->CR2 |= ADC_CR2_DMA; //включаем DMA
  
  ADC1->CR2 |= ADC_CR2_ADON; //аключаем АЦП
  while(! (ADC1->SR & ADC_SR_ADONS) ){} //и дожидаемся пока оно действительно включится
  
//-----------------------------------------------------
//-----------------------------------------------------  
  RCC->AHBENR |= RCC_AHBENR_DMA1EN; //включаем тактирование
  DMA1->IFCR = 0xFFFFFFFF; //сбрасываем все флаги без разбора
  DMA1_Channel1->CPAR = (uint32_t)(&(ADC1->DR)); //периферия - АЦП
  DMA1_Channel1->CMAR = (uint32_t)(adc_buf); //память - adc_buf
  DMA1_Channel1->CNDTR = 8; //количество посылок - 8
  {
    uint32_t temp = DMA1_Channel1->CCR & 0xFFFF8000;
    //размер данных 16 бит, максимальный приоритет, автоинкремент памяти
    temp |= (DMA_CCR_PL_1 | DMA_CCR_PL_0) | DMA_CCR_PSIZE_0 | DMA_CCR_MSIZE_0 | DMA_CCR_MINC;
    DMA1_Channel1->CCR = temp;
  }
  DMA1_Channel1->CCR |= DMA_CCR_CIRC; //циклический режим (чтобы не инициализировать начало буфера каждый раз)
  DMA1_Channel1->CCR |= DMA_CCR_EN; //разрешаем работу DMA
  
  while(1){
//!!! Обязательно надо сбросить флаг DMA и взвести снова. Зачем? !!!
    ADC1->CR2 &=~ADC_CR2_DMA;
    ADC1->CR2 |= ADC_CR2_DMA;
    
    ADC1->CR2 |= ADC_CR2_SWSTART;
//!!! Если раскомментировать тут - зацикливается !!!
    //while(! (ADC1->SR & ADC_SR_EOC) ){}
    
    while(! (DMA1->ISR & DMA_ISR_TCIF1)){}
    DMA1->IFCR = 0xFFFFFFFF;
    
    if(adc_buf[7] > (1<<11))GPO_ON(GLED); else GPO_OFF(GLED);
    
    GPO_T(RLED);
    sleep(100000);
  }
}
Собственно непонятные места помечены комментариями (они в конце простыни).
Почему окончание преобразование детектируется только по DMA, но не по флагам самого АЦП? Если раскомментировать ожидание флага ADC_SR_EOC, программа на нем и зависает.
Можно ли запустить цикл оцифровки повторно каким-то более простым способом? Потому что сброс флага ADC_CR2_DMA и тут же его установка выглядит как-то костыльно.
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
01.10.2019, 14:35
Ответы с готовыми решениями:

Как связать таймер с АЦП и АЦП с DMA?
Вообщем стоит такая задача: через определенные интервалы времени периодически запускать...

Чтение внешнего АЦП по SPI DMA по таймеру (HAL)
Здравствуйте. Начал изучение STM32 с МК STM32F746. Хотел использовать связку CubeMX + CooCox...

USART2(RX+IDLE+DMA)+USART3(TX DMA)
Принимаю по юсарт2, побайтно, на скрости 115200. Пытаюсь передавать по юсарт3 через DMA, скорость...

STM32F4Discovery - ADC DMA и FSMC DMA
Привет всем. Вынужден опять обратиться за Вашей помощью :) Ситуация такая. 1. Дисплей...

10
Модератор
Эксперт по электронике
8230 / 6097 / 814
Регистрация: 14.02.2011
Сообщений: 21,174
01.10.2019, 16:31 2
Цитата Сообщение от COKPOWEHEU Посмотреть сообщение
Почему окончание преобразование детектируется только по DMA, но не по флагам самого АЦП?
потому что флаги АЦП запускают DMA
1
823 / 501 / 164
Регистрация: 30.07.2015
Сообщений: 1,655
01.10.2019, 17:09 3
COKPOWEHEU, вы не указали серию контроллера, но я думаю у stm32 все имеют бит CONT в регистре CR2 у ADC.
Я обычно делаю связку SCAN + CONT + DMA(circular) + DMA прерывания. Таким образом у нас секция будет оцифровываться и раскладываться в массив, а по окончанию секции она, как и DMA пойдет по второму кругу.
Можно настроить два прерывания DMA: на Half Transfer и На Transfer Complete, тогда будет некое подобие кольцевого буффера, по первому прерыванию забираем первую половину, по второму вторую. Можно одно прерывание сделать только Transfer Complete и в нем переназначить адрес буффера куда писать следующую оцифровку.

Добавлено через 10 минут
Действительно флаг EOC триггерит DMA, при этом флаг EOC очищается при чтении из DR, таким образом DMA само чистит этот флаг хардварно и вы его не отловите.
1
2656 / 1581 / 339
Регистрация: 09.09.2017
Сообщений: 6,405
01.10.2019, 17:16  [ТС] 4
Цитата Сообщение от _SayHello Посмотреть сообщение
COKPOWEHEU, вы не указали серию контроллера
Ох блин точно. Причем в тегах указал stm32l151, а в заголовке забыл.
Цитата Сообщение от _SayHello Посмотреть сообщение
Я обычно делаю связку SCAN + CONT + DMA(circular) + DMA прерывания. Таким образом у нас секция будет оцифровываться и раскладываться в массив, а по окончанию секции она, как и DMA пойдет по второму кругу.
В моем случае нужен именно ручной запуск и одиночный проход по каналам. Если интересно, предполагается опрос нескольких фотодиодов при кратковременной засветке. То есть включили свет, сняли значения, выключили, уснули.
Цитата Сообщение от ValeryS Посмотреть сообщение
потому что флаги АЦП запускают DMA
Вроде бы немного не так: в моей конфигурации флаг ADC_SR_EOC означает конец всей серии оцифровок (а не каждой отдельной), значит пинать DMA на предмет очередной порции не может. Да и потом, не снимает же DMA флаги у АЦП.

Добавлено через 1 минуту
Цитата Сообщение от _SayHello Посмотреть сообщение
Действительно флаг EOC триггерит DMA, при этом флаг EOC очищается при чтении из DR, таким образом DMA само чистит этот флаг хардварно и вы его не отловите.
триггерить не триггерит, а вот на счет пускания флага наверное вы (и ValeryS, конечно) правы.
0
823 / 501 / 164
Регистрация: 30.07.2015
Сообщений: 1,655
01.10.2019, 17:26 5
COKPOWEHEU,
АЦП + DMA

Как раз EOC триггерит DMA так как DR единственный на все измерения, DMA должен забрать данные пока они не перетерлись следующим каналом.
Цитата Сообщение от COKPOWEHEU Посмотреть сообщение
В моем случае нужен именно ручной запуск и одиночный проход по каналам. Если интересно, предполагается опрос нескольких фотодиодов при кратковременной засветке. То есть включили свет, сняли значения, выключили, уснули.
Тогда и смысл DMA? Если вы все равно все включаете вручную и ждете флагов (читай , блочите поток)?
Я бы сделал так: АЦП можно настроить на запуск по внешнему триггеру - включение света. Настроили секцию АЦП, которую будете оцифровывать (без CONT), настроили DMA на circular mode в буффер размером по количеству каналов с прерывание по окончинию. В прерывании выставляете флаг который опрашиваете в основном цикле.
Свет включился - дернул АЦП он оцифровал секцию - DMA все разложил и дернул флаг. В это время ядро спокойно крутится в основном цикле опрашивая флаг.
0
Модератор
Эксперт по электронике
8230 / 6097 / 814
Регистрация: 14.02.2011
Сообщений: 21,174
01.10.2019, 17:28 6
Цитата Сообщение от COKPOWEHEU Посмотреть сообщение
stm32l151
насчет элек не знаю, а вот у F103 с которыми я работаю, есть два режима DMA циклический, когда он постоянно долбит по кругу, и однократный записал массив и остановился, как раз как ты описывал
Цитата Сообщение от COKPOWEHEU Посмотреть сообщение
Если интересно, предполагается опрос нескольких фотодиодов при кратковременной засветке. То есть включили свет, сняли значения, выключили, уснули.
наверно и у l151 есть что то подобное
0
823 / 501 / 164
Регистрация: 30.07.2015
Сообщений: 1,655
01.10.2019, 17:30 7
Триггеры есть разные, можно от внутренних таймеров, можно от EXTI.
0
2656 / 1581 / 339
Регистрация: 09.09.2017
Сообщений: 6,405
02.10.2019, 10:10  [ТС] 8
Цитата Сообщение от _SayHello Посмотреть сообщение
Тогда и смысл DMA? Если вы все равно все включаете вручную и ждете флагов (читай , блочите поток)?
В том, что не нужно дергать флаги ручками. Ну и возможность что-то еще посчитать пока АЦП крутится.
Цитата Сообщение от ValeryS Посмотреть сообщение
есть два режима DMA циклический, когда он постоянно долбит по кругу, и однократный записал массив и остановился, как раз как ты описывал
Мой код именно так и работает. Я ж не спрашивал как сделать в принципе, меня только два частных вопроса интересовало: куда теряется EOC (уже ответили что его DMA съедает) и можно ли не дергать связку DMA+ADC, а только пнуть кого-то одного чтобы запустился по-новой.
0
823 / 501 / 164
Регистрация: 30.07.2015
Сообщений: 1,655
02.10.2019, 11:42 9
COKPOWEHEU,
Цитата Сообщение от COKPOWEHEU Посмотреть сообщение
В том, что не нужно дергать флаги ручками. Ну и возможность что-то еще посчитать пока АЦП крутится.
Да, это понятно. Меня смутила фраза
Цитата Сообщение от COKPOWEHEU Посмотреть сообщение
В моем случае нужен именно ручной запуск и одиночный проход по каналам.
Я подумал вы хотите каждый канал вручную дергать и ждать EOC и при этом DMA использовать.
Цитата Сообщение от COKPOWEHEU Посмотреть сообщение
и можно ли не дергать связку DMA+ADC
Вообще надо искать ошибки инициализации, ибо бит ADC_CR2_DMA выставляется один раз при инициализации связки и больше не должен трогаться.

Могу накинуть пример как у меня связка работает:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
static uint8_t analog_Init(void)
{
    uint8_t status = 0;
 
    RCC->APB2ENR |= (RCC_APB2ENR_IOPAEN | RCC_APB2ENR_ADC1EN);
    RCC->CFGR &= ~(RCC_CFGR_ADCPRE);
    RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6;
 
    GPIOA->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_CNF1 | GPIO_CRL_CNF2 | GPIO_CRL_CNF3 | GPIO_CRL_CNF4 );
    GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_MODE1 | GPIO_CRL_MODE2 | GPIO_CRL_MODE3 | GPIO_CRL_MODE4 );
 
    ADC1->CR2 |= ADC_CR2_ADON;
    ADC1->CR1 |= ADC_CR1_SCAN;
    ADC1->CR2 |= ADC_CR2_EXTTRIG;
    ADC1->CR2 |= ADC_CR2_EXTSEL;
    ADC1->CR2 |= ADC_CR2_DMA;
 
    ADC1->SMPR2 |= (ADC_SMPR2_SMP0 | ADC_SMPR2_SMP1 | ADC_SMPR2_SMP2 | ADC_SMPR2_SMP3 | ADC_SMPR2_SMP4);
    ADC1->SQR1 |= (ADC_SQR1_L_2);                       // 0b0100 - 5 каналов
    ADC1->SQR3 &= ~(ADC_SQR3_SQ1);                      // 0b0000 - Канал 0
    ADC1->SQR3 |= (ADC_SQR3_SQ2_0);                     // 0b0001 - Канал 1
    ADC1->SQR3 |= (ADC_SQR3_SQ3_1);                     // 0b0010 - Канал 2
    ADC1->SQR3 |= (ADC_SQR3_SQ4_0 | ADC_SQR3_SQ4_1);    // 0b0011 - Канал 3
    ADC1->SQR3 |= (ADC_SQR3_SQ5_2);                     // 0b0100 - Канал 4
 
    ADC1->CR2 |= ADC_CR2_CAL;
    while(ADC1->CR2 & ADC_CR2_CAL){};
 
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
 
    DMA1_Channel1->CCR      |=  (DMA_CCR1_PL | DMA_CCR1_MSIZE_0 | DMA_CCR1_PSIZE_0 | DMA_CCR1_MINC | DMA_CCR1_TCIE | DMA_CCR1_CIRC);
    DMA1_Channel1->CNDTR    =   ANALOG_CH_NUM;
    DMA1_Channel1->CPAR     =   (uint32_t)&ADC1->DR;
    DMA1_Channel1->CMAR     =   (uint32_t)analog_DMABuffer;
 
    NVIC_SetPriority(DMA1_Channel1_IRQn, 2);
    NVIC_EnableIRQ(DMA1_Channel1_IRQn);
    DMA1_Channel1->CCR      |=   DMA_CCR1_EN;
 
    analog_Semaphore = xSemaphoreCreateBinary();
    if(analog_Semaphore == NULL)
        return status;
    return 1;
}
Окончание измерений
C
1
2
3
4
5
6
7
8
void DMA1_Channel1_IRQHandler(void)
{
    if(DMA1->ISR & DMA_ISR_TCIF1)
    {
        DMA1->IFCR |= DMA_IFCR_CTCIF1;
        xSemaphoreGiveFromISR(analog_Semaphore, NULL);
    }
}
//Таск обработки
C
1
2
3
4
5
6
7
8
9
10
11
12
13
void vAnalogTask(void *pvParameters)
{
    analog_Init();                               //Инициализация
    analog_StartConversion();             //Стартуем измерение
    TickType_t lastTick = xTaskGetTickCount();
    while(1)
    {
        xSemaphoreTake(analog_Semaphore, portMAX_DELAY);        //Ждем окончания измерений
        analog_Averaging();                                                           //Какая то обработка
        vTaskDelayUntil(&lastTick, ANALOG_MEAS_PERIOD);            //Ждем следующего периода измерений
        analog_StartConversion();                                                  //Запускаем снова  
    }
}
Запуск, без всяких флагов
C
1
2
3
4
static void analog_StartConversion(void)
{
    ADC1->CR2 |= ADC_CR2_SWSTART;
}
Пример достаточно тривиальный, но рабочий. Как видите никаких флагов тут не надо дергать, измеряется секция после триггера ADC_CR2_SWSTART
0
823 / 501 / 164
Регистрация: 30.07.2015
Сообщений: 1,655
02.10.2019, 11:56 10
COKPOWEHEU, На самом деле, заглянул в RM на L151 и понял, что достаточно сильные отличия от F1 серии, так что не все так однозначно.
Кажется нашел в чем может быть проблема:
АЦП + DMA

Посмотрите бит DDS в регистре CR2.
АЦП + DMA

Насколько я понял, если он сброшен, то надо передергивать связку, после N транзакций DMA которые вы настроили, если установлен то вроде не надо. Действительно особенность этой линейки видимо.
1
2656 / 1581 / 339
Регистрация: 09.09.2017
Сообщений: 6,405
02.10.2019, 13:18  [ТС] 11
Цитата Сообщение от _SayHello Посмотреть сообщение
Посмотрите бит DDS в регистре CR2.
Пробовал его ставить, но никакого эффекта не увидел. Да толком и не понял для чего он нужен. Разве что если ядро сильно медленное и не успевает за АЦП... сейчас проверю.

Добавлено через 10 минут
Нет, от слишком низкой скорости он тоже не помогает
Проверял на делителе АЦП 1:1 (по идее, 16 МГц тактовая, то есть ~1M sps. Хотя время выборки максимальное, тогда наверное 40k sps) и тактировании ядра от MSI (2 МГц)
Что интересно, если каналов меньше 18, то проблем не возникает. И от времени выборки это не зависит... совсем странно.

Добавлено через 3 минуты
Цитата Сообщение от _SayHello Посмотреть сообщение
Насколько я понял, если он сброшен, то надо передергивать связку, после N транзакций DMA которые вы настроили, если установлен то вроде не надо. Действительно особенность этой линейки видимо.
А, вот оно как. Проверил - действительно работает. Спасибо.
0
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
02.10.2019, 13:18

Заказываю контрольные, курсовые, дипломные и любые другие студенческие работы здесь.

ADC->DMA->SDIO (или NAND через FSMC) без остановки в обработчике прерываний DMA на STM32F407VG, реально или нет?
Добрый день. Столкнулся с необходимостью писать большой объём данных АЦП с высокой скоростью....

Алгоритм работы с ацп АЦП STM32F103
Здравствуйте, уважаемые форумчане. Подскажите пожалуйста алгоритм работы с ацп. Допустим мне надо...

ADC +DMA
может кому то понадобится буфер приема данных необходимо выравнивать по 32х битному типу

USART + DMA
Всем привет. stm32f103c8 Использую USORT + DMA, Channel4 - TX Channel5 - RX Как только...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
11
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2020, vBulletin Solutions, Inc.