Перейти к содержанию

Трёхканальный Вольтметр И Таймер На Arduino


Рекомендуемые сообщения

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

de17f802bd24251d237e66efe18059b9.jpg

Итак, разжившись платой Arduino Uno, я как-то начал почитывать офсайт, и совершенно незаметно для себя (примерно за полгода) дошёл до того, что сделал девайс со следующими возможностями:

- 3 независимых вольтметра, отображающие напряжения в милливольтах на LCD-дисплее (изначально подразумевалось использование в качестве термометров посредством микросхемы LM35 или аналогичной) с зумером, сигнализирующем, в случае, если напряжение на каком-либо входе превысило заданное значение

- 3 управляющих выхода для подключения нагрузки, работающие циклами: работа-отдых-работа-отдых, причём длительность работы и отдыха выставляется отдельно для каждого выхода при помощи одного-единственного (!) переменного резистора и двух кнопок

- Индикация состояния каждого из выходов на том же LCD-дисплее

- Отсутствие необходимости в самой плате Arduino для работы.

По отдельности это несложно. Про подключение дисплея к AVR уже даже специально сказано, что таких статей больше не нужно :), про наличие аналогового входа знают, наверное, все, код не требует особых ухищрений, а статья про ардуино на макетной плате есть на arduino.cc. Но я сам получил огромное удовольствие от разработки устройства и, надеюсь, кто-то тоже получит, повторив этот путь или просто читая про прогресс чайника в микроконтроллерах :)

О каждой части устройства будет рассказано отдельно и максимально подробно. Существенные вопросы по теме задавать можно и нужно, с несущественными, пожалуйста, подождите окончания повествования :)

Ссылка на комментарий
Поделиться на другие сайты

Реклама: ООО ТД Промэлектроника, ИНН: 6659197470, Тел: 8 (800) 1000-321

Начинаю. 1.Схема, код и документация.

Наверное, первое, что надо знать, это сайт arduino.cc. Он на английском и на нём есть всё, в частности инструкция по установке, параметры платы (напомню, Arduino Uno rev.3) и чертова туча примеров, благодаря которым цель и была достигнута :)

Давайте пойдём от конца. Печатная плата выглядит так: (фотовид мне не понравился, кстати)

da24d8c931cd6201b5ccf7c40817b2dc.png

Это вид сверху "сквозь" текстолит. Соответственно ноги микросхемы будут нумероваться с левой верхней против часовой стрелки.

Электрическую схему рисовать не вижу смысла - она будет примитивна. Это просто разводка ног микросхемы на разъёмы так, чтобы к ним удобно было подключать всё, что нужно.

Есть очень удобная картинка выводов микросхемы и ей место здесь:

d61df15791cf558c95933342a3556d13.png

Теперь кое-что в схеме должно стать понятно. На левой картинке обозначена линия "GND", то есть минуса питания, на правой - "Vcc", то есть плюс, и не абы сколько, а 5 вольт. Дорожек две и их надо будет не забыть замкнуть перемычкой. Трёхногое нечто слева вверху (корпус TO-220) - линейный регулятор напряжения L7805 (или КРЕН'ка на 5В). Средняя нога - земля, нижняя (на печатке) - вход, куда будет подключаться 9-вольтовая батарейка, верхняя - выход 5-ти вольт. Два смд конденсатора нужны для более стабильной работы, я впаял по 10мкФ на всякий случай.

4c80be334ef63364a39f05119209c0cb.png51c69670d94a53f7ac86e3f560d495e2.png

Ряд 5-ти контактов слева вверху - для программирования. Группа шести контактов чуть ниже - выходы, как видно, они идут на 4,5 и 6 ноги микросхемы, т.е цифровые выходы. Девять контактов по другую сторону и есть вольтметры (разъёмы на +5В и землю нужны для подключения датчика LM35, который сам нуждается в питании; каждому под его нужды отведён столбец 3-х контактов). Восемь контактов снизу - под дисплей: шесть сигнальных, +5В и земля. Кварцевый резонатор подключен прямо туда, куда он и предназначается (см. распиновку), а два смд конденсатора на 24пФ настоятельно рекомендуются знающими людьми :)

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

