Мар 032012
 

DMA (Direct Memory Access) – Прямой Доступ к Памяти (ПДП). Эта функция используется для быстрой пересылки данных, при этом ядро микроконтроллера не загружено соответствующими операциями, его ресурсы можно использовать для других задач. Передача данных (транзакции) между периферийными модулями и/или памятью идет абсолютно независимо от ядра микроконтроллера, этой задачей “рулит” специальный блок – контроллер DMA.

И все же иногда они (ядро и контроллер DMA) “пересекаются” друг с другом. Это происходит в случае, когда контроллер DMA и ЦПУ обращаются к одному устройству. Но и тут, максимум что позволено DMA – это “50% ресурсов” (цитирую документацию). Или же, другими словами, шину позволено занять на несколько тактов системной частоты этой шины, а затем освободить ее.

Теоретическая часть.

Итак, какие возможности предоставляет нам использование DMA?

Поскольку и источник данных и получатель можно задать абсолютными адресами в пространстве памяти, то возможны следующие варианты передачи данных:

  • Периферия – Память
  • Память – Периферия
  • Периферия – Периферия
  • Память – Память (этот режим имеет отличия от остальных, о чем будет ниже)

В режиме DMA можно получить доступ к FLASH, SRAM, периферийным устройствам на шинах APB1, APB2, AHB. Рассмотрим следующий рисунок

DMA request mapping

Как видим, каждое из периферийных устройств прикреплено к определенному каналу DMA. Периферийное устройство должно сформировать запрос по какому-то событию. Например, АЦП по завершению преобразования формирует запрос для пересылки результата в выделенный массив памяти. Запрос будет “висеть” до тех пор, пока контроллер DMA не подтвердит готовность канала. Каждому запросу можно установить уровень приоритета. Уровней всего 4:

  • Очень высокий(Very High)
  • Высокий (High)
  • Средний (Medium)
  • Низкий (Low)

Эти уровни для периферийных устройств задаются программно. В случае одновременного появления нескольких запросов с одинаковыми уровнями приоритетов (заданных программно), преимущество будет у запроса с меньшим номером, например, запрос канала 1 будет главнее запроса канала 3 и т.д.

Данные могут иметь разный размер (8, 16 или 32 бит). Причем для отправителя и получателя размерность задается отдельно. При этом, соответственно, и адресация элементов массива у отправителя/получателя будет привязана к заданному размеру данных.

Максимальное количество передаваемых данных (размер массива) может быть до 65536.

Каждый канал может сформировать 3 события:

  • Произошла передача половины данных
  • Завершилась передача всех данных
  • Ошибка передачи

По каждому из этих событий формируется соответствующий запрос прерывания.

DMA_interrupt_requests

Процедура конфигурации канала DMA.

Итак, первоначально надо определиться с источником и получателем данных, откуда и куда будем пересылать. Фактически все задается прямыми адресами в пространстве памяти, для этого предназначены два регистра: DMA_CPARx (базовый адрес периферии) и DMA_CMARx (базовый адрес памяти). В этих регистрах заданы начальные адреса, они остаются неизменными.

Затем надо определиться с общим количеством данных. Допустим, мы проводим измерения с помощью АЦП, хотим провести N измерений, а затем усреднить. N раз нам надо передать результат измерений АЦП в память. Вот это количество транзакций (N) задается через регистр DMA_CNTDRx – по сути это счетчик с начальным значением, которое декрементируется на 1 после каждой пересылки данных.

Еще необходимо задать следующие параметры:

  • Установка уровней приоритета каналов. Тут уж надо самостоятельно определиться что важней при одновременном появлении нескольких запросов, если предвидится использование различных источников/получателей.
  • Направление передачи данных. Кто у нас будет отправителем данных, периферия или память.
  • Использование циклического режима. В этом режиме, как только счетчик DMA_CNTDRx обнулится (произошло N пересылок данных, как мы и задали в этом счетчике), он снова автоматически перезагрузится числом N, и все пойдет сначала. Опять с тех же начальных адресов памяти и периферии, которые мы задали. То есть предыдущие значения массива данных получателя начнут безжалостно затираться.
  • Размерность данных периферии/памяти. Задаются отдельно, и могут быть разными (8, 16, 32 бита). Если размерность данных задана разная для отправителя и получателя, контроллер DMA при операциях пересылки выполняет выравнивание данных.
  • Режима инкремента адресов периферии, памяти. Базовые (начальные адреса) мы уже задали через регистры DMA_CPARx и DMA_CMARx. Эти значения остаются неизменными при всей последовательности транзакций. А вот текущие адреса, по которым будет происходить чтение/запись после окончания каждой пересылки и перед началом следующей можно инкрементировать. Величина инкремента адреса вычисляется автоматически, в зависимости от того, какую размерность данных мы установили для источника/получателя.
  • Использование режима “Память – Память”. В этом режиме для пересылки данных не нужен запрос от периферии, для этого достаточно установить бит MEM2MEM в регистре DMA_CCRx (где x = 1..7 – номер канала). Этот режим не может быть одновременно использован с циклическим режимом.
  • Разрешение прерываний

Все эти настройки производятся через один регистр – DMA_CCRx (x – номер канала). И, напоследок, после всех настроек, канал DMA можно активировать установкой бита EN в том же регистре.

Есть одна особенность. Шина AHB может работать только с 32-разрядными данными. Поэтому при передаче 8 или 16-разрядных данных к устройствам шины AHB, эти значения дублируются в неиспользуемых разрядах. Например, если мы хотим передать значение 0xAB, контроллер DMA выставит на шину значение 0xABABABAB. Или же, при передаче значения 0xABCD, будет передано 0xABCDABCD. Подробней об этом можно посмотреть Reference Manual (RM0038)  в разделе “DMA controller”. Там же есть таблица, в которой собраны все возможные варианты разрядности данных источника и получателя, здесь же показано какой порядок следования байт (big-endian или little-endian) будет при пересылке данных.

Практическая часть.

Для примера был выбран следующий алгоритм. АЦП производит 10 измерений подряд. При этом поочередно измеряются значения в двух каналах ADC_IN1 и ADC_IN2 (выводы PA1 и PA2), по 5 измерений на каждый канал. После каждого измерения результат пересылается в память RAM через канал DMA. В итоге, в памяти сохраняется массив из 10 значений. Для проверки примера была по-быстрому собрана вот такая схема:

Схема

Итак, вот код программы:

#include "stm32l1xx.h"

#define ADC1_DR_ADDRESS  0x40012458 //Адрес регистра данных регулярных каналов АЦП в пространстве памяти. 
#define MEMORY_ARRAY_ADDRESS 0x20000000 //Начальный адрес RAM в пространстве памяти. 

void adc_init(void); //Объявление функции инициализации АЦП
void dma_init(void); //Объявление функции инициализации DMA

int main()
{
  adc_init(); //Вызов функции инициализации АЦП
  dma_init(); //Вызов функции инициализации DMA
  ADC1->CR2 |= ADC_CR2_SWSTART; //Запуск преобразования
  while(1);
}

