Форум программистов, компьютерный форум, киберфорум
Наши страницы
locm
Войти
Регистрация
Восстановить пароль
Оценить эту запись

STM32F103C8T6 - Timer DMA GPIO

Запись от locm размещена 06.03.2018 в 23:58
Обновил(-а) locm 26.05.2018 в 10:39
Метки dma, embitz, gpio, stm32

У модуля DMA довольно простая задача - копировать данные из одного места в другое с заданными настройками. Запустить копирование можно несколькими способами, как программно, так и аппаратно событием от периферии. Сейчас пойдет речь о запуске DMA по событию от таймера. Для примера сделаем полностью аппаратную "мигалку светодиодом" подключенную к порту PC13.
Код.
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
#include "stm32f10x_conf.h"
 
extern uint32_t SystemCoreClock;
volatile uint16_t DmaBuff[2] = {0, 1<<13};
 
void Config(void)
{
 
    // Настройка GPIO.
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // Вкл. порт GPIOC,
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);  // и модуль альтернативных функций.
    GPIO_SetBits(GPIOC, GPIO_Pin_13);
 
    {
        GPIO_InitTypeDef GPIO;
 
        GPIO.GPIO_Pin = GPIO_Pin_13;
        GPIO.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO.GPIO_Speed = GPIO_Speed_50MHz;
 
        GPIO_Init(GPIOC, &GPIO); // Настройка GPIOC.13 в режим выхода.
    }
 
   // Настройка DMA.
   RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // Вкл. модуль DMA1.
   {
        DMA_InitTypeDef Dma;
 
        Dma.DMA_MemoryBaseAddr = (uint32_t) DmaBuff;           // Источник данных массив DmaBuff.
        Dma.DMA_PeripheralBaseAddr = (uint32_t) &(GPIOC->ODR); // Приемник - регистр ODR порта GPIOC.
        Dma.DMA_DIR = DMA_DIR_PeripheralDST;                   // Периферия является приемником данных.
        Dma.DMA_BufferSize = 2;                                // Размер данных в "словах".
        Dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;     // Не изменять адрес приемника.
        Dma.DMA_MemoryInc = DMA_MemoryInc_Enable;              // Увеличивать адрес памяти.
        Dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // Размер "слова" - 2 байта.
        Dma.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;         // Размер "слова" - 2 байта.
        Dma.DMA_Mode = DMA_Mode_Circular;                      // Циклическая работа.
        Dma.DMA_Priority = DMA_Priority_Low;                   // Приоритет канала DMA.
        Dma.DMA_M2M = DMA_M2M_Disable;                         // Не копирование из памяти в память.
        DMA_Init(DMA1_Channel2, &Dma);
   }
 
   // Настройка таймера.
   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // Вкл. таймер TIM2.
 
   {
        TIM_TimeBaseInitTypeDef Tim;
        TIM_TimeBaseStructInit(&Tim);
 
        Tim.TIM_Prescaler = SystemCoreClock / 10000 - 1; // Настройка предделителя таймера.
        Tim.TIM_Period = 10000 - 1;
 
        TIM_TimeBaseInit(TIM2, &Tim);
    }
 
    TIM_GenerateEvent(TIM2, TIM_EventSource_Update); // Генерация события при переполнении таймера.
    TIM_DMACmd(TIM2, TIM_DMA_Update, ENABLE);        // Разрешаем запуск DMA по событию переполнения.
    DMA_Cmd(DMA1_Channel2, ENABLE);                  // Разрешаем работу 2 канала модуля DMA1.
    TIM_Cmd(TIM2, ENABLE);                           // Разрешаем работу таймера TIM2.
}
 