int z = 0; //зарезервировано для коротких циклов
long sum[] = {0,0,0,0};

//long sum = 0;
int sensor[] = {A0,A1,A2,A3};
int sensors = 4; //всего сенсоров
unsigned long mm = 0;
int i = 0;
int menu = 0; //включенный режим
int enable = 0; //для кнопки
int thres = 4000;
float timezero = 1;
unsigned long time[] = {1,1,1,1,1,1,1,1}; //время: 0)показание датчика 1,2 - время работы и отдыха
int work[] = {0,0,0,1}; //изначально работают или нет
int workpin[] = {2,3,4}; //пины к устройствам
int ports = 3; //число активных портов
unsigned long switchtime[] = {0,0,0,0};
unsigned long LastPress = 0;
int index = 0;
int speaker = 11;
int butOne = 12;
int butTwo = 13;
// include the library code:
#include <LiquidCrystal.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(5, 8, 7, 6, 9, 10);
void setup() {
// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
// Print a message to the LCD.
lcd.print("Hello, world!");
//12,13 - buttons, 10-12 - load
pinMode(butOne, INPUT_PULLUP);
pinMode(butTwo, INPUT_PULLUP);
pinMode(speaker, OUTPUT);
LastPress = millis();
for(z=0; z<ports; z++) {
switchtime[z] = millis();
pinMode(workpin[z], OUTPUT);
digitalWrite(workpin[z], LOW);
}
for(int z = 1; z<(2*ports + 2); z++) {
time[z] = 100000000; //в начале время выставляется большим, чтобы порты не переключались бешено
}

}
void loop() {

for (int z=0; z<4; z++)
{
sum[z] = sum[z] + analogRead(sensor[z]);
}
i++;
//delayMicroseconds(10);

//режим настройки
if(digitalRead(butOne) == HIGH) {
enable = 1;
}

if(enable == 1) { //защита от двойных срабатываний
if (digitalRead(butOne)==LOW) {
menu++;
enable = 0;
LastPress = millis();
}
}

if (menu >= 11) {
menu = 0;
}

if(menu >= (2*ports + 1)) {
menu = 10;
}


if((millis() - LastPress)>20000 ) { //если последнее нажатие больше 20 секунд назад - сброс
menu = 0;
LastPress = millis();
}

//настройка выводов
if (menu!=0) {
if(digitalRead(butTwo) == LOW) {
if (menu <10) {
time[menu] = time[0];
}
}
}


//управление портами

for(int z=0; z<ports; z++) {
long interval = millis() - switchtime[z];
int num = 2*z + 1;
if(work[z]==1) { //если работает, считаем время работы
if(interval>time[num]) {
work[z] = 0;
digitalWrite(workpin[z], LOW);
switchtime[z] = millis();
}
}
else {
if(interval>time[num+1]) {
work[z] = 1;
digitalWrite(workpin[z], HIGH);
switchtime[z] = millis();
}
}

}



if (i==999) { //обработка данных по прошествии 1000 циклов
digitalWrite(speaker, LOW);

for(int z = 0; z<sensors; z++) {
sum[z] = sum[z]*5/1023;


//speaker check

if (sum[z]>thres) {
digitalWrite(speaker, HIGH);
}
}




int c = sum[0]/20;
timezero = 1;
for(int k = 0; k<c;k++) {
timezero = 1.08*timezero;
}
time[0] = (unsigned long)timezero; //переводим в long

lcd.clear();
lcd.setCursor(0,0);
if(menu != 0) { //отображаем время только в ненулевом режиме
if(digitalRead(butTwo) == HIGH) {
time[0] = time[menu];
}
else {
if (menu==10) {
thres = sum[0];
}
}


if(time[0]>7200000) {
lcd.print(time[0]/3600000);
lcd.print("h");
}
else {
if(time[0]>100000) {
lcd.print(time[0]/60000);
lcd.print("min");
}
else {
if (time[0]>5000) {
lcd.print(time[0]/1000);
lcd.print("s");
}
else {
lcd.print(time[0]);
lcd.print("ms");
}
}
}
}
lcd.setCursor(10,0);
//lcd.print(millis() - mm); //пишем время выполнения 1000 циклов
mm = millis();

for(z = 1; z<sensors; z++) {
lcd.setCursor(5*(z-1), 1);
lcd.print(sum[z]);
}
/*
//отладка
lcd. setCursor(5,1);
lcd.print(millis());
*/

lcd.setCursor(14,0);

switch (menu) {
case 0:
lcd.setCursor(0,0); //в нулевом режиме отображается работа и отдых портов
for (int z=0; z<ports; z++) {
lcd.setCursor(5*z,0);
float percent = (float)(millis() - switchtime[z]);
percent = 100*percent;

if(work[z] == 0) {
lcd.print("R");
index = 2*z+2;

}
else {
lcd.print("W");
index = 2*z + 1;
}
percent = percent/time[index];
lcd.print(percent);
}

break;
case 1:
lcd.print("W1"); //длительность работы первого порта
break;
case 2:
lcd.print("R1"); //длительность отдыха первого
break;
case 3:
lcd.print("W2");
break;
case 4:
lcd.print("R2");
break;
case 5:
lcd.print("W3");
break;
case 6:
lcd.print("R3");
break;
case 10:

lcd.print("TH");
lcd.setCursor(0,0);
lcd.print(thres);
lcd.print(" ");
break;
}

for (z = 0; z<sensors; z++) {
sum[z] = 0;
}
i = 0;
}
}

