Формирование строки

anarch
Offline
Зарегистрирован: 10.09.2017
#include <Arduino.h>
#include <SPI.h>
#include <Wire.h>
#include <BME280I2C.h>
#include <LiquidCrystal_I2C.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include "Ethernet.h"
#include <PubSubClient.h>
#include "Button.h"

const uint32_t BAUD_RATE = 115200;
const uint8_t RESOLUTION = 12; // res in {9,10,11,12}
const uint16_t ONE_SECOND = 1000;

// Pin 0 -> Rx UART
// Pin 1 -> Tx UART
const uint8_t WATER_SENSOR_PIN = 2;
const uint8_t BUTTON_PIN = 3;
const uint8_t ONE_WIRE_BUS = 4;
// Pin A4 -> SDA I2C
// Pin A5 -> SCL I2C

Button button(BUTTON_PIN);
LiquidCrystal_I2C lcd(0x27, 16, 2);
BME280I2C sensor;
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature wSensor(&oneWire);
EthernetClient ethClient;
PubSubClient client(ethClient);

BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
BME280::PresUnit presUnit(BME280::PresUnit_torr);

struct Display
{
  String name;
  float data;
};

enum Menu
{
  PRESSURE,
  TEMPERATURE_AIR,
  HUMIDITY,
  WATER_FLOW,
  WATER_TEMPERATURE
};

Display display[]{
    {"Pressure ", NAN},
    {"Temp air ", NAN},
    {"Humidity ", NAN},
    {"Water flow ", NAN},
    {"Water temp ", NAN}};

byte MAC[]{0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xE7};
const IPAddress IP(192, 168, 1, 177);
const IPAddress SERVER(192, 168, 1, 1);
const char *MQTT_NAME = "my";
const char *MQTT_PASSWORD = "YMS";
const char *MQTT_TOPIC = "outTopic";

uint8_t count = 0;
volatile float pulseFreq;
DeviceAddress tempDeviceAddress;
uint16_t conversionTime;
uint32_t previosTimeWS;
uint32_t previosTimeShow;
uint32_t previosTimeConversion;
uint32_t previosTimeMQTT;

typedef void (*Do)();
void timer(uint32_t &, uint32_t, Do);
void show();
void onButton();
void WSinterrupt();
void reconnect();

void setup()
{
  // Настройка UART
  Serial.begin(BAUD_RATE);

  // Настройка датчика потока воды
  pinMode(WATER_SENSOR_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(WATER_SENSOR_PIN),
                  WSinterrupt, RISING);
  previosTimeWS = millis();

  // Настройка LCD дисплея
  lcd.init();
  lcd.backlight();
  lcd.home();

  // Настройка BME280 I2C
  if (!sensor.begin())
  {
    Serial.println("Could not find BME280 sensor!");
    delay(1000);
  }

  // Настройка Button
  button.setHandler(onButton);

  // Настройка датчика температуры воды DallasTemperature
  conversionTime = 750 / (1 << (12 - RESOLUTION));
  previosTimeConversion = millis();
  wSensor.begin();
  wSensor.getAddress(tempDeviceAddress, 0);
  wSensor.setResolution(tempDeviceAddress, RESOLUTION);
  wSensor.setWaitForConversion(false);

  // start the Ethernet connection:
  Ethernet.begin(MAC, IP);
  if (Ethernet.hardwareStatus() == EthernetNoHardware)
    Serial.println("Ethernet shield was not found.");
  else if (Ethernet.linkStatus() == LinkOFF)
    Serial.println("Ethernet cable is not connected.");

  // Настройка PubSubClient
  client.setServer(SERVER, 1883);
  randomSeed(micros());
}

void publish()
{
  if (!client.connected())
    reconnect();
  else
  {
    String bufer;
    bufer = "{\n\t\"Air temperature\" : " +
            (String)display[TEMPERATURE_AIR].data +
            ",\n\t\"Air pressure\" : " +
            (String)display[PRESSURE].data +
            ",\n\t\"Air humidity\" : " +
            (String)display[HUMIDITY].data +
            ",\n\t\"Water temperature\" :" +
            (String)display[WATER_TEMPERATURE].data +
            ",\n\t\"Water flow\" : " +
            (String)display[WATER_FLOW].data +
            ",\n}";
    uint8_t msgLen = bufer.length();
    Serial.print(bufer);
    client.beginPublish(MQTT_TOPIC, msgLen, false);
    client.print(bufer);
    client.endPublish();
  }
}

