Приветствую всех жителей и гостей кибер-города Датагор! Работа устройства на базе микроконтроллера часто требует двустороннего обмена данными с приложениями на компьютере или смартфоне. Реализации проводного и беспроводного вариантов такого обмена и будет посвящена статья.
В данной части рассматривается проводной обмен посредством связки протоколов USB-UART.
Содержание статьи / Table Of Contents
   
Примем в качестве примера устройство:
• передающее с заданной периодичностью значение температуры Java-приложению на компьютере,
• управляющее RGB-лентой, яркость и цвет светодиодов которой задаётся Java-приложением.
Для его создания нам понадобятся:
1. Микроконтроллер 
ATmega328p (далее — «
МК») с частотой тактирования 8 МГц.
2. RGB-лента 
WS2812B.
3. Температурный датчик 
DS18B20.
4. 
USB-UART адаптер.
5. Подтягивающий резистор на 4.7 kОм.
6. Блок питания от смартфона на 5 В.
 представлена на Рисунке 1.
![Обмен данными между Java-приложением и МК. Часть 1. По проводу, USB-UART]()
Рисунок 1. Схема соединений устройства
Как видно из видео выше, алгоритм обмена данными следующий:
а) Каждые 2 секунды МК считывает показания DS18B20 и передаёт 4 байта, содержащие знак температуры, а также десятки, единицы и десятые доли значения температуры для их отражения в лэйбле Java-приложения.
б) При каждом изменении положения ползунка любого из трёх слайдеров Java-приложения цвет текстового поля меняется соответствующим образом, а значения красной, зелёной и синей его составляющих передаются в МК для соответствующей корректировки цвета светодиодов RGB-ленты.
Время 0:00 - Пишем Java-приложение с ипользованием jSerialComm
Время 2:15 - Пишем прошивку для МК
Время 6:12 - Как это работает. Проводной обмен данными в действии
Программа для МК оформлена на языках Си и ассемблер 
в Visual Studio Code.
Второй вариант прошивки расположен в подвале статьи и расчитан на применение ATMEGA8A.
Контроль за температурным датчиком. Код обмена данными между МК и DS18B20 достаточно подробно пояснялся в 
в моей предыдущей датагорской статье, поэтому лишь приведу его содержимое.
OneWire.h#ifndef ONEWIRE_H_
#define ONEWIRE_H_
#define F_CPU             8000000UL
#include <util/delay.h>
#include <avr/io.h>
#define DELAY_A           6
#define DELAY_B           64
#define DELAY_C           60
#define DELAY_D           10
#define DELAY_E           9
#define DELAY_F           55
#define DELAY_G           0
#define DELAY_H           480
#define DELAY_I           70
#define DELAY_J           410
#define MSBit             0x80
#define LSBit             0x01
#define OWI_DDR           DDRB
#define OWI_PIN           PINB
#define OWI_BUS           PB0
#define OWI_RELEASE_BUS   OWI_DDR &= ~(1 << OWI_BUS)
#define OWI_PULL_BUS_LOW  OWI_DDR |= (1 << OWI_BUS)
void owiInit();
void owiReset();
void owiWriteBit0();
void owiWriteBit1();
unsigned char owiReadBit();
void owiSendByte(unsigned char data);
unsigned char owiReceiveByte();
#endif /* ONEWIRE_H_ */
 OneWire.c#include "OneWire.h"
void owiInit()
{
  OWI_RELEASE_BUS;
  _delay_us(DELAY_H);
}
void owiReset()
{
  OWI_PULL_BUS_LOW;
  _delay_us(DELAY_H);  
  OWI_RELEASE_BUS;
  _delay_us(DELAY_I);  
  _delay_us(DELAY_J);
}
void owiWriteBit0()
{
  OWI_PULL_BUS_LOW;
  _delay_us(DELAY_C);
  OWI_RELEASE_BUS;
  _delay_us(DELAY_D);  
}
void owiWriteBit1()
{
  OWI_PULL_BUS_LOW;
  _delay_us(DELAY_A);
  OWI_RELEASE_BUS;
  _delay_us(DELAY_B);    
}
unsigned char owiReadBit()
{
  uint32_t bitsRead = 0;
  OWI_PULL_BUS_LOW;
  _delay_us(DELAY_A);  
  OWI_RELEASE_BUS;
  _delay_us(DELAY_E);  
  if(OWI_PIN & (1 << OWI_BUS)) 
      bitsRead = 1;  
  _delay_us(DELAY_F);  
    return bitsRead;
}
void owiSendByte(unsigned char data)
{
  unsigned char temp, currentBit;
  for (currentBit = 0; currentBit < 8; currentBit++) 
  {
    temp = data & 0x01;
    if (temp) 
    {
      owiWriteBit1();
    } 
    else 
    {
      owiWriteBit0();
    }
    data >>= 1;
  }  
}
unsigned char owiReceiveByte()
{
  unsigned char data = 0, currentBit = 0;  
  for (currentBit = 0; currentBit < 8; currentBit++) 
  {  
    data >>= 1;
    if (owiReadBit()) 
    {
      data |= MSBit;
    }
  }
  return data;    
}
 DS18B20.h#ifndef DS18B20_H_
#define DS18B20_H_
#include "OneWire.h"
#define SKIP_ROM            0xCC
#define READ_SCRATCH_PAD    0xBE
#define CONVERT_T           0x44
#define CONVERT_DELAY       750
#define MINUS               1
#define PLUS                0
#define SIGN_BITS           0xf8
void DS18B20_Init();
float DS18B20_ReadTemperature();
unsigned char data[2];
extern unsigned char temperatureSign;
#endif /* DS18B20_H_ */
 DS18B20.c#include "DS18B20.h"
unsigned char temperatureSign;
void DS18B20_Init()
{
  owiInit();
}
float DS18B20_ReadTemperature()
{
  owiReset();
  owiSendByte(SKIP_ROM);
  owiSendByte(CONVERT_T);
  _delay_ms(CONVERT_DELAY);
  owiReset();
  owiSendByte(SKIP_ROM);
  owiSendByte(READ_SCRATCH_PAD);
  for(int i = 0; i < 2; i++)
  {
    data[i] = owiReceiveByte();
  }
  owiReset();
  float temperature = (data[1] << 8 | data[0])/16.0;
  if(temperature >= 0)
  {
    temperatureSign = PLUS;
  }
  else if(temperature < 0)
  {
    temperatureSign = MINUS; 
  }
  return temperature;
}
Считывание значения температуры осуществляется, как уже говорилось выше, с периодичностью в 2 секунды, для чего использовалось прерывание по переполнению счётчика таймера TIMER1.
timer.h#ifndef TIMER_H_
#define TIMER_H_
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>
void timerInit();
extern volatile uint8_t timerFlag;
#endif /* TIMER_H_ */
 timer.c#include "timer.h"
