W806 - GPIO и таймеры. часть 1.

Продолжаем знакомство с китайским микроконтроллером Winner Micro W806. А с чего обычно эмбеддер начинает осваивать новый для него микроконтроллер? Правильно, мигает светодиодиком. Не будем нарушать эту славную традицию. На отладочной плате распаяны три синих светодиода, подключенные к выводам PB0 — PB2. Писать будем без использования HAL, напрямую в регистры.
Кому интересно — приглашаю под кат.

Начнем с таймеров. Их здесь 6 шт, все абсолютно идентичны друг другу, разрядность регистров счета 32 бита. Тактирование предусмотрено только внутреннее, от шины APB. Прерывание на таймеры одно на всех, по достижению счетным регистром заданного значения. Какие-либо периферийные блоки или пины GPIO к таймерам никак не привязаны, это вам не STM. Для управления таймерами нам выданы 14 32-битных регистров, из них два регистра общие для всех таймеров — TMR_CONFIG и CR, и по два регистра на каждый таймер отдельно — TIMx_PRD и TIMx_CNT(здесь и далее имена регистров будут соответствовать именам из файла wm_regs.h). Регистр TIMx_PRD соответствует регистру TIMx_ARR в микроконтроллерах STM, сюда загружается значение, до которого досчитает таймер, прежде чем сбросится и начнет счет заново. Кстати, таймеры считают только «вверх», обратный счет не предусмотрен. В регистрах TMRx_CNT содержится текущее значение таймера, эти регистры доступны только для чтения.
Регистр TMR_CONFIG должен содержать значение предделителя, чтобы общая частота тактирования таймеров равнялась 1 Мгц, для достижения минимального кванта времени 1 мкс. Тк. по умолчанию шина APB имеет частоту 40 МГц, то для установки тактирования таймеров 1 МГц нужно записать в этот регистр значение 40-1 = 39. По умолчанию оно такое и есть. Этот предделитель общий для всех таймеров.
Следующий общий регистр — CR, config register.
Его структура показана на рисунке
Каждый таймер управляется пятью битами, их описание для Таймера0 приведено ниже, для других таймеров значения битов идентичны.

TIM_CR_TIM0_UNIT — бит единиц счета. Если он равен «1», то тик таймера происходит каждую 1 миллисекунду, если «0» — каждую микросекунду. Вот для чего используется регистр TMR_CONFIG. Видимо, установка этого бита в «1» включает для каждого конкретного таймера какой-то внутренний предделитель на 1000. По умолчанию все таймеры тикают в миллисекундах (как впоследствии выяснится, не все).
TIM_CR_TIM0_MODE — бит режима работы таймера. При значении «1» таймер после включения досчитает до заданного значения и выключится, выставив перед этим флаг прерывания, и вызвав обработчик, если само прерывание разрешено. При значении «0» таймер после каждого переполнения начинает счет заново, с нуля.
TIM_CR_TIM0_EN — бит разрешения работы таймера. Если таймер настроен на однократный счет, этот бит сбрасывается после окончания счета. Для включения таймера нужно установить этот бит в «1». По умолчанию все таймеры выключены. При сбросе этого бита в «0» также обнуляется и счетный регистр TMRx_CNT.
TIM_CR_TIM0_TIE — бит разрешения прерывания.
TIM_CR_TIM0_TIF — флаг прерывания, или же окончания счета. Выставляется в «1» когда таймер досчитывает до заданного значения. Для сброса бита нужно записать в него «1»

