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

Кому интересно — приглашаю под кат.
Начнем с таймеров. Их здесь 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 управляет альтернативными функциями каждого пина. Их зависимости сведены в таблицу

Значения по умолчанию —
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 раз. Таким образом, можно гибко настраивать таймеры под необходимые параметры счета времени.
Ну и наконец, совсем

Не знаю, зачем вообще такую дичь творить, но так можно — это факт! :)
Функция 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 свернуть / развернуть