Приветствую всех жителей и гостей кибер-города Датагор! Работа устройства на базе микроконтроллера часто требует двустороннего обмена данными с приложениями на компьютере или смартфоне. Реализации проводного и беспроводного вариантов такого обмена и будет посвящена статья.
В данной части рассматривается проводной обмен посредством связки протоколов 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.
Рисунок 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 ⇣ 9
Наш файловый сервис предназначен для полноправных участников сообщества "Datagor Electronics".
Для получения файла зарегистрируйтесь и войдите на сайт с паролем.
🎁
Код для ATMEGA328P.7z
9.51 Kb ⇣ 8
Наш файловый сервис предназначен для полноправных участников сообщества "Datagor Electronics".
Для получения файла зарегистрируйтесь и войдите на сайт с паролем.
🎁
Код для ATMEGA8A.7z
3.32 Kb ⇣ 9
Наш файловый сервис предназначен для полноправных участников сообщества "Datagor Electronics".
Для получения файла зарегистрируйтесь и войдите на сайт с паролем.
Спасибо за внимание! Продолжение следует.
Камрад, рассмотри датагорские рекомендации
🌼 Полезные и проверенные железяки, можно брать
Опробовано в лаборатории редакции или читателями.