GPIO в W806 представлены двумя группами — GPIOA и GPIOB. У каждого свой набор регистров, но назначение битов в этих регистрах одинаковое, с той лишь разницей, что GPIOA — 16-битный порт, а GPIOB — 32-битный. Конкретно у W806 физически присутствуют не все биты порта B. Все GPIO имеют нагрузочную способность 12 мА, но я нигде не нашел указания, какой ток может выдержать кристалл в общем, через шины питания. А вот скоростные характеристики для пинов разные, они указаны в даташите, который прикреплен к предыдущей моей заметке.
Для управления портами доступно аж 15 регистров. Рассмотрим для примера регистры порта B:
DATA — регистр данных порта, он используется и для чтения состояния пинов порта настроенных на вход, и для установки выходных значений порта для пинов, настроенных на выход.
DATA_B_EN — регистр разрешения работы пинов порта (для GPIOA он тоже имеет название DATA_B_EN, data bit enable). При сброшенных битах в этом регистре чтение или запись в соответствующие биты порта запрещена. После старта имеет значение 0xFFFFFFFF (0xFFFF для порта А), то есть работа GPIO разрешена.
DIR — направление работы пина. Традиционно, «1» — выход, «0» — вход. По дефолту — все входы.
PULLUP_EN — регистр включения подтяжки к питанию. Имеет активный низкий уровень, то есть для включения подтяжки на пине нужно сбросить соответствующий бит в этом регистре. По дефолту — все «1», подтяжка вверх отключена.
PULLDOWN_EN — комплементарный предыдущему, регистр. Активный уровень — высокий, для включения подтяжки к земле необходимо записать в соотв. бит «1», по умолчанию имеет значение — все «0», подтяжка вниз отключена.
Внутренние резисторы подтяжки имеют номиналы 40К для pull-up и 49К для pull-down.
AF_SEL — бит выбора разрешения альтернативной функции пина GPIO. Совместно с регистрами AF_S1 и AF_S0 управляет альтернативными функциями каждого пина. Их зависимости сведены в таблицу

Значения по умолчанию — все «1» все «0» (исправлено) для регистра AF_SEL и все «0» для регистров AF_S1 и AF_S0.
IS — регистр настройки внешних прерываний. «0» — прерывание по перепаду уровней. «1» — прерывание по постоянному уровню. По дефолту — все «0».
IBE — выбор фронта для срабатывания прерывания. «0» — прерывание по фронту зависит от бита в регистре IEV, «1» — оба фронта, и передний и задний, вызывают прерывание. По дефолту — все «0».
IEV — выбор активного фронта для внешнего прерывания, «0» — спадающий фронт или низкий уровень (если прерывание по уровню), «1» — восходящий фронт или высокий уровень. По дефолту — все «0».
IE — регистр разрешения прерываний. Если бит в этом регистре установлен в «1», ножка может генерировать прерывание, если бит сброшен в «0» — изменения логических уровней на этой ножке не вызывают прерываний. По дефолту — все «0».
RIS — GPIO Raw Interrupt Status Register — статусный регистр внешних прерываний. По дефолту — все «0».
MIS — GPIO Masked Interrupt Status Register — статусный регистр внешних прерываний. По дефолту — все «0».
С этими двумя регистрами пока не очень понятно, в RM про них сказано следующее:
GPIO bare interrupt status (before MASK), each BIT corresponds to the corresponding GPIO line, 1'bx:
[x] = 0, no interrupt is generated for GPIO[x]
[x] = 1, GPIO[x] has an interrupt
про первый и
Interrupt status after GPIO masking (after MASK), each BIT corresponds to the cor-responding GPIO line, 1'bx:
[x] = 0, no interrupt is generated for GPIO[x] (after MASK)
[x] = 1, GPIO[x] interrupt is generated (after MASK)
про второй. Как только плотнее разберусь с внешними прерываниями, дополню статью.
IC — регистр сброса статуса внешних прерываний. Как и в контроллерах STM, флаги всех прерываний не сбрасываются автоматически, при входе в обработчик, поэтому их нужно сбрасывать вручную, иначе после выхода из обработчика прерывание будет вызвано снова. Запись «1» в этот регистр сбрасывает флаг внешнего прерывания соответствующего пина. По дефолту — все «0».