int main(void)
{
    SystemCoreClockUpdate();
    Config();
 
    while(1)
    {
    }
}
В массиве DmaBuff находятся данные выводимые в порт через DMA. В нулевом элементе массива находится 0, а в первом - установлен 13 бит который соответствует выводу порта PC13. Поочередная запись нулевого и первого элемента массива в порт приведет к изменению логического уровня на выводе PC13.
В функции Config сперва настраивается порт PC13 для работы на выход. Далее второй канал модуля DMA1 конфигурируется таким образом чтобы при запуске каждой транзакции DMA, данные копировались из массива DmaBuff в регистр ODR порта GPIOC. Работа производится циклически и как только будет достигнут конец массива, копирование начнется сначала. После настраивается таймер TIM2 таким образом чтобы он переполнялся раз в секунду и разрешается генерация события от переполнения. Затем разрешается запуск транзакций DMA от события переполнения таймера TIM2 и разрешаются работа второго канала модуля DMA1 и таймера. После всех этих действий, таймер начинает работать и генерировать события каждую секунду, от которого запускается работа DMA и копирование данных из массива в порт.
Вот таким довольно простым методом можно сделать полностью аппаратную "мигалку светодиодом". Светодиод мигает, а процессор занимается своими делами.
Но у этого метода есть существенный недостаток - запись в регистр ODR влияет на весь порт, а не на отдельные выходы. Чтобы избавится от этого недостатка, нужно использовать вместо ODR, регистр BSRR. Это 32-ух битный регистр, в котором старшие 16 бит используются установки логического 0 на выводах порта, а младшие 16 бит - для установки логической 1.
Такой метод позволяет изменять состояние одного или нескольких выводов порта не затрагивая остальные.
Для этого в код нужно внести небольшие изменения. Объявление массива.
C
1
volatile uint32_t DmaBuff[2] = {1<<13<<16, 1<<13}; // Настройка DMA.
Настройка DMA.
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   // Настройка DMA.
   RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // Вкл. модуль DMA1.
   {
        DMA_InitTypeDef Dma;
 
        Dma.DMA_MemoryBaseAddr = (uint32_t) DmaBuff;           // Источник данных массив DmaBuff.
        Dma.DMA_PeripheralBaseAddr = (uint32_t) &(GPIOC->BSRR); // Приемник - регистр BSRR порта GPIOC.
        Dma.DMA_DIR = DMA_DIR_PeripheralDST;                   // Периферия является приемником данных.
        Dma.DMA_BufferSize = 2;                                // Размер данных в "словах".
        Dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;     // Не изменять адрес приемника.
        Dma.DMA_MemoryInc = DMA_MemoryInc_Enable;              // Увеличивать адрес памяти.
        Dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; // Размер "слова" - 4 байта.
        Dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;         // Размер "слова" - 4 байта.
        Dma.DMA_Mode = DMA_Mode_Circular;                      // Циклическая работа.
        Dma.DMA_Priority = DMA_Priority_Low;                   // Приоритет канала DMA.
        Dma.DMA_M2M = DMA_M2M_Disable;                         // Не копирование из памяти в память.
        DMA_Init(DMA1_Channel2, &Dma);
   }
А теперь о том где это может пригодится на практике. Мигать светодиодом не имеет большого смысла. Проще добиться того же результата таймером в ШИМ режиме. Применять подобный метод целесообразно например для аппаратной реализации динамической индикации, т. е. без прерываний.
Подобным методом можно не только выводить данные в порт, но и считывать из него, что позволит аппаратно реализовать работу например с 1-Wire, DHT22 и др устройствами.

Программа тестировалась на "китайской" плате.
Нажмите на изображение для увеличения
Название: STM32F103C8T6.jpg
Просмотров: 269
Размер:	58.9 Кб
ID:	4715
Вложения
Тип файла: 7z STM32F103C8T6_DMA_GPIO.7z (197.2 Кб, 64 просмотров)
Размещено в Микроконтроллеры
Просмотров 802 Комментарии 5
Всего комментариев 5
Комментарии
  1. Старый комментарий
    Цитата:
    Подобным методом можно не только выводить данные в порт, но и считывать из него
    Если можно намекните как это сделать
    желательно по внешнему прерыванию...
    Запись от Khludenkov размещена 12.03.2018 в 09:01 Khludenkov вне форума
  2. Старый комментарий
    Необходимо заменить адрес регистра ODR, на IDR.
    Указать что источник данных - периферия, заменив DMA_DIR_PeripheralDST на DMA_DIR_PeripheralSRC.
    Дальнейшие действия зависят от того что в итоге нужно получить. Возможно таймер в режиме захвата совместно с DMA лучше подойдет для этой задачи.
    Запись от locm размещена 12.03.2018 в 16:26 locm вне форума
  3. Старый комментарий
    Спасибо!
    Т.е. можно сделать тактирование таймера от внешнего вывода и на каждый его такт делать прерывание...
    Запись от Khludenkov размещена 13.03.2018 в 08:53 Khludenkov вне форума
  4. Старый комментарий
    Напишите (лучше на форуме в разделе микроконтроллеров) максимально подробно что требуется. Потому что при такой постановке вопроса, наиболее подходящим будет внешние прерывание (без использования таймера).
    Запись от locm размещена 13.03.2018 в 13:23 locm вне форума
  5. Старый комментарий
    Запись от Khludenkov размещена 13.03.2018 в 15:19 Khludenkov вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru