PCB Black Pill V2.0 — эта дешевая плата, которая подходит для прототипирования электронных устройств. В этом тексте я написал с какой стороны подходить к плате Black Pill V2.0.

Что надо из оборудования?

Назначение

Пояснение

1

отладочная плата Black Pill V2.0

Само изделие основа прототипа

2

Кабель USB(A)-USB Type-C

для подачи электропитания на плату

3

Кабель USB(A)-USB mini

Для подключения к LapTop программатора

4

Программатор ST-Link V2

переходник с USB-SWD

5

Перемычки гнездо-гнездо 6 шт

для соединения программатора USB-SWD, PCB и переходника USB-UART

6

Переходник USB-UART на основе ASIC CP2102

Для подключения к UART-CLI и общения с прошивкой

7

USB-Hub 3+ порта

Понадобится 3 USB порта

Внешний вид платы Black Pill V2.0:

Блок-схема платы:

Это габариты платы:

Перечень компонентов BOM:

Вот распиновка основных разъёмов P1 и P2:

https://docs.google.com/spreadsheets/d/1F-8yMd7vx_-ITelL1Y58iiNsgo6U-dcQ-6kboUuZfQM/edit#gid=387110378

Чтобы работать с платой Black Pill V2.0 надо вот так её подключить:

Программная часть

Что надо из софтвера?

Утилита

Появнение

1

ST-LINK_CLI.exe

Утилита перепрошивки STM32 микроконтроллеров

2

Tera Term

Терминал последовательного порта для подключения к UART CLI

Вот схема подключения программатора ST-LINK/V2-ISOL:

Вот *.bat скрипт прошивки утилитой ST-LINK_CLI.exe:

echo off
cls

set project_name=black_pill_v2_0_mbr_m
set project_dir=%cd%
echo project_dir=%project_dir%
set artefact_hex=%project_dir%\build\%project_name%.hex
set FlashTool="C:\Program Files (x86)\STMicroelectronics\STM32 ST-LINK Utility\ST-LINK Utility\ST-LINK_CLI.exe"
rem set Device= ID=0x463 SN=066CFF323535474B43013113 
set Device= 
set options= -c %Device% SWD freq=4000 HOTPLUG  LPM -P %artefact_hex% -V "after_programming" -Log -TVolt
call %FlashTool% %options%
rem Reset System
call %FlashTool% -Rst

У каждой взрослой прошивки должна быть UART-CLI. В качестве UART можно выбрать вот эти пины.

Вот и появился лог загрузчика:

Достоинства PCB Black Pill V2.0

1++Малые размеры 54х22 мм.

2++Низкая цена. Всего 408 RUR.

3++ USB Type-C можно подключать повернув на 180 градусов.

4++Удобный шаг 2,54 мм для программирования и подключения модулей расширения.

Недостатки PCB Black Pill V2.0

1--Отсутствуют 4 отверстия для крепления платы на прототипе. К этому приходится приспосабливаться, делая заборы из стоек. Выглядит это очень кустарно.

2--отсутствует встроенный программатор.

3--отсутствует микросхема переходник USB-UART.

4--Отсутствует батарейный отсек для RTC.

5-- Неудачный цвет. На черном фоне создаются иллюзия, что неприпаянные микросхемы припаяны. Лучше бы маска PCB была оранжевой, синей.

6--Отсутствует аналоговый PCB ID. Прошивка не сможет понять на какой она плате.

Чтобы просто подключить Black Pill V2.0 надо сплести целый клубок из проводов и переходников. Оно вам надо?

Вывод

Плата Black Pill V2.0 подойдет как временный вариант для временного прототипа. Black Pill V2.0 - это полумера. Прототипы на Black Pill V2.0 выглядят очень кустарно. Лучше купить для прототипа плату серии Nucleo или хотя бы Super BlackPill так как на ней есть отверстия для крепления.

Links

https://stm32-base.org/boards/STM32F411CEU6-WeAct-Black-Pill-V2.0.html

https://docs.google.com/spreadsheets/d/1F-8yMd7vx_-ITelL1Y58iiNsgo6U-dcQ-6kboUuZfQM/edit#gid=387110378

https://habr.com/ru/articles/655879/

https://habr.com/ru/companies/ruvds/articles/721616/

https://habr.com/ru/companies/ruvds/articles/555864/

https://habr.com/ru/articles/722980/

https://habr.com/ru/articles/742964/

https://www.youtube.com/watch?v=SYsNlJq_4-4
https://www.youtube.com/watch?v=KJ_dOsxyo1Y

https://aliexpress.ru/item/32985219862.html?sku_id=66793774118&spm=a2g2w.productlist.search_results.0.2f804aa6ECCyJ0

Комментарии (13)


  1. gudvinr
    05.07.2023 18:31
    +5

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

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

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

    А если взять daplink или ещё какой-нибудь дебаггер, который умеет в cmsis-dap, то из компа будет выходить один провод и из отладчика несколько проводов в плату, и всё

    А если не жульничать, то и питание тоже с платы отладчика можно подавать, т.к. в nucleo все через один провод тоже идёт

    В общем, не понятно почему у вас эти проблемы. Это не обзор black pill, а какая-то жалоба на то, что вы с проводами не можете справиться


    1. zurabob
      05.07.2023 18:31
      +1

      Если уж используем копеечную таблетку, то к ней хорошо идет китайский же копеечный ST-LINK, где выведено 5В и 3.3В, причем некоторые можно перешить в версию 2.1, где уже есть CDC UART, или взять чуть более дорогой китайский JLINK-OB, где UART уже есть. При этом от таблетки будет идти всего один шлейфик с питанием, отладкой и консолью.


      1. sigprof
        05.07.2023 18:31
        +5

        А ещё таблетку и саму можно превратить в Black Magic Probe.

        Причём просто для заливки прошивки теоретически ничего, кроме USB-кабеля, вообще не нужно, поскольку, в отличие от STM32F103, в чипах F401/F411 есть встроенный загрузчик с поддержкой USB DFU. Правда, как обычно, гладко было на бумаге:

        • В загрузчике F401/F411 есть особенность, больше похожая на баг — не включается внутренняя подтяжка для вывода PA10, который используется как USART1 RX. В результате, если к этому пину ничего не подключено, загрузчик может поймать там мусор от наводок, после чего уйдёт в режим работы через последовательный порт и перестанет работать с USB. Поэтому в любом устройстве, где предполагается использовать встроенный загрузчик F4x1, необходимо каким-то способом обеспечить стабильный уровень на входе PA10 во время работы загрузчика (например, подключить туда внешний резистор подтяжки).

        • По каким-то причинам частота кварца на платах Blackpill — 25 МГц, а не более обычные для отладочных плат на STM32 8 МГц. Для работы прошивки, где параметры тактирования указываются статически при сборке, это не имеет особого значения, а вот загрузчик пытается определить частоту кварца путём сравнения её с частотой внутреннего RC-генератора (HSI), и запас на неточность частоты HSI при необходимости различить 24/25/26 МГц значительно меньше, чем при различении 7/8/9 МГц. В результате этого в описании платы всерьёз предлагается в случае проблем с запуском загрузчика обеспечить температуру чипа 25°C.

        • Для входа в загрузчик на входе PB2 в момент сброса должен быть уровень 0; на плате Blackpill это реализовано путём соединения этого пина с GND через резистор, который может мешать в каких-то вариантах применения этого пина.

        • Наконец, для входа в загрузчик нужно установить на входе BOOT0 уровень 1, что на этой плате может быть сделано единственным образом — путём нажатия кнопки BOOT; больше никуда сигнал BOOT0 не выведен. Можно, конечно, реализовать вход в загрузчик как одну из функций собственной прошивки, а вариант с доступом к кнопке BOOT оставить в качестве средства восстановления после неудачной прошивки, смирившись с необходимостью разбирать устройство для этой цели.


        1. zurabob
          05.07.2023 18:31
          +1

          Это неудобно, нажимать BOOT, RESET, ждать переинициализации USB. И все равно желателен UART для консоли. Гораздо удобнее, когда просто даешь команду Prog и через несколько секунд процессор уже сам запускается с новой прошивкой.


          1. COKPOWEHEU
            05.07.2023 18:31
            +1

            Это неудобно, нажимать BOOT, RESET, ждать переинициализации USB. И все равно желателен UART для консоли.

            Как раз сейчас пишу статью по превращению stm32f103 в такой вот UART программатор-отладчик.


            Кстати! Для полноты картины не могли бы вы подробно описать процесс прошивки по UART из windows, как там из консоли менять параметры порта (скорость, четность) и можно ли порту дать осмысленное имя?


            1. aabzel Автор
              05.07.2023 18:31

              не могли бы вы подробно описать процесс прошивки по UART из windows, как там из консоли менять параметры порта (скорость, четность) и можно ли порту дать осмысленное имя?




              https://www.xanthium.in/Serial-Port-Programming-using-Win32-API


            1. aabzel Автор
              05.07.2023 18:31

              не могли бы вы подробно описать процесс прошивки по UART из windows, как там из консоли менять параметры порта (скорость, четность) и можно ли порту дать осмысленное имя?

              #ifndef SCAN_SERIAL_PORT_H
              #define SCAN_SERIAL_PORT_H
              
              // https://www.xanthium.in/Serial-Port-Programming-using-Win32-API
              
              #ifdef __cplusplus
              extern "C" {
              #endif
              
              #include <stdbool.h>
              #include <stdint.h>
              #include <windows.h>
              
              #define DEDUG_RX_TEXT 0
              #define MAX_COM_NUM 20
              #define DEBUG_FINE_VI_REQ 0
              #define DEBUG_SPOT_COM 0
              #define DEBUG_FAILED_OPENS 0
              #define MAX_NUM_COM_DEV 40U
              
              
              typedef struct  {
                  bool isExistPort;
              
              } xSerialConnection_t;
              
              extern xSerialConnection_t deviceList[MAX_COM_NUM];
              extern HANDLE hComm;
              
              bool com_receive_remain(HANDLE hComm, uint32_t* outRealRxArrayLen);
              bool init_serial(char* com_name, uint32_t baud_rate);
              bool scan_serial(void);
              bool com_send_str(HANDLE hComm, char* txBuffer, uint32_t txBuffLen);
              bool com_receive_str(HANDLE hComm, char* outRxArray, uint32_t capasityRxArray, uint32_t* outRealRxArrayLen);
              bool com_receive_str_timeout(HANDLE hComm, char* outRxArray, uint32_t capasityRxArray, uint32_t* len, uint32_t time_out_ms);
              
              bool serial_init(void);
              
              #ifdef __cplusplus
              }
              #endif
              
              #endif /* SCAN_SERIAL_PORT_H */
              
              
              ---------------------------------
              
              #include "scan_serial_port.h"
              
              #include <stdio.h>
              #include <time.h>
              #include <sys/time.h>
              
              #include "log.h"
              #include "str_utils.h"
              #include "win_utils.h"
              
              #ifdef HAS_MCU
              #error That code only for desktop builds
              #endif
              
              HANDLE hComm;
              
              xSerialConnection_t deviceList[MAX_COM_NUM];
              
              static bool com_set_params(HANDLE hComm, uint32_t baud_rate) {
                  bool res = false;
                  // Initializing DCB structure
                  DCB dcbSerialParams = {0};
                  dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
              
                  BOOL status = GetCommState(hComm, &dcbSerialParams);
                  dcbSerialParams.BaudRate = baud_rate;  // Setting BaudRate = 460800
                  dcbSerialParams.ByteSize = 8;          // Setting ByteSize = 8
                  dcbSerialParams.StopBits = ONESTOPBIT; // Setting StopBits = 1
                  dcbSerialParams.Parity = NOPARITY;     // Setting Parity = None
              
                  SetCommState(hComm, &dcbSerialParams);
                  if(status) {
                      res = true;
                  }
                  return res;
              }
              
              static bool com_set_timeout(HANDLE hComm) {
                  bool res = false;
                  COMMTIMEOUTS timeouts = {0};
                  timeouts.ReadIntervalTimeout = 2;        // in milliseconds
                  timeouts.ReadTotalTimeoutMultiplier = 5; //
                  timeouts.ReadTotalTimeoutConstant = 100; // in milliseconds
                  timeouts.WriteTotalTimeoutConstant = 1;  // in milliseconds
                  timeouts.WriteTotalTimeoutMultiplier = 1;
              
                  SetCommTimeouts(hComm, &timeouts);
                  return res;
              }
              
              bool com_send_str(HANDLE hComm, char* txBuffer, uint32_t tx_buff_len) {
                  LOG_DEBUG(COM,"COMsend [%s]", txBuffer);
                  bool res = false;
                  uint32_t remain_len = 0;
                  res = com_receive_remain(hComm, &remain_len);
                  BOOL status;
                  DWORD dNoOfBytesWritten = 0;
                  status = WriteFile(hComm, txBuffer, (DWORD)tx_buff_len, &dNoOfBytesWritten, NULL);
                  if(dNoOfBytesWritten == tx_buff_len) {
                      if(status) {
                          res = true;
                      }
                  } else {
                      LOG_ERROR(COM,"WriteSerialErr [%s]",txBuffer);
                  }
                  LOG_DEBUG(COM,"COMsend end");
                  return res;
              }
              
              bool com_receive_remain(HANDLE hComm, uint32_t* remain_len) {
                  bool res = false;
              #ifdef HAS_COM_PORT_DEBUG
                  LOG_DEBUG(COM,"%s():", __FUNCTION__);
              #endif
                  if(NULL != remain_len) {
                      *remain_len = 0;
                      char tempChar;
                      DWORD numberBytesRead;
                      uint32_t BytesReadCnt = 0;
                      bool loopRun = true;
                      while(loopRun) {
                          ReadFile(hComm,            // Handle of the Serial port
                                   &tempChar,        // Temporary character
                                   sizeof(tempChar), // Size of TempChar
                                   &numberBytesRead, // Number of bytes read
                                   NULL);
              #if DEDUG_RX_CHAR
                          LOG_DEBUG(COM,"%c", tempChar);
              #endif
                          if(0 < numberBytesRead) {
                              loopRun = true;
                          } else {
                              loopRun = false;
                          }
                          BytesReadCnt++;
                      };
                      if(0 < BytesReadCnt) {
                          *remain_len = BytesReadCnt;
                          res = true;
                      }
                  }
                  return res;
              }
              
              bool com_receive_str(HANDLE hComm, char* outRxArray, uint32_t capasityRxArray, uint32_t* remain_len) {
                  bool res = false;
              #ifdef HAS_COM_PORT_DEBUG
                  LOG_DEBUG(COM,"%s():", __FUNCTION__);
              #endif
                  if((0 < capasityRxArray) && (NULL != outRxArray)) {
                      *remain_len = 0;
                      char tempChar;
                      DWORD numberBytesRead;
                      uint32_t BytesReadCnt = 0;
                      bool loopRun = true;
                      uint32_t  ret= 0;
                      while(loopRun) {
                          ret = ReadFile(hComm,            // Handle of the Serial port
                                   &tempChar,        // Temporary character
                                   sizeof(tempChar), // Size of TempChar
                                   &numberBytesRead, // Number of bytes read
                                   NULL);
                          if(0==ret){
                              loopRun = false;
                          }
              #ifdef DEDUG_RX_CHAR
                          LOG_DEBUG(COM,"%c", tempChar);
              #endif
                          if(0 < numberBytesRead) {
                              if(BytesReadCnt < capasityRxArray) {
                                  outRxArray[BytesReadCnt] = tempChar; // Store Tempchar into buffer
                              }
                          } else {
                              loopRun = false;
                          }
                          BytesReadCnt++;
                      };
                      if(0 < BytesReadCnt) {
                          *remain_len = BytesReadCnt;
                          res = true;
                      }
                  }
              #ifdef HAS_COM_PORT_DEBUG
                  LOG_DEBUG(COM,"%s(): end", __FUNCTION__);
              #endif
                  return res;
              }
              
              bool com_receive_str_timeout(HANDLE hComm, char* outRxArray, uint32_t capasityRxArray, uint32_t* remain_len, uint32_t time_out_ms) {
                  bool res = false;
              #ifdef HAS_COM_PORT_DEBUG
                  LOG_DEBUG(COM,"%s():", __FUNCTION__);
              #endif
                  if((0 < capasityRxArray) && (NULL != outRxArray)) {
                      *remain_len = 0;
                      char tempChar;
                      DWORD numberBytesRead =0;
                      uint32_t BytesReadCnt = 0;
                      bool loopRun = true;
                      uint32_t  ret= 0;
                      struct timeval start, cur;
                      double diff_secs = 0;
                      gettimeofday(&start, NULL);
                      while(loopRun) {
                          gettimeofday(&cur, NULL);
                          diff_secs = (double)(cur.tv_usec - start.tv_usec) / 1000000 + (double)(cur.tv_sec - start.tv_sec);
                          if(time_out_ms<diff_secs*1000 ){
                              loopRun = false;
                          }
                          ret = ReadFile(hComm,            // Handle of the Serial port
                                   &tempChar,        // Temporary character
                                   sizeof(tempChar), // Size of TempChar
                                   &numberBytesRead, // Number of bytes read
                                   NULL);
                          if(0==ret){
              
                          }
              #ifdef DEDUG_RX_CHAR
                          LOG_DEBUG(COM,"%c", tempChar);
              #endif
                          if(0 < numberBytesRead) {
                              if(BytesReadCnt < capasityRxArray) {
                                  outRxArray[BytesReadCnt] = tempChar; // Store Tempchar into buffer
                              }
                          } else {
                              loopRun = false;
                          }
                          BytesReadCnt++;
                      };
                      if(0 < BytesReadCnt) {
                          *remain_len = BytesReadCnt;
                          res = true;
                      }
                  }
              #ifdef HAS_COM_PORT_DEBUG
                  LOG_DEBUG(COM,"%s(): end", __FUNCTION__);
              #endif
                  return res;
              }
              
              
              bool init_serial(char* com_name, uint32_t baud_rate) {
                  bool res = false;
              
                  LOG_WARNING(COM,"try open [%s]... Rate: %u bit/s", com_name, baud_rate);
              
                  hComm = CreateFile(com_name,
                                     GENERIC_READ | GENERIC_WRITE, // Read/Write
                                     0,                            // No Sharing
                                     NULL,                         // No Security
                                     OPEN_EXISTING,                // Open existing port only
                                     0,                            // Non Overlapped I/O
                                     NULL);                        // Null for Comm Devices
                  LOG_INFO(COM,"hComm [%p]", hComm);
                  if(INVALID_HANDLE_VALUE == hComm) {
                      LOG_ERROR(COM, "Unable to open serial port [%s] ", com_name);
                      res = false;
                  } else {
                      LOG_INFO(COM,"[i] Open [%s] OK ", com_name);
              
                      com_set_params(hComm, baud_rate);
                      com_set_timeout(hComm);
                      res = true;
                  }
              
                  return res;
              }
              
              bool scan_serial(void) {
                  bool res = false;
                  clear_tui();
                  LOG_INFO(COM,"Start new scan");
                  bool out_res = false;
                  char com_name[20] = "";
                  uint8_t comPortNum;
                  for(comPortNum = 0; comPortNum <= MAX_COM_NUM; comPortNum++) {
                      snprintf(com_name, sizeof(com_name), "COM%u", comPortNum);
              #if DEBUG_FAILED_OPENS
                      LOG_DEBUG(COM,"  try [%s]...", com_name);
              #endif
                      HANDLE hComm;
              #if 1
                      hComm = CreateFile(com_name,
                                         GENERIC_READ | GENERIC_WRITE, // Read/Write
                                         0,                            // No Sharing
                                         NULL,                         // No Security
                                         OPEN_EXISTING,                // Open existing port only
                                         0,                            // Non Overlapped I/O
                                         NULL);                        // Null for Comm Devices
              #if DEBUG_FAILED_OPENS
                      LOG_DEBUG(COM,"  hComm [%p]", hComm);
              #endif
                      if(hComm == INVALID_HANDLE_VALUE) {
              #if DEBUG_FAILED_OPENS
                          LOG_DEBUG(COM," Unable open serial port [%s]", com_name);
              #endif
                      } else {
              #if DEBUG_SPOT_COM
                          LOG_DEBUG(COM,"  [%s] exists in PC", com_name);
              #endif
                          deviceList[comPortNum].isExistPort = true;
                          CloseHandle(hComm);
                          out_res = true;
                      }
              #endif
              
                      snprintf(com_name, sizeof(com_name), "\\\\.\\COM%u", comPortNum);
                      hComm = CreateFile(com_name, GENERIC_READ | GENERIC_WRITE, 0, // No Sharing
                                         NULL,                                      // No Security
                                         OPEN_EXISTING,
                                         0,     // Non Overlapped I/O
                                         NULL); // Null for Comm Devices
              #if DEBUG_FAILED_OPENS
                      LOG_DEBUG(COM,"  hComm [%p]", hComm);
              #endif
                      if(hComm == INVALID_HANDLE_VALUE) {
              #if DEBUG_FAILED_OPENS
                          LOG_DEBUG(COM,"   Error in opening serial port [%s]", com_name);
              #endif
                      } else {
              #if DEBUG_SPOT_COM
                          LOG_DEBUG(COM,"  [%s] exists in PC", com_name);
              #endif
                          deviceList[comPortNum].isExistPort = true;
                          com_set_params(hComm, 9600);
                          com_set_timeout(hComm);
              
              
                          CloseHandle(hComm);
                      }
                  }
              
                  print_device_list();
                  LOG_DEBUG(COM," Scan done ");
              
                  return out_res;
              }
              
              
              bool serial_init(void) {
                  LOG_WARNING(SERIAL," Init");
                  bool res = false;
                  res = init_serial("COM3", 115200);
                  return res;
              }
              


              1. COKPOWEHEU
                05.07.2023 18:31
                -1

                {куча кода}

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


                Под "читаемым именем" я имел в виду, что у моего устройства в дескрипторе прописано iFunction = "DBG" для одного CDC и iFunction = "STFLASH" для другого, соответственно система их читает и создает ссылки /dev/tty_DBG_0, /dev/tty_STFLASH_0, к которым обращаются что stm32flash (стандартная утилита прошивки), что stty (еще более стандартная утилита изменения настроек COM-порта). В результате мне не пришлось писать ни строчки кода под ПК.


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


                А теперь мне придется ваш код читать, да еще и по ссылке сходить… Ну зачем я про это спросил...


            1. COKPOWEHEU
              05.07.2023 18:31

              как там из консоли менять параметры порта (скорость, четность)

              О, нашел! Извиняюсь, что засоряю чужую тему, но иначе может потеряться: MODE.COM COM6 BAUD=50 > NUL


  1. j_aleks
    05.07.2023 18:31
    +2

    хорошая модель, лучше чем блю-пил..


  1. NR_electronics
    05.07.2023 18:31
    +1

    Есть видеобзор по ней:
    https://www.youtube.com/watch?v=SYsNlJq_4-4

    и по отладке получше:
    https://www.youtube.com/watch?v=KJ_dOsxyo1Y


  1. Vad3333
    05.07.2023 18:31
    +2

    Выводы порадовали. ))) Прототип - это и так временный вариант. Временный вариант временного варианта - это 5 ! Прототип, как правило, и выглядит всегда кустарно. Я так понимаю, что это Ваша первая плата STM и первый прототип, ну или второй. )))


  1. sav13
    05.07.2023 18:31
    +1

    А здесь оригинальный STM32 или клон/подделка как на дешевых BP v1 с алишки?
    Купить оригинальный STM32 довольно проблематично стало