Moscow, Russia

ESP-PSRAM64 / ESP-PSRAM64H

ESP-PSRAM64 / ESP-PSRAM64H — это микросхема последовательной псевдостатической оперативной памяти (PSRAM) объемом 64 Мбит (8 Мбайт) производства Espressif Systems. Она предназначена для расширения памяти микроконтроллеров, таких как ESP32. 
 
Псевдостатическая память (PSRAM) — это тип оперативной памяти, сочетающий высокую плотность упаковки ячеек DRAM с простым интерфейсом SRAM. Она использует конденсаторы для хранения данных, но имеет встроенную логику регенерации, делающую процесс обновления данных «прозрачным» для контроллера, что обеспечивает экономию энергии и места.

Особенности PSRAM:

  1. Структура и работа: PSRAM построена на базе динамической памяти (DRAM), что обеспечивает более низкую себестоимость и высокую плотность, чем у классической статической памяти (SRAM). Однако, в отличие от обычной DRAM, она автоматически управляет регенерацией данных внутри чипа, что имитирует поведение статической памяти.
  2. Преимущества:
    • Низкое энергопотребление: Идеально подходит для портативных устройств благодаря режимам глубокого сна.
    • Плотность: Больший объем памяти при тех же размерах чипа по сравнению с SRAM.
    • Интерфейс: Простой интерфейс (например, SPI, QPI), как у SRAM, что упрощает проектирование плат.
  3. Применение: Используется в устройствах, где критичны малый размер и низкое потребление, например, в периферийных устройствах, бюджетных микроконтроллерах и встроенных системах.
Псевдостатическая память обеспечивает баланс между производительностью и экономичностью, являясь промежуточным решением между быстрой, но дорогой SRAM, и емкой, но требующей сложного управления DRAM

Основные характеристики

  • Тип памяти: Псевдостатическая оперативная память (PSRAM).
  • Объем памяти: 64 Мбит (8 Мбайт), организована как 8M x 8 бит.
  • Интерфейсы:
    • SPI (Serial Peripheral Interface): Базовый последовательный режим.
    • QPI (Quad Peripheral Interface): Четырехпроводной интерфейс для достижения максимальной пропускной способности.
    • Особенности команд: Поддерживает быструю очистку, пакетное чтение/запись с настраиваемой длиной (32 или 1024 байта).
  • Рабочее напряжение (Vcc):
    • 2,7 В — 3,6 В (номинал 3,3 В) для ESP-PSRAM64H
    • 1,8 В для ESP-PSRAM64
  • Тактовая частота:
    • 133 МГц — при операциях, не пересекающих границы страниц.
    • 84 МГц — при пакетных (burst) операциях с пересечением границ страниц.
    • Типовая частота при работе с модулями ESP32 составляет около 72 МГц.
  • Корпус: Компактный корпус SOIC-8 (SOP8-150 mil).
  • Температурный диапазон: От -40°C до +85°C.
  • Дополнительно: Высокопроизводительная CMOS-технология. Поддерживает неограниченное количество операций чтения и записи, в отличие от флэш-памяти или EEPROM. 

Применение и особенности

  • Расширение памяти: ESP-PSRAM64H часто используется с платами на базе ESP32 для увеличения доступной оперативной памяти, что полезно для приложений, требующих большего объема хранения данных или сложных вычислений.
  • Простота интеграции: Она разработана для совместного использования с чипами, которые имеют поддержку отображения внешней PSRAM в память (memory-mapping), обеспечивая бесперебойную работу.
  • Высокая скорость: Использование интерфейса QPI обеспечивает высокую пропускную способность данных. 

Важное замечание по использованию с ESP32 

Хотя микросхема имеет объем 8 Мбайт, стандартная архитектура адресного пространства ESP32 напрямую поддерживает отображение (memory mapping) только 4 Мбайт внешней памяти. Для доступа к остальным 4 Мбайт требуется использование специального API (например, himem в ESP-IDF).

Подключение к Raspberry Pi

