Привет всем датагорчанам-любителям МК! Сегодня мы рассмотрим двусторонний обмен между Java-приложением и микроконтроллером посредством Bluetooth. Заодно сделаем весёлую штуку: рисуем цветными пикселями в приложении и получаем такой же рисунок на матрице из адресуемой RGB-ленты.
Содержание статьи / Table Of Contents
Соберем устройство:
• передающее значение освещённости Java-приложению на компьютере,
• управляющее RGB-лентой, выбор конкретного светодиода которой и его цвета определяется Java-приложением.
Нам потребуются:Микроконтроллер ATmega328p (далее — «МК») с частотой тактирования 8 МГц. Bluetooth-модуль HC-05. RGB-лента WS2812B. Фоторезистор. Резистор на 1 кОм. Блок питания на 5 В (пойдёт от смартфона).
представлена на Рисунке 1.
Рисунок 1. Схема соединений устройства
Алгоритм работы устройства следующий:
а) АЦП микроконтроллера в автоматическом режиме напряжение, величина которого отражает уровень освещенности на фоторезисторе, и передаёт полученное значение Java-приложению.
б) При перемене цвета любого из 105 лэйблов в графическом интерфейсе Java-приложение передаёт соответствующую информацию МК для изменения цвета соответствующего светодиода RGB-ленты.
осуществляется измерением напряжения на выводе PC0 МК, соединённом с точкой между фоторезистором и резистором.
Параметры функционирования АЦП определяются кодом нижеприведённых файлов.
adc.h#ifndef ADC_H_
#define ADC_H_
#include <avr/io.h>
#include <avr/interrupt.h>
#define ADC_COUNTER_MAX 1000
void adcInit();
extern volatile unsigned char adcFlag;
extern volatile unsigned int adcCounter;
extern unsigned char adcPreviousValue;
#endif /* ADC_H_ */
adc.c#include "adc.h"
volatile unsigned char adcFlag;
volatile unsigned int adcCounter;
unsigned char adcPreviousValue;
ISR(ADC_vect)
{
adcCounter++;
if(adcCounter == ADC_COUNTER_MAX)
{
adcCounter = 0;
adcFlag = 1;
}
}
void adcInit()
{
ADMUX = (1 << ADLAR);
ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
}
Как видите:
• На каждое тысячное (значение макроопределения ADC_COUNTER_MAX) измерение в обработчике прерывания устанавливается в 1 флаг
adcFlag. Реакция на это событие осуществляется в функции deviceControl, о чём — ниже.
• Во избежание шума результат измерения выравнивается по левому краю (бит ADLAR регистра ADMUX). Далее, в deviceControl, считывается значение лишь старшего байта ADCH регистра данных АЦП.
происходит посредством ассемблер-кода, представленного ниже.
WS2812B.SSREG = 0x3f
OUTBIT = 0
PORTB = 0x05
.text
.global stripRefresh
stripRefresh:
movw r26, r24
movw r24, r22
in r22, SREG
cli
in r20, PORTB
ori r20, (1 << OUTBIT)
in r21, PORTB
andi r21, ~(1 << OUTBIT)
ldi r19, 7
ld r18, X+
loop1:
out PORTB, r20
lsl r18
brcs L1
out PORTB, r21
nop
bst r18, 7
subi r19, 1
breq bit8
rjmp loop1
L1:
nop
bst r18, 7
subi r19, 1
out PORTB, r21
brne loop1
bit8:
ldi r19, 7
out PORTB, r20
brts L2
nop
out PORTB, r21
ld r18, X+
sbiw r24, 1
brne loop1
out SREG, r22
ret
L2:
ld r18, X+
sbiw r24, 1
out PORTB, r21
brne loop1
out SREG, r22
ret
.end
Принцип работы WS2812B, а также подробные пояснения к коду вы можете увидеть в
первой части статьи.
происходит через модуль HC-05 посредством протокола UART, код которого представлен ниже.
Serial.h#ifndef SERIAL_H_
#define SERIAL_H_
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdbool.h>
#define UART_BAUDRATE 9600
#define FREQUENCY 8000000UL
#define UBRR_VALUE FREQUENCY / 16 / UART_BAUDRATE - 1
void serialBegin();
void serialPrintln(const char* buff);
void serialSendByte(char symbol);
extern volatile uint8_t receivedByte[11];
extern bool receiveFlag;
#endif /* SERIAL_H_ */
Serial.c#include "Serial.h"
volatile uint8_t receivedByte[11], byteNum = 0;
bool receiveFlag;
ISR(USART_RX_vect)
{
receivedByte[byteNum] = UDR0;
byteNum++;
if(byteNum > 10)
{
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);
}
void serialSendByte(char data)
{
while (!(UCSR0A & (1 << UDRE0)));
UDR0 = data;
}
void serialPrintln(const char* buff)
{
while(*buff != '\0')
{
serialSendByte(*buff);
buff++;
}
serialSendByte('\n');
}
Отличие от аналогичного кода в первой части статьи заключается лишь в количестве принимаемых байтов (11 против 4). Обусловлено это следующими причинами:
а) Введён однобайтный ключ, назначение которого будет пояснено позже.
б) Добавлен 1 байт для номера светодиода, цвет которого необходимо изменить.
в) Значение цвета разложено на сотни, десятки и единицы для каждой составляющей (красной, синей и зелёной).
обусловлен кодом из файлов device.
device.h#ifndef DEVICE_H_
#define DEVICE_H_
#define F_CPU 8000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <string.h>
#include "Serial.h"
#include "adc.h"
#define STRIP_LENGTH 105
#define LEDS_NUM (STRIP_LENGTH * 3)
#define STRIP_DDR DDRB
#define STRIP_PIN PB0
void deviceInit();
void deviceControl();
void stripOff();
void setLedColor(uint8_t* p_buf, uint8_t led, uint8_t red, uint8_t green, uint8_t blue);
extern void stripRefresh(uint8_t * ptr, uint16_t count);
extern uint8_t buf[LEDS_NUM];
#endif /* DEVICE_H_ */
device.с#include "device.h"
unsigned char communicationFlag = 0;
uint8_t buf[LEDS_NUM];
uint8_t redColorValue, greenColorValue, blueColorValue, ledNum;
void deviceInit()
{
serialBegin();
STRIP_DDR |= (1 << STRIP_PIN);
stripOff();
adcInit();
sei();
}
void deviceControl()
{
if(receiveFlag)
{
receiveFlag = 0;
if(receivedByte[0] == 2)
{
communicationFlag = 1;
}
if(receivedByte[0] == 1)
{
ledNum = receivedByte[1];
greenColorValue = (int)(receivedByte[2] * 100 + receivedByte[3] * 10 + receivedByte[4]);
redColorValue = (int)(receivedByte[5] * 100 + receivedByte[6] * 10 + receivedByte[7]);
blueColorValue = (int)(receivedByte[8] * 100 + receivedByte[9] * 10 + receivedByte[10]);
setLedColor(buf, ledNum, redColorValue, greenColorValue, blueColorValue);
stripRefresh(buf, sizeof(buf));
}
}
if(communicationFlag)
{
if(adcFlag)
{
adcFlag = 0;
unsigned char adcCurrentValue = (unsigned char)(ADCH * 100 / 255);
if(adcPreviousValue != adcCurrentValue)
{
adcPreviousValue = adcCurrentValue;
serialSendByte(adcCurrentValue);
}
}
}
}
void stripOff()
{
memset(buf, 0, sizeof(buf));
stripRefresh(buf, sizeof(buf));
}
void setLedColor(uint8_t* p_buf, uint8_t led, uint8_t red, uint8_t green, uint8_t blue)
{
uint16_t index = 3 * led;
p_buf[index++] = red;
p_buf[index++] = green;
p_buf[index] = blue;
}
Коротко о каждой из функций:
1. В
deviceInit() настраивается необходимая периферия: UART, АЦП и пин PB0, управляющий лентой.
2. Функции
stripOff() и
setLedColor() служат для выключения ленты и записи в буфер параметров цвета определённого светодиода, соответственно.
3. В
deviceControl() циклично опрашиваются три флага:
а)
receiveFlag, сигнализирующий о размещении в массив
receivedByte пакета данных, поступивших от Java-приложения. Нулевой элемент массива receivedByte[0] содержит упоминавшийся выше ключ, в зависимости от значения которого происходит следующее:
• 1 — из данных элементов receivedByte[2]–receivedByte[10] «склеиваются» значения зелёной, красной и синей составляющих цвета, в который затем и окрашивается светодиод с номером, хранящемся в receivedByte[1].
• 2 — устанавливается в 1 флаг
communicationFlag, что разрешает МК отправку значения напряжения Java-приложению. Данное значение отправляется приложением в самом начале работы лишь один раз и свидетельствует о его готовности принимать данные от МК.
б) флаг
adcFlag устанавливается в 1 обработчиком прерывания АЦП по завершению измерения. В этом случает происходит отправка значения напряжения при условии, что communicationFlag установлен в 1.
Код приложения состоит из 5 классов, ответственных за:
•
Frame — графически интерфейс,
•
BluetoothClient,
IncomingMessagesLoggingRunnable,
DeviceDiscoveredLoggingCallback — обмен по протоколу Bluetooth,
•
Main — диспетчеризацию данных.
В конструкторе класса создаётся поле интерфейса (фрэйм), включающее в себя:
•
Button «Scanning» и «Connect» для поиска bluetooth-устройства и соединения с ним, соответственно.
•
ComboBox для отражения списка найденных bluetooth-устройств.
• 105
Label, имитирующих светодиоды RGB-ленты.
•
Button «Color» для вызова цветовой палитры.
•
Label с надписью «Luminosity».
•
ProgressBar, отражающий текущее значение напряжения на выводе PC0 МК, т.е. освещённости.
Рисунок 2. Графический интерфейс приложения
Методы класса призваны:
1.
actionPerformed() реагировать на нажатие кнопок:
• «Scanning» и «Connect» — установкой в 1 флагов scanningButtonFlag и connectButtonFlag, соответственно, реакция на что осуществляется в классе Main.
• «Color» — вызовом цветовой палитры.
2.
MouseListener() при нажатии левой клавиши мыши:
• окрашивать в предварительно выбранный цвет Label, на который в данный момент установлен курсор
• устанавливать в 1 флаг sendFlag c дальнейшей реакцией в классе Main.
3.
showMessageDialog() выдавать сообщение о соединении приложения с модулем HC-05.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Frame implements ActionListener {
JLabel luminosityLabel;
JProgressBar progressBar;
Color customColor = new Color(215, 215, 215);
Color currentColor;
MouseListener ml;
boolean scanningButtonFlag = false;
boolean connectButtonFlag = false;
boolean sendFlag = false;
JFrame frame;
JComboBox<String> comboBox;
JButton scanningButton, connectButton, colorButton;
Label[] label = new Label[105];
int ledNum;
int[] X = new int[]{
30, 53, 76, 99, 122, 145, 168, 191, 214, 237, 260, 283, 306, 329, 352, 375, 398, 421, 444, 467, 490,
490, 467, 444, 421, 398, 375, 352, 329, 306, 283, 260, 237, 214, 191, 168, 145, 122, 99, 76, 53, 30,
30, 53, 76, 99, 122, 145, 168, 191, 214, 237, 260, 283, 306, 329, 352, 375, 398, 421, 444, 467, 490,
490, 467, 444, 421, 398, 375, 352, 329, 306, 283, 260, 237, 214, 191, 168, 145, 122, 99, 76, 53, 30,
30, 53, 76, 99, 122, 145, 168, 191, 214, 237, 260, 283, 306, 329, 352, 375, 398, 421, 444, 467, 490
};
int[] Y = new int[]{
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193,
216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216,
239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239,
262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262
};
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == scanningButton) {
scanningButtonFlag = true;
}
else if(e.getSource() == connectButton) {
connectButtonFlag = true;
}
else if(e.getSource() == colorButton) {
currentColor = JColorChooser.showDialog(colorButton,"Choose",Color.WHITE);
}
}
Frame() {
ml = new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
for(int i = 0; i < 105; i++) {
if(e.getSource() == label[i]) {
label[i].setBackground(currentColor);
ledNum = i;
sendFlag = true;
}
}
}
@Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
};
frame = new JFrame("Bluetooth communication");
frame.setSize(555, 560);
frame.setLayout(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
scanningButton = new JButton("Scanning");
scanningButton.setBounds(125, 20, 110, 30);
scanningButton.setFont(new Font("Ink Free", Font.BOLD, 20));
scanningButton.setFocusable(false);
scanningButton.addActionListener(this);
frame.add(scanningButton);
connectButton = new JButton("Connect");
connectButton.setBounds(125, 60, 110, 30);
connectButton.setFont(new Font("Ink Free", Font.BOLD, 20));
connectButton.setFocusable(false);
connectButton.addActionListener(this);
connectButton.setEnabled(false);
frame.add(connectButton);
comboBox = new JComboBox<>();
comboBox.setBounds(275, 20, 130, 30);
frame.add(comboBox);
luminosityLabel = new JLabel("Luminosity");
luminosityLabel.setBounds(230, 410, 150, 30);
luminosityLabel.setForeground(Color.BLUE);
luminosityLabel.setFont(new Font("Ink Free", Font.BOLD, 18));
frame.add(luminosityLabel);
progressBar = new JProgressBar(0, 0, 100);
progressBar.setBounds(212, 440, 120, 20);
progressBar.setForeground(Color.BLUE);
frame.add(progressBar);
colorButton = new JButton("Color");
colorButton.setBounds(230, 300, 80, 30);
colorButton.setFont(new Font("Ink Free", Font.BOLD, 20));
colorButton.setFocusable(false);
colorButton.setEnabled(false);
colorButton.addActionListener(this);
frame.add(colorButton);
for(int i = 0; i < 105; i++) {
label[i] = new Label();
label[i].setBounds(X[i], Y[i], 20, 20);
label[i].setFocusable(true);
label[i].setBackground(customColor);
frame.add(label[i]);
}
for(int i = 0; i < 105; i++) {
label[i].addMouseListener(ml);
}
frame.setVisible(true);
}
void showMessageDialog(String message) {
JOptionPane.showMessageDialog(frame, message);
}
}
В данном классе создаётся экземпляр
DDLC класса DeviceDiscoveredLoggingCallback и содержится лишь один метод
startDiscovery(), обеспечивающий поиск bluetooth-устройств.
import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
public class BluetoothClient {
DeviceDiscoveredLoggingCallback DDLC = new DeviceDiscoveredLoggingCallback();
public void startDiscovery() throws BluetoothStateException, InterruptedException {
DiscoveryAgent agent = LocalDevice.getLocalDevice().getDiscoveryAgent();
agent.startInquiry(DiscoveryAgent.GIAC, DDLC);
synchronized (BluetoothClient.class) {
BluetoothClient.class.wait();
}
}
}
отвечает за приём данных от МК, для чего создаётся поток
input. Поступающие данные сохраняются в массив
buffer, после чего поднимается флаг
receiveFlag. Реакция на последнее событие реализована в классе Main.
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.bluetooth.RemoteDevice;
import javax.microedition.io.StreamConnection;
public class IncomingMessagesLoggingRunnable implements Runnable {
private StreamConnection connection;
public boolean receiveFlag = false;
int incomingData;
byte buffer[] = new byte[1];
public IncomingMessagesLoggingRunnable(StreamConnection connection) {
this.connection = connection;
}
@Override
public void run() {
InputStream input = null;
RemoteDevice device = null;
try {
input = new BufferedInputStream(connection.openInputStream());
device = RemoteDevice.getRemoteDevice(connection);
} catch (IOException e) {
System.err.println("Listening service failed. Incoming messages won't be displayed.");
e.printStackTrace();
return;
}
while (true) {
try {
input.read(buffer);
incomingData = Byte.toUnsignedInt(buffer[0]);
receiveFlag = true;
} catch (IOException e) {
System.err.println("Error while reading the incoming message.");
e.printStackTrace();
}
}
}
}
создаёт экземпляр
IMLR класса IncomingMessagesLoggingRunnable, и содержит следующие методы:
а)
openConnection() осуществляет соединение приложения с HC-05, создавая при этом отдельный поток
outputStream для передаваемых данных. Кроме того, метод запускает методы checkReceivedData() и sendData().
б)
checkReceivedData() запускает задачу
timerTask, которая при установке в 1 флага
receiveFlag поднимает флаг
dataReceivedFlag, реакция на что происходит в Main.
в)
sendData() отправляет микроконтроллеру 11 байтов с информацией о светодиоде, состояние которого необходимо изменить.
г)
deviceDiscovered() сохраняет адреса и имена обнаруженных bluetooth-устройств в одноимённые массивы.
д)
inquiryCompleted() поднятием флага
DDLCflag сигнализирует о завершении поиска bluetooth-устройств.
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.Timer;
import java.util.TimerTask;
public class DeviceDiscoveredLoggingCallback implements DiscoveryListener {
boolean DDLCflag = false;
String[] name = new String[5];
String[] address = new String[5];
int counter = 0;
StreamConnection connection;
OutputStream outputStream;
InputStreamReader inputStream;
IncomingMessagesLoggingRunnable IMLR;
public boolean dataReceivedFlag = false;
byte[] toSendBytes = new byte[11];
int receivedData;
public void openConnection(String address) throws IOException {
connection = (StreamConnection) Connector.open(address);
if (connection == null) {
System.err.println("Could not open connection to address: " + address);
System.exit(1);
}
outputStream = connection.openOutputStream();
ExecutorService service = Executors.newSingleThreadExecutor();
IMLR = new IncomingMessagesLoggingRunnable(connection);
service.submit(IMLR);
checkReceivedData();
toSendBytes[0] = 2;
sendData();
}
public void checkReceivedData() {
TimerTask timerTask = new TimerTask() {
public void run() {
if(IMLR.receiveFlag == true) {
IMLR.receiveFlag = false;
receivedData = IMLR.incomingData;
dataReceivedFlag = true;
}
}
};
Timer timer = new Timer();
timer.scheduleAtFixedRate(timerTask, 0, 200);
}
public void sendData() {
try {
outputStream.write(toSendBytes);
} catch (IOException e1) {
e1.printStackTrace();
}
}
@Override
public void deviceDiscovered(RemoteDevice btDevice, DeviceClass code) {
address[counter] = btDevice.getBluetoothAddress();
try {
name[counter] = btDevice.getFriendlyName(false);
counter++;
} catch (IOException e) {
System.err.println("Error while retrieving name for device [" + address + "]");
e.printStackTrace();
}
}
@Override
public void inquiryCompleted(int arg0) {
DDLCflag = true;
synchronized (BluetoothClient.class) {
BluetoothClient.class.notify();
}
}
@Override
public void serviceSearchCompleted(int arg0, int arg1) {
// TODO Auto-generated method stub
}
@Override
public void servicesDiscovered(int arg0, ServiceRecord[] arg1) {
// TODO Auto-generated method stub
}
}
В этом классе создаются экземпляры классов Frame и BluetoothClient.
Далее, в цикле опрашиваются флаги и в случае поднятия:
•
scanningButtonFlag — запускается поиск bluetooth-устройств,
•
DDLCflag — выдаётся сообщение о завершении поиска, активируется кнопка «Connect» и деактивируется «Scanning»,
•
ConnectButtonFlag — деактивируется кнопка «Connect» и активируется «Color», а также устанавливается соединение с выбранным bluetooth-устройством (в нашем случае — с HC-05),
•
sendFlag — формируются и отправляются микроконтроллеру 11 байт со значениями ключа, номера и цвета светодиода.
import java.io.IOException;
public class Main {
public static void main(String[] args) throws InterruptedException, IOException {
Frame f = new Frame();
BluetoothClient BC = new BluetoothClient();
while(true) {
if(f.scanningButtonFlag == true) {
f.scanningButtonFlag = false;
BC.startDiscovery();
}
if(BC.DDLC.DDLCflag == true) {
BC.DDLC.DDLCflag = false;
for(int i = 0; i < 5; i++) {
if(BC.DDLC.address[i] != null) {
f.comboBox.addItem(BC.DDLC.name[i]);
}
}
f.showMessageDialog("scanning completed");
f.connectButton.setEnabled(true);
f.scanningButton.setEnabled(false);
}
if(f.connectButtonFlag == true) {
f.connectButtonFlag = false;
f.connectButton.setEnabled(false);
String address = BC.DDLC.address[f.comboBox.getSelectedIndex()];
BC.DDLC.openConnection("btspp://" + address + ":1");
f.colorButton.setEnabled(true);
}
if(f.sendFlag == true) {
f.sendFlag = false;
int ColorValue;
byte key = 1, hundreds, tens, units;
BC.DDLC.toSendBytes[0] = key;
BC.DDLC.toSendBytes[1] = (byte)(f.ledNum);
ColorValue = f.currentColor.getRed();
hundreds = (byte)(ColorValue / 100);
tens = (byte)((ColorValue - hundreds * 100) / 10);
units = (byte)(ColorValue - hundreds * 100 - tens * 10);
BC.DDLC.toSendBytes[2] = hundreds;
BC.DDLC.toSendBytes[3] = tens;
BC.DDLC.toSendBytes[4] = units;
ColorValue = f.currentColor.getGreen();
hundreds = (byte)(ColorValue / 100);
tens = (byte)((ColorValue - hundreds * 100) / 10);
units = (byte)(ColorValue - hundreds * 100 - tens * 10);
BC.DDLC.toSendBytes[5] = hundreds;
BC.DDLC.toSendBytes[6] = tens;
BC.DDLC.toSendBytes[7] = units;
ColorValue = f.currentColor.getBlue();
hundreds = (byte)(ColorValue / 100);
tens = (byte)((ColorValue - hundreds * 100) / 10);
units = (byte)(ColorValue - hundreds * 100 - tens * 10);
BC.DDLC.toSendBytes[8] = hundreds;
BC.DDLC.toSendBytes[9] = tens;
BC.DDLC.toSendBytes[10] = units;
BC.DDLC.sendData();
}
if(BC.DDLC.dataReceivedFlag == true) {
BC.DDLC.dataReceivedFlag = false;
f.progressBar.setValue(BC.DDLC.receivedData);
}
Thread.sleep(20);
}
}
}
🎁
Исходники - kod.zip
15.82 Kb ⇣ 14
Наш файловый сервис предназначен для полноправных участников сообщества "Datagor Electronics".
Для получения файла зарегистрируйтесь и войдите на сайт с паролем.
Камрад, рассмотри датагорские рекомендации
🌼 Полезные и проверенные железяки, можно брать
Опробовано в лаборатории редакции или читателями.