Ну и наконец, давайте уже помигаем светодиодами на плате. Компилируем код:
#include "wm_hal.h"

int main(void)
{
    GPIOB->DIR |= GPIO_PIN_0;		// пин B0 на выход
    TIM->TIM0_PRD = 500;			// период таймера 0 - 500 мс
    TIM->CR &= ~TIM_CR_TIM0_MODE;	// режим счета - непрерывный
    TIM->CR |= TIM_CR_TIM0_EN;		// включаем таймер

    while (1)
    {
        if(TIM->CR & TIM_CR_TIM0_TIF)	// периодически проверяем флаг окончания счета
        {
            GPIOB->DATA ^= GPIO_PIN_0;	// инвертируем ножку PB0 
	    TIM->CR |= TIM_CR_TIM0_TIF; // сбрасываем флаг таймера 0
        }
    }
}
прошиваем, и видим, как светодиод на плате замигал с частотой 1 Гц, как и было задумано, ведь период таймера 500 мс. Настройки предделителя TMR_CONFIG и единицы счета таймера я оставил по умолчанию — 39 и «миллисек.»
Теперь давайте проверим, все ли йогурты одинаково полезны таймеры идентичны.
Меняем в коде таймер 0 на таймер 1, больше ничего не трогаем:
#include "wm_hal.h"

int main(void)
{
	GPIOB->DIR |= GPIO_PIN_0;	// пин B0 на выход
	TIM->TIM1_PRD = 500;		// период таймера 0 - 500 мс
	TIM->CR &= ~TIM_CR_TIM1_MODE;	// режим счета - непрерывный
	TIM->CR |= TIM_CR_TIM1_EN;	// включаем таймер
  while (1)
  {
        if(TIM->CR & TIM_CR_TIM1_TIF)	// периодически проверяем флаг окончания счета
	{
	    GPIOB->DATA ^= GPIO_PIN_0;	// инвертируем ножку PB0 
	    TIM->CR |= TIM_CR_TIM1_TIF; // сбрасываем флаг таймера 0
	}
    }
}
прошиваем, и светодиод… не мигает. Просто горит. Что такое? Смотрим осциллографом и видим:
меандр 1 КГц, у которого период 500 мкс. Но ведь RM нам обещал состояние регистров по умолчанию —
И сколько еще таких чудных открытий впереди?!
Следующий код использует прерывание от таймеров, чтобы мигать вторым светодиодом:
#include "wm_hal.h"

#define ISR __attribute__((isr)) void

ISR TIM0_5_IRQHandler(void)	// обработчик прерывания от таймеров
{
	GPIOB->DATA ^= GPIO_PIN_1;	// инвертируем ножку PB0 
	TIM->CR |= TIM_CR_TIM0_TIF; // сбрасываем флаг таймера 0
}

int main(void)
{
    GPIOB->DIR |= GPIO_PIN_0 | GPIO_PIN_1;   // пины B0 и B1 на выход
    TIM->TIM1_PRD = 500;		     // период таймера 1 - 500 мс
    TIM->CR &= ~TIM_CR_TIM1_MODE;            // режим счета - непрерывный
    TIM->CR |= TIM_CR_TIM1_EN | TIM_CR_TIM1_UNIT;    // включаем таймер1, явно указываем тики - миллисек
	
    TIM->TIM0_PRD = 100;                 // период таймера 0 - 100 мс
    TIM->CR &= ~TIM_CR_TIM0_MODE;        // режим счета - непрерывный
    TIM->CR |= TIM_CR_TIM0_EN | TIM_CR_TIM0_TIE;    // включаем таймер и разрешаем прерывание от него
    NVIC_EnableIRQ(TIM_IRQn);            // разрешаем глобально прерывания от таймеров

    while (1)
    {
        if(TIM->CR & TIM_CR_TIM1_TIF)	// периодически проверяем флаг окончания счета
        {
            GPIOB->DATA ^= GPIO_PIN_0;	// инвертируем ножку PB0 
	    TIM->CR |= TIM_CR_TIM1_TIF; // сбрасываем флаг таймера 1
	}
    }
}

После того, как для Таймера1 явно указаны единицы счета — «мс», все работает как задумано. Этот глюк присутствует только на Таймере1, остальные имеют настройки по умолчанию как указано в RM.
Пришло время поиграть с регистром TMR_CONFIG. Нам рекомендуют подогнать его значение так, чтобы поделить частоту шины APB 40 МГц до 1 МГц т.е. установить значение 39 (40 — 1). Установим следующие настройки таймеров:
#include "wm_hal.h"

#define ISR __attribute__((isr)) void

ISR TIM0_5_IRQHandler(void)	// обработчик прерывания от таймеров
{
    GPIOB->DATA ^= GPIO_PIN_1;	// инвертируем ножку PB0 
    TIM->CR |= TIM_CR_TIM5_TIF; // сбрасываем флаг таймера 5
}

int main(void)
{
    GPIOB->DIR |= GPIO_PIN_0 | GPIO_PIN_1;		// пины B0 и B1 на выход
    TIM->TMR_CONFIG = 3;        // частота тактирования блока таймеров 40 МГц / (TMR_CONFIG+1) = 10 МГц 
    TIM->TIM5_PRD = 10;		// период таймера5 - 10 (предположительно, миллисекунд, но это не точно :))
    TIM->CR &= ~TIM_CR_TIM5_MODE;	// режим счета - непрерывный
    TIM->CR |= TIM_CR_TIM5_EN | TIM_CR_TIM5_TIE;    // включаем таймер и разрешаем прерывание от него
    NVIC_EnableIRQ(TIM_IRQn);    // разрешаем глобально прерывания от таймеров
	
    while (1)
    {}
}

Смотрим в овцелограф:

Итог: мы подняли частоту блока таймеров в 10 раз. Таким образом, можно гибко настраивать таймеры под необходимые параметры счета времени.
Ну и наконец, совсем оху.. обнаглеем и установим TIM->TMR_CONFIG = 0; и TIM->TIM5_PRD = 0;

Не знаю, зачем вообще такую дичь творить, но так можно — это факт! :)

Функция timer_delay, использующая один из аппаратных таймеров для формирования задержки:
#define DELAY_MS 1
#define DELAY_US 0
void timer_delay(uint32_t delay_ticks,  uint8_t delay_units)
{
    TIM->CR &= ~(TIM_CR_TIM5_EN);		// выключаем таймер, на всякий случай
    TIM->TIM5_PRD = delay_ticks;		// загружаем значение задержки в регистр периода
    TIM->CR |= TIM_CR_TIM5_MODE;		// режим счета - одиночный
    TIM->CR &= ~(TIM_CR_TIM5_UNIT_Msk);	// сбрасываем единицы счета
    TIM->CR |= delay_units << TIM_CR_TIM5_UNIT_Pos;	// устанавливаем единицы счета
    TIM->CR |= TIM_CR_TIM5_EN;			// включаем таймер
    while(!(TIM->CR & TIM_CR_TIM5_TIF));
}

В коде использован #include «wm_hal.h», но это просто для удобства, чтобы не инклюдить каждый заголовочник периферии отдельно.

На этом первая часть о GPIO и таймерах подходит к концу. В следующей части освоим внешние прерывания и поиграем с альернативными функциями портов. Всем спасибо за просмотр, подписывайтесь на мой канал, ставьте лайки!
  • +5
  • 22 января 2022, 19:00
  • finskiy

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

RSS свернуть / развернуть
Ну и наконец, совсем оху… обнаглеем и установим TIM->TMR_CONFIG = 0; и TIM->TIM5_PRD = 0;
И на какой частоте в итоге таймер работает?
0
  • avatar
  • Vga
  • 22 января 2022, 20:57
Понятия не имею :)
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.