void loop()
{
  button.loop();
  sensor.read(display[PRESSURE].data,
              display[TEMPERATURE_AIR].data,
              display[HUMIDITY].data,
              tempUnit,
              presUnit);
  timer(previosTimeConversion, conversionTime, [] {
    display[WATER_TEMPERATURE].data = wSensor.getTempCByIndex(0);
    wSensor.requestTemperatures();
  });
  timer(previosTimeWS, ONE_SECOND, [] {
    // Liter/hour
    display[WATER_FLOW].data = pulseFreq * 60 / 7.5;
    pulseFreq = 0;
  });
  timer(previosTimeShow, ONE_SECOND, show);
  timer(previosTimeMQTT, 1000, publish);
}

void show()
{
  uint8_t _count = count;
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(display[_count].name + display[_count].data);
  if (++_count >= sizeof(display) / sizeof(display[0]))
    _count = 0;
  lcd.setCursor(0, 1);
  lcd.print(display[_count].name + display[_count].data);
}

/* Пересмотреть */
void reconnect()
{
  static unsigned long previousTimeFailed;

  if (millis() - previousTimeFailed > 5000)
  {
    Serial.print(F("Attempting MQTT connection..."));
    // Создание произвольного имени клиента
    String clientId = "Ethernet-";
    clientId += String(random(0xffff), HEX);
    // Попытка соединения с сервером
    if (client.connect(clientId.c_str(), MQTT_NAME, MQTT_PASSWORD))
    {
      Serial.println(F(" connected!"));
      /*
     .................................................................
     */
    }
    else
    {
      Serial.print(F(" failed, "));
      switch (client.state())
      {
      case MQTT_CONNECTION_TIMEOUT:
        Serial.print(F("the server didn't respond within the keepalive time"));
        break;
      case MQTT_CONNECTION_LOST:
        Serial.print(F("the network connection was broken"));
        break;
      case MQTT_CONNECT_FAILED:
        Serial.print(F("the network connection failed"));
        break;
      case MQTT_CONNECT_BAD_PROTOCOL:
        Serial.print(F("the server doesn't support the requested version of MQTT"));
        break;
      case MQTT_CONNECT_BAD_CLIENT_ID:
        Serial.print(F("the server rejected the client identifier"));
        break;
      case MQTT_CONNECT_UNAVAILABLE:
        Serial.print(F("the server was unable to accept the connection"));
        break;
      case MQTT_CONNECT_BAD_CREDENTIALS:
        Serial.print(F("the username/password were rejected"));
        break;
      case MQTT_CONNECT_UNAUTHORIZED:
        Serial.print(F("the client was not authorized to connect"));
        break;
      default:
        break;
      }
      Serial.println(F(" try again in 5 seconds"));
      previousTimeFailed = millis();
    }
  }
}

void onButton()
{
  if (++count >= sizeof(display) / sizeof(display[0]))
    count = 0;
}

void WSinterrupt()
{
  pulseFreq++;
}

void timer(uint32_t &previosTime, uint32_t interval, Do func)
{
  if (millis() - previosTime >= interval)
  {
    func();
    previosTime = millis();
  }
}

Если просто попробовать в loop(), все хорошо выводится в сериал.

String bufer;
    bufer = "{\n\t\"Air temperature\" : " +
            (String)display[TEMPERATURE_AIR].data +
            ",\n\t\"Air pressure\" : " +
            (String)display[PRESSURE].data +
            ",\n\t\"Air humidity\" : " +
            (String)display[HUMIDITY].data +
            ",\n\t\"Water temperature\" :" +
            (String)display[WATER_TEMPERATURE].data +
            ",\n\t\"Water flow\" : " +
            (String)display[WATER_FLOW].data +
            ",\n}";
Serial.println(burer);

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