Ссылка на комментарий
Поделиться на другие сайты

20% скидка на весь каталог электронных компонентов в ТМ Электроникс!

Акция "Лето ближе - цены ниже", успей сделать выгодные покупки!

Плюс весь апрель действует скидка 10% по промокоду APREL24 + 15% кэшбэк и бесплатная доставка!

Перейти на страницу акции

Реклама: ООО ТМ ЭЛЕКТРОНИКС, ИНН: 7806548420, info@tmelectronics.ru, +7(812)4094849

2. Дисплей.

Я всё-таки расскажу, как его подключать :) Нужен дисплей с драйвером Hitachi HD44780, они узнаются по 16-пиновому интерфейсу. Я использовал WH1602, даташит на него в аттаче. По факту из 16 пинов нужно 10, а проводов нужно всего 8.

Питание и земля (Vcc и GND) подключаются понятно куда. Есть 2 контакта, которые в примере на arduino.cc ведутся отдельными проводами, но этого можно не делать. Это R/W (в моём случае 5-ый контакт), который надо замкнуть с тем, на который приходит 5В (первый ), и Contrast Adjustment, напряжение на котором задаст контраст изображения. Менять его (контраст, т.е) в процессе смысла мало, поэтому нужно собрать делитель на 2 резисторах и подбором номиналов найти нужные. Затем этот делитель впаять прямо на плату дисплея, между 5В и землёй. В моём случае это 3КОма на +5В и 760Ом на землю.

Использоваться же будут следующие контакты: RS, E (H/L), DB0, DB1, DB2, DB3. Итого 6 контактов, к ним удобно припаять кусок шлейфа со штыревым разъёмом на конце. Именно в таком порядке, и развести на любые цифровы пины микроконтроллера. Здесь они идут на 5,8,7,6,9 и 10 пины в представлении Arduino (см. распиновку). Чтобы начать работать с дисплеем, надо до основного кода записать номера этих пинов, как это сделано:

LiquidCrystal lcd(5, 8, 7, 6, 9, 10);

И в setup (часть кода, выполняющаяся один раз при включении питания или перезагрузке) прописать

lcd.begin(16,2);

Если дисплей в длину не 16 символов и/или в высоту не 2 строчки, эти значения надо будет изменить. Всё! Теперь с помощью двух функций - lcd.setCursor() и lcd.print() можно выводить всё, что угодно. Надо только не забыть, что символы и строчки нумеруются с нуля.

Если дисплей не работает (видно, что контрастность выставлена, но отображается хрень или не отображается ничего) - косяк с проводами. Я сам несколько часов убил на поиски бага с дисплеем, пока не вспомнил, что номера пинов на нижнем разъёме идут не подряд, а я упорно писал в коде LiquidCrystal lcd(5, 6, 7, 8, 9, 10); :)

