Держите меня семеро. Даешь по статье в день! Есть такая клевая штука в STM32 как DMA. Вот про нее и поговорим.
В кратце, DMA дает возможность копировать без участия процессора. Есть несколько вариантов, откуда и куда.
1. От периферии к памяти, когда нам нужно получать какие то данные, это может быть прием данных от SPI, UART, I2C или от АЦП, т.е. данные автоматически по мере поступления будут помещаться в буфер.
2. От памяти к периферии, все как в предыдущем случае, только наоборот 🙂 Может пригодиться когда нужно отправлять данные. Указал откуда начать отправку и где закончить, все остальное сделает дма.
3. Из памяти в память. Данные копируются из одной области памяти в другую. В каких то случаях может пригодиться.
На борту микроконтроллера может быть один или несколько DMA контроллеров. Каждый имеет несколько каналов, на один канал можно привязать только один периферий узел, причем только определенный. Например для stm32f1, на первый канал можно повесить только АПЦ1 или таймеры 2,4, но использовать их одновременно нельзя.
В stm32f4 все очень похоже, но намного круче, там кроме каналов есть еще потоки, т.е. каждому потоку соответствует несколько каналов.
DMA может генерировать прерывание, когда достигли середины буфера, или конца. Это справедливо как для передачи, так и для приема. Например приняли половину буфера, сработало прерывание, обработали первую половину данных. Приняли вторую половину, сработало прерывание окончания буфера, обработали вторую половину.
Далее если нужно, можно включить режим циклического опроса, т.е. после того как буфер наполнится, он продолжит наполняться с первого элемента, естественно старые данные затрутся.
Вообще дма прекрасно подходит для чтения значений АЦП. Запустили преобразование АЦП, по окончанию оно автоматом копируется в буфер. А если в настройке АЦП включить ContinuousConvMode, то АЦП будет постоянно преобразовывать данные, а DMA постоянно их копировать. Таким образом у нас в буфере постоянно будут свежие данные с АЦП.
Пример для F4, для 8 каналов.
uint16_t adc_buffer[8]; void dma_setup(void) { DMA_DeInit(DMA2_Stream0); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); DMA_InitTypeDef dma2_setup; dma2_setup.DMA_Channel = DMA_Channel_0; //channel0 привязан к АЦП1 dma2_setup.DMA_PeripheralBaseAddr = (uint32_t) &ADC1->DR; //копируем из АЦП1 dma2_setup.DMA_Memory0BaseAddr = (uint32_t) &adc_buffer[0]; //адрес буфера, т.е. куда копировать dma2_setup.DMA_BufferSize = 8; //sizeof(adc_buffer); //размер буфера dma2_setup.DMA_DIR = DMA_DIR_PeripheralToMemory; //из периферии в память dma2_setup.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //не изменяем адрес периферии, все время копируем только из АЦП1 dma2_setup.DMA_MemoryInc = DMA_MemoryInc_Enable; //а вот адрес памяти изменяем, чтобы каждое новое значение записывалось в след. элемент массива dma2_setup.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //размер данных буфера 16бит dma2_setup.DMA_Mode = DMA_Mode_Circular; //циклический режим dma2_setup.DMA_Priority = DMA_Priority_High; //высший приоритет dma2_setup.DMA_FIFOMode = DMA_FIFOMode_Disable; //фифо не используем dma2_setup.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; //относится к фифо dma2_setup.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //размер данных периферии 16бит dma2_setup.DMA_MemoryBurst = DMA_MemoryBurst_Single; //относится к пакетной пересылке dma2_setup.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(DMA2_Stream0, &dma2_setup); DMA_Cmd(DMA2_Stream0, ENABLE); } void adc_setup(void) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitTypeDef pa_setup; pa_setup.GPIO_Mode = GPIO_Mode_AIN; pa_setup.GPIO_OType = GPIO_OType_PP; pa_setup.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_6 | GPIO_Pin_7; pa_setup.GPIO_PuPd = GPIO_PuPd_NOPULL; pa_setup.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &pa_setup); ////////////////////////////////// RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); GPIO_InitTypeDef pc_setup; pc_setup.GPIO_Mode = GPIO_Mode_AIN; pc_setup.GPIO_OType = GPIO_OType_PP; pc_setup.GPIO_Pin = GPIO_Pin_0 ; pc_setup.GPIO_PuPd = GPIO_PuPd_NOPULL; pc_setup.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &pc_setup); ///////////////////////////////// RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); GPIO_InitTypeDef pb_setup; pb_setup.GPIO_Mode = GPIO_Mode_AIN; pb_setup.GPIO_OType = GPIO_OType_PP; pb_setup.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; pb_setup.GPIO_PuPd = GPIO_PuPd_NOPULL; pb_setup.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &pb_setup); ADC_InitTypeDef ADC_setup; ADC_CommonInitTypeDef ADC_CommonInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); ADC_setup.ADC_ScanConvMode = ENABLE; ADC_setup.ADC_ContinuousConvMode = ENABLE; ADC_setup.ADC_DataAlign = ADC_DataAlign_Right; ADC_setup.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; ADC_setup.ADC_NbrOfConversion = 8; // 1 channel ADC_setup.ADC_Resolution = ADC_Resolution_12b; ADC_Init(ADC1, &ADC_setup); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_3Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_3Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 3, ADC_SampleTime_3Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 4, ADC_SampleTime_3Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 5, ADC_SampleTime_3Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 6, ADC_SampleTime_3Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_9, 7, ADC_SampleTime_3Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 8, ADC_SampleTime_3Cycles); ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2; ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; ADC_CommonInit(&ADC_CommonInitStructure); ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_SoftwareStartConv(ADC1); // Start ADC1 conversion } |
Про пакетную передачу, т.е. то что относится к DMA_MemoryBurst, пока не могу ничего сказать, остальные настройки в комментариях кода.
Оставлю еще пример, настройки DMA для STM32L1, немного отличается, но суть таже.
void init_dma(void) { RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_InitTypeDef dma_setup; dma_setup.DMA_BufferSize = 8; dma_setup.DMA_DIR = DMA_DIR_PeripheralSRC; //периферия источник(source) dma_setup.DMA_M2M = DMA_M2M_Disable; dma_setup.DMA_MemoryBaseAddr = (uint32_t)&adc_buf[0]; //source dma_setup.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //2 bytes dma_setup.DMA_MemoryInc = DMA_MemoryInc_Enable; dma_setup.DMA_Mode = DMA_Mode_Circular; dma_setup.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR); dma_setup.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; dma_setup.DMA_PeripheralInc = DMA_PeripheralInc_Disable; dma_setup.DMA_Priority = DMA_Priority_Medium; DMA_Init(DMA1_Channel1, &dma_setup); DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE); //включаем прерывание по половине буфера DMA_ITConfig(DMA1_Channel1, DMA_IT_HT, ENABLE); //включаем прерывание по окончанию буфера DMA_Cmd(DMA1_Channel1, ENABLE); NVIC_EnableIRQ(DMA1_Channel1_IRQn); } |
Простой жизненный пример использования. Запускаем таймер, переводим мк в сон. Таймер по переполнению запускает АЦП, который преобразовывает 1, 2, 10 сколько нужно значений, DMA копирует их в буфер. И так пока не наберется половина буфера. А набраться она может очень не скоро, все зависит от нужной скорости оцифровки. И все это время микроконтроллер спит! Когда же половина наберется, сработает прерывание, мк просыпается, быстро обрабатывает данные и уходит дальше в сон.
На этом все, если будет, что дополнить то добавлю в процессе.
Лол, ни одного коммента.
Видимо чересчур глубокие темы затронул.
dma2_setup — это какого типа? Откуда?
А, нашёл.
А вы пробовали останавливать и запускать АЦП в таком режиме. ADC_Cmd(ADC1, DISABLE) ; ADC_Cmd(ADC1, ЕNABLE) ; почему то каналы в памяти смещаются и находятся не на своих местах..Как с этим бороться, как правильно останавливать и запускать процесс без пере инициализации?
Была подобная ситуация, нужно было постоянно включать/выключать преобразование ацп. Но у меня был запуск ацп по таймеру, поэтому когда нужно было останавливать считывание ацп в дма, останавливал таймер. Вообще чтобы после считывания ацп в дма больше ничего не копировалось нужно выключить запросы от дма ADC_DMARequestAfterLastTransferCmd(ADC1, DISABLE);
Если данные находятся не на своих местах, нужно смотреть выровнен ли буфер
Доброго времени суток, товарищи. Так выложите этот простой жизненный опыт «таймер-сон-ацп-дма». Просто я уже голову сломал, чтобы где-то найти этот «простой» пример. Мучаю библиотеку Hal, черт те что, но отступать некуда, уже пол работы с ней сделано. Пожалуйста, товарищи, покажите что-то рабочее или скиньте путёвки ссылку, чтоб там было меньше слов, больше дела
А в чем проблема? В данном примере поменять одну строчку с триггером АЦП. В кубе вообще пару галок поставить и он сам сгенерит код.