Подключение ESP-PSRAM64H (3.3V) к Raspberry Pi 3 (RPi 3) технически возможно через интерфейс SPI, однако важно понимать ограничение: операционная система Linux (Raspbian/OS) не сможет использовать эту память как стандартное расширение ОЗУ (RAM). Вы сможете работать с ней только как с внешним устройством для хранения данных через драйвер или пользовательское ПО.

Схема подключения (SPI)

Для работы используйте основной интерфейс SPI0 на Raspberry Pi 3 (разъем GPIO):
 
Контакт PSRAM (SOP8) Контакт Raspberry Pi 3 (GPIO) Номер физического пина RPi
VCC (Pin 8) 3.3V Power 1 или 17
VSS (Pin 4) Ground 6, 9, 14, 20 или 25
CE# (Pin 1) GPIO 8 (SPI0 CE0) 24
SO / SIO1 (Pin 2) GPIO 9 (SPI0 MISO) 21
SI / SIO0 (Pin 5) GPIO 10 (SPI0 MOSI) 19
SCLK (Pin 6) GPIO 11 (SPI0 SCLK) 23
SIO2 / SIO3 (Pins 3, 7) Не подключать (или подтянуть к 3.3V)
Примечание: Для работы в режиме Quad SPI (QPI) у Raspberry Pi не хватает стандартных драйверов и аппаратной поддержки «из коробки», поэтому используется обычный режим SPI.

Ключевые сложности

  1. Режим QPI: Raspberry Pi 3 не поддерживает нативный режим Quad SPI для сторонних микросхем памяти так же эффективно, как контроллеры ESP32. Скорость обмена будет ограничена стандартным SPI (до 32–50 МГц на практике).
  2. Скорость: Хотя чип держит 133 МГц, физические провода между RPi и чипом создадут помехи. Для стабильной работы на макетной плате не превышайте 20-30 МГц.
  3. Уровни напряжения: ESP-PSRAM64H работает от 3.3V, что идеально совпадает с логическими уровнями Raspberry Pi. Не подключайте её к 5V!
  4. Назначение: Поскольку Linux не может «подмонтировать» SPI RAM как системную память, это решение оправдано только для специфических задач (например, видеобуфер для камеры или хранение логов без износа SD-карты).

Согласование линий связи

Для согласования линий связи между Raspberry Pi 3 и ESP-PSRAM64H (особенно если длина проводов превышает 10–15 см или частота SPI выше 20 МГц) необходимо учитывать целостность сигнала и емкостную нагрузку.

Вот основные шаги по убыванию важности:

  1. Последовательные резисторы (Source Termination). Это самый эффективный способ борьбы с отражениями сигнала и «звоном» (ringing).
    • Где ставить: В разрыв линий SCLK, MOSI и CS максимально близко к ножкам Raspberry Pi. Для линии MISO — максимально близко к ножке PSRAM.
    • Номинал: 33 – 47 Ом.
    • Зачем: Они согласуют выходное сопротивление драйвера GPIO с волновым сопротивлением линии и сглаживают фронты импульсов.
  2. Подтягивающие резисторы (Pull-up). Хотя у PSRAM есть внутренние механизмы, для стабильности на шине SPI крайне важны внешние подтяжки:
    • CS (Chip Select): Обязательно подтяните к 3.3В резистором 4.7 – 10 кОм. Это предотвратит ложные срабатывания, пока GPIO инициализируется.
    • HOLD# и WP#: Если они не используются, их нужно жестко соединить с 3.3В. Если линии длинные — также через резистор 10 кОм.
  3. Фильтрация питания (Decoupling). PSRAM очень чувствительна к просадкам напряжения в моменты переключения портов.
    • Установите керамический конденсатор 0.1 мкФ (100 нФ) максимально близко к 8-й ножке (VCC) и 4-й ножке (GND) микросхемы.
    • Если модуль выносной, добавьте электролитический или танталовый конденсатор 10 мкФ на входе питания модуля.
  4. Физическая компоновка (Wiring)
    • Общая земля (GND): Если вы используете шлейф, пускайте провод земли рядом с каждым сигнальным проводом (SCLK, MOSI, MISO). Это уменьшит наводки (crosstalk).
    • Витая пара: В идеале сигнал SPI и земля должны быть слегка свиты между собой.
    • Длина: Старайтесь не превышать 20 см. Для частот 80–133 МГц (максимум для PSRAM) дистанция должна быть не более 3–5 см (лучше всего — печатная плата с полигоном земли).
  5. Программное «согласование» (Slew Rate) В Raspberry Pi можно настроить ток нагрузки на пинах GPIO (Drive Strength).
    • По умолчанию ток ограничен. Если сигналы выглядят «заваленными» (медленно поднимаются), можно увеличить ток драйвера через утилиты типа raspi-gpio или в Device Tree, но обычно стандартных 8 мА достаточно для PSRAM.

