Iar и stm32 cortex m0. часть 0x00 (пустой треп и отмазки)

Что нужно знать о прерываниях

В этой статье я планирую поделиться с читателями своими скромными познаниями в области прерываний. Начать следует с того, что же представляют собой прерывания. Прерываниеэто событие как правило связанное с каким-либо блоком периферии микроконтроллера STM32.

Событий которые могут породить прерывание может быть множество. Например если речь о таком блоке периферии как UART, то там могут быть такие события: передача завершена, приём завершен, возникла ошибка чётности итд. Использование прерываний позволит нашей программе мгновенно регировать на подобные события.

Сам термин прерывание говорит о том, что что-то должно прерваться и в нашем случае прервется выполнение основного кода вашей программы и управление будет передано некоторому другому куску кода который называется обработчиком прерывания. Таких обработчиков достаточно много, ибо периферийных устройств в STM32 предостаточно.

Стоит отметить важный момент: В случае возникновения двух разных прерываний от одного блока периферии возникает одно и тоже прерывание. Например если произойдет прерывание по приёму байта через UART и прерывание по завершению передачи через тот же UART, то в обоих случаях будет вызван один и тот же обработчик.

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

То есть основная программа продолжит работать дальше как ни в чем не бывало.

  1. Они все независимо включаются/выключаются
  2. Имеют приоритет
  3. Могут быть вызваны программно
  4. Если для прерывания нет обработчика, а оно возникло, то будет вызван обработчик по умолчанию

С первым пунктом всё более-менее понятно, но есть один нюанс. Прерывание не возникнет, если оно запрещено (а оно по умолчанию запрещено) для какого-либо конкретного блока периферии. Чтоб его включить существует функция NVIC_EnableIRQ, ну а для выключения соответственно NVIC_DisableIRQ.

В качестве параметра этим двум функциям надо передать переменную типа IRQn. Этот тип является перечислением и соответственно переменная может принимать одно значение из списка который описан в заголовочном файле для вашего контроллера (у меня этот файл называется stm32f10x.h).

Пример использования: 

NVIC_EnableIRQ (USART1_IRQn); // Разрешаем прерывания от USART1 NVIC_EnableIRQ (ADC1_IRQn); // Разрешаем прерывания от АЦП NVIC_DisableIRQ (USART1_IRQn); // Запрещаем обратно NVIC_DisableIRQ (ADC1_IRQn); // Для АЦП запрещаем прерывания тоже

Следующий важный момент это приоритеты прерываний. Предположим, что у нас возникло прерывание с приоритетом 10 и управление было передано в его обработчик. Если в ходе исполнения кода обработчика возникнет прерывание приоритет которого меньше 10, то исполнение прервется и управление будет передано обработчику более высокоприоритетного прерывания.

Чем меньше приоритет – тем прерывание более приоритетное. Если ничего не менять, то все прерывания имеют такие приоритеты, как это написано в референс мануале (см. таблицу Interrupt and exception vectors).

Интересно, что будет если назначить одинаковые приоритеты всем? Судя по той табличке о которй я писал выше, в случае одновременного возникновения двух прерываний с одинаковыми приоритетами, будет вызван обработчик того прерывания которое имеет наименьшее значение в столбце “Position”. Но это всего лишь моё предположение и точно я ничего сказать не могу.

 Прерывания могут быть вызваны вашим кодом, просто поставьте нужный флаг и оно случится (если конечно его ни кто не запрещал). Хотя ни кто не запрещает вам вызвать обработчик просто как обычную функцию без всех этих извратов с флагами.

И наконец последнее: Если вдруг каким-то чудом у вас случается прерывание для которого нет обработчика, то управление передается в обработчик по умолчанию, в теле которого находится пустой бесконечный цикл. Ну а теперь еще немного теории и попробуем написать несложную программу которая обрабатывает прерывания от обыкновенного порта ввода/вывода.

Такие прерывания могут возникать в случае если на ноге: 1) появилась лог.1, 2) появился лог.0, 3) лог. уровень сменился на противоположный. Прерывание может генерировать любая ножка любого порта, но таких ног может быть не более 16 штук. Вот тут-то STM32 страшно рулит по сравнению с большинством AVRок у которых подобных прерываний всего два и они жестко привязаны к определённым ногам.

 Всего может существовать семь обработчиков внешних прерываний: EXTI0, EXTI1, EXTI2, EXTI3, EXTI4, EXTI9_5, EXTI15_10 а что касается выводов, то их у нас куда больше. Прерывание от нулевой ноги любого порта всегда будет обрабатываться при помощи обработчика EXTI0, прерывание второй ноги будет вызывать обработчик EXTI2 и так далее.

Что касает ножек с 5-й по 9-ю то для любой из них будет вызван обработчик EXTI9_5, аналогичная ситуация и с EXTI15_10. Существует ограничение: нельзя настроить прерывание таким образом чтоб оно возникало от двух ног с одинаковым номером. Например нельзя PA1 и PB1 одновременно настроить. Для того чтоб определить ноги каких портов будут вызывать возникновение прерываний, служат 4 регистра AFIO_EXTICR1AFIO_EXTICR4. Они все похожи друг на друга, покажу тут только первый:

В каждом таком регистре есть 4 группы по 4 бита в каждой. Каждая группа соответствует ноге от нулевой до пятнадцатой. Изменяя биты группы можно задать какому порту соответствует эта нога. Возможны такие комбинации: 

EXTI3 EXTI2 EXTI1 EXTI0 Порт
PA
1 PB
1 PC
1 1 PD
1 PE
1 1 PF
1 1 PG

Если мы хотим чтоб ноги PB3, PC2, PA1 и PD0 могли порождать прерывания то нужно записать в регистр AFIO_EXTICR1 такую комбинацию бит: 0001 0010 0000 0011.

По умолчанию во всех 4-х регистрах AFIO_EXTICR записаны нули, а то означает, что каждая нога порта А готова генерировать прерывания.