void adc_init(void)
{
  //Настройка порта
  RCC->AHBENR |= RCC_AHBENR_GPIOAEN; //Разрешаем тактирование порта А
  GPIOA->MODER |= GPIO_MODER_MODER1;  //PA1 - аналоговый вход
  GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR1; //PA1 - PullUp/PullDown отключен
  
  GPIOA->MODER |= GPIO_MODER_MODER2;  //PA2 - аналоговый вход
  GPIOA->PUPDR |= GPIO_PUPDR_PUPDR2; //PA2 - PullUp/PullDown отключен
  
  //Настройка АЦП
  RCC->CR |= RCC_CR_HSION; //Включаем внутренний генератор HSI - 16МГц
  while(!(RCC->CR&RCC_CR_HSIRDY)); //Ждем его стабилизации
  RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; //Разрешаем тактирование АЦП
  
  ADC1->SQR1 |= (ADC_SQR1_L_3 | ADC_SQR1_L_0); //Число преобразований в последовательности равно 10. 
  ADC1->SQR5 |= ADC_SQR5_SQ1_0; //Устанавливаем бит 0. Выбираем канал ADC_IN1 для 1-го преобразования.
  ADC1->SQR5 |= ADC_SQR5_SQ2_1; //Второе преобразование - канал ADC_IN2
  ADC1->SQR5 |= ADC_SQR5_SQ3_0; //Третье преобразование - канал ADC_IN1
  ADC1->SQR5 |= ADC_SQR5_SQ4_1; //Четвертое преобразование - канал ADC_IN2
  ADC1->SQR5 |= ADC_SQR5_SQ5_0; //Пятое преобразование - канал ADC_IN1
  ADC1->SQR5 |= ADC_SQR5_SQ6_1; //Шестое преобразование - канал ADC_IN2
  ADC1->SQR4 |= ADC_SQR4_SQ7_0; //Седьмое преобразование - канал ADC_IN1
  ADC1->SQR4 |= ADC_SQR4_SQ8_1; //Восьмое преобразование - канал ADC_IN2
  ADC1->SQR4 |= ADC_SQR4_SQ9_0; //Девятое преобразование - канал ADC_IN1
  ADC1->SQR4 |= ADC_SQR4_SQ10_1; //Десятое преобразование - канал ADC_IN2
  
  ADC1->CR1 |= ADC_CR1_SCAN; //Режим сканирования. Новое преобразование запускается автоматически после завершения предыдущего.
  ADC1->CR2 |= ADC_CR2_DELS_0; //Вводим задержку между преобразованиями
  ADC1->CR1 &= ~ADC_CR1_RES; //Разрядность АЦП - 12 бит
  ADC1->CR2 &= ~ADC_CR2_ALIGN; //Выравнивание результата вправо
  ADC1->CR2 |= ADC_CR2_DMA; //Разрешаем использование режима DMA. АЦП будет формировать запрос DMA после каждого преобразования.
  
  ADC1->CR2 |= ADC_CR2_ADON; //Включаем АЦП
  while(!(ADC1->SR&ADC_SR_ADONS)); //Ждем готовности АЦП 
}

void dma_init(void)
{
  //Настройка DMA
  RCC->AHBENR |= RCC_AHBENR_DMA1EN; //Разрешаем тактирование DMA
  DMA1_Channel1->CPAR |= ADC1_DR_ADDRESS; //Задаем адрес периферии - регистр результата преобразования АЦП для регулярных каналов. 
  DMA1_Channel1->CMAR |= MEMORY_ARRAY_ADDRESS; //Задаем адрес памяти - базовый адрес массива в RAM.
  DMA1_Channel1->CCR &= ~DMA_CCR1_DIR; //Направление передачи данных - чтение из периферии, запись в память. 
  DMA1_Channel1->CNDTR = 10; //Количество пересылаемых значений
  DMA1_Channel1->CCR &= ~DMA_CCR1_PINC; //Адрес периферии не инкрементируется после каждой пересылки. 
  DMA1_Channel1->CCR |= DMA_CCR1_MINC; //Адрес памяти инкрементируется после каждой пересылки. 
  DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0; //Размерность данных периферии - 16 бит.
  DMA1_Channel1->CCR |= DMA_CCR1_MSIZE_0; //Размерность данных памяти - 16 бит
  DMA1_Channel1->CCR |= DMA_CCR1_PL; //Приоритет - очень высокий (Very High)
  DMA1_Channel1->CCR |= DMA_CCR1_EN; //Разрешаем работу канала 1 DMA  
}

Стандартные библиотеки я снова здесь не использовал. В принципе, когда уже есть понимание работы контроллера DMA, использовать стандартную библиотеку будет очень просто. А пока рассмотрим всю процедуру инициализации на уровне регистров, мне кажется, что так проще разобраться с работой модуля. Хотя, опять же, это личное предпочтение. Сам лично использую библиотеки, но изучать предпочитаю с углублением в регистры.

Итак, вначале подключаем файл stm32l1xx.h. В нем заданы (через макросы define) практически все регистры и их разряды, которые нам будут нужны. Далее задаем адреса источника и получателя данных: ADC1_DR_ADDRESS (это у нас физический адрес регистра данных регулярных каналов АЦП) и MEMORY_ARRAY_ADDRESS (это у нас начальный адрес массива в памяти, куда мы будем складывать данные. Здесь выбран начальный адрес RAM в пространстве памяти). Далее объявляем функции инициализации АЦП и DMA, затем начинается основная функция main(), из которой первым делом мы и вызываем эти функции инициализации. Сперва попадаем в функцию инициализации АЦП, в ней же вначале настраиваем нужные выводы портов:

RCC->AHBENR |= RCC_AHBENR_GPIOAEN; //Разрешаем тактирование порта А 
GPIOA->MODER |= GPIO_MODER_MODER1; //PA1 - аналоговый вход 
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR1; //PA1 - PullUp/PullDown отключен 
GPIOA->MODER |= GPIO_MODER_MODER2; //PA2 - аналоговый вход 
GPIOA->PUPDR |= GPIO_PUPDR_PUPDR2; //PA2 - PullUp/PullDown отключен


Затем начинаем настройку АЦП. Вначале запускаем внутренний тактовый генератор HSI (16 МГц), поскольку АЦП тактируется только от него. Ждем его стабилизации и разрешаем тактирование АЦП:

RCC->CR |= RCC_CR_HSION; //Включаем внутренний генератор HSI - 16МГц 
while(!(RCC->CR&RCC_CR_HSIRDY)); //Ждем его стабилизации 
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; //Разрешаем тактирование АЦП


Далее задаем длину последовательности преобразований равной 10. Здесь в нужные разряды записывается число 9 а не 10. Поскольку нельзя выполнить 0 преобразований в последовательности, поэтому при нулевом значении в разрядах выполнится 1 преобразование, при значении равном 1 выполнится 2 преобразования и т.д.:

 ADC1->SQR1 |= (ADC_SQR1_L_3 | ADC_SQR1_L_0);

Смотрите файл stm32l1xx.h, все эти имена разрядов и регистров заданы именно там!!!

А теперь четко для каждого номера преобразования в последовательности задаем канал АЦП, в котором будет производиться измерение. Следующими командами задается такой порядок: первый канал АЦП, второй канал АЦП, первый канал, второй, первый, второй… и т.д., всего надо прописать номера каналов для первых 10 измерений.

ADC1->SQR5 |= ADC_SQR5_SQ1_0; //Первое преобразование - канал ADC_IN1 
ADC1->SQR5 |= ADC_SQR5_SQ2_1; //Второе преобразование - канал ADC_IN2 
ADC1->SQR5 |= ADC_SQR5_SQ3_0; //Третье преобразование - канал ADC_IN1 
ADC1->SQR5 |= ADC_SQR5_SQ4_1; //Четвертое преобразование - канал ADC_IN2 
ADC1->SQR5 |= ADC_SQR5_SQ5_0; //Пятое преобразование - канал ADC_IN1 
ADC1->SQR5 |= ADC_SQR5_SQ6_1; //Шестое преобразование - канал ADC_IN2 
ADC1->SQR4 |= ADC_SQR4_SQ7_0; //Седьмое преобразование - канал ADC_IN1 
ADC1->SQR4 |= ADC_SQR4_SQ8_1; //Восьмое преобразование - канал ADC_IN2 
ADC1->SQR4 |= ADC_SQR4_SQ9_0; //Девятое преобразование - канал ADC_IN1 
ADC1->SQR4 |= ADC_SQR4_SQ10_1; //Десятое преобразование - канал ADC_IN2


Далее идут такие команды, тут в комментариях все понятно

ADC1->CR1 |= ADC_CR1_SCAN; //Режим сканирования. Новое преобразование запускается автоматически после завершения предыдущего. 
ADC1->CR2 |= ADC_CR2_DELS_0; //Вводим задержку между преобразованиями 
ADC1->CR1 &= ~ADC_CR1_RES; //Разрядность АЦП - 12 бит 
ADC1->CR2 &= ~ADC_CR2_ALIGN; //Выравнивание результата вправо 
ADC1->CR2 |= ADC_CR2_DMA; //Разрешаем использование режима DMA. АЦП будет формировать запрос DMA после каждого преобразования.

Задержку между преобразованиями пришлось ввести, потому что после первого преобразования “выскакивал” бит OVR, что говорило о потере результата преобразования. То есть канал DMA не успевал считывать результат первого преобразования, а уже прошло второе. Можно было попробовать уменьшить частоту тактирования АЦП делителем, но я использовал другой вариант. Между преобразованиями ввел задержку, длительность ее задается битами DELS, там много вариаций. Именно здесь в эти биты записано число 0x001, при таком значении задержка не будет задана конкретным числом тактов, а АЦП просто “зависнет” между преобразованиями до тех пор, пока регистр данных не будет прочитан. То есть, я тут ушел от необходимости считать необходимую величину задержки.

А теперь включаем АЦП и ждем его готовности.

ADC1->CR2 |= ADC_CR2_ADON; //Включаем АЦП 
while(!(ADC1->SR&ADC_SR_ADONS)); //Ждем готовности АЦП


Переходим к инициализации DMA. Первым делом, естественно, подключаем канал DMA к источнику тактирования:

 RCC->AHBENR |= RCC_AHBENR_DMA1EN; //Разрешаем тактирование DMA


Затем задаем адреса источника и получателя данных:

DMA1_Channel1->CPAR |= ADC1_DR_ADDRESS; //Задаем адрес периферии - регистр результата преобразования АЦП для регулярных каналов.
DMA1_Channel1->CMAR |= MEMORY_ARRAY_ADDRESS; //Задаем адрес памяти - базовый адрес массива в RAM.


Далее, направление передачи данных “Периферия – Память”, и количество передаваемых данных – 10.

DMA1_Channel1->CCR &= ~DMA_CCR1_DIR; //Направление передачи данных - чтение из периферии, запись в память. 
DMA1_Channel1->CNDTR = 10; //Количество пересылаемых значений


А затем задаем режим инкремента адреса периферии/памяти после каждой транзакции. Адрес периферии нам инкрементировать не нужно, поскольку это у нас всегда регистр данных регулярных каналов АЦП. А адрес памяти, наоборот, нужно каждый раз инкрементировать, поскольку мы хотим получить массив значений для 10 измерений.

DMA1_Channel1->CCR &= ~DMA_CCR1_PINC; //Адрес периферии не инкрементируется после каждой пересылки. 
DMA1_Channel1->CCR |= DMA_CCR1_MINC; //Адрес памяти инкрементируется после каждой пересылки.


Далее задаем одинаковую размерность данных (16 бит) для периферии и памяти, уровень приоритета (я взял наивысший – Very High), и “включаем” первый канал DMA.

DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0; //Размерность данных периферии - 16 бит. 
DMA1_Channel1->CCR |= DMA_CCR1_MSIZE_0; //Размерность данных памяти - 16 бит 
DMA1_Channel1->CCR |= DMA_CCR1_PL; //Приоритет - очень высокий (Very High) 
DMA1_Channel1->CCR |= DMA_CCR1_EN; //Разрешаем работу канала 1 DMA


После инициализации АЦП и DMA в функции main() дается команда на запуск измерений для заданной последовательности.

ADC1->CR2 |= ADC_CR2_SWSTART; //Запуск преобразования

При отладке в IAR результат работы связки ADC –> DMA можно посмотреть, открыв окно memory.

Save memory

Как видим, 10 первых значений RAM содержат чередующиеся значения 0x0800 и 0x0400 (с небольшим разбросом). Пересчитаем значения, при максимальном уровне входного напряжения получим величину 0x0FFF. Тогда полученные нами значения соответствуют 1/2 и 1/4 от уровня VDD. Все верно.

Для такого же отображения данных, надо сделать следующие настройки. В окне памяти жмем кнопку, расположенную правее поля memory (с треугольником вершиной вниз). Появляется вот такое меню:

Memory setting

Здесь выбираем режим отображения 2-х байтных значений (2x Units) и порядок следования байт – Little Endian (от младшего к старшему).

Напоследок отмечу, что не все выводы АЦП подключены на внешние контакты платы. А те что выведены, в основном также соединяются и с ЖКИ,  а вход ADC_IN4 (вывод порта PA4) через джампер JP1 подключен также к схеме измерения тока потребления. На плате STM32L-DISCOVERY в итоге есть только один абсолютно “чистый” вход АЦП, выведенный на внешние контакты. Это ADC_IN5 (PA5).

Загрузить готовый проект можно по ссылке  Проект ADC -> DMA -> RAM

Описание регистров DMA здесь Регистры DMA (RUS)

