Решил я подержать за вымя набирающие популярность процессоры на ядре ARM, а именно STM32L152RB, который стоит на отладочной плате STM32L Discovery.
Скажу сразу – это сложнее чем возиться с Arduino, но в целом не так сложно как я думал. К моему удивлению, перемешенному с разочарованием документации для начинающих по этой плате очень мало. Все статьи посвящены более старой STM32VL Discovery, построенной на STM32F10x, которая отличается от моей STM32L1xx.
Итак, для начала почитал замечательный мануал от камрада Di Halt, пишет он конечно хорошо и доходчиво, но проблема в том, что как он сам признается – про ARM он пишет не для новичков, а для товарищей уже вкуривших AVR или PIC. Arduino хоть и построен на AVR, но все же штука сильно упрощенная и я себя к “вкурившим AVR” причислить не могу. Тем ни менее, установить по выше указанной статье Keil 4.6 и даже прошить через него мою STM32L-DISCOVERY мне удалось. Для этого я загрузил в Keil проект Demo (находится в папке Keil\ARM\Boards\ST\STM32L-Discovery) и залил его в плату. То что все прошло успешно можно увидеть по версии тестовой прошивки (показывается если нажать и удержать на несколько секунд синюю кнопку), была V1.1.1, стала V2.0.0.
А вот дальше начались затыки. Код мигания светодиодом описанный в той статье у меня не скомпилировался, пошла ругня на имена регистров. Ну что же, придется разбираться подумал я и полез качать главную доку по моему камню, а именно Reference manual для серий STM32L151xx, STM32L152xx и STM32L162xx. Увидев, что документик всего на 822 страницы я понял, что тут за 5 минут не разберешься, но мы не привыкли отступать! Итак, компилятор ругался в первую очередь на
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
В комментарии сказано, что этой командой мы подаем такты на порт, без нее с портом ничего сделать не получится, т.к. все что можно из периферии по умолчанию выключено – энергетический кризис, чо. Покурив мануал, заменил данную строчку на
RCC->AHBENR |= RCC_AHBENR_GPIOBEN;
Что это значит – как я понимаю на STM32F10x порты висят на шине APB2 (Advanced Peripheral Bus №2) и соответственно там их и включают, а вот в моём STM32L1xx они висят на шине AHB (Advanced High-performance Bus) и включать их мне надо именно на ней. А вот как включать это дело вкуса, можно в мануале посмотреть биты, в которые надо записывать нужные значения и записать их непосредственно, а можно открыть stm32l1xx.h (который я скопировал в свой проект из проекта Demo) и посмотреть там имя нужного дефайна. Собственно, по имени дефайна и по комментариям его и выбираешь, а потом, если очень хочется, можно перевести его в двоичное значение и сравнить с мануалом. Конечно, с непривычки это все выглядит мрачно и непонятно, но когда вкуриваешься немного глубже начинаешь видеть тут глубоко продуманную логику. Остановлюсь на этом подробней. Для того, чтобы конфигурировать микроконтроллер используются управляющие регистры, записываем в какой-то бит такого регистра нужное значение и готово. Как тумблер включили. Проблема только, что тумблеров этих как в кабине Боинга. Но у нас есть мануал, в котором все эти регистры описаны и указано что куда писать или читать. Из него можно понять, что порты GPIO находятся на шине AHB и соответственно рулятся с помощью управляющих регистров этой же шины, а регистры, кстати, носят весьма логичные названия, например AHBENR – AHB Enabler Register , теперь если залезть в файл stm32l1xx.h, то можно увидеть секцию озаглавленную Bit definition for RCC_AHBENR register, а в ней узреть дефайн RCC_AHBENR_GPIOBEN с коментом «GPIO port B clock enable» хотя и без комментария можно понять, что GPIOBEN обозначает GPIO – речь о протах ввода/вывода, B – порт B, EN – ENable Почему порт B? Потому что светодиоды висят именно на нем, а конкретно на выводах PB6 И PB7 (это написано на плате возле самих светодиодов). Ну и два слова про конструкцию |= на случай, если читатель не знаком с битовой логикой языка С, эти два символа означают, что к тому что справа от них (в нашем случае это RCC_AHBENR_GPIOBEN, который представляет из себя дефайн числа 0×00000002 или в двоичном виде 10) нужно применить операцию логического ИЛИ с тем что слева от них и результат занести опять таки в то, что слева. Что это значит на практике? Что второй бит регистра RCC_AHBENR гарантировано примет значение единицы, при этом значения остальных битов не изменятся. Почему? Потому что логическое сложение ИЛИ чего угодно с единицей даст на выходе единицу, то есть там где были единицы там они и останутся, а где были ноли они же и останутся (кроме, конечно, того бита в который мы подаем единицу). Если последнее предложение не понятно, то лучше сначала прочитать вот это. Хватит о ерунде пойдем дальше. А дальше нужно сконфигурировать порт на выход и не просто выход а, а Push-Pull. Для STM32VL Discovery в статье по которой я учусь (ссылка выше) автор делает это командами
GPIOA->CRL &= ~GPIO_CRL_CNF3; // Обнулили биты CNF3 т.к. после старта они настроены как (01)
// режим Open Drain. А нам надо Push-Pull (00)
GPIOA->CRL |= GPIO_CRL_MODE3_0; // Настроили порт на выход (для простоты возьмем тот факт,
// что GPIOA-CRL-MODE после сброса равны (00). Но в реальной программе
// эти моменты надо проверять и делать полную инициализацию порта.
// Согласно таблице 18 из RM

У нас это все тоже не работает и даже не компилится. Лезем в мануал и выясняем, что регистры у нас другие. Для того, чтобы сконфигурировать порт на выход, нужно использовать регистр GPIOx_MODER (где x это буква порта, в нашем случае это B). Тут опять таки все логично MODER это регистр режима (MODE — режим, R — регистр). Нам нужно установить выводы 6 и 7 на выход, для этого нужно записать логическую единицу в биты 12 и 14, а в биты 13 и 15 наоборот — ноли. Вы спросите – а почему так странно, биты портов 6 и 7, а биты регистра 12 и 14? Дело в том, что на каждый порт в регистре отводится по два бита, а для того чтобы порт работал как выход нужно записать в эти два бита двоичное значение 01. Это всё можно узнать из мануала на камень, но гораздо проще полезть все в тот же stm32l1xx.h, найти там раздел отмеченный комментарием «Bit definition for GPIO_MODER register» а в нем найти два дефайна GPIO_MODER_MODER6_0 и GPIO_MODER_MODER7_0 которые установят нужные нам биты в правильные значения. Но этим мы только сконфигурируем порт на выход, а еще нужно бы выбрать режим в котором он будет работать, нам нужен push-pull, а не open-drain, но т.к. push-pull это и так значение по умолчанию, то можно не париться, а если хочется попариться, но для гарантии нужно в регистре GPIOB_ OTYPER биты 6 и 7 изменить на 0 (кстати в реальной программе это надо делать обязательно, мало ли что там по дефолту стоит). Кстати, оценили имя регистра? O – Output, TYPE – Тип, R – регистр, то есть регистр определяющий тип выхода порта. На всякий еще упомяну, что есть регистр GPIOx_OSPEEDR, который, как можно понять из названия, определяет выходную скорость порта (от 400кГц до 50МГц), то есть, как я понимаю, максимальную частоту с которой мы этот порт можем писать значения. Ну нам для моргания светодиодом и 400кГц многовато, так что тут ничего менять не будем.
С конфигурацией покончили, теперь надо бы записать в порты единицы, чтобы светодиоды зажглись. Делается это через регистры GPIOB_ODR (очевидно Output Data Register, для входа используется IDR – Input Data Register), просто пишем в нужные биты единички. Пошерстив файл stm32l1xx.h это можно сделать гламурно через дефайны GPIO_ODR_ODR_7 и GPIO_ODR_ODR_6 вот что получилось:
#include "stm32l1xx.h"
void InitAll(void){
RCC->AHBENR |= RCC_AHBENR_GPIOBEN; // включили тактирование на портБ
GPIOB->MODER |=GPIO_MODER_MODER7_0; //порт B вывод 7 сконфигурили как выход
GPIOB->MODER |=GPIO_MODER_MODER6_0; //порт B вывод 6 сконфигурили как выход
GPIOB->ODR |= GPIO_ODR_ODR_7; //порт B вывод 7 записали 1
GPIOB->ODR |= GPIO_ODR_ODR_6; //порт B вывод 6 записали 1
return;
}
int main(void) {
InitAll();
while(1){
}
}

Если все настроено как в статье товарища Di Halt, то запустится симуляция, но нам интересней реальный свет и поэтому для начала зайдем в свойства проекта (Alt + F7) и в вкладке Debug вместо Use Simulator Выберем Use и в выпадающем окне St-Link Debugger (это дебагер, который встроен в нашу плату). Теперь компилим (F7) и заливаем (Ctrl + F5), откроется окно дебага, там безусловно много всего интересного, но нам для начала надо узнать горят ли наши светики, поэтому жмем сразу Run (F5) и наслаждаемся синим и зеленым светом наших светодиодов. На этом месте можно выпить чая, оправится и закурить. Самое сложное уже позади, дальше будет только легче.
А дальше собственно хотелось бы этими самыми светодиодами помигать. Для этого читаем статью про использование базовых таймеров в STM32, но беда в том, что и эта статья для треклятой STM32VL Discovery и для моей новенькой STM32L приведенный код не работает. Ну что ж придется опять лезть в мануал. Автор статьи использует Standard Peripherals Library (про которую можно почитать тут) так что не удивляйтесь по поводу странных функции в той статье, я ее не использую, там в общем и без нее ничего сложного. Для начала, как и в случае с портами ввода/вывода, надо пустить тактирование на таймер (я решил использовать шестой, т.к. из мануала понял, что простые таймеры это 6 и 7, остальные с ненужными мне пока примочками). Несложный поиск по ставшему уже родным мануалу дает нам следующее: таймер 6 расположен на шине APB1 (в STM32VL Discovery кстати на ней же, хоть тут совпадение) и активируется через регистр RCC_APB1ENR в который нужно записать дефайн RCC_APB1ENR_TIM6EN любезно предоставленный все тем же файлом stm32l1xx.h
Далее нужно разрешить прерывания от таймера, чтобы когда он переполнится, сработало прерывание и светодиоды сменили свое состояние. Про прерывания можно почитать тут.
Делается это через регистр TIM6_DIER который расшифровывается так: TIM6 это собственно таймер6, а DIE это не «УМРИ!» а всего лишь DMA/Interrupt Enable, R как обычно Register
TIM6->DIER |= TIM_DIER_UIE;
UIE означает Update Interrupt Enable
Теперь автор статьи советует установить делитель таймера на 24000. Это означает, что в таймер будет попадать только каждый 24000 тик. Выбрано это число так: раз у нас плата работает на частоте 24МГц, то записывая каждый 24000ый такт таймер будет считать одну тысячную секунды, соответственно когда значение таймера дойдет до тысячи пройдет одна секунда!
TIM6->PSC = 24000 - 1; // Настраиваем делитель что таймер тикал 1000 раз в секунду
TIM6->ARR = 1000 ; // Чтоб прерывание случалось раз в секунду

Ну и собственно запускаем таймер:
TIM6->CR1 |= TIM_CR1_CEN; // Начать отсчёт!
Что порадовало — эти операции я целиком скопировал из статьи – регистры называются одинаково для обоих плат. А вот с назначением прерывания есть небольшая разница, у автора для в STM32VL Discovery это:
NVIC_EnableIRQ(TIM6_DAC_IRQn);
А у нас для в STM32L Discovery:
NVIC_EnableIRQ(TIM6_IRQn); //Разрешение TIM6_IRQn прерывания
А вот содержимое самой функции осталось то же, только имя изменим и номера светодиодов
void TIM6_IRQHandler(void){
TIM6->SR &= ~TIM_SR_UIF; //Сбрасываем флаг UIF
GPIOB->ODR^=(GPIO_ODR_ODR_6 | GPIO_ODR_ODR_7); //Инвертируем состояние светодиодов
}

Ну вот и все, итого программа выглядит вот так:

#include "stm32l1xx.h"
void InitAll(void)
{
RCC->AHBENR |= RCC_AHBENR_GPIOBEN; // включили тактирование на портБ
RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; //включили таймер6
GPIOB->MODER |=GPIO_MODER_MODER7_0; //порт B вывод 7 сконфигурили как выход
GPIOB->MODER |=GPIO_MODER_MODER6_0; //порт B вывод 6 сконфигурили как выход
GPIOB->ODR |= GPIO_ODR_ODR_7; //порт B вывод 7 записали 1
GPIOB->ODR |= GPIO_ODR_ODR_6; //порт B вывод 6 записали 1
TIM6->DIER |= TIM_DIER_UIE; //разрешаем прерывание от таймера
TIM6->PSC = 24000-1; // Настраиваем делитель что таймер тикал 1000 раз в секунду
TIM6->ARR = 1000 ; // Чтоб прерывание случалось раз в секунду
TIM6->CR1 |= TIM_CR1_CEN; // Начать отсчёт!
NVIC_EnableIRQ(TIM6_IRQn); //Разрешение TIM6_IRQn прерывания
return;
}

int main(void) {
InitAll();
while(1){
}
}
void TIM6_IRQHandler(void){
TIM6->SR &= ~TIM_SR_UIF; //Сбрасываем флаг UIF
GPIOB->ODR^=(GPIO_ODR_ODR_6 | GPIO_ODR_ODR_7); //Инвертируем состояние светодиодов
}

компилим, заливаем и… что-то не то, вместо мигания раз в секунду светодиоды меняют свое состояние раз примерно в 12 секунд… Явно дело в несовпадении частоты, поэтому для начала надо разобраться как же выставляется частота контроллера, описано это вот в этой статье, но я хоть и прочитал статью внимательно толком все равно не смог выставить частоту в 24МГц, пришлось пойти другим путем – погуглить на какой частоте изначально работает моя плата. Как выяснилось в отличии от STM32VL Discovery, которая действительно работает на 24МГц, моя STM32L Discovery поставляется вообще без внешнего кварца (теперь понятно почему у меня не получилось настроить его на частоту 24МГц) и работает от внутреннего источника MSI, который тикает «всего то» на частоте 2,097МГц. Теперь становится ясно, почему светодиоды мигают не раз в секунду, а раз в 12 секунд. Подправим нашу программу с учетом вновь поступивших инструкций, а именно уменьшим делитель таймера до 2000:
TIM6->PSC = 2000-1;
Теперь каждый двухтысячный тик будет записываться в таймер, а поскольку в секунду таких тиков приходит 2097000 то при достижении 1000 тиков пройдет примерно секунда. Примерно, потому что делим мы не на 2097, а на 2000 для ровного счета так сказать. Но на глаз эту разницу мы все равно не заметим, а внутренний резонатор не такой точный как внешний, так что все равно мегаточности добиться не получится. Перекомпиливаем, заливаем, проверяем – работает! Ну вот и все, микроконтроллерный Hello, World! Мы написали, теперь можно переходить к более сложным проектам.
Ссылки:
проект для keil

5 comments on “STM32L-DISCOVERY для начинающих

  1. Роман on said:

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

  2. Anger on said:

    Огромное спасибо за статью, ибо в процессе программирования столкнулся с теми же проблемами, что и у Вас. Хотел бы еще указать Вам на неточность: Замените регистры GPIO_ODR_ODR_7 и GPIO_ODR_ODR_6 на GPIO_OTYPER_ODR_7 и GPIO_OYPER_ODR_6 соответственно, т.к. на них ругается компилятор. А в целом статья очень хороша:)

  3. Александр on said:

    Присоединяюсь к предыдущему комментарию. Полностью. Начало, так сказать, программированию STM32L152RBT6 положено.
    Могу добавить, что были проблемы со связью с платой ST и прошивкой. Информации данной в статье «Di Halt» было не достаточно. Необходимые настройки во вкладках «Debag» и «Utilites» определил сам.

  4. Никита on said:

    Огромное спасибо за статью. Очень рад, что Вы всё подробно расписали и что после простого копипаста всё заработало. Хотелось бы почитать статью про обработку нажатия кнопки в таком же Вашем исполнении.

    Заранее благодарен!

  5. rastamanoff on said:

    Cпасибо за статью.
    Все получилось.Причем тот же код ,только в CoID.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

9 405 Spam Comments Blocked so far by Spam Free Wordpress

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code lang=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" extra="">

   
© 2012 php