Администрация форума не несёт ответственности за достоверность информации и оставляет за собой право редактировать или в особых случаях даже удалять посты без предупреждения. Спасибо за понимание.

Программирование ATMEL в BASCOM.

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.



STM32F103C8T6 - Timer и DMA

Сообщений 1 страница 4 из 4

1

У модуля DMA довольно простая задача - копировать данные из одного места в другое с заданными настройками. Запустить копирование можно несколькими способами, как программно, так и аппаратно событием от периферии. Сейчас пойдет речь о запуске DMA по событию от таймера. Для примера сделаем полностью аппаратную "мигалку светодиодом" подключенную к порту PC13.
Код.

Код:
#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.

Его описание

Старшие 16 бит — для сброса соответствующих пинов в 0. 0 — ничего не делает, 1 — сбрасывает соответствующий бит.
Младшие 16 бит — для установки битов в 1. Точно так же, запись «0» ничего не делает, запись «1» устанавливает соответствующий бит в 1.

Регистр только для записи — он сбрасывается в ноль на каждом такте APB2.

http://catethysis.ru/stm32-gpio/

http://s7.uploads.ru/t/WvwL2.png

Такой метод позволяет изменять состояние одного или нескольких выводов порта не затрагивая остальные.
Для этого в код нужно внести небольшие изменения. Объявление массива.

Код:
volatile uint32_t DmaBuff[2] = {1<<13<<16, 1<<13};

Настройка DMA.

Код:
      // Настройка 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 и др. устройствами. :)

Файлы. http://pure-basic.narod.ru/forum_files/ … MA_GPIO.7z

+3

2

Ай да красавец! Респект. Никак у меня нету времени сделать то-же самое с STM...

0

3

Пётр написал(а):

Применять подобный метод целесообразно например для аппаратной реализации динамической индикации, т. е. без прерываний.

Аппаратная динамическая индикация (без прерываний). http://pure-basic.narod.ru/forum_files/ … 8T6_Led.7z
В архиве два примера. Первый отображает цифры 1234 на индикаторе, а второй - термометр на DS18B20.

Схема.
http://s7.uploads.ru/t/XYJIl.png

+2

4

Симуляция в Proteus 8.7 конструкции из предыдущего сообщения. http://pure-basic.narod.ru/forum_files/ … Proteus.7z

+2