Другие статьи:

  40 Responses to “STM32L. Контроллер DMA.”

  1. Nosferatu

    А как записать данные в EEPROM при помощи DMA?

  2. А будут статьи посвященные работе с EEPROM, FLASH и т п ?

  3. Допустим мне нужно будет отправлять значения из RAM куда нибудь , например в USART, нужно так же будет использовать контрллер DMA (память — перефнрия) ? Как правильно обратиться к области памяти c адресом 0x2000 0000 а не к значению 0x2000 0000?. И например, если я использую указатель на начала адреса с которого хочу считать информацию (*StartAdrRAM + i) i — будет сдвигать на размер 4 байта или на размер который мы указали в DMA ?

    • 1. Да
      2. Просто задаем адреса источника и приемника данных. Контроллер DMA понимает, что это именно адреса, а не значения.
      3. Все зависит от размерности данных и приращения адреса. Cortex тем и хороши, что позволяют работать с данными разного размера в памяти.

  4. Вот теперь дело такое, хочу передать АЦП в RAM с помощью DMA1_Channel1. После этого вызвать прерывание и с помощью DMA1_Channel2 передать из RAM — USART1_TX. ФЛАГ на разрешение прерывания я выставил в DMA1_Channel1 будет ли вызов обработчика прерывания
    DMA1_Channel2_IRQHandler или нужно указать что DMA1_Channel1_IRQHandler и в нем сделать конфигурацию для DMA1_Channel2.

    void initDMA(void)
    {
    //===_____===//Настройка DMA — переферия->память
    RCC->AHBENR |= RCC_AHBENR_DMA1EN; //Разрешаем тактирование DMA
    DMA1_Channel1->CPAR |= ADC1_DR_ADDRESS; //Задаем адрес периферии — регистр результата преобразования АЦП для регулярных каналов.
    DMA1_Channel1->CMAR |= 0x2000 000; //Задаем адрес памяти — базовый адрес массива в RAM.
    DMA1_Channel1->CCR &= ~DMA_CCR1_DIR; //Направление передачи данных — чтение из периферии, запись в память.
    DMA1_Channel1->CNDTR = 4; //Количество пересылаемых значений.
    DMA1_Channel1->CCR &= ~DMA_CCR1_PINC; //Адрес периферии не инкрементируется после каждой пересылки.
    DMA1_Channel1->CCR |= DMA_CCR1_MINC; //Адрес памяти инкрементируется после каждой пересылки.
    DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0; //Размерность данных периферии — 16 бит.
    DMA1_Channel1->CCR |= DMA_CCR1_MSIZE_0; //Размерность данных памяти — 16 бит
    DMA1_Channel1->CCR |= DMA_CCR1_PL; //Приоритет — очень высокий (Very High)
    DMA1_Channel1->IFCR |= DMA_IFCR_CGIF1//сброс всех флагов прерывания
    DMA1_Channel1->CCR |= DMA_CCR1_TCIE//разрешение прерывания при полной передаче данных из АЦП в RAM
    DMA1_Channel1->CCR |= DMA_CCR1_EN; //Разрешаем работу канала 1 DMA
    }

    void DMA1_Channel2_IRQHandler(void)
    {
    //===_____===///обработчик прерываний DMA1_Channel1
    DMA1_Channel1->IFCR |= DMA_IFCR_CTCIF1;
    while(!(DMA1_Channel1->ISR&DMA_ISR_TCIF1));
    DMA1_Channel2->CPAR |= USART1_TX; //Задаем адрес периферии — регистр результата преобразования АЦП для регулярных каналов.
    DMA1_Channel2->CMAR |= 0x2000 000; //Задаем адрес памяти — базовый адрес массива в RAM.
    DMA1_Channel2->CCR |= DMA_CCR1_DIR; //Направление передачи данных — из памяти в переферию.
    DMA1_Channel2->CNDTR = 4; //Количество пересылаемых значений.
    DMA1_Channel2->CCR &= ~DMA_CCR1_PINC; //Адрес периферии не инкрементируется после каждой пересылки.
    DMA1_Channel2->CCR |= DMA_CCR1_MINC; //Адрес памяти инкрементируется после каждой пересылки.
    DMA1_Channel2->CCR |= DMA_CCR1_PSIZE_0; //Размерность данных периферии — 16 бит.
    DMA1_Channel2->CCR |= DMA_CCR1_MSIZE_0; //Размерность данных памяти — 16 бит
    DMA1_Channel2->CCR |= DMA_CCR1_PL; //Приоритет — очень высокий (Very High)
    DMA1_Channel2->IFCR |= DMA_IFCR_CGIF1//сброс всех флагов прерывания для DMA1_Channel2
    DMA1_Channel2->CCR |= DMA_CCR1_EN; //Разрешаем работу канала 1 DMA
    }

  5. Добрый день.Довольно часто захожу на ваш сайт,спасибо за хорошую работу.Недавно возникла необходимость с помощью ЦАП подавать 2 синусоиды со сдвигом одновременно.
    Желательно режим «Одновременный программный запуск»,хотя можно и от таймеров.Для сих целей
    используются регистры DHR (DAC_DHR12RD,DAC_DHR12LD или DAC_DHR8RD).
    Как зто возможно реализовать с помощью DMA, насколько понимаю там отдельные каналы привязаны к конкретным каналам ЦАП (DMA channel3->DACchannel2,DMA channel2->DACchannel1), а регистры DHRD где?

  6. Periphery и ubas! Не смог ответить быстро, поскольку выходные, хорошая погода и т.д. напрочь гонят из интернета куда-то на природу :). Постараюсь в ближайшие дни порешать ваши задачки.

  7. Какой то провал, который день сижу не могу вызвать прерывание ни по одному событию, хотя в ram пишет.

    • В коде много ошибок, как компилятор-то не ругается? Попробовал загрузить код, часто пропущены точки с запятой в конце строки и еще вот такая запись неправильная
      DMA1_Channel2->IFCR |= DMA_IFCR_CGIF1;
      Надо писать так
      DMA1->IFCR |= DMA_IFCR_CGIF1;

      А еще надо контроллеру прерываний указания дать, разрешить прерывание и установить приоритет
      NVIC_SetPriority(…….)
      NVIC_EnableIRQ(…….)

      И адрес приемника
      DMA1_Channel2->CPAR |= USART1_TX;
      указан неверно, так нельзя. Либо физический адрес прописать, либо как-то через указатели.
      Вот, примерно, как-то так, что сразу в глаза бросается.

  8. ubas, все верно, но для каналов DMA это источники запуска, а не получатели, так они привязаны к каналам. Периферия тут только активирует канал на передачу. А адрес получателя можно прописать для любого из каналов DMA, например, DAC_DHR12RD,DAC_DHR12LD или DAC_DHR8RD. Понятно объяснил? Вот у меня тут пример рассмотрен
    http://chipspace.ru/stm32-dac-3/
    Одновременный запуск лучше от таймера сделать, программный «съест» несколько тактов между запусками каналов. Если есть запуск от одного таймера для двух каналов, то хорошо. Да и в этом случае каналы будут запускаться не синхронно, а последовательно, в зависимости от приоритета. Но заметно будет только на максимальных частотах.

  9. Periphery, еще нюанс, в USART пишешь 16-разрядные данные для передачи. Там ведь 8 бит.

  10. Спасииибо сегодня попробую.

  11. Не могу уйти в прерывание , чсовсем совсем не получается.

  12. Вот ссылка на mail загрузчик
    http://files.mail.ru/A2FE3FEF09C74A1EA501EC8B149D87BC тут проект main.c

    • Вот в этой функции прописан канал DMA2:

      void SetNVIC(void)
      {
      NVIC_EnableIRQ(DMA2_Channel1_IRQn);
      NVIC_SetPriority(DMA2_Channel1_IRQn,1);
      }

      А все настройки для DMA1 сделаны. Да и откуда там контроллер DMA2 взялся, это же STM32L152, верно?
      Я внимательно пока не смотрел код, так поверхностно. А зачем там прерывание вообще от DMA, с какой целью?

  13. Идея такая была, есть допустим два независемых порта которые принимают опрелененные данные и складывает в свой собственный временный буфер, два или один канал обрабатывают их одновременно и отправляют в память, а дальше работает основная программа и она отдает на выход обработанные данные в виде одного сигнала в форме либо 4-20 либо modbus по выбору. А прерывания использую для разгарузки канала или для формирования повторного запроса.
    Ошибки которые вы мне указывали еще не исправил, после отладки dma буду приводить все в порядок.

  14. Спасибо, да все заработало, Но вышла опять проблема,,в настройках DMA ставлю циклический режим- и после первого запуска данные из DMA передаются в область памяти 0x20000000 , но только один раз. Проблема решилась тем что в основном цикле while(1) убираю и выставляю флаг (ADC1->CR2 &= ~ADC_CR2_DMA;), после этого DMA заработал в циклическом режиме, о еще если убрать в настройках DMA (ADC1->CR2 |= ADC_CR2_CONT;) то повторные запросы ADC к DMA ни к чему не приведут.
    ///===========================////
    while(1)
    {
    ADC1->CR2 &= ~ADC_CR2_DMA;
    ADC1->CR2 |= ADC_CR2_DMA;
    }
    Прочитал в даташите что АЦП формирует запрос к DMA а не DMA к АЦП.
    ///====================================///
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
    DMA1_Channel1->CPAR |= ADC1_DR_ADDRESS;
    DMA1_Channel1->CMAR |= MEMORY_ARRAY_ADDRESS;
    DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;
    DMA1_Channel1->CNDTR = 10;
    DMA1_Channel1->CCR &= ~DMA_CCR1_PINC;
    DMA1_Channel1->CCR |= DMA_CCR1_MINC;
    DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0;
    DMA1_Channel1->CCR |= DMA_CCR1_MSIZE_0;
    DMA1_Channel1->CCR |= DMA_CCR1_PL;
    DMA1_Channel1->CCR |= DMA_CCR1_TCIE;
    DMA1_Channel1->CCR |= DMA_CCR1_HTIE;
    DMA1_Channel1->CCR |= DMA_CCR1_CIRC;
    DMA1->IFCR |= DMA_IFCR_CGIF1;
    DMA1_Channel1->CCR |= DMA_CCR1_EN;

    Что за проблема такая ? неужели только так DMA будет работать в циклическом режиме только по запросу с ADC ?!

  15. Статья очень полезная! Подскажите пожалуйста.
    1. Как теперь из памяти RAM извлечь данные и переписать их в свои регистры например unsigned short BUFF0, BUFF1 …. BUFF9 с которыми я бы мог дальше работать. Насколько я понимаю это делается через указатель * только не знаю как.
    2. Я заметил что после того как программа считала данные из АЦП и поместила их в ячейки памяти в отладчики я данные вижу все отлично, но потом когда я дальше иду отладчиком по программе данные в ОЗУ затираются. Потому что в программе я объявлял массивы данных и насколько я понимаю эти данные по умолчанию начинаются с адреса 0х20000000 с того же адреса с которого я пишу данные контроллером DMA с АЦП. Я посмотрел есть такой регистр в контроллере как SYSCFG memory remap register (SYSCFG_MEMRMP) насколько я понимаю им можно указать начала размещения массива данных например с 0х20000002 что бы не затереть данные с АЦП. Вот только не понятно как это реализовать в программе.

  16. 1. Все эти переменные так же объявляются в RAM. Указатели указывают на адрес в RAM (в основном). То есть все это происходит в оперативной памяти, надо просто разобраться как. Все не так просто, надо понять где располагаются переменные, массивы, как они адресуются. Особо сложная тема — это указатели (по себе сужу). В общем, работа с памятью не так проста, там надо многое понять, и не все понимается с первого раза. Но понимать это надо, иначе вы застрянете на определенном уровне.
    2. Тут вообще не понял. Адрес 0х20000000 — адрес начал SRAM. А дальше по тексту выводы следующие: надо почитать теорию. Извини, я уж прямо говорю. :)

    • Написал вот такую супер простую функцию:
      //******************************************************************
      uint32_t RAM_READ(uint32_t address)
      {
      return (*(__IO uint32_t*) address);
      }
      //*************************************************************************
      Может кому пригодиться эта функция, Вам нужно вызвать эту функцию и передать адрес, обратно функция вернет данные из SRAM.

      Теперь осталось разобраться как сделать так что бы все мои пользовательские регистры начинались с области памяти SRAM 0x20000030, а не как сейчас адреса по умолчанию компоновщик пишет все мои регистры которые я объявляю начиная с адреса 0х20000000.

      • По поводу сохранения результатов очень удобно вначале объявить массив данных например
        //========================
        strADC[8]; // для сохр. результатов АЦП
        //============================

        Далее в инициализации DMA нужно указать:
        //=============================================================================
        ……..
        DMA1_Channel1->CMAR |= (uint32_t) & strADC; //это указатель для DMA где будет начало массива
        //==========================================================================
        И не нужно париться по поводу распределения памяти, компоновщик все сам сделает.
        Теперь можно удобно работать с данными.

        Но у меня появилась новая проблема(как у товарища выше Periphery) — происходит только первое измерение после включения и все дальше не измеряет по циклу((((.
        Побывал сбрасывать и устанавливать биты (ADC_CR2_DMA) в цикле while(1); — не помогло.

        Потом добавил в инициализации АЦП строку
        ADC1->CR2 |= ADC_CR2_CONT;
        и строку в инициализации DMA
        DMA1_Channel1->CCR |= DMA_CCR1_CIRC;
        и в цикле while(1); дописал
        ADC1->CR2 &= ~ADC_CR2_DMA; //
        ADC1->CR2 |= ADC_CR2_DMA; //

        В результате данные в массиве strADC обновляются но данные циклически сдвигаются по кругу с шагом 32бита при кадом стробе установке флага ADC_CR2_DMA

        Подскажи что я делаю неправильно.

        вот фрагмент моей программы:
        /*************************************************************************/*****************************************************************************
        * Устновка АЦП
        *****************************************************************************/
        void ADC_Setting (void)
        {
        RCC->CR |= RCC_CR_HSION; //Включаем внутренний генератор HSI — 16МГц
        while(!(RCC->CR&RCC_CR_HSIRDY)); //Ждем его стабилизации
        RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; //Разрешаем тактирование АЦП

        ADC1->SQR1 |= (ADC_SQR1_L_2 | ADC_SQR1_L_1 | ADC_SQR1_L_0); // кол-во последовательностей 111 — т.е 8
        ADC1->SQR5 |= (ADC_SQR5_SQ1_2); // V_REF, ADC4 — 00100
        ADC1->SQR5 |= (ADC_SQR5_SQ2_3 | ADC_SQR5_SQ2_1);// V_OUT, ADC10 -01010
        ADC1->SQR5 |= (ADC_SQR5_SQ3_3 | ADC_SQR5_SQ3_1 | ADC_SQR5_SQ3_0);// V_IN, ADC11 -01011
        ADC1->SQR5 |= (ADC_SQR5_SQ4_3 | ADC_SQR5_SQ4_2); // I_OUT, ADC12 -01100
        ADC1->SQR5 |= (ADC_SQR5_SQ5_3 | ADC_SQR5_SQ5_2 | ADC_SQR5_SQ5_0);// I_IN, ADC13 -01101
        ADC1->SQR5 |= (ADC_SQR5_SQ6_2 | ADC_SQR5_SQ6_0); // TEMP_1, ADC5 -00101
        ADC1->SQR4 |= (ADC_SQR4_SQ7_2 | ADC_SQR4_SQ7_1); // TEMP_2, ADC6 -00110
        ADC1->SQR4 |= (ADC_SQR4_SQ8_2 | ADC_SQR4_SQ8_1 | ADC_SQR4_SQ8_0);// TEMP_3, ADC7 -00111

        //ADC1->CR1 |= ADC_CR1_SCAN; //Режим сканирования. Новое преобразование запускается автоматически после завершения предыдущего.
        //ADC1->CR2 |= ADC_CR2_DELS_0; //Вводим задержку между преобразованиями
        ADC1->CR1 &= ~ADC_CR1_RES; //Разрядность АЦП — 12 бит
        ADC1->CR2 &= ~ADC_CR2_ALIGN; //Выравнивание результата вправо
        ADC1->CR2 |= ADC_CR2_DMA; //Разрешаем использование режима DMA. АЦП будет формировать запрос DMA после каждого преобразования.

        // ADC1->CR2 |= ADC_CR2_EXTSEL; // Преобразование регулярной группы
        //ADC1->CR2 |= ADC_CR2_EXTEN; // Разрешаем внешний запуск регулярной группы
        ADC1->CR2 |= ADC_CR2_CONT; // Преобразования запускаются одно за другим
        ADC1->CR2 |= ADC_CR2_DMA; // Подключаем DMA
        ADC1->CR1 |= ADC_CR1_SCAN; // Сканирование группы каналов — без него больше одного канала не запустить

        ADC1->CR2 |= (ADC_CR2_DELS_2 | ADC_CR2_DELS_0);// Задержка в 63 такта между преобразованиями
        //while(!(ADC1->SR&(~ADC_SR_RCNR))); //Ждем готовности АЦП

        ADC1->CR2 |= ADC_CR2_ADON; //Включаем АЦП
        while(!(ADC1->SR&ADC_SR_ADONS)); //Ждем готовности АЦП

        ADC1->CR2 |= ADC_CR2_SWSTART; //Запуск преобразовании АЦП 8 измерений
        }
        //—————————————————————————-

        void DMA_Init(void)
        {
        //Настройка DMA
        RCC->AHBENR |= RCC_AHBENR_DMA1EN; //Разрешаем тактирование DMA
        DMA1_Channel1->CPAR |= ADC1_DR_ADDRESS;//Задаем адрес периферии — регистр результата преобразования АЦП для регулярных каналов.
        DMA1_Channel1->CMAR |= (uint32_t) & strADC;//(uint32_t)strADC; (uint32_t) & strADC; //MEMORY_ARRAY_ADDRESS; //Задаем адрес памяти — базовый адрес массива в RAM.
        DMA1_Channel1->CCR &= ~DMA_CCR1_DIR; //Направление передачи данных — чтение из периферии, запись в память.

        DMA1_Channel1->CNDTR = 8; //Количество пересылаемых значений
        DMA1_Channel1->CCR &= ~DMA_CCR1_PINC; //Адрес периферии не инкрементируется после каждой пересылки.
        DMA1_Channel1->CCR |= DMA_CCR1_MINC; //Адрес памяти инкрементируется после каждой пересылки.
        DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0;//Размерность данных периферии — 16 бит.
        DMA1_Channel1->CCR |= DMA_CCR1_MSIZE_0;//Размерность данных памяти — 16 бит
        DMA1_Channel1->CCR |= DMA_CCR1_PL; //Приоритет — очень высокий (Very High)
        DMA1_Channel1->CCR |= DMA_CCR1_CIRC; //ВСТАВИЛ Циклический режим — постоянная передач
        DMA1_Channel1->CCR |= DMA_CCR1_EN; //Разрешаем работу канала 1 DMA
        }

        /*****************************************************************************
        * Инизализация портов
        *****************************************************************************/
        void Periph_Init(void)
        {
        RCC->AHBENR |= RCC_AHBENR_GPIOAEN; //Включить тактирование порта А
        GPIOA->MODER |= GPIO_MODER_MODER4; //Устанавливаем 11 — аналоговый вход/выход для PA4
        GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR4;//Очищаем быты 00 — аналоговый вход/выход без подтяжки для PA4

        GPIOA->MODER |= GPIO_MODER_MODER5; //Устанавливаем 11 — аналоговый вход/выход для PA5
        GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR5;//Очищаем быты 00 — аналоговый вход/выход без подтяжки для PA5

        GPIOA->MODER |= GPIO_MODER_MODER6; //Устанавливаем 11 — аналоговый вход/выход для PA6
        GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR6;//Очищаем быты 00 — аналоговый вход/выход без подтяжки для PA6

        GPIOA->MODER |= GPIO_MODER_MODER7; //Устанавливаем 11 — аналоговый вход/выход для PA7
        GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR7;//Очищаем быты 00 — аналоговый вход/выход без подтяжки для PA7

        //*** настроить на выход для диагностики генератора***
        GPIOA->MODER |= GPIO_MODER_MODER8_1;
        GPIOA->OTYPER &= ~GPIO_OTYPER_OT_8;
        GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR8;
        GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR8;
        GPIOA->AFR[1] &= ~GPIO_AFRH_AFRH8;
        RCC->CFGR &= ~RCC_CFGR_MCOPRE;

        //RCC->CFGR |= RCC_CFGR_MCO_MSI;
        //RCC->CFGR |= RCC_CFGR_MCO_HSI;
        //RCC->CFGR |= RCC_CFGR_MCO_LSI;
        //RCC->CFGR |= RCC_CFGR_MCO_LSE;
        //RCC->CFGR |= RCC_CFGR_MCO_PLL;
        RCC->CFGR |= RCC_CFGR_MCO_SYSCLK; // Вывести сигнал на MCO (PA8)

        RCC->AHBENR |= RCC_AHBENR_GPIOBEN; //Включить тактирование порта B

        GPIOB->MODER |= GPIO_MODER_MODER7_0;
        GPIOB->OTYPER &= ~GPIO_OTYPER_OT_7;
        GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR7;
        GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7;

        GPIOB->MODER |= GPIO_MODER_MODER6_0;
        GPIOB->OTYPER &= ~GPIO_OTYPER_OT_6;
        GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR6;
        GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6;

        GPIOB->MODER |= GPIO_MODER_MODER12_0; // настроить линию на выход
        GPIOB->OTYPER &= ~GPIO_OTYPER_OT_12; // Push-Pull (двухтактный выход)
        GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR12; // подтягивающие резисторы отключены
        GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR12;// Настроить скорость порта 40Мгц

        GPIOB->MODER |= GPIO_MODER_MODER13_0; // настроить линию на выход
        GPIOB->OTYPER &= ~GPIO_OTYPER_OT_13; // Push-Pull (двухтактный выход)
        GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR13; // подтягивающие резисторы отключены
        GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR13;// Настроить скорость порта 40Мгц

        GPIOB->MODER |= GPIO_MODER_MODER14_0; // настроить линию на выход
        GPIOB->OTYPER &= ~GPIO_OTYPER_OT_14; // Push-Pull (двухтактный выход)
        GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR14; // подтягивающие резисторы отключены
        GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR14;// Настроить скорость порта 40Мгц

        //*** настройка портов управления симисторов***
        RCC->AHBENR |= RCC_AHBENR_GPIOCEN; // Включить тактирование порта С

        GPIOC->MODER |= GPIO_MODER_MODER0; //Уст. 11 — аналоговый вход/выход для PC0 — V_OUT
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR0;//Очищаем быты 00 — аналоговый вход/выход без подтяжки для PC0

        GPIOC->MODER |= GPIO_MODER_MODER1; //Уст. 11 — аналоговый вход/выход для PC1 — V_OUT
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR1;//Очищаем быты 00 — аналоговый вход/выход без подтяжки для PC1

        GPIOC->MODER |= GPIO_MODER_MODER2; //Уст. 11 — аналоговый вход/выход для PC2 — V_OUT
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR2;//Очищаем быты 00 — аналоговый вход/выход без подтяжки для PC2

        GPIOC->MODER |= GPIO_MODER_MODER3; //Уст. 11 — аналоговый вход/выход для PC3 — V_OUT
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR3;//Очищаем быты 00 — аналоговый вход/выход без подтяжки для PC3

        GPIOC->MODER |= GPIO_MODER_MODER4_0; // настроить линию на выход
        GPIOC->OTYPER &= ~GPIO_OTYPER_OT_4; // Push-Pull (двухтактный выход)
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR4; // подтягивающие резисторы отключены
        GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR4; // Настроить скорость порта 40Мгц

        GPIOC->MODER |= GPIO_MODER_MODER5_0; // настроить линию на выход
        GPIOC->OTYPER &= ~GPIO_OTYPER_OT_5; // Push-Pull (двухтактный выход)
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR5; // подтягивающие резисторы отключены
        GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR5; // Настроить скорость порта 40Мгц

        GPIOC->MODER |= GPIO_MODER_MODER6_0; // настроить линию на выход
        GPIOC->OTYPER &= ~GPIO_OTYPER_OT_6; // Push-Pull (двухтактный выход)
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR6; // подтягивающие резисторы отключены
        GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6; // Настроить скорость порта 40Мгц

        GPIOC->MODER |= GPIO_MODER_MODER7_0; // настроить линию на выход
        GPIOC->OTYPER &= ~GPIO_OTYPER_OT_7; // Push-Pull (двухтактный выход)
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR7; // подтягивающие резисторы отключены
        GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7; // Настроить скорость порта 40Мгц

        GPIOC->MODER |= GPIO_MODER_MODER8_0; // настроить линию на выход
        GPIOC->OTYPER &= ~GPIO_OTYPER_OT_8; // Push-Pull (двухтактный выход)
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR8; // подтягивающие резисторы отключены
        GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR8; // Настроить скорость порта 40Мгц

        GPIOC->MODER |= GPIO_MODER_MODER9_0; // настроить линию на выход
        GPIOC->OTYPER &= ~GPIO_OTYPER_OT_9; // Push-Pull (двухтактный выход)
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR9; // подтягивающие резисторы отключены
        GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9; // Настроить скорость порта 40Мгц

        GPIOC->MODER |= GPIO_MODER_MODER10_0; // настроить линию на выход
        GPIOC->OTYPER &= ~GPIO_OTYPER_OT_10; // Push-Pull (двухтактный выход)
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR10; // подтягивающие резисторы отключены
        GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR10; // Настроить скорость порта 40Мгц

        GPIOC->MODER |= GPIO_MODER_MODER11_0; // настроить линию на выход
        GPIOC->OTYPER &= ~GPIO_OTYPER_OT_11; // Push-Pull (двухтактный выход)
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR11; // подтягивающие резисторы отключены
        GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR11; // Настроить скорость порта 40Мгц

        GPIOC->MODER |= GPIO_MODER_MODER12_0; // настроить линию на выход
        GPIOC->OTYPER &= ~GPIO_OTYPER_OT_12; // Push-Pull (двухтактный выход)
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR12; // подтягивающие резисторы отключены
        GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR12; // Настроить скорость порта 40Мгц

        //GPIOC->MODER |= GPIO_MODER_MODER13_0; // настроить линию на выход
        //GPIOC->OTYPER &= ~GPIO_OTYPER_OT_13; // Push-Pull (двухтактный выход)
        //GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR13; // подтягивающие резисторы отключены
        //GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR13; // Настроить скорость порта 40Мгц

        GPIOC->MODER |= GPIO_MODER_MODER14_0; // настроить линию на выход
        GPIOC->OTYPER &= ~GPIO_OTYPER_OT_14; // Push-Pull (двухтактный выход)
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR14; // подтягивающие резисторы отключены
        GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR14; // Настроить скорость порта 40Мгц

        GPIOC->MODER |= GPIO_MODER_MODER15_0; // настроить линию на выход
        GPIOC->OTYPER &= ~GPIO_OTYPER_OT_15; // Push-Pull (двухтактный выход)
        GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR15; // подтягивающие резисторы отключены
        GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR15; // Настроить скорость порта 40Мгц
        }
        //—————————————————————————-

        int main(void)
        {
        Periph_Init(); // Устновка портов
        ADC_Setting(); // Настройка АЦП
        DMA_Init(); // Настройка DMA контроллера для АЦП

        while(1)
        {
        ADC1->CR2 &= ~ADC_CR2_DMA; //
        ADC1->CR2 |= ADC_CR2_DMA; //
        }
        }

  17. Слишком много текста, сложно понять. Можно в двух словах «желаемое» и «полученное». Как можно короче.
    А вот это:
    «и в цикле while(1); дописал
    ADC1->CR2 &= ~ADC_CR2_DMA; //
    ADC1->CR2 |= ADC_CR2_DMA; //»

    что оно делает?

    • В результате данные в массиве strADC обновляются но данные циклически сдвигаются по кругу с разным шагом кратно 16 бита при кадом стробе установке флага ADC_CR2_DMA

      к примеру первый раз в 8 каналах в RAM памяти значения AAFF 4455 6677 8899 BBCC FFAC DDDD A1C4
      потом сбрасываю и устанавливаю бит «и в цикле while(1); дописал
      ADC1->CR2 &= ~ADC_CR2_DMA; //
      ADC1->CR2 |= ADC_CR2_DMA; //»
      Данные в патия RAM становятся BBCC FFAC DDDD A1C4 AAFF 4455 6677 8899 (крутятся по кругу)
      если еще раз пройти отладким флаги
      ADC1->CR2 &= ~ADC_CR2_DMA; //
      ADC1->CR2 |= ADC_CR2_DMA; //»
      то значения 4455 6677 8899 BBCC FFAC DDDD A1C4 AAFF ( в этот раз значения сдвинулись на другой шаг)

    • Я так и не вижу ваш вопрос, и не получил ответ на свой.
      Вас что интересует, почему данные сдвигаются, когда вы выключаете и тут же снова включаете режим DMA? А зачем вы так делаете?
      Давайте, если есть конкретный вопрос, спрашивайте.

      • Смотрите если я в цикле while(1) запускаю «ADC1->CR2 |= ADC_CR2_SWSTART;//Запуск преобразовании АЦП 8 измерений», то после того как прошил контроллер и в отладчике смотрю Memory , данные записываются правильно 8 каналов — 8 измерений, все измерения правильные. Но после того как я в цикле повторно запускаю …ADC_CR2_SWSTART, то данные не изменяются. Вот в чем вопрос данные я получаю всего один раз, только после первой инициализации контроллера.
        Повторюсь еще раз потом я различным способом пытался запустить повторно ADC_CR2_SWSTART, что бы данные обновлялись в памяти SRAM, сделал как товарищ(Periphery) выше сбрасывал и устанавливал бит DMA, а потом запускал преобразование ADC_CR2_SWSTART — в итоге данные обновись, происходило измерение но данные циклически сдвигались как я писал выше.

        В кратце в первом случает происходит только одно измерение после включения, а дальше данные в SRAM не обновляются.
        Во втором случает(установка, сброс бита DMA) данные измеряются в цикле, данные правильные, но данные все время сдвигаются в цикле.

        • Хорошо, я понял. Я попробую сделать подобный пример, когда точно не могу сказать, может в течении недели.

          • Большое спасибо, звените меня за то что не смогу изложить сразу свою проблему. Буду с нетерпением ждать вашего ответа. Сам тоже изо всех сил пытаюсь разобраться, если получиться раньше то напишу.
            Большое Вам супер спасибо!

            • Разобрался заработало с работой ADC+DMA
              Теперь осталось разобраться как сделать по прерыванию (по окончанию измерения во всех каналах)

              вот рабочая инициализация ADC+DMA. Теперь работает в цикле.
              Просто запускам в цикле ADC1->CR2 |= ADC_CR2_SWSTART; и получаем результат.
              /*****************************************************************************
              * СУПЕР НОВАЯ ИНИЦИАЛИЗАЦИЯ АЦП И DMA
              *****************************************************************************/
              void SUPER_ADC_DMA_Init (void)
              {
              RCC->AHBENR |= RCC_AHBENR_DMA1EN; //Разрешаем тактирование DMA
              DMA1_Channel1->CPAR |= ADC1_DR_ADDRESS;//Задаем адрес периферии — регистр результата преобразования АЦП для регулярных каналов.
              DMA1_Channel1->CMAR |= (uint32_t) & strADC;//(uint32_t)strADC;//MEMORY_ARRAY_ADDRESS; //Задаем адрес памяти — базовый адрес массива в RAM.
              DMA1_Channel1->CCR &= ~DMA_CCR1_DIR; //Направление передачи данных — чтение из периферии, запись в память.
              DMA1_Channel1->CNDTR = 8; // кол-во переселаемых данных
              DMA1_Channel1->CCR &= ~DMA_CCR1_PINC; //Адрес периферии не инкрементируется после каждой пересылки.
              DMA1_Channel1->CCR |= DMA_CCR1_MINC; //Адрес памяти инкрементируется после каждой пересылки.
              DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0;//Размерность данных периферии — 16 бит.
              DMA1_Channel1->CCR |= DMA_CCR1_MSIZE_0;//Размерность данных памяти — 16 бит
              DMA1_Channel1->CCR |= DMA_CCR1_CIRC; //Циклический режим — постоянная передач
              DMA1_Channel1->CCR |= DMA_CCR1_PL; //Приоритет — очень высокий (Very High)
              DMA1_Channel1->CCR &= ~DMA_CCR1_MEM2MEM;//Передача из переферии в память
              DMA1_Channel1->CCR |= DMA_CCR1_EN; //Разрешаем работу канала 1 DMA

              RCC->CR |= RCC_CR_HSION; //Включаем внутренний генератор HSI — 16МГц
              while(!(RCC->CR&RCC_CR_HSIRDY)); //Ждем его стабилизации
              RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; //Разрешаем тактирование АЦП

              // CCR->CFGR |= ADC_CCR_ADCPRE_1; // предделитель на 4 ADCCLK 16мгц/4=4мгц
              ADC1->CR1 |= ADC_CR1_SCAN; // Сканирование группы каналов — без него больше одного канала не запустить
              ADC1->CR2 |= ADC_CR2_CONT; // Преобразования запускаются одно за другим
              ADC1->CR2 &= ~ADC_CR2_EXTEN;// Выключить внешний триггер
              ADC1->CR2 &= ~ADC_CR2_ALIGN; //Выравнивание результата вправо
              ADC1->SQR1 |= (ADC_SQR1_L_2 | ADC_SQR1_L_1 | ADC_SQR1_L_0); // кол-во последовательностей 111 — т.е 8
              ADC1->CR2 &= ~ADC_CR2_ALIGN; //Выравнивание результата вправо
              ADC1->SQR1 |= (ADC_SQR1_L_2 | ADC_SQR1_L_1 | ADC_SQR1_L_0); // кол-во последовательностей 111 — т.е 8
              ADC1->SQR5 |= (ADC_SQR5_SQ1_2); // V_REF, ADC4 — 00100
              ADC1->SQR5 |= (ADC_SQR5_SQ2_3 | ADC_SQR5_SQ2_1);// V_OUT, ADC10 -01010
              ADC1->SQR5 |= (ADC_SQR5_SQ3_3 | ADC_SQR5_SQ3_1 | ADC_SQR5_SQ3_0);// V_IN, ADC11 -01011
              ADC1->SQR5 |= (ADC_SQR5_SQ4_3 | ADC_SQR5_SQ4_2); // I_OUT, ADC12 -01100
              ADC1->SQR5 |= (ADC_SQR5_SQ5_3 | ADC_SQR5_SQ5_2 | ADC_SQR5_SQ5_0);// I_IN, ADC13 -01101
              ADC1->SQR5 |= (ADC_SQR5_SQ6_2 | ADC_SQR5_SQ6_0); // TEMP_1, ADC5 -00101
              ADC1->SQR4 |= (ADC_SQR4_SQ7_2 | ADC_SQR4_SQ7_1); // TEMP_2, ADC6 -00110
              ADC1->SQR4 |= (ADC_SQR4_SQ8_2 | ADC_SQR4_SQ8_1 | ADC_SQR4_SQ8_0);// TEMP_3, ADC7 -00111

              ADC1->SMPR3 |= ADC_SMPR3_SMP0_1; // Sample Time 010 — 16 cycles
              ADC1->SMPR3 |= ADC_SMPR3_SMP1_1; // Sample Time 010 — 16 cycles
              ADC1->SMPR3 |= ADC_SMPR3_SMP2_1; // Sample Time 010 — 16 cycles
              ADC1->SMPR3 |= ADC_SMPR3_SMP3_1; // Sample Time 010 — 16 cycles
              ADC1->SMPR3 |= ADC_SMPR3_SMP4_1; // Sample Time 010 — 16 cycles
              ADC1->SMPR3 |= ADC_SMPR3_SMP5_1; // Sample Time 010 — 16 cycles
              ADC1->SMPR3 |= ADC_SMPR3_SMP6_1; // Sample Time 010 — 16 cycles
              ADC1->SMPR3 |= ADC_SMPR3_SMP7_1; // Sample Time 010 — 16 cycles

              ADC1->CR2 |= (ADC_CR2_DELS_2 | ADC_CR2_DELS_0);// Задержка в 63 такта между преобразованиями

              ADC1->CR2 |= ADC_CR2_DDS; // ВОПРОС??? DMA request (запрос)
              ADC1->CR2 |= ADC_CR2_DMA; // Подключаем DMA
              ADC1->CR2 |= ADC_CR2_ADON; //Включаем АЦП
              while(!(ADC1->SR&ADC_SR_ADONS)); //Ждем готовности АЦП

  18. Ну и отлично.

  19. При создании массива или объявлении переменных надо указывать размерность. Компилятор конечно умный, но не настолько :). Учитывайте это!

    • Подскажите где взять инструмент отладчика (такой как в MPLAB Stop Watch) который позволяет посчитать время(или циклов) выполнения команд(ы) или узнать время выполнения преобразований АЦП в группе.

      • Не работал с таким. Надо искать в документации, есть зависимость времени преобразования от напряжения питания вроде бы. Навскидку не помню цифры.

        • Смотри к примеру я делаю Run To Cursor строчка номер 1, сбрасываем Stop Watch и ставим к 12 строчке и делам Run To Cursor и инструмент отладчика(Microchip) Stop Watch нам показывает сколько циклов заняли 12 операторов или микросекунд если указать частоту тактирования процессора. Очень удобная штука, а IAR не могу найти такого инструмента для отладки