Top.Mail.Ru
Уведомления
Очистить все

[Решено] Проблема связки ADC+DMA+SPI

(@sanyx)
Level 1

Всем, привет.

Пытаюсь, принятые от АЦП данные по одному каналу, передать по сети с помощью модуля w5500.

Алгоритм простой: АЦП настроен на непрерывное преобразование, данные забирает DMA, как только заполнена одна из половинок буфера, она отправляется по SPI в сетевой модуль w5500. В итоге график полученных данных выглядит так, как будто передача по SPI влияет на АЦП.

 

Может ли это быть и как от этого избавиться?

Экран
Спойлер
Настройка АЦП
#include "system_stm32f10x.h"
#include "stm32f10x.h"
#include "adc1.h"

static uint16_t ADC1_Data[ADC_DATA_WORK_COUNT*2];     // Массив для приёма данных с АЦП (массив в 2 раза больше, чтобы обеспечить двойную буферизацию)
static uint16_t *pADC_Data = 0;                       // Адрес с которого нужно будет забирать данные АЦП
static uint32_t ADC_Done = 0;                         // Признак готовности данных

void ADC1_Init(uint32_t chnum, uint32_t tp) // Инициализация АЦП1 на оцифровку с частотой 1 МГц
{
  // chnum - номер канала с которого будут поступать аналоговые данные (нумеруется с 0)
  // tp - Кол-во тактов АЦП за которое выполняется преобразование из аналогового значения в цифровое

  // АЦП можно настроить на преобразование регулярных групп - последовательное преобразование нескольких каналов АЦП и помещение результата в один регистр для всех каналов ( естественно нужно успеть забрать данные прежде чем они затерлись преобразованием от другого канала )
  // АЦП можно настроить на преобразование инжекторных групп - тоже, что и регулярные, только для результата выделенно 4 регистра

  // Делитель для АЦП (тактирование АЦП по документации не должно превышать 14 МГц )
  // Тактирование АЦП1 выполняется от шины APB2 с делением устанавливаемым в регистре RCC->CFGR
  RCC->CFGR &= ~RCC_CFGR_ADCPRE_DIV8;  // тут сбрасываются 2 бита, которые отвечают за делитель для АЦП
  RCC->CFGR |= RCC_CFGR_ADCPRE_DIV8;   // тут устанавливаются биты т.о., чтобы делитель был 8 (112000000 / 8 = 14000000) (предполагается, что частота тактирования APB2 (PCLK2) равна 112МГц)

  RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // Разрешаем тактирование АЦП
  RCC->AHBENR |= RCC_AHBENR_DMA1EN;   // Включаем тактирование DMA1  (DMA1 будет забирать данные с АЦП1)

  // Настройка DMA (настраивается первый канал DMA1 т.к. этот канал выделен для АЦП)
  DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR;      // Адрес с которого будут браться данные ( адрес регистра переферии )
  DMA1_Channel1->CMAR = (uint32_t)&ADC1_Data[0];  // Адрес массива в который будут поступать данные
  DMA1_Channel1->CNDTR = ADC_DATA_WORK_COUNT*2;   // Количество передаваемых отсчётов из АЦП в память
  DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;            // Направление передачи из перефирии в память
  DMA1_Channel1->CCR |= DMA_CCR1_MINC;            // Увеличение адреса памяти
  DMA1_Channel1->CCR |= DMA_CCR1_CIRC;            // Циклический режим
  DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0;         // Будет передаваться по 16 бит из переферийного устройства
  DMA1_Channel1->CCR |= DMA_CCR1_MSIZE_0;         // Будет передаваться по 16 бит в память
  DMA1_Channel1->CCR |= DMA_CCR1_HTIE;            // Разрешение прерывания по передаче половины данных
  DMA1_Channel1->CCR |= DMA_CCR1_TCIE;            // Разрешаем прерывание по завершению передачи
  DMA1_Channel1->CCR |= DMA_CCR1_EN;              // Включаем DMA (передача начнётся, когда АЦП начнёт передавать данные)

  NVIC_EnableIRQ(DMA1_Channel1_IRQn);

  ADC1->CR2 &= ~ADC_CR2_ADON; // Перед тем как всё настраивать выключаем АЦП

  ADC1->CR1 = 0; // Такое значение подходит

  // Эти регистры указывают за сколько циклов будет определено значение аналогового сигнала (возможные варианты 1.5, 7.5, 13.5, 28.5, 41.5, 55.5, 71.5, 239.5)
  // при этом полное преобразование для одного канала будет выполняться за 12.5 + x (x - то, что задаётся в регистр)
  // Т.о. минимальное время преобразования для одного канала составляет 12.5 + 1.5 = 14 тактов АЦП, если АЦП тактируется с частотой 14 МГц, то преобразование будет занимать 1 мкс.
  // чем больше время выборки, тем точнее результат
  ADC1->SMPR1 = 0; ADC1->SMPR2 = tp;

  // Далее настраивается последовательность в которой будут выполняться преобразования
  ADC1->SQR1 = ((1-1)<<20);  // Указываем количество преобразований (указывается на 1 меньше) (будет выполняться одно преобразование)
  ADC1->SQR3 = chnum;  // будет выполняться преобразование по регулярному каналу c номером chnum

  // Первая установка бита ADC_CR2_ADON пробуждает АЦП (АЦП выходит из режима Power Down), последующие установки ADC_CR2_ADON запускают преобразования, если их нужно запускать вручную.
  ADC1->CR2 = ADC_CR2_ADON;

  // Калибровка АЦП
  ADC1->CR2 |= ADC_CR2_RSTCAL; // Установка бита сброса калибровки ( для подготовки регистра калибровки )
  while(ADC1->CR2 & ADC_CR2_RSTCAL); // Ожидание установки регистра для калибровки
  ADC1->CR2 |= ADC_CR2_CAL; // Установка бита для начала калибровки
  while(ADC1->CR2 & ADC_CR2_CAL); // Ожидание окончания калибровки

  ADC1->CR2 |= ADC_CR2_DMA;  // Данные будет забирать DMA
  ADC1->CR2 |= ADC_CR2_CONT; // Установка непрерывного режима преобразования (АЦП выполняет преобразования автоматически с частотой, которая определяется частотой тактирования и временем преобразования)
  ADC1->CR2 |= ADC_CR2_ADON; // Т.к. задан непрерывный режим, то АЦП начинает непрерывно выполнять преобразование, которое сохраняется с помощью DMA
}

