• Уважаемые посетители сайта esp8266.ru!
    Мы отказались от размещения рекламы на страницах форума для большего комфорта пользователей.
    Вы можете оказать посильную поддержку администрации форума. Данные средства пойдут на оплату услуг облачных провайдеров для сайта esp8266.ru
  • Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

UBIA - USB / BLE to I2C/SMBus Adapter.

Т.е. уже убедились, что самый сложный метод - это делать что либо по Arduino подобию?
Нет, пока не убедился, этот язык я хотя бы понимаю (там все элементарно), а ваш расшифровываю по подобию.
В качестве примера давайте расскажу как я буду исправлять 1Кгц в скорости шины "не-ардуино-подобным" образом (когда имею два образца на непонятном мне языке:)
В тексте примера с датчиком 9250 есть фрагмент кода с такой записью в лог:

log('Send command#01: Init I2C/SMBUS CLK 400 kHz, MPU9250 Reset...'); // - init I2C

ага ! Оно! Данные передаваемые в функцию выглядят так:

new Uint8Array([11, 1, 0, 0, 255, 255, 0x90, 1, 0xD0, 0x6B, 0, 0x80, 0]));

В функции i2cDrvInit(clk) этот блок данных выглядит следующим образом.

let blk = new Uint8Array([6 , 1, 0, 0, 255, 255, clk&0xff, (clk>>8)&0xff]);

Пишем строчки рядом и выравниваем их по параметрам. Видно что первая строчка длиннее второй, по всей видимости хвост это и есть "MPU9250 Reset..."
Начало совпадает кроме первого байта (длина пакета). Затем, после двух 0xFF по всей видимости и идут два байта нужных мне CLK 400 Кбит.
Исправленный вариант функции на мой взгляд должен выглядеть так:
JavaScript:
function i2cDrvInit()
{
   let blk = new Uint8Array([6 , 1, 0, 0, 255, 255, 0x90,1]);
   this.characteristicCache.writeValue(blk);
}
Правильно?

Вроде вы хотели акселерометр на web страничке...
Уже заказал такие же модули аксселерометров, хотелось бы переделать ее под имеющиеся у меня, но я за это возьмусь только когда мы закончим курс молодого бойца.


ps: В процессе стала понятна ваша ранняя фраза "блоками или побайтово?" Получается никто не запрещает мне паковать в одну посылку сразу несколько команд и они в теории выполнятся в пакетном режиме. Так?
 

pvvx

Активный участник сообщества
log('Send command#01: Init I2C/SMBUS CLK 400 kHz, MPU9250 Reset...'); // - init I2C
ага ! Оно! Данные передаваемые в функцию выглядят так:
new Uint8Array([11, 1, 0, 0, 255, 255, 0x90, 1, 0xD0, 0x6B, 0, 0x80, 0]));
0x90, 1 - это два байта в виде слова 0x190, что в DEC будет 400.
Все форматы посылок описаны в Cи хидере исходников.
Чуть позже, когда доделаю тут кое что, то слеплю вам чтение и запись для DS часов. Вот только кому нужен лог на подобии COM порта в Web и как его применить?
 
Вот только кому нужен лог на подобии COM порта в Web и как его применить?
Это простой учебный пример позволяющий понять как запрограммировать модули для работы с вашей прошивкой.
В более сложных примерах новичку очень трудно понять какие части кода за что отвечают. Например если бы я взял в качестве примера MPU9250Cube и попытался на его основе сделать страничку показывающую расстояние с лазерного дальномера. мне бы пришлось:

1) Убирать со страницы весь код связанный с рисованием графики на странице (ведь в моем приложении он не нужен). При условии что я с этой библиотекой раньше не общался, а значит мне придется сначала разобраться как она работает.
2) Убирать весь код связанный с MPU9250Cube, при условии что я не знаю как он программируется, да и вообще туманно представляю как программируется i2c шина как таковая. А значит мне придется сначала разобраться как программируется MPU9250 и шина i2c

И только затем приступать к привинчиванию на оставшуюся часть своего функционала. Думаю 80% новичков-ардуинщиков обломаются в самом начале. Ваша ошибка как популяризатора TSLR имхо заключается в том, что вы заваливаете читателей тоннами вкусной рыбы, а им нужны сети, для того чтобы ловить рыбу самостоятельно.

Чем хорош это лог:

1) В нем нет ничего лишнего, ничего не нужно удалять. Бери и дополняй нужными функциями.
2) В нем использован примитивный широкоизвестный модуль, который легко запрограммировать небольшими кусками простого кода. Вместе с тем, читатель получает опыт посылки и получения команд, а также записи и чтения данных по i2c шине. Этот опыт позволит ему в дальнейшем запрограммировать модуль нужный уже ему. Сам я следующим примером начну программировать свои лазерные измерители расстояния и акселлераторы.

