Комментарии 19
С таким подходом, как у вас, конечно будет паук из проводов и надежно выглядеть не будет.
Но если вспомнить, что микроконтроллеры с периферией работают, через 2 наносекунды после начала работы, у вас из nucleo получится такой же паук, из которого провода в датчики, экраны и кнопки уйдут.
Ну и если stlink соединять шлейфом, как и задумывалось, а не по проводку, тоже будет выглядеть всё приличнее.
А если взять daplink или ещё какой-нибудь дебаггер, который умеет в cmsis-dap, то из компа будет выходить один провод и из отладчика несколько проводов в плату, и всё
А если не жульничать, то и питание тоже с платы отладчика можно подавать, т.к. в nucleo все через один провод тоже идёт
В общем, не понятно почему у вас эти проблемы. Это не обзор black pill, а какая-то жалоба на то, что вы с проводами не можете справиться
Если уж используем копеечную таблетку, то к ней хорошо идет китайский же копеечный ST-LINK, где выведено 5В и 3.3В, причем некоторые можно перешить в версию 2.1, где уже есть CDC UART, или взять чуть более дорогой китайский JLINK-OB, где UART уже есть. При этом от таблетки будет идти всего один шлейфик с питанием, отладкой и консолью.
А ещё таблетку и саму можно превратить в 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 оставить в качестве средства восстановления после неудачной прошивки, смирившись с необходимостью разбирать устройство для этой цели.
Это неудобно, нажимать BOOT, RESET, ждать переинициализации USB. И все равно желателен UART для консоли. Гораздо удобнее, когда просто даешь команду Prog и через несколько секунд процессор уже сам запускается с новой прошивкой.
Это неудобно, нажимать BOOT, RESET, ждать переинициализации USB. И все равно желателен UART для консоли.
Как раз сейчас пишу статью по превращению stm32f103 в такой вот UART программатор-отладчик.
Кстати! Для полноты картины не могли бы вы подробно описать процесс прошивки по UART из windows, как там из консоли менять параметры порта (скорость, четность) и можно ли порту дать осмысленное имя?
не могли бы вы подробно описать процесс прошивки по UART из windows, как там из консоли менять параметры порта (скорость, четность) и можно ли порту дать осмысленное имя?
https://www.xanthium.in/Serial-Port-Programming-using-Win32-API
не могли бы вы подробно описать процесс прошивки по 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;
}
{куча кода}
Ужас какой! Причем с ходу я в нем не увидел где задается осмысленное имя. Просто-то работать с COM-портом я умею, и вроде бы даже код не такой монструозный получился. Вероятно, просто потому что какие-то особенности игнорирую.
Под "читаемым именем" я имел в виду, что у моего устройства в дескрипторе прописано iFunction = "DBG"
для одного CDC и iFunction = "STFLASH"
для другого, соответственно система их читает и создает ссылки /dev/tty_DBG_0
, /dev/tty_STFLASH_0
, к которым обращаются что stm32flash (стандартная утилита прошивки), что stty (еще более стандартная утилита изменения настроек COM-порта). В результате мне не пришлось писать ни строчки кода под ПК.
Соответственно и доверия к такому подходу больше. Я ведь не призываю запускать непонятные бинарники, максимум вписать в конфиги пару строк, которые элементарно проверяются и совершенно точно не содержат вирусни.
А теперь мне придется ваш код читать, да еще и по ссылке сходить… Ну зачем я про это спросил...
как там из консоли менять параметры порта (скорость, четность)
О, нашел! Извиняюсь, что засоряю чужую тему, но иначе может потеряться: MODE.COM COM6 BAUD=50 > NUL
хорошая модель, лучше чем блю-пил..
Есть видеобзор по ней:
https://www.youtube.com/watch?v=SYsNlJq_4-4
и по отладке получше:
https://www.youtube.com/watch?v=KJ_dOsxyo1Y
и по отладке получше:https://www.youtube.com/watch?v=KJ_dOsxyo1Y
Тогда уж лучше с RTC. Вот эта
https://aliexpress.ru/item/1005001579853052.html?spm=a2g2w.orderdetail.0.0.70474aa6NWuSFS&sku_id=12000016669658581

Выводы порадовали. ))) Прототип - это и так временный вариант. Временный вариант временного варианта - это 5 ! Прототип, как правило, и выглядит всегда кустарно. Я так понимаю, что это Ваша первая плата STM и первый прототип, ну или второй. )))
А здесь оригинальный STM32 или клон/подделка как на дешевых BP v1 с алишки?
Купить оригинальный STM32 довольно проблематично стало
Поясните, пожалуйста, откуда взялось обозначение V2.0?
И чем плата с этим обозначением отличается от других вариантов?
https://github.com/WeActStudio/WeActStudio.MiniSTM32F4x1 - тут, например, говорится о версии V3.0
Нашел - https://github.com/WeActStudio/WeActStudio.MiniSTM32F4x1/blob/master/HDK/README.md
Текущая версия V3.1
Если что-то сложнее общения с ПК по UART прототипировать собираетесь таким способом как у вас, то и с Nucleo ваше устройство превратится в клубок перемычек. BlackPill более компактная, легко встраивается в макет, и ее не так жалко, если что-то пойдет не так:

Купить в РФ с быстрой доставкой и на оригинальном чипе ее можно тут:
https://sl.aliexpress.ru/p?key=xEVqrIH
Обзор платы Black Pill V2.0