Резюме для монтажа на макетке: Поставьте резисторы 33 Ом в линии SCLK/MOSI и конденсатор 0.1 мкФ на питание чипа — это решит 90% проблем с ошибками чтения данных.

Настройка программной части

Основные команды для ESP-PSRAM64H (Datasheet):
Вам потребуется реализовать функции для следующих базовых операций:
  • Read (0x03): Стандартное чтение. Формат:
    [0x03] [A23-A16] [A15-A8] [A7-A0] [Data...]
  • Write (0x02): Стандартная запись. Формат:
    [0x02] [A23-A16] [A15-A8] [A7-A0] [Data...]
  • Fast Read (0x0B): Чтение на высокой частоте (требует 8 «пустых» тактов после адреса).
  • Enter Quad Mode (0x35): Перевод чипа в режим QPI (не рекомендуется для RPi без специальных драйверов).
  • Включите SPI:
    • Введите
      sudo raspi-config
    • Перейдите в Interface Options -> SPI -> Yes.
    • Перезагрузитесь.
  • Проверка устройства:
    После перезагрузки в системе должен появиться файл устройства:
    /dev/spidev0.0
    Примеры реалиции кода
  • Пример на Python (библиотека spidev):
    Для взаимодействия с чипом вам придется вручную отправлять команды согласно даташиту (например, команду 0x03 для чтения или 0x02 для записи).
import spidev

spi = spidev.SpiDev()
spi.open(0, 0) # Открываем шину 0, устройство 0
spi.max_speed_hz = 10000000 # Начните с 10 МГц для стабильности

# Пример: чтение ID (команда зависит от чипа, обычно 0x9F или 0xAF)
def read_id():
    resp = spi.xfer2([0x9F, 0x00, 0x00, 0x00])
    print(f"ID чипа: {resp}")

read_id()
spi.close()
  • Пример кода на C (библиотека spidev):

    Для работы с ESP-PSRAM64H на Raspberry Pi на языке C используется библиотека wiringPi или стандартный интерфейс ядра Linux spidev. Ниже приведен пример с использованием spidev, так как это универсальный способ для систем на Linux.
    Этот код инициализирует SPI и отправляет команду на чтение ID устройства.
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>

#define SPI_DEVICE "/dev/spidev0.0"

int fd;

// Функция для передачи данных по SPI
void spi_transfer(uint8_t *tx, uint8_t *rx, int len) {
    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = (unsigned long)rx,
        .len = len,
        .speed_hz = 10000000, // Частота 10 МГц
        .bits_per_word = 8,
    };

    if (ioctl(fd, SPI_IOC_MESSAGE(1), &tr) < 1) {
        perror("Ошибка передачи SPI");
    }
}

int main() {
    // 1. Открываем SPI устройство
    fd = open(SPI_DEVICE, O_RDWR);
    if (fd < 0) {
        perror("Не удалось открыть SPI");
        return 1;
    }

    // 2. Настройка режима SPI (Mode 0: CPOL=0, CPHA=0)
    uint8_t mode = SPI_MODE_0;
    ioctl(fd, SPI_IOC_WR_MODE, &mode);

    // 3. Команда чтения ID для ESP-PSRAM64H (Команда 0x9F)
    // Формат: [Command] + [3 Address Bytes (dummy)] + [Data]
    uint8_t tx[6] = {0x9F, 0x00, 0x00, 0x00, 0x00, 0x00};
    uint8_t rx[6] = {0};

    printf("Отправка команды чтения ID...\n");
    spi_transfer(tx, rx, 6);

    // Вывод результата (первые 4 байта после команды - это ID)
    printf("Ответ: ");
    for (int i = 1; i < 6; i++) {
        printf("%02X ", rx[i]);
    }
    printf("\n");

    close(fd);
    return 0;
}

Более универсальный код

#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>

#define SPI_DEVICE "/dev/spidev0.0"
#define SPEED_HZ   10000000  // 10 МГц для стабильности на проводах

int spi_fd;

void spi_transfer(uint8_t *tx, uint8_t *rx, int len) {
    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = (unsigned long)rx,
        .len = len,
        .speed_hz = SPEED_HZ,
        .bits_per_word = 8,
    };
    if (ioctl(spi_fd, SPI_IOC_MESSAGE(1), &tr) < 1) {
        perror("SPI transfer error");
    }
}

// Чтение ID чипа
void read_id() {
    uint8_t tx[6] = {0x9F, 0x00, 0x00, 0x00, 0x00, 0x00};
    uint8_t rx[6] = {0};
    spi_transfer(tx, rx, 6);
    printf("PSRAM ID: %02X %02X %02X (Expected MF: 0x0D)\n", rx[1], rx[2], rx[3]);
}

// Запись байта
void write_byte(uint32_t addr, uint8_t data) {
    uint8_t tx[5];
    tx[0] = 0x02;             // Команда Write
    tx[1] = (addr >> 16) & 0xFF; // MSB адреса
    tx[2] = (addr >> 8) & 0xFF;
    tx[3] = addr & 0xFF;      // LSB адреса
    tx[4] = data;
    spi_transfer(tx, NULL, 5);
}

// Чтение байта
uint8_t read_byte(uint32_t addr) {
    uint8_t tx[5] = {0x03, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF, 0x00};
    uint8_t rx[5] = {0};
    spi_transfer(tx, rx, 5);
    return rx[4];
}

int main() {
    spi_fd = open(SPI_DEVICE, O_RDWR);
    if (spi_fd < 0) { perror("Can't open SPI device"); return 1; }

    uint8_t mode = SPI_MODE_0;
    ioctl(spi_fd, SPI_IOC_WR_MODE, &mode);

    read_id();

    printf("Writing 0xAB to address 0x000100...\n");
    write_byte(0x000100, 0xAB);

    uint8_t val = read_byte(0x000100);
    printf("Read value: 0x%02X\n", val);

    close(spi_fd);
    return 0;
}

Для сохранения и чтения массивов (структур данных) на C через SPI в Linux используется системный вызов ioctl с драйвером spidev. Это по-прежнему самый эффективный низкоуровневый способ работы с внешней памятью на Raspberry Pi 3.


Ниже приведен пример кода, который сохраняет массив структур во внешнюю память ESP-PSRAM64H и считывает его обратно.

#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>

#define SPI_PATH "/dev/spidev0.0"
#define PSRAM_WRITE 0x02
#define PSRAM_READ  0x03

// Пример структуры, которую мы будем хранить в массиве
typedef struct {
    uint32_t id;
    float temperature;
    uint8_t status;
} SensorData;

int spi_fd;

// Функция для обмена данными по SPI
int psram_transfer(uint8_t *tx, uint8_t *rx, size_t len) {
    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = (unsigned long)rx,
        .len = len,
        .speed_hz = 10000000, // 10 МГц
        .bits_per_word = 8,
    };
    return ioctl(spi_fd, SPI_IOC_MESSAGE(1), &tr);
}

// Запись массива в PSRAM
void psram_write_array(uint32_t addr, void *data, size_t size) {
    size_t total_len = 4 + size; // 1 байт команды + 3 байта адреса + данные
    uint8_t *buffer = malloc(total_len);

    buffer[0] = PSRAM_WRITE;
    buffer[1] = (addr >> 16) & 0xFF;
    buffer[2] = (addr >> 8) & 0xFF;
    buffer[3] = addr & 0xFF;
    memcpy(&buffer[4], data, size);

    psram_transfer(buffer, NULL, total_len);
    free(buffer);
}

// Чтение массива из PSRAM
void psram_read_array(uint32_t addr, void *dest, size_t size) {
    size_t total_len = 4 + size;
    uint8_t *tx = calloc(1, total_len);
    uint8_t *rx = malloc(total_len);

    tx[0] = PSRAM_READ;
    tx[1] = (addr >> 16) & 0xFF;
    tx[2] = (addr >> 8) & 0xFF;
    tx[3] = addr & 0xFF;

    psram_transfer(tx, rx, total_len);
    
    // Данные начинаются после 4-го байта (команда + адрес)
    memcpy(dest, &rx[4], size);

    free(tx);
    free(rx);
}

int main() {
    spi_fd = open(SPI_PATH, O_RDWR);
    if (spi_fd < 0) { perror("SPI open failed"); return 1; }

    // Создаем массив данных
    SensorData my_sensors[3] = {
        {1, 22.5, 0x01},
        {2, 24.1, 0x01},
        {3, 19.8, 0x00}
    };

    uint32_t mem_addr = 0x001000; // Адрес в PSRAM

    // 1. Сохраняем массив
    printf("Сохранение массива структур в PSRAM...\n");
    psram_write_array(mem_addr, my_sensors, sizeof(my_sensors));

    // 2. Читаем массив обратно в новое место
    SensorData read_sensors[3];
    printf("Чтение данных обратно...\n");
    psram_read_array(mem_addr, read_sensors, sizeof(read_sensors));

    // 3. Проверка результата
    for(int i = 0; i < 3; i++) {
        printf("Датчик %d: Температура = %.1f, Статус = %d\n", 
                read_sensors[i].id, read_sensors[i].temperature, read_sensors[i].status);
    }

    close(spi_fd);
    return 0;
}

  • Примечание: Ограничение 32 КБ: Драйвер spidev в Linux по умолчанию имеет ограничение на размер одного буфера передачи (обычно 4096 или 32768 байт). Если нужно записывать мегабайты за раз, данные нужно дробить на части или менять параметры модуля ядра.

Проверить лимит: cat /sys/module/spidev/parameters/bufsiz Увеличить лимит: добавить spidev.bufsiz=65536 в /boot/cmdline.txt

Как скомпилировать и запустить:
    • Сохраните код в файл psram.c.

    • Скомпилируйте:

      gcc psram.c -o psram
  • Пример кода на Perl (модуль Device::SPI) :

    Для работы с SPI в Perl на Raspberry Pi 3 проще всего использовать модуль Device::SPI. Поскольку это низкоуровневая работа с оборудованием, вам может потребоваться установить модуль через CPAN (например, cpan Device::SPI).

#!/usr/bin/perl
use strict;
use warnings;
use Device::SPI;

# Настройки устройства
my $dev = Device::SPI->new(device => '/dev/spidev0.0');
$dev->mode(0);             # SPI Mode 0
$dev->speed(10_000_000);   # 10 MHz
$dev->bits_per_word(8);

# 1. Чтение ID чипа (Команда 0x9F)
# Формат: CMD(1) + Dummy(2) + ID(3) = 6 байт
sub read_id {
    my $tx = pack('C*', 0x9F, 0x00, 0x00, 0x00, 0x00, 0x00);
    my $rx = $dev->transfer($tx);
    my @bytes = unpack('C*', $rx);
   
    printf("Device ID: %02X %02X %02X (Expected MF: 0x0D)\n",
           $bytes[3], $bytes[4], $bytes[5]);
}

# 2. Запись байта (Команда 0x02)
# Формат: CMD(1) + ADDR(3) + DATA(1)
sub write_byte {
    my ($addr, $data) = @_;
    my $tx = pack('C*',
        0x02,
        ($addr >> 16) & 0xFF,
        ($addr >> 8) & 0xFF,
        $addr & 0xFF,
        $data
    );
    $dev->transfer($tx);
}

# 3. Чтение байта (Команда 0x03)
# Формат: CMD(1) + ADDR(3) + DATA(1) = 5 байт
sub read_byte {
    my ($addr) = @_;
    my $tx = pack('C*',
        0x03,
        ($addr >> 16) & 0xFF,
        ($addr >> 8) & 0xFF,
        $addr & 0xFF,
        0x00
    );
    my $rx = $dev->transfer($tx);
    my @bytes = unpack('C*', $rx);
    return $bytes[4];
}

# Основной цикл
print "Starting PSRAM test...\n";

read_id();

my $test_addr = 0x000123;
my $test_val  = 0x77;

print "Writing $test_val to address $test_addr...\n";
write_byte($test_addr, $test_val);

my $result = read_byte($test_addr);
printf("Read value: %02X\n", $result);

if ($result == $test_val) {
    print "Success!\n";
} else {
    print "Error: Data mismatch.\n";
}

Для сохранения структур данных или работы с большими блоками) лучше всего реализовать функции пакетного (burst) чтения и записи. ESP-PSRAM64H эффективнее всего работает именно тогда, когда данные передаются длинными последовательностями.

Ниже представлен продвинутый пример на Perl, использующий объектно-ориентированный подход для работы с памятью как с хранилищем.

#!/usr/bin/perl
use strict;
use warnings;
use Device::SPI;

# Класс-обертка для PSRAM
package ESP_PSRAM;
sub new {
    my ($class, $dev) = @_;
    my $spi = Device::SPI->new(
        device => $dev,
        mode   => 0,
        speed  => 12000000, # 12 МГц
    );
    return bless { spi => $spi }, $class;
}

# Запись строки или буфера в память
sub write_block {
    my ($self, $addr, $data) = @_;
    my @payload = unpack("C*", $data); # Превращаем строку в массив байтов
    
    # Команда 0x02 + 24-битный адрес
    my @header = (
        0x02,
        ($addr >> 16) & 0xFF,
        ($addr >> 8)  & 0xFF,
        $addr & 0xFF
    );
    
    $self->{spi}->transfer([@header, @payload]);
    return length($data);
}

# Чтение блока данных заданной длины
sub read_block {
    my ($self, $addr, $len) = @_;
    
    # Команда 0x03 + 24-битный адрес + пустые байты для заполнения ответом
    my @tx = (
        0x03,
        ($addr >> 16) & 0xFF,
        ($addr >> 8)  & 0xFF,
        $addr & 0xFF,
        (0x00) x $len
    );
    
    my $rx_ref = $self->{spi}->transfer(\@tx);
    
    # Извлекаем только те байты, что пришли после команды и адреса (индексы 4+)
    my @data_bytes = @$rx_ref[4 .. $#$rx_ref];
    return pack("C*", @data_bytes); # Собираем обратно в строку
}

package main;

# Инициализация
my $psram = ESP_PSRAM->new('/dev/spidev0.0');

print "--- Тест ESP-PSRAM64H в 2026 году ---\n";

# 1. Запись текстового сообщения
my $message = "Привет от Raspberry Pi 3 через Perl SPI!";
my $start_address = 0x1000; # Записываем по адресу 4096

print "Запись данных...\n";
$psram->write_block($start_address, $message);

# 2. Чтение сообщения обратно
print "Чтение данных...\n";
my $result = $psram->read_block($start_address, length($message));

print "Результат из памяти: '$result'\n";

# 3. Тест на целостность
if ($message eq $result) {
    print "Успех: Данные идентичны.\n";
} else {
    print "Ошибка: Данные повреждены.\n";
}
  • Ключевые моменты для Perl:

    1. Функции pack/unpack: Используются для формирования бинарных пакетов. C* означает «unsigned char» (байты).

    2. Бинарная передача: Метод transfer в Device::SPI отправляет строку байтов и возвращает строку такой же длины, считанную с линии MISO одновременно с отправкой.

    3. Адресация: Не забывайте, что PSRAM требует 3 байта адреса (MSB first).

    4. Права доступа: Для запуска скрипта ваш пользователь должен входить в группу spi, либо запускайте скрипт через sudo

    Ограничение Perl для PSRAM: Perl не является языком реального времени и имеет накладные расходы на обработку массивов. Если ваша задача — перекачивать мегабайты данных из PSRAM на высокой скорости (например, для видео), Perl может стать «узким местом» по сравнению с C или Python (использующим C-библиотеки). Однако для хранения настроек или небольших буферов данных этот метод вполне надежен.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *