Удивительно, насколько все люди разные, очень разные. В общем, один тип сильно замотивировал меня заниматься ARM. Надеюсь, этого заряда бодрости хватит, чтобы освоить основные принципы. Попробуем повторить, старую отлаженную систему уроков по периферии. Кроме того, на этот раз, это будут не просто куски кода, а постараемся проникнуться идеей и найти более менее понятное объяснение в даташите.
Предыдущий пост по армам был вводным, оставим его в качестве ознакомления и для любителей танцев с бубном. Эту же статью можно считать первым уроком. Рекомендую скачать кокос (CoIde/Cocox), ибо такого гемора с установкой и настройкой как в IAR там нет. Да и в целом, он более дружелюбен для новичка. Дальнейшие объяснения будут на его примере, хотя выбор среды по сути неважен. В качестве железа на данный момент под рукой только STM32DiscoveryF4, поэтому уроки на данный момент будут ориентированы именно на эту плату.
Попробуем разобраться, что такое GPIO. По сути это обычные ножки, которыми можно подрыгать, либо прочитать их состояние. Для того, чтобы микроконтроллер понимал, в каком режиме ему работать, ножки предварительно нужно настраивать. Итак первый документ, который вам обязательно следует найти «RM0090 Reference manual», в нем вся полезная информация на нашу плату. Ищем раздел 8 посвященный General-purpose I/Os (GPIO), т.е. нашим ножкам. Из него мы узнаем, что ножки могут работать в таких режимах:
• Input floating — ножка настроена как вход и не подтянута к плюсу питания, ни к земле
• Input pull-up — вход и подтянута к плюсу питания
• Input-pull-down — вход и подтяжка к земле
• Analog — аналоговый вход
• Output open-drain with pull-up or pull-down capability — выход с открытым стоком
• Output push-pull with pull-up or pull-down capability — выход в режиме пуш пул
• Alternate function push-pull with pull-up or pull-down capability — альтернативная функция ножки с пуш-пулом
• Alternate function open-drain with pull-up or pull-down capability — альтернативная функция ножки открытым коллектором
Итак, зачем делают подтяжку входа к земле или к плюсу питания? Дело в том, что если ножка болтается в воздухе, то на ней может быть какое угодно напряжение из-за помех, поэтому когда делают подтяжку, тогда на ней будет однозначно либо плюс питания, либо земля в зависимости от того какой режим выберем.
Чтобы просто подрыгать ножкой, достаточно настроить ее в режим пуш-пул, в этом режиме через нагрузку может течь как положительный, так и отрицательный ток. Соответственно, ножка может быть включена и выключена, т.е. либо логическая единица либо, ноль. Когда ножка включена, на ней +3В (как на фото), если выключена — соответственно вольтметр покажет ноль. Не забывайте, что ножки весьма дохлые и если ток больше 25мА, она может отдать концы.
Отличие работы в режиме выхода с открытым стоком, то что при логической единице выход остается в режиме Hi-Z (высокоомный вход), это дает определенные преимущества (рассмотрим в следующих уроках).
На одной ножке могут быть альтернативные функции — uart, spi и т.п. Alternate function, позволяет задействовать эти режимы.
Допустим, вы выключили ножку и тут же включили, через сколько времени состояние ножки изменится? Любая ножка тактируется от генератора и состояние ножки обновляется с каждым тактом, не чаще. Поэтому генератор задает максимальную частоту работы ножки, при этом работать можно на нескольких частотах, в зависимости от настроек. Чем выше частота, тем выше энергопотребление.
Теперь попробуем написать первую программу. Запускаем CoIDE и создаем новый проект. На вкладке Project выбираем имя и папку, где будет сохранен проект.
На вкладке Model выбираем Chip. В списке чипов ищем наш, который называется STM32F407VGT6. Название камня легко ищется на сайте производителя среди обзора платы дискавери.
Подключаем необходимые библиотеки. Здесь достаточно поставить галочку напротив GPIO, остальные поставятся автоматически.
Щелкаем по исходнику main.c и переходим в окно редактирования нашей программы. Пока что это функция main и бесконечный цикл while.
Среди библиотек (инклудов) болтаются два файла stm32f4xx_gpio.h и stm32f4xx_rcc.h их нужно подключить к исходникам
#include <stm32f4xx_gpio.h>; //библиотека для работы с портами #include <stm32f4xx_rcc.h>; //библиотека для работы с тактовым генератором int main(void) { while(1) { } } |
Далее нужно запустить тактирование порта. Для этого нужно знать название регистра, который за это отвечает. В первый раз, когда я открыл мануал (RM0090), мозг просто взорвало, но если внимательно посмотреть, то ничего необычного нет, ножки это периферия, reset register регистры сброса нам не нужны, значит интересными нам могут быть регистры с AHB1 до APB2.
Открываем AHB1 и смотрим, что может включить данный регистр, напротив каждого бита все любезно расписано, например бит 30 включает USB OTG HSULPI.
Крутим ниже и находим, что третий бит AHB1 включает тактирование порта D. Повезло, первый попавшийся регистр оказался нужным. Почему нас интересует порт D, потому что на нем сидят светодиоды, которыми мы хотим помигать. Откуда узнал, что светодиоды на порту D — из схемы платы. Другая периферия может включаться от другого регистра, так что смотрите внимательно. Если у вас другая плата, то названия регистров тоже могут отличаться.
Есть более читерский способ заходите в инклуд stm32f4xx_rcc.h и ищете строки по ключевому слову GPIOD (если нужен порт D), среди прочих находятся такие строки, из которых понятно, что тактирование GPIOD включается в AHB1.
Для того, чтобы настроить ножку используется структура. Структура это несколько связанных переменных. Например GPIO_InitTypeDef структура которая относится к настройке порта, поэтому назовем ее PORT_SETUP. И включим тактирование. Далее следуют непосредственно настройки, о чем было сказано выше, в принципе все в комментариях.
#include <stm32f4xx_gpio.h> //библиотека для работы с портами #include <stm32f4xx_rcc.h> //библиотека для работы с тактовым генератором int main(void) { GPIO_InitTypeDef PORT_SETUP; //Создаем структуру, в которую заносим настройки RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); //Включить тактирование PORTD PORT_SETUP.GPIO_Pin=GPIO_Pin_All; //настройки для всех ножек PORT_SETUP.GPIO_Mode = GPIO_Mode_OUT; //ножка выход PORT_SETUP.GPIO_OType = GPIO_OType_PP; //режим пуш-пул PORT_SETUP.GPIO_Speed = GPIO_Speed_2MHz; //скорость 2мгц GPIO_Init( GPIOD , &PORT_SETUP); //записать настройки while(1) { } } |
Вопрос, где все эти значения можно увидеть? Ответ в stm32f4xx_gpio.h. Например, GPIO_Mode_OUT означает ножка как выход.
С настройками надеюсь разобрались. Теперь нужно дернуть ногу, например PORTD12. Для того, чтобы изменить состояние порта имеется регистр GPIOx_ODR, позволяющий обращаться ко всему порту, либо GPIOx_BSRR позволяющий обращаться побитно, т.е. к каждой ножке отдельно. Но тут тоже все через ж.., в смысле не все так просто, регистр состоит из 32 бит, при этом один порт состоит из 16 ножек. Поэтому младшие 16 бит регистра GPIOx_BSRR отвечают за установку битов, т.е. за включение ножки, а старшие 16 бит за сброс бита, т.е. отключение ножки, о чем нам любезно сообщает даташит.
В принципе можно записать в регистр BSRRL(L-low,т.е. в младшие 16бит) требуемое число, допустим ножка PD12, значит нужно запихать в 12 единичку, 0001 0000 0000 0000 в двоичной системе или 1000 в шестнадцатеричной. Разработчики позаботились о нас и чтобы не испытывать неудобств запихали в файл stm32f4xx.h дефайны всех ножек,
Можно не мучаться, если нужно включить 12 ножку пишем GPIOD->BSRRL = GPIO_BSRR_BS_12; Когда нужно отключить пишем тоже самое, только в старшие 16 бит GPIOD->BSRRH = GPIO_BSRR_BS_12; С одной стороны не привычно, а с другой стороны вроде понятно и удобно.
#include <stm32f4xx_gpio.h> //библиотека для работы с портами #include <stm32f4xx_rcc.h> //библиотека для работы с тактовым генератором int main(void) { volatile uint32_t i; GPIO_InitTypeDef PORT_SETUP; //Создаем структуру, в которую заносим настройки RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); //Включить тактирование PORTD PORT_SETUP.GPIO_Pin=GPIO_Pin_All; //настройки для всех ножек PORT_SETUP.GPIO_Mode = GPIO_Mode_OUT; //ножка выход PORT_SETUP.GPIO_OType = GPIO_OType_PP; //режим пуш-пул PORT_SETUP.GPIO_Speed = GPIO_Speed_2MHz; //скорость 2мгц GPIO_Init( GPIOD , &PORT_SETUP); //записать настройки while(1) { GPIOD->BSRRL = GPIO_BSRR_BS_12; // пишем в младшие 16 бит, чтобы включить ножку for (i=0; i<400000; i++) {} // ждем GPIOD->BSRRH = GPIO_BSRR_BS_12; // пишем в старшие 16 бит, чтобы выключить ножку for (i=0; i<400000; i++) {} // ждем } } |
Щелкаем Project-Build чтобы скомпилировать, а затем Flash-Program Download. Получилась мигалка, сложно — вроде нет, но разобраться со всем придется. Чтобы меня не называли типичным хеллоувордищиком 🙂 попробуем организовать бегущий огонек, аля как плата прошита с завода. На плате четыре светодиода, сидят они на ножках PD12-PD15, удобно что рядом. Кроме того, используем готовые функции для работы с ножками GPIO_SetBit — включить ножку, GPIO_ResetBit — выключить.
#include <stm32f4xx_gpio.h> //библиотека для работы с портами #include <stm32f4xx_rcc.h> //библиотека для работы с тактовым генератором int main(void) { uint32_t PORTD; //переменная которая будет содержать номер текущей ножки GPIO_InitTypeDef PORT_SETUP; //Создаем структуру, в которую заносим настройки RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); //Включить тактирование PORTD PORT_SETUP.GPIO_Pin = GPIO_Pin_All; //настройки для всех ножек PORT_SETUP.GPIO_Mode = GPIO_Mode_OUT; //ножка выход PORT_SETUP.GPIO_OType = GPIO_OType_PP; //режим пуш-пул PORT_SETUP.GPIO_Speed = GPIO_Speed_2MHz; //скорость 2мгц GPIO_Init( GPIOD , &PORT_SETUP); //записать настройки while(1) { PORTD = GPIO_Pin_12; //записываем в переменную ножку 12 for(g=0; g<4; g++) //цикл будет состоять из 4 миганий { GPIO_SetBits(GPIOD, PORTD); // Включаем ножку for(volatile uint32_t i = 0; i < 100000; i++) {} // ждем GPIO_ResetBits(GPIOD, PORTD); // Выключаем ножку for(volatile uint32_t i = 0; i < 100000; i++) {} // ждем PORTD = PORTD << 1; //сдвигаем бит, чтобы мигнуть следующей ножкой } } } |
Вот такой вот бегущий огонек, видосик для наглядности.
Проект кокоса
Если бы аtmel имели такую же библиотеку как STDPeriph у ST, то цены бы ей не было б, и не надо было заморачиваться со всякими регистрами по отдельности. Просто шуруй как под винду
От себя добавлю:
переменную g лучше объявить, а то кокос ругаться будет.
GPIO_BSRR_BS_12 проще заменить на более человечное GPIO_Pin_12/
GPIOD->BSRRL заменить на функцию GPIO_SetBits(порт, переменная)
GPIOD->BSRRH на GPIO_ResetBits(порт, переменная)
В целом, спасибо админу! Продолжай.
Для Atmel есть code vision avr (платный). Да и в atmel studio можно и io view биты регистров выставить а затем посмотреть значения регистров.
Тут имелось ввиду что у atmel studio, что в cavr все настройки выставляются битами в регистрах. В stm32 для этого есть библиотека SPL ныне переросшая в HAL, ибо количество регистров поболе чем в АВР. Возможно в этом есть доля смысла, ибо PORTB.1=1 работает, а в некоторых контроллерах PORTE.1=1 не работает. Или например, было бы удобнее писать как то так: uart_init(9600,8,n,1); вместо всяких кодогенов. Но как исторически сложилось, что у AVR нет «родных» библиотек для периферии.
Здравствуйте, хороший пример, подскажите пожалуйста как выключить только один диод??( плата stm32f4vg) т.е. я включаю все диоды и хочу выключать их по очереди.
GPIO_SetBits(порт, переменная) — включить ножку
GPIO_ResetBits(порт, переменная) — выключить ножку
Да это понятно, проблема вот в чем, по кнопке у меня должен выключаться только один диод, а выключаются три, остается гореть только один.
вот так включаю диоды:
void led_on_all(void)
{
GPIOD-> ODR= GPIO_ODR_ODR_12;
GPIOD-> ODR= GPIO_ODR_ODR_13;
GPIOD-> ODR= GPIO_ODR_ODR_14;
GPIOD-> ODR= GPIO_ODR_ODR_15;
}
вот так выключаю:
GPIOD->BSRRH = GPIO_Pin_13;
весь main:
int main(void)
{
setup_leds();
while(1)
{
if (GPIOA->IDR & GPIO_IDR_IDR_0)
{
GPIOD->BSRRH = GPIO_Pin_13;
}
else {
GPIOD-> ODR= GPIO_ODR_ODR_12;
GPIOD-> ODR= GPIO_ODR_ODR_13;
GPIOD-> ODR= GPIO_ODR_ODR_14;
GPIOD-> ODR= GPIO_ODR_ODR_15;
}
}
}
после нажатия кнопки гаснут диоды 12, 13, 14, 15- горит, когда я меняю 14 и 15 местами то остаётся гореть 14))
setup_leds :
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
GPIOA->MODER &= ~GPIO_MODER_MODER0;
GPIOD->MODER |= GPIO_MODER_MODER12_0 | GPIO_MODER_MODER13_0 |
GPIO_MODER_MODER14_0 | GPIO_MODER_MODER15_0;
Отбой, попробовал включить по другому :
GPIOD->BSRRL = GPIO_BSRR_BS_12;
GPIOD->BSRRL = GPIO_BSRR_BS_13;
GPIOD->BSRRL = GPIO_BSRR_BS_14;
GPIOD->BSRRL = GPIO_BSRR_BS_15;
и выключать:
GPIOD->BSRRH = GPIO_BSRR_BS_13;
все получилось,
если знаете почему не получалось раньше объясните пожалуйста
Потому что сбрасывать нужно не
GPIOD->BSRRH = GPIO_Pin_12;
а вот так
GPIOD->BSRRH = GPIO_BSRR_BR_12;
по моему пока ты нажмешь и отожмешькнопку пройдет 10000 тактов
С такой кучей циклов не нужен ли сторожевой пес?
//Кроме того, используем готовые функции для работы с ножками GPIO_SetBit — включить ножку, GPIO_ResetBit — выключить.
Откуда взялись эти функции GPIO_SetBit и GPIO_ResetBit в ref manual ничего не сказано про них, ничего там и не расписано про например GPIO_Mode с его вариантами (IN/ OUT/ AF/ AN)?
эти функции взялись из standart periph lib которую предоставляет сама ST. По факту это удобные обертки, можете заглянуть в определение функции GPIO_SetBit и увидите что то вроде GPIOx->BRR = (uint32_t)GPIO_Pin;
Подскажите начинающему, при попытке скомпилировать выдается сообщение you have not configured the dcc tool chain path, please configure it. Какая ему еще конфигурация нужна?
да, нужно скачать компилятор arm-gcc-none-eabli и указать до него путь
Подскажите пожалуйста как записать 8-bit, или 16-bit число целиком в порт ну например как Atmege PORTA=0xEF; или PORTA=0b11101111; если есть такая возможность.
точно также, если напрямую через регистры то так GPIOA->ODR = 0xFF, если используете SPL то так GPIO_Write(GPIOA, 0xFF)
Вы в дальнейших занятиях упоминали среду Keil в который есть отладчик, можно установить ее вместе Coocox. Конфликтов не будет?
можно, даже я бы рекомендовал, ибо кокос загнулся и развиваться не планирует. у кейла бесплатно 32кб проекты можно создавать.
Скажите создавать структуру (GPIO_InitTypeDef PORT_SETUP), а затем записывать настройки я так понял в порт (GPIO_Init( GPIOD , &PORT_SETUP); //записать настройки) обязательно можно обойтись без них? Если можно пример. Спасибо.
Записать целиком число в порт получилось только так GPIOD->ODR = ((uint16_t)0xFFFF);
Можно не использовать структуры, для записи настроек. Открываете даташит и смотрите регистры, что значит каждый бит, но есть шанс ошибиться, поэтому проще использовать структуры. С записью в порт все правильно, порт ведь 16 битный.
Еще вопрос, в Coocox режим отладки, симуляции есть? Или предусмотрен?
отладка есть, симуляции нет
Вы пишите:
GPIO_InitTypeDef PORT_SETUP; //Создаем структуру, в которую заносим настройки// а затем:
PORT_SETUP.GPIO_Pin = GPIO_Pin_All;
Можно записать «напрямую» ну вроде:
GPIO_InitTypeDef.GPIO_Pin = GPIO_Pin_All; или что то подобное, мне интересно. Пробовал записать так выдает ошибку.
И еще, уж не обижайтесь на мою бестолковость. Как в режиме отладки ставить точки останова? 😳
Андрей, почитайте что такое структура, хотя бы тут http://avr-start.ru/?p=3602 если не понятно то спросите. По сути вы пытаетесь не выделив память под переменную писать в нее. Это как бы написать int = 0, а переменной не указать. Вообще если вам более понятен путь как в авр, то пишите напрямую в регистры. Там не так страшно как кажется.
Спасибо за подсказку, прочитал, и извините за глупый вопрос.
Пишу из конца 2017, скачал CooCox, при создании проекта негде выбрать GPIO, использую STM32VLDiscovery
Прикладываю скриншот, подключил все что можно, библиотека stm32f10x_gpio.h присутствует в трех последних папках STM32F10x_HD_VL_STDLIB, STM32F10x_LD_VL_STDLIB, STM32F10x_MD_VL_STDLIB
https://pp.userapi.com/c840632/v840632127/233c7/Ho-xxGwj_x8.jpg
кокос вроде загнулся уже давно, там прикол был что до какой то версии вроде 1.7, использовались старые библиотеки для инициализации SPL, начиная выше используется HAL.