Pull to refresh

Нейроконнектор «Мысль»

Reading time12 min
Views9.6K

После прохождения Atomic Heart у меня всё вертелось в голове, чего бы такого сделать по лору игры, чтобы было прикольно и несложно, а главное, более‑менее реально реализуемо в реале (например, я крайне сомневаюсь, что можно создать тех же пчёл в реале, как в игре, чтобы они аналогичным образом летали).

С выходом DLC 1 для игры мысль, что сделать, пришла сама собой.:) Гусь! Точнее, эксперимент с надетым на гуся нейроконнектором «Мысль» и натолкнул меня на создание схожего девайса (а на носу как раз был Хэллоуин на тот момент, что только разжигало желание создать хотя бы прототип на коленке).

Ну, собственно, начал делать девайс на Black‑Pill (STM32F401CCU6), т. к. она была под рукой, и, в принципе, всё, что необходимо по интерфейсам, МК имел на борту. Сделать кастомную плату и в итоге сделать монолитный девайс менее чем за месяц я не успел бы, поэтому изначально было решено сделать всё максимально на готовых платах‑кубиках, просто упрятав девайс «за воротник». :D

Итак, вот сходу видос с частичным результатом работы девайса на Локи (имя котэ :) ):

Железо

Собственно, структурная схема девайса:

После этой картинки думаю уже и так ясно, что будет дальше...
После этой картинки думаю уже и так ясно, что будет дальше...

Всё предельно просто. :) Начинаем с мозга - МК, открываем куб и прикидываем, чё как раскидать по пинам:

Используем МК менее чем на половину
Используем МК менее чем на половину

Основная используемая периферия:

  • SPI — SPI1 на пинах PA4...7 для SPI Flash

  • I2C — I2C1 на пинах PB6, PB7 для работы с акселерометром

  • I2S — I2S3 на пинах PB3...5, PA15 для вывода звука

  • TIM PWM — TIM1 на пинах PA8...10 для статусного светодиода

  • SWD — на стандартных пинах PA13, PA4 для отладки и подключения консоли RTT

  • RCC — на стандартных пинах PH0, PH1 с внешним кварцем 16 МГц

Для хранения аудиофайлов я использовал первую попавшуюся плату типа такой для SPI Flash:

Напаяв на неё 128 Мбит БУ флешку
Напаяв на неё 128 Мбит БУ флешку

В качестве акселерометра я взял давно валявшуюся плату на дешёвом ADXL345, выбирая по принципу «с чем я ущё не работал и что +\‑ километр подойдёт под задачу»:

Акселерометр, просто акселерометр
Акселерометр, просто акселерометр

Для вывода звука я захотел поковыряться с I2S и поэтому нашёл офигенный моно ЦАП с I2S входом и на выходе у него уже усилитель D класса — MAX98 357A:

Ничего настраивать не нужно по I2C как у I2S ЦАП, подключил и оно просто работает!
Ничего настраивать не нужно по I2C как у I2S ЦАП, подключил и оно просто работает!

Ну и светодиод... я решил изъевернуться и взял, что было под рукой — SMD RGB светодиод FC‑B1010RGBT‑HG:

Маленький :D
Маленький :D

Зато к нему очень удобно паять проволочки:

Не ну а чё, пад аккурат под проволочку, что не так то ?
Не ну а чё, пад аккурат под проволочку, что не так то ?

Результат:

4 проволочки
4 проволочки

Собственно, фотка примерно под конец сборки макета:

Да, тут я ещё параллельно думал собрать на модуле RTL-01, прикидывал варианты
Да, тут я ещё параллельно думал собрать на модуле RTL-01, прикидывал варианты

Локи уже подозревает что-то нехорошее:

Корпус

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

Поэтому решено забить на точные габариты и плясать от динамика, который вместе со статусным светодиодом и поместим в корпус, спрятав всю остальную электронику под одежду (воротник) коту.

Незатейливо
Незатейливо
По-хорошему надо бы ещё нормальное углубление для статусного светодиода, но я забил
По-хорошему надо бы ещё нормальное углубление для статусного светодиода, но я забил

Получается, динамик с чёрной декоративной тряпочкой вклеивается сверху, сбоку просверливается отверстие под статусный светодиод и снизу всё заливаем клеем.

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

Ну... типа... нейроконнектор
Ну... типа... нейроконнектор

Аудио-файлы

А будем мы их брать, конечно же, из игры :) Надо получить примерно такой набор файлов:

Да, спустя месяц я нашёл, как ещё дораспаковать под 100 дополнительных звуков\фраз.. но для начала и этого хватило ;)
Да, спустя месяц я нашёл, как ещё дораспаковать под 100 дополнительных звуков\фраз.. но для начала и этого хватило ;)

Собственно, для распаковки нам нужна лицензионная игра с DLC 1 на ПК и UnrealPakTool. Распаковываем pakchunk13-WindowsNoEditor.pak командой:

UnrealPakTool\UnrealPak.exe "%path-to-game%\AtomicHeart\Content\Paks\pakchunk13-WindowsNoEditor.pak" -Extract "D:\atomic unpack\unpacked"

Далее потребуются утилиты:

  • ww2ogg - для преобразования wem (игровой формат звуковых файлов) в ogg

  • FFmpeg-Builds - для преобразования ogg в wav (скачивать ffmpeg-master-latest-win64-gpl.zip)

Преобразовываем все wem файлы в ogg следующей командой:

set folder=D:\atomic unpack\unpacked\WwiseAudio\Windows\Ru
for %%f in ("%folder%\*.wem") do ww2ogg.exe "%%f" --pcb packed_codebooks_aoTuV_603.bin

После чего я послушал все полученные ogg файлы, отобрал подходящие (например, явные фразы, указывающие что это гусь, я не стал брать, чтобы набор фраз был наиболее универсальным).

Далее перепаковываем полученные ogg в wav моно и 32 кГц (чтобы меньше весили, но качество голоса не слишком заметно упало, как, например, на 8 или 16 кГц) командой:

for %%f in ("*.ogg") do ffmpeg -hide_banner -loglevel error -i "%%f" -ab 128 -ar 32000 "%%f.wav"

После чего я разбиваю файлы на группы по префиксу + номеру файла:

Тут собственно уже можно догадаться, что есть 2 состояния - Idle и Move, а так же Special фраза
Тут собственно уже можно догадаться, что есть 2 состояния - Idle и Move, а так же Special фраза

Всё, теперь это можно паковать в образ файловой системы и заливать на SPI Flash любым удобным способом.:)

Примечание: с распаковкой сильно помогла данная статья — ATOMIC HEART вытаскиваем ресурсы, музыку и звуки.

Файловая система

Была взята из open‑source проекта RTL00_WEB, берём из него файлы webfs.c \ webfs.h с небольшими модификациями, чтобы работать не напрямую с памятью МК \ SoC, а внешней флешкой, подключенной по SPI.

Сами файлы, как уже было сказанно выше, проименованны специальным образом, и вот для чего. Объявляем состояния, в которых может находиться нейроконнектор (кот):

typedef enum
{
   NC_STATE_IDLE = 0,
   NC_STATE_MOVE,
   NC_STATE_ATTACK,
   NC_STATE_FREE_FALL,
   NC_STATE_ERROR = 0xff,
} nc_state_t;

Далее объявляем структуру для каждого статуса с параметрами рандомизации, префикса в имени файла и количества файлов:

typedef struct
{
   char prefix; // 'x'
   uint8_t start; //0 ...
   uint8_t end; /// n
   uint8_t luck; // to play audio 0...100
   uint32_t timeout; // ms to maybe play next file
} nc_state_audiofile_t;

Ну и, собственно, теперь можно обьявить экземпляры для каждого статуса (последняя строка):

typedef struct
{
   uint8_t playing_flag;
   uint8_t special_flag;
   uint32_t timeout;
   uint32_t timeout_sp;
   void *hi2s;
   void *hi2c;
   void *hspi;
   void *cs_port;
   uint16_t cs_pin;
   wav_header_t wavh;
   webfs_handle_t hfs;
   nc_state_t state;
   nc_state_audiofile_t audio[4]; // NC_STATE_IDLE ... NC_STATE_FREE_FALL
} nc_t;

А инитится это следующим образом:

   nc.audio[NC_STATE_IDLE].prefix = 'I';
   nc.audio[NC_STATE_IDLE].start = 0;
   nc.audio[NC_STATE_IDLE].end = 10;
   nc.audio[NC_STATE_IDLE].luck = 30;
   nc.audio[NC_STATE_IDLE].timeout = 50000;

   nc.audio[NC_STATE_MOVE].prefix = 'M';
   nc.audio[NC_STATE_MOVE].start = 0;
   nc.audio[NC_STATE_MOVE].end = 19;
   nc.audio[NC_STATE_MOVE].luck = 50;
   nc.audio[NC_STATE_MOVE].timeout = 10000;

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

Оно запускается! :O
Оно запускается! :O

Рандомайзер

Да-да.... несмотря на название и лор игры, никакими полимерами и даже просто нейросетями тут не пахнет. :(

Фальшивка...
Фальшивка...

Я не пограмист и не нейронщик... поэтому быдлокодим в лоб банальный рандомайзер для проигрывания случайного файла из текущего состояния:

void nc_RandomPlay()
{
   static char filename[10];
   static uint8_t s;
   uint8_t luck = rand() % 101;

   SysView_LogInfo("[nc] Luck: %u (Threshold: %u)\r\n", luck, nc.audio[nc.state].luck);
   if (luck <= nc.audio[nc.state].luck)
   {
      for (;;)
      {
         uint8_t n = rand() % (nc.audio[nc.state].end + 1);
         if (s != n)
         {
            s = n;
            break;
         }
      }

      if (s < nc.audio[nc.state].start)
      {
         s = nc.audio[nc.state].start;
      }

      snprintf(&filename[0], 10, "%c%u.wav", nc.audio[nc.state].prefix, s);
      //SysView_LogInfo("[nc] Generated filename: %s\r\n", filename);
      nc_PlayFile(&filename[0]);
   }
}

Который вызывается из вечного цикла в зависимости от текущего состояния через прописанные для этого состояния промежутки времени:

void nc_Routine()
{
   led_Routine();

   if ((LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_8)) && (nc.playing_flag == 0))
   {
      adxl345_GetIntSource(&adxl);

      if (((adxl.int_src & ADXL345_INT_EN_BIT_DOUBLE_TAP) > 0) &&
            SysTick_GetCurrentTick() - nc.timeout_sp > 5000)
      {
         nc_TapSpecialPlay();
         nc.state = NC_STATE_ATTACK;
         nc.timeout_sp = SysTick_GetCurrentTick();
         nc_UpdateLedStatus();
      }
      else if ((adxl.int_src & ADXL345_INT_EN_BIT_ACTIVITY) > 0)
      {
         nc.state = NC_STATE_MOVE;
         nc_UpdateLedStatus();
      }
      else if ((adxl.int_src & ADXL345_INT_EN_BIT_INACTIVITY) > 0)
      {
         nc.state = NC_STATE_IDLE;
         nc_UpdateLedStatus();
      }

      SysView_LogInfo("\r\n[nc] Set State: %u\r\n", nc.state);
      SysView_LogInfo("[nc] Int Status: 0x%x\r\n", adxl.int_src);
   }

   switch (nc.state)
   {
      case NC_STATE_IDLE:
      case NC_STATE_MOVE:
         if (nc.playing_flag == 0)
         {
            if (SysTick_GetCurrentTick() - nc.timeout > nc.audio[nc.state].timeout)
            {
               nc.timeout = SysTick_GetCurrentTick();
               nc_RandomPlay();
            }
         }
      break;

      case NC_STATE_ATTACK:
      case NC_STATE_FREE_FALL:
         if (nc.playing_flag == 0)
         {
            nc.state = NC_STATE_MOVE;
            nc_UpdateLedStatus();
            SysView_LogInfo("\r\n[nc] Set State: %u\r\n", nc.state);
            nc.timeout = SysTick_GetCurrentTick();
         }
      break;

      default: // NC_STATE_ERROR
      break;
   }
}

Увы, тут ничего прямо уж интересного и сложного нет.

Проигрывание аудио

С этим особо сложностей тоже не возникло, просто читаем из FS файл кратно выбранному буферу (в моём случае PLAYBACK_BUF_SIZE = 1024 байт), после чего копируем данные из полученного массива в буфер для I2S DMA 2 раза (в wav файле у нас mono, а для I2S в STM32 нужно stereo, поэтому полуслово данных необходимо копировать 2 раза) и выводим следующим образом:

void nc_PlayFile(char *name)
{
   nc.hfs = webfs_Open(name);
   if (nc.hfs != WEBFS_INVALID_HANDLE)
   {
      SysView_LogInfo("[wav] File %d name: %s (size %u)\r\n", nc.hfs, name, webfs_GetBytesRem(nc.hfs));
      uint8_t filedata[78];
      webfs_GetArray(nc.hfs, &filedata[0], 78);
      if (wav_parser_ParseHeader(&filedata[0], &nc.wavh) == 0x00)
      {
         SysView_LogInfo("[wav] Type %u Sample rate: %u Bits per sample: %u Channels: %u Size: %u Data Offset: %u\r\n",
                          nc.wavh.type, nc.wavh.sample_rate, nc.wavh.bits_per_sample, nc.wavh.channels, nc.wavh.size, nc.wavh.data_offset);
      }

      // wavh.size в байтах (2 на полуслово), а в playback_Start надо передавать размер в полусловах, но каналов 2 (!)
      playback_Start(read_buf, nc.wavh.size, 0);
      nc.playing_flag = 1;
      nc_UpdateLedStatus();
   }
}

Для автоматического проигрывания файла, начатого в nc_PlayFile, определяем буфер и callback:

static uint16_t buf[PLAYBACK_BUF_SIZE];
static void read_buf(uint16_t *dest, uint32_t count)
{
   // count в полусловах (2 канала)
   // webfs_GetArray запрашиваем в байтах
   // и в итоге один канал с флешки дублируем на 2

   //SysView_LogInfo("[wav] req %u\r\n", count);
   webfs_GetArray(nc.hfs, (uint8_t *)&buf[0], count);
   for (uint32_t i = 0; i < count / 2; i++)
   {
      dest[i * 2 + 0] = buf[i];
      dest[i * 2 + 1] = buf[i];
   }
}

Инитим микро-библиотеку, представляющую собой простую надстройку над I2S HAL:

   playback_Init(nc.hi2s);
   playback_SetStop_Callback(&nc_FileDone);

И, собственно, самописная библиотека для проигрывания.

playback.h

#ifndef _PLAYBACK_H_
#define _PLAYBACK_H_

#include "main.h"
#include <string.h>

#define PLAYBACK_BUF_SIZE                 1024 // 256 // 4096

typedef void (*fill_audio_buf_cb_t)(uint16_t *dest, uint32_t count);

void playback_Init(void *hi2s);
int playback_Start(fill_audio_buf_cb_t cb, const uint32_t len, uint8_t loop_audio);
void playback_Stop(void);
void playback_Pause(void);
void playback_Resume(void);
void playback_SetStop_Callback(void (*callback)(void));

void playback_I2S_Tx_Cplt_Callback(void);
void playback_I2S_Tx_HalfCplt_Callback(void);

#endif // _PLAYBACK_H_

playback.c

#include "playback.h"
#include "stm32f4xx_hal.h"

static fill_audio_buf_cb_t fill_audio_buf_cb;
static I2S_HandleTypeDef *i2s;
static uint8_t loop;
static uint32_t offset;
static uint32_t total_len;
static void (*on_playback_stopped)(void);
static uint16_t audio_bufs[2][PLAYBACK_BUF_SIZE];

void playback_Init(void *hi2s)
{
   i2s = (I2S_HandleTypeDef *) hi2s;
}

static void update_audio_buf(uint8_t buf_number)
{
   uint16_t *dest = audio_bufs[buf_number];
   size_t len = total_len - offset;

   if (!fill_audio_buf_cb)
   {
      return;
   }

   if (len > PLAYBACK_BUF_SIZE)
   {
      len = PLAYBACK_BUF_SIZE;
   }

   fill_audio_buf_cb(dest, len);
   offset += len;
}

void playback_Stop(void)
{
   HAL_I2S_DMAStop(i2s);
}

void playback_Pause(void)
{
   HAL_I2S_DMAPause(i2s);
}

void playback_Resume(void)
{
   HAL_I2S_DMAResume(i2s);
}

int playback_Start(fill_audio_buf_cb_t cb, const uint32_t len, uint8_t loop_audio)
{
   total_len = len;
   offset = 0;
   fill_audio_buf_cb = cb;
   update_audio_buf(0);
   loop = loop_audio;
   size_t count = len;
   if (count > PLAYBACK_BUF_SIZE)
   {
      count = PLAYBACK_BUF_SIZE;
   }

   return HAL_I2S_Transmit_DMA(i2s, (uint16_t *)&audio_bufs[0], count * 2);
}

void playback_SetStop_Callback(void (*callback)(void))
{
   on_playback_stopped = callback;
}

void playback_I2S_Tx_Cplt_Callback(void)
{
   if (offset >= total_len)
   {
      if (loop)
      {
         offset = 0;
      }
      else
      {
         playback_Stop();
         if (on_playback_stopped)
         {
            on_playback_stopped();
         }
      }
   }
   else
   {
      update_audio_buf(1);
   }
}

void playback_I2S_Tx_HalfCplt_Callback(void)
{
   if (loop || offset < total_len)
   {
      update_audio_buf(0);
   }
}

Да, ещё момент, простой парсер wav-заголовков.

wav_parser.h :

#ifndef _WAV_PARSER_H_
#define _WAV_PARSER_H_

#include <stdint.h>

#define WAV_PARSER_HEADER_LEN             44

typedef struct
{
   uint8_t type;
   uint16_t sample_rate;
   uint8_t bits_per_sample;
   uint8_t channels;
   uint32_t size;
   uint32_t data_offset;
} wav_header_t;

int wav_parser_ParseHeader(const uint8_t *header, wav_header_t *format);

#endif // _WAV_PARSER_H_

wav_parser.с :

#include "wav_parser.h"
#include <string.h>

int wav_parser_ParseHeader(const uint8_t *header, wav_header_t *format)
{
   const char *p = (char *)header;
   if (strncmp(p, "RIFF", 4) != 0) // 0
   {
      return 1;
   }

   p += 8;
   if (strncmp(p, "WAVE", 4) != 0) // 8
   {
      return 1;
   }

   p += 4;
   if (strncmp(p, "fmt", 3) != 0) // 12
   {
      return 1;
   }

   p += 8;
   format->type = p[0] | (p[1] << 8); // 20

   p += 2;
   format->channels = p[0] | (p[1] << 8); // 22

   p += 2;
   format->sample_rate = 0; // 24
   for (uint8_t i = 0; i < 4; i++)
   {
      format->sample_rate |= p[i] << (8 * i);
   }

   p += 10;
   format->bits_per_sample = p[0] | (p[1] << 8); // 34

   p += 2;
   for (uint8_t i = 0; i < 10; i++) // 10 chunks Max
   {
      if (strncmp(p, "data", 4) != 0)
      {
         p += 4;
         uint32_t chunk_len = (p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24));
         p += (4 + chunk_len);
      }
      else
      {
         p += 4;
         format->size = (p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24));
         //format->data_offset = (uint32_t)*p;
         format->data_offset = 78;//+= 4; // TODO: какой то баг, для файла получаю 90, а должно быть 78
         break;
      }
   }

   return 0;
}

Инициализация I2S и DMA выглядит следующим образом:

void SysInit_DMA(void)
{
   LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);

   hdma_spi3_tx.Instance = DMA1_Stream5;
   hdma_spi3_tx.Init.Channel = DMA_CHANNEL_0;
   hdma_spi3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
   hdma_spi3_tx.Init.PeriphInc = DMA_PINC_DISABLE;
   hdma_spi3_tx.Init.MemInc = DMA_MINC_ENABLE;
   hdma_spi3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
   hdma_spi3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
   hdma_spi3_tx.Init.Mode = DMA_CIRCULAR;
   hdma_spi3_tx.Init.Priority = DMA_PRIORITY_HIGH;
   hdma_spi3_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // DMA_FIFOMODE_ENABLE
   hdma_spi3_tx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
   hdma_spi3_tx.Init.MemBurst = DMA_MBURST_SINGLE;
   hdma_spi3_tx.Init.PeriphBurst = DMA_PBURST_SINGLE; 
   HAL_DMA_Init(&hdma_spi3_tx);
   __HAL_LINKDMA(&hi2s3, hdmatx, hdma_spi3_tx);

   //NVIC_SetPriority(SPI3_IRQn, 0);
   //NVIC_EnableIRQ(SPI3_IRQn);
   NVIC_SetPriority(DMA1_Stream5_IRQn, 0);
   NVIC_EnableIRQ(DMA1_Stream5_IRQn);
}

void SysInit_I2S(void)
{
   LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_SPI3);

   // I2S3: SD (PB5), extSD (PB4), CK (PB3), WS (PA15)
   LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_5, LL_GPIO_MODE_ALTERNATE);
   LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_3, LL_GPIO_MODE_ALTERNATE);
   LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_15, LL_GPIO_MODE_ALTERNATE);
   LL_GPIO_SetAFPin_0_7(GPIOB, LL_GPIO_PIN_5, LL_GPIO_AF_6);
   LL_GPIO_SetAFPin_0_7(GPIOB, LL_GPIO_PIN_3, LL_GPIO_AF_6);
   LL_GPIO_SetAFPin_8_15(GPIOA, LL_GPIO_PIN_15, LL_GPIO_AF_6);

   hi2s3.Instance = SPI3;
   hi2s3.Init.Mode = I2S_MODE_MASTER_TX;
   hi2s3.Init.Standard = I2S_STANDARD_PHILIPS;
   hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;
   hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
   hi2s3.Init.AudioFreq = I2S_AUDIOFREQ_32K;
   hi2s3.Init.CPOL = I2S_CPOL_LOW;
   hi2s3.Init.ClockSource = I2S_CLOCK_PLL;
   hi2s3.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;
   HAL_I2S_Init(&hi2s3);
}

Прерывания:

void DMA1_Stream5_IRQHandler(void)
{
   HAL_DMA_IRQHandler(&hdma_spi3_tx);
}
void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef * i2s)
{
   playback_I2S_Tx_Cplt_Callback();
}

void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
{
   playback_I2S_Tx_HalfCplt_Callback();
}

Вывод

Получилось увлекательно и, блин, угарно, когда ты играешься с котом и он тебя кроет матом иногда очень в тему кидает фразы:D

Был как‑то такой момент, я отлаживал работу специальной фразы (что по двойному тапу по коту из начала видео) и для теста нацепил девайс на кота, который валялся на пледике рядом. Немного «потестировав», я отошёл обратно к компу и залип в написание прошивки, и так удачно совпало, что рандомизатор довольно долгое время в Idle состоянии ничего не выводил и я забыл о включенном девайсе.:) Сижу я значит, сижу и тут:

БУ! Испугался?!

Я аж вздрогнул XD

Саму статью я думал написать сильно раньше (с Хэллоуина так‑то уже немало времени прошло), но навалилось работы и потом мне просто было лень. Зато до Нового года я сделал следующую, нормальную версию железа нейроконнектора:

Пока микро-сикрет, на чём собрано)
Пока микро-сикрет, на чём собрано)

Успел пока только проверить основные узлы и, главное, работу SWD поверх Type-C с самодельным адаптером, выглядит это как то так:

К адаптеру подключается напрямую J-Link, а также USB Type-C, data линии которого проброшены до целевого МК (можно проверять работу USB Device)
К адаптеру подключается напрямую J-Link, а также USB Type-C, data линии которого проброшены до целевого МК (можно проверять работу USB Device)

Адаптер автоматически определяет подключение Type‑C кабеля, умеет определять его ориентацию и автоматически перекидывает SWDIO\SWCLK линии в соответствии с подключением. Главный косяк только в том, что тут не хватает одной линии для Reset, и если на целевом МК отключен отладочный интерфейс, то его так не прошить и никак не сбросить... можно, конечно, нарушить спецификации USB и задействовать линии CC1\2, но я так делать не стал.

В общем это, когда проверю плату полноценно и перенесу прошивку нейроконнектора для этой версии, то думаю тогда и сделать репу на гите, да статейку о нём и адаптере.;)

Спасибо, что прочитали! Надеюсь, было интересно хоть немного.

Tags:
Hubs:
Total votes 49: ↑42 and ↓7+35
Comments9

Articles