volatile uint8_t timerFlag = 0;
ISR(TIMER1_OVF_vect) 
{
  timerFlag = 1;
}
void timerInit() 
{
  TCCR1A = 0;
  /* Разрешить локально прерывание по переполнению счётчика */
  TIMSK1 = (1 << TOIE1);
  /* Разрешить прерывания глобально */
  sei();
  /* Включить тактирование таймера с делителем 256 */
  TCCR1B = (1 << CS12);
}
Как видите:
• тактовая частота МК делится на 256, что даёт прерывание раз в 256×65535 / 8000000 = 2 секунды.
• в обработчике прерывания поднимается флаг 
timerFlag, уведомляя основной цикл о наступлении времени измерения температуры.
На Рисунке 2 представлена выдержка из выложенного в архив статьи даташита WS2812B.
![]()
Рисунок 2. Параметры записи в WS2812B:
а) формат слова, определяющего цвет и яркость светодиода
б) тайминг записи бинарных «0» и «1»
Как видно из Рисунка 2а, цвет и яркость каждого отдельного светодиода ленты определяется значением 24-битного числа, запись которого начинается со старшего бита зелёной составляющей и завершается младшим битом синей. Сама запись значения бита (бинарные «0» или «1») осуществляется чередованием высоких и низких состояний на выводе 
DIN WS2812B (т.е. — на пине 
PC5 МК), с таймингом согласно Рисунка 2б.
Запись последовательности из N 24-битных чисел приводит к изменению состояния первых N светодиодов в ленте в прямой очерёдности, т. е. первое число записывается в первый от входа DIN светодиод, второе — во второй и. т. д. При этом, промежуток времени между записью двух соседних чисел не должен превышать 280 мкс, иначе контроллер ленты сбросится и вновь начнёт запись с первого светодиода.
Для пояснения сказанного выше, на Рисунке 3 представлены случаи записи последовательности из чисел 0xff0000, 0×00ff00 и 0×0000ff, обуславливающими максимальную интенсивность зелёной, красной и синей составляющей, соответственно, когда интервал времени:
а) не превышает 280 мкс во всех случаях,
б) превышает 280 мкс между записью второго и третьего числа.
![]()
Рисунок 3. Результат записи последовательности чисел в ленту из трёх светодиодов
а) интервал между записью чисел не превышает 280 мкс
б) интервал между записью второго и третьего чисел превышает 280 мкс 
Из вышеизложенного следует, что изменить значение N-го по счёту светодиода в ленте можно, лишь записав в неё N чисел, в т. ч.:
• первые (N-1) чисел — текущие значения светодиодов, предшествующих N-му,
• последнее число — новое значение N-го светодиода.
В плоскости программы это означает необходимость хранения текущего значения всех светодиодов в ленте. Код заметно упрощается, если разбить 24-битные числа на 8-битные составляющие по каждому цвету.
Тогда, буфер для хранения в случае с девятью светодиодами будем выглядеть так:
#define STRIP_LENGTH   9
#define GRB_NUM        (STRIP_LENGTH * 3)
grbValue[GRB_NUM];
Обеспечить средствами Си тайминг согласно Рисунка 2б мне не удалось, поскольку период тактового импульса при частоте 8 МГц составляет 125 нс. При этом, установка управляющего пина в высокое, а затем низкое состояние
PORTC |= (1 << PC5);
PORTC &= ~(1 << PC5);
использует более 4-х тактов (т.е. 500 нс), что превышает 380 нс, отведённые даташитом для высокого состояния бинарного числа «0». Как итог — некорректная работа ленты.
С учётом изложенного, было принято решение оформить фрагмент записи в WS2812B на ассемблере. Чтобы не изобретать велосипед, я обратился к интернету, где обнаружил 
статью Mike Silva — «Driving WS2812 RGB LEDs». Статья написана доступным языком, с подробными комментариями, поэтому сразу приведу окончательный вариант кода с переводом комментариев, а затем дам несколько пояснений.
WS2812B.SSREG   = 0x3f    
OUTBIT = 5 /* управляющий пин - PC5 */
PORTC  = 0x08
  .text
    .global stripRefresh
    stripRefresh:
      movw  r26, r24            /* сохранить адрес буфера grbValue в пару r26-r27 */
      movw  r24, r22            /* сохранить количество элементов буфера grbValue в пару r24-r25 */
      in    r22, SREG           /* сохранить текущее состояние регистра SREG */
      cli                       /* запретить глобально прерывания */
      in    r20, PORTC
      ori   r20, (1 << OUTBIT)  /* сохранить маску бинарного "1" - в r20 */
      in    r21, PORTC
      andi  r21, ~(1 << OUTBIT) /* сохранить маску бинарного "0" - в r21 */
      ldi   r19, 7              /* r19 - счётчик записи первых 7 битов */
      ld    r18, X+             /* скопировать в r18 первый элемент буфера grbValue */
    loop1:
      out   PORTC, r20          /* 1    +0   установить PC5 в 1 */
      lsl   r18                 /* 1    +1   сдвинуть старший бит r18 в бит С регистра SREG */
      brcs  L1                  /* 1/2  +2   если старший бит r18 - 1, перейти к метке L1 */
      out   PORTC, r21          /* 1    +3   сбросить PC5 в 0 (итого - три такта в состоянии High) */
      nop                       /* 1    +4 */
      bst   r18, 7              /* 1    +5   сохранить 7-й бит r18 в бит Т регистра SREG для последующей проверки */
      subi  r19, 1              /* 1    +6   декрементировать r19 */
      breq  bit8                /* 1/2  +7   если в WS2812B записан 7-й бит перейти к метке bit8 для записи 8-го бита */
      rjmp  loop1               /* 2    +8   итого - 10 тактов на запись бинарного "0" */
    L1:
      nop                       /* 1    +4 */
      bst   r18, 7              /* 1    +5   сохранить 7-й бит r18 в бит Т регистра SREG для последующей проверки */
      subi  r19, 1              /* 1    +6   декрементировать r19 */
      out   PORTC, r21          /* 1    +7   сбросить PC5 в 0 (итого - 7 тактов в состоянии High) */
      brne  loop1               /* 2/1  +8   итого - 10 тактов на запись бинарного "1" */
    bit8:
      ldi   r19, 7              /* 1    +9   обновить счётчик битов */
      out   PORTC, r20          /* 1    +0   установить PC5 в 1 */
      brts  L2                  /* 1/2  +1   если последний бит текущего элемента буфера grbValue - 1, перейти к метке L2 */
      nop                       /* 1    +2 */
      out   PORTC, r21          /* 1    +3   сбросить PC5 в 0 (итого - три такта в состоянии High) */
      ld    r18, X+             /* 2    +4   загрузить следующий элемент буфера grbValue */
      sbiw  r24, 1              /* 2    +6   декрементировать счётчик элементов буфера grbValue */
      brne  loop1               /* 2    +8   если записаны не все элементы, перейти к метке loop1 */
      out   SREG, r22           /* восстановить значение регистра SREG */
      ret
    L2:
      ld    r18, X+             /* 2    +3   загрузить следующий элемент буфера grbValue */
      sbiw  r24, 1              /* 2    +5   декрементировать счётчик элементов буфера grbValue */
      out   PORTC, r21          /* 1    +7   сбросить PC5 в 0 (итого - 7 тактов в состоянии High) */
      brne  loop1               /* 2    +8   если записаны не все элементы, перейти к метке loop1 */
      out   SREG, r22           /* восстановить значение регистра SREG */
      ret
.end
1. Алгоритм работы кода сводится к тому, чтобы уложиться в 10 тактов при записи бита элемента буфера 
grbValue, в т.ч:
• 3 такта (375 нс) в состоянии High и 7 тактов (875 нс) в состоянии Low пина PC5 — для бинарного «0»,
• 7 тактов (875 нс) в состоянии High и 3 такта (375 нс) в состоянии Low пина PC5 — для бинарного «1».
2. По стандартам Си, первый аргумент фукнции помещается в пару r24–r25, а второй — в пару r22–r23. Именно из этих регистров при вызове из Си-файла
stripRefresh(grbValue, sizeof(grbValue));
ассемблер-код считывает адрес буфера и количество его элементов.
3. В комментариях к коду первый столбец — число тактов на исполнение инструкции, второй — оно же, но с нарастающим результатом.
Далее были созданы хидер- и Си-файлы 
strip, в которых:
а) объявлены:
• внешней функция stripRefresh () посредством ключевого слова 
extern,
• вспомогательные переменные 
redValue, 
greenValue, 
blueValue и 
stripState,
б) прописаны функции:
• 
setLedColor (), которая записывает требуемые значения красной, зелёной и синей составляющих в соответствующие элементы буфера grbValue,
• 
stripSetColor (), устанавливающая все светодиоды ленты в заданный цвет,
• 
stripOff (), гасящая все светодиоды ленты.
strip.h#ifndef STRIP_H_
#define STRIP_H_
#include <avr/io.h>
#include <stdint.h>
#include <string.h>
#define STRIP_LENGTH  9
#define GRB_NUM       (STRIP_LENGTH * 3)
#define STRIP_DDR     DDRC
#define STRIP_PIN     PC5
#define STRIP_OFF     0
#define STRIP_ON      1
void stripOff();
void stripSetColor();
void setLedColor(uint8_t* buf, uint8_t ledNum, uint8_t green, uint8_t red, uint8_t blue);
extern void stripRefresh(uint8_t* buf, uint16_t grbNum);
extern uint8_t grbValue[GRB_NUM];
extern uint8_t redValue, greenValue, blueValue;
#endif /* STRIP_H_ */
 strip.c#include "strip.h"
uint8_t grbValue[GRB_NUM];
uint8_t	stripState = STRIP_OFF;
uint8_t redValue, greenValue, blueValue;
void stripOff()
{
  memset(grbValue, 0, sizeof(grbValue));
  stripRefresh(grbValue, sizeof(grbValue));
}
void stripSetColor()
{
  for(uint8_t ledNum = 0; ledNum < STRIP_LENGTH; ledNum++)
  {
    setLedColor(grbValue, ledNum, greenValue, redValue, blueValue);
  }
  stripRefresh(grbValue, sizeof(grbValue));
}
void setLedColor(uint8_t* buf, uint8_t ledNum, uint8_t green, uint8_t red, uint8_t blue)
{
  uint16_t index = 3 * ledNum;
  grbValue[index++] = red;
  grbValue[index++] = green;
  grbValue[index] = blue;
}
Обмен данными с Java-приложением осуществляется по протоколу UART на следующих условиях:
а) Скорость обмена — 9600.
б) Приём данных — через прерывание RX.
Serial.h#ifndef SERIAL_H_
#define SERIAL_H_
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>
#include <stdbool.h>
#define UART_BAUDRATE  9600
#define FREQUENCY      8000000UL
#define UBRR_VALUE     FREQUENCY / 16 / UART_BAUDRATE - 1
void serialBegin();
void serialPrintln(const uint8_t* buff);
void serialSendByte(uint8_t symbol);
extern volatile uint8_t receivedByte[4];
extern volatile bool receiveFlag;
#endif /* SERIAL_H_ */
 Serial.c#include "Serial.h"
volatile uint8_t receivedByte[4], byteNum = 0;
volatile bool receiveFlag;
ISR(USART_RX_vect)
{
  receivedByte[byteNum] = UDR0;
  byteNum++;
  if(byteNum > 3)
  {
    byteNum = 0;
    receiveFlag = 1;
  }  
}
void serialBegin()
{
  uint16_t ubrrValue = UBRR_VALUE;
  UBRR0H = (uint8_t)(ubrrValue >> 8);
  UBRR0L = (uint8_t)(ubrrValue);
  UCSR0B |= (1 << TXEN0) | (1 << RXEN0) | (1 << RXCIE0);
  UCSR0C |= (3 << UCSZ00);
  sei();
}
void serialSendByte(uint8_t data)
{
  while (!(UCSR0A & (1 << UDRE0)));
  UDR0 = data;
}
void serialPrintln(const uint8_t* buff)
{
  while(*buff != '\0')
  {
    serialSendByte(*buff);
    buff++;
  }
  serialSendByte('\n');
}
Как видите, в обработчике прерывания поступающий от Java-приложения четырёх-байтный массив сохраняется в буфере 
receivedByte, а затем поднимается флаг 
receiveFlag, уведомляющий основной цикл о поступлении новых данных.
Общий контроль за устройством осуществляется из файлов 
device посредством двух функций:
1. 
deviceInit () обеспечивает инициализацию:
• протокола UART,
• термодатчика,
• RGB-ленты,
• таймера.
2. 
deviceControl ():
• при поднятии флага receiveFlag сбрасывает последний, анализирует значение нулевого из 4-х принятых байтов и, если его значение равно заданному (87), копирует значения первого, второго и третьего байтов в переменные redValue, greenValue и blueValue, соотвественно, а затем обновляет цвет светодиодов RGB-ленты. Указанная проверка нулевого байта введена для исключения ошибок в принятых данных.
• при поднятии флага timerFlag сбрасывает последний, считывает текущее значение температуры, раскладывает его на десятки, единицы и десятые доли, а затем передаёт Java-приложению.
device.h#ifndef DEVICE_H_
#define DEVICE_H_
#include "Serial.h"
#include "DS18B20.h"
#include "timer.h"
#include "strip.h"
#define KEY  87        
void deviceInit();
void deviceControl();
#endif /* DEVICE_H_ */
 device.c#include "device.h"
