Задача - сделать простой в использовании драйвер. В идеале - указываем вывод, который хотим слушать, тип регистрируемого фронта и обработчик. Типа такого:
читать дальшеВ таком случае использование внешних прерываний будет выглядеть примерно так:
По-моему, круто. Очень лаконично и всё платформозависимое скрыто внутри драйвера - пусть он сам определяет, что и как надо настраивать, чтоб получить этот эффект. Пусть хоть программно состояние линии проверяет постоянно, это его личные проблемы.
Настройка внутри данной функции незамысловата: 1. Настраиваем регистры SYSCFG->EXTICR, вычислив и номер регистра, и положение поля в регистре на основе номера вывода (Pin->Pin), запишем в это поле номер порта (Pin->Port); 2. Настраиваем детектор фронтов на основании параметра Edge с помощью регистров EXTI->RTSR и EXTI->FTSR; 3. Разрешаем прерывания на данной линии (и NVIC, и EXTI->IMR); 4. Запоминаем в таблице адрес обработчика.
При наступлении события по номеру вывода (используя все вектора EXTI) ищем обработчик и вызываем его. Всё.
Если вы вдруг делаете устройство какое-нибудь с поддержкой сети Ethernet, то следует обратить внимание на ряд моментов:
1. 100 Mbps хорошо, но успеет ли девайс их обработать, если пакеты пойдут пачкой? Для встраиваемых устройств лучше использовать старый добрый 10BASE-T Full Duplex. Если вдруг кто пулемётом выстрелит в сеть пакеты, а комп какой-нибудь может и гигабитом быть подключён к сети, и время между пакетами может быть очень маленьким, то коммутатор пакеты задержит у себя ровно настолько, чтоб по очереди их передать по 10Mbps линии. Весьма удобно. 2. Если есть возможность, используйте режим Full-Duplex, чтоб не было мороки с коллизиями и убитыми ей пакетами... 3. Используйте автоопределение скорости! Протокол позволяет выбрать предпочитаемые режимы, например те же 10BASE-T Full Duplex и 10BASE-T Half Duplex, оставив более быстрые режимы не у дел. Если скорость задаётся хардкодом, то можно использовать только полудуплексный режим со всеми его недостатками. Да и то коммутатор будет не уверен, что действует правильно, и вообще, ему будет грустно. 4. Контроллеры Ethernet в МК позволяют автоматически генерировать и проверять CRC у Ethernet Frame. Результат проверки выводится в какой-то флаг. Нужно его обязательно проверять. Если CRC не совпало — пакет должен быть уничтожен на месте. кроме того есть и иные очень полезные флаги ошибок, которые говорят, что в пакет во избежание досадных случайностей смотреть всё же не стоит. 5. Не забываем проверять адрес назначения широковещательного пакета с учётом маски. К тому же, контроллеры могут иметь возможность настраивать фильтры, откуда и что принимать, а какие пакеты выбрасывать, не глядя. 6. PHY, подключённый через интерфейс RMII обязан в случае наличия ошибки при приёме (например, коллизия) испортить все дальнейшие данные пакета. В частности, dp83849c выдаёт вместо всех недостоверных байт число 0x55. В LPC2368 я с этим столкнулся и не знал, что это за глюк такой. "To eliminate the requirement for this signal and still meet the requirements for undetected error rate, RXD[1:0] shall replace the decoded data in the receive stream with “01” until the end of carrier activity."
Я хотел всё написать про протокол DMX на STM32. Я всё же сделал это>< Протокол сей используется большей частью в освещении (в театрах и типа того) и подобных некритических к потерям пактов областям. Один пакет DMX содержит информацию о уровнях 512 каналов (0-255), посылается он 44 раза в секунду, если без перерывов. В качестве физической линии используется стандарт RS-485 (дифференциальная линия), программно же это последовательный асинхронный протокол (UART) со скоростью передачи данных 250 кбит/сек с двумя стоповыми битами и без контроля чётности.
читать дальшеПакет состоить из трёх частей: сброс линии (BREAK), стартовый код (байт 0x00) и данные (512 байт с уровнями каналов). То есть достаточно просто. Тайминги установлены тоже не жёстко, только на минимум: ширина BREAK не менее 92 мкс (лучше ~120 мкс), перерыв между BREAK и стартовым кодом - не менее 12 мкс. В остальном полная свобода.
Приём пакета заключается в ожидании сброса линии (определяется как Frame Error, FE) и последовательном приёме 513 байт в буфер (но их может быть и меньше). Если нулевой байт равен нулю, то это пакет DMX и его можно передать дальше в устройство. Если нет, пакет отбрасывается (так как это пакет какого-то другого формата). Отправка тоже незамысловата: передаём BREAK нужной длины, передаём Mark After Break (время между BREAK и стартовыйм кодом) нужной длины, передаём ноль и 512 байт данных. После чего всё начинается с начала.
Самая хитрость заключается в создании BREAK и MAB. Некоторые контроллеры, в частности и STM32, хвастаются, что умеют делать BREAK аппаратно. В общем-то, это хорошо, но BREAK отличается от нулевого байта только отсутствием стопового бита (вместо 1 там 0), длина же байта остаётся той же. Для DMX такое положение дел не годится — байт при заявленной скорости занимает 44 мкс, что в два раза меньше минимально необходимой. потому его надо генерировать как-то иначе. Раньше я использовал для таких целей таймер, но это нерационально - целый таймер на уарт, жуть. А если надо 4 DMX генерировать? Не вариант. Если вспомнить, что в уарте есть встроенный таймер (протокол-то асинхронный), то можно воспользоваться и им. Алгоритм таков: 1. Передача BREAK. Переводим линию в низкий уровень, знаменующий собой стартовый бит, используя режим GPIO. Выбираем такую скорость UART, чтобы передача одного байта занимала требуемое нам время в 120 мкс. Передаём какой-нибудь байт (так как он никуда не улетит всё равно). Окончание передачи в линию ознаменует и конец заданного интервала времени. 2. Передача Mark After Break. Всё то же самое, только уровень на линии дефолтный: высокий. Переводим линию в высокий уровень, знаменуя возврат линии в неактивное состояние, используя режим GPIO. Выбираем такую скорость UART, чтобы передача одного байта занимала требуемое нам время в 24 мкс (да, с запасом). Передаём какой-нибудь байт. Окончание передачи завершит данный этап. 3. Передача стартового кода. Линия переводится в режим UART, желательно таким образом, чтоб промежуточных состояний не было (никаких отклонений от высокого уровня на линии быть не должно). В частности, в СТМ обратите внимание на переключение в альтернативную функцию: сначала выбираем номер, потом переводим режим! Если номер выбирать после, то кто знает, какая периферия подключится на это время и что будет на выводе. Выбирается скорость передачи данных 250 кбит/сек. Передаётся нулевой байт 0x00. 4. Передача данных. По очереди передаются данные, все 512 байт.
Всё. В итоге получается, что передача DMX осуществляется только на прерываниях/флагах статуса самого приёмопередатчика, без привлечения посторонней периферии. К сожалению, в некоторых контроллерах (типа LPC23xx) нет прерываний о завершении передачи в линию, потому там данный алгоритм применим с некоторыми ограничениями и костылями. Но это уже древний хлам, так что не будем о нём.
Расчёт времени передачи байта по UART выполняется примерно так: 1 бит передаётся за (1000000 / скорость) микросекунд. В байте с двумя стоповыми битами всего 11 бит: один стартовый, восемь бит данных и два стоповых. То есть в 11 раз больше времени передачи одного бита. Обратив эту формулу, получим формулу для расчёта скорости по заданной длине.
Собственно, пример приёма и передачи DMX. проект сейчас настроен на передачу сигнала (который ещё надо драйвером RS485 сконвертировать в дифференциальный вид).
Если dmx_Transmit закомментировать, а dmx_Receive наоборот, то плата начнёт принимать DMX.
В данный момент используется USART3 с выводами PB10, PB11:
Линия управления направлением драйвера RS485 (~RE/DE) выведена на PA2:
UART в STM32 достаточно прост (несмотря на широкие возможности) и имеет всего несколько управляющих регистров: USART_SR — регистр статуса, текущее состояние приёмопередатчика; USART_DR —- регистр данных; USART_BRR — регистр настройки скорости, делитель, 4 бита дробной части и 12 целой, но по сути, туда надо просто записать результат деления (частота шины/нужная скорость); USART_CR1 — включение/выключение модуля, разрешение генерации прерываний; USART_CR2 — для настройки генерации синхросигнала, количества стоповых бит и LIN; USART_CR3 — для DMA и всяких хитрых режимов работы (IrDA, линии CTS, RTS); USART_GTPR — какие-то тайминги для работы со смарт-картами.
Чтоб UART заработал, достаточно подать на него тактирование, включить (USART_CR1: UE, RE, TE), настроить скорость (USART_BRR) и передавать/принимать данные (USART_DR). Ну и, если надо, настроить режимы чётности и всякие там стоповые биты. Вот.
Файлик system_stm32f4xx.c реализует канонную функцию SystemInit и настраивает тактирование системы. Не все знают, что для него есть гуй в виде экселевского файла, в котором можно изменить параметры под другой кварц, например, или ещё какую ерунду натворить.
Чем мне нравится код, управляемый событиями, так это тем, что он позволяет его потестировать в автоматическом режиме. Код не знает, кто генерирует событие (нажатие кнопки или кручение энкодера), да и не важно ему это. И никто не запрещает сделать тест, который эмулировал бы нужную последовательность действий пользователя/системы или целый ряд их вариантов, чтобы посмотреть, как устройство отреагирует.
Есть прибор, например, часы, с кнопками настройки: «Режим настройки», «+», «-», «Ок». Есть обработчик нажатия кнопки какой-либо:
И надо бы проверить, что настройка выполняется корректно. Что если мы жамкнем кнопку программирования, потом пощёлкаем кнопочками плюс или минус, применим изменения и проверим, получили ли ожидаемый результат. Или там пожамкаем без режима настройки, и убедимся, что ничего при этом не поменялось. Вручную всё это прогонять может быть лень. Или при разработке устройства временно может не быть кнопочек (не успели сделать панель управления), а код писать всё же как-то надо. Или код вручную проверить весьма затруднительно, там, например, обработка какого-нибудь хитрого пакета от недоступного в данный момент устройства. Мало ли может быть причин это сделать.
И можно поглядеть, как циферки мило моргают и сами меняются. Причём, такие тесты могут исполняться достаточно быстро, если не требуется визуальный контроль за поведением прибора. Код может быть и совершенно иной, как и контроль выполнения, это так, пример.
Думаю, понятно, почему меня не очень привлекает код вида:
Или даже такой:
Они, конечно, работоспособны, но куда как более неудобны и закостенелы. И потестировать их автоматически куда как сложнее.
В общем-то это мои первые пробы на пути самотестируемого кода для МК, так что посмотрим, куда это приведёт.
Смотрел видео водяной трёхступенчатой ракеты, которая улетела под две сотни метров в высоту. Было б здорово запустить что-то такое, да >< Хотя бы и одноступенчатое на пару десятков метров...
Таймеры TIM2-TIM5 умеют аппаратно работать с поворотным энкодером. То есть вращение энкодера будет вызывать изменение регистра-счётчика (который TIMx->CNT). На самой плате энкодера нема, потому нужен свой. Если нема своего, то можно вовсе не смотреть.
читать дальшеНапример, возьмём любой таймер, хотя бы и тот же TIM4. У него выводы PB6 (канал 1) и PB7 (канал 2) выведены на штыри. Энкодер имеет четыре вывода, два из которых отвечают за питание, а два - за полезный сигнал. Называются они A и B. Они симметричны, в общем-то, даже если перепутать их местами, изменится лишь направление вращения (воспринимаемое таймером). Но, следуя алфавиту, подключим сигнал A к первому каналу (PB6), а B ко второму (PB7). Напомню, альтернативная функция таймера TIM4 для выводов: 2.
Теперь, имея подключённый энкодер, можно и код писать. Таймер включается обычным образом.
Специфика для энкодера: 1. Выбор режима энкодера осуществляется флагами SMS в регистре TIMx_SMCR: 0b001 — счёт только по фронтам на втором канале (состояние другого определяет направление); 0b010 — счёт только по фронтам на первом канале (состояние другого определяет направление); 0b011 — счёт по фронтам на обоих каналах; 2. Полярность фронтов (и направление счёта при вращении) определяются флагами CC1P/CC2P в регистре TIMx_CCER. При изменении любого в противоположное состояние — направление меняется. 3. Предел счёта всё так же определяется регистром TIMx_ARR, потому надо бы не забыть его настроить. По превышении его, счёт начнётся с нуля (с возникновением соответствующих событий и прерываний). В обратную сторону аналогично. 4. Далее настраивается режим захвата: в регистре TIMx_CCMR1 есть два поля: CC1S и CC2S, которые определяют режим работы канала (вход/выход), и если вход, то откуда. Чтобы он брал сигнал с соответствующей ножки, надо в каждое поле единицу (CC1 channel is configured as input, IC1 is mapped on TI1). Там же можно настроить цифровую фильтрцию входного сигнала. 5. И последний штрих: включение первого и второго каналов захвата флагами CC1E и CC2E в регистре TIMx_CCER.
Из интересного: в регистре TIM_CR1 есть флаг DIR, который автоматически показывает направление счёта последнего.
После такой настройки можно запустить выполнение и в режиме отладки, в View->System Viewer->TIM->TIM4 поглядеть, как содержимое регистра TIM4_CNT меняется. Оно всегда будет привязано к текущему положению счётчика. Если интересует перемещение, на сколько повернули ручку между вызовами, то можно сделать функцию, которая будет помнить состояние переменной на момент последнего вызова и смотреть разницу:
Этому коду всё равно, было переполнение регистра-счётчика или же не было его, разница будет верной. Правда, такое будет только если TIMx_ARR = 0xFFFF. В ином случае придётся учитывать программно, что переход через ноль идёт в другое крайнее значение. Да и вообще, программный интерфейс энкодера можно сделать интересным, с фильтрацией, масштабированием перемещения согласно скорости вращения, генерацией событий и так далее.
Прилагаемый проект работает следующим образом: 100 раз в секунду код смотрит, был ли повёрнут энкодер. Если счётчик увеличился, зажигается левый светодиод, если уменьшился — правый. Если положение счётчика больше нуля ((int16_t)TIM4_CNT > 0), то светится верхний светодиод. Для управления светодиодами, и для системного таймера код я взял из предыдущих проектов, для краткости логики:
Тут локально объявляется буфер размером 32 байта, который затем заполняется чем-то. В регистры запихать его, конечно, ещё можно, но это весьма неудобно. Конечно, можно объявить этот буфер обычным образом в статической памяти, в сегменте данных:
Но это слишком жирно. Тратить целых 32 байта навсегда на то, что, может, отработает всего один раз... Брр. Лучше уж выделить их тогда, когда они реально понадобятся, прямо в функции, стек же есть.
Перед блоком, где нам понадобятся локальные переменные надо на стеке выделить память, сдвинув указатель на нужное количество байт, округлённое до ближайшего большего кратного восьми числа. И потом можно обращаться к переменным по смещениям относительно SP. И можно обзывать смещения по именам, как регистры периферии относительно базы.
Мм, как-то так что ли.
Кстати, должно быть понятно, почему нельзя возвращать указатель на буфер, объявленный таким вот образом внутри функции:
После выхода из функции стек будет достаточно быстро перезаписан чем-нибудь ещё, а ведь туда будет указатель смотреть. И хорошо если никто не попробует по этому указателю что-либо записывать.
Точно такой же отладочный вывод можно применять и в ассемблерном проекте. Кстати, это вполне себе повод ознакомиться с представлением строк в си и хотя бы самой простой работе с ними. Файл retarget.c пусть и остаётся самим собой, реализация функции ITM_SendChar описана в файле cm4_core.h и пусть она там остаётся. Желающие, конечно, могут переписать на асм, она маленькая.
читать дальшеСтроки представляются ассемблеру как массив байт:
Если будут использоваться какие-либо функции из языка Си (например, библиотечные strcmp, strcpy и т.д.), то в конце каждой должен присутствовать ноль. Он, по соглашению, и обозначает конец. За перенос строки отвечает код 0x0a (10). Коды иных спецсимволов можно узнать в таблице ASCII/ANSI.
Строки лучше всего разместить в отдельной секции с иными неизменяемыми данными. В секции кода им делать нечего. Как-нибудь так:
Использование их не представляет ничего экстраординарного:
Что надо передавать в printf смотрите в мануале по стандартной библиотеке языка Си. Библиотека Microlib поддерживает это хоть и не в совсем полном объёме, но вполне достаточном для использования.
А что делать с printf и другими функциями, если аргументов больше, чем четыре? Например, printf("%d %d %d %d %s %d", 1, 2, 3, 4, "mimimi", 5)? Указатель на строку для форматирования записывается в регистр R0. Потом три аргумента ещё влезают в регистры R0-R3. А дальше фиг, регистров для аргументов больше нет. Регистры R4-R12 вложенная функция трогать не должна, они не для неё. Зато есть стек. Все аргументы функции, что не влезли в регистры, должны располагаться там, куда указывает SP. То есть пятый аргумент будет взят по адресу *SP, шестой по адресу *(SP + 4), седьмой - *(SP + 8) и т.д.
А для этого надо их туда поместить, ничего не сломав. Мы помним, что стек в армах растёт в сторону меньших адресов. Каждый PUSH одного регистра уменьшает SP на 4, каждый POP увеличивает на 4. Это значит, что в сторону больших адресов лежат всякие важные данные. Например, адреса возврата, сохранённые с помощью PUSH {LR}.
Если будем записывать вот так прямо по указателю SP, то всё будет плохо:
Потому сдвинем указатель так, чтоб вся запись оказалась в безопасной и никому кроме нас не нужной зоне:
Так вот, для передачи дополнительных аргументов через стек, надо бы сначала указатель подвинуть на достаточное и безопасное расстояние и заполнить все значения. Ассемблер кейла настоятельно желает, чтобы указатель стека всегда мог быть нацело поделён на 8, потому сдвигаем его на числа, кратные 8: 8, 16, 24, 32 и т.д, даже если нам
лишние байты не нужны. Размер каждого аргумента 4 байта, как размер регистра. Он не зависит от типа аргумента никак, такое вот соглашение. Пример передачи семи аргументов в функцию printf:
В общем, подобным образом можно использовать все строковые функции из стандартной библиотеки, их там много полезных.
Не все знают, что ST-Link на плате поддерживает трассировку (хотя бы по минимуму) и это можно использовать, чтобы выводить текстовую отладочную информацию с помощью printf прямо в кейл. Причём, это даже совсем не сложно.
Для начала требуется модуль трассировки в свойствах отладчика включить (см. на рисунке 1). И указать актуальную частоту ядра контроллера (см. на рисунке 2). Если system_stm32f4xx.c не используется, пишем 16, если же используется, то пишем 168. Если своя настройка производится — пишем своё число. Для текстового отладочного вывода используется нулевой порт, только его и разрешим (см. на рисунке 3), нечего захламлять линию. То есть надо снять все галочки, каких на картинке нет (или в соответствующее поле записать 1). читать дальше
В Кейле все функции printf и иже с ними уже реализованы в стандартной библиотеке (MicroLIB которая что ли), единственное, что мешает их непосредственному использованию, так это то, что библиотека не знает, куда девать получившийся текст. Может, вы его в какой-нибудь уарт хотите отправить, или в USB-CDC. Может, по Ethernet, а в данном случае в ITM (это какой-то модуль трассировки в контроллере). Да и библиотека не знает, как их настраивать перед использованием. Потому она всё это оставляет на пользователя. Для данной задачи необходимо реализовать всего одну функцию:
По идее, в ней надо смотреть, куда же (аргумент stream) библиотека хочет отправить очередной символ (аргумент c) и отправлять его туда. Но пока файлов нет, будем отправлять всё в отладочный порт. Для этого где-то в CMSIS есть объявление и реализация функции ITM_SendChar, которая записывает символ в нулевой порт трассировщика, чтоб передать его на комп. Т.е.
Если функция не реализована, то при вызове printf и иже с ним контроллер застрянет в fputc-заглушке. Конечно, можно реализовать отправку символа вообще куда угодно, было бы желание. Для работы с файлами надо ещё дополнительно реализовать ряд функций, но это уже смотрите сами. Или я потом посмотрю.
Пример использования:
UPD от 15.10.2013: если Кейл после правильной настройки пишет Trace: No Synchronization вместо Trace: Running... Можно попробовать обновить прошивку отладчика, для чего скачайте STM Studio посвежее, (или ST Toolset, или ST Link Utility), там есть программа-обновлятор, например, у меня она содержится в папке C:\Program Files\STMicroelectronics\STMStudio\dll и называется ST-LinkUpgrade.exe. Или тут: C:\Program Files\STMicroelectronics\STM32 ST-LINK Utility\ST-LINK Utility. Или тут: C:\Program Files\STMicroelectronics\st_toolset\stlink. Что будет, то можно и обновить. У меня в данный момент версия отладчика V2.J17.S0 JTAG Debugger.
Можно использовать для отладки. Любители printf-отладки могут порадоваться. Само окошко вывода смотрим в меню View->Serial Windows->Debug (printf) Viewer.
В STM32, конечно же, есть контроллер часов реального времени.
Для его запуска не обязательно даже иметь внешний кварц на 32.768 кГц, так как затактировать можно от внутреннего LSI-генератора на ~32 кГц (с возможностью калибровки) или внешнего основного кварца (с настраиваемым предделителем). А ещё, как бонус, 20 регистров (80 байт) для хранения разных данных, питание им будет идти от батарейки, от которой часы и работают, когда нет основного питания. Возможностей у часов много, они подробно описаны в RM, остановимся пока на базовом уровне...
читать дальшеОсновные регистры, которые надо знать, чтоб запустить RTC в минимальном режиме: RCC_BDCR — выбор источника и включение тактирования RTC, управление внешним кварцем на 32.768 кГц; RCC_CFGR — предделитель частоты внешнего основного кварца для RTC (чтоб на RTC шёл сигнал с частотой 1 МГц);
RTC_TR — регистр с текущим состоянием времени (запись возможна только при инициализации); RTC_DR — регистр с текущей датой (аналогично); RTC_CR — основная настройка режима работы часов; RTC_ISR — регистр инициализации, с его помощью можно ввести часы в одноимённый режим; RTC_PRER — внутренний предделитель тактовго сигнала; RTC_CALIBR — подстройка часов (с шагом в 2 ppm в минус и 4 ppm в плюс); RTC_WPR — разблокировка защиты от записи;
Так-то регистров там ещё много, но пока фиг с ними.
Итак, контроллер включился первый раз, все регистры часов сброшены, тактирование на модуль не поступает. Для теста попробуем включить часы от внутреннего источника тактирования LSI (на STM32F4-Discovery есть место под внешний часовой кварц, но он не распаян: X3). Конечно, при ресете, от такого источника часы накроются медным тазом, но пофиг.
1. Первым делом надо запустить внутренний генератор на 32 кГц. В регистре RCC_CSR есть два флага: LSIRDY и LSION. Логика проста: выставляем флаг LSION, чтобы генератор завёлся, и ждём, когда он стабилизируется. Это будет показано появлением флага LSIRDY. После этого генератор можно использовать. Для внешнего кварца логика такая же, только флаги LSERDY и LSEON находятся в регистре RCC_BDCR. 2. Регистры энергонезависимого домена (RCC_BDCR к ним относится) защищены от записи. Чтобы это исправить, надо запустить тактирование модуля управления питанием (POWER), установив флаг PWREN в регистре RCC_APB1ENR, а затем и флаг DBF (бит 8) в регистре PWR_CR. Защита отключится. Иначе можно долго гадать, какого же флаг RTCEN не хочет ставиться, когда BDRST прекрасно это делает. 3. На всякий случай сбросим состояние энергонезависимого домена, установив и сбросив флаг BDRST в том же регистре RCC_BDCR. 4. Далее включим тактирование часов (RTCCLK) от этого источника: в регистре RCC_BDCR надо выбрать источник тактирования флагами RTCSEL (биты 9-8, за LSI отвечает значение 2) и включить его флагом RTCEN (бит 15). После выбора источника тактирования часов, менять его уже нельзя, только полным сбросом домена (всё тот же BDRST). 5. Тактирование на модуль часов подано. Теперь можно их инициализировать. После запуска все важные регистры часов защищены от записи. Чтобы её отключить, надо по очереди записать в регистр RTC_WPR два числа: 0xCA и 0x53, в таком вот порядке. Запись любых иных чисел вернёт защиту снова. 6. Когда защита отключена, в регистре RTC_ISR надо установить флаг INIT (бит 7), при этом часы будут остановлены и станет возможно их значения изменить. Но сначала надо подождать установки флага INITF там же, как свидетельство готовности. 7. Дальше настраивается предделитель, который должен выдавать сигнал с частотой 1 Гц. Тут тоже хитрый процесс. Сначала записывается значение в синхронный предделитель, потом в асинхронный. Даже если нужно установить только одно из них, актов записи должно быть два! Для LSI синхронный предделитель равен 249, асинхронный 127. Для LSE синхронный предделитель становится 255, асинхронный такой же. 8. Часы готовы к загрузке начальных значений в регистры RTC_TR и RTC_DR. Формат времени (12/24) выбирается флагом FMT (бит 6) в регистре RTC_CR, но это по желанию и после загрузки даты и времени. Важное замечание: загружать надо сразу весь регистр целиком! То, что будет читаться из регистра сейчас не соответствует истине (там предыдущее значение сидит, бывшее при установке флага INIT). И оно обновится только после сброса этого же флага. Все операции |= и &= на самом регистре пойдут прахом. 9. После всего этого режим инициализации отключается сбросом флага INIT и часы начинают идти. А ещё теперь в регистре RTC_ISR установлен флаг INITS, который можно проверять при запуске контроллера насчёт наличия этой вот настройки часов. 10. Ну и на всякий случай следует в конце вернуть блокировки записи и т.д. Успех!
Читать регистры TR и DR можно уже когда угодно.
Примерный код инициализации:
Код теста:
Проект настраивает RTC при запуске и считывает постоянно дату и время. Моргает, если секунда нечётная.
Практически все таймеры, за исключением базовых, имеют каналы сравнения, которые, в частности, умеют делать ШИМ. До четырёх штук на таймер. А именно: много всего TIM1: 4 канала (+инверсные); TIM2: 4 канала; TIM3: 4 канала; TIM4: 4 канала; TIM5: 4 канала; TIM6: 0 каналов; TIM7: 0 каналов; TIM8: 4 канала (+инверсные); TIM9: 2 канала; TIM10: 1 канал; TIM11: 1 канал; TIM12: 2 канала; TIM13: 1 канал; TIM14: 1 канал; То есть очень дофига. 32 канала в сумме для всех таймеров, если я правильно посчитал.
читать дальшеДля примера возьмём обычный таймер TIM4, выводы которого подключены к светодиодам(PD12-PD15). Сразу смотрим, куда можно его вывести: TIM4_CH1: PB6, PD12 TIM4_CH2: PB7, PD13 TIM4_CH3: PB8, PD14 TIM4_CH4: PB9, PD15
Номер альтернативной функции для таймеров TIM3-TIM5: 2.
Настройка самого таймера такая же, как описывалась ранее. Только что на выбор предделителя и предела счёта накладывают ограничения итоговое разрешение и частота ШИМ. Сначала определим предел счёта согласно выбранному разрешению:
Предделитель определяется отношением частоты шины APB1 к итоговой частоте и ARR:
Пример. Частота тактирования контроллера 16 МГц, предделителя на APB1 нет. Разрешение шима хотим 16 бит, частоту порядка 50 Гц (например, чтоб серву крутить, но тут светодиод):
Правда, из-за низкой частоты тактирования (для такого высокого разрешения) получим большую погрешность по частоте ШИМ: Fpwm = 16 МГц / (ARR + 1) / (PSC + 1) = 16000000 / 0x10000 / 4 = 61 Гц.
Впрочем, серве на это должно быть пофиг.
Итак, таймер с заданными параметрами запустить можем. Посмотрим новые регистры, которые связанны с генерацией ШИМ: TIMx_SR — регистр статуса, можно посмотреть, было ли срабатывание по сравнению или переполнение счётчика; TIMx_CCER — включение каналов и настройка полярности выходного сигнала (в т.ч. ШИМ); TIMx_CCMR1 — настройка 1 и 2 канала захвата/сравнения; TIMx_CCMR2 — настройка 3 и 4 канала захвата/сравнения; TIMx_CCR1 — регистр сравнения 1 канала; TIMx_CCR2 — регистр сравнения 2 канала; TIMx_CCR3 — регистр сравнения 3 канала; TIMx_CCR4 — регистр сравнения 4 канала;
Таймер управляет состоянием какого-то внутреннего сигнала (OCxREF), который может выводиться наружу прямо как есть или же инвертированно (настраивается в TIMx_CCER). Активное состояние — когда OCxREF равен 1, неактивное, соответственно, — когда OCxREF равен 0.
В общем-то, чтобы ШИМ вышел наружу, достаточно включить канал, выбрать нужный режим сравнения, задать скважность и настроить альтернативную функцию порта. Канал включается флагами CCxE в регистра CCER. Выбрать режим генерации ШИМ можно в регистрах CCMRx, там есть поля OCxM размером в три бита: 000 — выключено; 001 — установить выход в активное состояние по сравнению; 010 — установить выход в неактивное состояние по сравнению; 011 — переключить состояние по сравнению; 100 — насильно установить неактивный уровень на выходе; 101 — насильно установить активный уровень на выходе; 110 — ШИМ, режим 1 (из активного уровня по сравнению переходит в неактивный); 111 — ШИМ, режим 2 (из неактивного уровня по сравнению переходит в активный);
В режиме ШИМ необходимо выставить ещё и флаг OCxPE (буферизация значения сранения) в этом же регистре. Так как все значения буферизуются и загружаются во внутренние регистры только при сбросе счётчика, перед запуском (после записи чисел в CCR) сделаем же это, выставив флаг UG в регистре TIMx_EGR. Флаг сбросится тут же, но значения попадут куда надо. Возможно, всё будет хорошо, даже если так не сделать. Флаг выставится сам при первом переполнении счётчика.
Код:
Если сделать так, то на ножке PВ12 будет ШИМ с частотой 61 Гц (погрешность же) и скважностью 50%, как на картинке внизу:
В проекте скважность меняется постоянно и светодиод мерцает. Так нагляднее:
Кстати, по канону на светодиоды надо заводить ШИМ с частотой в 600 Гц, не иначе. Но здесь на это пофиг.
UPD от 29.03.2013: ШИМ перенесён с ножки PA5 на PD12 (TIM2->TIM4), где его можно увидеть невооружённым глазом по яркости светодиода.
Onee-chan to isshou 4.1 - Храм Вашиномия, из аниме Lucky Star Наконец-то и Онее-тян сподобилась побывать в этом самом первом и самом известном месте аниме-путешествий. Место, где уже побывали все отаку Японии, храм Вашиномия считается не только храмом аниме Lucky Star, но и храмом всего аниме вообще. Может быть, это потому, что это место находится относительно недалеко и в нём могут побывать все, кто приехал хотя бы в Токио.
Однако, ехать туда - путь не близкий: от Каматы по линии Кейхин-Тохоку почитай полтора часа до Омии, столицы префектуры Сайтама, а потом - ещё несколько остановок до линии Уцуномия до Куки, а в Куки - пересадка и ещё одна остановка.
Вот она, деревня Вашиномия! Будний день, тишина, тока петухи поют:
читать дальшеТолько поднимаешься с платформы - и, на тебе, прямо в здании станции, церемониальный паланкин Lucky Star:
И, он не просто там стоит, а его таскают во время специального фестиваля! Вот стенд с инфой о нём:
Колоритное здание прямо напротив выхода со станции:
Идти - всего ничего: чуть-чуть прямо, до речки, и налево. Весенние краски, розовый и жёлтый, слива и сурепка:
По всей дороге Кагами и Цукаса:
Когда подошла к речке, тишину нарушили обезьяньи крики. Эти звуки везде узнаются сразу: китайские туристы, которые возвращались из храма, куда я только шла. Как же они омерзительны и как омерзительно там, где они есть!
Как и в посёлке Тоёсато, родине K-On!, витрины почти всех магазинов здесь украшены картинками и фигурками персонажей LS:
Вот, уже близко, виднеется красный храмовый мостик:
И, вот... Гокигеньё, Кагами-тян! (Я подсела на Маримите...)
Удивительно красивая аллейка ведёт к храму, не знаю, чем она так хороша, но тогучи давно просят оранжевой краски:
Вот и Онее-тян запечатлела свою физиономию в легендарном месте. Погода была солнечной и по-июньски жаркой, так что куртку пришлось повесить на сумку и таскать с собой.
За этими маленькими ториями находится прудик, в данный момент - сухой:
Подходя к ним, я почувствовала чудесный запах. Откуда такой аромат, не иначе как от этих цветов:
И, наконец, стенд с табличками для желаний:
С одной стороны - они все стандартные, с рисунками храма, но с другой...
Как мы видим, здесь не только персонажи Lucky Star, но и множество кроссоверов и героев других аниме, ибо храм, как я уже говорила, стал храмом всея аниме.
На территории находится несколько святилищ, вот основное, которое попало в кадр:
Детали:
Напротив - что-то очень напоминающее сцену традиционного театра Но. Подобное я видела в святилище Ицукушима.
Поблизости:
А, прямо за основных храмом, на высоком фундаменте, за глухим забором расположен ещё один павильон. Что в нём, для чего он - без понятия:
С левой стороны я заметила постройку, затянутую сеткой. Это действительно оказаля вольер, а в нём - три курицы
и... павлин!
Как только увидел, что я стараюсь примостить объектив к мелкой решётке - начал позировать:
Ещё красивое:
Аллейка по пути назад:
И, обратно, на станцию:
Обратно можно ехать десятком разных дорог. Я решила не переходить в Куки и дождалась поезда Тобу напрямую до Ошиаге. В Ошиаге, а это уже Токио, Асакуса, он въезжает в метро и едет от начала до конца по линии Ханазомон до Сибуйи. В Сибуйе я просто перешла на линию Фукутосин, которую теперь продолает линия Тоёко и, как в день концерта Иноуе Кикуко - до Тамагавы и до Каматы. Удобно, ни одно турникета. В идеале, можно было взять минимальный билет и съекономить 1000 йен, выйдя незаметно в Камате) Можно было сделать иначе: пересесть в метро в Кита-сендзи и ехать до Нака-Мегуро, где тоже пересадка на Тоёко) В очередной раз убеждаюсь, что если кажется, что в Японии куда-то трудно и неудобно доехать, то ты просто не знаешь, как нужно)
Шестиногая и очень маленькая прозрачная фигня (2х2 мм). Напряжение питания до двух вольт (1.8 В), потребление очень маленькое, особенно если не использовать ИК-дальномер. Разрешение АЦП: 14 бит. При таком разрешении единичное преобразование длится 100 мс (можно чувствительность увеличить в 4 раза за счёт времени интерполяции в 400 мс), при снижении до 8 бит - менее 2 мс.
Кроме непосредственного измерения цвета падающего на датчик света, он умеет и реагировать на события (когда какое-либо из значений выйдет из заданного диапазона) и установить низкий уровень на линии прерывания (~INT). Правда, прерывание возможно только по изменению расстояния или общей освещённости, но, думаю, большего и не надо.
Чтобы начать использовать датчик, достаточно подать напряжение на него и подключить к линиям i2c контроллера. Что круто, напряжение на линиях цифрового интерфейса может быть до 6 В (через подтяжку, естественно), то есть подключить можно даже к атмеге без каких-либо проблем. Если нужен датчик расстояния, то надо ещё подключить ИК-светодиод на линию DRV (без каких-либо резисторов), линия коммутирует землю. Скорость интерфейса может быть до 400 кГц. Адрес датчика: 0x88.
Первым делом, при обращении к датчику, надо дождаться, пока в 0 регистре сбросится бит PWRON (бит 2). Это будет означать, что он запустился и работает в штатном режиме. Дальше надо выставить режим работы (что измерять и с какой точностью) и можно считывать измеренные данные.
Например, чтоб устройство работало с 8битным разрешением и измеряло температуру, освещённость и цвет (RGB) с разрешением 8 бит, надо в регистры 0x01 и 0x02 записать следующие значения: В 0x01 пишем 0x20: (MODE = 2, AMB + RGB + IR) В 0x02 пишем 0x4C: (TEMPEN = 1, AMBTIM=3, 8 bit).
Единственное, я сейчас заметил, что TEMPEN в разных местах описан то как 5, то как 6 бит. Потом уточню, какой он на самом деле =D
Дальше можно считывать данные: 0x05: AMB. Общая освещённость; 0x07: RED. Красный цвет; 0x09: GREEN. Зелёный цвет; 0x0B: BLUE. Синий цвет; 0x11: IR. Расстояние; 0x13: TEMP. Температура.
Интерфейс i2c широко распространён и используется. В stm32f4 модулей, реализующих данный протокол, аж целых три штуки. Естественно, с полной поддержкой всего этого дела.
читать дальшеРабота с модулем, в целом, такая же, как и в других контроллерах: даёшь ему команды, он их выполняет и отчитывается о результате: Я> Шли START. S> Ок, послал. Я> Круто, шли адрес теперь. Вот такой: 0xXX. S> Ок, послал. Мне сказали, что ACK. Давай дальше. Я> Жив ещё, хорошо. Вот тебе номер регистра: 0xYY, - шли. S> Послал, получил ACK. Я> Шли ему теперь данные, вот тебе байт: 0xZZ. S> Послал, он согласен на большее: ACK. Я> Фиг ему, а не ещё. Шли STOP. S> Okay.
И всё примерно в таком духе.
В данном контроллере выводы i2c раскиданы по портам таким образом: PB6: I2C1_SCL PB7: I2C1_SDA
PB8: I2C1_SCL PB9: I2C1_SDA
PB10: I2C2_SCL PB11: I2C2_SDA
PA8: I2C3_SCL PC9: I2C3_SDA Вообще, распиновку периферии удобно смотреть в даташите на 59 странице.
Что удивительно, но для работы с i2c нужны все его регистры, благо их немного: I2C_CR1 — команды модулю для отправки команд/состояний и выбор режимов работы; I2C_CR2 — настройка DMA и указание рабочей частоты модуля (2-42 МГц); I2C_OAR1 — настройка адреса устройства (для slave), размер адреса (7 или 10 бит); I2C_OAR2 — настройка адреса устройства (если адресов два); I2C_DR — регистр данных; I2C_SR1 — регистр состояния модуля; I2C_SR2 — регистр статуса (slave, должен читаться, если установлен флаги ADDR или STOPF в SR1); I2C_CCR — настройка скорости интерфейса; I2C_TRISE — настройка таймингов фронтов.
Впрочем, половина из них типа «записать и забыть».
На плате STM32F4-Discovery уже есть I2C устройство, с коим можно попрактиковаться: CS43L22, аудиоЦАП. Он подключён к выводам PB6/PB9. Главное, не забыть подать высокий уровень на вывод PD4 (там сидит ~RESET), иначе ЦАП не станет отвечать.
Порядок настройки примерно таков: 1. Разрешить тактирование портов и самого модуля. Нам нужны выводы PB6/PB9, потому надо установить бит 1 (GPIOBEN) в регистре RCC_AHB1ENR, чтоб порт завёлся. И установить бит 21 (I2C1EN) в регистре RCC_APB1ENR, чтоб включить модуль I2C. Для второго и третьего модуля номера битов 22 и 23 соответственно. 2. Дальше настраиваются выводы: выход Oped Drain (GPIO->OTYPER), режим альтернативной функции (GPIO->MODER), и номер альтренативной функции (GPIO->AFR). По желанию можно настроить подтяжку (GPIO->PUPDR), если её нет на плате (а подтяжка к питанию обеих линий необходима в любом виде). Номер для I2C всегда один и тот же: 4. Приятно, что для каждого типа периферии заведён отдельный номер. 3. Указывается текущая частота тактирования периферии Fpclk1 (выраженная в МГц) в регистре CR2. Я так понял, это нужно для расчёта разных таймингов протокола. Кстати, она должна быть не менее двух для обычного режима и не менее четырёх для быстрого. А если нужна полная скорость в 400 кГц, то она ещё и должна делиться на 10 (10, 20, 30, 40 МГц). Максимально разрешённая частота тактирования: 42 МГц. 4. Настраивается скорость интерфейса в регистре CCR, выбирается режим (обычный/быстрый). Cмысл таков: Tsck = CCR * 2 * Tpckl1, т.е. период SCK пропорционален CCR (для быстрого режима всё несколько хитрее, но в RM расписано). 5. Настраивается максимальное время нарастания фронта в регистре TRISE. Для стандартного режима это время 1 мкс. В регистр надо записать количество тактов шины, укладывающихся в это время, плюс один: если такт Tpclk1 длится 125 нс, то записываем (1000 нс / 125 нс) + 1 = 8 + 1 = 9. 6. По желанию разрешается генерация сигналов прерывания (ошибки, состояние и данных); 7. Модуль включается: флаг PE в регистре CR1 переводится в 1.
Дальше модуль работает уже как надо. Надо только реализовать правильный порядок команд и проверки результатов. Например, запись регистра: 1. Сначала нужно отправить START, установив флаг с таким именем в регистре CR1. Если всё ок, то спустя некоторое время выставится флаг SB в регистре SR1. Хочу заметить один момент, - если нет подтяжки на линии (и они в 0), то этот флаг можно не дождаться вовсе. 2. Если флаг-таки дождались, то отправляем адрес. Для семибитного адреса просто записываем его в DR прям в таком виде, как он будет на линии (7 бит адреса + бит направления). Для десятибитного более сложный алгоритм. Если устройство ответит на адрес ACK'ом, то в регистре SR1 появится флаг ADDR. Если нет, то флаг AF (Acknowledge failure). Если ADDR появился, надо прочитать регистр SR2. Можно ничего там и не смотреть, просто последовательное чтение SR1 и SR2 сбрасывает этот флаг. А пока флаг установлен, SCL удерживается мастером в низком состоянии, что полезно, если надо попросить удалённое устройство подождать с отправкой данных. Если всё ок, то дальше модуль перейдёт в режим приёма или передачи данных в зависимости от младшего бита отправленного адреса. Для записи он должен быть нулём, для чтения - единицей. но мы рассматриваем запись, потому примем, что там был ноль. 3. Дальше отправляем адрес регистра, который нас интересует. Точно так же, записав его в DR. После передачи выставится флаг TXE (буфер передачи пуст) и BTF (передача завершена). 4. Дальше идут данные, которые можно отправлять, пока устройство отвечает ACK. Если ответом будет NACK, то эти флаги не установятся. 5. По завершении передачи (или в случае непредвиденного состояния) отправляем STOP: устанавливается одноимённый флаг в регистре CR1.
При чтении всё то же самое. Меняется только после записи адреса регистра. Вместо записи данных идёт повторная отправка START (повторный старт) и отправка адреса с установленным младшим битом (признак чтения). Модуль будет ждать данных от устройства. Чтобы поощрать его к отправке следующих байт, надо перед приёмом установить флаг ACK в CR1 (чтобы после приёма модуль посылал этот самый ACK). Как надоест, флаг снимаем, устройство увидит NACK и замолчит. После чего шлём STOP обычным порядком и радуемся принятым данным.
Вот то же самое в виде кода:
Конечно, кроме как в учебном примере так делать нельзя. Ожидание окончания действия слишком уж долгое для такого быстрого контроллера.
UPD от 29.03.2013: вместо внешнего редкого i2c-устройства max44005 для примера взят имеющийся на плате ЦАП.