Есть, конечно, режим "бегущей строки", TFT дисплей и куча других фич, но уже в самом простом варианте функциональность оказалось достаточна.

Тварищ Desert _Eagl.5, ты пришёл в раздел Arduino, чтобы кричать, что всё это - хрень? Так пиз .. ва.. почему бы не направиться в раздел AVR и не засорять тему, тем более, что я специально в начале об этом попросил?

Display_WH1602D.pdf

Изменено пользователем Q109
Ссылка на комментарий
Поделиться на другие сайты

Особенности хранения литиевых аккумуляторов и батареек

Потеря емкости аккумулятора напрямую зависит от условий хранения и эксплуатации. При неправильном хранении даже самый лучший литиевый источник тока с превосходными характеристиками может не оправдать ожиданий. Технология, основанная на рекомендациях таких известных производителей литиевых источников тока, как компании FANSO и EVE Energy, поможет организовать правильный процесс хранения батареек и аккумуляторов. Подробнее>>

Реклама: АО КОМПЭЛ, ИНН: 7713005406, ОГРН: 1027700032161

3. Кнопки и меню.

До недавнего времени я не имел ни малейшего представления о том, как запрограммировать нечто, похожее на меню, скажем, мобильника (старых моделей, без сенсорных экзанов :) ) или хотя бы наручных часов. И до сих пор не знаю, где про это можно прочитать и как это обычно делается. Но допёр-таки сам.

Нужна переменная, которая будет отвечать за то, что отображать на экране - основную информацию, или значение какой-нибудь переменной, или подсказку, какой параметр сейчас будет выставляться. Переменную назовём menu; она будет меняться от 0 (главный экран, типа) до 9. И каждое нажатие кнопки будет увеличивать значение, а по достижении 10 будет производиться сброс обратно в 0.

if(digitalRead(butOne) == HIGH) {
   enable = 1;
 }

 if(enable == 1) { //защита от двойных срабатываний
 if (digitalRead(butOne)==LOW) {
   menu++;
   enable = 0;
   LastPress = millis();
 }
 }

 if (menu >= 11) {
   menu = 0;
 }

Вышенаписанный кусок отвечает именно за это. Значение menu увеличиевается, если на входе низкий уровень, то есть он замкнут кнопкой на землю ( если кнопка разомкнута, уровень высокий, т.к в настройках прописано INPUT_PULLUP, что означает, что вход будет подтянут к +5В посредством внутреннего резистора). Следующий раз значение сможет увеличиться только после отпускания и очередного нажатия кнопки, для этого понадобилась новая переменная enable.

Помимо этого полезной оказалась следующая фича:

if((millis() - LastPress)>20000 ) { //если последнее нажатие больше 20 секунд назад - сброс
   menu = 0;
   LastPress = millis();
 }

millis() - это функция, возвращающая количество миллисекунд, прошедших с момента запуска. Теперь нет необходимости перещёлкивать обратно в "нулевой" режим, который наиболее информативен. (нажатия кнопок всё-таки утомляют :) )

Дальше используется волшебное слово case.

case 1:
   lcd.print("W1"); //длительность работы первого порта
   break;
   case 2:
   lcd.print("R1"); //длительность отдыха первого
   break;
   case 3:
   lcd.print("W2");
   break;
   case 4:
   lcd.print("R2");
   break;

Нулевой режим чуть сложнее, про него пока не буду. Здесь же разница только в отображаемой подсказке, говорящей, какое из времён сейчас будем устанавливать. Происходит же установка в этом куске кода:

int c = sum[0]/20;
   timezero = 1;
   for(int k = 0; k<c;k++) {
  timezero = 1.08*timezero;
   }
   time[0] = (unsigned long)timezero; //переводим в long

   lcd.clear();
   lcd.setCursor(0,0);
   if(menu != 0) { //отображаем время только в ненулевом режиме
  if(digitalRead(butTwo) == HIGH) {
    time[0] = time[menu];
  }
  else {
    if (menu==10) {
	  thres = sum[0];
    }
  }


   if(time[0]>7200000) {
  lcd.print(time[0]/3600000);
  lcd.print("h");
   }
   else {
   if(time[0]>100000) {
  lcd.print(time[0]/60000);
  lcd.print("min");
   }
   else {
   if (time[0]>5000) {
  lcd.print(time[0]/1000);
  lcd.print("s");
   }
   else {
  lcd.print(time[0]);
  lcd.print("ms");
   }
 }
 }
 }

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

Если же вторая кнопка нажата, то считываем напряжение на "нулевом" аналоговом входе, том самом, к которому подключается средний вывод переменного резистора. По экспоненциальному закону переводим его в миллисекунды записываем это значение в соответствующее место. Пока кнопка удерживается нажатой, можно перемещением движка переменника выставлять время от 3 миллисекунд до 50 часов. И отображаться, естественно, будет уже свежевыставленное время.

Дальше уже просто - в функции loop() (она выполняется циклически всё время работы контроллера) считывается текущее время и проверяется, не пора ли сменить работу на отдых какому-либо выходу. Извращения с переменной percent нужны для того, чтобы видеть, какая часть от отведённого времени уже прошла, что очень полезно.

Тут я не могу не упомянуть баг, поиск которого занял у меня очень много времени. Утверждается, что значение функции millis() обнуляется каждые 50 дней - банально переполняются все разряды. Я же наблюдал, что по прошествии 10 часов после запуска значение percent становится отрицательным. Не сразу понял, что ошибка в том, что чтобы вычислить, какая часть отведённого времени, скажем, работы, прошла, я брал текущее значение millis(), вычитал из него время, когда выход был переключен на работу, умножал на 100 и делил на отведённое время работы. Так вот, надо было сначала делить, а не умножать - переполнение разрядов происходило не через 50 дней, а ровно в 100 раз быстрее. Просто слишком большое число получалось в процессе.

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

Ссылка на комментарий
Поделиться на другие сайты

Секреты депассивации литиевых батареек FANSO EVE Energy

Самыми лучшими параметрами по энергоемкости, сроку хранения, температурному диапазону и номинальному напряжению обладают батарейки литий-тионилхлоридной электрохимической системы. Но при длительном хранении происходит процесс пассивации. Разберем в чем плюсы и минусы, как можно ее избежать или уменьшить последствия и как проводить депассивацию батареек на примере продукции и рекомендаций компании FANSO EVE Energy. Подробнее>>

Реклама: АО КОМПЭЛ, ИНН: 7713005406, ОГРН: 1027700032161

4. Измерения

Самая простая часть. Есть чудесные микросхемы - термометры (одна из них - LM35 http://cxem.net/house/1-341.php ), выдающие 10 или 20 мВ на каждый градус температуры корпуса. Благодаря встроенному в ATMega328 аналого-цифровому преобразователю (и их аж 6 штук!) всё реализуется очень просто. Функция analogRead() требует номер порта (в Arduino они называются A0...A5) в качестве аргумента, а возвращает число от 0 до 1023, пропорциональное напряжению. То есть в теории напряжение можно знать с точностью до процента. Но так не выходит, приходится много раз измерять и усреднять. У меня это происходит 1000 раз, что занимает, кстати говоря, суммарно аж 400мс. И как видно по коду, после каждого 1000-ного измерения сумма всех результатов для каждого сенсора усредняется и переводится в милливольты. Тогда же и проводится отображение на экран - обновлять его чаще нет смысла.

Необходимость такого достаточно длительного измерения делает задачу чуть сложнее: у Ардуины большие сложности с многозадачностью, поэтому, вместо того, чтобы писать в main() цикл из 1000 повторений, приходится использовать в качестве цикла сам main(), в нём же проверяя каждый раз, не пора ли переключить состояние какого-нибудь из выходов. Банальность, но для новичка всё удивительно :)

Естественно, нынешний вариант имеет существенные недостатки. Во-первых, та самая длительность измерения. Ничто не мешает усреднять не 1000, а 100 значений, несколько пожертвовав при этом точностью. Но это не самый красивый вариант, поскольку мне самому не очень верится в точность встроенного АЦП, да и допустимая длина проводов весьма ограничена. Вскоре я стал облизываться на цифровые датчики с интерфейсом 1-wire, но они дороже, сложнее в освоении, и, по-моему, не шибко быстрее. Зато двух проводов хватает для неограниченного количества датчиков.