void deviceInit()
{
  serialBegin(); 
  STRIP_DDR |= (1 << STRIP_PIN); 
  stripOff();
  DS18B20_Init();
  timerInit();
}
void deviceControl()
{
  if(receiveFlag)
  {
    receiveFlag = 0;
    if(receivedByte[0] == KEY)
    {
      redValue = receivedByte[1];
      greenValue = receivedByte[2];
      blueValue = receivedByte[3];
      stripSetColor();
    }	
  }
  if(timerFlag)
  {
    uint8_t temperatureBuff[4];
    timerFlag = 0;
    float currentTemperature = DS18B20_ReadTemperature();
    if(temperatureSign == PLUS)
    {
      temperatureBuff[0] = 0;
    }
    else if(temperatureSign == MINUS)
    {
      temperatureBuff[0] = 1;
    }
    temperatureBuff[1] = (uint8_t)(currentTemperature / 10);
    temperatureBuff[2] = (uint8_t)(currentTemperature - temperatureBuff[1] * 10);
    temperatureBuff[3] = (uint8_t)(currentTemperature * 10 - temperatureBuff[1] * 100 - temperatureBuff[2] * 10);
    for(int byteNum = 0; byteNum < 4; byteNum++)
    {
      serialSendByte(temperatureBuff[byteNum]);
    }	
  }
}
Проект приложения разбит для удобства на три класса, которые отвечают:
• 
MyFrame — за графически интерфейс,
• 
MySerial — за обмен данными с МК,
• 
Main — за общий контроль и диспетчеризацию данных между двумя предыдущими классами.
В конструкторе класса MyFrame создаётся фрэйм, включающий следующий элементы:
а) Пять 
ComboBox — для выбора таких параметров обмена, как скорость, количество битов данных, количество стоп-битов, чётности, номера COM-порта.
б) 
Button — кнопка «
Open» для соединения с выбранным COM-портом.
в) Три 
Slider — для выбора значения красной, зелёной и синей составляющей цвета RGB-ленты.
г) 
TextField — для отображения выбранного цвета.
д) 
Label — для отображения значения температуры, принимаемого от МК.
Результат работы конструктора представлен на Рисунке 4.
![]()
Рисунок 4. Результат работы конструктора класса 
MyFrame Методы класса MyFrame:• 
actionPerformed () при нажатии «Open» поднимает флаг 
openCloseButtonFlag,
• 
stateChanged () при движении ползунка любого из слайдеров меняет соответствующим образом цвет TextField, а также поднимает флаг 
slidersFlag.
• 
showMessageDialog () выводит на экран сообщение заданного содержания, в частности — об успешном соединении с COM-портом.
Реакция на поднятие указанных флагов прописана в классе Main и будет рассмотрена ниже.
MyFrame.javaimport javax.swing.*;   
import java.awt.*;  
import java.awt.event.*;  
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ColorUIResource;
import com.fazecast.jSerialComm.SerialPort;
public class MyFrame implements ActionListener, ChangeListener {
  Font MyFont;
  boolean OPEN = true;
  boolean CLOSE = false;
  boolean openCloseButtonFunction = OPEN;
  boolean openCloseButtonFlag = false;
  boolean slidersFlag = false;
  JFrame MyFrame;       
  JComboBox<String> comPortBox, baudRateBox, dataBitsBox, stopBitsBox, parityBitsBox;
  JButton openCloseButton;
  JLabel temperatureValueLabel, celsiusLabel;
  JSlider redColorSlider, greenColorSlider, blueColorSlider;    
  JTextField colorField;    
  SerialPort []portList;
  MyFrame() {  
    MyFont = new Font("Ink Free", Font.BOLD, 15);
    MyFrame = new JFrame("Led control"); 
    MyFrame.setResizable(false);
    MyFrame.setLayout(null);    
    MyFrame.setSize(440, 500); 
    baudRateComponentsInit();
    dataBitsComponentsInit();
    stopBitsComponentsInit();
    parityBitsComponentsInit();
    comPortComponentsInit();
    openCloseButtonInit();
    redSliderComponentsInit();
    greenSliderComponentsInit();
    blueSliderComponentsInit();
    colorFieldInit();
    temperatureComponentsInit();
    MyFrame.setVisible(true);    
  }
    
  private void baudRateComponentsInit() {
    JLabel baudRateLabel;
    baudRateLabel = new JLabel("Baud rate");  
    baudRateLabel.setFont(MyFont);
    baudRateLabel.setBounds(90, 20, 100,30); 
    MyFrame.add(baudRateLabel);
    
    String baudRate[]={"4800", "9600", "14400", "19200", "28800", "38400", "57600", "115200"};  
    baudRateBox = new JComboBox<>(baudRate);  
    baudRateBox.setBounds(73, 45, 100, 30);   
    baudRateBox.setEnabled(true);
    baudRateBox.setFont(MyFont);
    baudRateBox.setSelectedItem("9600");
    MyFrame.add(baudRateBox);
  }
  private void dataBitsComponentsInit() {
    JLabel dataBitsLabel;
    dataBitsLabel = new JLabel("Data bits");  
    dataBitsLabel.setFont(MyFont);
    dataBitsLabel.setBounds(223, 20, 100,30);
    MyFrame.add(dataBitsLabel);
   
    String dataBits[]={"5", "6", "7", "8"};  
    dataBitsBox = new JComboBox<>(dataBits); 
    dataBitsBox.setBounds(205, 45, 100, 30); 
    dataBitsBox.setEnabled(true);
    dataBitsBox.setFont(MyFont);
    dataBitsBox.setSelectedItem("8");
    MyFrame.add(dataBitsBox);
  }
  private void stopBitsComponentsInit() {
    JLabel stopBitsLabel;
    stopBitsLabel = new JLabel("Stop Bits");  
    stopBitsLabel.setFont(MyFont);
    stopBitsLabel.setBounds(90, 95, 100,30);
    MyFrame.add(stopBitsLabel);
    
    String stopBits[]={"1", "1.5", "2"};
    stopBitsBox = new JComboBox<>(stopBits);
    stopBitsBox.setBounds(73, 120, 100, 30);
    stopBitsBox.setFont(MyFont);
    stopBitsBox.setEnabled(true);
    MyFrame.add(stopBitsBox);
  }
  private void parityBitsComponentsInit() {
    JLabel parityBitsLabel;
    parityBitsLabel = new JLabel("Parity Bits");  
    parityBitsLabel.setFont(MyFont);
    parityBitsLabel.setBounds(220, 95, 100,30);
    MyFrame.add(parityBitsLabel);
    
    String parityBits[]={"none", "odd", "even", "mark", "space"};  
    parityBitsBox = new JComboBox<>(parityBits);
    parityBitsBox.setBounds(205, 120, 100,30);    
    parityBitsBox.setFont(MyFont); 
    parityBitsBox.setEnabled(true);
    MyFrame.add(parityBitsBox);
  }
  private void comPortComponentsInit() {
    JLabel comPortLabel;
    comPortLabel = new JLabel("COM port");  
    comPortLabel.setFont(MyFont);
    comPortLabel.setBounds(90, 170, 100,30);
    MyFrame.add(comPortLabel);
    
    comPortBox = new JComboBox<>(); 
    comPortBox.setFont(MyFont);
    comPortBox.setBounds(73, 195, 100,30);  
    portList = SerialPort.getCommPorts();
    for (SerialPort port: portList) {
      comPortBox.addItem(port.getSystemPortName());
    }
    MyFrame.add(comPortBox);
  }
  private void openCloseButtonInit() {
    openCloseButton = new JButton("Open");
    openCloseButton.setBounds(205, 195, 100,30);
    openCloseButton.setFocusable(false);
    openCloseButton.setFont(MyFont);
    openCloseButton.addActionListener(this);
    openCloseButton.setEnabled(true);
    MyFrame.add(openCloseButton);
  }
  private void redSliderComponentsInit() {
    JLabel redSliderLabel = new JLabel("R");
    redSliderLabel.setBounds(55, 250, 15, 15);
    redSliderLabel.setForeground(Color.RED);
    redSliderLabel.setFont(MyFont);
    MyFrame.add(redSliderLabel);
    redColorSlider = new JSlider(0, 255, 0);
    redColorSlider.setBounds(73, 240, 150, 40);
    redColorSlider.setValue(0);
    redColorSlider.setPaintLabels(true);
    redColorSlider.addChangeListener(this);
    MyFrame.add(redColorSlider);
  }
  private void greenSliderComponentsInit() {
    JLabel greenSliderLabel = new JLabel("G");
    greenSliderLabel.setBounds(55, 280, 15, 15);
    greenSliderLabel.setForeground(Color.GREEN);
    greenSliderLabel.setFont(MyFont);
    MyFrame.add(greenSliderLabel);
    greenColorSlider = new JSlider(0, 255, 0);
    greenColorSlider.setBounds(73, 270, 150, 40);
    greenColorSlider.setValue(0);
    greenColorSlider.setPaintLabels(true);
    greenColorSlider.addChangeListener(this);
    MyFrame.add(greenColorSlider);
  }
  private void blueSliderComponentsInit() {
    JLabel blueSliderLabel = new JLabel("B");
    blueSliderLabel.setBounds(55, 310, 15, 15);
    blueSliderLabel.setForeground(Color.BLUE);
    blueSliderLabel.setFont(MyFont);
    MyFrame.add(blueSliderLabel);
    blueColorSlider = new JSlider(0, 255, 0);
    blueColorSlider.setBounds(73, 300, 150, 40);
    blueColorSlider.setValue(0);
    blueColorSlider.setPaintLabels(true);
    blueColorSlider.addChangeListener(this);
    MyFrame.add(blueColorSlider);
  }
  private void colorFieldInit() {
    colorField = new JTextField();
    colorField.setBounds(235, 255, 70, 70);
    colorField.setBackground(new ColorUIResource(redColorSlider.getValue(), greenColorSlider.getValue(), blueColorSlider.getValue()));
    MyFrame.add(colorField);
  }
  private void temperatureComponentsInit() {
    temperatureValueLabel = new JLabel("00.0", SwingConstants.RIGHT);
    temperatureValueLabel.setFont(new Font("Ink Free", Font.BOLD, 35));
    temperatureValueLabel.setBounds(110, 365, 110, 100);
    temperatureValueLabel.setBackground(MyFrame.getBackground());
    MyFrame.add(temperatureValueLabel);
    celsiusLabel = new JLabel();
    celsiusLabel.setText("\u00B0" + "C");
    celsiusLabel.setFont(new Font("Ink Free", Font.BOLD, 35));
    celsiusLabel.setBounds(225, 365, 40, 100);
    celsiusLabel.setBackground(MyFrame.getBackground());
    MyFrame.add(celsiusLabel);
  }
  void showMessageDialog(String message) {
    JOptionPane.showMessageDialog(MyFrame, message);
  }
  @Override
  public void actionPerformed(ActionEvent e) {
    if(e.getSource() == openCloseButton) {
      openCloseButtonFlag = true;
    }
  }
  @Override
  public void stateChanged(ChangeEvent e) {
    if(e.getSource() == redColorSlider || e.getSource() == greenColorSlider || e.getSource() == blueColorSlider) {
      colorField.setBackground(new ColorUIResource(redColorSlider.getValue(), greenColorSlider.getValue(), blueColorSlider.getValue()));
      slidersFlag = true;   
    }
  }
}
Класс MySerial содержит четыре метода следующего назначения:
а) 
openPort () и 
closePort () обеспечивают соединение с COM-портом и отсоединение, соответственно.
б) 
sendColorData () передаёт МК выбранное слайдерами значение цвета.
в) 
SerialEventBasedReading () принимает от МК 4 байта со значением температуры и объединяет их в строковую переменную 
dataBuffer.
MySerial.javaimport java.io.IOException;
import java.io.OutputStream;
import com.fazecast.jSerialComm.SerialPort;  
import com.fazecast.jSerialComm.SerialPortDataListener;  
import com.fazecast.jSerialComm.SerialPortEvent; 
public class MySerial {
  String dataBuffer = "";
  SerialPort currentComPort;
  final byte NO_FLAG = 0;
  final byte SUCCESS_TO_OPEN = 1;
  final byte FAIL_TO_OPEN = 2;
  final byte PLEASE_CHOOSE_COM_PORT = 3;
  final byte PORT_IS_CLOSED = 4;
  byte comPortPane = NO_FLAG;
  OutputStream stripStream;
  enum comPortPane {
    NO_FLAG,
    SUCCESS_TO_OPEN,
    FAIL_TO_OPEN,
    PLEASE_CHOOSE_COM_PORT,
    PORT_IS_CLOSED
  }
  void openPort() {
    try { 
      currentComPort.openPort();
      if(currentComPort.isOpen()) {
        comPortPane = SUCCESS_TO_OPEN;
        SerialEventBasedReading(currentComPort);
      }
      else {
        comPortPane = FAIL_TO_OPEN;
      }
    }
    catch(ArrayIndexOutOfBoundsException a) {
      comPortPane = PLEASE_CHOOSE_COM_PORT;
    }
    catch(Exception b) {
    }
  }
  void closePort() {
    if(currentComPort.isOpen()) {
      currentComPort.closePort();
      comPortPane = PORT_IS_CLOSED;
    }
  }
  void sendColorData(int redValue, int greenValue, int blueValue) {
    stripStream = currentComPort.getOutputStream();
    int[] dataToSend = new int[4];
    dataToSend[0] = 87;
    dataToSend[1] = redValue;
    dataToSend[2] = greenValue;
    dataToSend[3] = blueValue;
    try {
      stripStream.write(dataToSend[0]);
      stripStream.write(dataToSend[1]);
      stripStream.write(dataToSend[2]);
      stripStream.write(dataToSend[3]);
    }
    catch(IOException noSendException) {
    }
  }
  private void SerialEventBasedReading(SerialPort activePort) {
    activePort.addDataListener(new SerialPortDataListener() {
      @Override
      public int getListeningEvents() {
        return SerialPort.LISTENING_EVENT_DATA_RECEIVED;
      }
      @Override
      public void serialEvent(SerialPortEvent arg0) {
        byte []newData = arg0.getReceivedData();  
        if(newData.length == 4) {
          dataBuffer = "";
          if(newData[0] == 0) {
            dataBuffer = "+";
          }
          else if(newData[0] == 1) {
            dataBuffer = "-";
          }
          dataBuffer += newData[1];
          dataBuffer += newData[2];
          dataBuffer += ".";
          dataBuffer += newData[3];
        }
      }
    });
  }
}
В классе Main создаются экземпляры 
frame и 
serial классов MyFrame и MySerial, соответственно, а затем запускается задача, метод 
run () которой по прерыванию таймера с периодичностью 200 мс:
1. Проверяет флаг openCloseButtonFlag и, если он поднят:
• сбрасывает флаг,
• в случае, если текущая функция кнопки — соединение с портом, меняет название кнопки с «Open» на «Close», обеспечивает соединение с портом и выдаёт сообщение об удачной/неудачной попытке соединения,
• в случае, если текущая функция кнопки — отключение порта, меняет название кнопки с «Close» на «Open», отключает порт и выдаёт соответствующее сообщение,
2. Если переменная dataBuffer — не пустая, отображает её содержимое в Label, а затем очищает.
3. Проверяет флаг slidersFlag и, если он поднят:
• сбрасывает флаг,
• передаёт МК новое значение цвета RGB-ленты.
Main.javaimport java.util.Timer;
import java.util.TimerTask;
public class Main {
  static int currentRedValue, currentGreenValue, currentBlueValue;
  public static void main(String[] args) {
    MyFrame frame = new MyFrame();
    MySerial serial = new MySerial();
      
    TimerTask timerTask = new TimerTask() {
      public void run() {
        if(frame.openCloseButtonFlag == true) {
          frame.openCloseButtonFlag = false;
          if(frame.openCloseButtonFunction == frame.OPEN) {
            serial.currentComPort = frame.portList[frame.comPortBox.getSelectedIndex()];
            serial.currentComPort.setBaudRate(Integer.parseInt(frame.baudRateBox.getSelectedItem().toString()));
            serial.currentComPort.setNumDataBits(Integer.parseInt(frame.dataBitsBox.getSelectedItem().toString()));
            serial.currentComPort.setNumStopBits(Integer.parseInt(frame.stopBitsBox.getSelectedItem().toString()));
            serial.currentComPort.setParity(frame.parityBitsBox.getSelectedIndex());
            serial.openPort();
            if(serial.comPortPane == serial.SUCCESS_TO_OPEN) {
              frame.openCloseButtonFunction = frame.CLOSE;
              frame.openCloseButton.setText("Close");
              frame.showMessageDialog("        Success to OPEN");                       
            }
            else {
              frame.showMessageDialog("            Fail to OPEN");
            }
          }
          else if(frame.openCloseButtonFunction == frame.CLOSE) {
            frame.openCloseButtonFunction = frame.OPEN;
            frame.openCloseButton.setText("Open");
            serial.closePort();
            frame.showMessageDialog("             Port closed");
          }
        }
        if(serial.dataBuffer != "") {
          frame.temperatureValueLabel.setText(serial.dataBuffer);
          serial.dataBuffer = "";           
        }
        if(frame.slidersFlag == true) {
          frame.slidersFlag = false;
          serial.sendColorData(frame.redColorSlider.getValue(), frame.greenColorSlider.getValue(), frame.blueColorSlider.getValue());
        }
      }
    };
        
    Timer timer = new Timer();
    timer.scheduleAtFixedRate(timerTask, 0, 200);
  }    
}
 🎁
Отличный бесплатный программный редактор Visual Studio Code🎁
Даташит ws2812b.pdf
🎁
Приложение на Java - java-app.7z
 866.14 Kb ⇣ 13
    Наш файловый сервис предназначен для полноправных участников сообщества "Datagor Electronics".
    Для получения файла зарегистрируйтесь и войдите на сайт с паролем.
 
🎁
Код для ATMEGA328P.7z
 9.51 Kb ⇣ 11
    Наш файловый сервис предназначен для полноправных участников сообщества "Datagor Electronics".
    Для получения файла зарегистрируйтесь и войдите на сайт с паролем.
 
🎁
Код для ATMEGA8A.7z
 3.32 Kb ⇣ 12
    Наш файловый сервис предназначен для полноправных участников сообщества "Datagor Electronics".
    Для получения файла зарегистрируйтесь и войдите на сайт с паролем.
 
Спасибо за внимание! Продолжение следует.
Камрад, рассмотри датагорские рекомендации
  🌼 Полезные и проверенные железяки, можно брать
  Опробовано в лаборатории редакции или читателями.