Русский
Русский
English
Статистика
Реклама

Stm32f407

Zx spectrum128, CPM 2.2 и терминал на STM32F407

28.10.2020 02:10:35 | Автор: admin
Нарисовавши черно-белый Spectrum48 за $3 на телевизоре и получив вопросы как: а почему не цветной, а почему без музыки, а где НЕИГРУШКИ решил поправить и опубликовать менее бюджетную версию, Spectrum128+СP/M, примерно на $25 железа.


Мне могут возразить это уже дорого, можно купить Raspberry или другой Linux board на эти деньги, однако реализация на STM32 имеет несколько выигрышных черт, таких как:
1 Время готовности при включении меньше пары секунд.
2 Лаг на клавиатуру меньше одного фрейма (20 миллисекунд, примерно как на настоящем, железном спектруме)
3 Эмулятор на STM мне нравится больше, так как у эмуляторов на Linux, а также и других эмуляторов есть фатальный недостаток их писал не я.
4. Можно питаться от батареек.

Подробнее о реализации
Подсистема звука:
2 ШИМ канала 16 bit, на частоте 44100, работают постоянно и вытягивают из Fifo звук. С другой стороны в Fifo пишет текущий процесс. Это или ZX spectrum (Z80 эмулятор), или MP3 player decoder. Если нет места для звука, то процесс ждет сканируя клавиатуру. Z80, в свою очередь, отрабатывает один кадр и пишет эвенты связанные со звуком в очередь событий звука такие как speaker ON-OFF и AY 8910.С каждым эвентом идет и счетчик клоков Z80. По окончании кадра в 20 миллисекунд симуляции (4-8 миллисекунд stm32 ), рендерится кадр на экран(только изменения) и события звука из очереди рендерятся соотвественно в Fifo звука. Если он забит значит мы торопимся, и нужно подождать. Таким образом осуществляется синхронизация времени между симулируемой и симулирующей системами. При чтении ТAP файлов (виртуального магнитофона ), звук рендерится плотнее, с более коротким шагом, в 3-5 раз, и виртуальное время течет быстрее, соотвественно загрузка скорее чем с физического магнитофона. Впрочем можно ускорение отключить, с клавиатуры (F11-F12).
ШИМ, а не DAC, для звука выбран специально. Он позволяет уменьшить помехи от питания и вынести усилитель класса D наружу и организовать регулировку громкости питающим ключи напряжением. Даже простейший, на паре транзисторов, дает более качественный звук, чем просто усиление сигнала с шумного DACa (плата STM покупная, китайская, готовая. С ошибками.)
Чип AY-3-8910 не то что бы сильно сложный, но документация на него скорее предназначена для пользователей и интеграторов, а не для эмуляции/симуляции. О многом приходилось скорее догадываться и смотреть по коду разных эмуляторов, чем просто сесть и написать по документации. Возможно, что где-то и не угадал И фокус написать его нетребовательным к СPU ресурсам. Вроде получилось.

Подсистема видео:
Изменения цвета бордера (если они были) записывается в специальную очередь с клоками процессора. Это будет видно при загрузке с магн. Ну и некоторые игры обновляют его на ходу, для спецэффектов.
После этого, когда z80 отработал свои такты на 20 миллисекунд кадр рендерится.
То есть трюки с изменением кадра на ходу в демках работать будут не все. Но с играми проблем не замечено.
Контроллер обычный, параллельный 16bit FSMC <-> ILI9341 c SPI тачем ( работает, но не нигде не используется).

z80:
Это эмуляция CMOS версии Z80. Салат из разных мест, но большей частью из FUSE и немного моего творчества тоже (в основном на обьем и скорость). Тест zexall проходит.

Клавиатура:
USB host HID device контроллер от STM. C USB роутерами работать не будет. Все претензии к ST, они поленились и я тоже. Конкретно на том боарде, что я использую нужно удалить резистор R21 1.5K: 3.3v<->USB D+.

Disk:
Примерно так отформатирован у меня, но думаю, что не все критично:
Partition 1 type Start 2048: W95 FAT32 (b)
Attributes: 80
Filesystem UUID: xxxx-xxxx
Filesystem: vfat

Корневые директории(для этого кода именно так):
ZX48
сюда класть программы для spectrum48.Форматы: .TAP,.Z80,.SNA
ZX128
сюда класть программы для spectrum128.Форматы: .TAP,.Z80,.SNA
MP3
попробуйте угадать? И форматы?

Видео тест Спектрума:
www.youtube.com/watch?v=WKQc5I55Ji8
Бонус. Реализован слой согласования с CP/M 2.2 от Grant Searle's.
www.searle.wales

Мужик сделал отличную работу по реализации CP/M на Z80 (отдельно) и FPGA (другая система)
Там и BIOS и образ диска с дофига программ, утилит и компиляторов:
Си, Лисп, Ада, Паскаль,Cobol, Алгол,Forth,APL,Fortran,PL/I,Basic всякий, muMath & muSimp (CAS math package), игры, редакторы всего 240 мега. Это сегодня пару страниц текста, а тогда, во времена CP/M столько не было ни у кого.
Подробнее здесь:
obsolescence.wixsite.com/obsolescence/multicomp-fpga-cpm-demo-disk


Ну и видео тест CP/M:
www.youtube.com/watch?v=-h3XCy79aJw

Я немного добавил к эмулятору z80 на STM32 слой обращения к диску и терминал экрана: клавиатуры, микс ANSI и VT100. Надо будет допроверить и доделать. В основном работает, CATCHUM и Turbo Pascal исправны. Запускается CP/M когда видит диск отсюда, по первым двум байтам образа:
obsolescence.wixsite.com/obsolescence/multicomp-fpga-cpm-demo-disk
Если диска вообще нет, то запускается в режиме Spectrum 128 в меню.
Что не сделано:
1. Чтение и запись с физического магнитофона, магнитофон купил, жду пустые кассеты с Али.
2. Вообще любая запись в спектруме не написана.(В CP/M есть запись)
2.".TGZ" формат?

Что еще:
Хм. Положил перед собой железо, выбираю:

Слева направо, сверху вниз:
Stm32h743, программер на stm32f103, Xilinx XC6SLX16, на ней программер для альтеры, Altera ep2c5 & PSRAM 8M, Zynq7010(на али распродажи по $15!!! ) ,W600-pico (60MHz m3+280 ram $2) ,ESP32-CAM with PSRAM 8M (очень дешевая но мало свободных ножек).
Нижний ряд: Еще Xilinx XC6SLX16,Xilinx XC6SLX9, ANLOGIC (22k lut+SDRAM), ALTERA EP4CE15F23C8, еще W600 за $1- но мало ножек.
Справа от клавиатуры белый магнитофон для демонстрации загрузок компьютеров внукам.
Нет ничего дороже $30 Особенно интересно для тех, кто хочет учить Zynq7010 это FPGA 28 K LUT + 2 ARM cores ~600MHz + flash 128MB + ddr 256MB +ETH100. Видимо распродают контрольные платы от битмайнеров. Удивился и купил.
Эмулятор z80 прогонял на них на всех, кроме программера для альтеры и магнитофона. На некоторых и эмулятор 80286-го прогнал.

для тех кто хочет повторить
Искать:
STM32F407VET6 Development Board Cortex-M4 STM32 minimum system learning board ARM core board +3.2 inch LCD TFT With Touch Screen. Проверял только с 3.2 экраном, чип ILI9341.
source:
github.com/sdima1357/spectrum128_cpm
Подробнее..

Из песочницы Вывод текста на OLED дисплей с контроллером SH1106 по шине SPI через библиотеку HAL

23.08.2020 16:12:18 | Автор: admin

Здравствуйте, уважаемые читатели. В своих разработках на микроконтроллерах STM32, для вывода осмысленной информации, я пользуюсь OLED дисплеями на чипе SSD1306. В последний раз пришел ко мне 1,3" SPI модель по демократичной цене около 200руб. Первое, что бросилось в глаза надпись SH1106 вместо SSD1306, поиск в интернете прояснил, что это практически тоже самое, только оставлен единственный страничный режим адресации, да и тот ограничен одной строкой. Как с ним работать я и постараюсь объяснить вам в этой публикации.


Где-то с год назад мне стало не хватать возможностей синей пилюли (STM32F103) и была заказана китайская плата разработчика STM32F407VE. Для отладки, часто, двух светодиодов не хватает, поэтому в каждом проекте для вывода информации подключаю OLED SSD1306 по шине I2C, в который влюбился еще со времен Arduino. Так как графику я на него не вывожу, в основном числа и текст, а размер готовых библиотек и их содержание поражал мое воображение, была написана небольшая библиотечка, которую я немного адаптировал под SH1106 и хочу поделится с вами процессом ее написания. Дисплей приехал 7pin SPI:


sh1106

Плата разработчика у меня такая, но ничего вам не помешает подключить к другой, хоть на STM3F103, для чего HAL и был придуман (разве не так ?):


F407board


Выберем в CubeMX наш STM32F407VE кристалл, обязательно включим режим отладки, иначе потом перешивать придется через UART1. В Clock Configuration выберем резонаторы 8MHz и зададим частоту работы кристалла 168MHz от HSE. При желании можете сконфигурировать на выход ножки PA6 и PA7, к которым подключены светодиоды D2 и D3 (загораются подачей лог 0) для контроля прохождения проблемных точек кода:


RccDebug

На плате SPI1 выведен на колодку NRF24L01 и к нему у меня подключен ESP-PSRAM64H, для дисплея остался SPI3. Выберем его мастером на передачу в DMA режиме и активируем прерывания, настройки будут такие :


SpiAll

Теперь настроим ножки управляющих сигналов DC (данные/команда), RESET (аппаратный сброс) и CS (выбор дисплея) :


Gpio

Таблица соединений :


SH1106 STM32F407
  1. GND GND
  2. VDD 3V3
  3. SCK PC10
  4. SDA PC12
  5. RES PD0
  6. DC PC11
  7. CS PA15

Увеличим Heap и Stack в 2 раза и создадим проект для Atollic, и выберем его запуск. Сразу создадим библиотеку, которую потом будем подключать к своим проектам. В левом окне раскроем папку Src нашего проекта и в меню выберем File->Source File, введем имя нашей библиотеки spi1106.c аналогично создадим File->Header File с именем spi1106.h. Последний перенесем из папки Src в Inc и откроем для редактирования, между #define ic1306_H_ и #endif /* ic1306_H_ */ определим короткие команды для управлением сигналами CD, RESET и CS и функцию инициализации экрана:

#define SPI1106_H_#include "main.h"void sh1106Init (uint8_t contrast, uint8_t bright, uint8_t mirror);#define SH_Command HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_RESET)#define SH_Data HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_SET)#define SH_ResHi HAL_GPIO_WritePin(RES_GPIO_Port, RES_Pin, GPIO_PIN_SET)#define SH_ResLo HAL_GPIO_WritePin(RES_GPIO_Port, RES_Pin, GPIO_PIN_RESET)#define SH_CsHi HAL_GPIO_WritePin(GPIOA, CS_Pin, GPIO_PIN_SET)#define SH_CsLo HAL_GPIO_WritePin(GPIOA, CS_Pin, GPIO_PIN_RESET)#endif /* SPI1106_H_ */

В файле spi1106.c начнем создавать наши функции, вначале подключим main.h, хэдер нашей библиотеки и выбранного канала SPI дисплея:


#include "main.h"#include <spi1106.h>extern SPI_HandleTypeDef hspi3;

Напишем функции пересылки кода команды через SPI при помощи библиотеки HAL:


void SH1106_WC (uint8_t comm){uint8_t temp[1];SH_Command;SH_CsLo;temp[0]=comm;HAL_SPI_Transmit(&hspi3,&temp,1,1);SH_CsHi;}

Функция инициализации с минимальным набором команд:


void sh1106Init (uint8_t contrast, uint8_t bright,uint8_t mirror){SH_ResLo;HAL_Delay(1);SH_ResHi;HAL_Delay(1);SH1106_WC(0xAE); //display offSH1106_WC(0xA8); //--set multiplex ratio(1 to 64)SH1106_WC(0x3F); //SH1106_WC(0x81); //--set contrast control registerSH1106_WC(contrast);if (mirror) {SH1106_WC(0xA0);SH1106_WC(0xC0);}else {SH1106_WC(0xA1);SH1106_WC(0xC8); }SH1106_WC(0xDA);SH1106_WC(0x12);SH1106_WC(0xD3);SH1106_WC(0x00);SH1106_WC(0x40);SH1106_WC(0xD9); //--set pre-charge periodSH1106_WC(bright);SH1106_WC(0xAF); //--turn on SSD1306 panel}

С ней, надеюсь все понятно, первый параметр контрастность (0-255), второй яркость (разбита на два полубайта, комбинации 0xX0 0x0X недопустимы), третий ориентация дисплея (0/1). Для понимания работы дисплея и дальнейших функций вывода изображения советую почитать статью на Датагоре Визуализация для микроконтроллера. Часть 1. OLED дисплей 0.96 (128х64) на SSD1306" и переведенный русский даташит SSD1306.


До бесконечного цикла в main.c проведем инициализацию дисплея :


/* USER CODE BEGIN 2 */sh1106Init (40,0x22,0);/* USER CODE END 2 */

На экране увидите графические узоры типа таких :


noise

В файле spi1106.h напишем определение сразу еще трех функций очистки, печати мелким и средним шрифтом :


void sh1106Clear(uint8_t start, uint8_t stop);void sh1106SmallPrint(uint8_t posx, uint8_t posy, uint8_t *str);void sh1106MediumPrint(uint8_t posx, uint8_t posy,uint8_t *str);

В функции очистки в файле spi1106.c выбор страницы с 0-ой по 7-ю осуществляется командой 0xB0...0xB7 и будет выглядеть так :


void sh1106Clear(uint8_t start, uint8_t stop){ uint32_t *adrclear;uint32_t timep,timec;uint8_t dt[128];adrclear=(uint32_t *)dt;for(uint8_t i=0;i<32;i++) {*adrclear++=0x00;}for (uint8_t m = start; m <= stop; m++){SH1106_WC(0xB0+m);SH1106_WC(2);SH1106_WC(0x10);SH_Data;SH_CsLo;HAL_SPI_Transmit_DMA(&hspi3,dt,128);timec=HAL_GetTick();timep=timec+50;while ((HAL_SPI_GetState(&hspi3) != HAL_SPI_STATE_READY)&&(timec<timep)){timec=HAL_GetTick();}SH_CsHi;}}

До основного цикла в main.c, после инициализации, вставим очистку :


sh1106Clear(0,7);

Можете поиграться с очисткой, задав заполнение своим узором, мы же двигаем далее, вставим в файл spi1106.c ближе к началу сперва маленький шрифт из файла DefaultFonts.c (взял из какой-то ардуиновской библиотеки). Особенностью данного шрифта является, то что он вертикально ориентирован (как раз под структуру отображения байта дисплеями SSD1306/SH1106). В начале массива символов необходимо удалить ненужные нам 4-е служебных байта. Весь фонт вы найдете в архиве библиотеки, в качестве примера, приведу лишь начало:


SmallFont
const uint8_t SmallFont[] =  //  Шрифт  SmallFont{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //001)0x20=032пробел0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, //002)0x21=033!0x00, 0x00, 0x07, 0x00, 0x07, 0x00, //003)0x22=034"0x00, 0x14, 0x7F, 0x14, 0x7F, 0x14, //004)0x23=035#0x00, 0x24, 0x2A, 0x7F, 0x2A, 0x12, //005)0x24=036$0x00, 0x23, 0x13, 0x08, 0x64, 0x62, //006)0x25=037%0x00, 0x36, 0x49, 0x55, 0x22, 0x50, //007)0x26=038&0x00, 0x00, 0x05, 0x03, 0x00, 0x00, //008)0x27=039'0x00, 0x00, 0x1C, 0x22, 0x41, 0x00, //009)0x28=040(0x00, 0x00, 0x41, 0x22, 0x1C, 0x00, //010)0x29=041)0x00, 0x14, 0x08, 0x3E, 0x08, 0x14, //011)0x2A=042*0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, //012)0x2B=043+0x00, 0x00, 0x00, 0xA0, 0x60, 0x00, //013)0x2C=044,0x00, 0x08, 0x08, 0x08, 0x08, 0x08, //014)0x2D=045-0x00, 0x00, 0x60, 0x60, 0x00, 0x00, //015)0x2E=046.0x00, 0x20, 0x10, 0x08, 0x04, 0x02, //016)0x2F=047///0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E, //017)0x30=04800x00, 0x00, 0x42, 0x7F, 0x40, 0x00, //018)0x31=04910x00, 0x42, 0x61, 0x51, 0x49, 0x46, //019)0x32=05020x00, 0x21, 0x41, 0x45, 0x4B, 0x31, //020)0x33=05130x00, 0x18, 0x14, 0x12, 0x7F, 0x10, //021)0x34=05240x00, 0x27, 0x45, 0x45, 0x45, 0x39, //022)0x35=05350x00, 0x3C, 0x4A, 0x49, 0x49, 0x30, //023)0x36=05460x00, 0x01, 0x71, 0x09, 0x05, 0x03, //024)0x37=05570x00, 0x36, 0x49, 0x49, 0x49, 0x36, //025)0x38=05680x00, 0x06, 0x49, 0x49, 0x29, 0x1E, //026)0x39=05790x00, 0x00, 0x36, 0x36, 0x00, 0x00, //027)0x3A=058:0x00, 0x00, 0x56, 0x36, 0x00, 0x00, //028)0x3B=059;0x00, 0x08, 0x14, 0x22, 0x41, 0x00, //029)0x3C=060<0x00, 0x14, 0x14, 0x14, 0x14, 0x14, //030)0x3D=061 =0x00, 0x00, 0x41, 0x22, 0x14, 0x08, //031)0x3E=062>0x00, 0x02, 0x01, 0x51, 0x09, 0x06, //032)0x3F=063?//0x00, 0x32, 0x49, 0x59, 0x51, 0x3E, //033)0x40=064@0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C, //034)0x41=065A0x00, 0x7F, 0x49, 0x49, 0x49, 0x36, //035)0x42=066B// ...}


Напишем функцию печати маленьким шрифтом высотой одна страница (ширина 6, высота 8 точек), где posx знакоместо, кратное 6 пикселям дисплея, а posy номер страницы (0 верхняя, 7 нижняя):


void sh1106SmallPrint(uint8_t posx, uint8_t posy, uint8_t *str){uint8_t dt[128];uint16_t posfont, posscr;uint32_t *adrclr;uint16_t *adrdst,*adrsrc;uint32_t timer,timec;adrclr=(uint32_t *)&dt;uint8_t code;code=*str++;for(uint8_t i=0;i<32;i++) { *adrclr++=0; }posscr=posx*6;while (code>31){if(code==32) {posscr+=2;}else{posfont=6*(code-32);adrdst=(uint16_t *)&dt[posscr];adrsrc=(uint16_t *)&SmallFont[posfont];*(adrdst++)=*(adrsrc++);*(adrdst++)=*(adrsrc++);*(adrdst++)=*(adrsrc++);posscr+=6;}code=*str++;if (posscr>122) break;}SH1106_WC(0xB0+posy);SH1106_WC(2);SH1106_WC(0x10);SH_Data;SH_CsLo;HAL_SPI_Transmit_DMA(&hspi3,dt,128);timec=HAL_GetTick();timer=timec+50;while ((HAL_SPI_GetState(&hspi3) != HAL_SPI_STATE_READY)&&(timec<timer)){timec=HAL_GetTick();}SH_CsHi;}

Затем, в библиотеке spi1106.c после мелкого шрифта вставим средний (высота 16, ширина 12 пикселей) из того же файла DefaultFonts.c (также удалите 4 служебных байта в начале массива) и напишем функцию печати средним шрифтом высотой две страницы, где posx знакоместо, кратное уже 12 пикселям дисплея, а posy аналогично предыдущей функции номер страницы:


void sh1106MediumPrint(uint8_t posx, uint8_t posy, uint8_t *str){uint8_t dt[256];uint16_t posfont, posscr;uint32_t *adrdst, *adrsrc;uint32_t timer,timec;adrdst=(uint32_t *)&dt;uint8_t code;code=*str++;for(uint8_t i=0;i<64;i++) { *adrdst++=0; }posscr=posx*12;while (code>31){posfont=24*(code-32);adrsrc=(uint32_t *)&MediumFont[posfont];adrdst=(uint32_t *)&dt[posscr];*(adrdst++)=*(adrsrc++);*(adrdst++)=*(adrsrc++);*(adrdst++)=*(adrsrc++);adrsrc=(uint32_t *)&MediumFont[posfont+12];adrdst=(uint32_t *)&dt[posscr+128];*(adrdst++)=*(adrsrc++);*(adrdst++)=*(adrsrc++);*(adrdst++)=*(adrsrc++);code=*str++;posscr+=12;if (posscr>116) break;}SH1106_WC(0xB0+posy);SH1106_WC(2);SH1106_WC(0x10);SH_Data;SH_CsLo;HAL_SPI_Transmit_DMA(&hspi3,dt,128);timec=HAL_GetTick();timer=timec+50;while ((HAL_SPI_GetState(&hspi3) != HAL_SPI_STATE_READY)&&(timec<timer)){timec=HAL_GetTick();}SH1106_WC(0xB0+posy+1);SH1106_WC(2);SH1106_WC(0x10);SH_Data;SH_CsLo;HAL_SPI_Transmit_DMA(&hspi3,dt+128,128);timec=HAL_GetTick();timer=timec+50;while ((HAL_SPI_GetState(&hspi3) != HAL_SPI_STATE_READY)&&(timec<timer)){timec=HAL_GetTick();}SH_CsHi;}

В main.c после очистки напечатаем приветствие :


sh1106SmallPrint(0,0,(uint8_t *) "Hello SH1106_1234567890");sh1106MediumPrint(0,1,(uint8_t *) "Hi SH1106");sh1106MediumPrint(0,3,(uint8_t *) "Hello SH1106");

hello

Как видно, библиотека корректно обрезает строку, не налезая на следующую и не вылетая в HardFault при выходе за выделенный буфер. Можно было написать еще функцию вывода больших символов и графические примитивы, но честно говоря, мне рассмотренных пока хватает, оставим это на домашнее задание для вас.


И в завершение определим скорость обновления всего экрана, в главном цикле напишем такую программу, которая заполняет по одному пикселю буфер экрана и затем обновляет 7 страниц и печатает на последней строке время в миллисекундах :


/* Infinite loop *//* USER CODE BEGIN WHILE */uint8_t buf[128*8];char str[32];uint16_t count;uint8_t x,y,b;uint32_t timep,timec;while (1){count++;b=count&0x07;x=(count>>3)&0x7f;y=(count>>10)&0x07;buf[y*128+x]=buf[y*128+x]|(1<<b);timec=HAL_GetTick();for (uint8_t m = 0; m < 7; m++){SH1106_WC(0xB0+m);SH1106_WC(2);SH1106_WC(0x10);SH_Data;SH_CsLo;HAL_SPI_Transmit_DMA(&hspi3,buf+m*128,128);while ((HAL_SPI_GetState(&hspi3) != HAL_SPI_STATE_READY)){__NOP();}SH_CsHi;}timep=HAL_GetTick();sprintf(str, "%d", timep-timec);sh1106SmallPrint(0,7,str);/* USER CODE END WHILE */

speed

По скорости заполнения примерно одна страница в секунду и мелькающим в нижней строке то "0" то "1" можно сказать, что достигнута скорость обновления семи строк экрана менее 1ms, т.е. около 1000fps. Конечно с учетом реальных задач построения изображения в буфере, будет медленнее, но результат впечатляет. Да и задержку ожидания можно вставить до пересылки по SPI и распаралелить задачи отрисовки МК и перекидывания по SPI в режиме DMA.


Надеюсь данная публикация будет полезна вам для изучения алгоритма работы с данным дисплеем.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru