Последнее обновление:
October 19, 2020

Есть мысль... Жми, напиши!
Что имеем: Постов : 141 Авторов: 1 Категорий: 22

Разделение на отдельные пакеты байтового потока данных.

Потребовалось мне тут обеспечить общение двух девайсов (пусть будут, для упрощения, Ардуинки — тестировалось всё равно на них) через последовательный интерфейс, ака com-порт, он же rs232.

Так как оба девайса имеют буферы на приём и отправку, то нет никакой гарантии, что если мы отправим 5 байт и сделаем небольшую задержку, то на другой девайс так же придут эти 5 байт разом. То есть при Serial.readBytes их там может оказаться и 5 и 8 (от предыдущей отправки). То есть нужно как-то гарантированно разделять на отдельные «пакеты» поток байтов.

Сразу напрашивается некоторый «разделитель» пакетов, например три байта подряд 0xFF. НО никто не даст гарантии, что при обмене не окажется этих трёх значений среди самих данных, тем самым нарушив нашу разбивку на «пакеты».
Можно, конечно, увеличить разделитель до 20 байт, но пропускная способность существенно снизиться, к тому же память у девайсов не резиновая (Особенно Ардуинок). Либо самого себя ограничить при отправке, что не будет посылать подряд эти байты, короче, извращаться. Меня это не устраивает, т.к. лень. Хочется отправить, не думая, и получить, не парясь особо.

Для упрощения, пока что, опустим тот факт, что данные могут быть искажены при передаче. Так как контрольные суммы и прочее тема отдельного поста. В общем, считаем, что потерь/искажений нет.

Так же ограничим себя следующими хотелками и условиями:
1. Не должно быть существенного увеличения объёма передаваемых данных для служебных целей.
2. Вероятность ошибки при разбивке и сборке должна быть равной 0.
3. Обеспечить минимальную потерю производительности
4. Не выделять дополнительный объём памяти динамически.
5. Длина одного пакета максимум 254 байта.
6. Без потерь

Естественно, под это подходят разные алгоритмы сжатия данных, например LZ77 , но тогда придётся вставлять дополнительные байты сколько есть повторов/сколько без повторов, что в худшем случае, увеличит объём данных практически в 1.5 раза. И цели, как таковой, «сжать» у нас не было. Разве что бонусом получить.

Давайте для примера, возьмём разделитель пакетов из 3х байт 0x0, как самые неудобные и часто встречающиеся.

Так как нам нужно исключить повторение лишь этой последовательности, то придуман (наверняка не мной и он уже как-то по-умному называется, но я не смог найти)

Алгоритм исключения последовательности из данных с последующим восстановлением:

Допустим есть массив байт (десятичные значения тут и далее):
5 6 7 0 0 0 65 12 14 88 66 0 0 88 99 104 55 19 22 29 0 0 0 18 78 89 0 0 0 252 5

Ищем первое появление 2х и более нулей подряд, запоминаем смещение первого 0 и считаем сколько их. Как посчитали, по следующему индексу после него ставим количество, а перед всеми данными вставляем смещение.
4 5 6 7 0 0 0 65 12 14 88 66 0 0 88 99 104 55 19 22 29 0 0 0 18 78 89 0 0 0 252 5

Ищем следующее включение 2х и более нулей подряд, запоминаем индекс, считаем сколько подряд.
На следующий от текущего индекса ставим сколько повторений нулей подряд.
Выясняем смещение, относительно предыдущего (так сказать получается расстояние до следующего).
По индексу смещения, найденного в предыдущий раз вставляем это расстояние. Остальное можно забить любыми значениями (у меня 255).

4 5 6 7 17 3 255 65 12 14 88 66 0 0 88 99 104 55 19 22 29 0 0 0 18 78 89 0 0 0 254 5

Можно, конечно, и вместо, тем самым бонусом немного «сжать», но это ведёт к усложнению декодирования, так что пока что не паримся.

Повторяем так для всех следующих включений 2х нулей подряд:

4 5 6 7 17 3 255 65 12 14 88 66 0 0 88 99 104 55 19 22 29 6 3 255 18 78 89 0 0 0 252 5

Если достигли конца, и повторы не найдены, то на место предыдущих нулей записать 255 как индикатор, что дальше нет трёх подряд.
Если трёх подряд вообще не было, то вставить 255 перед всеми данными, опять же, как индикатор, что можно не париться.

4 5 6 7 17 3 255 65 12 14 88 66 0 0 88 99 104 55 19 22 29 6 3 255 18 78 89 255 3 255 252 5

Если длина массива больше, чем 254 байта, то, соответственно, начинать заново весь алгоритм, но уже относительно смещения в 255.

В итоге
нужен всего лишь 1 байт дополнительных данных и всё =)
достаточно быстро, т.к. нет сложных мат. вычислений и всего лишь один прогон по всему массиву.

Итоговая реализация (на Qt):

#include <QCoreApplication>

#include <QByteArray>>
#include <QDebug>

/**
* @brief Выводим массив байт в виде десятичных значений
* @param title
* @param ba
*/
void showBA(QString title, const QByteArray ba)
{
    QStringList sl;
    foreach (uchar b, ba) {
        sl.append(QString::number(b, 10));
    }
    qDebug()<<"ByteArray:"<<title<<sl.join(" ");
}

/**
* @brief Кодируем байты
* @param ba
*/
int serialEncode(QByteArray &ba)
{
    int firstOffset = 255; //-- Что бы не сдвигать данные, будем возвращать, с какого места начинается первая последовательность. Пока что считаем, что таких нет.
    int lastIdx = -1; //-- С какого индекса в предыдущий раз началась последовательность
    int fromIdx = -1; //-- С какого индекса сейчас началась последовательность
    int together = 0;

    for (int i=0; i<ba.length(); ++i) {
        uchar b = ba[i];
        if ( b==0 ) { //-- Начинаем считать, сколько подряд
            together++;
            if ( fromIdx<0 ) { fromIdx = i; } //-- Запоминаем, с какого началась последовательность
            if ( together>2 ) { ba[i] = 0xFF; } //-- Всё, что дальше, забиваем сразу 255 что бы лишних нулей не было.
        } else //-- Подряд кончились
        if ( together>0 ) {
            if ( together>=2 ) { //-- Ну, как минимум 2 подряд есть
                ba[fromIdx+1] = together; //-- Укажем, сколько нас
                if (lastIdx==-1) { //-- Если первый раз нашли, то запоминаем отдельнго
                    firstOffset = fromIdx;
                } else {
                    ba[lastIdx] = fromIdx-lastIdx; //-- Расстояние до текущего
                }
                lastIdx = fromIdx;
            }
            together = 0;
            fromIdx = -1;
        }
    }

    if ( lastIdx>-1 ) { ba[lastIdx] = 255; }//-- Установим, что дальше нет подряд идущих

    return firstOffset;
}

/**
* @brief Декодируем обратно
* @param firstOffset - с какого индекса начинается первая последовательность
* @param ba
*/
void serialDecode(int firstOffset, QByteArray &ba)
{
    if ( firstOffset==255 ) { return; } //-- Нет у нас повторений, так что нечего декодировать

    int fromOffset = firstOffset; //-- С какого начинать заполнение
    int fillCount = 0; //-- Сколько заполнять
    for (int i=0; i<ba.length(); ++i) {
        if ( i==fromOffset ) { //-- Дошли до места, откуда нужно начинать заполнять
            fromOffset += ba[i]; //-- Узнаём, откуда в следующий раз начинать заполнять
            fillCount = ba[i+1]; //-- Узнаём, сколько сейчас надо заполнить
        }
        if ( fillCount>0 ) { //-- Если ещё не заполнили, то пора
            fillCount--;
            ba[i] = 0;
            if ( fillCount==0 && fromOffset==255 ) { break; } //-- Дальше идти нет смысла, т.к. повторения кончились.
        }
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QByteArray ba = QByteArray(std::begin<char>({0x5, 0x6, 0x7, 0x0, 0x0, 0x0, 0x41, 0xC, 0xE, 0x58, 0x42, 0x0, 0x0, 0x58, 0x63, 0x68, 0x37, 0x13, 0x16, 0x1D, 0x0, 0x0, 0x0, 0x12, 0x4E, 0x59, 0x0, 0x0, 0x0, 0x4, 0x5}), 31);

    showBA("Source  ", ba);

    int firstOffset = serialEncode(ba);
    showBA("Encoded:", ba);


    serialDecode(firstOffset, ba);
    showBA("Decoded:", ba);

    return a.exec();
}

Вот как-то так.

Views :

17

Конвертер картинок для Arduino LCD OLED 128×64 I2C дисплея

Пришёл вот такой дисплейчик:

Но вот нигде не нашёл для него генератора, что бы модно было конвертировать jpg/png/bmp картинку в код. 
Неспешно накалякал, выбираете любой jpg/bmp файл и получаете на выходе код:

Тестовый скетч:

#include <OLED_I2C.h>  

OLED  myOLED(A4, A5, A4); 

extern uint8_t SmallFont[];

//--PASTE GENERATED CODE HERE

void setup()
{
    myOLED.begin();
    myOLED.setFont(SmallFont);    
}

void loop() 
{
    myOLED.clrScr(); 
    myOLED.drawBitmap(0, 0, icon1, 21, 21); //-- X, Y, IMG, Width, Height 
    myOLED.update();
    delay(150);
}




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

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

Библиотека OLED_I2C.

Views :

7258

Arduino прошивка загрузчика (bootloader) на новый чип Atmega 328P-PU / 168 через Bit-Bang на самой плате Arduino в Ubuntu 14 c помощью avrdude версии 6.1

Доигрался я со своей Ардуинкой, и сжёг несколько пинов у чипа =))

Но так как они мне все были нужны, сгонял в магазин за заменой, взял чип Atmega 328P   но и программатора у меня под рукой не оказалось да и паять было лень.

Благо на Ардуинке присутствует микросхема от ftdi (ft232rl) благодаря которой Ардуинку можно подключать через usb, а в системе появляется виртуальный com-порт,

но у неё есть ещё один режим работы так называемый Bit-Bang т.е. можно напрямую дёргать её ножки. Этим мы и воспользуемся.

Чип Atmega 328 а так же 168 и большинство других прошиваются через протокол SPI, на самой ардуинке присутствует разъём для этого под названием SPI или ICSP

так же выведен разьём микросхемы ft232rl (под названием x3 или как то так с четырьмя ножками)

Всё, что нам нужно для заливки прошивки, это их соединить по схеме:

 

Prog_Bit-Bang_scheme

 

Если чип прошивается на самой плате, то подключать питающую линию не нужно.  (да, можно прошивать чип ардуинки  с другой ардуинки)

Выглядит примерно так:

Prog_Bit-Bang

 

Фото честно спёрты с robocraft.ru =)

Вот впринципе и всё =)

Дальше устанавливаем avrdude командой:

sudo apt-get install avrdude

Потом открываем консольку и переходим в каталог установленной Arduino IDE:

cd /opt/Arduino/hardware/arduino/avr/bootloaders/atmega

Узнаем как там чип:

sudo avrdude -c arduino-ft232r -p m328p -P ft0 -U hfuse:r:-:h -B 4800

Если вывод показывает что всё прошло успешно, то заливаем прошивку:

sudo avrdude -q -C /etc/avrdude.conf -p m328p -c arduino-ft232r -P ft0 -B 4800 -e -U flash:w:ATmegaBOOT_168_atmega328.hex

Ну и на последок прошиваем фьюзы:

sudo avrdude -C /etc/avrdude.conf -p m328p -c arduino-ft232r -B 4800 -u -U lock:w:0x3f:m -U efuse:w:0x05:m -U hfuse:w:0xDA:m -U lfuse:w:0xFF:m

Вот и всё =) Отсоединяем провода, перетыкаем usb и можно залить простенький скетч.

За что какие параметры отвечают можно прочитать вызвав «avrdude -?»


Для чипа Atmega168 прошивка будет ATmegaBOOT_168_diecimila.hex
фьюзы:-Uefuse:w:0x00:m -Uhfuse:w:0xdd:m -Ulfuse:w:0xff:m -Ulock:w:0x0F:m
чип: m168

Для Atmega8 прошивка будет в папке atmega8 файл ATmegaBOOT.hex
фьюзы: -Ulfuse:w:0xdf:m -Uhfuse:w:0xCA:m
чип: m8
В Arduino IDE выбираете плату Arduino NG or older и чип Atmega8

 

Views :

2329

Arduino использование 7 или 8 сегментных 4х разрядных индикаторов.

Восьмисегментный индикатор GNQ-5641AS-21

Восьмисегментный индикатор GNQ-5641AS-21

ОБНОВЛЕНО 26.07.2015.  Исправил известные баги. Выложил на Гитхаб.

Понадобилось тут на индикатор GNQ-5641AS и выводить значения.

У него 4 разряда по 8 сегментов (7 — цифра и 1 — точка)
Для того что бы отобразить на нём значение необходимо на катод нужного разряда подать «-» а на необходимые сегменты «+» что бы получилась цифра.
Так как каждый сегмент разряда завязан на одну линию (посмотрите в даташите, станет понятнее), то что бы вывести разные числа в разные разряды,
необходимо зажигать разряды по очереди с соответствующими сегментами.
Т.е. сначала подали «-» на катод первого разряда и выставили нужные сегменты (при этом сегменты остальных разрядов гореть не будут, т.к. у их катодов в это время должен быть «+»)
потом аналогично второго и т.д. Для иллюстрации процесса в примере delay(5); замените на delay(500);
При быстрой смене моргание не заметно, поэтому и получается число целиком.

После поиска готовых библиотек приуныл.
Все готовые либы использовали тормознутый digitalWrite да ещё и в циклах, кроме того слишком было намудрено с выводом плавающей точки.

Представляю свой вариант библиотеки для работы с таким индикатором и ему подобными.
Выводит только целочисленные значения со знаком.
Для отображения плавающей точки необходимо явно задать её положение.
Нет жёстких ограничений на кол-во разрядов, сегментов и символов.
Кастомные символы (например букву C можно прописать в массиве масок символов digits)
Вся работа по выводу идёт напрямую через порты и битовые маски.
Под себя либу можете подкорректировать, я же стараюсь на слабых чипах не прибегать к плавающей точке, вся арифметика — целочисленная.

Подключайте индикатор в какие угодно пины, только порядок правильно пропишите при инициализации либы.
Пины разрядов подключать через резисторы в 100Ом.
Пины сегментов и разрядов на индикаторе смотреть в даташите.

Комменты в коде , надеюсь не переборщил и не слишком тупо 😉
Установка проста — папку из архива разархивировать в папку libraries к папке установки Arduino IDE (Для Ubuntu /usr/share/arduino/libraries Для Винды уже не помню)

СКАЧАТЬ

В Arduino IDE в менюшке примеров появится SegmentsIndicator там этот же пример использования:

#include <SegmentsIndicator.h>

/*
* Первым параметром - количество разрядов + количество сегментов. 
* Если используется стандартный индикатор, то должно стоять 12
* Дальше через запятую пины разрядов в порядке 4, 3, 2, 1
* и пины сегментов  A, B, C, D, E, F, G, DP
* кстати, A3,A5,A2 - значит аналоговое пины
*/
SegmentsIndicator segmentsIndicator(12,   13, 12 , 11, 10, 1, A5, A3, 3, 2, A2, A4, 4);

void setup() 
{
  segmentsIndicator.displayVal(-22, 1); //-- Подготавливаем значение первый параметр - значение, второй - положение точки
}

void loop() 
{
  segmentsIndicator.displayRefresh(); //-- Отображаем значение на индикаторе.
  delay(5); //-- Дадим время разгорется сегментам индикатора
}

У меня этот индикатор с общим катодом, для индикаторов с общим анодом потребуется в либе инвертировать маски символов (digits) а так же поменять значения c HIGH на LOW при конфигурации пинов,
а так же убрать инвертирование разрядов. В общем, читайте комменты в коде, там написано =)

Так же можно использовать несколько 7 сегментных индикаторов вместе: соединив выводы сегментов, а 4 вывода разряда подключить к Ардуинке.
Так же потребуется изменить константы в библиотеке.

Подходит для чипов ATmega168 и ATmega328, для остальных необходимо будем изменить порты и правильно указать диапазоны (в коде в комментах есть пояснение).

В общем как-то так =)  Надеюсь кому-нибудь пригодится =)

Views :

10784