Осталось добавить в копилку примеров еще и код моргания лампочкой на ноге контроллера по нажатию. на нем же кнопки на соседнем выводе через ble (показать использование чипа как устройства управления чем-либо или от чего либо) и можно смело давать вашу либу ардуинщикам. Что им в голову придет реализовать на вашей прошивке вам даже в голову придти не сможет. :) Если же вам хочется чтобы народ ломанулся на саму прошивку напишите для нее пакет а ля ESP-IDF VS Code Extension делали же вы что то подобное для ардуины. Все таки Eclipse в наше время не совсем комильфо.

ps: К слову свой первый "Hello world" для ардуины я написал месяцев так 8 тому назад, до этого вообще никогда микроконтроллеры не программировал. .. ну за исключением периодических всплесков типа этого :)
 
Подправил код, теперь застреваю на самой первой записи

Send command #00: WhoIs?
>> (2) ["0x00", "0x00"]
characteristicvaluechanged
<< (6) ["0x04", "0x00", "0x21", "0x10", "0x08", "0x00"]
Инициализируем I2c шину
Init I2C/SMBUS
>> (8) ["0x06", "0x01", "0x00", "0x00", "0xff", "0xff", "0x90", "0x01"]
characteristicvaluechanged
<< (2) ["0x00", "0x81"]
Отправляю аналог команд Wire.write(0x0E);Wire.write(B00000000)
>> (7) ["0x07", "0x0e", "0x00", "0x00", "0xd0", "0x0e", "0x00"]
 

pvvx

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

Вот вам готовый извращенный к Arduino зависимым пример к вашим часам i2c, там всё регулируется :)
 

Вложения

Я прошел квест с первым Write !!! Ж)
Правильный ответ получил?


Отправляю аналог команд Wire.write(0x0E);Wire.write(B00000000)
>> (7) ["0x05", "0x0e", "0x00", "0x00", "0xd0", "0x0e", "0x00"]
characteristicvaluechanged
<< (2) ["0x00", "0x0e"]
 

pvvx

Активный участник сообщества
Прям как вы привыкли:
JavaScript:
// установки
const I2C_DEV_ADDR = 0x68;
const I2C_DEV_CLK_KHZ = 400;

var I2C_REG_AINIT = new Uint8Array([0x0E]);
var I2C_REG_DINIT = new Uint8Array([0x00,0x88]);

const I2C_REG_AREAD = new Uint8Array([0]);
const I2C_REG_LREAD = 7;
// главная функция
function ParseI2CDataRead(data) {
     let seconds = data.getUint8(4); // get seconds
     let minutes = data.getUint8(5); // get minutes
     let hours   = data.getUint8(6); // get hours
     let day     = data.getUint8(7);
     let date    = data.getUint8(8);
     let month   = data.getUint8(9); //temp month
     let year    = data.getUint8(10);  
     $("ctime").innerHTML = "Seconds: " + hex(seconds,2) + ", Minutes:" + hex(minutes,2) + ", Hours:" + hex(hours,2) + ", Day:" + hex(day,2) + ", Date:" + hex(date,2) + ", Month:" + hex(month,2) + ", Year:" + hex(year,2);
}
Остальное считайте либой :) Жалко "либу" не подать в бинарном виде, как это делается в Arduino.
 

pvvx

Активный участник сообщества
У вызова ParseI2CDataRead(data) обрезать лишние данные? Т.е. передавать только массив считанных байт?
Или сами справитесь?
 
1) В SendInit посылается 35 байт данных из которых две трети нули. Это с какой то особой целью делается?
2) SetConnParms, ConnParUpdate,GetConnParms обязательны?




Или сами справитесь?
сам справлюсь. Пока разбираюсь .
к слову падает на Ыутв command #4: Get current connect parameters...
Uncaught (in promise) DOMException: GATT operation already in progress.
Как я уже выяснил такое бывает при двух командах подряд.
 

pvvx

Активный участник сообщества
Вот тут посылаются не все нули.
Просто для примера передает 2000 чтений регистров с ваших любимых часов в сек.
 

Вложения

pvvx

Активный участник сообщества
Это не пример на скорость, а просто так для прикола, в смысле как вы любите - микросхема для прикола с логом в Arduino. Но тут лог отключен, а лог эксплорера уже тормозит....
 
в смысле как вы любите
:)
Вот как я люблю (полный проект в приложенном файле):

Основная страничка в ардуино-стайл. Она ничего не знает ни про датчики ни про ble. Чисто юзесркий код с довеском в два класса, ble (основная либа) и ds3231 (код датчика).
JavaScript:
...
<script src="ble.js"></script>
<script src="ds3231.js"></script>
<script>
  var ble;
  var sensor;
  document.addEventListener('DOMContentLoaded', () => {
    sensor = new ds3231({
      log, 
      datetimeChanged:(d) =>{
        document.getElementById("ctime").innerText = d.toLocaleString("ru");
      }
    });
    ble = new Ble({log,
      onerror: (e,info)=>{
        document.getElementById("error").innerHTML+="<li>"+info;
      },
      onsetup: (e) =>{
        sensor.Init(e);
      },
      onloop: (e,idx,value) => {
        // log(value, 'din');
        sensor.Loop(e,idx,value); 
      }
    });
  });
...
В класс Ble в теории можно и не заглядывать, в него я свалил всю внутреннюю кухню, а вот класс под датчик создается конечным пользователем библиотеки. Класс ds3231 генерирует событие при изменении даты, подключившись к нему можно вывести данные на экран.

Ble генерирует события onerror при ошибке, ну и сладкая парочка событий setup и loop никуда не делась, куда уж без них в таком проекте. :)
Setup вызывается после того как инициализируется и настроится i2c шина, датчик там должен провести свою, дополнительную инициализацию о которой не знает ble.
Loop это обработчик ble.characteristicvaluechanged после отработки событий связанных с внутренней кухней библиотеки. По задумкам тут должны оказываться только пользовательские команды и данные и больше ничего, свою кухню библиотека разруливает самостоятельно.

Код датчика ds3231 тоже довольно прост и примитивен. Если спрятать всю его внутреннюю кухню, останутся два основных метода которые и вызываются по событиям ble
JavaScript:
...
 Init(e)
    {
        this.stage_read = 1;
        this._ble=e.ble;
        this.Write(e, new Uint8Array([0x0E]), new Uint8Array([0x00, 0x88]));
    }
    
    Loop(e,idx,value)
    {
        this._ble=e.ble;
        let ds = value.getUint8(0);
        if (idx == 0x0C && ds >= 2)
        {
            if (this.stage_read > 0) 
            {
                if(this.stage_read > 1 && ds >= 9)
                {
                    if(this.datetimeChanged)
                    { 
                        let seconds = this._decode(value.getUint8(4));
                        let minutes = this._decode(value.getUint8(5)); 
                        let hours   = this._decodeH(value.getUint8(6)); 
                        let day     = value.getUint8(7);
                        let date    = this._decode(value.getUint8(8));
                        let month   = this._decode(value.getUint8(9)); 
                        let year    = this._decodeY(value.getUint8(10))+2000;
                        this.datetimeChanged(new Date(year, month, date, hours, minutes, seconds, 0));  
                    }
                }
                if( this._new_date)
                {
                    this.Write(e, new Uint8Array([0x0]), this._new_date);
                    this._new_date=null;
                } else {
                    this.Read(e,new Uint8Array([0]), 7)
                }
                this.stage_read = 2;
            } 
        }
    }
 

Вложения

  • 4.8 KB Просмотры: 4
2000 чтений регистров с ваших любимых часов в сек
Интересно. часам понятное дело это не надо, а вот другим датчикам может пригодится.

В процессе написания кода у меня возникло несколько вопросов. Давайте я их вам по порядку позадаю :)

1) Каким образом можно узнать что некий ответ пришел именно на посланную нами конкретную команду? Или они строго по очереди ходят "запрос-ответ"?
Поясню на примере. Для того чтобы послать команду установки даты на часах, я вызываю метод, который сохраняет подготовленный для передачи массив во внутреннюю переменную объекта. Затем, во время цикла loop, переменная проверяется на пустоту. Если переменная не пуста, отправляется запрос на установку даты и переменная обнуляется, если она изначально была пуста выполняется запрос на чтение даты.
На мой взгляд это как то не кудряво и похоже на костыль. Никак нельзя во время вызова записи добавить какую либо метку, по которйо станет понятно что вот этот конкретный ответ от шины относится именно к нашему запросу?

JavaScript:
 setDateTime(dstr)
{
        var d = new Date(dstr);
        var date_arr =  new Uint8Array([
            this._encode(d.getSeconds()),
            ...
            this._encode(d.getFullYear()-2000) // 34
        ]);
        this._new_date=date_arr;
}

Loop(e,idx,value)
{
  ..
     if( this._new_date)
     {
        this.Write(e, new Uint8Array([0x0]), this._new_date);
        this._new_date=null;
     } else {
        this.Read(e,new Uint8Array([0]), 7)
     }
  ..
}
 

pvvx

Активный участник сообщества
Ble генерирует события onerror при ошибке, ну и сладкая парочка событий setup и loop никуда не делась, куда уж без них в таком проекте. :)
Как-бы всё современное, в чем есть CPU, ныне работает по событиям или типа по калбаскам (callback). Нету ныне setup() и тем более loop(), а сплошная много-задчка-проточка.. :)
Зачем детей учите таким алго и построению программы, что им не пригодится уже никогда?
1) Каким образом можно узнать что некий ответ пришел именно на посланную нами конкретную команду? Или они строго по очереди ходят "запрос-ответ"?
У команды и ответа есть ID, чтобы вы не запутались. И в принципе они ходят запрос-ответ, т.е. так как вы запрограммируете.
В данном случае применен не классический пример для BLE. Причина - совместимость команд с USB и UART (длина пакета там только для синхронизации на пакеты для UART).
У BLE (и USB) существуют шкафы и ящики. У каждого свой номерок ID - UUID. У каждого ящика есть атрибуты - типа он сикретный (подавай пароль или защищенное соединение) и что может: чтение и/или запись. Так-же на ящике может быть флаг - notify.
Если вы его включили, то данные из ящика устройство будет передавать само по своим хотелкам (таймер или обновились, или ...).
В классическом виде работают с ящиками и каждый ящик содержит только свои специфические форматированные данные.
К примеру - Для чтения температуры - свой ящик, для влажности - свой, для параметров соединения - свой, для имени устройства - свой --- номера ящиков и формат данных в них печатает институт стандартов BLE.
Приложение должно открыть дцать ящиков (чтобы побыстрее исчерпать ресурсы дров в системе на дескрипторы к этим ящикам (их не так уж и много выделено почему-то) и увеличить жор у эксплорера, ...
Но допустимо использовать специальный ящик для своих нужд и ковыряться в нем как вам нравиться. Вот так и сделано...
 
Нету ныне setup() и тем более loop(), а сплошная много-задчка-проточка..
Дык это и есть эвенты и калбаки. Они просто так называются, рискну предположить что и коде ардуины схема похожая.
У команды и ответа есть ID, чтобы вы не запутались. И
Я имел в виду как мне отличить что эта команда с кодом indx=0xС пришла в ответ на установку времени, а эта с тем же кодом 0xC в ответ на запрос этого времени.
Во время инициализации бегают разные idx коды команд, а потом одни и теже . например если я захочу с этого датчика еще и температуру вывести, как я смогу отличить ответ с температурой от ответа с датой-временем?

или это 0xC это вы сами придумали, и я могу использовать скажем -0xE1 ?
 

pvvx

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

Я имел в виду как мне отличить что эта команда с кодом indx=0xС пришла в ответ на установку времени, а эта с тем же кодом 0xC в ответ на запрос этого времени.
Во время инициализации бегают разные idx коды команд, а потом одни и теже . например если я захочу с этого датчика еще и температуру вывести, как я смогу отличить ответ с температурой от ответа с датой-временем?

или это 0xC это вы сами придумали, и я могу использовать скажем -0xE1 ?
А поток с типа 0x0C всегда запрос- ответ.
Номерки в нутре пакетов с данного ящика я придумал. Можете в исходниках дополнить или изменить на ваше усмотрение. UUID ящик взят из разрешенных по стандарту для таких выкрутасов.
 
речь вот о чем

uint8_t size; //+0 размер данных пакета
uint8_t cmd; //+1 номер команды / тип пакета (=1)

Этот номер команды может быть любым числом или обязательно 0xC как указано тут?


Read(event,reg_addr, len)
{
let blk = new Uint8Array(reg_addr.byteLength + 5);
blk[0] = blk.byteLength - 2;
blk[1] = 0x0C;
blk[2] = reg_addr.byteLength;
blk[3] = (len & 0x7f) | 0x80;



Другими словами, можно сделать 0xC для получения времени и 0xD для получения температуры?
 

pvvx

Активный участник сообщества
Я имел в виду как мне отличить что эта команда с кодом indx=0xС пришла в ответ на установку времени, а эта с тем же кодом 0xC в ответ на запрос этого времени.
Для времени есть стандартные ящики (UUID) со своим форматом от института стандартизации -> https://www.bluetooth.com/specifications/assigned-numbers/
 
это я знаю. вопрос в том можно ли использовать пользовательские uint8_t cmd; //+1 номер команды / тип пакета (=1)
 
Сверху Снизу