STM32CubeMX по умолчанию отключает SWD для серии F1

STM32CubeMX — это генератор исходного кода для микроконтроллеров STM32. Позволяет настроить периферию с помощью графического интерфейса.

Если создать проект в STM32CubeMX, выбрать микроконтроллер серии F1 и сгенерировать исходный код, не производя никаких настроек, то этот код отключит интерфейсы программирования JTAG и SWD. Если такую программу скомпилировать и прошить, то в следующий раз прошить микроконтроллер будет затруднительно.

То есть новичок, делающий первую программу для STM32F1, гарантировано окажется в тупиковой ситуации.

Проблема проявляется только для микроконтроллеров серии F1. Для проверки использовался STM32CubeMX последней (4.17.0, на 13.11.2016) версии.

Проблема несколько раз упоминалась (раз, два, три) в 2015 году. И STM даже обещали (по третьей ссылке) ее исправить, но не исправили.

Код, отвечающий за отключение JTAG и SWD, находится в файле stm32f1xx_hal_msp.c в функции HAL_MspInit(), которая вызывается функцией HAL_Init(), и выглядит следующим образом:

 /**DISABLE: JTAG-DP Disabled and SW-DP Disabled
*/
__HAL_AFIO_REMAP_SWJ_DISABLE();

Чтобы избежать этой проблемы, нужно в STM32CubeMX на вкладке Pinout в дереве настроек найти пункт Configuration -> Peripherals -> SYS -> Debug и из выпадающего списка выбрать подходящее значение:

  • No Debug — значение по умолчанию, отключает отладочные интерфейсы JTAG и SWD.
  • Serial Wire — отключает JTAG, включает SWD, который использует только два вывода, подходит для программатора ST-Link.
  • JTAG (4 pin) — включает JTAG и SWD, используется 4 вывода (без NJTRST).
  • JTAG (5 pin) — включает JTAG и SWD, используется 5 выводов (с NJTRST), что соответствует состоянию микроконтроллера после сброса.

Как прошить микроконтроллер с отключенными JTAG и SWD

Если SWD и JTAG отключились, то не все потеряно. Уверен, что есть проекты в которых это даже необходимо.

Для начала нужно создать прошивку, которая бы не отключала SWD и JTAG.

Вариант 1

Вариант работает при прошивке через SWD с помощью ST-LINK/V2 (ST-LINK второй версии). Через JTAG тоже можно, но с ST-LINK, у которого версия прошивки V2J15Sx или новее.

В программе STM32 ST-LINK utility в настройках нужно выбрать режим (Mode) «Connect Under Reset» (подключение при сбросе). Этот режим позволяет подключиться к микроконтроллеру до начала выполнения программы.

Чтобы прошить микроконтроллер, нужно на вывод сброса микроконтроллера (NRST) подать низкий уровень (или соединить вывод с землей), запустить прошивку и сразу убрать низкий уровень с NRST.

Возможно, чтобы вручную не дергать NRST, можно соединить его с одноименным вывод ST-LINK и произвести еще какие-то настройки STM32 ST-LINK utility. Но автор эту идею не проверял. Если вы знаете, напишите в комментариях.

Вариант 2

Если у вас нет ST-LINK/V2, то микроконтроллер можно прошить через USART с помощью программы STM32 Flash loader demonstrator. Для этого понадобится переходник USB-UART, который создаст на компьютере виртуальный COM-порт.

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

В Application note AN2606 можно уточнить какие USART с какими выводами использует загрузчик, а также необходимое состояние выводов BOOTx.

QextSerialPort::read() и таймаут в Виндоус

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

Проблема решается, если открывать порт в режиме QIODevice::Unbuffered, например:

port->open( QIODevice::ReadWrite | QIODevice::Unbuffered );

Такое решение нашлось на Stack Overflow.

Но после этого в моей программе стало некорректно работать чтение в Юникс. Читалось меньше данных, чем запрашивалось, даже при достаточно большом таймауте, как-будто таймаут вообще не работает. Проблема решилась использованием разных режимов на разных операционных системах:

const QIODevice::OpenMode openMode =
#if   (defined Q_OS_UNIX)
    QIODevice::ReadWrite;
#elif (defined Q_OS_WIN)
    QIODevice::ReadWrite | QIODevice::Unbuffered;
#else
    #error "Unknown Operating System"
    QIODevice::NotOpen;
#endif

port->open(openMode);

Мне кажется, первая проблема связана с поведением функции «qint64 QIODevice::read(char *data, qint64 maxSize)» из библиотеки Qt. Проблемная часть кода из файла qiodevice.cpp библиотеки Qt версии 5.3.0:

qint64 QIODevice::read(char *data, qint64 maxSize)
{
    ...
            if ((d->openMode & Unbuffered) == 0 && maxSize < QIODEVICE_BUFFERSIZE) {
                ...
                int bytesToBuffer = QIODEVICE_BUFFERSIZE;
                ...
                qint64 readFromDevice = readData(writePointer, bytesToBuffer);
                ...
            }
    ...
}

При этом QIODEVICE_BUFFERSIZE задается равным 16384 в файле qiodevice_p.h:

#define QIODEVICE_BUFFERSIZE Q_INT64_C(16384)

Функция readData() реализуется в библиотеке QextSerialPort и в Виндоус вызывает функцию ReadFile() из WinApi, которая читает заданное количество байт из COM-порта.

Таким образом, если устройство не открыто в режиме Unbuffered, то в Виндоус у COM-порта запрашивается 16384 байт, чтобы заполнить некий буфер. Если нам требуется меньше данных, то ReadFile() не возвращает управление пока не истечет таймаут.