void DMA1_Channel1_IRQHandler(void) // Обработчик прерывания от 1-го канала DMA1
{
  if(DMA1->ISR & DMA_ISR_HTIF1)     // Если преобразование произошло по причине окончания половины передачи, то
  {
    DMA1->IFCR |= DMA_IFCR_CHTIF1;  // это, чтобы флаг прерывания (DMA_ISR_HTIF1) сбросить
    pADC_Data = &ADC1_Data[0];      // т.к. заполнена 1-я половина, то указатель на данные устанавливается в начало массива
    ADC_Done = 1;                   // установка признака, который означает, что данные заполнены
    return;
  }

  if(DMA1->ISR & DMA_ISR_TCIF1) // Если преобразование произошло по причине окончания передачи, то
  {
    DMA1->IFCR |= DMA_IFCR_CTCIF1;                // это, чтобы флаг прерывания (DMA_ISR_TCIF1) сбросить
    pADC_Data = &ADC1_Data[ADC_DATA_WORK_COUNT];  // т.к. заполнена 2-я половина, то указатель устанавливается на начало 2-й части массива (для данных было выделено в 2 раза больше памяти, чем передаётся на обработку)
    ADC_Done = 1;                                 // установка признака, который означает, что данные заполнены
    return;
  }
}

uint16_t *ADC1_CheckADCData(void)
{
  if(ADC_Done) // Если данные от АЦП заполнены, то
  {
    ADC_Done = 0; // Сбрасывается признак, чтобы больше не попадать сюда
    return pADC_Data; // Возвращается адрес на массив с готовыми данными
  }
  return 0; // Если данные с АЦП не готовы, то возвращаем ноль
}

Могу выложить весь проект если необходимо.

Цитата
Создатель темы Размещено : 25.06.2022 19:12
Aveal
(@aveal)
Top level Admin

Привет, надо попробовать поэтапно данные сравнить:

  • без передачи, только измерения,
  • непосредственно отправляемые измерения,
  • измерения принятые.
ОтветитьЦитата
Размещено : 27.06.2022 11:22
(@sanyx)
Level 1

@aveal Я сделал немного проще. Сначала накопление данных в буфер, после чего АЦП останавливается и выполняется передача данных по SPI в модуль w5500.

В таком варианте сигнал вполне себе нормальный

сигнал с одним буфером

 Похоже, что активность на одних ногах влияет на другие и в случае с аналоговыми входами влияние очень сильное.

Я даже переделал передачу данных через другой порт, но лучше не стало 🙁

ОтветитьЦитата
Создатель темы Размещено : 27.06.2022 19:34
Aveal
(@aveal)
Top level Admin

А можешь рабочий и проблемный проекты выложить?

ОтветитьЦитата
Размещено : 27.06.2022 21:06
(@sanyx)
Level 1

@aveal 

Рабочего проекта по сути нет, т.к. версия в которой сигнал нормальный выполняется сначала приём данных от АЦП, а потом передача по SPI в это время данные с АЦП не принимаются.

Версия с двумя буферами.

https://cloud.mail.ru/public/BzU4/j5E3cBHhG

 

Версия с одним буфером и прерывистым опросом АЦП.

https://cloud.mail.ru/public/QtCn/FMUeS48oi

ОтветитьЦитата
Создатель темы Размещено : 28.06.2022 00:06
Aveal
(@aveal)
Top level Admin

@sanyx Может проблема именно в заполнении буферов? Можно с 2-мя буферами убрать отправку по SPI, но проанализировать другим способом данные в этих буферах.

ОтветитьЦитата
Размещено : 28.06.2022 10:52
(@sanyx)
Level 1

Похоже вы правы, проблема действительно в заполнении.

Я убрал инициализацию SPI и принимаю данные в 2 буфера, и только потом выдаю из через USART и увидел такую печальную картину:

вывод через usart

посмотрел внимательно на график получается чётко первые 1000 отсчётов (первая половина буфера) с какими-то выбросами, вторая нормальная.

Есть идеи почему такое может быть?

ОтветитьЦитата
Создатель темы Размещено : 28.06.2022 14:25
(@sanyx)
Level 1

Беру свои слова обратно, я не с того места считывал данные.

Когда поправил адрес, то увидел нормальный сигнал в 2-х буферах, так что видимо проблема всё же в наводках от соседних ног.

ОтветитьЦитата
Создатель темы Размещено : 28.06.2022 17:55
Aveal
(@aveal)
Top level Admin

@sanyx Странно, что переход на другой SPI картину не меняет.

ОтветитьЦитата
Размещено : 29.06.2022 10:16
(@sanyx)
Level 1

Да, странно, я думал, что с переходом на другой порт помехи будут хотя бы меньше, но ничего не изменилось.

Похоже, что помехи идут по питанию, тогда самое простое, что можно сделать это разделить питание аналоговой части и цифровой.

ОтветитьЦитата
Создатель темы Размещено : 29.06.2022 11:59
(@sanyx)
Level 1

Получилось запитать аналоговую часть отдельно, благо у МК есть отдельные ноги для питания аналоговой части и цифровой.

Вот как это выглядит.

IMG 20220630 170454

И вот какой результат после этой доработки получился.

в отдельным питанием

 

Так что тему можно считать закрытой.

ОтветитьЦитата
Создатель темы Размещено : 30.06.2022 19:48
Aveal
(@aveal)
Top level Admin

@sanyx Это соответственно с двумя буферами?

ОтветитьЦитата
Размещено : 01.07.2022 10:11
(@sanyx)
Level 1

@aveal Да с двумя буферами и с передачей данных во время оцифровки. Если хорошо присмотреться, то можно увидеть когда идёт передача данных по SPI (в динамике это видно лучше).

Как оказалось аналоговая земля всё же внутри МК соединена с цифровой землёй, т.е. можно только одну ножку аналогового питания оторвать от платы и запитать отдельно.

ОтветитьЦитата
Создатель темы Размещено : 01.07.2022 11:05
Поделиться: