STM32 blink++ или читаем данные инкрементального энкодера

Мотивация
Пару месяцев назад я рассказывал, как работает инкрементальный энкодер и как читать угол при помощи простейшей ардуины. Разумеется, немедленно получил вот такие комментарии:

bb1943ab84f8a8845da1dfe232865d62.png

У меня ардуино головного мозга. Пусть лично я самой средой ардуино и не пользуюсь, но всё же считаю, что это весьма полезная штука. Я слышал много ужасов про то, как начинать с stm32, и не хотел в это влезать. С другой стороны, в последнее время всё чаще стали слышны комментарии о том, что инструментарий допилили и вообще всё в шоколаде. Решил попробовать, сколько времени у меня займёт сделать простейший проект типа помигать светодидом. Купил синюю таблетку, купил китайский аналог отладчика st-link v2, и сел с этим всем разбираться.

Забегая вперёд, вот так выглядит железка, о которой идёт речь:

49fe2fd6bcf384d791eea70a28c02fc4.jpg

Поехали: настройка проекта
В качестве среды разработки я выбрал System Workbench for STM32, это эклипс с предустановленными плагинами для работы с stm32. Вторая софтина, которой очень удобно пользоваться, это STM32CubeMX. В идеальном случае больше ничего не нужно. Достаточно зарегистироваться на этих двух сайтах, скачать софт, покликать на кнопочки «ок».

А дальше начинается самое интересное. Интернет очень беден на туториалы, которые работают. Посему покажу целиком весь кликодром, который позволил запустить мой код. Я чувствую, что сам буду возвращаться в этот пост.

Запускаем STM32CubeMX, вбиваем наш процессор и жмём на кнопку start project:

a53096dcb904e687b9e4c3cf5c7a4bc2.png

STM32CubeMX — это софтина, в которой мы конфигурируем ноги процессора, а потом получаем готовый шаблон кода, в котором уже всё сконфигурировано, нужно лишь использовать.

У большинства синих таблеток на пине PC13 висит светодиод, как же мы без него, конфигурируем его в GPIO_OUTPUT:

8c0e629e3c071b3a198d6c311f243a1b.png

Мы хотим, чтобы работал процесс отладки, поэтому ставим serial wire в SYS:

6cd3d0ff9f8110f3bceec35d3c9cc4d4.png

Я решил повесить энкодер на таймер TIM4, ставлю его в этот режим. Обратите внимание, что НЕКОТОРЫЕ входы процессора терпимы к 5 вольтам (five volt tolerant, FT), и конкретно PB6/PB7 позволяют подключить пятивольтовый энкодер.

4d95c14155c5323be4ae6c1028838e9c.png

Я люблю выводить данные в старый добрый виртуальный последовательный порт, прямо как в ардуине, поэтому кликаем ещё пару раз:

3de301a2520a487f66b549b9b22bdad8.png

Процессор будет получать клок от внешнего резонатора:

0278edd602ebd835a3c165cea18ce4f7.png

Теперь ноги более-менее расставлены, самое время расставить клоки. Идём в закладку clock configuration, и отказываемся от автоматического помощника:

34b8c1a9014ba0d7d584dfee916a037c.png

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

d4991da78a7f197da9d774951cbdae71.png

Дальше configuration→GPIO настраиваем ногу GPIO:

567bec622a293ef090cef43bf650aa6b.png

и в configuration→TIM4 настраиваем таймер так, чтобы он считал в режиме 4x (см. мою предыдующую статью, там объясняется, что это такое)

689c697c23301440c57758e27ab7211f.png

После чего в настройках проекта выставляем директории и то, что код должен сгенерироваться под System Workbench for STM32:

d795fbf0a498d8ae230df32f01729d5d.png

После чего генерируем код, и открываем эклипс. Первым делом кликаем сюда:

0413f52d56c3f4d97b7ef22b11e35e7c.png

И дальше мы готовы писать код.

Непосредственно код и отладка
Вот так выглядит единственный код, который я написал руками, в нём всё довольно прозрачно:

int main(void) {
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_TIM4_Init();
  MX_USB_DEVICE_Init();
  HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);
  char buf[25];
  int32_t capture=0, capture_prev=0, encoder=0;
  while (1) {
    capture = TIM4->CNT;
    encoder += capture - capture_prev;
    if (abs(capture-capture_prev)>32767) {
      encoder += (capture


Единственная тонкость в том, что у синей таблетки все счётчики 16-битные, а у меня энкодер генерирует 10000 событий на оборот. Поэтому я руками отслеживаю переполнение значения счётчика, мой код предполагает, что между двумя чтениями счётчика энкодер не сгенерирует 32к импульсов, писать прерывания-обработчики события overflow/underflow мне было откровенно лень. Светодиод у меня изменяет своё состояние при каждом переполнении, у нас блинк или нет?

Всё вроде готово, поэтому компилируем проект и запускаем отладчик:

759da02e8d141806ef95899b2b6bf420.png

Сама синяя таблетка воткнута в usb, от неё идут 3 провода к китайскому stlink, который тоже в свою очередь воткнут в usb. Энкодер получает питание 5В от USB. Если всё прошло хорошо, то запустится отладчик и у нас в системе появится виртуальный порт /dev/ttyACM0.

Вращаю вал энкодера пальцами, делаю cat /dev/ttyACM0 и наслаждаюсь правильным чтением энкодера:

bfcdd876ba473c16656500cf8f0ec15c.png

Если хочется сделать просто блинк/подобное, то минимальное подключение синей таблетки выглядит вот так:

5d927c0d2965c3aecccc1712de86250b.jpg

А если не всё хорошо?

USB


Чтобы дойти до этого момента, мне понадобилось полных три дня. Что же заняло столько времени? Например, если я повторно залью код в синюю таблетку, то она будет хорошо работать, отладка тоже, последовательный порт будет присутствовать в системе, но ничего из него не будет поступать. Если переткнуть usb шнурок, то всё будет прекрасно.

Длительное гугление приводит к тому, что в разводке платы есть проблемы, и более того, чаще всего в ней стоит неправильный резистор:

4f6ea18a5337b57380e065815fa45cd2.jpg

Замена этого резистора у мне помогла на половине компьютеров. Более внимательное гугление показывает вот это. Вот так выглядит предложенный фикс:

79793a3239775dc9ed98c0ea2e6a3bea.jpg
3d18ef80ff9f8814dfcc1dd9f92c6d50.jpg

Я же удовольствовался программным резетом усб устройства. Вот код, ему достаточно сделать cc usbreset.c:

Скрытый текст
/* usbreset -- send a USB port reset to a USB device */

#include 
#include 
#include 
#include 
#include 

#include 


int main(int argc, char **argv)
{
    const char *filename;
    int fd;
    int rc;

    if (argc != 2) {
        fprintf(stderr, "Usage: usbreset device-filename\n");
        return 1;
    }
    filename = argv[1];

    fd = open(filename, O_WRONLY);
    if (fd < 0) {
        perror("Error opening output file");
        return 1;
    }

    printf("Resetting USB device %s\n", filename);
    rc = ioctl(fd, USBDEVFS_RESET, 0);
    if (rc < 0) {
        perror("Error in ioctl");
        return 1;
    }
    printf("Reset successful\n");

    close(fd);
    return 0;
}


А вот так его работа:

0cf249a5315adcd7b26a320eaec3ac99.png

Обратите внимание, что сначала из порта ничего не поступало, после софтерного резета всё починилось.

SWD


Какие ещё могут быть проблемы? Например, неработающий отладчик:


Я очень долго не мог залить код, а когда смог, то сначала мне приходилось ставить джамперы в boot0=1, boot1=0, заливать код, а потом переставлять джамперы обратно. При том, что нормальное положение джамперов — это boot0=boot1=0. Это из-за того, что в процессоре был отключен SWD, обратите внимание, что мы его специально включали в STM32CubeMX. Но мы-то включили, а плата могла прийти с отключенным SWD, откуда свистопляска.

Бутлоадер


Если вдруг кто-то решит, что ардуино-среду уже допилили под стм, то имейте в виду, что бутлоадер скорее всего предпрошит не будет.

Защита от записи


В одной из купленных мною плат был просто защищён от записи флеш. Единственно известный мне способ это починить — это под виндой установить STM32 ST-LINK Utility и полностью затереть память. Линуксовые приблуды не помогают.

Просто дохлые платы


А бывает и так. Причём оно как-то шевелится, но частично дохлое. Как это отличать от своих кривых рук — не очень ясно.

SWO: Serial wire output


У вас есть официальная демо-плата и вы привыкли делать вывод информации через SWO? С китайским клоном напрямую такой фикус не пройдёт, не теряйте время, вот фикс:


Вывод
И много-много других проблем, каждая из которых в принципе может быть решена. Например, то, что у stm32 errata (если существует) зачастую длиннее самого даташита…

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

В целом я не жалею потраченного времени, и следующий проект буду пилить на stm32. Но теперь мне совершенно ясно, отчего у подобных синих таблеток существенно меньше коммьюнити. Если вам не нужна головная боль, а нужно просто работающее железо, то не ведитесь на рекламу, мол, оно всё копеечное. Покупайте настоящие программаторы и настоящие платы разработчика. А то и вообще оставайтесь в среде ардуино, в ней много чего вкусного.

© Habrahabr.ru