Уже третий вечер сижу бьюсь ни чего сделать не могу :(

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

функцию в студию

Komandir
Offline
Зарегистрирован: 18.08.2018

Может банально места в памяти не хватает ?

anarch
Offline
Зарегистрирован: 10.09.2017

DetSimen пишет:

функцию в студию

Так яж код весь выложил.

void publish()
{
  if (!client.connected())
    reconnect();
  else
  {
    String bufer;
    bufer = "{\n\t\"Air temperature\" : " +
            (String)display[TEMPERATURE_AIR].data +
            ",\n\t\"Air pressure\" : " +
            (String)display[PRESSURE].data +
            ",\n\t\"Air humidity\" : " +
            (String)display[HUMIDITY].data +
            ",\n\t\"Water temperature\" :" +
            (String)display[WATER_TEMPERATURE].data +
            ",\n\t\"Water flow\" : " +
            (String)display[WATER_FLOW].data +
            ",\n}";
    uint8_t msgLen = bufer.length();
    Serial.print(bufer);
    client.beginPublish(MQTT_TOPIC, msgLen, false);
    client.print(bufer);
    client.endPublish();
  }
}

То что памяти не хватает уже задумывался. 

DATA:    [=======   ]  69.7% (used 1427 bytes from 2048 bytes)
PROGRAM: [========= ]  92.3% (used 29786 bytes from 32256 bytes
 
DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

и чо Serial.print(buffer)  пишет?

Komandir
Offline
Зарегистрирован: 18.08.2018

При компиляции bufer это указатель на строку. А в момент работы - под строку выделяется реальная память и судя по всему не малого размера.

Как вариант вынести объявление bufer из функции и разу задать максимальный размер.

anarch
Offline
Зарегистрирован: 10.09.2017

В функции ни чего, в лупе выводит.

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

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

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

Komandir пишет:

При компиляции bufer это указатель на строку. 

Хреналысого.  Это сразу локальный (стековый) обьект. 

Komandir
Offline
Зарегистрирован: 18.08.2018

DetSimen с размером указателя же. 

Я про то, что в "DATA:    [=======   ]  69.7% (used 1427 bytes from 2048 bytes)" не учтен размер строки от слова СОВСЕМ.

b707
Offline
Зарегистрирован: 26.05.2017

anarch - вариант решения - переписать функцию без использования String - памяти нужно будет раз в 10 меньше

anarch
Offline
Зарегистрирован: 10.09.2017

Урезал строку и все заработало 

void publish()
{
  if (!client.connected())
    reconnect();
  else
  {
    String bufer;
    bufer = "{\n\t\"Air temperature\" : " +
            (String)display[TEMPERATURE_AIR].data +
            ",\n\t\"Air pressure\" : " +
            (String)display[PRESSURE].data +
            ",\n\t\"Air humidity\" : " +
            (String)display[HUMIDITY].data +
            ",\n\t\"Water temperature\" :" +
            /* (String)display[WATER_TEMPERATURE].data +
            ",\n\t\"Water flow\" : " +
            (String)display[WATER_FLOW].data + */
            ",\n}"; 
    uint8_t msgLen = bufer.length();
    Serial.print(bufer);
    client.beginPublish(MQTT_TOPIC, msgLen, false);
    client.print(bufer);
    client.endPublish();
  }
}

 

Komandir
Offline
Зарегистрирован: 18.08.2018

Не удивлен.

anarch
Offline
Зарегистрирован: 10.09.2017

b707 пишет:

anarch - вариант решения - переписать функцию без использования String - памяти нужно будет раз в 10 меньше

Если решения со String не найдется буду переписывать.

 

Komandir
Offline
Зарегистрирован: 18.08.2018

А проверить вариант с объявлением вне функции и сразу достаточного размера ?

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

Komandir пишет:

DetSimen с размером указателя же. 

Нет. Размером класса сразу  но на стеке. Он обьявлен не как 

String *buffer;

Komandir
Offline
Зарегистрирован: 18.08.2018

Вкурил - String, это не string.

b707
Offline
Зарегистрирован: 26.05.2017

anarch пишет:

Если решения со String не найдется буду переписывать.

 

любое решение со String - временное, лучше сразу писать правильно

anarch
Offline
Зарегистрирован: 10.09.2017

Вот так вот переписал 

    char *temp = new char();
    char *buf = new char();
    strcat(buf, "{\n\t\"Air temperature\" : ");
    sprintf(temp, "%f", display[TEMPERATURE_AIR].data);
    strcat(buf, temp);
    strcat(buf, ",\n\t\"Air pressure\" : ");
    sprintf(temp, "%f", display[PRESSURE].data);
    strcat(buf, temp);
    strcat(buf, ",\n\t\"Air humidity\" : ");
    sprintf(temp, "%f", display[HUMIDITY].data);
    strcat(buf, temp);
    strcat(buf, ",\n\t\"Water flow\" : ");
    sprintf(temp, "%f", display[WATER_FLOW].data);
    strcat(buf, temp);
    strcat(buf, ",\n\t\"Water temperature\" :");
    sprintf(temp, "%f", display[WATER_TEMPERATURE].data);
    strcat(buf, temp);
    strcat(buf, ",\n}");
    uint8_t msgLen = strlen(buf);
    Serial.println(buf);
    client.beginPublish(MQTT_TOPIC, msgLen, false);
    client.print(buf);
    client.endPublish();
    delete[] temp;
    delete[] buf;

Выводит полностью. Но на выходе не получаю значений.

{
        "Air temperature" : ?,
        "Air pressure" : ?,
        "Air humidity" : ?,
        "Water flow" : ?,
        "Water temperature" :?,
}

Как преобразовать поле структуры float  в строку.

И о том что меньше занимает места я бы не сказал.

DATA:    [=======   ]  69.8% (used 1429 bytes from 2048 bytes)
PROGRAM: [==========]  96.2% (used 31018 bytes from 32256 bytes)
b707
Offline
Зарегистрирован: 26.05.2017

anarch пишет:

И о том что меньше занимает места я бы не сказал.

DATA:    [=======   ]  69.8% (used 1429 bytes from 2048 bytes)
PROGRAM: [==========]  96.2% (used 31018 bytes from 32256 bytes)

То что знамает меньше места - видно хотя бы по тому, что теперь строчка не теряется. А что ардуино показывает те же цифры - так и должно быть. Диагностика Ардуино учитывает в своих расчетах только глобальные переменные, а у вас тут работа с локальными.

b707
Offline
Зарегистрирован: 26.05.2017

значения теряются. потому что вы используете тип float. В ардуино sprintf с float не работает

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

dtostrf() наше все. 

anarch
Offline
Зарегистрирован: 10.09.2017

Всем спасибо! 

    char temp[7];
    char *buf = new char;
    strcat(buf, "{\n\t\"Air temperature\" : ");
    dtostrf(display[TEMPERATURE_AIR].data,6,1,temp);
    strcat(buf, temp);
    strcat(buf, ",\n\t\"Air pressure\" : ");
    dtostrf(display[PRESSURE].data,6,1,temp);
    strcat(buf, temp);
    strcat(buf, ",\n\t\"Air humidity\" : ");
    dtostrf(display[HUMIDITY].data,6,1,temp);
    strcat(buf, temp);
    strcat(buf, ",\n\t\"Water flow\" : ");
    dtostrf(display[WATER_FLOW].data,6,1,temp);
    strcat(buf, temp);
    strcat(buf, ",\n\t\"Water temperature\" :");
    dtostrf(display[WATER_TEMPERATURE].data,6,1,temp);
    strcat(buf, temp);
    strcat(buf, ",\n}");
    uint8_t msgLen = strlen(buf);
    client.beginPublish(MQTT_TOPIC, msgLen, false);
    client.print(buf);
    client.endPublish();
    delete[] buf;

 

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

anarch пишет:

Всем спасибо! 

    char temp[7];
    char *buf = new char;
    

Я ах..еваю, дорогая редакция! ;))) Я вааще фигею от здорового аптимизьмуса!

Komandir
Offline
Зарегистрирован: 18.08.2018

Что бы куча не скучала ...

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

wdrakula,нормально.

Чапаев нарисовал фальшивые 15 рублей, а сразу не сообразил, что такой купюры нет. Что делать? Послал Петьку разменять.
- Ну что, разменял?
- Ага.
- И что дали?
- 7 и 8

 

anarch
Offline
Зарегистрирован: 10.09.2017

wdrakula, готов выслушать ваши предложения по решению данного момента мне то же не нравиться.

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

anarch пишет:

wdrakula, готов выслушать ваши предложения по решению данного момента мне то же не нравиться.

Родное сердце! Если ты поймешь, почему я глумился, то это уже "путь к исцелению".

1. что, по твоему, делает строка:

char *buf = new char;

Вот четко опиши, основываясь не на своих домыслах, а на правилах языка.

2. Вообще  кто, где и когда выделяет память под твои строки и где указывает потребный размер памяти?

anarch
Offline
Зарегистрирован: 10.09.2017

Выделяет 1 байт памяти. 

Тоды задам по другому вопрос. Как выделять память не зная размер строки?

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

anarch пишет:

Тоды задам по другому вопрос. Как выделять память не зная размер строки?

Вы сами-то себя услышали? "Как выделить кусок памяти, не зная какого размера кусок нужен?"

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

ЕвгенийП пишет:

Вы сами-то себя услышали? "Как выделить кусок памяти, не зная какого размера кусок нужен?"

Похоже, существуют люди, уверенные, что компьютер умнее, чем они.