Но чтоб это происходило нужно явно разрешить это при помощи регистра EXTI_IMR


Нам интересны первые 16 бит, каждый бит соответствует ноге, если он установлен то нога может генерить прерывания. Как я уже писал выше, прерывание может возникнуть по нарастающему или спадающему фронту а так же при любом изменении лог. уровня на ноге.

Настраивается это в регистрах EXTI_RTSR и EXTI_FTSR. В них нас так же интересуют первые 16 бит. Если мы установим бит в регистре EXTI_RTSR то соответствующая нога при   появлении на ней лог. 1 вызовет прерывание.

Ну а соответственно установив бит в EXTI_FTSR мы разрешим ноге генерить прерывание в момент появления лог 0. Если для одной ноги стоят биты в обоих этих регистрах, то прерывание будет возникать всякий раз когда происходит изменение логического уровня.

Еще есть регистр EXTI_SWIER при помощи которого вы можете программно вызвать прерывание от имени любой ноги. Достаточно просто записать единицу в интересующий нас бит. И последний важный регистр EXTI_PR. Это регистр флагов, флаги устанавливаются в зависимости от того какая нога вызвала прерывание.

Находясь в обработчике прерывания EXTI9_5, при помощи этого регистра легко проверить какая из ног (от 5 до 9) вызвала прерывание. Важный момент: Флаг надо сбрасывать самому в обработчике прерывания. Сбрасывается он записью единицы в соответствующий бит. Теперь попробуем сделать небольшой практический пример.

Есть две кнопки и два светодиода. Нажимаем кнопку и светодиод меняет своё состояние на противоположное. Я использовал как обычно свою дискавери прикрутив к ней еще одну кнопку с резистором: 

А вот собственно код который вдохнет жизнь в железку: 

#include “stm32f10x.h”
#include “stm32f10x_gpio.h”
#include “stm32f10x_rcc.h” int main() { GPIO_InitTypeDef PORT; //Затактируем все три порта RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC , ENABLE); //Прерывания – это альтернативная функция порта //поэтому надо установить бит Alternate function I/O clock enable //в регистре RCC_APB2ENR RCC_APB2PeriphClockCmd(RCC_APB2ENR_AFIOEN , ENABLE); // Настроим ноги со светодиодами на выход PORT.GPIO_Pin = (GPIO_Pin_9 | GPIO_Pin_8); PORT.GPIO_Mode = GPIO_Mode_Out_PP; PORT.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOC, &PORT); //Наша задача получить в регистре EXTICR[0] такую комбинацию бит // 0000 0000 0001 0000 // по умолчанию там ноли, поэтому установим только 1 бит AFIO->EXTICR[0]|=AFIO_EXTICR1_EXTI1_PB; //Прерывания от нулевой и первой ноги разрешены EXTI->IMR|=(EXTI_IMR_MR0 | EXTI_IMR_MR1); //Прерывания на обоих ногах по нарастающему фронту EXTI->RTSR|=(EXTI_RTSR_TR0 | EXTI_RTSR_TR1); //Разрешаем оба прерывания NVIC_EnableIRQ (EXTI0_IRQn); NVIC_EnableIRQ (EXTI1_IRQn); while(1) { //Программа ничего не делает в пустом цикле }
} // Обработчик прерывания EXTI0
void EXTI0_IRQHandler(void)
{ GPIOC->ODR^=GPIO_Pin_8; //Инвертируем состояние светодиода EXTI->PR|=0x01; //Очищаем флаг } // Обработчик прерывания EXTI1
void EXTI1_IRQHandler(void)
{ GPIOC->ODR^=GPIO_Pin_9; //Инвертируем состояние светодиода EXTI->PR|=0x02; //Очищаем флаг }

Как обычно я написал пояснения к программе в самом коде, но если есть вопросы то пишите ниже. 

P.S. если я что-то не так написал, то тем более пишите. 

Источник: http://easystm32.ru/for-beginners/25-interrupts-handling-in-stm32

Alex_EXE

«Много информации не бывает —
бывает мало места для её хранения»
какая-то реклама в каком-то популярном

компьютерном журнале 90-х годов.

Если в проекте нужно выводить или получать большие объемы данных то на помощь придут различные flash микросхемы памяти.

Если же нежно получать или выводить ОГРОМНЫЕ объемы данных, то стоит задуматься о более вместительном хранилище информации. На роль такого вместилища подходит SD карта памяти.

Это – огромный носитель информации, по сравнению с обычными микросхемами памяти, объём которых редко превышает 64Мбита.

Подключение SD карты к STM32vlDiscovery

В статье рассмотрим подключение и диалог SD карты памяти с микроконтроллером STM32, через SPI. Без использование файловой системы.

В качестве контроллера и платы выступит демонстрационная плата STM32vlDiscovery с контроллером STM32F100RB на борту. Диалог с платой будет производиться по UART, точнее, через преобразователь USB-UART будем общаться через COM терминальную программу AL Terminal. Связь с картой памяти будет осуществляться через SPI.

Подключение карты памяти к STM32F100RB установленному в Discovery будет выглядеть следующим образом:

Схема

Контроллер установлен на демонстрационной плате и его подключение расписывать не буду. Преобразователь USB-UART описан в статье — USB-UART на cp2102 , можете применить и любой другой, например ft232 . Карта памяти подключается напрямую к микроконтроллеру, т.к. напряжение питание и логические уровни сигналов, в 3.3В, у них одинаковые.

Распиновка SD карты в SPI режиме

С подключением ничего сложного нет, переходим к коду.

Проект написан в Keil с использованием CMSIS и Standard Peripheral Library. Из SPL применены GPIO, RCC И USART библиотеки. Библиотеку для работы с картой памяти воспользовался готовой, взял с сайта «My Controller» — http://mycontroller.ru/category/vneshnie-ustroystva/karta-pamyati-sd/ .

Возможности библиотеки:

SD_init() – инициализация SPI и карты памяти
в случае удачи возвращает 0.

SD_sendCommand(cmd, arg) – посылка команды SD карте
Где: cmd – команда, arg – аргумент Основные команды:

GO_IDLE_STATE 0 – программная перезагрузка

SEND_IF_COND 8 – для SDC V2 — проверка диапазона напряжений
READ_SINGLE_BLOCK 17 – чтение указанного блока данных
WRITE_SINGLE_BLOCK 24 – запись указанного блока данных
SD_SEND_OP_COND 41 – начало процесса инициализации
APP_CMD 55 – главная команда из ACMD команд
READ_OCR 58 – чтение регистра OCR
Команд для работы с картой памяти на самом деле гораздо больше, но в библиотеки задействованы только эти, чего для минимальной работы достаточно.

SD_ReadSector(BlockNumb,*buff) – прочитать сектор в случае неудачи возвращает 0

Где: BlockNumb – адрес блока карты памяти, *buff – ссылка для буфера

SD_WriteSector(BlockNumb,*buff) – записать сектор в случае неудачи возвращает 0

Где: BlockNumb – адрес блока карты памяти, *buff – ссылка для буфера

На основе библиотеки написал небольшую оболочку для ознакомления с картой памяти:

unsigned long address,i; unsigned char c; init(); // инициализация переферии if(SD_init()==0) // инициализация SD карты памяти { send_Uart_str(USART1,”init sd ok
“); } else { send_Uart_str(USART1,”init sd fail
“); } send_Uart_str(USART1,”alex-exe.ru”); // выводим сообщение в UART while(1) { send_Uart_str(USART1,”
—————————-
“); send_Uart_str(USART1,”Read or write sd card r/w = “); c=getch_Uart(USART1); // читаем байт с uart while(getch_Uart(USART1)!=13){} // дожидаемся ввода enter с uart send_Uart_str(USART1,”
Press enter address = “); buff_clear(); // очистка буфера address=read_int_uart(USART1); // читаем число с uart, адрес сектора на sd карте send_Uart_str(USART1,”
Address = “); send_int_Uart(USART1,address); // выводим для проверки введенное число send_Uart(USART1,'
'); if(c=='w') // проверка на запись или чтение карты памяти { send_Uart_str(USART1,”Press enter data blok (max 512B), to exit press enter
“); read_str_uart(USART1,Buff); // читаем строку с uart, окончание enter i=0; while((i < 512)&&(Buff[i]!=0))i++; // ищем конец текстовой строки send_Uart_str(USART1,"Length text data = "); send_int_Uart(USART1,i); // выводим длину текстового послания send_Uart(USART1,' '); if(SD_WriteSector(address, Buff)==0) // запись буфера на SD карту { send_Uart_str(USART1,"write sd ok "); } else { send_Uart_str(USART1,"write sd fail "); } } else { if(SD_ReadSector(address, Buff)==0) // чтения SD карты в буфер { send_Uart_str(USART1,"read sd ok "); } else { send_Uart_str(USART1,"read sd fail "); } for(i=0;i < 512;i++) // вывод содержимого буфера в терминал { send_Uart(USART1,Buff[i]); } } // на последок мигнём светодиодом GPIO_SetBits(GPIOC, GPIO_Pin_8); // включить синий светодиод на PC8 delay(1000000); GPIO_ResetBits(GPIOC, GPIO_Pin_8); // выключить синий светодиод на PC8 }