Есть также режим, про который я уже упоминал - индикатор перегрева. Выставление предела срабатывания (в милливольтах же) идёт в меню последним пунктом, и если напряжение на каком-либо из выходов превышает пороговое, на ноге, названной в коде speaker, выставится высокий уровень. Этого хватит, чтобы запитать пятивольтовую "пищалку". Под неё выделено место на плате, но самой пищалки у меня пока нет, надеюсь это скоро исправится. Кстати, чтобы исключить ложные срабатывания, все неиспользуемые входы АЦП надо заземлить, или сократить количество используемых портов в коде.

Ссылка на комментарий
Поделиться на другие сайты

5. Программируемые выходы и прошивка.

Сейчас заметил, что не рассказал, для чего нужен блок контактов слева от платы. Верхние шесть - три по два - это и есть выходы, нумерацию которых можно проследить по распиновке и массиву int workpin(). Один раз прописать все 3 порта в начале кода оказалось удобнее, чем писать их явно - иначе легко перепутать и трудно править, если понадобится что-то изменить в схеме. А так их легко даже поменять местами, изменив циферки всего в одном месте. Очередное банальное открытие новичка :)

Сам МК способн выдавать порядка 15мА на каждый из выводов. Этого достаточно, чтобы запитать любую управляющую схему, включая оптореле на 25А. Для низковольтных нагрузок лучше использовать какой-нибудь MOSFET (некоторые имеют сопротивление в открытом состоянии всего 50мОМ). Была идея поставить мосфеты прямо на эту же плату - оказывается, они бывают не только в здоровых корпусах типа TO-220 или D2PAK, но и в SOT23, как пример - IRLML0030. 30 вольт, 5 ампер постоянного тока, 27миллиом в открытом состоянии, и это при размерах 2*3мм! Кстати, ничто не мешает сделать отдельную плату с мощными мосфетами, к которой будет подключаться внешний мощный источник питания - литиевый аккумулятор, например.

Такую плату я делать пока не стал, а вот индикацию сигнала на выходе всё-таки добавил. Для этого и нужны ещё 6 отверстий под основными - туда впаиваются светодиоды, "плюс" которых через резисторы подсоединяется к соответствующей дорожке, идущей от контроллера. Навесной монтаж тут оказался очень даже уместен.

Таким образом, все выводы оказываются задействованы - 3 выхода, 2 под кнопки, 1 под зумер, 6 под дисплей. Остаются ещё 2 неработающих входа - А4 и А5 в правом верхнем углу. Несмотря на то, что изначально они считаются входами АЦП, их можно сконфигурировать в качестве обычных цифровых входом или выходов.

Чем мне сразу приглянулась Arduino Uno, так это тем, что из неё можно вытащить микроконтроллер. В принципе, можно залить в него код (и бутлодер, если его ещё нет), вытащить и вставить в разъём девайса. Если понадобится - вытащить, вернуть на место, прошить и снова переставить. Это не очень удобно, гораздо проще подвести провода от платы Arduino, которая (со снятым с неё МК) вполне годится как программатор. Проводов нужно 5 штук - земля, Tx, Rx, Reset (!) и +5В. Последний можно не вести, если девайс подключён к собственному питанию. Удобно сразу сделать некое подобие шлейфа, это поможет избегать ошибок при подключении. А если есть программатор AVR, прошивать можно и им.

IRLML0030_ds.pdf

Изменено пользователем Q109
Ссылка на комментарий
Поделиться на другие сайты

Спасибо за труд и терпение, уважаемый Q109!

Очень подробно и доходчиво излагаете материал!

Вот только огорчает отсутствие обильных комментариев в тексте програмы. Для новичков это очень было бы полезно..

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

...Пока без дополнительных пояснений, хотя язык настолько прост, что кое-где они и не нужны....

Ссылка на комментарий
Поделиться на другие сайты

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

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

Ссылка на комментарий
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы публикуете как гость. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.

Гость
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Ответить в этой теме...

×   Вставлено с форматированием.   Восстановить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

Загрузка...
  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...