Первые шаги с STM32 и компилятором mikroC для ARM архитектуры — Часть 2, продолжение

Разобравшись с таймером, попробуем использовать его для чего, то кроме генерации временных интервалов. Чаще всего при помощи таймера генерируется ШИМ сигнал. Что это такое можно почитать на просторах Сети, например во всеведающий Википедии.
Основная прелесть ШИМ в том, что он позволяет при помощи ключей работающих в импульсном режиме (наиболее эффективном относительно потерь энергии) изменять действующее значение напряжение приложенного у той или иной нагрузке. Для ШИМ сигнала основными параметрами является общая длительность импульса и длительность его активного состояния (обычно высокого уровня сигнала).


Действующее значение напряжения имеет зависимость от длительности активного состояния импульса. Наш МК умеет генерировать ШИМ при помощи аппаратных функций таймера, не занимая драгоценное время ядра процессора.


image


Использовав МК в качестве ШИМ генератора можно управлять мощной нагрузкой (например нагревателем или электродвигателем). Для сглаживания высших гармоник проникающих в нагрузку от самого ШИМ модулятора необходимо применять LC или RC фильтр низких частот.
Одна из схем управления электродвигателем постоянного тока при помощи МК приведена ниже.
image


В данном случае сам двигатель имея индуктивную составляющую нагрузки выступает фильтром низких частот.


Каждый таймер имеет 4 независимо настраиваемых канала, каждый из которых может использоватся для генерации импульсов (compare mode) либо для их захвата (capature mode). Для начала рассмотрим какие конфигурационные регистры необходимы для того, чтобы настроить наш таймер в режим генерации ШИМ сигнала:


TIMx_CR1 — Регистр настройки таймера.
В прошлой части мы уже использовали бит TIMx_CR1.CEN (включение/отключение таймера) этого регистра. Еще важным для нас будет бит TIMx_CR1.DIR — направление счета. По умолчанию TIMx_CR1.CEN=0, счет прямой, показания инкрементируются до значения 65535 или значения, записанного в регистр TIMx_ARR. При установке бита TIMx_CR1.CEN=1 — счет обратный, при каждом такте входящего синхросигнала показания уменьшаются от 65535 до 0 (или до значения регистра (TIMx_ARR).


TIMx_ССR1, TIMx_ССR2, TIMx_ССR3, TIMx_ССR4 — регистры захвата-сравнения.
Запись значений в них задает скважность ШИМ импульсов. В режиме захвата импульсов в эти ячейки записывается значение регистра таймера TIMx_CNT при фиксации на входе соответствующего канала импульса.


TIMx_CCMR1, TIMx_CCMR2 — регистры настройки каналов захввата/генерации.
Настраивает режим работы таймера как источника импульсов либо ШИМ сигнала, либо как измерителя длительности импульсов (режим захвата). TIMx_CCMR1 относится к 1 и 2 каналу таймера, TIMx_CCMR2 — к 3 и 4. Далее мы будем рассматривать регистр TIMx_CCMR1, настройки TIMx_CCMR2 — аналогичны
image
TIMx_CCMR1.CC1S, используются для включения режима генерации импульсов либо режима захвата импульсов.


  • TIMx_CCMR1.CC1S= 00 — канал работает в режиме генерации импульсов;
  • TIMx_CCMR1.CC1S= 01 — канал работает в режиме захвата, сигнал захвата — TI1;
  • TIMx_CCMR1.CC1S= 10 — канал работает в режиме захвата, сигнал захвата — TI2;

для TIMx_CCMR1.CC2S, аналогично, за исключением того, что 


  • TIMx_CCMR2.CC1S= 01 — канал работает в режиме захвата, сигнал захвата — TI2;
  • TIMx_CCMR2.CC1S= 10 — канал работает в режиме захвата, сигнал захвата — TI1.

Данная настройка необходима для возможности подключения к 2 каналам сигнала с одного входа, что используется, например, для захвата ШИМ сигнала.
Остальные биты регистра TIMx_CCMR1 зависят от того включён режим генерации или режим захвата. Пока наша цель — генерация ШИМ (TIMx_CCMR1.CC1S= 00) сигнала, поэтому опишем назначение основных параметров для этого режима работы.


TIMx_CCMR1.CC1S, TIMx_CCMR1.CC2S — биты настройки сигнала на выходе соответствующего канала таймера.


  • TIMx_CCMR1.CC1S = 000 — совпадение регистра сравнения (CCR1) и счетного регистра (CNT) не влияет на состояние выхода таймера;
  • TIMx_CCMR1.CC1S = 001 — при CCR1=CNT сигнал на выходе канала =1;
  • TIMx_CCMR1.CC1S = 010 — при CCR1=CNT сигнал на выходе канала =0;
  • TIMx_CCMR1.CC1S = 010 — при CCR1=CNT сигнал на выходе канала инвертируется;
  • TIMx_CCMR1.CC1S =100 — на выходе постоянно 0;
  • TIMx_CCMR1.CC1S =101 — на выходе постоянно 1;
  • TIMx_CCMR1.CC1S = 110 — режим ШИМа №1 (Прямой ШИМ). Если счетный регистр работает на сложение: при CNTCCR1 сигнал на выходе = 0, иначе 1.
  • TIMx_CCMR1.CC1S = 111 — режим ШИМа №2 (Обратный ШИМ). Если счетный регистр работает на сложение: при CNTCCR1 сигнал на выходе = 1, иначе 0 .

TIMx_CCER — Регистр включающий и отключающий соответствующий канал генерации/захвата сигнала таймером.
image


  • TIMx_CCER1.CC1E, TIMx_CCER1.CC2E, TIMx_CCER1.CC3E, TIMx_CCER1.CC4E  — включает и отключает соответствующий канал генерации/захвата.
  • TIMx_CCER1.CC1P, TIMx_CCER1.CC2P, TIMx_CCER1.CC3P, TIMx_CCER1.CC4P  — инверсия полярности выходного сигнала для режима генерации и выбор переднего либо заднего фронта импульса для режима захвата.
    Для начала необходимо настроить порт микроконтроллера для работы с выходом таймера. Этот режим работы порта называется: Alternate Function. Для его включения в microC есть функция:

GPIO_Alternate_Function_Enable(&module);

здесь &module — указатель на модуль, реализующий альтернативную функцию порта ввода/вывода. например для 3 канала TIM3 это &_GPIO_MODULE_TIM3_CH3_PB0, для 2 канала TIM2: &_GPIO_MODULE_TIM2_CH2_PA1. В общем, указатель имеет вид &_GPIO_MODULE_TIMa_CHb_Pxy, где х — порт, а у — вывод, к которому согласно даташита на конкретный микроконтроллер подключен b-канал а-таймера (Здесь очень может помочь Code Assistsnt (ctrl-пробел), вводим _GPIO_MODULE и выбираем то что нам нужно из выпадающего списка).


Давайте попробуем сгенерировать ШИМ сигнал со скважностью 25% на выводе PB0 нашего микроконтроллера:


void main()
{

   GPIO_Alternate_Function_Enable(&_GPIO_MODULE_TIM3_CH3_PB0); // Включаем альтернативную функцию для вывода РВ0

  RCC_APB1ENRbits.TIM3EN=1; //Подаем тактирование на TIM3

  TIM3_PSC=7199; // Предделитель равен 7200, таймер тактируется с частотой 10 КГц.

  TIM3_ARR=99; // Таймер считает от 0 до 99, и обнуляется. 10000 / (99+1) = 100 Гц. Это и будет частота сигнала на выходе нашего ШИМа

  TIM3_CCR3=25; // При значении 25 сигнал на выходе меняет уровень с высокого  на низкий, из 100 тактов 25 уровень на выходе будет высоким, остальные 75 - низким.

  TIM3_CCERbits.CC3E=1; // Включаем 3 выход нашего таймера

  TIM3_CCMR2_Outputbits.OC3M=0b110; // Конфигурируем режим выхода  - OC3M=110 - прямой ШИМ.

  TIM3_CR1bits.CEN=1; //Включаем таймер
  while(1)
    {

    }
}

На выходе PB0 получаем такую картину:


image


В microC ШИМ модулятор можно настроить при помощи встроенных функций. Для этого сначала иннициализируем таймер в режим ШИМ модулятора при помощ ифункции


period = PWM_TIMх_Init(freg);

  • freg — частоnа работы ШИМ модулятора в герцах;
  • period — функция возвращает период нашего генератора: количество тактов таймера за один период выходного сигнала, по сути значение регистра TIMх_ARR

После этого установим скважность генерируемых импульсов нужного нам канала при помощи функции:


PWM_TIMх_Set_Duty(duty, _PWM_INVERTED /_PWM_NON_INVERTED, channel);

  • duty — скважность нашего ШИМ сигнала, по сути значение регистра TIMх_CCRy, изменяется от 0 до значения period;
  • _PWM_INVERTED или _PWM_NON_INVERTED — прямой или обратный шим;
  • channel — канал ШИМ модулятора, имеет значения:
    • _PWM_CHANNEL1 — для 1 канала;
    • _PWM_CHANNEL2 — для 2 канала;
    • _PWM_CHANNEL3 — для 3 канала;
    • _PWM_CHANNEL4 — для 4 канала;

Наконец запустим наш ШИМ генератор функцией


PWM_TIM3_Start(channel, &module)

  • channel — канал ШИМ модулятора;
  • &module — указатель на модуль, выводящий сигнал ШИМ на физический вывод микроконтроллера, значение то же, что мы указывали при переводе порта микроконтроллера командой GPIO_Alternate_Function_Enable (&module) в режим Alternate Function для подключения выхода таймера к выводу МК.

Напишем простую программу увеличивающую яркость встроенного в нашу плату светодиода при нажатии на кнопку.


unsigned short state; // наша переменная состояний, бит1 используется для определения отпускания кнопки.
unsigned int period; //переменная хранящая значения периода импульсов 
unsigned short dutyled;//переменная хранящая скважность импульсов
void main()
{
  dutyled=10; //начнем с 10%
  GPIO_Alternate_Function_Enable(&_GPIO_MODULE_TIM3_CH4_PB1);  //Включаем альтернативную функцию для пина PB0  GPIO_Digital_Input(&GPIOb_BASE, _GPIO_PINMASK_8); 
  period = PWM_TIM3_Init(5000); //Иннициализируем ШИМ с частотой 5000 Гц
  PWM_TIM3_Set_Duty((period/100)*dutyled, _PWM_NON_INVERTED, _PWM_CHANNEL4); //устанавливаем скважность для 4 канала ШИМ Таймера3
  PWM_TIM3_Start(_PWM_CHANNEL4, &_GPIO_MODULE_TIM3_CH4_PB1); // Запускаем 4 канал ШИМ, выводем на пин PB1 

  while(1)
   {
     if (Button(&GPIOb_IDR, 8, 1, 1))
           state.b1=1;

     if (state.b1 && Button(&GPIOb_IDR, 8, 1, 0))
           {
             //Кнопку нажали и отпустили
             state.b1= ~state.b1; 
             dutyled = dutyled + 10;
             if (dutyled >= 100) //если скважность больше 100% то сбрасываем в 0
             dutyled=10;
             PWM_TIM3_Set_Duty((period/100)*dutyled, _PWM_NON_INVERTED, _PWM_CHANNEL4); // Изменяем скважность импульсов
           }
   }
}

Теперь при каждом нажатии на кнопку наш светодиод начинает светится ярче. Скважность ШИМ сигнала увеличивается после каждого нажатия на 10% — увеличивается и яркость светодиода.


Давайте при помощи встроенных функций microC сгенерируем 2 ШИМ сигнала на выходах каналов CH2 (PA7) и CH3 (PB0) TIM3 со скважностями соответственно 15% и 55%


unsigned int period, duty1, duty2; //переменные хранящие значения периода и скважности импульсов 
void main()
{
  duty1=55; //Заносим в переменные значения скважности импульсов
  duty2=15;

  GPIO_Alternate_Function_Enable(&_GPIO_MODULE_TIM3_CH3_PB0); //Включаем альтернативную функцию для пина PB0
  GPIO_Alternate_Function_Enable(&_GPIO_MODULE_TIM3_CH2_PA7);//Включаем альтернативную функцию для пина PА7

  period = PWM_TIM3_Init(1000); // Иннициализируем ШИМ с частотой 1000 Гц

  PWM_TIM3_Set_Duty((period/100)*duty1, _PWM_NON_INVERTED, _PWM_CHANNEL3);  //устанавливаем скважность для 3 канала ШИМ Таймера3
  PWM_TIM3_Set_Duty((period/100)*duty2, _PWM_NON_INVERTED, _PWM_CHANNEL2); //устанавливаем скважность для 2 канала ШИМ Таймера3

  PWM_TIM3_Start(_PWM_CHANNEL3, &_GPIO_MODULE_TIM3_CH3_PB0); //Запускаем 3 канал ШИМ, выводем на пин PB0
  PWM_TIM3_Start(_PWM_CHANNEL2, &_GPIO_MODULE_TIM3_CH2_PA7); // Запускаем 2 канал ШИМ, выводем на пин PA7

  while(1)
   {

   }

Подключив осциллограф к выводам PA7 и PB0 можем наблюдать наши сигналы:


image


Осциллограф измеряет действующие значения RMS, и мы можем наблюдать что они зависят от скважности наших импульсов. Этим можно пользоваться для генерации ШИМом произвольных сигналов, например синусоиды. Давайте попробуем это сделать:


Таймер 3 будет будет генерировать ШИМ сигнал, скважность которого мы будем изменять по закону синусоиды. Для эксперимента разобьем период на 40 частей (с шагом в 9 градусов) и для них будем вычислять значения синуса, и записывать нормированные по значению периода ШИМа в регистр CCR нужного нам канала, тем самым изменяя скважность импульсов и формируя на выходе пина таймера МК синусоиду. STM32 достаточно приозводительный МК, поэтому для невысоких частот, порядка 100-н герц, можно пользоваться непосредственно Сишной функцией SIN (). Настроим Таймер 2 на вызов прерываний с частотой 4000 Гц. Учитывая что один период синусоиды у нас занимает 40 отсчетов (360 / 9) мы должны на экране осциллографа увидеть синусоиду с частотой 100 Гц.


unsigned int cpwm; //переменная для текущего значения скважности импульсов
unsigned int ugol = 0; //переменная для угла в градусах
const float PI=3.1416; //константа ПИ=3,1416

void main()

{
  GPIO_Alternate_Function_Enable(&_GPIO_MODULE_TIM3_CH3_PB0);

  // Настраиваем 3 таймер в режим генерации ШИМ сигнала частотой 72 КГц
  RCC_APB1ENRbits.TIM3EN=1;
  TIM3_CCERbits.CC3E=1;
  TIM3_CCMR2_Outputbits.OC3M=0b110;
  TIM3_CCR3 = 1000;
  TIM3_ARR = 1999;
  TIM3_PSC=0;
  TIM3_CR1bits.CEN=1;

  // Настраиваем 4 таймер для генерации прерываний с частотой 4000 Гц
  RCC_APB1ENR.TIM4EN = 1;
  TIM4_CR1.CEN = 0;
  TIM4_PSC = 719;
  TIM4_ARR = 24; 
  NVIC_IntEnable(IVT_INT_TIM4); // Разрешаем вектор прерываний от таймера TIM4
  TIM4_DIER.UIE = 1; // Разрешаем таймеру вызывать прерывания
  TIM4_CR1.CEN = 1; //Включаем таймер 4

       while(1)
      {

      }
}
void Timer2_interrupt() iv IVT_INT_TIM4
{
  TIM4_SR.UIF = 0;// Сбрасываем флаг прерывания 
  cpwm= 1000 + (1000 * sin((PI*ugol)/180)); //Вычисляем текущее значение синусоиды
  TIM3_CCR3 = cpwm; //Пишем вычисленное значение в регистр ССR таймера
  ugol = ugol + 9; //шаг в 9 градусов
  if (ugol > 359) ugol = 0; //замыкаем аргумент функции sin в ее области определения
}

сигнал с выхода нашего МК нужно пропустить через фильтр низких частот, для того чтобы отсечь частоту, на которой работает ШИМ генератор. Так как частота нашей синусоиды 100 Гц, частоту среза фильтра выберем 300 Гц.
image
Вот такой сигнал показывает нам осцилограф:


image


Вот и подошла к концу вторая часть нашего цикла статей. В следующей части я постараюсь рассказать о работе с последовательным интерфейсом UART, и попробуем использовать возможности МК для связи с GSM модулем.

Комментарии (2)

  • 10 января 2017 в 20:39

    0

    >… о работе с параллельным интерфейсом UART
    Или поставьте запятую в предложении или исправьте слово параллельный на последовательный :)
    • 10 января 2017 в 22:11

      0

      Очепятка :) Спасибо:) Испарвил :)

© Habrahabr.ru