В коде реализована возможность чтения и записи блоков карты памяти через терминал.

Эксперименты проводил с картами памяти SD Transcend 2 GB и SDHC Qumo 8GB.

Скачать исходник

В статье использовались материалы с сайта «My Controller» — http://mycontroller.ru/category/vneshnie-ustroystva/karta-pamyati-sd/.

Схема обновлена 2 октября 2013 года

Источник: https://alex-exe.ru/radio/stm32/connect-sd-card-stm32-spi/

Таймер SysTick, реализация задержек в программе | arm | programming

Источник: http://microsin.net/programming/ARM/stm32-systick.html

Программирование STM32. Часть 1. GPIO, порты ввода-вывода STM32

Недавно коллега меня подсадил на идею создания умного дома, я даже успел заказать себе десятки разных датчиков. Встал вопрос о выборе Микроконтроллера (далее МК) или платы. После некоторых поисков нашёл несколько вариантов.

Среди них были и Arduino (включая его клоны, один из которых себе заказал ради того, чтобы просто побаловаться) и Launchpad, но всё это избыточно и громоздко (хотя в плане программирования гораздо проще, но тему холиваров поднимать не буду, у каждого свои вкусы). В итоге решил определяться не с готовой платой, а взять только МК и делать всё с нуля.

В итоге выбирал между Atmel ATtiny (2313), Atmel ATmega (решил отказаться т.к. не смог найти за адекватные деньги), STM32 (Cortex на ядре ARM). С тинькой я уже успел побаловаться, так что взял себе STM32VL-Discovery. Это можно назвать вступлением к циклу статей по STM32.

Оговорюсь сразу, автором большинства этих статей буду являться не я, т.к. сам только познаю, здесь я публикую их в первую очередь для себя, чтоб удобнее было искать если что-то забуду. И так поехали!

Общие сведения

Микроконтроллеры семейства STM32 содержат в своём составе до семи 16-разрядных портов ввода-вывода c именами от PORTA до PORTG. В конкретной модели микроконтроллера без исключений доступны все выводы портов, общее количество которых зависит от типа корпуса и оговорено в DataSheet на соответствующее подсемейство.

Для включения в работу порта x необходимо предварительно подключить его к шине APB2 установкой соответствующего бита IOPxEN в регистре разрешения тактирования периферийных блоков RCC_APB2ENR:

RCC->APB2ENR |= RCC_APB2ENR_IOPxEN; // Разрешить тактирование PORTx.

Управление портами STM32 осуществляется при помощи наборов из семи 32-разрядных регистров:

  • GPIOx_CRL, GPIOx_CRH – задают режимы работы каждого из битов порта в качестве входа или выхода, определяют конфигурацию входных и выходных каскадов.
  • GPIOx_IDR – входной регистр данных для чтения физического состояния выводов порта x.
  • GPIOx_ODR– выходной регистр осуществляет запись данных непосредственно в порт.
  • GPIOx_BSRR – регистр атомарного сброса и установки битов порта.
  • GPIOx_BSR – регистр сброса битов порта.
  • GPIOx_LCKR – регистр блокировки конфигурации выводов.

Режимы работы выводов GPIO

Режимы работы отдельных выводов определяются комбинацией битов MODEy[1:0] и CNFy[1:0] регистров GPIOx_CRL и GPIOx_CRH (здесь и далее: x-имя порта, y- номер бита порта).

GPIOx_CRL — регистр конфигурации выводов 0…7 порта x:

Структура регистра GPIOx_CRH аналогична структуре GPIOx_CRL и предназначена для управления режимами работы старших выводов порта (биты 8…15).

Биты MODEy указанных регистров определяют направление вывода и ограничение скорости переключения в режиме выхода:

  • MODEy[1:0] = 00: Режим входа (состояние после сброса);
  • MODEy[1:0] = 01: Режим выхода, максимальная скорость – 10МГц;
  • MODEy[1:0] = 10: Режим выхода, максимальная скорость – 2МГц;
  • MODEy[1:0] = 11: Режим выхода, максимальная скорость – 50МГц.

Биты CNF задают конфигурацию выходных каскадов соответствующих выводов:

в режиме входа:

  • CNFy[1:0] = 00: Аналоговый вход;
  • CNFy[1:0] = 01: Вход в третьем состоянии (состояние после сброса);
  • CNFy[1:0] = 10: Вход с притягивающим резистором pull-up (если PxODR=1) или pull-down (если PxODR=0);
  • CNFy[1:0] = 11: Зарезервировано.

в режиме выхода:

  • CNFy[1:0] = 00: Двухтактный выход общего назначения;
  • CNFy[1:0] = 01: Выход с открытым стоком общего назначения;
  • CNFy[1:0] = 10: Двухтактный выход с альтернативной функцией;
  • CNFy[1:0] = 11: Выход с открытым стоком с альтернативной функцией.

С целью повышения помехоустойчивости все входные буферы содержат в своём составе триггеры Шмидта. Часть выводов STM32, снабженных защитными диодами, соединёнными с общей шиной и шиной питания, помечены в datasheet как FT (5V tolerant) — совместимые с напряжением 5 вольт.

Защита битов конфигурации GPIO

Для защиты битов в регистрах конфигурации от несанкционированной записи в STM32 предусмотрен регистр блокировки настроек GPIOx_LCKR
GPIOx_LCKR — регистр блокировки настроек вывода порта:

Для защиты настроек отдельного вывода порта необходимо установить соответствующий бит LCKy. После чего осуществить последовательную запись в разряд LCKK значений “1” — “0” — “1” и две операции чтения регистра LCKR, которые в случае успешной блокировки дадут для бита LCKK значения “0” и “1” . Защита настроечных битов сохранит своё действие до очередной перезагрузки микроконтроллера.

Файл определений для периферии микроконтроллеров STM32 stm32f10x.h определяет отдельные группы регистров, объединённые общим функциональным назначением (в том числе и GPIO), как структуры языка Си, а сами регистры как элементы данной структуры. Например:

GPIOC->BSRR – регистр BSRR установки/сброса порта GPIOC.
Воспользуемся определениями из файла stm32f10x.h для иллюстрации работы с регистрами ввода-вывода микроконтроллера STM32F100RB установленного в стартовом наборе STM32VLDISCOVERY:

#include “stm32F10x.h” u32 tmp; int main (void) { RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // Разрешить тактирование PORTC. GPIOC->CRH |= GPIO_CRH_MODE8; // Вывод светодиода LED4 PC8 на выход. GPIOC->CRH &=~GPIO_CRH_CNF8; // Двухтактный выход на PC8. GPIOC->CRH |= GPIO_CRH_MODE9; // Вывод светодиода LED3 PC9 на выход. GPIOC->CRH &=~GPIO_CRH_CNF9; // Двухтактный выход на PC9. GPIOA->CRL&=~GPIO_CRL_MODE0; // Кнопка “USER” PA0 – на вход. // Заблокировать настройки выводов PC8, PC9. GPIOC->LCKR = GPIO_LCKR_LCK8|GPIO_LCKR_LCK9| GPIO_LCKR_LCKK; GPIOC->LCKR = GPIO_LCKR_LCK8|GPIO_LCKR_LCK9; GPIOC->LCKR = GPIO_LCKR_LCK8|GPIO_LCKR_LCK9| GPIO_LCKR_LCKK; tmp=GPIOC->LCKR; tmp=GPIOC->LCKR; }

Запись и чтение GPIO

Для записи и чтения портов предназначены входной GPIOx_IDR и выходной GPIOx_ODR регистры данных.

Запись в выходной регистр ODR порта настроенного на вывод осуществляет установку выходных уровней всех разрядов порта в соответствии с записываемым значением. Если вывод настроен как вход с подтягивающими резисторами, состояние соответствующего бита регистра ODR активирует подтяжку вывода к шине питания (pull-up, ODR=1) или общей шине микроконтроллера (pull-down, ODR=0).

Чтение регистра IDR возвращает значение состояния выводов микроконтроллера настроенных как входы:

// Если кнопка нажата (PA0=1), установить биты порта C, иначе сбросить. if (GPIOA->IDR & GPIO_IDR_IDR0) GPIOC->ODR=0xFFFF; else GPIOC->ODR=0x0000;

Сброс и установка битов порта

Для атомарного сброса и установки битов GPIO в микроконтроллерах STM32 предназначен регистр GPIOx_BSRR.

Традиционный для архитектуры ARM способ управления битами регистров не требующий применения операции типа “чтение-модификация-запись” позволяет устанавливать и сбрасывать биты порта простой записью единицы в биты установки BS (BitSet) и сброса BR (BitReset) регистра BSRR. При этом запись в регистр нулевых битов не оказывает влияния на состояние соответствующих выводов.

GPIOx_BSRR – регистр сброса и установки битов порта:

GPIOC->BSRR=GPIO_BSRR_BS8|GPIO_BSRR_BR9; // Зажечь LED4 (PC8), погасить LED3.
GPIOC->BSRR=GPIO_BSRR_BS9|GPIO_BSRR_BR8; // Зажечь LED3 (PC9), погасить LED4.

Альтернативные функции GPIO и их переназначение (remapping)
Практически все внешние цепи специального назначения STM32 (включая выводы для подключения кварцевых резонаторов, JTAG/SWD и так далее) могут быть разрешены на соответствующих выводах микроконтроллера, либо отключены от них для возможности их использования в качестве выводов общего назначения. Выбор альтернативной функции вывода осуществляется при помощи регистров с префиксом “AFIO”_.
Помимо этого регистры AFIO_ позволяют выбирать несколько вариантов расположения специальных функций на выводах микроконтроллера. Это в частности относится к выводам коммуникационных интерфейсов, таймеров (регистры AFIO_MAPR), выводам внешних прерываний (регистры AFIO_EXTICR) и т. д.

Подробнее смотрите документы “Reference manual” на соответствующую подгруппу микроконтроллеров.

Проекты к статье:

Для управления GPIO STM32 Вы можете применить макросы написанные как альтернативу далеко не оптимальным по мнению многих библиотекам от ST: gpio_emcu.h

Дополнительный материал:

Другие части

Источник: https://ergoz.ru/programmirovanie-stm32-chast-1-gpio-portyi-vvoda-vyivoda-stm32/

Ссылка на основную публикацию
Adblock
detector
",css:{backgroundColor:"#000",opacity:.6}},container:{block:void 0,tpl:"
"},wrap:void 0,body:void 0,errors:{tpl:"
",autoclose_delay:2e3,ajax_unsuccessful_load:"Error"},openEffect:{type:"fade",speed:400},closeEffect:{type:"fade",speed:400},beforeOpen:n.noop,afterOpen:n.noop,beforeClose:n.noop,afterClose:n.noop,afterLoading:n.noop,afterLoadingOnShow:n.noop,errorLoading:n.noop},o=0,p=n([]),h={isEventOut:function(a,b){var c=!0;return n(a).each(function(){n(b.target).get(0)==n(this).get(0)&&(c=!1),0==n(b.target).closest("HTML",n(this).get(0)).length&&(c=!1)}),c}},q={getParentEl:function(a){var b=n(a);return b.data("arcticmodal")?b:(b=n(a).closest(".arcticmodal-container").data("arcticmodalParentEl"),!!b&&b)},transition:function(a,b,c,d){switch(d=null==d?n.noop:d,c.type){case"fade":"show"==b?a.fadeIn(c.speed,d):a.fadeOut(c.speed,d);break;case"none":"show"==b?a.show():a.hide(),d();}},prepare_body:function(a,b){n(".arcticmodal-close",a.body).unbind("click.arcticmodal").bind("click.arcticmodal",function(){return b.arcticmodal("close"),!1})},init_el:function(d,a){var b=d.data("arcticmodal");if(!b){if(b=a,o++,b.modalID=o,b.overlay.block=n(b.overlay.tpl),b.overlay.block.css(b.overlay.css),b.container.block=n(b.container.tpl),b.body=n(".arcticmodal-container_i2",b.container.block),a.clone?b.body.html(d.clone(!0)):(d.before("
"),b.body.html(d)),q.prepare_body(b,d),b.closeOnOverlayClick&&b.overlay.block.add(b.container.block).click(function(a){h.isEventOut(n(">*",b.body),a)&&d.arcticmodal("close")}),b.container.block.data("arcticmodalParentEl",d),d.data("arcticmodal",b),p=n.merge(p,d),n.proxy(e.show,d)(),"html"==b.type)return d;if(null!=b.ajax.beforeSend){var c=b.ajax.beforeSend;delete b.ajax.beforeSend}if(null!=b.ajax.success){var f=b.ajax.success;delete b.ajax.success}if(null!=b.ajax.error){var g=b.ajax.error;delete b.ajax.error}var j=n.extend(!0,{url:b.url,beforeSend:function(){null==c?b.body.html("
"):c(b,d)},success:function(c){d.trigger("afterLoading"),b.afterLoading(b,d,c),null==f?b.body.html(c):f(b,d,c),q.prepare_body(b,d),d.trigger("afterLoadingOnShow"),b.afterLoadingOnShow(b,d,c)},error:function(){d.trigger("errorLoading"),b.errorLoading(b,d),null==g?(b.body.html(b.errors.tpl),n(".arcticmodal-error",b.body).html(b.errors.ajax_unsuccessful_load),n(".arcticmodal-close",b.body).click(function(){return d.arcticmodal("close"),!1}),b.errors.autoclose_delay&&setTimeout(function(){d.arcticmodal("close")},b.errors.autoclose_delay)):g(b,d)}},b.ajax);b.ajax_request=n.ajax(j),d.data("arcticmodal",b)}},init:function(b){if(b=n.extend(!0,{},a,b),!n.isFunction(this))return this.each(function(){q.init_el(n(this),n.extend(!0,{},b))});if(null==b)return void n.error("jquery.arcticmodal: Uncorrect parameters");if(""==b.type)return void n.error("jquery.arcticmodal: Don't set parameter \"type\"");switch(b.type){case"html":if(""==b.content)return void n.error("jquery.arcticmodal: Don't set parameter \"content\"");var e=b.content;return b.content="",q.init_el(n(e),b);case"ajax":return""==b.url?void n.error("jquery.arcticmodal: Don't set parameter \"url\""):q.init_el(n("
"),b);}}},e={show:function(){var a=q.getParentEl(this);if(!1===a)return void n.error("jquery.arcticmodal: Uncorrect call");var b=a.data("arcticmodal");if(b.overlay.block.hide(),b.container.block.hide(),n("BODY").append(b.overlay.block),n("BODY").append(b.container.block),b.beforeOpen(b,a),a.trigger("beforeOpen"),"hidden"!=b.wrap.css("overflow")){b.wrap.data("arcticmodalOverflow",b.wrap.css("overflow"));var c=b.wrap.outerWidth(!0);b.wrap.css("overflow","hidden");var d=b.wrap.outerWidth(!0);d!=c&&b.wrap.css("marginRight",d-c+"px")}return p.not(a).each(function(){var a=n(this).data("arcticmodal");a.overlay.block.hide()}),q.transition(b.overlay.block,"show",1*")),b.overlay.block.remove(),b.container.block.remove(),a.data("arcticmodal",null),n(".arcticmodal-container").length||(b.wrap.data("arcticmodalOverflow")&&b.wrap.css("overflow",b.wrap.data("arcticmodalOverflow")),b.wrap.css("marginRight",0))}),"ajax"==b.type&&b.ajax_request.abort(),p=p.not(a))})},setDefault:function(b){n.extend(!0,a,b)}};n(function(){a.wrap=n(document.all&&!document.querySelector?"html":"body")}),n(document).bind("keyup.arcticmodal",function(d){var a=p.last();if(a.length){var b=a.data("arcticmodal");b.closeOnEsc&&27===d.keyCode&&a.arcticmodal("close")}}),n.arcticmodal=n.fn.arcticmodal=function(a){return e[a]?e[a].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof a&&a?void n.error("jquery.arcticmodal: Method "+a+" does not exist"):q.init.apply(this,arguments)}}(jQuery)}var debugMode="undefined"!=typeof debugFlatPM&&debugFlatPM,duplicateMode="undefined"!=typeof duplicateFlatPM&&duplicateFlatPM,countMode="undefined"!=typeof countFlatPM&&countFlatPM;document["wri"+"te"]=function(a){let b=document.createElement("div");jQuery(document.currentScript).after(b),flatPM_setHTML(b,a),jQuery(b).contents().unwrap()};function flatPM_sticky(c,d,e=0){function f(){if(null==a){let b=getComputedStyle(g,""),c="";for(let a=0;a=b.top-h?b.top-h{const d=c.split("=");return d[0]===a?decodeURIComponent(d[1]):b},""),c=""==b?void 0:b;return c}function flatPM_testCookie(){let a="test_56445";try{return localStorage.setItem(a,a),localStorage.removeItem(a),!0}catch(a){return!1}}function flatPM_grep(a,b,c){return jQuery.grep(a,(a,d)=>c?d==b:0==(d+1)%b)}function flatPM_random(a,b){return Math.floor(Math.random()*(b-a+1))+a}
");let k=document.querySelector(".flat_pm_modal[data-id-modal=\""+a.ID+"\"]");if(-1===d.indexOf("go"+"oglesyndication")?flatPM_setHTML(k,d):jQuery(k).html(b+d),"px"==a.how.popup.px_s)e.bind(h,()=>{e.scrollTop()>a.how.popup.after&&(e.unbind(h),f.unbind(i),j())}),void 0!==a.how.popup.close_window&&"true"==a.how.popup.close_window&&f.bind(i,()=>{e.unbind(h),f.unbind(i),j()});else{let b=setTimeout(()=>{f.unbind(i),j()},1e3*a.how.popup.after);void 0!==a.how.popup.close_window&&"true"==a.how.popup.close_window&&f.bind(i,()=>{clearTimeout(b),f.unbind(i),j()})}f.on("click",".flat_pm_modal .flat_pm_crs",()=>{jQuery.arcticmodal("close")})}if(void 0!==a.how.outgoing){let b,c="0"==a.how.outgoing.indent?"":" style=\"bottom:"+a.how.outgoing.indent+"px\"",e="true"==a.how.outgoing.cross?"":"",f=jQuery(window),g="scroll.out"+a.ID,h=void 0===flatPM_getCookie("flat_out_"+a.ID+"_mb")||"false"!=flatPM_getCookie("flat_out_"+a.ID+"_mb"),i=document.createElement("div"),j=jQuery("body"),k=()=>{void 0!==a.how.outgoing.cookie&&"false"==a.how.outgoing.cookie&&h&&(jQuery(".flat_pm_out[data-id-out=\""+a.ID+"\"]").addClass("show"),j.on("click",".flat_pm_out[data-id-out=\""+a.ID+"\"] .flat_pm_crs",function(){flatPM_setCookie("flat_out_"+a.ID+"_mb",!1)})),(void 0===a.how.outgoing.cookie||"false"!=a.how.outgoing.cookie)&&jQuery(".flat_pm_out[data-id-out=\""+a.ID+"\"]").addClass("show")};switch(a.how.outgoing.whence){case"1":b="top";break;case"2":b="bottom";break;case"3":b="left";break;case"4":b="right";}jQuery("body > *").eq(0).before("
"+e+"
");let m=document.querySelector(".flat_pm_out[data-id-out=\""+a.ID+"\"]");-1===d.indexOf("go"+"oglesyndication")?flatPM_setHTML(m,d):jQuery(m).html(e+d),"px"==a.how.outgoing.px_s?f.bind(g,()=>{f.scrollTop()>a.how.outgoing.after&&(f.unbind(g),k())}):setTimeout(()=>{k()},1e3*a.how.outgoing.after),j.on("click",".flat_pm_out .flat_pm_crs",function(){jQuery(this).parent().removeClass("show").addClass("closed")})}countMode&&(flat_count["block_"+a.ID]={},flat_count["block_"+a.ID].count=1,flat_count["block_"+a.ID].click=0,flat_count["block_"+a.ID].id=a.ID)}catch(a){console.warn(a)}}function flatPM_start(){let a=flat_pm_arr.length;if(0==a)return flat_pm_arr=[],void jQuery(".flat_pm_start, .flat_pm_end").remove();flat_body=flat_body||jQuery("body"),!flat_counter&&countMode&&(flat_counter=!0,flat_body.on("click","[data-flat-id]",function(){let a=jQuery(this),b=a.attr("data-flat-id");flat_count["block_"+b].click++}),flat_body.on("mouseenter","[data-flat-id] iframe",function(){let a=jQuery(this),b=a.closest("[data-flat-id]").attr("data-flat-id");flat_iframe=b}).on("mouseleave","[data-flat-id] iframe",function(){flat_iframe=-1}),jQuery(window).on("beforeunload",()=>{jQuery.isEmptyObject(flat_count)||jQuery.ajax({async:!1,type:"POST",url:ajaxUrlFlatPM,dataType:"json",data:{action:"flat_pm_ajax",data_me:{method:"flat_pm_block_counter",arr:flat_count}}})}).on("blur",()=>{-1!=flat_iframe&&flat_count["block_"+flat_iframe].click++})),flat_userVars.init();for(let b=0;bflat_userVars.textlen||void 0!==a.chapter_sub&&a.chapter_subflat_userVars.titlelen||void 0!==a.title_sub&&a.title_subc&&cc&&c>d&&(b=flatPM_addDays(b,-1)),b>e||cd||c-1!=flat_userVars.referer.indexOf(a))||void 0!==a.referer.referer_disabled&&-1!=a.referer.referer_disabled.findIndex(a=>-1!=flat_userVars.referer.indexOf(a)))&&(c=!0),c||void 0===a.browser||(void 0===a.browser.browser_enabled||-1!=a.browser.browser_enabled.indexOf(flat_userVars.browser))&&(void 0===a.browser.browser_disabled||-1==a.browser.browser_disabled.indexOf(flat_userVars.browser)))){if(c&&void 0!==a.browser&&void 0!==a.browser.browser_enabled&&-1!=a.browser.browser_enabled.indexOf(flat_userVars.browser)&&(c=!1),!c&&(void 0!==a.geo||void 0!==a.role)&&(""==flat_userVars.ccode||""==flat_userVars.country||""==flat_userVars.city||""==flat_userVars.role)){flat_pm_then.push(a),flatPM_setWrap(a),flat_body.hasClass("flat_pm_block_geo_role")||(flat_body.addClass("flat_pm_block_geo_role"),flatPM_ajax("flat_pm_block_geo_role")),c=!0}c||(flatPM_setWrap(a),flatPM_next(a))}}}let b=jQuery(".flatPM_sticky");b.each(function(){let a=jQuery(this),b=a.data("height")||350,c=a.data("top");a.wrap("
");let d=a.parent()[0];flatPM_sticky(this,d,c)}),debugMode||countMode||jQuery("[data-flat-id]:not([data-id-out]):not([data-id-modal])").contents().unwrap(),flat_pm_arr=[],jQuery(".flat_pm_start, .flat_pm_end").remove()}

Важнейшей функцией почти всех приложений на микроконтроллерах является вычисление задержек времени в программе (отслеживание таймаутов во время передачи данных, периодический опрос клавиатуры и т. п.). Когда отслеживание времени является важным для приложения, Вы ничего не сможете сделать без хорошей функции задержки.

Во всех моих проектах, начиная с простейших 8-битных MCU, функции задержек всегда было непросто реализовать, если нужно было точно отсчитывать время. Часто время правильно вычислялось только для определенной тактовой частоты, но чтобы код работал на всех тактовых частотах, требовалось применять продвинутое программирование на ассемблере.

К счастью, как и у всех других микроконтроллеров ARM Cortex, семейство STM32F4 имеет отличный 24-битный системный таймер (system timer, SysTick), который считает вниз до нуля от предварительно загруженной в счетчик величины.

Благодаря этому таймеру можно довольно просто и качественно реализовать отсчет реального времени в программе.

Функции для SysTick можно найти в заголовочном файле core_cm4.h, если Вы используете CoIDE. Если вы используете IAR, то функции для управления и использования таймера SysTick Вы сможете найти в примерах кода на основе стандартной библиотеки ST: в среде IAR 6.

50, на стартовой странице выберите EXAMPLE PROJECTS -> ST -> STM32F4xx -> CMSIS and STM32F4xx stdperiph lib 1.1.0 (ищите функции SysTick_Config, SysTick_Handler).

Все, что необходимо – просто вызвать функцию SysTick_Config, где в параметре будет указано количество тиков между прерываниями, и предоставить обработчик прерывания SysTick_Handler.

В ядро процессора STM32 встроен 24-битный системный таймер, так называемый SysTick (STK), который считает в обратном направлении от загруженной в таймер величины до нуля.

В момент достижения значения 0 счетчик автоматически на следующем тактовом перепаде перезагружает сам себя значением из регистра STK_LOAD, и далее продолжает счет вниз, считая каждый приходящий импульс тактирования.

Когда процессор остановлен (halted) для отладки, то счетчик не декрементируется.

Таймер SysTick (Cortex System Timer) специально предназначен для операционных систем реального времени (real-time operating system, RTOS), но может использоваться просто как стандартный счетчик вниз. Его возможности:

• 24-битный счетчик с обратным отсчетом.• Возможность автозагрузки.• Маскируемая генерация системного прерывания, когда счетчик достигает 0.

• Программируемый источник тактирования.

Базовый адрес для блока регистров SysTick равен 0xE000E010.

Таблица 54. Обзор регистров таймера SysTick.

Адрес Имя Тип Режим доступа Значение после сброса Описание
0xE000E010 STK_CTRL RW Привилегированный 0x00000000 Регистр статуса и управления SysTick.
0xE000E014 STK_LOAD RW Привилегированный не определено Значение для перезагрузки счетчика SysTick.
0xE000E018 STK_VAL RW Привилегированный не определено Текущее значение счетчика SysTick.
0xE000E01C STK_CALIB RO Привилегированный 0xC0000000 Регистр калибровки SysTick.

[Описание регистров SysTick]

STK_CTRL, SysTick control and status register (регистр статуса и управления).

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
Reserved COUNTFLAG
rw
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
   Reserved CLKSOURCE TICKINT ENABLE
rw rw rw

Смещение адреса 0x00, значение после сброса 0x00000000. Требуемый режим доступа: привилегированный. Через регистр статуса и управления разрешаются фичи SysTick.

Биты 31..17, 15..3 зарезервированы, их состояние должно сохраняться в нулевом значении.

COUNTFLAG: этот бит читается как 1, если таймер досчитал до нуля после последнего чтения этого бита.

CLKSOURCE: выбор тактовой частоты для счетчика SysTick. 0: частота AHB/8, 1: частота процессора (AHB).

TICKINT: разрешение на запрос прерывания (exception request enable) SysTick. 0: счет продолжается вниз до нуля, но при этом не выставляется запрос на SysTick exception, 1: то же самое, но по достижении нуля выставляется запрос на SysTick exception. Примечание: программное обеспечение может использовать флаг COUNTFLAG, чтобы определить, что сосчитал ли когда-либо счетчик SysTick до нуля.

ENABLE: разрешение работы счетчика, 0: счетчик запрещен, 1: разрешен. Когда в 1, то счетчик загружает в себя значение RELOAD из регистра STK_LOAD, когда счет достигает 0.

В этот момент также устанавливается в 1 флаг COUNTFLAG, и может быть выставлен запрос от прерывания, в зависимости от значения флага TICKINT.

После перезагрузки счетчика значением RELOAD счет продолжается, и цикл повторяется.

STK_LOAD, SysTick reload value register (регистр для значения перезагрузки).

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
Reserved RELOAD[23:16]
rw
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
RELOAD[15:0]
rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw

Смещение адреса 0x04, значение после сброса не определено. Требуемый режим доступа: привилегированный. Биты 31..24 зарезервированы, их состояние должно сохраняться в нулевом значении.

RELOAD: в этих битах содержится значение для перезагрузки счетчика SysTick. Это начальное значение, которое автоматически загружается в регистр STK_VAL, когда счетчик разрешен, и когда счет достигает 0.

Значение для RELOAD вычисляется в соответствии с целями программы:

• Для генерации multi-shot таймера (т. е. у которого срабатывания повторяются снова и снова) с периодом N циклов процессора, используйте значение для RELOAD, равное N-1.

Например, когда нужно, чтобы прерывание SysTick было каждые 100 тактовых импульсов, то установите значение RELOAD на величину 99.
• Для генерации одиночного прерывания SysTick после задержки в N циклов процессора, используйте для RELOAD значение N.

 Например, когда нужно, чтобы прерывание SysTick было каждые 100 тактовых импульсов, то установите значение RELOAD на величину 100. 

STK_VAL, SysTick current value register (регистр текущего значения счетчика).

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
Reserved CURRENT[23:16]
rw
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
CURRENT[15:0]
rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw

Смещение адреса 0x08, значение после сброса не определено. Требуемый режим доступа: привилегированный. Биты 31..24 зарезервированы, их состояние должно сохраняться в нулевом значении.

CURRENT: текущее значение счетчика SysTick. Чтение возвратит это значение. Запись любого значения очистит это поле в 0, и также очистит в 0 бит COUNTFLAG регистра STK_CTRL. 

STK_CALIB, SysTick calibration value register (регистр значения калибровки). Содержимое регистра показывает свойства калибровки SysTick.

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
NOREF SKEW Reserved TENMS[23:16]
r r r r r r r r r r
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
TENMS[15:0]
r r r r r r r r r r r r r r r r

Смещение адреса 0x0C, значение после сброса 0xC0000000. Требуемый режим доступа: привилегированный. Биты 29..24 зарезервированы, их состояние должно сохраняться в нулевом значении.

NOREF: флаг NOREF, читается как 0. Показывает, что предоставляется отдельная опорная частота тактов. Частота этих тактов HCLK/8.

SKEW: флаг SKEW (уход), показывает, точно ли установлено значение TENMS. Читается как 1. Значение калибровки для 1 мс не определено, потому что значение TENMS не известно. Это может повлиять на пригодность SysTick в качестве программных часов реального времени.

TENMS: значение калибровки. Показывает значение калибровки, когда счетчик SysTick работает от тактов HCLK max/8.

Реальное значение зависит от конкретного чипа и определяется в процессе производства, за подробностями обращайтесь к Product Reference Manual, секция SysTick Calibration Value.

Когда HCLK запрограммирован на максимальную частоту, период таймера SysTick равен 1 мс. Если информация калибровки не известна, вычислите значение калибровки по частоте тактов процессора или внешней частоте тактов.

[Общие указания по использованию таймера SysTick]

Счетчик SysTick работает от тактов процессора. Если тактовый сигнал остановлен в режиме пониженного энергопотребления, то счетчик SysTick прекращает счет.

Убедитесь, что программа использует адрес, выровненный по слову для доступа к регистрам SysTick.

После сброса текущее состояние счетчика и значение его перезагрузки не определены, и корректная последовательность инициализация для счетчика SysTick следующая:

1. Нужно запрограммировать значение для перезагрузки счетчика.2. Затем очистить текущее значение счетчика.

3. Program Control and Status register.

Вектор прерывания таймера SysTick находится по адресу 0x000003C. RCC подает тактовую частоту на System Timer (SysTick) от частоты шины AHB (HCLK), поделенной на 8. SysTick может работать либо от этой частоты, либо от частоты тактов Cortex (HCLK), что конфигурируется через регистр статуса и управления SysTick.

Более подробно о таймере SysTick можно прочитать в руководстве PM0214 компании ST (STM32F3 and STM32F4 Series Cortex®-M4 programming manual).