Доброго дня всяк сюда входящий!
Чего только не придумывают люди сидящие в заточении самоизоляции! Одно радует, подопечные с работы меньше достают, и есть время позаниматься любимым хобби. Я радиолюбитель с пеленок, и даже позывной есть R6DCZ. Иногда грею ионосферу передатчиком в 1,0кВт, за что имею разные разговоры с инспекцией по электросвязи. Ну да ладно... мелкие шалости. Так вот... нарисовалась задача исследовать амплитудно-частотную характеристику некоторых узлов, которые я сконструировал для очередного приемника. Раньше (при СССР) был доступ к любой измерительной аппаратуре и проблем не было. В настоящее же время приходится выкручиваться всякими подручными средствами купленные по случаю у китайцев на Али или на радиорынках. Исследовать АЧХ нам поможет эквивалент измерительного прибора Х1-47 собранный на коленках из Ардуины, модуля синтезатора частот на AD9850, небольшой обвески из дискретных компонентов и, конечно же, обработка на 1С!
Приступим
Осуществим сборку "прибора" согласно принципиальной схемы.
У меня получилось на монтажной доске вот такое изделие
Черная коробочка рядом с доской - это логический анализатор DSLogic. Классная вещь для хака различных протоколов обмена. При помощи него удалось подобрать специфические параметры для стабильного обмена 1С с ардуинкой. Но это позже.
Ищем Arduino IDE, и загружаем скетч в Ардуино.
#define W_CLK 13 //Тактирование AD9850
#define FQ_UD 10 //Запуск AD9850
#define DATA 11 //Данные установки частоты
#define pulseHigh(pin) {digitalWrite(pin, HIGH); digitalWrite(pin, LOW); } //Строб импульс
#define INP_VAL A0 //Выход исследуемой цепи (детектор)
#define INP_OUT A1 //Вход исследуемой цепи (выход генератора)
long freq = 3500000; //Частота по умолчанию 3,5 МГц
String serial_readline = "";
const int bSize = 64; //Длина буфера
char Buffer[bSize]; //Буфер приема данных RS232
float vin = 0;
float vout = 0;
float ratio = 0; //Коэфф. передачи исследуемой цепи
void setup()
{
pinMode(FQ_UD, OUTPUT);
pinMode(W_CLK, OUTPUT);
pinMode(DATA, OUTPUT);
sendFrequency(freq);
Serial.begin(115200); //Настройка COM порта
Serial.setTimeout(100);
}
//Главный цикл
void loop()
{
while (Serial.available() > 0)
{
memset(Buffer, 0, bSize);
Serial.readBytesUntil('\n',Buffer, bSize);
serial_readline = String(Buffer);
if (serial_readline.startsWith("GET "))
{
long fr_Start = (long)serial_readline.substring(4, 14).toFloat();
long fr_End = (long)serial_readline.substring(15, 24).toFloat();
long fr_Step = (long)serial_readline.substring(25).toFloat();
for (freq = fr_Start; freq <= fr_End; freq += fr_Step)
{
sendFrequency(freq);
delay(2);
readInVoltage();
String freq_str = "000000000" + String(freq) + " ";
String out_str = freq_str.substring(freq_str.length() - 10) + String(ratio, 6);
Serial.println(out_str);
}
}
Serial.flush();
}
}
//Измерения
void readInVoltage()
{
vin = 0;
vout = 0;
ratio = 0;
for (int i = 0; i < 16; i++)
{
vin = vin + ((float)analogRead(INP_VAL) / 1023 * 5);
vout = vout + ((float)analogRead(INP_OUT) / 1023 * 5);
}
vin = vin / 16;
vout = vout / 16;
ratio = vin / vout;
}
// Обслуживание AD9850
void tfr_byte(byte data)
{
for (int i = 0; i < 8; i++, data >>= 1) {
digitalWrite(DATA, data & 0x01);
pulseHigh(W_CLK);
}
}
void sendFrequency(double frequency) {
int32_t freq = frequency * 4294967295 / 125000000;
for (int b = 0; b < 4; b++, freq >>= 8) {
tfr_byte(freq & 0xFF);
}
tfr_byte(0x000);
pulseHigh(FQ_UD);
}
С железом закончили. Займёмся 1С
Готовую к использованию обработку я выкладывать в виде отдельного файла не буду. Опубликую только код. Там все понятно. В конфигураторе нарисуем форму вот такого вида
В модуле формы запишем следующий код
Перем КомПорт,Серия,Ответ;
Процедура ОсновныеДействияФормыОсновныеДействияФормыВыполнить(Кнопка)
Команда = "GET ";
Команда = Команда + Прав("000000000" + Старт, 9)+" ";
Команда = Команда + Прав("000000000" + Стоп, 9)+" ";
Команда = Команда + Прав("000000000" + Шаг, 9)+Символы.ПС;
Если (КомПорт.PortOpen=1) Тогда
ЭлементыФормы.Диаграмма1.Очистить();
Итераций = Цел((Число(Стоп)-Число(Старт))/Число(Шаг));
//0.01 - магическое число, без него не работает
Интервал = Цел(Итераций*0.01)+1;
Ответ = Новый Массив;
КомПорт.Output=Команда;
ПодключитьОбработчикОжидания("Отрисовать", Интервал, Истина);
Иначе
Сообщить("Порт не открыт!");
КонецЕсли;
КонецПроцедуры
Процедура ПолучитьДанные()
Ответ.Добавить(Компорт.Input);
КонецПроцедуры
Процедура Отрисовать()
Если (Ответ.Количество()>0) Тогда
ТекстФ = Новый ТекстовыйДокумент;
Для Каждого Стр_Ответ Из Ответ Цикл
ТекстФ.ДобавитьСтроку(Стр_Ответ);
КонецЦикла;
Для Индекс = 1 По ТекстФ.КоличествоСтрок() Цикл
Текст = ТекстФ.ПолучитьСтроку(Индекс);
Если СокрЛП(Текст)="" Тогда
Продолжить;
КонецЕсли;
Точка = ЭлементыФормы.Диаграмма1.УстановитьТочку(Число(Лев(ТекстФ.ПолучитьСтроку(Индекс),9)));
Серия = ЭлементыФормы.Диаграмма1.УстановитьСерию("Kp");
Попытка
Если Линейная Тогда
ЭлементыФормы.Диаграмма1.УстановитьЗначение(Точка,Серия,Число(Прав(ТекстФ.ПолучитьСтроку(Индекс),8)));
Иначе
ЭлементыФормы.Диаграмма1.УстановитьЗначение(Точка,Серия,20*log10(Число(Прав(ТекстФ.ПолучитьСтроку(Индекс),8))));
КонецЕсли;
Исключение
Сообщить(Текст);
КонецПопытки;
КонецЦикла;
ЭлементыФормы.Диаграмма1.Обновление = Истина;
Иначе
Сообщить("Нет данных!");
КонецЕсли;
КонецПроцедуры
Процедура Кнопка1Нажатие(Элемент)
ТекущееВремя = ТекущаяУниверсальнаяДатаВМиллисекундах();
Если (КомПорт.PortOpen=1) Тогда
КомПорт.PortOpen=0;
ЭлементыФормы.Кнопка1.Заголовок = "Connect";
Иначе
КомПорт.CommPort = НомерПорта;
КомПорт.Settings = СтрокаИни;
КомПорт.RThreshold = 20;//Количество символов в ответе
КомПорт.Handshaking = 1;
КомПорт.PortOpen = 1;
ЭлементыФормы.Кнопка1.Заголовок = "Disconnect";
КонецЕсли;
Пока (ТекущаяУниверсальнаяДатаВМиллисекундах()-ТекущееВремя < 2000) Цикл
//Ожидаем около 2 сек для открытия/закрытия канала передачи данных
КонецЦикла;
КонецПроцедуры
Процедура ПриЗакрытии()
Если (КомПорт.PortOpen=1) Тогда
КомПорт.PortOpen=0;
Компорт="";
КонецЕсли;
КонецПроцедуры
//Значения по умолчанию
НомерПорта = 12;
СтрокаИни = "115200,N,8,1";
Старт = "400000";
Стоп = "500000";
Шаг = "250";
//Инициализация
КомПорт=Новый COMОбъект("MsCommLib.MsComm.1");
ДобавитьОбработчик КомПорт.OnComm,ПолучитьДанные;
Здесь необходимы некоторые пояснения. Как известно MSCommControl - крайне капризная штука, особенно когда работает с виртуальным COMM портом, созданным USB соединением. Да и проинсталировать эту компоненту на современные 64bit ОС - это отдельная песня. На Инфостарте эту проблему уже обсуждали. У меня резонный вопрос к разработчикам платформы 1С - Что, за 20 лет развития, не хватило сил для реализации встроенных объектов для управления сокетами и портами на уровне платформы? Т.е. для мобильной "Телефонию" запилили , а для десктопа?
По поводу свойства компоненты RThreshold. Туда пишется число полученных байт, при достижении которых сработает событие onComm. Благодаря анализатору стало понятно сколько вешать в граммах.
Магическое число в 10 ms вычислил там же. Оно необходимо для запуска обработчика ожидания. Интервал ожидания взял с запасом плюс 1 секунда.
Ну и полученный результат измерения АЧХ, точнее коэффициента передачи в полосе частот, колебательного контура на 465 кГц в линейном и логарифмическом масштабе. На картинках виден резонанс. Именно этого я и добивался.
Глядя на картинки напрашивается какой нибудь цифровой фильтр чтобы сгладить шум в измерениях. Но это другая история.
Пожалуй все.Благодарю за внимание.
Будут вопросы,отвечу.
Upd1: На управляемой форме, качаем бесплатно :)
Upd2: от 06.05.2020 - про сеть
Я обещал читателю event-driven networking с 1С и Ардуинкой. Получите... распишитесь... Не забываем плюсики на статью вешать. Не жалко же :) , я надеюсь...
Исходные данные таковы. Имеем плату MEGA2560, ethernet шилд на базе W5100 и комнатный датчик DHT11. Выглядит вот так:
Вместо Меги можно Леонардо или УНО R3 задействовать. Без разницы. Можно и Нано, только тогда монтажными перемычками нужно все правильно соединить. Маленький лайфхак по поводу эзернет шилда. Иногда китайцы не те номиналы паяют на платы. Внимательно смотрите сюда:
Если у Вас резисторная сборка такого номинала, придется поработать с паяльником. Если номинал 510 - ничего не делаем.
Найдите пару резисторов с номиналом 100 ом и напаяйте таким образом. Сделайте это аккуратно, без соплей и коротышей.
В результате этой процедуры получите устойчивую работу шилда с любым сетевым оборудованием.
Скетч в Ардуинку заливаем такой:
#include <SPI.h>
#include <Ethernet.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>
#define DHTPIN 2
#define DHTTYPE DHT11
byte mac[] = {0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02};
uint32_t delayMS;
EthernetServer server(23);
boolean gotAMessage = false;
DHT_Unified dht(DHTPIN, DHTTYPE);
void setup() {
Ethernet.init(10); // Most Arduino shields
if (Ethernet.begin(mac) == 0) {
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
while (true) {
delay(1); //Зависаем
}
}
if (Ethernet.linkStatus() == LinkOFF) {
while (true) {
delay(1); //Зависаем
}
}
}
server.begin();
dht.begin();
sensor_t sensor;
dht.humidity().getSensor(&sensor);
delayMS = sensor.min_delay / 1000;
}
void loop() {
EthernetClient client = server.available();
if (client) {
if (!gotAMessage) {
client.println("Hello, client!");
gotAMessage = true;
}
delay(delayMS);
sensors_event_t event;
dht.temperature().getEvent(&event);
if (isnan(event.temperature)) {
client.println(F("Error reading temperature!"));
}
else {
client.print(F("Temperature: "));
client.print(event.temperature);
client.print(F("°C"));
}
dht.humidity().getEvent(&event);
if (isnan(event.relative_humidity)) {
client.println(F("Error reading humidity!"));
}
else {
client.print(F(" Humidity: "));
client.print(event.relative_humidity);
client.println(F("%"));
}
Ethernet.maintain();
}
}
Это квинтэссенция штатных примеров из Arduino IDE. Для нашего эксперимента сойдет. Ну и займемся обработкой на 1С. Обычная форма, толстый клиент. На УФ тоже работать должно, я не проверял.
Рисуем такую форму:
Код модуля формы, тут все понятно :
Перем НетСокет,Start;
Процедура ДанныеПришли(bytes)
buff="";
НетСокет.GetData(buff);
Incoming = Incoming+buff;
КонецПроцедуры
Процедура Кнопка1Нажатие(Элемент)
Если НетСокет.State=0 Тогда
НетСокет.RemoteHost = СокрЛП(host);
НетСокет.RemotePort = СокрЛП(port);
НетСокет.Protocol = 0; //TCP
НетСокет.Connect();
Incoming="";
ЭлементыФормы.Кнопка1.Заголовок="Disconnect";
Иначе
НетСокет.Close();
Start=Ложь;
ЭлементыФормы.Кнопка1.Заголовок="Connect";
КонецЕсли;
КонецПроцедуры
Процедура ПоказатьСтатус()
Если НетСокет.State=0 Тогда
ЭлементыФормы.Надпись3.Заголовок="State:Connection closed...";
ИначеЕсли НетСокет.State=1 Тогда
ЭлементыФормы.Надпись3.Заголовок="State:Connection open...";
ИначеЕсли НетСокет.State=2 Тогда
ЭлементыФормы.Надпись3.Заголовок="State:Listening for incoming connections...";
ИначеЕсли НетСокет.State=3 Тогда
ЭлементыФормы.Надпись3.Заголовок="State:Connection pending...";
ИначеЕсли НетСокет.State=4 Тогда
ЭлементыФормы.Надпись3.Заголовок="State:Resolving remote host name...";
ИначеЕсли НетСокет.State=5 Тогда
ЭлементыФормы.Надпись3.Заголовок="State:Remote host name successfully resolved...";
ИначеЕсли НетСокет.State=6 Тогда
ЭлементыФормы.Надпись3.Заголовок="State:Connecting to remote host...";
ИначеЕсли НетСокет.State=7 Тогда
ЭлементыФормы.Надпись3.Заголовок="State:Connected to remote host...";
ИначеЕсли НетСокет.State=8 Тогда
ЭлементыФормы.Надпись3.Заголовок="State:Connection is closing...";
ИначеЕсли НетСокет.State=9 Тогда
ЭлементыФормы.Надпись3.Заголовок="State:Error occured...";
КонецЕсли;
Если (НЕ Start) И (НетСокет.State = 7) Тогда
Start = Истина;
НетСокет.SendData("Start"+Символы.ВК+Символы.ПС);
КонецЕсли;
КонецПроцедуры
НетСокет=Новый COMОбъект("MSWinsock.Winsock.1");
ДобавитьОбработчик НетСокет.DataArrival,ДанныеПришли;
ПодключитьОбработчикОжидания("ПоказатьСтатус", 1, Ложь);
host="192.168.100.24";
port="23";
Start=Ложь;
Ну и результат работы без всяких HTTP,WEB и прочих заморочек
По поводу регистрации древних компонентов в 64bit ОС нашел кино на ресурсе ютуб. Смотрим, кто не видел.
Ну, пожалуй все...