И возможно даже, они правы. (((((((((((

anarch
Offline
Зарегистрирован: 10.09.2017

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

Komandir
Offline
Зарегистрирован: 18.08.2018

Вы выделили память под один байт, а пихаете туда ... Хвост строки живет своей жизнью. Вам просто повезло что под этим хвостом не оказались важные данные. В Вашем случае очень легко вычисляется необходимый размер буфера под всю строку плюс один байт под 0. И ещё первой командой должна быть не strcat, а strcpy.

baby_in_Arduino
Offline
Зарегистрирован: 21.07.2019

весело тут у вас)) жду свою ардуинку и тоже буду вопросы глупые задавать)

Komandir
Offline
Зарегистрирован: 18.08.2018

baby_in_Arduin для старта плата даже не нужна - достаточно Proteus !

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

а для глупых вопросов не нужно ни того, ни другова. 

anarch
Offline
Зарегистрирован: 10.09.2017

Умные вопросы то же не стоит задавать :x

И так если я знаю размер буфера, что мне мешает сделать так:

char buf[200];

после того как блок отработает переменная удалиться (или просто исчезнет из области видимости?).

Т.е. смысл или когда нужно выделять динамически память?

 

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

Если это локальная переменная функции - то удалица, если глобальная то нет.

b707
Offline
Зарегистрирован: 26.05.2017

anarch пишет:

Т.е. смысл или когда нужно выделять динамически память?

имеет смысл, если у вас потребности в памяти постоянно меняются. Например, в большинстве случаев вам хватает строчки в 30 символов, а раз в месяц проскакивает запрос в 400. В этом случае нет смысла всегда держать буфер в 400 символов.

Или вариант когда вы даже приблизительно не знаете, сколько понадобится - в этом случае при каждом новом запросе сначала вычисляете его длину и отводите нужное количество памяти.

Komandir
Offline
Зарегистрирован: 18.08.2018

Динамическая память для тех случаев, когда неизвестно количество даже близко (от 0 до ... размеров доступной памяти) ! У Вас же явно можно посчитать сколько символов в тексте и сколько позиций надо под цифры.

baby_in_Arduino
Offline
Зарегистрирован: 21.07.2019

anarch пишет:

Умные вопросы то же не стоит задавать :x

И так если я знаю размер буфера, что мне мешает сделать так:

char buf[200];

после того как блок отработает переменная удалиться (или просто исчезнет из области видимости?).

Т.е. смысл или когда нужно выделять динамически память?

 

 

так и надо делать если объем данных невелик это намного быстрее работы с динамической кучей

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

вопрос опытным ардуинщикам какой максимальный размер буфера на стеке? вообще каков размер стека? какая область памяти "за стеком"? что мы потрем в случае если превысим максимальный размер буфера?

 

Komandir
Offline
Зарегистрирован: 18.08.2018

Стек растет вниз и может переехать даже через порты IO. Если же локальная переменная в стеке "перерастет" свои границы, то первым делом наедет на адрес возврата (иногда это делают осознанно) и возврат будет ХЗ куда.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

Komandir пишет:

Динамическая память для тех случаев, когда неизвестно количество даже близко (от 0 до ... размеров доступной памяти) ! У Вас же явно можно посчитать сколько символов в тексте и сколько позиций надо под цифры.

так этож тогда ассемблер получается )))

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

У нас статистики такой нету. Те, у каво стек переполнился, получают психологическую травму, несовместимую с нормальным функционирование арганизма, и на форумах больше не пишуть.

Komandir
Offline
Зарегистрирован: 18.08.2018

DetSimen надо взять на заметку как способ отписаться от форумов !!!

baby_in_Arduino
Offline
Зарегистрирован: 21.07.2019

Komandir пишет:

Стек растет вниз и может переехать даже через порты IO. Если же переменная в стеке "перерастет" свои границы, то первым делом наедет на адрес возврата (иногда это делают осознанно) и возврат будет ХЗ куда.

вот про ардуино уно пишут ОЗУ 2КБ в этих 2КБ и стек и куча? 

Komandir
Offline
Зарегистрирован: 18.08.2018

baby_in_Arduino плюс глобальные переменные :-)

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

baby_in_Arduino пишет:

Komandir пишет:

Стек растет вниз и может переехать даже через порты IO. Если же переменная в стеке "перерастет" свои границы, то первым делом наедет на адрес возврата (иногда это делают осознанно) и возврат будет ХЗ куда.

вот про ардуино уно пишут ОЗУ 2КБ в этих 2КБ и стек и куча? 

вот почему я за тебя искать должен?  Ты ленив, или просто сын депутата? 

http://robocraft.ru/blog/arduino/531.html

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

DetSimen пишет:

вот почему я за тебя искать должен?  Ты ленив, или просто сын депутата? 

http://robocraft.ru/blog/arduino/531.html

а нет какого метода прикрутить внешнюю память?

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

ua6em пишет:

а нет какого метода прикрутить внешнюю память?

научиться писать софт при ограниченных ресурсах железа

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

ua6em пишет:

а нет какого метода прикрутить внешнюю память?

Есть и даже на этом форуме описано. Великий как-то прикручивал. Доприкрутил ли - не помню, но постов было много.