<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title></title>
	<atom:link href="http://easyelectronics.ru/feed" rel="self" type="application/rss+xml" />
	<link>http://easyelectronics.ru</link>
	<description></description>
	<lastBuildDate>Sat, 09 Sep 2023 17:01:21 +0000</lastBuildDate>
	<language>ru-RU</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
		<item>
		<title>Как понять сколько дать памяти задаче во FreeRTOS. Распределение памяти. Отладочные функции</title>
		<link>http://easyelectronics.ru/kak-ponyat-skolko-dat-pamyati-zadache-vo-freertos-raspredelenie-pamyati-otladochnye-funkcii.html</link>
		<comments>http://easyelectronics.ru/kak-ponyat-skolko-dat-pamyati-zadache-vo-freertos-raspredelenie-pamyati-otladochnye-funkcii.html#comments</comments>
		<pubDate>Sat, 09 Sep 2023 17:01:21 +0000</pubDate>
		<dc:creator><![CDATA[DI HALT]]></dc:creator>
				<category><![CDATA[ARM. Учебный курс]]></category>
		<category><![CDATA[FreeRTOS]]></category>
		<category><![CDATA[Операционная система]]></category>
		<category><![CDATA[Оптимизация]]></category>
		<category><![CDATA[Отладка]]></category>

		<guid isPermaLink="false">http://easyelectronics.ru/?p=2032</guid>
		<description><![CDATA[Вообще тема довольно обширная и тут есть множество разных методик, принципов. Да еще FreeRTOS поддерживает кучу разных методов организации памяти. Я предпочитаю использовать кооперативный режим без вытеснения (задачи передают управление сами) и модель кучи heap_1.c, в которой все создается один раз и не удаляется. Поэтому нет фрагментации. Влезает потенциально меньше, зато меньше нежданчиков, когда формально память есть, а выделить ее не получается. И случается это после дождичка в четверг на полную луну. В этом случае приходится тщательней оценивать аппетиты задачи, чтобы выделить ей ресурсов столько сколько нужно. Что особенно важно, когда задач много. В двух словах у нас память делится на две группы. Первая это просто оперативка, статичная. Там где у нас тусуются глобальные и статические переменные и куча RTOS, где обитают локальные переменные задач, всякие там временные буфера и прочее ситуативное непотребство. Куча делится между всеми задачами. Размер кучи задается в конфиге в параметре: #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 18 * 1024 ) ) Определить сколько надо статической памяти довольно просто. После компиляции занятое количество будет написано под секцией .bss примерно так: bin\Release\BusTracker.map 1 Program size (bytes): 54180 Data size (bytes): 1476 BSS size (bytes): 18536 Total size (bytes): 74192 (R/W Memory: 20012)&#124; === Finished: 0 errors, 0 warnings (0 minutes, 4 seconds) === Размер кучи я обычно сначала выделяю по максимуму, под всю оперативку. Ну килобайтик там оставлю под стек для main, чтобы можно было там экспериментировать. Если нам не хватит места под глобальные переменные об этом сразу же ругнется линкер: d:/program files/embitz/2.60/share/armgcc/bin/../lib/gcc/arm-none-eabi/9.3.1/../../../../arm-none-eabi/bin/ld.exe: region `RAM&#8217; overflowed by 32 bytes И скажет сколько ему не хватает. В данном случае у нас не влезло 32 байта. Тогда мы урезаем осетра. Тут все просто, никаких внезапных нежданчиков не будет. Так по мере написания кода я выделяю статику под разный функционал. Выделяя максиму куче. Интересней обстоит ситуация с задачей. Сколько ей выделить стека в этой куче? xTaskCreate(vBlinker2,"B", configMINIMAL_STACK_SIZE+512, NULL, tskIDLE_PRIORITY + 10, NULL); Обычно на глазок прикидываю сколько точно должно хватить и просто приплюсовываю эту добавку к минималке. &#171;configMINIMAL_STACK_SIZE+512&#187;. А дальше надо погонять задачу, понагружать и спросить у самой RTOS что у нас там с памятью происходит? У FreeRTOS есть отличные способы узнать состояние кучи и то сколько стека жрет сама задача. И, главное, это можно узнать в реалтайме! Делается это посредством API функции vTaskGetInfo, также нам потребуется функция xTaskGetHandle &#8212; которая по текстовой метке задачи находит ее заголовок. Чтобы эти функции работали в FreeRTOSconfig.h надо добавить следующие дефайны: #define configUSE_TRACE_FACILITY 1 и подключить функции работы с диагностикой туда же: #define INCLUDE_uxTaskGetStackHighWaterMark 1 #define INCLUDE_xTaskGetHandle 1 Поскольку у меня почти всегда прикручена отладочная консоль, то я там завел команду анализатора. У которой можно спросить, кто сколько кушал сегодня: // Создаем консольную команду "s" она будет выводить параметры задачи. CONSOLE_COMMAND_DEF(s, "Gets a task info", CONSOLE_STR_ARG_DEF(task, "RTOS Task Name") ); static void s_command_handler(const s_args_t* args) { // Показать размер свободной кучи. // Частый вызов этой функции позволит показать есть ли утечка памяти. // У меня, поскольку все вызывается один раз, то куча принимает один и тот же размер // И не меняется динамически. А вот если уменьшается, то спустя какое то время получим переполнение // и все повиснет. xprintf("Heap Size = %d\n",xPortGetFreeHeapSize()); // Показать аптайм задачи в секундах. FreeRTOS считает тики, а у меня они на 1мс записаны. xprintf("Uptime = %ds\n",xTaskGetTickCount()/1000; TaskStatus_t xTaskStatus; // Создаем струкуру под статус. // Читаем получаем заголовок нужной задачи. В качестве параметра идет текстовая метка задачи. Помните как создается задача? // xTaskCreate(vBlinker2,"B", configMINIMAL_STACK_SIZE+512, NULL, tskIDLE_PRIORITY + 10, NULL); // Вот второй параметр "B" это та самая метка и есть. По этой метке функция xTaskGetHandle найдет заголовок нужной задачи. TaskHandle_t xTask = xTaskGetHandle(args->task); // Читаем данные задачи по ее заголовку vTaskGetInfo(xTask, &#038;xTaskStatus, pdTRUE, eInvalid); // Выводим информацию о задаче // Имя задачи. Очень важно выводить Т.к. чуть ошибешься в написании и оно выведет не то. // А тут можно проконтроллировать туда ли мы смотрим. xprintf("Task name: %s\n", xTaskStatus.pcTaskName); // Показать текущее состояние задачи. // 0 - запущена, 1 - ожидает запуска, готова, 2 - заблокирована (ждет чего то по таймеру), // 3 - усыплена (заблокирована с бесконечным временем ожидания условия), // 4 - удалена, 5 - ошибка. xprintf("Task status: %d\n", xTaskStatus.eCurrentState); // Показать текущий приоритет задачи. xprintf("Task priority: %d\n", xTaskStatus.uxCurrentPriority); // Показать наибольшее заполнение стека за всю историю выполнения. Т.е. верхняя отметка. // Сколько осталось свободного места в худшем случае. xprintf("Task stack high water mark (freespace): %d\n", xTaskStatus.usStackHighWaterMark); } Вот так примерно выглядит использование в консоли. Команда &#171;s&#187; с параметром имени задачи &#171;Coord&#187; выдаст такую вот картину s Coord Heap Size = 6488 Uptime = 24s Task name: Coord Task status: 2 Task priority: 1 Task stack high water mark (freespace): 122 >> Вот тут видно, что у нас в стеке лишних 122 байта остается. Если погонять задачу подольше, чтобы она повертелась по всем сценариям которые возможные для нее, а потом посмотреть watermark, то можно срезать несколько сот или десятков байт. Так по всем задачам пройтись, так несколько килобайт можно отгрызть. Еще Один из примеров использования. Делал я тут недавно один заказ &#8212; автоинформатор для транспорта. По координатам выдает названия остановок голосом. И обнаружил, что спустя какое то время прога виснет. Выпадает в переполнение стека. Отследить это просто. Достаточно включить в FreeRTOSconfig.h #define configCHECK_FOR_STACK_OVERFLOW 2 Это проверка на переполнение стека. У ней есть три варианта 0 &#8212; выключено, 1 &#8212; способ и 2 &#8212; второй способ. Способы делают одно и то же, но несколько разными методами. Первый метод ловит ситуацию по факту. Т.е. диспетчер переключаясь на задачу проверяет не торчит ли указатель стека за пределами доступной памяти? Если торчит, ловите хук справа. Сей метод быстр, но не всегда работает. Т.к. переполнение стека может быть не в тот момент когда диспетчер это проверяет. Как в том анекдоте &#171;Пока вы трахались я уже три раза за круг выходил УХАХАХАХА&#187;. Для решения этой проблемы есть второй метод. Стек когда инициализируется, то заполняется байтом 0хА5. А стек, естественно эти метки затирает какими то своими данными. Диспетчер проверяет наличие маркера А5 вблизи границы стека. И если там кто то топтался, то поднимает тревогу. Для тревоги где нибудь в main.c, внизу, надо определить хук функцию: void vApplicationStackOverflowHook( xTaskHandle pxTask, signed char *pcTaskName ) { ( void ) pcTaskName; ( void ) pxTask; for( ;; ); } Сюда прилетит программа в случае переполнения стека. А тут у нас уже будет хэндл задачи которая проштрафилась. И ее текстовая метка. И тут можно, например, тупым выводом в UART на регистрах, с ожиданием флагов, без прерываний чтоб, вывести поганца в лог какой-нибудь. Ну так вот, прилетаю я в этот хук, вижу задачу которая пакостит. Но ничего понять не могу, на чем она пакостит. Вроде все работает ровно. Давай периодически вызывать в консоль свой анализатор и заметил, что у меня постепенно, раз в несколько минут, немного уменьшается размер кучи&#8230; Ага! Вот тут то собака порылась. Когда куча кончается тут то все падает. Но ничего криминального не заметил. Отследил период усыхания кучи&#8230; сопоставил с логикой и обнаружил, что это происходит в момент чтения файла с флеша&#8230; Стал ковыряться дальше и нашел тупизну &#8212; я очередь определял в цикле и каждую итерацию чтения у меня создавалась новая очередь. А старая естественно оставалась я же ее не удалял. Вынес инициализацию очереди из цикла и все падать перестало.]]></description>
				<content:encoded><![CDATA[<p> Вообще тема довольно обширная и тут есть множество разных методик, принципов. Да еще FreeRTOS поддерживает кучу разных методов организации памяти. Я предпочитаю использовать кооперативный режим без вытеснения (задачи передают управление сами) и модель кучи heap_1.c, в которой все создается один раз и не удаляется. Поэтому нет фрагментации. Влезает потенциально меньше, зато меньше нежданчиков, когда формально память есть, а выделить ее не получается. И случается это после дождичка в четверг на полную луну. </p>
<p>В этом случае приходится тщательней оценивать аппетиты задачи, чтобы выделить ей ресурсов столько сколько нужно. Что особенно важно, когда задач много. </p>
<p>В двух словах у нас память делится на две группы. Первая это просто оперативка, статичная. Там где у нас тусуются глобальные и статические переменные и куча RTOS, где обитают локальные переменные задач, всякие там временные буфера и прочее ситуативное непотребство. Куча делится между всеми задачами.</p>
<p>Размер кучи задается в конфиге в параметре: </p>
<pre lang="C" line="1">
#define configTOTAL_HEAP_SIZE		( ( size_t ) ( 18 * 1024 ) )
</pre>
<p>Определить сколько надо статической памяти довольно просто. После компиляции занятое количество  будет написано под секцией .bss примерно так:</p>
<pre>
bin\Release\BusTracker.map 1 Program size (bytes):   	54180
						Data size    (bytes):    1476
						BSS size     (bytes):   18536

						Total size   (bytes):   74192   (R/W Memory: 20012)|
=== Finished: 0 errors, 0 warnings (0 minutes, 4 seconds) ===
</pre>
<p><span id="more-2032"></span></p>
<p> Размер кучи я обычно сначала выделяю по максимуму, под всю оперативку. Ну килобайтик там оставлю под стек для main, чтобы можно было там экспериментировать. Если нам не хватит места под глобальные переменные об этом сразу же ругнется линкер:</p>
<p><em>d:/program files/embitz/2.60/share/armgcc/bin/../lib/gcc/arm-none-eabi/9.3.1/../../../../arm-none-eabi/bin/ld.exe: region `RAM&#8217; overflowed by 32 bytes</em></p>
<p>И скажет сколько ему не хватает. В данном случае у нас не влезло 32 байта.  Тогда мы урезаем осетра.  Тут все просто, никаких внезапных нежданчиков не будет. Так по мере написания кода я выделяю статику под разный функционал. Выделяя максиму куче. </p>
<p>Интересней обстоит ситуация с задачей. Сколько ей выделить стека в этой куче? </p>
<pre lang="C" line="1">
xTaskCreate(vBlinker2,"B", 	configMINIMAL_STACK_SIZE+512, NULL, tskIDLE_PRIORITY + 10, NULL);
</pre>
<p>Обычно на глазок прикидываю сколько точно должно хватить и просто приплюсовываю эту добавку к минималке. &#171;configMINIMAL_STACK_SIZE+512&#187;.</p>
<p>А дальше надо погонять задачу, понагружать и спросить у самой RTOS что у нас там с памятью происходит? У FreeRTOS есть отличные способы узнать состояние кучи и то сколько стека жрет сама задача. И, главное, это можно узнать в реалтайме! </p>
<p>Делается это посредством API функции  vTaskGetInfo, также нам потребуется функция xTaskGetHandle &#8212; которая по текстовой метке задачи находит ее заголовок. Чтобы эти функции работали в <b>FreeRTOSconfig.h</b> надо добавить следующие дефайны:</p>
<pre lang="C" line="1">
#define configUSE_TRACE_FACILITY	1</pre>
<p>и подключить функции работы с диагностикой туда же:</p>
<pre lang="C" line="1">
#define INCLUDE_uxTaskGetStackHighWaterMark 1
#define INCLUDE_xTaskGetHandle              1
</pre>
<p>Поскольку у меня почти всегда прикручена <a href="http://easyelectronics.ru/konsolnyj-interfejs-v-mikrokontrollere.html">отладочная консоль</a>, то я там завел команду анализатора. У которой можно спросить, кто сколько кушал сегодня:</p>
<pre lang="C" line="1">

// Создаем консольную команду "s" она будет выводить параметры задачи. 
CONSOLE_COMMAND_DEF(s, "Gets a task info",
CONSOLE_STR_ARG_DEF(task, "RTOS Task Name")
);


static void s_command_handler(const s_args_t* args)
{
// Показать размер свободной кучи. 
// Частый вызов этой функции позволит показать есть ли утечка памяти. 
// У меня, поскольку все вызывается один раз, то куча принимает один и тот же размер
// И не меняется динамически. А вот если уменьшается, то спустя какое то время получим переполнение
// и все повиснет. 
    xprintf("Heap Size = %d\n",xPortGetFreeHeapSize());

// Показать аптайм задачи в секундах. FreeRTOS считает тики, а у меня они на 1мс записаны. 
    xprintf("Uptime = %ds\n",xTaskGetTickCount()/1000;

    TaskStatus_t xTaskStatus;  // Создаем струкуру под статус. 			

// Читаем получаем заголовок нужной задачи. В качестве параметра идет текстовая метка задачи. Помните как создается задача? 
// xTaskCreate(vBlinker2,"B", configMINIMAL_STACK_SIZE+512, NULL, tskIDLE_PRIORITY + 10, NULL);
// Вот второй параметр "B" это та самая метка и есть. По этой метке функция xTaskGetHandle найдет заголовок нужной задачи. 
    TaskHandle_t xTask = xTaskGetHandle(args->task); 

// Читаем данные задачи по ее заголовку
    vTaskGetInfo(xTask, &xTaskStatus, pdTRUE, eInvalid);

// Выводим информацию о задаче

// Имя задачи. Очень важно выводить Т.к. чуть ошибешься в написании и оно выведет не то. 
// А тут можно проконтроллировать туда ли мы смотрим. 
    xprintf("Task name: %s\n", xTaskStatus.pcTaskName);  

// Показать текущее состояние задачи. 
// 0 - запущена, 1 - ожидает запуска, готова, 2 - заблокирована (ждет чего то по таймеру), 
// 3 - усыплена (заблокирована с бесконечным временем ожидания условия),
// 4 - удалена, 5 - ошибка. 
    xprintf("Task status: %d\n", xTaskStatus.eCurrentState);

// Показать текущий приоритет задачи. 
    xprintf("Task priority: %d\n", xTaskStatus.uxCurrentPriority);

// Показать наибольшее заполнение стека за всю историю выполнения. Т.е. верхняя отметка.
// Сколько осталось свободного места в худшем случае. 
    xprintf("Task stack high water mark (freespace): %d\n", xTaskStatus.usStackHighWaterMark);
}
</pre>
<p>Вот так примерно выглядит использование в консоли. Команда &#171;s&#187; с параметром имени задачи &#171;Coord&#187; выдаст такую вот картину </p>
<p><b><br />
s Coord<br />
Heap Size = 6488<br />
Uptime = 24s<br />
Task name: Coord<br />
Task status: 2<br />
Task priority: 1<br />
Task stack high water mark (freespace): 122<br />
>></b></p>
<p>Вот тут видно, что у нас в стеке лишних 122 байта остается. Если погонять задачу подольше, чтобы она повертелась по всем сценариям которые возможные для нее, а потом посмотреть watermark, то можно срезать несколько сот или десятков байт. Так по всем задачам пройтись, так несколько килобайт можно отгрызть. </p>
<p>Еще Один из примеров использования. Делал я тут недавно один заказ &#8212; автоинформатор для транспорта. По координатам выдает названия остановок голосом. И обнаружил, что спустя какое то время прога виснет. Выпадает в переполнение стека. Отследить это просто. Достаточно включить в <b>FreeRTOSconfig.h</b> </p>
<pre lang="C" line="1">#define configCHECK_FOR_STACK_OVERFLOW	2</pre>
<p>Это проверка на переполнение стека. У ней есть три варианта 0 &#8212; выключено, 1 &#8212; способ и 2 &#8212; второй способ. Способы делают одно и то же, но несколько разными методами. </p>
<p>Первый метод ловит ситуацию по факту. Т.е. диспетчер переключаясь на задачу проверяет не торчит ли указатель стека за пределами доступной памяти? Если торчит, ловите хук справа.  Сей метод быстр, но не всегда работает. Т.к. переполнение стека может быть не в тот момент когда диспетчер это проверяет. Как в том анекдоте &#171;Пока вы трахались я уже три раза за круг выходил УХАХАХАХА&#187;. </p>
<p>Для решения этой проблемы есть второй метод.  Стек когда инициализируется, то заполняется байтом 0хА5. А стек, естественно эти метки затирает какими то своими данными. Диспетчер проверяет наличие маркера А5 вблизи границы стека. И если там кто то топтался, то поднимает тревогу. </p>
<p>Для тревоги где нибудь в main.c, внизу, надо определить хук функцию:</p>
<pre lang="C" line="1">
void vApplicationStackOverflowHook( xTaskHandle pxTask, signed char *pcTaskName )
{
    ( void ) pcTaskName;
    ( void ) pxTask;

    for( ;; );
}
</pre>
<p>Сюда прилетит программа в случае переполнения стека. А тут у нас уже будет хэндл задачи которая проштрафилась. И ее текстовая метка. И тут можно, например, тупым выводом  в UART на регистрах, с ожиданием флагов, без прерываний чтоб, вывести поганца в лог какой-нибудь. </p>
<p>Ну так вот, прилетаю я в этот хук, вижу задачу которая пакостит. Но ничего понять не могу, на чем она пакостит. Вроде все работает ровно. Давай периодически вызывать в консоль свой анализатор и заметил, что у меня постепенно, раз в несколько минут, немного уменьшается размер кучи&#8230; Ага! Вот тут то собака порылась. Когда куча кончается тут то все падает.  Но ничего криминального не заметил.  Отследил период усыхания кучи&#8230; сопоставил с логикой и обнаружил, что это происходит в момент чтения файла с флеша&#8230;  Стал ковыряться дальше и нашел тупизну &#8212; я очередь определял в цикле и каждую итерацию чтения у меня создавалась новая очередь. А старая естественно оставалась я же ее не удалял. Вынес инициализацию очереди из цикла и все падать перестало. </p>
]]></content:encoded>
			<wfw:commentRss>http://easyelectronics.ru/kak-ponyat-skolko-dat-pamyati-zadache-vo-freertos-raspredelenie-pamyati-otladochnye-funkcii.html/feed</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>STM32 Bootloader</title>
		<link>http://easyelectronics.ru/stm32-bootloader.html</link>
		<comments>http://easyelectronics.ru/stm32-bootloader.html#comments</comments>
		<pubDate>Sun, 02 Jul 2023 20:31:17 +0000</pubDate>
		<dc:creator><![CDATA[DI HALT]]></dc:creator>
				<category><![CDATA[ARM. Учебный курс]]></category>
		<category><![CDATA[Bootloader]]></category>

		<guid isPermaLink="false">http://easyelectronics.ru/?p=2027</guid>
		<description><![CDATA[Довольно частая задача &#8212; удаленное обновление прошивки устройства. В моем случае это десятки вендинговых автоматов по продаже сигарет, раскиданных на заправках и пивняках. Которые управляются от линуксовой головы. До устройства там проброшена консоль отладочная прямо с линя, а само устройство сидит на шине RS485. Так что задача тривиальная. Поделюсь готовым решением. Которое работает как под GD32F103 так и под STM32F1xx ▌Немного теории Как стартует контроллер? После сброса проц проверяет состояние BOOT пинов и битов. В памяти у некоторых STM32 есть доступные программатору nBOOT биты, чем то похожие на FUSE у AVR. Их можно достать, например, через STLink через меню Options Byte. А дальше, если стоит пин Boot 0, то проц решает какой именно загрузчик выбирать и куда прыгать. По классике, если BOOT0=1 BOOT1=0 происходит прыжок на заводской, зашитый, загрузчик. Который согласно битам nBOOT опеределяет каким именно способом грузиться. Либо через UART (1 или 2), а то и по CAN, i2c и черт знает через что еще. В Документе STM32 AN2606 описанна масса вариантов встроенного загрузчика для разных семейств STM32. После чего загрузчик отдает управление уже пользовательской программе. Как она устроена? Вначале, первыми четырмя байтами лежит адрес где должен быть указатель стека. Потом идет вектор сброса, за ним вектора прерываний. 000000 estack 000004 ResetVector 000008 InterruptVector И после этой таблицы уже располагается тело программы. Все бы ничего, но вот только у нас задача обновить прошивку, а для обновления надо дернуть BOOT0, аппаратно дернуть. Кто-то, кстати ,аппаратно и дергает. Вешая туда через конденсатор вывод ноги. Хочешь перезагрузиться &#8212; заряжаешь конденсатор и быстро ребутишься. Работает отлично. Но надо не забыть сделать еще разряжающий резистор туда. Чтобы конденсатор всегда был надежно разряжен. Но это если заранее продумал такой способ. А если, как я, вначале делаем, а потом думаем&#8230; то :))))) Поначалу я попытался еще подергаться. Вот такая картинка есть в документации от STM. Это карта адресов. Тут видно, что на адресе 0х1FFF77FF располагается системная память ,где и живет тот самый системный загрузчик. И, в принципе, нам ничего не мешает в нашем устройстве сделать интерфейс совпадающий с интерфейсом пригодным для системного загрузчика и сделать прыжок на адрес этого загрузчика BOOTADDRESS. // Тип - указатель на функцию typedef void (*func_ptr)(void); // Определяем указатель func_ptr jump_to_app; // Присваиваем указателю значение. // +4 нужно чтобы перешагнуть адрес стека и сразу встать на вектор сброса jump_to_app = (fnc_ptr)(*(volatile uint32_t*)(BOOTADDRESS+4u)); // Сбросить все биты ожидания прерывания. // Выключить таймеры // Выключить тактирование периферии. __disable_irq(); SysTick->CTRL = 0; HAL_RCC_DeInit(); // Количество регистров сброса и ожидания прерывания зависит от ядра. У М3 их 3 for (i=0;iICER[i]=0xFFFFFFFF; NVIC->ICPR[i]=0xFFFFFFFF; } __enable_irq(); __set_MSP(*(volatile uint32_t*)BOOTADDRESS); jump_to_app(); Но на практике не все так просто. Дело в том, что BOOTADDRESS часто нифига не совпадает с началом системной памяти. А просто &#171;расположена в этой области&#187;. В документации можно найти порой истинные значения адресов расположения бутлоадера. В той же STM32 AN2606 А может и не найти. Как повезет. Еще там есть зависимость от версии бутлодера, а она тоже меняется от года к году. В общем, у меня оно то работало, то не работало. И я забил на встроенный бутлоадер. Считаем его аппаратным. :) Паять и резать готовые устройства не хотелось. Ладно, будем писать свой загрузчик. ▌Программный загрузчик Тут еще проще, процессор стартует как положено. Со своего привычного адреса, но стартует это загрузчик. А дальше ждет условия, таймаута или еще чего то, после чего передает управление уже программе пользователя. Для этого программу пользователя надо несколько модифицировать. А именно сделать две вещи: Указать в скрипте линковщике где у нас будет начинаться пользовательский флеш. Указать в хидере проца, где у насколько у нас смещена таблица векторов. ▌Загрузчик XMODEM Поскольку у меня у устройства есть человеческая консолька отладочная. То я бы в нее и высунул загрузчик. Да не просто высунул, а сделал так, чтобы можно было прошиться через любую терминальную утилитку, Типа TerraTerm да хоть виндовый Hyperterminal закинув программу по протоколу XMODEM. Олдовая вещь, по которой мы с далеком 2000 году с друганом фотки сисястые и Win386.swp пересылали друг другу по модемам. В доинтернетные времена :) Поскольку задача ну очень уже банальная, то готовое решение нашлось мгновенно. Немножко только допилил его под свои реалии. Исходный проект, я брать не стал, ибо не переношу кубовую отрыжку. Просто написал ручками, на том же HAL. #include "main.h" #include "xmodem.h" #include "flash.h" #include "string.h" UART_HandleTypeDef MyUART; int main(void) { // Обычная инициализация тактирования и периферии. Из которой тут только UART HAL_Init(); RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&#038;RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK&#124;RCC_CLOCKTYPE_SYSCLK &#124;RCC_CLOCKTYPE_PCLK1&#124;RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&#038;RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } HAL_RCC_EnableCSS(); // UART иницилизируем втупую, без прерываний. Только минимальная работа. MyUART.Instance = USART3; MyUART.Init.BaudRate = 9600; MyUART.Init.WordLength = UART_WORDLENGTH_8B; MyUART.Init.StopBits = UART_STOPBITS_1; MyUART.Init.Parity = UART_PARITY_NONE; MyUART.Init.Mode = UART_MODE_TX_RX; MyUART.Init.HwFlowCtl = UART_HWCONTROL_NONE; MyUART.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&#038;MyUART) != HAL_OK) { Error_Handler(); } // заводим буфер под строку. char str[6]=""; while (1) { // Очищаем буфер UART чтобы там не было предыдущих сообщений, байтов и прочего // А также очищаем строковый буфер. void uart_clrbuff(); memset(str,0,6); // Шлем приглашение ко вводу. uart_transmit_str((uint8_t*)"BOOTLOADER Enter cmd>>\n\r"); // В блокирующем режиме ждетм ответа. Принимается функцией HAL с таймаутом в 3 секунды if(uart_receive((uint8_t*)str,4) == UART_ERROR) continue; // Парсим команды. if(strcmp(str,"RUN!") == 0) flash_jump_to_app(); if(strcmp(str,"BOOT") != 0) continue; uart_transmit_str((uint8_t*)"Send a new binary file with Xmodem protocol to update the firmware.\n\r"); // Собственно эта функция и запускает процесс приема и прожига. xmodem_receive(); // Если ошибка - начинаем все с начала. uart_transmit_str((uint8_t*)"\n\rFailed... Try again.\n\r"); } } void Error_Handler(void) { while(1) { } } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ Небольшая прокладка между флешером и HAL библиотекой осталась от исходного проекта: /** * @file uart.c * @author Ferenc Nemeth * @date 21 Dec 2018 * @brief This module is a layer between the HAL UART functions and my Xmodem protocol. * * Copyright (c) 2018 Ferenc Nemeth - https://github.com/ferenc-nemeth */ #include "uart.h" /** * @brief Receives data from UART. * @param *data: Array to save the received data. * @param length: Size of the data. * @return status: Report about the success of the receiving. */ uart_status uart_receive(uint8_t *data, uint16_t length) { uart_status status = UART_ERROR; if (HAL_OK == HAL_UART_Receive(&#038;MyUART, data, length, UART_TIMEOUT)) { status = UART_OK; } return status; } /** * @brief Transmits a string to UART. * @param *data: Array of the data. * @return status: Report about the success of the transmission. */ uart_status uart_transmit_str(uint8_t *data) { uart_status status = UART_ERROR; uint16_t length = 0u; /* Calculate the length. */ while ('\0' != data[length]) { length++; } if (HAL_OK == HAL_UART_Transmit(&#038;MyUART, data, length, UART_TIMEOUT)) { status = UART_OK; } return status; } /** * @brief Transmits a single char to UART. * @param *data: The char. * @return status: Report about the success of the transmission. */ uart_status uart_transmit_ch(uint8_t data) { uart_status status = UART_ERROR; /* Make available the UART module. */ if (HAL_UART_STATE_TIMEOUT == HAL_UART_GetState(&#038;MyUART)) { HAL_UART_Abort(&#038;MyUART); } if (HAL_OK == HAL_UART_Transmit(&#038;MyUART, &#038;data, 1u, UART_TIMEOUT)) { status = UART_OK; } return status; } void uart_clrbuff(void) { HAL_UART_AbortReceive(&#038;MyUART); } UART_TIMEOUT прописан в 3 секунды. Для работы с флешем используется все тот же HAL инструментарий. Оставил этот код практически без изменений. Разве что добавил обнуление флагов прерываний и ожидания перед выходом в приложение пользователя. /** * @file flash.c * @author Ferenc Nemeth * @date 21 Dec 2018 * @brief This module handles the memory related functions. * * Copyright (c) 2018 Ferenc Nemeth - https://github.com/ferenc-nemeth */ #include "flash.h" /* Function pointer for jumping to user application. */ typedef void (*fnc_ptr)(void); /** * @brief This function erases the memory. * @param address: First address to be erased (the last is the end of the flash). * @return status: Report about the success of the erasing. */ flash_status flash_erase(uint32_t address) { HAL_FLASH_Unlock(); flash_status status = FLASH_ERROR; FLASH_EraseInitTypeDef erase_init; uint32_t error = 0u; erase_init.TypeErase = FLASH_TYPEERASE_PAGES; erase_init.PageAddress = address; erase_init.Banks = FLASH_BANK_1; /* Calculate the number of pages from "address" and the end of flash. */ erase_init.NbPages = (FLASH_BANK1_END - address) / FLASH_PAGE_SIZE; /* Do the actual erasing. */ if (HAL_OK == HAL_FLASHEx_Erase(&#038;erase_init, &#038;error)) { status = FLASH_OK; } HAL_FLASH_Lock(); return status; } /** * @brief This function flashes the memory. * @param address: First address to be written to. * @param *data: Array of the data that we want to write. * @param *length: Size of the array. * @return status: Report about the success of the writing. */ flash_status flash_write(uint32_t address, uint32_t *data, uint32_t length) { flash_status status = FLASH_OK; HAL_FLASH_Unlock(); /* Loop through the array. */ for (uint32_t i = 0u; (i < length) &#038;&#038; (FLASH_OK == status); i++) { /* If we reached the end of the memory, then report an error and don't do anything else.*/ if (FLASH_APP_END_ADDRESS APB1RSTR = 0xFFFFFFFFU; RCC->APB1RSTR = 0x00; RCC->APB2RSTR = 0xFFFFFFFFU; RCC->APB2RSTR = 0x00; //SysTick DeInit SysTick->CTRL=0; SysTick->VAL=0; SysTick->LOAD=0; __disable_irq(); //NVIC DeInit __set_BASEPRI(0); __set_CONTROL(0); NVIC->ICER[0]=0xFFFFFFFF; NVIC->ICPR[0]=0xFFFFFFFF; NVIC->ICER[1]=0xFFFFFFFF; NVIC->ICPR[1]=0xFFFFFFFF; NVIC->ICER[2]=0xFFFFFFFF; NVIC->ICPR[2]=0xFFFFFFFF; __enable_irq(); /* Change the main and local stack pointer. */ __set_MSP(*(volatile uint32_t*)FLASH_APP_START_ADDRESS); SCB->VTOR=*(volatile uint32_t*)FLASH_APP_START_ADDRESS; jump_to_app(); } Тут обратите внимание на то как происходит переход к приложению. Мы вначале делаем HAL_DeInit() чтобы выключить все что включили. Правда родной HAL_DeInit() не подходит, т.к. во первых он не восстанавливает состояние SysTick, хотя HAL_Init() его меняет. А во-вторых, если включить оптимизацию, то все рушится. Т.к. оптимизатор каким то хитрым образом запаковывает все в стек, а тут я меняю указатель стека и при выходе из HAL_DeInit() все крашится. Поэтому я эту функцию развернул и дополнил. Потом я, на всякий случай, зачищаю таблицы прерываний и битов NVIC чтобы никакое прерывание внезапно не выскочило. Перенаправляю таблицу векторов на новый адрес __set_MSP и прыгаю на новый вектор сброса FLASH_APP_START_ADDRESS . Который будет после нашего бутлоадера. Чему он равен? А это мы потом вычислим, когда сделаем бутлоадер до конца. Ну и процедура работы с xmodem тоже оставил авторскую, без изменений вообще /** * @file xmodem.c * @author Ferenc Nemeth * @date 21 Dec 2018 * @brief This module is the implementation of the Xmodem protocol. * * Copyright (c) 2018 Ferenc Nemeth - https://github.com/ferenc-nemeth */ #include "xmodem.h" /* Global variables. */ static uint8_t xmodem_packet_number = 1u; /**< Packet number counter. */ static uint32_t xmodem_actual_flash_address = 0u; /**< Address where we have to write. */ static uint8_t x_first_packet_received = false; /**< First packet or not. */ /* Local functions. */ static uint16_t xmodem_calc_crc(uint8_t *data, uint16_t length); static xmodem_status xmodem_handle_packet(uint8_t size); static xmodem_status xmodem_error_handler(uint8_t *error_number, uint8_t max_error_number); /** * @brief This function is the base of the Xmodem protocol. * When we receive a header from UART, it decides what action it shall take. * @param void * @return void */ void xmodem_receive(void) { volatile xmodem_status status = X_OK; uint8_t error_number = 0u; x_first_packet_received = false; xmodem_packet_number = 1u; xmodem_actual_flash_address = FLASH_APP_START_ADDRESS; /* Loop until there isn't any error (or until we jump to the user application). */ while (X_OK == status) { uint8_t header = 0x00u; /* Get the header from UART. */ uart_status comm_status = uart_receive(&#038;header, 1u); /* Spam the host (until we receive something) with ACSII "C", to notify it, we want to use CRC-16. */ if ((UART_OK != comm_status) &#038;&#038; (false == x_first_packet_received)) { (void)uart_transmit_ch(X_C); } /* Uart timeout or any other errors. */ else if ((UART_OK != comm_status) &#038;&#038; (true == x_first_packet_received)) { status = xmodem_error_handler(&#038;error_number, X_MAX_ERRORS); } else { /* Do nothing. */ } xmodem_status packet_status = X_ERROR; /* The header can be: SOH, STX, EOT and CAN. */ switch(header) { /* 128 or 1024 bytes of data. */ case X_SOH: case X_STX: /* If the handling was successful, then send an ACK. */ packet_status = xmodem_handle_packet(header); if (X_OK == packet_status) { (void)uart_transmit_ch(X_ACK); } /* If the error was flash related, then immediately set the error counter to max (graceful abort). */ else if (X_ERROR_FLASH == packet_status) { error_number = X_MAX_ERRORS; status = xmodem_error_handler(&#038;error_number, X_MAX_ERRORS); } /* Error while processing the packet, either send a NAK or do graceful abort. */ else { status = xmodem_error_handler(&#038;error_number, X_MAX_ERRORS); } break; /* End of Transmission. */ case X_EOT: /* ACK, feedback to user (as a text), then jump to user application. */ (void)uart_transmit_ch(X_ACK); (void)uart_transmit_str((uint8_t*)"\n\rFirmware updated!\n\r"); (void)uart_transmit_str((uint8_t*)"Jumping to user application...\n\r"); flash_jump_to_app(); break; /* Abort from host. */ case X_CAN: status = X_ERROR; break; default: /* Wrong header. */ if (UART_OK == comm_status) { status = xmodem_error_handler(&#038;error_number, X_MAX_ERRORS); } break; } } } /** * @brief Calculates the CRC-16 for the input package. * @param *data: Array of the data which we want to calculate. * @param length: Size of the data, either 128 or 1024 bytes. * @return status: The calculated CRC. */ static uint16_t xmodem_calc_crc(uint8_t *data, uint16_t length) { uint16_t crc = 0u; while (length) { length--; crc = crc ^ ((uint16_t)*data++]]></description>
				<content:encoded><![CDATA[<p>Довольно частая задача &#8212; удаленное обновление прошивки устройства. В моем случае это десятки вендинговых автоматов по продаже сигарет, раскиданных на заправках и пивняках.  Которые управляются от линуксовой головы. До устройства там проброшена консоль отладочная прямо с линя, а само устройство сидит на шине RS485. Так что задача тривиальная. Поделюсь готовым решением. Которое работает как под GD32F103 так и под STM32F1xx</p>
<p><b>▌Немного теории</b><br />
Как стартует контроллер? После сброса проц проверяет состояние BOOT пинов и битов. В памяти у некоторых STM32 есть доступные программатору nBOOT биты, чем то похожие на FUSE у AVR. Их можно достать, например, через STLink через меню Options Byte.</p>
<p><img src="https://easyelectronics.ru/img/ARM_kurs/Bootloader/OptBytes.png"></p>
<p>А дальше, если стоит пин Boot 0, то проц решает какой именно загрузчик выбирать и куда прыгать. По классике, если BOOT0=1 BOOT1=0 происходит прыжок на заводской, зашитый, загрузчик. Который согласно битам nBOOT опеределяет каким именно способом грузиться. Либо через UART (1 или 2), а то и по CAN, i2c и черт знает через что еще. В Документе <b><a href="https://www.st.com/resource/en/application_note/cd00167594-stm32-microcontroller-system-memory-boot-mode-stmicroelectronics.pdf">STM32 AN2606</a></b> описанна масса вариантов встроенного загрузчика для разных семейств STM32.  После чего загрузчик отдает управление уже пользовательской программе. </p>
<p>Как она устроена? Вначале, первыми четырмя байтами лежит адрес где должен быть указатель стека. Потом идет вектор сброса, за ним вектора прерываний.  </p>
<p>000000 estack<br />
000004 ResetVector<br />
000008 InterruptVector<br />
<span id="more-2027"></span></p>
<p>И после этой таблицы уже располагается тело программы.</p>
<p>Все бы ничего, но вот только у нас задача обновить прошивку, а для обновления надо дернуть BOOT0, аппаратно дернуть. Кто-то, кстати ,аппаратно и дергает. Вешая туда через конденсатор вывод ноги. Хочешь перезагрузиться &#8212; заряжаешь конденсатор и быстро ребутишься. Работает отлично. Но надо не забыть сделать еще разряжающий резистор туда. Чтобы конденсатор всегда был надежно разряжен.  Но это если заранее продумал такой способ. А если, как я, вначале делаем, а потом думаем&#8230; то :)))))</p>
<p>Поначалу я попытался еще подергаться. Вот такая картинка есть в документации от STM. Это карта адресов.<br />
<img src="https://easyelectronics.ru/img/ARM_kurs/Bootloader/8EKT8.png"></p>
<p>Тут видно, что на адресе  0х1FFF77FF располагается системная память ,где и живет тот самый системный загрузчик. И, в принципе, нам ничего не мешает в нашем устройстве сделать интерфейс совпадающий с интерфейсом пригодным для системного загрузчика и сделать прыжок на адрес этого загрузчика BOOTADDRESS. </p>
<pre lang="C" line="1">
// Тип - указатель на функцию
typedef void (*func_ptr)(void);  	

// Определяем указатель 
func_ptr jump_to_app;		

// Присваиваем указателю значение. 
// +4 нужно чтобы перешагнуть адрес стека и сразу встать на вектор сброса

jump_to_app = (fnc_ptr)(*(volatile uint32_t*)(BOOTADDRESS+4u));	


// Сбросить все биты ожидания прерывания. 
// Выключить таймеры
// Выключить тактирование периферии. 
 __disable_irq();
SysTick->CTRL = 0;
HAL_RCC_DeInit();

// Количество регистров сброса и ожидания прерывания зависит от ядра. У М3 их 3
     for (i=0;i<3;i++)
     {
	  NVIC->ICER[i]=0xFFFFFFFF;
	  NVIC->ICPR[i]=0xFFFFFFFF;
     }	
__enable_irq();

__set_MSP(*(volatile uint32_t*)BOOTADDRESS);
jump_to_app();
</pre>
<p> Но на практике не все так просто. Дело в том, что BOOTADDRESS часто нифига не совпадает с началом системной памяти. А просто &#171;расположена в этой области&#187;. В документации можно найти порой истинные значения адресов расположения бутлоадера. В той же <b><a href="https://www.st.com/resource/en/application_note/cd00167594-stm32-microcontroller-system-memory-boot-mode-stmicroelectronics.pdf">STM32 AN2606</a></b> </p>
<p><img src="https://easyelectronics.ru/img/ARM_kurs/Bootloader/bootaddr.png"></p>
<p>А может и не найти. Как повезет. Еще там есть зависимость от версии бутлодера, а она тоже меняется от года к году. В общем, у меня оно то работало, то не работало. И я забил на встроенный бутлоадер. Считаем его аппаратным. :) Паять и резать готовые устройства не хотелось. Ладно, будем писать свой загрузчик. </p>
<p><b>▌Программный загрузчик</b><br />
Тут еще проще, процессор стартует как положено. Со своего привычного адреса, но стартует это загрузчик. А дальше ждет условия, таймаута или еще чего то, после чего передает управление уже программе пользователя. </p>
<p>Для этого программу пользователя надо несколько модифицировать. А именно сделать две вещи:<br />
Указать в скрипте линковщике где у нас будет начинаться пользовательский флеш.<br />
Указать в хидере проца, где у насколько у нас смещена таблица векторов. </p>
<p><b>▌Загрузчик XMODEM</b><br />
Поскольку у меня у устройства есть человеческая консолька отладочная. То я бы в нее и высунул загрузчик. Да не просто высунул, а сделал так, чтобы можно было прошиться через любую терминальную утилитку, Типа TerraTerm да хоть виндовый Hyperterminal закинув программу по протоколу XMODEM. Олдовая вещь, по которой мы с далеком 2000 году с друганом фотки сисястые и Win386.swp пересылали друг другу по модемам. В доинтернетные времена :)</p>
<p>Поскольку задача ну очень уже банальная, то готовое <a href="https://github.com/ferenc-nemeth/stm32-bootloader">решение нашлось мгновенно</a>. </p>
<p>Немножко только допилил его под свои реалии. Исходный проект, я брать не стал, ибо не переношу кубовую отрыжку. Просто написал ручками, на том же HAL. </p>
<pre lang="C" line="1">
#include "main.h"

#include "xmodem.h"
#include "flash.h"
#include "string.h"

UART_HandleTypeDef MyUART;

int main(void)
{

// Обычная инициализация тактирования и периферии. Из которой тут только UART
  	HAL_Init();

	RCC_OscInitTypeDef RCC_OscInitStruct = {0};
	RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

	RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
	RCC_OscInitStruct.HSEState = RCC_HSE_ON;
	RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
	RCC_OscInitStruct.HSIState = RCC_HSI_ON;
	RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
	RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
	RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
	if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
	{
	Error_Handler();
	}

	RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
							  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
	RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
	RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
	RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
	RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
	if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
	{
	Error_Handler();
	}

	HAL_RCC_EnableCSS();

// UART иницилизируем втупую, без прерываний. Только минимальная работа.
	MyUART.Instance = USART3;
	MyUART.Init.BaudRate = 9600;
	MyUART.Init.WordLength = UART_WORDLENGTH_8B;
	MyUART.Init.StopBits = UART_STOPBITS_1;
	MyUART.Init.Parity = UART_PARITY_NONE;
	MyUART.Init.Mode = UART_MODE_TX_RX;
	MyUART.Init.HwFlowCtl = UART_HWCONTROL_NONE;
	MyUART.Init.OverSampling = UART_OVERSAMPLING_16;
	if (HAL_UART_Init(&MyUART) != HAL_OK)
	{
	  Error_Handler();
	}

// заводим буфер под строку. 
	char str[6]="";

	while (1)
	{

// Очищаем буфер UART чтобы там не было предыдущих сообщений, байтов и прочего
// А также очищаем строковый буфер. 
		void uart_clrbuff();
		memset(str,0,6);

// Шлем приглашение ко вводу.
		uart_transmit_str((uint8_t*)"BOOTLOADER Enter cmd>>\n\r");


// В блокирующем режиме ждетм ответа. Принимается функцией HAL с таймаутом в 3 секунды
		if(uart_receive((uint8_t*)str,4) == UART_ERROR) continue;

// Парсим команды.
		if(strcmp(str,"RUN!") == 0)  flash_jump_to_app();
		if(strcmp(str,"BOOT") != 0)  continue;

		uart_transmit_str((uint8_t*)"Send a new binary file with Xmodem protocol to update the firmware.\n\r");

// Собственно эта функция  и запускает процесс приема и прожига. 

		xmodem_receive();

// Если ошибка - начинаем все с начала. 
		uart_transmit_str((uint8_t*)"\n\rFailed... Try again.\n\r");
	}
}

void Error_Handler(void)
{
  while(1)
  {
  }
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */


</pre>
<p>Небольшая прокладка между флешером и HAL библиотекой осталась от исходного проекта:</p>
<pre lang="C" line="1">
/**
 * @file    uart.c
 * @author  Ferenc Nemeth
 * @date    21 Dec 2018
 * @brief   This module is a layer between the HAL UART functions and my Xmodem protocol.
 *
 *          Copyright (c) 2018 Ferenc Nemeth - https://github.com/ferenc-nemeth
 */

#include "uart.h"

/**
 * @brief   Receives data from UART.
 * @param   *data: Array to save the received data.
 * @param   length:  Size of the data.
 * @return  status: Report about the success of the receiving.
 */
uart_status uart_receive(uint8_t *data, uint16_t length)
{
  uart_status status = UART_ERROR;

  if (HAL_OK == HAL_UART_Receive(&MyUART, data, length, UART_TIMEOUT))
  {
    status = UART_OK;
  }

  return status;
}

/**
 * @brief   Transmits a string to UART.
 * @param   *data: Array of the data.
 * @return  status: Report about the success of the transmission.
 */
uart_status uart_transmit_str(uint8_t *data)
{
  uart_status status = UART_ERROR;
  uint16_t length = 0u;

  /* Calculate the length. */
  while ('\0' != data[length])
  {
    length++;
  }

  if (HAL_OK == HAL_UART_Transmit(&MyUART, data, length, UART_TIMEOUT))
  {
    status = UART_OK;
  }

  return status;
}

/**
 * @brief   Transmits a single char to UART.
 * @param   *data: The char.
 * @return  status: Report about the success of the transmission.
 */
uart_status uart_transmit_ch(uint8_t data)
{
  uart_status status = UART_ERROR;

  /* Make available the UART module. */
  if (HAL_UART_STATE_TIMEOUT == HAL_UART_GetState(&MyUART))
  {
    HAL_UART_Abort(&MyUART);
  }

  if (HAL_OK == HAL_UART_Transmit(&MyUART, &data, 1u, UART_TIMEOUT))
  {
    status = UART_OK;
  }
  return status;
}

void uart_clrbuff(void)
{
	HAL_UART_AbortReceive(&MyUART);
}

</pre>
<p>UART_TIMEOUT прописан в 3 секунды. </p>
<p>Для работы с флешем используется все тот же HAL инструментарий. Оставил этот код практически без изменений. Разве что добавил обнуление флагов прерываний и ожидания перед выходом в приложение пользователя. </p>
<pre lang="C" line="1">
/**
 * @file    flash.c
 * @author  Ferenc Nemeth
 * @date    21 Dec 2018
 * @brief   This module handles the memory related functions.
 *
 *          Copyright (c) 2018 Ferenc Nemeth - https://github.com/ferenc-nemeth
 */

#include "flash.h"

/* Function pointer for jumping to user application. */
typedef void (*fnc_ptr)(void);

/**
 * @brief   This function erases the memory.
 * @param   address: First address to be erased (the last is the end of the flash).
 * @return  status: Report about the success of the erasing.
 */
flash_status flash_erase(uint32_t address)
{
  HAL_FLASH_Unlock();

  flash_status status = FLASH_ERROR;
  FLASH_EraseInitTypeDef erase_init;
  uint32_t error = 0u;

  erase_init.TypeErase = FLASH_TYPEERASE_PAGES;
  erase_init.PageAddress = address;
  erase_init.Banks = FLASH_BANK_1;
  /* Calculate the number of pages from "address" and the end of flash. */
  erase_init.NbPages = (FLASH_BANK1_END - address) / FLASH_PAGE_SIZE;
  /* Do the actual erasing. */
  if (HAL_OK == HAL_FLASHEx_Erase(&erase_init, &error))
  {
    status = FLASH_OK;
  }

  HAL_FLASH_Lock();

  return status;
}

/**
 * @brief   This function flashes the memory.
 * @param   address: First address to be written to.
 * @param   *data:   Array of the data that we want to write.
 * @param   *length: Size of the array.
 * @return  status: Report about the success of the writing.
 */
flash_status flash_write(uint32_t address, uint32_t *data, uint32_t length)
{
  flash_status status = FLASH_OK;

  HAL_FLASH_Unlock();

  /* Loop through the array. */
  for (uint32_t i = 0u; (i < length) &#038;&#038; (FLASH_OK == status); i++)
  {
    /* If we reached the end of the memory, then report an error and don't do anything else.*/
    if (FLASH_APP_END_ADDRESS <= address)
    {
      status |= FLASH_ERROR_SIZE;
    }
    else
    {
      /* The actual flashing. If there is an error, then report it. */
      if (HAL_OK != HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data[i]))
      {
        status |= FLASH_ERROR_WRITE;
      }
      /* Read back the content of the memory. If it is wrong, then report an error. */
      if (((data[i])) != (*(volatile uint32_t*)address))
      {
        status |= FLASH_ERROR_READBACK;
      }

      /* Shift the address by a word. */
      address += 4u;
    }
  }

  HAL_FLASH_Lock();

  return status;
}

/**
 * @brief   Actually jumps to the user application.
 * @param   void
 * @return  void
 */


void flash_jump_to_app(void)
{
  /* Function pointer to the address of the user application. */
  fnc_ptr jump_to_app;

  jump_to_app = (fnc_ptr)(*(volatile uint32_t*) (FLASH_APP_START_ADDRESS+4u));

//HAL_DeInit();
  RCC->APB1RSTR = 0xFFFFFFFFU;
  RCC->APB1RSTR = 0x00;
  RCC->APB2RSTR = 0xFFFFFFFFU;
  RCC->APB2RSTR = 0x00;

//SysTick DeInit
  SysTick->CTRL=0;
  SysTick->VAL=0;
  SysTick->LOAD=0;

  __disable_irq();

  //NVIC DeInit
  __set_BASEPRI(0);
  __set_CONTROL(0);
  NVIC->ICER[0]=0xFFFFFFFF;
  NVIC->ICPR[0]=0xFFFFFFFF;
  NVIC->ICER[1]=0xFFFFFFFF;
  NVIC->ICPR[1]=0xFFFFFFFF;
  NVIC->ICER[2]=0xFFFFFFFF;
  NVIC->ICPR[2]=0xFFFFFFFF;

  __enable_irq();

  /* Change the main and local  stack pointer. */
  __set_MSP(*(volatile uint32_t*)FLASH_APP_START_ADDRESS);
    SCB->VTOR=*(volatile uint32_t*)FLASH_APP_START_ADDRESS;

  jump_to_app();
}
</pre>
<p>Тут обратите внимание на то как происходит переход к приложению. Мы вначале делаем HAL_DeInit() чтобы выключить все что включили. Правда родной HAL_DeInit() не подходит, т.к. во первых он не восстанавливает состояние SysTick, хотя HAL_Init() его меняет. А во-вторых, если включить оптимизацию, то все рушится. Т.к. оптимизатор каким то хитрым образом запаковывает все в стек, а тут я меняю указатель стека и при выходе из HAL_DeInit()  все крашится. Поэтому я эту функцию развернул и дополнил.  Потом я, на всякий случай, зачищаю таблицы прерываний и битов NVIC чтобы никакое прерывание внезапно не выскочило. Перенаправляю таблицу векторов на новый адрес __set_MSP  и прыгаю на новый вектор сброса FLASH_APP_START_ADDRESS . Который будет после нашего бутлоадера.  Чему он равен? А это мы потом вычислим, когда сделаем бутлоадер до конца. </p>
<p>Ну и процедура работы с xmodem тоже оставил авторскую, без изменений вообще</p>
<pre lang="C" line="1">
/**
 * @file    xmodem.c
 * @author  Ferenc Nemeth
 * @date    21 Dec 2018
 * @brief   This module is the implementation of the Xmodem protocol.
 *
 *          Copyright (c) 2018 Ferenc Nemeth - https://github.com/ferenc-nemeth
 */

#include "xmodem.h"

/* Global variables. */
static uint8_t xmodem_packet_number = 1u;         /**< Packet number counter. */
static uint32_t xmodem_actual_flash_address = 0u; /**< Address where we have to write. */
static uint8_t x_first_packet_received = false;   /**< First packet or not. */

/* Local functions. */
static uint16_t xmodem_calc_crc(uint8_t *data, uint16_t length);
static xmodem_status xmodem_handle_packet(uint8_t size);
static xmodem_status xmodem_error_handler(uint8_t *error_number, uint8_t max_error_number);

/**
 * @brief   This function is the base of the Xmodem protocol.
 *          When we receive a header from UART, it decides what action it shall take.
 * @param   void
 * @return  void
 */
void xmodem_receive(void)
{
  volatile xmodem_status status = X_OK;
  uint8_t error_number = 0u;

  x_first_packet_received = false;
  xmodem_packet_number = 1u;
  xmodem_actual_flash_address = FLASH_APP_START_ADDRESS;

  /* Loop until there isn't any error (or until we jump to the user application). */
  while (X_OK == status)
  {
    uint8_t header = 0x00u;

    /* Get the header from UART. */
    uart_status comm_status = uart_receive(&#038;header, 1u);

    /* Spam the host (until we receive something) with ACSII "C", to notify it, we want to use CRC-16. */
    if ((UART_OK != comm_status) &#038;&#038; (false == x_first_packet_received))
    {
      (void)uart_transmit_ch(X_C);
    }
    /* Uart timeout or any other errors. */
    else if ((UART_OK != comm_status) &#038;&#038; (true == x_first_packet_received))
    {
      status = xmodem_error_handler(&#038;error_number, X_MAX_ERRORS);
    }
    else
    {
      /* Do nothing. */
    }

    xmodem_status packet_status = X_ERROR;
    /* The header can be: SOH, STX, EOT and CAN. */
    switch(header)
    {

      /* 128 or 1024 bytes of data. */
      case X_SOH:
      case X_STX:
        /* If the handling was successful, then send an ACK. */
        packet_status = xmodem_handle_packet(header);
        if (X_OK == packet_status)
        {
          (void)uart_transmit_ch(X_ACK);
        }
        /* If the error was flash related, then immediately set the error counter to max (graceful abort). */
        else if (X_ERROR_FLASH == packet_status)
        {
          error_number = X_MAX_ERRORS;
          status = xmodem_error_handler(&#038;error_number, X_MAX_ERRORS);
        }
        /* Error while processing the packet, either send a NAK or do graceful abort. */
        else
        {
          status = xmodem_error_handler(&#038;error_number, X_MAX_ERRORS);
        }
        break;
      /* End of Transmission. */
      case X_EOT:
        /* ACK, feedback to user (as a text), then jump to user application. */
        (void)uart_transmit_ch(X_ACK);
        (void)uart_transmit_str((uint8_t*)"\n\rFirmware updated!\n\r");
        (void)uart_transmit_str((uint8_t*)"Jumping to user application...\n\r");
        flash_jump_to_app();
        break;
      /* Abort from host. */
      case X_CAN:
        status = X_ERROR;
        break;
      default:
        /* Wrong header. */
        if (UART_OK == comm_status)
        {
          status = xmodem_error_handler(&#038;error_number, X_MAX_ERRORS);
        }
        break;
    }
  }
}

/**
 * @brief   Calculates the CRC-16 for the input package.
 * @param   *data:  Array of the data which we want to calculate.
 * @param   length: Size of the data, either 128 or 1024 bytes.
 * @return  status: The calculated CRC.
 */
static uint16_t xmodem_calc_crc(uint8_t *data, uint16_t length)
{
    uint16_t crc = 0u;
    while (length)
    {
        length--;
        crc = crc ^ ((uint16_t)*data++ << 8u);
        for (uint8_t i = 0u; i < 8u; i++)
        {
            if (crc &#038; 0x8000u)
            {
                crc = (crc << 1u) ^ 0x1021u;
            }
            else
            {
                crc = crc << 1u;
            }
        }
    }
    return crc;
}

/**
 * @brief   This function handles the data packet we get from the xmodem protocol.
 * @param   header: SOH or STX.
 * @return  status: Report about the packet.
 */
static xmodem_status xmodem_handle_packet(uint8_t header)
{
  xmodem_status status = X_OK;
  uint16_t size = 0u;

  /* 2 bytes for packet number, 1024 for data, 2 for CRC*/
  uint8_t received_packet_number[X_PACKET_NUMBER_SIZE];
  uint8_t received_packet_data[X_PACKET_1024_SIZE];
  uint8_t received_packet_crc[X_PACKET_CRC_SIZE];

  /* Get the size of the data. */
  if (X_SOH == header)
  {
    size = X_PACKET_128_SIZE;
  }
  else if (X_STX == header)
  {
    size = X_PACKET_1024_SIZE;
  }
  else
  {
    /* Wrong header type. This shoudn't be possible... */
    status |= X_ERROR;
  }

  uart_status comm_status = UART_OK;
  /* Get the packet number, data and CRC from UART. */
  comm_status |= uart_receive(&#038;received_packet_number[0u], X_PACKET_NUMBER_SIZE);
  comm_status |= uart_receive(&#038;received_packet_data[0u], size);
  comm_status |= uart_receive(&#038;received_packet_crc[0u], X_PACKET_CRC_SIZE);
  /* Merge the two bytes of CRC. */
  uint16_t crc_received = ((uint16_t)received_packet_crc[X_PACKET_CRC_HIGH_INDEX] << 8u) | ((uint16_t)received_packet_crc[X_PACKET_CRC_LOW_INDEX]);
  /* We calculate it too. */
  uint16_t crc_calculated = xmodem_calc_crc(&#038;received_packet_data[0u], size);

  /* Communication error. */
  if (UART_OK != comm_status)
  {
    status |= X_ERROR_UART;
  }

  /* If it is the first packet, then erase the memory. */
  if ((X_OK == status) &#038;&#038; (false == x_first_packet_received))
  {
    if (FLASH_OK == flash_erase(FLASH_APP_START_ADDRESS))
    {
      x_first_packet_received = true;
    }
    else
    {
      status |= X_ERROR_FLASH;
    }
  }

  /* Error handling and flashing. */
  if (X_OK == status)
  {
    if (xmodem_packet_number != received_packet_number[0u])
    {
      /* Packet number counter mismatch. */
      status |= X_ERROR_NUMBER;
    }
    if (255u != (received_packet_number[X_PACKET_NUMBER_INDEX] + received_packet_number[X_PACKET_NUMBER_COMPLEMENT_INDEX]))
    {
      /* The sum of the packet number and packet number complement aren't 255. */
      /* The sum always has to be 255. */
      status |= X_ERROR_NUMBER;
    }
    if (crc_calculated != crc_received)
    {
      /* The calculated and received CRC are different. */
      status |= X_ERROR_CRC;
    }
  }

    /* Do the actual flashing (if there weren't any errors). */
    if ((X_OK == status) &#038;&#038; (FLASH_OK != flash_write(xmodem_actual_flash_address, (uint32_t*)&#038;received_packet_data[0u], (uint32_t)size/4u)))
    {
      /* Flashing error. */
      status |= X_ERROR_FLASH;
    }

  /* Raise the packet number and the address counters (if there weren't any errors). */
  if (X_OK == status)
  {
    xmodem_packet_number++;
    xmodem_actual_flash_address += size;
  }

  return status;
}

/**
 * @brief   Handles the xmodem error.
 *          Raises the error counter, then if the number of the errors reached critical, do a graceful abort, otherwise send a NAK.
 * @param   *error_number:    Number of current errors (passed as a pointer).
 * @param   max_error_number: Maximal allowed number of errors.
 * @return  status: X_ERROR in case of too many errors, X_OK otherwise.
 */
static xmodem_status xmodem_error_handler(uint8_t *error_number, uint8_t max_error_number)
{
  xmodem_status status = X_OK;
  /* Raise the error counter. */
  (*error_number)++;
  /* If the counter reached the max value, then abort. */
  if ((*error_number) >= max_error_number)
  {
    /* Graceful abort. */
    (void)uart_transmit_ch(X_CAN);
    (void)uart_transmit_ch(X_CAN);
    status = X_ERROR;
  }
  /* Otherwise send a NAK for a repeat. */
  else
  {
    (void)uart_transmit_ch(X_NAK);
    status = X_OK;
  }
  return status;
}

</pre>
<p>Алгортим работы ее следующий:</p>
<p><img src="https://easyelectronics.ru/img/ARM_kurs/Bootloader/bootloader.png"></p>
<p>Теперь надо сконфигурировать приложение пользователя. А для этого надо вычислить FLASH_APP_START_ADDRESS. Можно, конечно, с запасом бахнуть адрес  в небеса и не париться. Но я в этом плане более рационален. Для начала откроем наш hex бута редактором и поглядим конечный адрес программы. </p>
<p><img src="https://easyelectronics.ru/img/ARM_kurs/Bootloader/BootEND.png"></p>
<p>А теперь вспоминаем размер секций памяти. Дело в том, что для того, чтобы записать во флеш ее надо вначале стереть, а стирать можно только блоками по 2КБ. И поэтому программа должна начинаться по границе блока. Откроем ут же STLink Utility и глянем что там у нас:</p>
<p><img src="https://easyelectronics.ru/img/ARM_kurs/Bootloader/OptBytes.png"> </p>
<p>Ближайшая старшая страница номер 4, с адресом 0х08002000. </p>
<p>Значит  FLASH_APP_START_ADDRESS = 0х08002000</p>
<p>идем в <b>flash.h</b> и правим там:</p>
<pre lang="C" line="1">
#include "stm32f1xx_hal.h"

/* Start and end addresses of the user application. */
#define FLASH_APP_START_ADDRESS ((uint32_t)0x08002000u)
#define FLASH_APP_END_ADDRESS   ((uint32_t)FLASH_BANK1_END-0x10u) /**< Leave a little extra space at the end. */

/* Status report for the functions. */
typedef enum {
  FLASH_OK              = 0x00u, /**< The action was successful. */
  FLASH_ERROR_SIZE      = 0x01u, /**< The binary is too big. */
  FLASH_ERROR_WRITE     = 0x02u, /**< Writing failed. */
  FLASH_ERROR_READBACK  = 0x04u, /**< Writing was successful, but the content of the memory is wrong. */
  FLASH_ERROR           = 0xFFu  /**< Generic error. */
} flash_status;

flash_status flash_erase(uint32_t address);
flash_status flash_write(uint32_t address, uint32_t *data, uint32_t length);
void flash_jump_to_app(void);
</pre>
<p>Теперь бутлоадер будет после запуска слать программу по адресу 0х08002000.</p>
<p>Осталось поправить проект, чтобы он делал старт с адреса 0х08002000. Для этого надо поправить два файла: скрипт линковщика и инклюдник проца. </p>
<ul>
<li> STM32F103XC.ld </li>
<li> system_stm32f10x.c </li>
</ul>
<p>Причем, лучше, не править исходные файлы, а сделать отдельную копию с припиской _BOOT этих файлов и создать в IDE или в мейкфайле отдельную цель, чтобы можно было компилировать проект с загрузчиком  и без него. Один боевой, а другой для отладки. И просто переключать эти цели по мере необходимости. </p>
<p>Итак. Идем в файл линковщика у меня процессор GD32 поэтому файл GD32F103XC_BOOT.ld</p>
<p>Там в самом начале секция, примерно что-то вроде:</p>
<pre lang="C" line="1">
/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
__initial_sp = 0x2000BFFF;    /* end of RAM */

/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack */

/* Specify the memory areas */
MEMORY
{
FLASH (rx)      : ORIGIN = 0x08002000, LENGTH = 248K
RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 48K
}

</pre>
<p>Вот строка </p>
<p>FLASH (rx)      : ORIGIN = 0x08002000, LENGTH = 248K </p>
<p>изначально была </p>
<p>FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 256K</p>
<p>Т.е. я добавил смещение и укоротил длину на наши 4 сектора, каждый из которых по 2кб. </p>
<p>Теперь надо указать смещение точки векторной таблицы. Это в файле system_gd32f10x_boot.c<br />
Там будет штука вида:</p>
<pre lang="C" line="1">
#include "gd32f10x.h"

/* system frequency define */
#define __IRC8M           (IRC8M_VALUE)            /* internal 8 MHz RC oscillator frequency */
#define __HXTAL           (HXTAL_VALUE)            /* high speed crystal oscillator frequency */
#define __SYS_OSC_CLK     (__IRC8M)                /* main oscillator frequency */

#define VECT_TAB_OFFSET  (uint32_t)0x00002000U            /* vector table base offset */

/* select a system clock by uncommenting the following line */
/* use IRC8M */
//#define __SYSTEM_CLOCK_48M_PLL_IRC8M            (uint32_t)(48000000)
//#define __SYSTEM_CLOCK_72M_PLL_IRC8M            (uint32_t)(72000000)
//#define __SYSTEM_CLOCK_108M_PLL_IRC8M           (uint32_t)(108000000)
</pre>
<p>Вот строку </p>
<pre lang="C" line="1">
#define VECT_TAB_OFFSET  (uint32_t)0x00000000U            /* vector table base offset */
</pre>
<p>Надо и поправить на наше смещение в 0x2000.</p>
<p>После чего проект надо скомпилировать в бинарик. Проще всего сделать это через встроенную утилитку компилятора </p>
<p><b>arm-none-eabi-objcopy -O binary "bin\Release\GDscud-bw.elf" "bin\Release\GDscud-bw.bin"</b></p>
<p>Я ее подсунул в поспроцессинг компилятора и при компилировании бутлоадерной ветки она делается автоматически.</p>
<p>Осталось зайти в бутлоадер через терминалку:</p>
<p><img src="https://easyelectronics.ru/img/ARM_kurs/Bootloader/ttm1.png"></p>
<p>Бутлоадер шлет приглашение. Если наберу RUN! запустит программу. Если наберу BOOT будет ждать прошивку. </p>
<p><img src="https://easyelectronics.ru/img/ARM_kurs/Bootloader/ttm2.png"></p>
<p>Прошьемся. </p>
<p><img src="https://easyelectronics.ru/img/ARM_kurs/Bootloader/ttm3.png"></p>
<p>Выберем XMODEM и подсунем ему бинарик. </p>
<p><img src="https://easyelectronics.ru/img/ARM_kurs/Bootloader/ttm31.png"></p>
<p>Заливается... </p>
<p><img src="https://easyelectronics.ru/img/ARM_kurs/Bootloader/ttm4.png"></p>
<p>Готово! Дальше уже запустилась моя прошивка устройства. Выдала свое приглашение консоли. FreeRTOS запустилась, прерывания и периферия работает верно. </p>
<p><b>▌Извращения</b><br />
Ну, а дальше, таким вот макаром. Никто не запрещает развлекаться разным образом. Например, делать на одной флеше две-три разных прошивки. И бутлоадером выбирать куда прыгнуть. Зачем? Например, для безопасного обновления критично важных устройств. Делаем две области памяти. Бутлоадер проверяет в какой области памяти более старая, из двух, прошивка и льет новую туда. После пытается ее стартануть. Если она стартанула криво об этом узнает небольшой процессор-вачдог который можно присобачить снаружи. Его можно сделать на какой нибудь Tiny6 6 ногой. А может и готовые решения есть. И если его фельдиперсово не пнуть хитрым образом, чтобы он перезапустил контроллер и настучал бутлоадеру, что у нас тут ататат. Бутлоадер проверит какая прошивка более свежая и переключится на более старую. Просигналя каким-либо образом ей что у нас тут авария и надо бы попробовать еще раз скачать-обновить. Вариантов тут придумать можно много разных. </p>
<p><a href="https://easyelectronics.ru/img/ARM_kurs/Bootloader/STM103BOOT.ZIP"><b>Проект с исходниками из статьи</b></a></p>
<p>А в одной <a href="http://easyelectronics.ru/proshivka-arm-cortex-m3-na-primere-stm32-i-lpc1300.html">из старых статей</a> был сторонний mass storage бутлоадер, позволяющий прошивать STM32F103C8T6 просто подцепив его как флешку и закидывая бинарик на "диск". </p>
]]></content:encoded>
			<wfw:commentRss>http://easyelectronics.ru/stm32-bootloader.html/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Газовая заshitа :)</title>
		<link>http://easyelectronics.ru/gazovaya-zashita.html</link>
		<comments>http://easyelectronics.ru/gazovaya-zashita.html#comments</comments>
		<pubDate>Thu, 08 Jun 2023 17:20:40 +0000</pubDate>
		<dc:creator><![CDATA[DI HALT]]></dc:creator>
				<category><![CDATA[MegaHard Lab]]></category>
		<category><![CDATA[Диагностика и ремонт]]></category>

		<guid isPermaLink="false">http://easyelectronics.ru/?p=2023</guid>
		<description><![CDATA[Звонят мне тут с одного завода Костанайского, мол у нас тут котельная встала. Можете приехать разобраться. Приезжаю. Начинаю разбираться в схеме управления. Выхожу на газоанализатор СЗЦ-1. От компании Энергосистемы. Который измеряет утечки газа в помещении и если концентрация высокая &#8212; вырубает газ и дает сигнализацию. Обычным релейным контактом, замыкая контактор, который рубит задвижки и врубает сирену. Датчик сработал, но ведет себя странно. При отключении питания продолжает питаться от&#8230; сухого контакта реле. Штобля??? Срываю пломбы, разбираю прибор анализатора, пытаюсь понять что происходит и начинаю просто орать чаечкой. Короче, в предыдущей, видимо, версии прибора там на выходе и вправду был сухой контакт. Обычное реле. Как и положено по схеме. Но изначальный прибор сдох несколько лет назад и его заменили на такой же, такого же производителя. Но&#8230; тот, ВНЕЗАПНО, внес в конструкцию коррективы. Теперь он не замыкает реле, а ЧЕРЕЗ реле выдает питание на выход. Подразумевается, видимо, что там будет уже какое-нибудь исполнительное устройство, вроде катушки реле или контактора. По счастливой случайности, при монтаже, вилку питания воткнули так, что фаза сразу не попала на ноль. И все это благополучно завелось и работало несколько лет пока не случилась утечка газа&#8230; датчик сработал и устроил эпичное КЗ внутри себя. АВТОПОДЖИГ, ЕПТА! И что, сука, характерно старый и новый прибор не отличаются внешне никак. Обычная вилка (sic!) питания и выход в виде торчащего поросячего хвоста, с зачищенными жилами. Ни бэ ни мэ ,ни кукареку в документации или описании. И да, изменения сделаны на заводе, были под заводской пломбой и на печатной плате под такое исполнение есть задел в виде дорожек и отверстий. Т.е. все запланированно! Шильдики не отличаются. По крайней мере указания на применение не замечено. В общем, если вы узнали этот прибор, то проверьте, а не детонатор ли это? ;) З.Ы. Я уж молчу про исползование РЕЛЕ в газозащитном оборудовании. Это отдельный кринж.]]></description>
				<content:encoded><![CDATA[<p>Звонят мне тут с одного завода Костанайского, мол у нас тут котельная встала. Можете приехать разобраться. Приезжаю. Начинаю разбираться в схеме управления. Выхожу на газоанализатор  СЗЦ-1. От компании <a href="https://systemgaz.ru/">Энергосистемы</a>. Который измеряет утечки газа в помещении и если концентрация высокая &#8212; вырубает газ и дает сигнализацию. Обычным релейным контактом, замыкая контактор, который рубит задвижки и врубает сирену. </p>
<p>Датчик сработал, но ведет себя странно. При отключении питания продолжает питаться от&#8230; сухого контакта реле. Штобля??? </p>
<p>Срываю пломбы, разбираю прибор анализатора, пытаюсь понять что происходит и начинаю просто орать чаечкой.<br />
<span id="more-2023"></span><br />
<img src="https://easyelectronics.ru/img/megahardlab/gasanal/v2.jpg"></p>
<p>Короче, в предыдущей, видимо, версии прибора там на выходе и вправду был сухой контакт. Обычное реле. Как и положено по схеме. Но изначальный прибор сдох несколько лет назад и его заменили на такой же, такого же производителя. Но&#8230; тот, ВНЕЗАПНО, внес в конструкцию коррективы. Теперь он не замыкает реле, а ЧЕРЕЗ реле выдает питание на выход. Подразумевается, видимо, что там будет уже какое-нибудь исполнительное устройство, вроде катушки реле или контактора. </p>
<p><img src="https://easyelectronics.ru/img/megahardlab/gasanal/sxgas.jpg"></p>
<p>По счастливой случайности, при монтаже, вилку питания воткнули так, что фаза сразу не попала на ноль. И все это благополучно завелось и работало несколько лет пока не случилась утечка газа&#8230; датчик сработал и устроил эпичное КЗ внутри себя.  <b>АВТОПОДЖИГ</b>, ЕПТА!</p>
<p>И что, сука, характерно старый и новый прибор не отличаются внешне никак. Обычная вилка (sic!) питания и выход в виде торчащего поросячего хвоста, с зачищенными жилами. Ни бэ ни мэ ,ни кукареку в документации или описании.  И да, изменения сделаны на заводе, были под заводской пломбой и на печатной плате под такое исполнение есть задел в виде дорожек и отверстий. Т.е. все запланированно!  </p>
<p><img src="https://easyelectronics.ru/img/megahardlab/gasanal/v2v1.jpg"></p>
<p>Шильдики не отличаются. По крайней мере указания на применение не замечено. </p>
<p><img src="https://easyelectronics.ru/img/megahardlab/gasanal/v2v1sh.jpg"></p>
<p>В общем, если вы узнали этот прибор, то проверьте, а не детонатор ли это? ;) </p>
<p>З.Ы.<br />
Я уж молчу про исползование РЕЛЕ в газозащитном оборудовании. Это отдельный кринж. </p>
]]></content:encoded>
			<wfw:commentRss>http://easyelectronics.ru/gazovaya-zashita.html/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Электрообереги</title>
		<link>http://easyelectronics.ru/elektrooberegi.html</link>
		<comments>http://easyelectronics.ru/elektrooberegi.html#comments</comments>
		<pubDate>Fri, 02 Jun 2023 10:53:26 +0000</pubDate>
		<dc:creator><![CDATA[DI HALT]]></dc:creator>
				<category><![CDATA[Книги]]></category>

		<guid isPermaLink="false">http://easyelectronics.ru/?p=2020</guid>
		<description><![CDATA[Давненько я не рекомендовал хороших книжек. Павел Серков, автор замечательно инженерного блога и отличной книги по электротехническим материалам (проводники, диэлектрики, всякие там пленки, лакоткани, проволоки разных марок и прочее прочее) Дао Изоленты написал очередную книжку Книга про разные системы защиты. УЗО, автоматы, предохранители и прочие средства защищающие глупых человеков от злых токов. Слог шикарный, юморной. Читается замечательно. Расписано подробно и дотошно. Книга бесплатна для скачивания. Можно купить и в бумаге. Донат автору крайне рекомендуется. Он этого заслуживает многократно. Скачали? Понравилось? Скиньте товарищу от 100р. Мелочь же, а он глядишь через годик выдаст очередной справочник.]]></description>
				<content:encoded><![CDATA[<p>Давненько я не рекомендовал хороших книжек.</p>
<p><strong> Павел Серков</strong>, автор замечательно <a href="https://serkov.su/blog/">инженерного блога</a> и отличной книги по электротехническим материалам (проводники, диэлектрики, всякие там пленки, лакоткани, проволоки разных марок и прочее прочее) <a href="https://serkov.su/blog/wp-content/uploads/2018/04/Dao_v1.4_LQ.pdf">Дао Изоленты</a>  написал очередную книжку   </p>
<p><img src="https://serkov.su/blog/wp-content/uploads/2023/04/cover-724x1024.png"></p>
<p>Книга про разные системы защиты. УЗО, автоматы, предохранители и прочие средства защищающие глупых человеков от злых токов.  Слог шикарный, юморной. Читается замечательно.  Расписано подробно и дотошно. <a href="https://serkov.su/blog/?page_id=6294">Книга бесплатна для скачивания.</a>  Можно купить и в бумаге.  </p>
<p><a href="https://serkov.su/blog/?page_id=6298">Донат автору крайне рекомендуется</a>. Он этого заслуживает многократно. Скачали? Понравилось? Скиньте товарищу от 100р. Мелочь же, а он глядишь через годик выдаст очередной справочник. </a></p>
]]></content:encoded>
			<wfw:commentRss>http://easyelectronics.ru/elektrooberegi.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>CAN шина. Часть 2</title>
		<link>http://easyelectronics.ru/can-shina-chast-2.html</link>
		<comments>http://easyelectronics.ru/can-shina-chast-2.html#comments</comments>
		<pubDate>Mon, 01 May 2023 13:23:16 +0000</pubDate>
		<dc:creator><![CDATA[DI HALT]]></dc:creator>
				<category><![CDATA[Начинающим]]></category>
		<category><![CDATA[CAN]]></category>
		<category><![CDATA[шина]]></category>

		<guid isPermaLink="false">http://easyelectronics.ru/?p=2017</guid>
		<description><![CDATA[Часть первая Хотя CAN шина довольно неприхотливая, ввиду множества защит и механизмов, но поскольку она может работать на высоких скоростях и больших расстояниях, то важным моментом является грамотное ее проектирование. В частности правильное терминирование оконечных устройств или перегрузка из-за количества устройств. Из-за которых возникает большинство проблем в шине. ▌Количество устройств Когда то давно стандарт предписывал 32 устройства на шине. Но было это очень давно и сейчас таких ограничений нет. Все теперь зависит от нагрузочной способности самого слабого трансивера на шине. Чтобы понять о чем речь надо рассмотреть внутреннее устройство трансивера: Обратите внимание на устройство выходного каскада. В свободном (рецессивном) состоянии выводы CANL и CANH притянуты к внутреннему источнику напряжения, равному половине питания. В результате в шине находится 2.5 вольта примерно. В доминантном состоянии транзисторы прижимают линию вверх и вниз, соответственно. Сопротивление этих резисторов может быть около 20&#8230;30кОм, их можно найти в даташите, а если трансивер под рукой, то просто измерить сопротивление между линиями CANL и CANH и поделить пополам. Таким образом, для того, чтобы транзистор трансивера мог продавить линию его выходной ток должен соответствовать нагрузке. Обычно минимальное сопротивление линии для трансиверов около 60 Ом. В даташите это параметр RL, считаете сопротивление всех узлов, они будут как параллельно включенные резисторы по 30кОм, и получите искомую нагрузку на шину. Например, у нас есть 100 приемников с сопротивлением между CANL и CANH = 30кОм. И два терминатора по 120 Ом. И все это включено в параллель. Считаем суммарное сопротивление этой параллельной цепи резисторов: Сопротивление трансиверов 30 000/100 = 300ом Два трансивера в параллели 120/2. Итого, суммарное сопротивление которое нагрузит выходные цепи трансивера (300*60)/(300+60) = 50ом. ▌Terminator Терминирование на обоих конечных узлах шины CAN является необходимостью. Без оконечной нагрузки 120 Ом на обоих концах возникает отражение сигнала, вызванное несоответствием импеданса между шиной CAN и драйвером, которое приводит к полной неработоспособности всей шины, особенно на высоких скоростях. Как я писал в прошлой статье, лучше соединять узлы шлейфом: В принципе, шина допускает соединение ветвями. Автомобильный стандарт ISO 11898 даже описывает длину этой ветви в 0.3м. Если же у вас медленная скорость и очень хочется, то можно и длинней делать. Но в этом случае, на высоких скоростях, могут начаться отражения сигнала. Которые положат всю сеть. Что делать? Поставить дополнительный терминатор на такую вот ветку. Но не полноценный, иначе мы можем завалить сопротивление шины, а на 1-2 кОм. Существенно улучшает ситуацию. Но лучше так не делать, а разводить шлейфом. ▌Terminator 2 Высокое входное сопротивление шины в рецессивном состоянии делает ее уязвимой даже для небольших токов утечки, которые могут возникать если в шине есть обесточенные узлы. Как, например, может быть в автомобиле. Где часть систем запитана всегда, а часть только когда двигатель заведен. В результате синфазное напряжение может быть ниже, по сравнению с номинальным Vcc/2. При передаче первого доминантного бита синфазное напряжение возвращается до номинального значения. Такие скачки порождают повышенные помехи. Чтобы их снизить в некоторых трансиверах есть Vref или SPLIT выход. Отдельная нога, на которую выводится половина питающего напряжения. Поэтому если в сети есть узлы которые могут терять питание, то терминатор лучше подключать не между линиями CANL и CANH, а до этого опорного напряжения, через половину терминатора. Как указано по схеме: Хоть в этом случае у нас больше деталей, но зато мы получаем фильтр нижних частот для синфазного шума в сети, и, таким образом, может помочь в уменьшении электромагнитных излучений. Резисторы и конденсатор (RC) создают RC-фильтр нижних частот с частотой среза. f = 1/(2*pi*R*C ) И да, частота среза этого фильтра не зависит от частоты полезного сигнала. Поскольку конденсатор фильтрует только синфазный сигнал. А состояние шины определяет дифференциальный сигнал. Поэтому нет необходимости выбирать фильтр с частотой среза выше бодрейта. Главное, что тут надо следить за согласованностью резисторов терминатора. Они должны быть как можно более одиннаковые. Чтобы не было ни малейшего перекоса. Любое изменение сопротивления преобразует синфазный шум, присутствующий в сети, в дифференциальный шум, что снижает помехоустойчивость приемника. Но есть современные трансиверы которые не требуют разделения терминатора. Например TCAN4550 от TI. Сам же резистор терминатора лучше брать побольше, такой чтобы тянул хотя бы миллиампер 100.]]></description>
				<content:encoded><![CDATA[<p><a href="https://easyelectronics.ru/can-shina.html">Часть первая</a></p>
<p>Хотя CAN шина довольно неприхотливая, ввиду множества защит и механизмов, но поскольку она может работать на высоких скоростях и больших расстояниях, то важным моментом является грамотное ее проектирование. В частности правильное терминирование оконечных устройств или перегрузка из-за количества устройств. Из-за которых возникает большинство проблем в шине. </p>
<p><b>▌Количество устройств</b><br />
Когда то давно стандарт предписывал 32 устройства на шине. Но было это очень давно и сейчас таких ограничений нет. Все теперь зависит от нагрузочной способности самого слабого трансивера на шине. Чтобы понять о чем речь надо рассмотреть внутреннее устройство трансивера:</p>
<p><img src="https://easyelectronics.ru/img/starters/CAN/TJA_inside.png"></p>
<p>Обратите внимание на устройство выходного каскада. В свободном (рецессивном) состоянии выводы CANL и CANH притянуты к внутреннему источнику напряжения, равному половине питания. В результате в шине находится 2.5 вольта примерно.</p>
<p><img src="https://easyelectronics.ru/img/starters/CAN/CANHL.png"></p>
<p>В доминантном состоянии транзисторы прижимают линию вверх и вниз, соответственно. </p>
<p>Сопротивление этих резисторов может быть около 20&#8230;30кОм, их можно найти в даташите, а если трансивер под рукой, то просто измерить сопротивление между линиями CANL и CANH и поделить пополам. </p>
<p>Таким образом, для того, чтобы транзистор трансивера мог продавить линию его выходной ток должен соответствовать нагрузке.  Обычно минимальное сопротивление линии для трансиверов около 60 Ом. В даташите это параметр R<sub>L</sub>, считаете сопротивление всех узлов, они будут как параллельно включенные резисторы по 30кОм, и получите искомую нагрузку на шину. </p>
<p><img src="https://easyelectronics.ru/img/starters/CAN/resistors.png"></p>
<p>Например, у нас есть 100 приемников с сопротивлением между CANL и CANH  = 30кОм. И два терминатора по 120 Ом. И все это включено в параллель. Считаем суммарное сопротивление этой параллельной цепи резисторов:  </p>
<ul>
<li> Сопротивление трансиверов 30 000/100 = 300ом </li>
<li> Два трансивера в параллели 120/2. </li>
</ul>
<p>Итого, суммарное сопротивление которое нагрузит выходные цепи трансивера (300*60)/(300+60) = 50ом.<br />
<span id="more-2017"></span></p>
<p><b>▌Terminator</b><br />
<img src="https://easyelectronics.ru/img/starters/CAN/Terminator-pic.png"><br />
Терминирование на обоих конечных узлах шины CAN является необходимостью. Без оконечной нагрузки 120 Ом на обоих концах возникает отражение сигнала, вызванное несоответствием импеданса между шиной CAN и драйвером, которое приводит к полной неработоспособности всей шины, особенно на высоких скоростях. </p>
<p>Как я писал в прошлой статье, лучше соединять узлы шлейфом:</p>
<p><img src="https://easyelectronics.ru/img/starters/CAN/CAN-bus.png"></p>
<p>В принципе, шина допускает соединение ветвями. Автомобильный стандарт ISO 11898 даже описывает длину этой ветви в 0.3м. Если же у вас медленная скорость и очень хочется, то можно и длинней делать. </p>
<p><img src="https://easyelectronics.ru/img/starters/CAN/canbus.png"></p>
<p>Но в этом случае, на высоких скоростях, могут начаться отражения сигнала. Которые положат всю сеть.  Что делать? Поставить дополнительный терминатор на такую вот ветку. Но не полноценный, иначе мы можем завалить сопротивление шины, а на 1-2 кОм. Существенно улучшает ситуацию. Но лучше так не делать, а разводить шлейфом.</p>
<p><b>▌Terminator 2</b><br />
<img src="https://easyelectronics.ru/img/starters/CAN/Terminator2.jpg"></p>
<p>Высокое входное сопротивление шины в рецессивном состоянии делает ее уязвимой даже для небольших токов утечки, которые могут возникать если в шине есть обесточенные узлы. Как, например, может быть в автомобиле. Где часть систем запитана всегда, а часть только когда двигатель заведен.  В результате синфазное напряжение может быть ниже, по сравнению с номинальным Vcc/2. При передаче первого доминантного бита синфазное напряжение возвращается до номинального значения. Такие скачки порождают повышенные помехи.</p>
<p> Чтобы их снизить в некоторых трансиверах есть Vref или SPLIT выход. Отдельная нога, на которую выводится половина питающего напряжения.  Поэтому если в сети есть узлы которые могут терять питание, то терминатор лучше подключать не между линиями CANL и CANH, а до этого опорного напряжения, через половину терминатора. Как указано по схеме:</p>
<p><img src="https://easyelectronics.ru/img/starters/CAN/canbussplit.png"></p>
<p>Хоть в этом случае у нас больше деталей, но зато мы получаем фильтр нижних частот для синфазного шума в сети, и, таким образом, может помочь в уменьшении электромагнитных излучений. Резисторы и конденсатор (RC) создают RC-фильтр нижних частот с частотой среза.</p>
<p>f = 1/(2*pi*R*C )</p>
<p>И да, частота среза этого фильтра не зависит от частоты полезного сигнала.  Поскольку конденсатор фильтрует только синфазный сигнал. А состояние шины определяет дифференциальный сигнал. Поэтому нет необходимости выбирать фильтр с частотой среза выше бодрейта. </p>
<p>Главное, что тут надо следить за согласованностью резисторов терминатора. Они должны быть как можно более одиннаковые. Чтобы не было ни малейшего перекоса. Любое изменение сопротивления преобразует синфазный шум, присутствующий в сети, в дифференциальный шум, что снижает помехоустойчивость приемника.</p>
<p>Но есть современные трансиверы которые не требуют разделения терминатора. Например TCAN4550 от TI. </p>
<p>Сам же резистор терминатора лучше брать побольше, такой чтобы тянул хотя бы миллиампер 100.  </p>
]]></content:encoded>
			<wfw:commentRss>http://easyelectronics.ru/can-shina-chast-2.html/feed</wfw:commentRss>
		<slash:comments>13</slash:comments>
		</item>
		<item>
		<title>Тестовые пины</title>
		<link>http://easyelectronics.ru/testovye-piny.html</link>
		<comments>http://easyelectronics.ru/testovye-piny.html#comments</comments>
		<pubDate>Fri, 17 Feb 2023 19:42:02 +0000</pubDate>
		<dc:creator><![CDATA[DI HALT]]></dc:creator>
				<category><![CDATA[Радиолюбительские Технологии]]></category>
		<category><![CDATA[Осциллограф]]></category>
		<category><![CDATA[Отверстия]]></category>
		<category><![CDATA[Отладка]]></category>
		<category><![CDATA[Херота]]></category>

		<guid isPermaLink="false">http://easyelectronics.ru/?p=2012</guid>
		<description><![CDATA[Проектируя плату я всегда оставляю множество контрольных точек. Это удобно для отладки, а денег и места они почти не требуют. Всегда, вот всегда, я делал петлю для земли. Просто оставлял рядом два отверстия и вставлял туда проволочную петлю, чтобы удобно было цеплять крокодил осциллографа. Но в нее неудобно тыкать щупом, если надо просто измерить мультиметром напряжение от земли. Для сигналов можно оставить просто луженую переходку, вскрытую от маски. Или даже отдельный пустой пад. В него удобно тыкать иголкой мультиметра или осциллографа, отверстие в площадке не даст щупу соскользнуть. Но в нее не зацепишь крокодил. Более того, промышленность выпускает множество видов разных тестпадов, которые можно заказать и установить на этапе производства. Есть как простые петельки с цветной бусинкой Так и более фельдиперсовые, вроде таких вот, точененьких штуковин: или даже двухэтажные Они очень красиво выглядят, в них удобно цепляться крокодилом или крючком. Но вот совершенно неудобно удерживать на них мультиметр. А недавно я, совершенно случайно, обнаружил совершенно чумовой и офигенный теспад, обладающий всеми достоинствами сразу. Это&#8230; Выдранный из обоймы пин от цанговой линейки SIP или из цанговой панельки. Чуть нагреть обойму феном или зажигалкой и достаются вот такие красавцы: Это просто чума. Ее идеально цепляет крокодил, за нее идеально, просто намертво, хватается пружинный крюк осциллографа. В отверстие сверху отлично втыкается игольчатый щуп. Более того!!! В нее можно воткнуть проволочку и куда-нибудь засадить, и она никуда не денется, т.к. там внутри цанга! Шикааааарно! Единственный минус &#8212; серийно заказать и воткнуть на стадии производства не выйдет, только кустарно, вручную. Но надо ли оно серийно? Обычно нет. Так что вот! Пользуйтесь :)]]></description>
				<content:encoded><![CDATA[<p>Проектируя плату я всегда оставляю множество контрольных точек.  Это удобно для отладки, а денег и места они почти не требуют. Всегда, вот всегда, я делал петлю для земли. Просто оставлял рядом два отверстия и вставлял туда проволочную петлю, чтобы удобно было цеплять крокодил осциллографа. Но в нее неудобно тыкать щупом, если надо просто измерить мультиметром напряжение от земли. </p>
<p><img src="https://easyelectronics.ru/img/tehnologia/testpins/pb2.jpg"></p>
<p>Для сигналов можно оставить просто луженую переходку, вскрытую от маски. Или даже отдельный пустой пад. В него удобно тыкать иголкой мультиметра или осциллографа, отверстие в площадке не даст щупу соскользнуть. Но в нее не зацепишь крокодил. </p>
<p>Более того, промышленность выпускает множество видов разных тестпадов, которые можно заказать и установить на этапе производства. Есть как простые петельки с цветной бусинкой</p>
<p><img src="https://easyelectronics.ru/img/tehnologia/testpins/testpinloop.jpg"><br />
<span id="more-2012"></span></p>
<p>Так и более фельдиперсовые, вроде таких вот, точененьких штуковин:</p>
<p><img src="https://easyelectronics.ru/img/tehnologia/testpins/testpin.jpg"></p>
<p>или даже двухэтажные</p>
<p><img src="https://easyelectronics.ru/img/tehnologia/testpins/testpin2.jpg"></p>
<p>Они очень красиво выглядят, в них удобно цепляться крокодилом или крючком. Но вот совершенно неудобно удерживать на них мультиметр. </p>
<p>А недавно я, совершенно случайно, обнаружил совершенно чумовой и офигенный теспад, обладающий всеми достоинствами сразу. Это&#8230; </p>
<p>Выдранный из обоймы пин от цанговой линейки SIP или из цанговой панельки. </p>
<p><img src="https://easyelectronics.ru/img/tehnologia/testpins/sip.jpg"></p>
<p>Чуть нагреть обойму феном или зажигалкой и достаются вот такие красавцы:</p>
<p><img src="https://easyelectronics.ru/img/tehnologia/testpins/pin.jpg"></p>
<p>Это просто чума. Ее идеально цепляет крокодил, за нее идеально, просто намертво, хватается пружинный крюк осциллографа. В отверстие сверху отлично втыкается игольчатый щуп. Более того!!! В нее можно воткнуть проволочку и куда-нибудь засадить, и она никуда не денется, т.к. там внутри цанга!  Шикааааарно!</p>
<p><img src="https://easyelectronics.ru/img/tehnologia/testpins/onplate.jpg"></p>
<p>Единственный минус &#8212; серийно заказать и воткнуть на стадии производства не выйдет, только кустарно, вручную.  Но надо ли оно серийно? Обычно нет. Так что вот! Пользуйтесь :) </p>
]]></content:encoded>
			<wfw:commentRss>http://easyelectronics.ru/testovye-piny.html/feed</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>Консольный интерфейс в микроконтроллере</title>
		<link>http://easyelectronics.ru/konsolnyj-interfejs-v-mikrokontrollere.html</link>
		<comments>http://easyelectronics.ru/konsolnyj-interfejs-v-mikrokontrollere.html#comments</comments>
		<pubDate>Thu, 16 Feb 2023 18:29:31 +0000</pubDate>
		<dc:creator><![CDATA[DI HALT]]></dc:creator>
				<category><![CDATA[ARM. Учебный курс]]></category>
		<category><![CDATA[AVR. Учебный курс]]></category>
		<category><![CDATA[I/O]]></category>
		<category><![CDATA[UART]]></category>
		<category><![CDATA[USART]]></category>
		<category><![CDATA[Библиотека]]></category>
		<category><![CDATA[Интерфейс]]></category>
		<category><![CDATA[Начинающим]]></category>
		<category><![CDATA[Терминал]]></category>

		<guid isPermaLink="false">http://easyelectronics.ru/?p=2008</guid>
		<description><![CDATA[Проектируя сложное устройство часто требуется како-нибудь отладочный терминал, чтобы можно было легко заглянуть в недра контроллера, выполнять команды, посмотреть логику. Вмешаться в ход событий, что-то запустить. Да банально логи посмотреть и ошибки считать. Обычно для этого применяют UART и терминальный доступ в текстовом режиме. А для простоты и универсальности весь интерфейс реализуют прям в самом контроллере. Благо ресурсов это много не требует. Задача столь частая, что существуют хорошие готовые библиотеки терминальных систем для контроллеров. Одну из них я давно применяю и хочу познакомить читателей с ней. Написана на Си и с большим количеством макросов. Состоит из трех файлов. Умеет автодополнение, контроль параметров, самодокументирущуся справку (!!!), историю. Общение может выглядеть так: > help Available commands: help - List all commands, or give details about a specific command gpio_set - Sets a GPIO pin gpio_get - Gets a GPIO pin value > help gpio_get Gets a GPIO pin value Usage: gpio_get port pin port - The port pin - The pin > gpio_set A 1 1 > gpio_get A 1 value=1 > gpio_set A 1 0 > gpio_get A 1 value=0 Ну обо всем по порядку. Установка консоли в проект довольно проста, но имеет ряд нюансов. Надо подключить в проект файлы: console.c console.h console_config.h console_private_helpers.h Библиотека требует наличие типа bool, для этого можно либо подключить stdbool.h или пробежаться по файлам библиотеки и заменить bool на _Bool. Мне пришлось так сделать, т.к. на GD32, в его версии SPL, булевый тип криво прибит гвоздями в хидерах библиотеки и возникает гемор ненужный с переопределением типов. Также я создал отдельный файл для конфиугурации, собственно, консоли, где прописал команды и всякие функции myconsole.h myconsole.с Идем в console_config.h и настраиваем параметры: #pragma once // See the README for documentation on these options. // Максимальное число команд. Потом правите под свои нужды. #ifndef CONSOLE_MAX_COMMANDS #define CONSOLE_MAX_COMMANDS 16 #endif // Максимальная длина командной строки, ака самая длинная команда которую можно ввести (со всеми параметрами) #ifndef CONSOLE_MAX_LINE_LENGTH #define CONSOLE_MAX_LINE_LENGTH 32 #endif // Вид строки приглашения. Тут на свой вкус у меня это PB II> #ifndef CONSOLE_PROMPT #define CONSOLE_PROMPT "PB II>" #endif // Настроки расположения буфера. Это для компоновщика GCC я не вникал и оставил по дефолту. #ifndef CONSOLE_BUFFER_ATTRIBUTES #define CONSOLE_BUFFER_ATTRIBUTES #endif // Нужна ли встроенная справка. #ifndef CONSOLE_HELP_COMMAND #define CONSOLE_HELP_COMMAND 1 #endif // Давать ли полное управление. Например, можно ли стирать символы, т.е. править команду. Я включаю. #ifndef CONSOLE_FULL_CONTROL #define CONSOLE_FULL_CONTROL 1 #endif // Включать ли автодополнение. Штука удобная, хотя работает немного кривовато. Часто не дополняет (визуально), но при этом работает #ifndef CONSOLE_TAB_COMPLETE #define CONSOLE_TAB_COMPLETE 0 #endif #if CONSOLE_TAB_COMPLETE &#038;& !CONSOLE_FULL_CONTROL #error "CONSOLE_TAB_COMPLETE requires CONSOLE_FULL_CONTROL to be enabled" #endif // История ввода. Я выключаю, чтобы не забивать память. #ifndef CONSOLE_HISTORY #define CONSOLE_HISTORY 0 #endif #if CONSOLE_HISTORY &#038;& !CONSOLE_FULL_CONTROL #error "CONSOLE_HISTORY requires CONSOLE_FULL_CONTROL to be enabled" #endif С этим файлом разобрались. Теперь надо настроить ввод-вывод. В файле myconsole.с я прописываю обработчики и задачи. void console_write_function(const char* str) { xprintf(str); } Это функция вывода. Я использую xprintf от ElmChan, очень маленькая и быстрая библиотека, в отличии от той, что в stdio.h даже по сравнению с newlib-nano и не трахает мозги буфферизацией. Приколы с буфферизацией стандартной библиотеки вывода я наверное отдельной статьей распишу. Заодно и про xprintf расскажу. В данном случае не важно, что за функция вывода используется. Главное она должна получать ASCIIZ строку (т.е. строку с 0х00 в конце) и выводить ее в UART. Поскольку у меня FreeRTOS, то я запускаю консоль в отдельной задаче: xTaskCreate(ConsoleTask,"ConsoleTask", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); А сама задача выглядит так: void ConsoleTask (void *pvParameters) { uint8_t Buffer[64]; uint32_t len; while(1) { len = USART1_Receive(0x0D,Buffer,sizeof(Buffer),portMAX_DELAY); // принимаем строку оканчивающуюся на 0x0D и portMAX_DELAY - т.е. ждем до талого. console_process(Buffer, len); } } И вот тут надо развернуть ряд нюансов. Для работы консоли ей надо просто скормить буфер который содержит введенную команду с 0x0A (LF он же \n) байтом на конце. И длину строки. А, например, PuTTy в конце строки отдает не LF, а CR (0x0D). Поэтому я написал специализированный обработчик данных из UART который принимает строку ограничивающуюся на 0x0D, а потом подменяет его на 0x0A. uint32_t USART1_Receive(uint8_t chr, uint8_t *Buffer, uint32_t Length, TickType_t TimesUp) { uint8_t Receive; uint32_t i=0; while(1) { if(pdTRUE!=xQueueReceive(USART1_In,&#038;Receive,TimesUp)) return 0; if(Receive == chr) { Buffer[i++] = 0x0A; //LF OLD = chr return i; } if(iled; uint8_t value = args->value; // Поскольку консоль никак не проверят валидность значений это нам обязательно надо делать // На входе. Светодиодов у нас 3, поэтому проверяем, чтобы не больше 3. А значение может быть либо // 0 либо 1, поэтому не больше 1. if( led>3 &#124;&#124; value>1 ) { xprintf("What is this shit?\n"); return; } switch(led) { case 0: IO_SetLine(io_LED0,value); break; case 1: IO_SetLine(io_LED1,value); break; case 2: IO_SetLine(io_LED2,value); break; case 3: IO_SetLine(io_LED3,value); break; default: break; } } Для строковых параметров все похожим образом. Вообще передаваемая командная строка args просто содержит массив, где параметры лежат рядочком друг за другом. И его разбирают структурой. static void get_command_handler(const get_args_t* args) { uint8_t i=0; // Сначала пересчитаем сколько у нас символов в имени порта. Т.к. имя порта передатся строковой переменной // оканчивающейся на 0х00 while(args->port[i] !='\0') { i++; } // Если всего один символ, значит это скорей то что нужно. Разбираем его через SWITCH if(i==1) { switch (args->port[0]) { case 'A': case 'a': { if (args->pin < 16 ) // Второй параметр проверяем на максимальное значение. { // Так как пинов у порта всего 16. И выводим значение в консоль. xprintf("GPIO A.%d = %d \n",args->pin,(GPIOA->IDR &#038; 1pin < 16 ) { xprintf("GPIO B.%d = %d \n",args->pin,(GPIOB->IDR &#038; 1pin &#038;& args->pin < 16 ) // У STM32F103C8T6 порт С не полный { xprintf("GPIO C.%d = %d \n",args->pin,(GPIOB->IDR &#038; 1]]></description>
				<content:encoded><![CDATA[<p>Проектируя сложное устройство часто требуется како-нибудь отладочный терминал, чтобы можно было легко заглянуть в недра контроллера, выполнять команды, посмотреть логику. Вмешаться в ход событий, что-то запустить. Да банально логи посмотреть и ошибки считать. </p>
<p>Обычно для этого применяют  UART и терминальный доступ в текстовом режиме. А для простоты и универсальности весь интерфейс реализуют прям в самом контроллере. Благо ресурсов это много не требует. Задача столь частая, что существуют хорошие готовые библиотеки терминальных систем для контроллеров. <a href="https://github.com/rideskip/anchor/tree/master/console">Одну из них я давно применяю и хочу познакомить читателей с ней</a>. </p>
<p>Написана на Си и с большим количеством макросов. Состоит из трех файлов. Умеет автодополнение, контроль параметров, самодокументирущуся справку (!!!), историю. </p>
<p>Общение может выглядеть так:</p>
<pre lang="C" line="1">> help
Available commands:
  help     - List all commands, or give details about a specific command
  gpio_set - Sets a GPIO pin
  gpio_get - Gets a GPIO pin value
> help gpio_get
Gets a GPIO pin value
Usage: gpio_get port pin
  port - The port <A,B,C>
  pin  - The pin <0-15>
> gpio_set A 1 1
> gpio_get A 1
value=1
> gpio_set A 1 0
> gpio_get A 1
value=0</pre>
<p><span id="more-2008"></span></p>
<p>Ну обо всем по порядку. Установка консоли в проект довольно проста, но имеет ряд нюансов. Надо подключить в проект файлы:</p>
<ul>
<li> console.c </li>
<li> console.h </li>
<li> console_config.h </li>
<li> console_private_helpers.h </li>
</ul>
<p>Библиотека требует наличие типа bool, для этого можно либо подключить stdbool.h или пробежаться по файлам библиотеки и заменить bool на _Bool. Мне пришлось так сделать, т.к. на GD32, в его версии SPL,  булевый тип криво прибит гвоздями в хидерах библиотеки и возникает гемор ненужный с переопределением типов. </p>
<p>Также я создал отдельный файл для конфиугурации, собственно, консоли, где прописал команды и всякие функции</p>
<ul>
<li> myconsole.h  </li>
<li> myconsole.с </li>
</ul>
<p>Идем в console_config.h и настраиваем параметры:</p>
<pre lang="C" line="1">
#pragma once

// See the README for documentation on these options.

// Максимальное число команд. Потом правите под свои нужды. 
#ifndef CONSOLE_MAX_COMMANDS
#define CONSOLE_MAX_COMMANDS 16
#endif

// Максимальная длина командной строки, ака самая длинная команда которую можно ввести (со всеми параметрами)
#ifndef CONSOLE_MAX_LINE_LENGTH
#define CONSOLE_MAX_LINE_LENGTH 32
#endif

// Вид строки приглашения. Тут на свой вкус у меня это PB II>
#ifndef CONSOLE_PROMPT
#define CONSOLE_PROMPT "PB II>"
#endif

// Настроки расположения буфера. Это для компоновщика GCC я не вникал и оставил по дефолту. 
#ifndef CONSOLE_BUFFER_ATTRIBUTES
#define CONSOLE_BUFFER_ATTRIBUTES
#endif

// Нужна ли встроенная справка. 
#ifndef CONSOLE_HELP_COMMAND
#define CONSOLE_HELP_COMMAND 1
#endif

// Давать ли полное управление. Например, можно ли стирать символы, т.е. править команду. Я включаю. 
#ifndef CONSOLE_FULL_CONTROL
#define CONSOLE_FULL_CONTROL 1
#endif

// Включать ли автодополнение. Штука удобная, хотя работает немного кривовато. Часто не дополняет (визуально), но при этом работает 
#ifndef CONSOLE_TAB_COMPLETE
#define CONSOLE_TAB_COMPLETE 0
#endif

#if CONSOLE_TAB_COMPLETE && !CONSOLE_FULL_CONTROL
#error "CONSOLE_TAB_COMPLETE requires CONSOLE_FULL_CONTROL to be enabled"
#endif

// История ввода. Я выключаю, чтобы не забивать память. 
#ifndef CONSOLE_HISTORY
#define CONSOLE_HISTORY 0
#endif

#if CONSOLE_HISTORY && !CONSOLE_FULL_CONTROL
#error "CONSOLE_HISTORY requires CONSOLE_FULL_CONTROL to be enabled"
#endif
</pre>
<p>С этим файлом разобрались. Теперь надо настроить ввод-вывод. В файле myconsole.с я прописываю обработчики и задачи.</p>
<pre lang="C" line="1">
void console_write_function(const char* str)
{
    xprintf(str);
}
</pre>
<p>Это функция вывода. Я использую xprintf от ElmChan, очень маленькая и быстрая библиотека, в отличии от той, что в stdio.h даже по сравнению с newlib-nano и не трахает мозги буфферизацией. Приколы с буфферизацией стандартной библиотеки вывода я наверное отдельной статьей распишу. Заодно и про xprintf расскажу. В данном случае не важно, что за функция вывода используется. Главное она должна получать ASCIIZ строку (т.е. строку с 0х00 в конце) и выводить ее в UART.</p>
<p>Поскольку у меня FreeRTOS, то я запускаю консоль в отдельной задаче:</p>
<pre lang="C" line="1">
xTaskCreate(ConsoleTask,"ConsoleTask", 	configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL);
</pre>
<p>А сама задача выглядит так:</p>
<pre lang="C" line="1">
void ConsoleTask (void *pvParameters)
{
uint8_t Buffer[64];
uint32_t len;

    while(1)
    {
        len = USART1_Receive(0x0D,Buffer,sizeof(Buffer),portMAX_DELAY); // принимаем строку оканчивающуюся на 0x0D и portMAX_DELAY - т.е. ждем до талого. 
        console_process(Buffer, len);
    }
}
</pre>
<p>И вот тут надо развернуть ряд нюансов. Для работы консоли ей надо просто скормить буфер который содержит введенную команду с 0x0A (LF он же \n) байтом на конце. И длину строки. </p>
<p>А, например, PuTTy в конце строки отдает не LF, а CR (0x0D). </p>
<p>Поэтому я написал специализированный обработчик данных из UART который принимает строку ограничивающуюся на 0x0D, а потом подменяет его на 0x0A.</p>
<pre lang="C" line="1">
uint32_t USART1_Receive(uint8_t chr, uint8_t *Buffer, uint32_t Length, TickType_t TimesUp)
{
uint8_t Receive;
uint32_t i=0;

    while(1)
    {
        if(pdTRUE!=xQueueReceive(USART1_In,&Receive,TimesUp)) return 0;

        if(Receive == chr)
        {
            Buffer[i++] = 0x0A; //LF  OLD = chr
            return i;
        }

        if(i<Length)
        {
            Buffer[i++]=Receive;
        }
        else
        {
            return i;
        }
    }
}
</pre>
<p>chr - символ который надо ждать и по которому мы отбиваем ввод. Это позволяет ждать сколько угодно. Сам ввод сделан на очередях FreeRTOS, т.е. в прерывании USART данные приходящие пихаются в очередь. А ожидающая задача на другом конце просыпается и по одному байту их зажевывает. УАРТ штука медленная, поэтому всякие DMA с кольцевыми буферами тут, ИМХО, излишняя заморочка. Удобней очередь, т.к. она сама разблокируется, сама передаст.  Ну так вот, в выше указанной функции мы ждем конец строки, что для PuTTy  0x0D, либо до заполнения буфера, а когда встречаем 0x0D подменяем его на 0x0A и уже это отдаем консоли. </p>
<p>Сама инициализация консоли при этом выглядит следующим образом:</p>
<pre lang="C" line="1">
void MyConsole_Setup(void)
{

// Прописываем функцию вывода. Обратите, кстати, на обращение к полю структуры без имени. Сразу через точку :) 
const console_init_t init_console = {
        .write_function = console_write_function,
    };

//Инициализируем
    console_init(&init_console);

//Регистрируем команды. В данном случае это команды hello, led, get
    console_command_register(hello);
    console_command_register(led);
    console_command_register(get);

// Запускаем задачу консоли. Размер стэка должен вместить её буфер!!!  В системах на конечном автомате можно же просто 
// периодически запускать консольную службу, да  хоть по таймеру

    xTaskCreate(ConsoleTask,"ConsoleTask", 	configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL);
}

</pre>
<p>Теперь о создании самой задачи и ее хэндле. Задача описывается регистрирующим макросом и самой функцией:</p>
<p>Макрос:</p>
<pre lang="C" line="1">

CONSOLE_COMMAND_DEF(hello, "Say hello!");

CONSOLE_COMMAND_DEF(led, "Sets a LED No",
    CONSOLE_INT_ARG_DEF(led, "The LED 0...3"),
    CONSOLE_INT_ARG_DEF(value, "The value 0-1")
);

CONSOLE_COMMAND_DEF(get, "Gets a GPIO pin value",
    CONSOLE_STR_ARG_DEF(port, "The port <A,B,C>"),
    CONSOLE_INT_ARG_DEF(pin, "The pin <0-15>")
);

</pre>
<p>Обратите внимание на то, что тут сразу прописывается имя команды, а также подсказка которая будет выводиться для системе в целом (help) . Как в общей подсказке ,так и индивидуальная подсказка по каждой команде (например help get )!!! </p>
<p>Существует два типа параметров. Строковые и численные. Строковый это </p>
<ul>
<li> CONSOLE_STR_ARG_DEF  позволяет засунуть в себя строку, которую ограничит нулем. Не забывайте про размер буфера (CONSOLE_MAX_LINE_LENGTH) который прописан в настройках. </li>
<li> CONSOLE_INT_ARG_DEF  целочисленное значение в виде long числа. </li>
</ul>
<p>Сколько надо параметров столько и прописываете в эти макросы. </p>
<p>Потом надо сделать обработчики:</p>
<pre lang="C" line="1">
static void hello_command_handler(const hello_args_t* args)
{
    xprintf("Hi!\n");
}
</pre>
<p>Для команды без параметров все просто. Она выполняется когда попадает в консоль. Обратите внимание на то, что имя тут параметры тут содержат название команды.  </p>
<p>static void ####_command_handler(const ####_args_t* args)</p>
<p>Если у команды есть цифровые параметры, то их можно достать через поля структуры которая намазана на буффер ввода:</p>
<pre lang="C" line="1">
static void led_command_handler(const led_args_t* args)
{

// Берем значение из строки ввода команды, которую нам передают сюда. 
uint8_t led = args->led;
uint8_t value = args->value;

// Поскольку консоль никак не проверят валидность значений это нам обязательно надо делать 
// На входе. Светодиодов у нас 3, поэтому проверяем, чтобы не больше 3. А значение может быть либо 
// 0 либо 1, поэтому не больше 1. 
    if( led>3 || value>1 )
    {
        xprintf("What is this shit?\n");
        return;
    }

    switch(led)
    {
        case 0: IO_SetLine(io_LED0,value); break;
        case 1: IO_SetLine(io_LED1,value); break;
        case 2: IO_SetLine(io_LED2,value); break;
        case 3: IO_SetLine(io_LED3,value); break;
        default: break;
    }
}
</pre>
<p>Для строковых параметров все похожим образом. Вообще передаваемая командная строка args  просто содержит массив, где параметры лежат рядочком друг за другом. И его разбирают структурой. </p>
<pre lang="C" line="1">
static void get_command_handler(const get_args_t* args)
{
uint8_t i=0;

// Сначала пересчитаем сколько у нас символов в имени порта. Т.к. имя порта передатся строковой переменной
// оканчивающейся на 0х00
    while(args->port[i] !='\0')
    {
        i++;
    }

// Если всего один символ, значит это скорей то что нужно. Разбираем его через SWITCH
    if(i==1)
    {
       switch (args->port[0])
       {
            case 'A':
            case 'a':
            {
                if (args->pin < 16 ) // Второй параметр проверяем на максимальное значение. 
                {                           // Так как пинов у порта всего 16. И выводим значение в консоль. 
                    xprintf("GPIO A.%d = %d \n",args->pin,(GPIOA->IDR & 1<<(args->pin)) ? 1:0  );
                }
                else xprintf("Error: Wrong pin!\n");
                break;
            }
            case 'B':
            case 'b':
            {
                if (args->pin < 16 )
                {
                    xprintf("GPIO B.%d = %d \n",args->pin,(GPIOB->IDR & 1<<(args->pin)) ? 1:0  );
                }
                else xprintf("Error: Wrong pin!\n");
                break;
            }
            case 'C':
            case 'c':
            {
                if (12 < args->pin && args->pin < 16 )  // У STM32F103C8T6 порт С не полный
                {
                    xprintf("GPIO C.%d = %d \n",args->pin,(GPIOB->IDR & 1<<(args->pin)) ? 1:0  );
                }
                else xprintf("Error: Wrong pin!\n");
                break;
            }
            default: xprintf("Bullshit Port!\n");
       }
    }
    else
    {
        xprintf("To long name!\n");
    }
}

</pre>
<p>Программа готова. Осталось только сделать небольшую настройку PuTTy, чтобы она корректно отображала ввод и переносы:</p>
<p><img src="https://easyelectronics.ru/img/ARM_kurs/Console/putty.png"></p>
<p><a href="https://easyelectronics.ru/img/ARM_kurs/Console/STM32F103C8-console.zip"><b>Собранный проект под STM32F103C8T6</b></a> </p>
<p>И все! </p>
]]></content:encoded>
			<wfw:commentRss>http://easyelectronics.ru/konsolnyj-interfejs-v-mikrokontrollere.html/feed</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>Встроенные предохранители</title>
		<link>http://easyelectronics.ru/vstroennye-predoxraniteli.html</link>
		<comments>http://easyelectronics.ru/vstroennye-predoxraniteli.html#comments</comments>
		<pubDate>Sat, 28 Jan 2023 12:44:41 +0000</pubDate>
		<dc:creator><![CDATA[DI HALT]]></dc:creator>
				<category><![CDATA[Начинающим]]></category>

		<guid isPermaLink="false">http://easyelectronics.ru/?p=2003</guid>
		<description><![CDATA[Увидел забавное в интернете. Предоранитель из дорожки. А рядом &#171;запасные&#187; дорожки. Весьма упоротое решение, но тем не менее имеющиее право на жизнь. Я бы только добавил рядом еще пады для впайки обычного предохранителя. На всякий случай. Вообще в прототипах я так часто делал, но не в серийном изделии. З.Ы. Кстати, рядом видно дорожку вскрытую от маски. Если кто не знает, то так делают чтобы увеличить сечение дорожки. При лужении платы на нее ляжет припой и увеличит толщину проводника. Правда мне это решение не нравится, прирост сечения небольшой, а проблем от такого вскрытия куда больше. Это и отслоения маски могут быть и вообще замыкания по конденсату и прочие неприятные вещи. Лучше уж взять фольгу потолще или слой лишний добавить и развести дорогу еще и в нем.]]></description>
				<content:encoded><![CDATA[<p>Увидел забавное в интернете. Предоранитель из дорожки. А рядом &#171;запасные&#187; дорожки. Весьма упоротое решение, но тем не менее имеющиее право на жизнь. Я бы только добавил рядом еще пады для впайки обычного предохранителя. На всякий случай. </p>
<p><img src="https://easyelectronics.ru/img/Misc/pcb_fuse.jpg" alt="" /></p>
<p>Вообще в прототипах я так часто делал, но не в серийном изделии. </p>
<p>З.Ы.<br />
Кстати, рядом видно дорожку вскрытую от маски. Если кто не знает, то так делают чтобы увеличить сечение дорожки. При лужении платы на нее ляжет припой и увеличит толщину проводника. Правда мне это решение не нравится, прирост сечения небольшой, а проблем от такого вскрытия куда больше. Это и отслоения маски могут быть и вообще замыкания по конденсату и прочие неприятные вещи. Лучше уж взять фольгу потолще или слой лишний добавить и развести дорогу еще и в нем. </p>
]]></content:encoded>
			<wfw:commentRss>http://easyelectronics.ru/vstroennye-predoxraniteli.html/feed</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
		<item>
		<title>Производство печатных плат в NextPCB с доставкой и оплатой из России.</title>
		<link>http://easyelectronics.ru/proizvodstvo-pechatnyx-plat-v-nextpcb-s-dostavkoj-i-oplatoj-iz-rossii.html</link>
		<comments>http://easyelectronics.ru/proizvodstvo-pechatnyx-plat-v-nextpcb-s-dostavkoj-i-oplatoj-iz-rossii.html#comments</comments>
		<pubDate>Fri, 13 Jan 2023 14:05:15 +0000</pubDate>
		<dc:creator><![CDATA[DI HALT]]></dc:creator>
				<category><![CDATA[Новости]]></category>

		<guid isPermaLink="false">http://easyelectronics.ru/?p=1988</guid>
		<description><![CDATA[Постучались мне недавно товарищи из NextPcb, мол у нас клевая фабрика. А можете вы про нас рассказать? Рассказать, то я могу. Только вот без тестового образца, чтобы оценить сам процесс заказа-доставки, качества и прочего я ничего естественно писать не буду, ни за какие деньги. Сделайте мне небольшой тестовый заказ, а там видно будет стоит про вас что-то говорить, али нет. Ударили по рукам и я им скинул на изготовление один небольшой коммерческий заказик. Простенькая плата, ну очень простенькая. Просто заказывать бесполезную фиговину не хотелось, а из того, что было нужно только это. Но, в целом, оценить продукцию можно и по ней. ▌Заказ Идем на https://www.nextpcb.com, регистрируемся, как водится. Вверху линейка жизненного цикла заказа. Которая, почему то, не работает. Только для красоты. Ну да ладно, кнопочку Quote Now в правом верхнем углу трудно не заметить. Тут есть выбор что можно заказать. Плату (PCB Quote), Сборку(PCB Assembly Quote), Трафарет(PCB Stencil Quote), Комплектуху по списку (BOM Service). Для начала я закажу просто плату. Посмотреть как процессы работают в принципе. Жмем кнопочку Upload File и скармливаем системе архив гербер файлов. У меня они сразу выгружены из KiCad как есть, я даже не переименовывал их никак. Система сразу же подгружает их сама, правильно распознавая слои. Если слои не распознаются, то возможно стоит сменить тип архиватора, либо переименовать слои очевидным образом, чтобы в именах файлов слоев были ключевые слова их принадлежности вида top_сopeer, bottom_copper, top_silk, drill, outline и т. д. В любом случае плату потом будет проверять человек и если что он задаст уточняющие вопросы по почте. Неведомая фигня вам не придет в любом случае. А параметры заказа, если он не определится автоматически можно указать ниже вручную. Правда картинка обрезана немного, что неудобно. Приходится жать кнопку Gerber View. Можно поглядеть плату с одной и другой стороны. Сразу будет видно косяки неправильного определения слоев, если они есть. Заодно себя проверите, не попутали ли где ориентацию шелкухи. А то, бывает, протупишь, бомбанешь какой-нибудь логотип или надпись зеркально, а потом сидишь и обтекаешь :) Тут такое сразу будет видно на стадии заказа еще. Справа немного статистики платы. Количество и диаметр отверстий, минимальная дорожка/зазор, процент платы под позолоту&#8230; Мне прям мне понравилось как сделано у них превьюха. Сразу же плата загрузилась, ее не приходится отдельно подгружать как в PcbWay и при этом есть история всех заказов, где можно сразу же посмотреть что было заказано раньше и дозаказать. Не пытаясь разглядеть на крохотной превьюшке та это плата или нет как в JLCPCB. Когда постоянно заказываешь разную разносортицу, повторяя предыдущие заказы, это удобно. Пожалуй смотрелка в заказе у них лучшая из всех китайских сайтов которые видел. Так как интегрирована как в заказ, так и в историю одновременно. Идем дальше, закрываем смотрелку и смотрим что ниже. Там поля где можно добавить номер партии Ниже, в разделе Basic informations идут параметры платы. Обычно они определяются автоматически по гербер файлу, а что то из них выставится по умолчанию. Часто я их даже не меняю. Material Type &#8212; из чего делать плату. На выбор тут только текстолит. Layer Count &#8212; количество слоев. Определяется само. Но если гербер файл не распознался, то ничего страшного. Можно выставить вручную. Все равно файл обрабатывает человек после заказа. Board Type &#8212; в каком виде вам давать платы. Single piece &#8212; поштучно, в пакетике. Panel by Customer &#8212; в виде панели с пропилами для легкого разлома (скрайбирование). Размер панели по усмотрению пользователя. Можно указать ниже размер панели. Он выбирается под ваше оборудования для монтажа. Panel by NextPcb &#8212; эта опция нужна если вы там же заказываете сборку. В этом случае контора сама выберет удобную им панель. Есть небольшая подсказочка о том как считать размер платы-панели. Break-away Rail &#8212; оставлять ли с краев платы отламываемые рельсы. Например, у вас есть много круглых плат. Если вы их соберете в панель, то такая пластина не будет иметь ровных краев и в вашем расстановщике будет лежать криво. Добавление этой опции позволяет оставить края, которые потом можно будет отломить. Указывается с какой стороны добавить рельсы. Слева-справа, сверху &#8212; снизу, или со всех четырех сторон. Qty(single) &#8212; количество одиночных плат в заказе. Не панелей, именно плат. Как их соберут в панель зависит уже от плат, тут на усмотрение производителя. PCB Thickness &#8212; толщина текстолита. Зависит от необходимости, самая стандартная дешевле всего. Т.к. ваш заказ бомбанут на огромную сборную пластину с другими и будет дешевле. PCB Solder Mask Color &#8212; цвет маски. Я сам люблю разноцветные веселые платы, но нестандартный, не зеленый, цвет обходится заметно дороже. Кстати, знаете почему зеленый это стандарт и самый массовый цвет? А потому, что маска засвечивается ультрафиолетом и зеленый краситель меньше всего мешает ее отверждать. Поэтому зеленые платы дешевле выходят. Конечно при большом заказе разница в цене ничтожная. Но тем не менее. Silkscreen &#8212; цвет шелкухи. Черный или белый. Для всех цветов маски, кроме белого, он белый. Для белого черный. Вы даете выбирать красков? Нет, только смотреть. Кросивое! :) PCB Parameters Определяют каковы параметры платы. В плане толщин дорожек, зазоров и прочего. Это все определяет класс, а значит и стоимость платы. Finished Copper Weight &#8212; толщина меди. Указывается как масса в oz на квадратный метр. Min. Trace / Space Outer &#8212; минимальная дорожка/зазор. Min. Drilled Hole &#8212; минимальное отверстие. Как видим, тут технологический предел указан как 0.2мм. Via Process &#8212; какими должны быть переходные отверстия. И вот тут есть интересные опции. Помимо классических переходных отверстий, открытых и накрытых маской &#8212; Tenting (название идет от того, что переходки закрываются маской как тентом), есть еще два варианта. Первый это ink via plug т.е. отверстие забивается краской, а потом накрывается маской. Что дает увеличение надежности, т.к. тент из маски может прорваться, обнажив переходное отверстие. Куда начнет попадать влага, грязь и может привести к коррозии переходного отверстия. И есть еще epoxy via plug &#8212; когда переходное отверстие заполняется эпоксидкой, а поверх кладется маска. Что вообще превращает плату в монолит. Разумеется это дорого и требует дополнительных нескольких дней на производство. И, честно говоря, я даже не представляю зачем это может потребоваться, но такая возможность есть. ▌Surface Finish Финишное покрытие открытой меди. HASL это обычное лужение свинцовым припоем. Из плюсов его &#8212; дешевое, ремонтопригодное. Минусы тоже есть. Главный минус в том, что HASL наносится как бы волной, со сдуванием припоя воздушным лезвием. Это вызывает термоудар, что может привести к внутренним разрывам переходных отверстий в сложных платах. Вторая проблема этого типа &#8212; слой образуется толстый, как бы с горкой, неровный, что может вызывать сложности при монтаже, сползание деталей с мелким шагом в промежуток между падами. Еще такое покрытие уменьшает размер посадочных отверстий, на толщину слоя. Впрочем, производитель может учесть этот момент. LEAD FREE HASL &#8212; то же самое, только используется бессвинцовый припой. Подозреваю еще больший термоудар при нанесении, т.к. температура у такого припоя выше. Но это не точно. ENIG &#8212; это покрытие никелем с золотом. Тут хитрая двухслойная структура образуется. Вначале осаждается химическим способом никель, а чтобы он не окислялся его защищают золотом. Плюсы такого покрытия &#8212; идеальная геометрия площадок. А минусы в цене, невысокой механической прочности покрытия (для ножевых разъемов критично), а также в том ,что при хранении могут образовываться интерметаллидных сплавов и легирования золота с медью через слой никеля. С образованием черных площадок. Которые не лудятся и при монтаже дают брак. Также такую площадку тяжело переделать. Т.к. тончайший слой растворяется в припое и в дальнейшем ее не восстановить до заводского состояния. OSP &#8212; это покрытие спец лаком. Тончайшая пленочка, которая защищает медь от окисления, а при монтаже от температуры смешивается с флюсом и испаряется. Из плюсов тут дешевизна, идеальная геометрия поверхности площадки. Нет термоудара при нанесении. Но минусы тоже присутствуют. В первую очередь требуется другие флюсы для пайки, чтобы были совместимы с данным типом покрытия, само покрытие боится царапин и вообще механического воздействия. Обращаться с платами надо осторожней, не лапать руками и прочие предосторожности. Покрытие препятствует электроконтролю и требуются более мощные щупы, способные его проколоть, что дает существенную механическую нагрузку на плату, при проверке, особенно если речь идет о микроскопических дорожках при сверхплотном монтаже. ▌Special Process Специальные фичи. HDI &#8212; слепые переходные отверстия. В многослойных платах могут быть переходные не выходящие наружу, расположенные во внутренних слоях. Это сложней технологически, их приходится делать на промежуточных этапах производства платы, что удорожает плату. Но зато позволяет экономить место на других слоях. Impendance &#8212; дополнительный контроль волновых сопротивлений цепей. Очень критичный параметр для высокоскоростых цепей. Импенданс тут начинает зависеть от кучи вещей. А не только от геометрии дорожек. Банально чуть другой состав эпоксидной смолы, с другой проницаемостью и может поплыть вся схема, т.к. изменятся емкостные связи между дорожками. Эта опция позволяет включить контроль таких цепей на этапе производства, чтобы отсеять брак до того как он будет смонтирован. Beveling G/F &#8212; заточка ножевых разъемов, чтобы они легче вставлялись. В месте где образуется разъем делают фаски на плате. Plated Self hole/castellated hole &#8212; полукольцевые разъемы под пайку. Как на некоторых модулях. PAD Holes &#8212; пады с переходными отверстиями. Для плотного монтажа иногда приходится делать такое. А с некоторым корпусами (например BGA) иначе и невозможно. Но тут есть одна проблема, при пайке в это отверстие утечет припой и монтаж может быть некачественным. Поэтому при таком случае при производстве печатной платы эти отверстия заливают эпоксидкой после формирования. ▌Personalized Information Персональные предпочтения. Тут можно указать надо ли делать финальное утверждение гербер файлов (Approve working gerber), т.е. после сборки панели вам предоставят финальный файл на утверждение. Delivery report &#8212; требование отчетов по всем этапам процессов, а также нахождения брака в ходе процессов. Важно когда делаешь большую партию плат, чтобы увидеть что где-то большой процент брака и внести исправления в проект, чтобы убрать узкое место. Microsection analyse report &#8212; отчет о анализе среза. После изготовления многослойной печатной платы производится срез по слоям и анализируется как изготовились переходные отверстия, качество внутренних соединений металлизации и тд. Вот можно запросить эти данные. UL mark &#8212; метка сертификации о безопасности США. Все платы произведенные в NextPcb выполнены в соответствии со стандартами UL (пожаробезопасность и прочее) поэтому можно указать, что на плате должна быть такая маркировка. Нужно если планируется продавать товар на американском рынке. После всех этих манипуляций, где то автоматических, а где то выбираемых вручную, у вас в правой части страницы сформируется образ заказа. С ценой платы, ценой доставки и сроков. Срок изготовления, как по мне, очень большой :( Почти неделя. Есть фабрики которые стандартный, не срочный, заказ делают буквально за сутки-двое. А срочный чуть ли не за пару часов. После чего остается только выбрать доставку и нажать кнопку Add to Cart и приступить к оформлению заказа. После чего вы сразу попадаете в корзину, где ваш заказ ждет одобрения (Await to Review). Инженеры NextPCB просмотрят вашу плату на соответствие технических норм, а также на отсутствие косяков. Обычно это занимает около часа в рабочие дни (с понедельника по пятницу с 9:00 до 18:00 (GMT+8)). Что тоже новость. Впервые вижу такое ревью. Обычно это занимает максимум час, вне зависимости от времени суток. После чего плата будет доступна к оплате. Либо вам вышлют список причин по которым она ревью не прошла. Там будет прям на картинках показаны сомнительные места которые надо исправить. ▌Оплата Тут есть ряд тонкостей. Дело в том, что оплата через Paypal, визу и прочие западные системы для России уже не доступна. Но NextPCB работает с Алиэкспрессом и тот поддерживает карты МИР и Киви. Поэтому оплатить можно через Алиэкспресс. Напрямую такого способа нет, но если написать вашему менеджеру, то он скинет оплату через алик. Адрес почты менеджера найдете в правом нижнем углу. У меня это некий Finn, я и написал ему письмо следующего содержания: &#171;Hello. I need Aliexpress payment. How can I do it? Order number: P-E50848ASK3&#187; Ответом было: Перешел по линку, там была какая то левая цена. Явно не похожая на то что надо. Спросил что за фигня? Finn извинился, поблагодарил за обратную связь и прислал новую ссылку. Там цена была верной. Ошибся, бывает. Плюс комиссия али, около 5%. Оплатил через карту МИР и все. Платы пошли в производство и были изготовлены. Плата была отправлена 21 августа, а 20 сентября она уже пришла на почту в Челябинск. Ровно месяц. Вполне приемлемый срок для современного времени. Посмотрим что пришло&#8230; Качество отличное. Впрочем, я не сомневался в этом даже. Шелкография ровная, маска гладкая, красивая, в пады попадание безупречное. Тонкие линии шелкографии пропечатываются отлично. Линии, слева направо 0.1мм 0.2мм 1мм. Высота текста отражена в самом тексте. Ничего не слепляется, можно смело уменьшать шрифт маркировки платы до минимума. Все будет четко пропечатано. Резюмируя. NextPCB Удобный производитель, с приятным редактором, отличным качеством изготовления. Но долгие сроки ревью и производства. Тут день, там два-три, так и неделя набежит. А с учетом и так удлинившейся доставки это прям грустно.]]></description>
				<content:encoded><![CDATA[<p>Постучались мне недавно товарищи из NextPcb, мол у нас клевая фабрика. А можете вы про нас рассказать? Рассказать, то я могу. Только вот без тестового образца, чтобы оценить сам процесс заказа-доставки, качества и прочего я ничего естественно писать не буду, ни за какие деньги. Сделайте мне небольшой тестовый заказ, а там видно будет стоит про вас что-то говорить, али нет. Ударили по рукам  и я им скинул на изготовление один небольшой коммерческий заказик. Простенькая плата, ну очень простенькая. Просто заказывать бесполезную фиговину не хотелось, а из того, что было нужно только это. Но, в целом, оценить продукцию можно и по ней. </p>
<p><b>▌Заказ</b><br />
Идем на <a href="https://www.nextpcb.com/?code=easyelectronicsdo">https://www.nextpcb.com</a>, регистрируемся, как водится. </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/1.png"></p>
<p>Вверху линейка жизненного цикла заказа. Которая, почему то, не работает. Только для красоты. Ну да ладно, кнопочку Quote Now в правом верхнем углу трудно не заметить. </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/upload.png"></p>
<p>Тут есть выбор что можно заказать. Плату (<b>PCB Quote</b>), Сборку(<b>PCB Assembly Quote</b>), Трафарет(<b>PCB Stencil Quote</b>), Комплектуху по списку (<b>BOM Service</b>).</p>
<p>Для начала я закажу просто плату. Посмотреть как процессы работают в принципе.</p>
<p>Жмем кнопочку Upload File и скармливаем системе архив гербер файлов. У меня они сразу выгружены из KiCad как есть, я даже не переименовывал их никак. Система сразу же подгружает их сама, правильно распознавая слои. Если слои не распознаются, то возможно стоит сменить тип архиватора, либо переименовать слои очевидным образом, чтобы в  именах файлов слоев были ключевые слова их принадлежности  вида top_сopeer, bottom_copper, top_silk, drill, outline и т. д. В любом случае плату потом будет проверять человек и если что он задаст уточняющие вопросы по почте. Неведомая фигня вам не придет в любом случае.  А параметры заказа, если он не определится автоматически можно указать ниже вручную. </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/uploaded.png"></p>
<p>Правда картинка обрезана немного, что неудобно. Приходится жать кнопку Gerber View. </p>
<p>Можно поглядеть плату с одной и другой стороны. Сразу будет видно косяки неправильного определения слоев, если они есть. Заодно себя проверите, не попутали ли где ориентацию шелкухи. А то, бывает, протупишь, бомбанешь какой-нибудь логотип или надпись зеркально, а потом сидишь и обтекаешь :)  Тут такое сразу будет видно на стадии заказа еще. </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/GerbView.png"></p>
<p>Справа немного статистики платы. Количество и диаметр отверстий, минимальная дорожка/зазор, процент платы под позолоту&#8230; </p>
<p>Мне прям мне понравилось как сделано у них превьюха. Сразу же плата загрузилась, ее не приходится отдельно подгружать как в PcbWay и при этом есть история всех заказов, где можно сразу же посмотреть что было заказано раньше и дозаказать. Не пытаясь разглядеть на крохотной превьюшке та это плата или нет как в JLCPCB. Когда постоянно заказываешь разную разносортицу, повторяя предыдущие заказы, это удобно. Пожалуй смотрелка в заказе у них лучшая из всех китайских сайтов которые видел. Так как интегрирована как в заказ, так и в историю одновременно. </p>
<p><span id="more-1988"></span></p>
<p>Идем дальше, закрываем смотрелку и смотрим что ниже. Там поля где можно добавить номер партии </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/partyNo.png"></p>
<p>Ниже, в разделе <b>Basic informations</b> идут параметры платы. Обычно они определяются автоматически по гербер файлу, а что то из них выставится по умолчанию. Часто я их даже не меняю. </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/info1.png"></p>
<p><b>Material Type</b> &#8212; из чего делать плату. На выбор тут только текстолит.</p>
<p><b>Layer Count</b>  &#8212; количество слоев. Определяется само. Но если гербер файл не распознался, то ничего страшного. Можно выставить вручную. Все равно файл обрабатывает человек после заказа. </p>
<p><b>Board Type</b> &#8212; в каком виде вам давать платы. <b>Single piece</b> &#8212; поштучно, в пакетике. <b>Panel by Customer</b> &#8212; в виде панели с пропилами для легкого разлома (скрайбирование). Размер панели по усмотрению пользователя. Можно указать ниже размер панели. Он выбирается под ваше оборудования для монтажа. <b>Panel by NextPcb</b>  &#8212; эта опция нужна если вы там же заказываете сборку. В этом случае контора сама выберет удобную им панель. </p>
<p>Есть небольшая подсказочка о том как считать размер платы-панели. </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/popup.png"></p>
<p>Break-away Rail &#8212; оставлять ли с краев платы отламываемые рельсы. Например, у вас есть много круглых плат. Если вы их соберете в панель, то такая пластина не будет иметь ровных краев и в вашем расстановщике будет лежать криво. </p>
<p><img src="https://easyelectronics.ru/img/PCB/nextPCB/pcb-edge-and-waste-rail.jpg" alt="" /><br />
Добавление этой опции позволяет оставить края, которые потом можно будет отломить. </p>
<p>Указывается с какой стороны добавить рельсы. Слева-справа, сверху &#8212; снизу, или со всех четырех сторон. </p>
<p><b>Qty(single)</b> &#8212; количество одиночных плат в заказе. Не панелей, именно плат. Как их соберут в панель зависит уже от плат, тут на усмотрение производителя. </p>
<p><b>PCB Thickness</b> &#8212; толщина текстолита. Зависит от необходимости, самая стандартная дешевле всего. Т.к. ваш заказ бомбанут на огромную сборную пластину с другими и будет дешевле. </p>
<p><b>PCB Solder Mask Color</b> &#8212; цвет маски. Я сам люблю разноцветные веселые платы, но нестандартный, не зеленый, цвет обходится заметно дороже. Кстати, знаете почему зеленый это стандарт и самый массовый цвет? А потому, что маска засвечивается ультрафиолетом и зеленый краситель меньше всего мешает ее отверждать. Поэтому зеленые платы дешевле выходят. Конечно при большом заказе разница в цене ничтожная. Но тем не менее. </p>
<p><b>Silkscreen</b> &#8212; цвет шелкухи. Черный или белый. Для всех цветов маски, кроме белого, он белый. Для белого черный.  Вы даете выбирать красков? Нет, только смотреть. Кросивое! :) </p>
<p><b>PCB Parameters</b><br />
Определяют каковы параметры платы. В плане толщин дорожек, зазоров и прочего. Это все определяет класс, а значит и стоимость платы. </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/pcbparam.png"></p>
<p><b>Finished Copper Weight</b>  &#8212; толщина меди. Указывается как масса в oz на квадратный метр.<br />
<b>Min. Trace / Space Outer</b> &#8212; минимальная дорожка/зазор.<br />
<b>Min. Drilled Hole</b> &#8212; минимальное отверстие. Как видим, тут технологический предел указан как 0.2мм.<br />
<b>Via Process</b> &#8212; какими должны быть переходные отверстия. И вот тут есть интересные опции.  Помимо классических переходных отверстий, открытых и накрытых маской &#8212; Tenting (название идет от того, что переходки закрываются маской как тентом), есть еще два варианта. Первый это <b>ink via plug</b> т.е. отверстие забивается краской, а потом накрывается маской. Что дает увеличение надежности, т.к. тент из маски может прорваться, обнажив переходное отверстие. Куда начнет попадать влага, грязь и может привести к коррозии переходного отверстия.  И есть еще <b>epoxy via plug</b> &#8212; когда переходное отверстие заполняется эпоксидкой, а поверх кладется маска. Что вообще превращает плату в монолит. Разумеется это дорого и требует дополнительных нескольких дней на производство. И, честно говоря, я даже не представляю зачем это может потребоваться, но такая возможность есть. </p>
<p><b>▌Surface Finish </b><br />
Финишное покрытие открытой меди. <b>HASL</b> это обычное лужение свинцовым припоем. Из плюсов его &#8212; дешевое, ремонтопригодное. Минусы тоже есть. Главный минус в том, что <b>HASL</b> наносится как бы волной, со сдуванием припоя воздушным лезвием. Это вызывает термоудар, что может привести к внутренним разрывам переходных отверстий в сложных платах.  Вторая проблема этого типа &#8212; слой образуется толстый, как бы с горкой, неровный, что может вызывать сложности при монтаже, сползание деталей с мелким шагом в промежуток между падами. Еще такое покрытие уменьшает размер посадочных отверстий, на толщину слоя. Впрочем, производитель может учесть этот момент. </p>
<p><b>LEAD FREE HASL</b> &#8212; то же самое, только используется бессвинцовый припой. Подозреваю еще больший термоудар при нанесении, т.к. температура у такого припоя выше. Но это не точно. </p>
<p><b>ENIG</b> &#8212; это покрытие никелем с золотом. Тут хитрая двухслойная структура образуется. Вначале осаждается химическим способом никель, а чтобы он не окислялся его защищают золотом. Плюсы такого покрытия &#8212; идеальная геометрия площадок. А минусы в цене, невысокой механической прочности покрытия (для ножевых разъемов критично), а также в том ,что при хранении могут образовываться интерметаллидных сплавов и легирования золота с медью через слой никеля. С образованием черных площадок. Которые не лудятся и при монтаже дают брак. Также такую площадку тяжело переделать. Т.к. тончайший слой растворяется в припое и в дальнейшем ее не восстановить до заводского состояния. </p>
<p><b>OSP</b> &#8212; это покрытие спец лаком. Тончайшая пленочка, которая защищает медь от окисления, а при монтаже от температуры смешивается с флюсом и испаряется.  Из плюсов тут дешевизна, идеальная геометрия поверхности площадки. Нет термоудара при нанесении. </p>
<p>Но минусы тоже присутствуют. В первую очередь требуется другие флюсы для пайки, чтобы были совместимы с данным типом покрытия, само покрытие боится царапин и вообще механического воздействия. Обращаться с платами надо осторожней, не лапать руками и прочие предосторожности.<br />
 Покрытие препятствует электроконтролю и требуются более мощные щупы, способные его проколоть, что дает существенную механическую нагрузку на плату, при проверке, особенно если речь идет о микроскопических дорожках при сверхплотном монтаже. </p>
<p><b>▌Special Process</b><br />
Специальные фичи. </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/spec.png"></p>
<p><b>HDI</b> &#8212; слепые переходные отверстия. В многослойных платах могут быть переходные не выходящие наружу, расположенные во внутренних слоях. Это сложней технологически, их приходится делать на промежуточных этапах производства платы, что удорожает плату. Но зато позволяет экономить место на других слоях. </p>
<p><b>Impendance</b> &#8212; дополнительный контроль волновых сопротивлений цепей. Очень критичный параметр для высокоскоростых цепей. Импенданс тут начинает зависеть от кучи вещей. А не только от геометрии дорожек. Банально чуть другой состав эпоксидной смолы, с другой проницаемостью и может поплыть вся схема, т.к. изменятся емкостные связи между дорожками. Эта опция позволяет включить контроль таких цепей на этапе производства, чтобы отсеять брак до того как он будет смонтирован. </p>
<p><b>Beveling G/F</b> &#8212; заточка ножевых разъемов, чтобы они легче вставлялись. В месте где образуется разъем делают фаски на плате. </p>
<p><b>Plated Self hole/castellated hole</b> &#8212; полукольцевые разъемы под пайку. Как на некоторых модулях. </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/zubi.png"></p>
<p><b>PAD Holes</b> &#8212; пады с переходными отверстиями. Для плотного монтажа иногда приходится делать такое. А с некоторым корпусами (например BGA) иначе и невозможно.   Но тут есть одна проблема, при пайке в это отверстие утечет припой и монтаж может быть некачественным. Поэтому при таком случае при производстве печатной платы эти отверстия заливают эпоксидкой после формирования. </p>
<p><b>▌Personalized Information</b></p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/pers.png"></p>
<p>Персональные предпочтения. Тут можно указать надо ли делать финальное утверждение гербер файлов (Approve working gerber), т.е. после сборки панели вам предоставят финальный файл на утверждение. </p>
<p><b>Delivery report</b> &#8212; требование отчетов по всем этапам процессов, а также нахождения брака в ходе процессов. Важно когда делаешь большую партию плат, чтобы увидеть что где-то большой процент брака и внести исправления в проект, чтобы убрать узкое место.  </p>
<p><b>Microsection analyse report</b>  &#8212; отчет о анализе среза. После изготовления многослойной печатной платы производится срез по слоям и анализируется как изготовились переходные отверстия, качество внутренних соединений металлизации и тд. Вот можно запросить эти данные. </p>
<p><b>UL mark</b> &#8212; метка сертификации о безопасности США. Все платы произведенные в <a href="https://www.nextpcb.com/?code=easyelectronicsdo">NextPcb</a> выполнены в соответствии со стандартами UL (пожаробезопасность и прочее) поэтому можно указать, что на плате должна быть такая маркировка. Нужно если планируется продавать товар на американском рынке. </p>
<p>После всех этих манипуляций, где то автоматических, а где то выбираемых вручную, у вас в правой части страницы сформируется образ заказа. С ценой платы, ценой доставки и сроков. </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/time.png"><br />
Срок  изготовления, как по мне, очень большой :(  Почти неделя. Есть фабрики которые стандартный, не срочный, заказ делают буквально за сутки-двое. А срочный чуть ли не за пару часов. </p>
<p>После чего остается только выбрать доставку и нажать кнопку <b>Add to Cart</b> и приступить к оформлению заказа. </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/addtocart.png"></p>
<p>После чего вы сразу попадаете в корзину, где ваш заказ ждет одобрения (Await to Review). </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/rewiewe.png"></p>
<p>Инженеры <a href="https://www.nextpcb.com/?code=easyelectronicsdo">NextPCB</a> просмотрят вашу плату на соответствие технических норм, а также на отсутствие косяков. Обычно это занимает около часа в рабочие дни (с понедельника по пятницу с 9:00 до 18:00 (GMT+8)). Что тоже новость. Впервые вижу такое ревью. Обычно это занимает максимум час, вне зависимости от времени суток. </p>
<p> После чего плата будет доступна к оплате. Либо вам вышлют список причин по которым она ревью не прошла. Там будет прям на картинках показаны сомнительные места которые надо исправить. </p>
<p><b>▌Оплата</b><br />
Тут есть ряд тонкостей. Дело в том, что оплата через Paypal, визу и прочие западные системы для России уже не доступна. Но NextPCB работает с Алиэкспрессом и тот поддерживает карты МИР и Киви. Поэтому оплатить можно через Алиэкспресс. Напрямую такого способа нет, но если написать вашему менеджеру, то он скинет оплату через алик. </p>
<p>Адрес почты менеджера найдете в правом нижнем углу. У меня это некий Finn, я и написал ему письмо следующего содержания:<br />
<img src="https://easyelectronics.ru/img/PCB/nextPCB/finn.png"></p>
<p>&#171;Hello. I need Aliexpress payment. How can I do it? Order number: P-E50848ASK3&#187;</p>
<p>Ответом было:<br />
<img src="https://easyelectronics.ru/img/PCB/nextPCB/finn2.png" alt="" /></p>
<p>Перешел по линку, там была какая то левая цена. Явно не похожая на то что надо. Спросил что за фигня? Finn извинился, поблагодарил за обратную связь и прислал новую ссылку. Там цена была верной. Ошибся, бывает. Плюс комиссия али, около 5%. Оплатил через карту МИР и все. </p>
<p> Платы пошли в производство и были изготовлены. Плата была отправлена 21 августа, а 20 сентября она уже пришла на почту в Челябинск. Ровно месяц. Вполне приемлемый срок для современного времени. </p>
<p>Посмотрим что пришло&#8230; </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/plate_arrived.jpg"></p>
<p>Качество отличное. Впрочем, я не сомневался в этом даже. Шелкография ровная, маска гладкая, красивая, в пады  попадание безупречное.</p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/plate_macro.jpg"></p>
<p>Тонкие линии шелкографии пропечатываются отлично. </p>
<p><img src="http://easyelectronics.ru/img/PCB/nextPCB/plate_line.jpg"></p>
<p>Линии, слева направо 0.1мм 0.2мм 1мм.  Высота текста отражена в самом тексте. Ничего не слепляется, можно смело уменьшать шрифт маркировки платы до минимума. Все будет четко пропечатано. </p>
<p>Резюмируя.<a href="https://www.nextpcb.com/?code=easyelectronicsdo"> NextPCB</a> Удобный производитель, с приятным редактором, отличным качеством изготовления. Но долгие сроки ревью и производства. Тут день, там два-три, так и неделя набежит. А с учетом и так удлинившейся доставки это прям грустно.  </p>
]]></content:encoded>
			<wfw:commentRss>http://easyelectronics.ru/proizvodstvo-pechatnyx-plat-v-nextpcb-s-dostavkoj-i-oplatoj-iz-rossii.html/feed</wfw:commentRss>
		<slash:comments>18</slash:comments>
		</item>
		<item>
		<title>CAN шина</title>
		<link>http://easyelectronics.ru/can-shina.html</link>
		<comments>http://easyelectronics.ru/can-shina.html#comments</comments>
		<pubDate>Mon, 09 Jan 2023 18:14:42 +0000</pubDate>
		<dc:creator><![CDATA[DI HALT]]></dc:creator>
				<category><![CDATA[Начинающим]]></category>
		<category><![CDATA[CAN]]></category>
		<category><![CDATA[Интерфейс]]></category>
		<category><![CDATA[шина]]></category>

		<guid isPermaLink="false">http://easyelectronics.ru/?p=1981</guid>
		<description><![CDATA[Одним из популярных сетевых интерфейсов в промышленности является Controller Area Network. Его специально придумали в BOSCH, чтобы плодить сети из контроллеров. Особенно он прижился в автопроме, где часто по автомобилю идет одна CAN шина и все на нее повешано. Причем сам стандарт не подразумевает физическую реализацию протокола. Она может быть по витой паре, оптике, радиоканалу. На чем угодно. Лишь бы можно было реализовать систему из рецессивного и доминантного бита. Т.е. два состояния одно из которых всегда подавляет другое. Например, оптоволокно, в которое светят передатчики. Доминантное состояние это свет. Потому, что мы не можем включить темноту. Если будет хоть один передатчик светить, то светло будет всем. Если же брать проводную линию, то в линии с подтяжкой и схемой включения &#171;монтажное И&#187; будет доминантным состоянием низкий уровень. Т.к. если хоть кто-то подтянет линию к земле, то никто ее поднять не сможет. ▌Арбитраж В CAN шине нет ни ведущих, ни ведомых. Все могут галдеть одновременно и обращаться кто к кому хочет. Чтобы не было коллизий существует механизм аппаратного арбитража. Основанный как раз на том самом доминантном и рецессивном состоянии. Т.е. когда два передатчика начинают вещать одновременно они выкатывают на шину бит и смотрят, не перебил ли его кто-то доминантным состоянием. Например, доминантное состояние это 0. Передатчик 1: 101101010&#8230; Передатчик 2: 10111х Второй передатчик, на пятом бите попытался выставить на шину 1, а первый в это время выкатил 0. 0 бьёт единицу. Поэтому второй передатчик затыкается и ждет когда шина освободится. Это приводит к тому, что не теряется пропускная способность шины. Но с другой стороны, требует вдумчивого составления пакета данных. Так, чтобы более приоритетные данные имели больше доминантных бит в начале посылки. Чтобы чаще выигрывать арбитраж. ▌Адресация Данные по шине летают в виде стандартных кадров, в несколько байт. Где есть различные служебные данные и, собственно, передаваемая информация. У каждого кадра в CAN шине есть идентификатор, адрес кому этот кадр предназначен. Причем это не адрес физического устройства как такового. А просто идентификатор. Все кадры принимаются всеми. Но дальше, уже внутри принявшего устройства, кадр фильтруется по нужным идентификаторам и софт уже сам решает что обрабатывать, а что отбрасывать. Адресация может быть от 11 или 29 бит, в зависимости от формата кадра (расширенный и стандартный). А раз так, то на шине может быть очень много разных адресов под кучу применений. Например, в контроллере можно каждому выводу GPIO присвоить свой ID и спрашивать сразу его. Без посылки дополнительных уточнений на тему кому куда. ▌Контроль ошибок и сбоев CAN шина славится своей надежностью. Если данные приняты, то они, вероятней всего, корректны с огромной долей вероятности. На это работает куча механизмов. 1. При передаче идет контроль того, что на самом деле выставилось на линии. Это происходит всегда, т.к. именно таким образом работает механизм арбитража. 2. Дополняющие биты (bit stuffing): после передачи пяти одинаковых битов подряд автоматически передаётся бит противоположного значения. Таким образом кодируются все поля кадров данных или запроса, кроме служебных разграничителей кадра. 3. Контрольная сумма кадра. Передатчик вычисляет ее и добавляет в передаваемый кадр данных. А приемник на лету считает контрольную сумму и если она совпала, то он дает доминантный бит ACK в в специально заготовленное место кадра, в конце, перед финальным битом окончания кадра. ▌Дальнобойность Дальность передачи зависит, в первую очередь, от реализации физического уровня интерфейса. Если это оптика, то тут могут быть огромные расстояния. А для обычной витой пары, что более распространено, все зависит от скорости передачи. Для самой быстрой, в 1 мегабит, это может быть около 50 метров. Если же скорость понижать, то на 10кбитах можно бросить и на 5км. Защита линии подобна RS485 и им подобным. Т.е. экранирование, разрядники, супрессоры. ▌Аппаратная реализация Ну точнее одна из. Физика процесса может быть разной. Я видел, например, на оптике. Но это все же редкость. Обычно витая пара. Часто встречается CAN интерфейс в разных микроконтроллерах, например в STM32F103 он входит в стандартный набор периферии. Но это только логический уровень. Тот что отвечает за работу всех этих фильтров, арбитражей, контрольных сумм. Аппаратно же нужна микросхема трансивер. Типичные представители это: MCP2551. Напряжение питания 5 вольт. По входу CAN держит до +/-45 вольт. Имет выход опорного напряжения и токовый вход управления режимом работы передатчика. TJA1050 или A1050. Напряжение питания 5 вольт. По входу CAN держит до +40/-27 вольт. Имет выход опорного напряжения и вход перехода в тихий режим, когда трансивер только слушает линию, но никак на нее не влияет. SN65HVD230. Напряжение питания 3.3 вольт. По входу CAN держит +/-25 вольт. Дополнительных выводов не имеет Имеют почти одинаковую распиновку, почти совместимую друг с другом. Разница заключается в двух свободных выводах, которые не обязательно использовать, но они имеют разную функцию у разных трансиверов. Я когда делаю устройство, то стараюсь заложить максимально возможное количество вариантов компонентов. Чтобы не попасть в ситуацию когда нужного трансивера нет, либо он стоит каких то невменяемых денег. В качестве быстрого решения популярны трансиверы на TJA1050 которые за копейки продают на Али в виде модулей готовых. Правда у этого трансивера есть один прикол, он почему то не работает на низких скоростях. От 60kbaud и выше. От чего я выкинул парочку таких модулей, т.к. сначала заводил шину на низких скоростях, чтобы логический анализатор меньше тупил. И получил прикол, что один трансивер работает (он был MCP2551), а второй нет. Потом только в даташите, буквально случайно, увидел эту особенность. Сеть выглядит следующим образом. Обратите внимание на тип подключения к шине. Шлейфом. Без ответвлений. На высоких скоростях это может быть критичным. Отражения сигнала меньше. На малых скоростях можно и ответвления делать, но не желательно. Для других дифференциальных шин (вроде RS485) это тоже справедливо. На начальном и конечном устройстве должны стоять терминирующие резисторы на 120 ом. Обратите внимание на обозначение выводов трансивера. Тут надо внимательно смотреть в устройство самого трансивера: По нему видно, что RxD трансивера это ВЫХОД, т.е. он назван так потому, что идет на RxD контроллера. А TxD это, соответственно, вход. Идет с выхода TxD контроллера. Не перекрестите по привычке :) Я так когда то с этой путаницей всаживал одно на другое, а потом дорожки приходилось резать и крест накрест сигнал посылать. Еще важный момент. Видите, что тут, в отличии от ADM485 нет запрета работы входа при работе выхода. Т.е. все что попадает на выход дублируется эхом на вход одновременно. Собственно это нужно для арбитража. ▌Гальваническая развязка Делается аналогично RS232 или RS485. Ставится какой нибудь оптопара H11L1 или что-то более удобное, двуканальное, вроде ISO7321/ADUM1201 Также есть изолированные трансиверы, вроде TJA1052i, но цена их кусается. Гораздо дешевле взять сборку на H11L1 или ADUM1201 В следующей статье разберу работу с CAN интерфейсом более детально. С описанием кадра передачи, адресации, фильтрации кадров. На примере STM32F103 и GD32F103 с его собственными библиотеками. Со сборкой полноценной сети и передачей данных по ней.]]></description>
				<content:encoded><![CDATA[<p>Одним из популярных сетевых интерфейсов в промышленности является Controller Area Network. Его специально придумали в BOSCH, чтобы плодить сети из контроллеров. Особенно он прижился в автопроме, где часто по автомобилю идет одна CAN шина и все на нее повешано. Причем сам стандарт не подразумевает физическую реализацию протокола. Она может быть по витой паре, оптике, радиоканалу. На чем угодно.  Лишь бы можно было реализовать систему из рецессивного и доминантного бита.  Т.е. два состояния одно из которых всегда подавляет другое. </p>
<p>Например, оптоволокно, в которое светят передатчики.  Доминантное состояние это свет. Потому, что мы не можем включить темноту. Если будет хоть один передатчик светить, то светло будет всем.  Если же брать проводную линию, то в линии с подтяжкой и схемой включения &#171;<a href="http://easyelectronics.ru/montazhnoe-i.html">монтажное И</a>&#187;  будет доминантным состоянием низкий уровень. Т.к. если хоть кто-то подтянет линию к земле, то никто ее поднять не сможет. </p>
<p><img src="http://easyelectronics.ru/img/starters/mont-i.GIF"></p>
<p><b>▌Арбитраж</b><br />
В CAN шине нет ни ведущих, ни ведомых. Все могут галдеть одновременно и обращаться кто к кому хочет. Чтобы не было коллизий существует механизм аппаратного арбитража.  Основанный как раз на том самом доминантном и рецессивном состоянии. Т.е. когда два передатчика начинают вещать одновременно  они выкатывают на шину бит и смотрят, не перебил ли его кто-то доминантным состоянием. </p>
<p>Например, доминантное состояние это 0. </p>
<p>Передатчик 1: 101101010&#8230;<br />
Передатчик 2: 10111х   </p>
<p>Второй передатчик, на пятом бите попытался выставить на шину 1, а первый в это время выкатил 0. 0 бьёт единицу. Поэтому второй передатчик затыкается и ждет когда шина освободится.  </p>
<p>Это приводит к тому, что не теряется пропускная способность шины. Но с другой стороны, требует вдумчивого составления пакета данных. Так, чтобы более приоритетные данные имели больше доминантных бит в начале посылки. Чтобы чаще выигрывать арбитраж. </p>
<p><span id="more-1981"></span></p>
<p><b>▌Адресация</b><br />
Данные по шине летают в виде стандартных кадров, в несколько байт. Где есть различные служебные данные и, собственно, передаваемая информация. У каждого кадра в CAN шине есть идентификатор, адрес кому этот кадр предназначен. Причем это не адрес физического устройства как такового. А просто идентификатор. Все кадры принимаются всеми. Но дальше, уже внутри принявшего устройства, кадр фильтруется по нужным идентификаторам и софт уже сам решает что обрабатывать, а что отбрасывать. Адресация может быть от 11 или 29 бит, в зависимости от формата кадра (расширенный и стандартный).<br />
 А раз так, то на шине может быть очень много разных адресов под кучу применений. Например, в контроллере можно каждому выводу GPIO присвоить свой ID и спрашивать сразу его. Без посылки дополнительных уточнений на тему кому куда. </p>
<p><b>▌Контроль ошибок и сбоев</b><br />
CAN шина славится своей надежностью. Если данные приняты, то они, вероятней всего, корректны с огромной долей вероятности. На это работает куча механизмов. </p>
<p>1. При передаче идет контроль того, что на самом деле выставилось на линии. Это происходит всегда, т.к. именно таким образом работает механизм арбитража.<br />
2. Дополняющие биты (bit stuffing): после передачи пяти одинаковых битов подряд автоматически передаётся бит противоположного значения. Таким образом кодируются все поля кадров данных или запроса, кроме служебных разграничителей кадра.<br />
3. Контрольная сумма кадра. Передатчик вычисляет ее и добавляет в передаваемый кадр данных. А приемник на лету считает контрольную сумму и если она совпала, то он дает доминантный бит ACK в в специально заготовленное место кадра, в конце, перед финальным битом окончания кадра. </p>
<p><b>▌Дальнобойность</b><br />
Дальность передачи зависит, в первую очередь, от реализации физического уровня интерфейса. Если это оптика, то тут могут быть огромные расстояния. А для обычной витой пары, что более распространено, все зависит от скорости передачи. Для самой быстрой, в 1 мегабит, это может быть около  50 метров.  Если же скорость понижать, то на 10кбитах можно бросить и на 5км.  Защита линии подобна RS485 и им подобным. Т.е. экранирование, разрядники, супрессоры. </p>
<p><b>▌Аппаратная реализация</b><br />
Ну точнее одна из. Физика процесса может быть разной. Я видел, например, на оптике. Но это все же редкость. Обычно витая пара. </p>
<p>Часто встречается CAN интерфейс в разных микроконтроллерах, например в STM32F103 он входит в стандартный набор периферии. Но это только логический уровень. Тот что отвечает за работу всех этих фильтров, арбитражей, контрольных сумм. Аппаратно же нужна микросхема трансивер. </p>
<p>Типичные представители это:</p>
<ul>
<li> MCP2551. Напряжение питания 5 вольт. По входу CAN держит до +/-45 вольт.  Имет выход опорного напряжения и токовый вход управления режимом работы передатчика.  </li>
<li> TJA1050 или A1050. Напряжение питания 5 вольт. По входу CAN держит до +40/-27 вольт.  Имет выход опорного напряжения и вход перехода в тихий режим, когда трансивер только слушает линию, но никак на нее не влияет.</li>
<li> SN65HVD230. Напряжение питания 3.3 вольт.  По входу CAN держит +/-25 вольт. Дополнительных выводов не имеет</li>
</ul>
<p>Имеют почти одинаковую распиновку, почти совместимую друг с другом. Разница заключается в двух свободных выводах, которые не обязательно использовать, но  они имеют разную функцию у разных трансиверов. Я когда делаю устройство, то стараюсь заложить максимально возможное количество вариантов компонентов. Чтобы не попасть в ситуацию когда нужного трансивера нет, либо он стоит каких то невменяемых денег. </p>
<p><img src="https://easyelectronics.ru/img/starters/CAN/TJA1050.png"></p>
<p>В качестве быстрого решения популярны трансиверы на TJA1050 которые за копейки продают на Али в виде модулей готовых. Правда у этого трансивера есть один прикол, он почему то не работает на низких скоростях. От 60kbaud и выше. От чего я выкинул парочку таких модулей, т.к. сначала заводил шину на низких скоростях, чтобы логический анализатор меньше тупил. И получил прикол, что один трансивер работает (он был MCP2551), а второй нет. Потом только в даташите, буквально случайно, увидел эту особенность. </p>
<p>Сеть выглядит следующим образом.<br />
<img src="https://easyelectronics.ru/img/starters/CAN/CAN-bus.png"></p>
<p>Обратите внимание на тип подключения к шине. Шлейфом. Без ответвлений. На высоких скоростях это может быть критичным. Отражения сигнала меньше.  На малых скоростях можно и ответвления делать, но не желательно. Для других дифференциальных шин (вроде RS485) это тоже справедливо. На начальном и конечном устройстве должны стоять терминирующие резисторы на 120 ом. </p>
<p>Обратите внимание на обозначение выводов трансивера. Тут надо внимательно смотреть в устройство самого трансивера:</p>
<p><img src="https://easyelectronics.ru/img/starters/CAN/TJA1050ins.png"></p>
<p>По нему видно, что RxD трансивера это ВЫХОД, т.е. он назван так потому, что идет на RxD контроллера. А TxD это, соответственно, вход. Идет с выхода TxD контроллера. Не перекрестите по привычке :)  Я так когда то с этой путаницей всаживал одно на другое, а потом дорожки приходилось резать и крест накрест сигнал посылать. </p>
<p>Еще важный момент. Видите, что тут, в отличии от ADM485 нет запрета работы входа при работе выхода. Т.е. все что попадает на выход дублируется эхом на вход одновременно. Собственно это нужно для арбитража. </p>
<p><b>▌Гальваническая развязка</b><br />
Делается аналогично RS232 или RS485. Ставится какой нибудь оптопара H11L1 или что-то более удобное, двуканальное, вроде ISO7321/ADUM1201</p>
<p><img src="https://easyelectronics.ru/img/starters/CAN/optocan.png"></p>
<p>Также есть изолированные трансиверы, вроде TJA1052i, но цена их кусается. Гораздо дешевле взять сборку на H11L1 или ADUM1201</p>
<p><img src="https://easyelectronics.ru/img/starters/CAN/TJA1052.png"></p>
<p>В следующей статье разберу работу с CAN интерфейсом более детально. С описанием кадра передачи,  адресации, фильтрации кадров. На примере STM32F103 и GD32F103 с его собственными библиотеками. Со сборкой полноценной сети и передачей данных по ней. </p>
<p><img src="https://easyelectronics.ru/img/starters/CAN/can-can.png"></p>
]]></content:encoded>
			<wfw:commentRss>http://easyelectronics.ru/can-shina.html/feed</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
	</channel>
</rss><!-- hyper cache 2026-03-21 04:12:02 -->