Serial communication, automatically detect MCU from Java application. Part 1.
Let's say that we want to create a standalone device that can be connected to computer. There are many applications where this might be needed.
Cover image by SUEZ.
- Part 1 - Set communication rules and define address component.
- Part 2 - Define communication packet component
- Part 3 - Virtual Multithreading technique.
- Part 4 - Establish simple communication with MCU.
- Part 5 - Java setup.
- Part 6 - Java side Communication.
- Part 7 - Create handshake protocol.
- Part 8 - Swing GUI.
- Part 9 - Testing and creating executable.
Idea
Let’s say that we want to create a standalone device that can be connected to computer. There are many applications where this might be needed. For example, USB key for authorizing user, some low power wearable device from which user can pull data to a PC and analyse it, some micro-system that needs to connect to computer to log data, receive control instructions and report system state variables, etc. There are many ways to achieve this but in this series of, we can call them tutorials, we will use UART to establish communication between MCU and PC. For this application UART has some advantages and disadvantages. First of all, it is widely spread, large majority of MCUs has at least 1 UART, it is easy to implement, it can achieve medium transfer speeds and it can be easily converted to USB protocol. Some disadvantages are that it cannot achieve high transfer speed, it is prone to errors in a packet transfer, does not have much control over error detection, error correction and it cannot be sent over long cables.
With this in mind, lets set a goal for this series. We will create the firmware for MCU and GUI application for PC. When the MCU is connected to a PC and the application is running, application will handshake with the device and if handshake was successful, link will be established. If the application is closed, device will disconnect. If the device is removed while the application is running, connection will be closed. If the device is again connected, link will be established. In the application we will have input field where user can type some text and send is to the device. Upon receiving the message, the device will echo it back to a PC.
What will you need
- MCU with at least 1 UART,
- Basic knowledge of the Arduino Framework,
- Computer with JRE 10+ installed,
- Some IDE for Java development like Eclipse or IntelliJ,
- Some environment with Arduino Framework - Like PlatformIO.
My setup
- For programming MCU I am using PlatformIO integration with VSCode that I described in this post.
- For Java programming I’m using Eclipse with openJDK 11 that I described in this post.
- As a MCU I’m using custom dev board for STM32F103VE with STLink-v2 and custom FTDI FT232RQ board.
- As an operating system I’m using Ubuntu 18.
Defining communication rules
When you have some sort of communication in a project, it is always a good idea, before writing a code, to start by defining set of rules for communication packet. Communication packet is, we can say, one box of data that is structured in some particular way, so that when the packet is received, software knows what to expect. If a packet does not follow defined set, it is thrown away. This set of rules defined for the communication packet we will call packet format. Our packet format will be a little bit complex and overkill for this application but this will be done so at the end of this series we have reusable code. Our packet will be formated as shown:
A B C D E F G
| | | | | | |_ Stop byte
| | | | | |___ n bytes of message
| | | | |_____ Split byte
| | | |_______ Data type byte
| | |_________ 4 byte TO address
| |___________ 4 byte FROM address
|_____________ Start byteA- byte that has fixed value of 0x02 and indicates the start of the packet.B- 4 bytes that contains the address of the device that is sending packet. For example 192.168.1.2, B will have value of 0xC0A80102.C- 4 bytes that contains the address of the device that is receiving packet. It follows the same rule asB.D- byte that describes what is onFposition.- If
Fcontains STRING value -Dis 0x14. - If
Fcontains INTEGER value -Dis 0x15. - If
Fcontains FLOAT value -Dis 0x16. - If
Fcontains some custom data type - …..
- If
E- byte that has fixed value of 0x1D and indicates separation between packet information and packet message.F- some array of bytes that represent the message of the packet.G- byte that has fixed value of 0x17 and indicates the end of the packet.
We can see that address value is vary large, 1 byte could be easily used to represent address of a sender and receiver. This would use less memory, packets would be smaller, therefore faster to transfer. If you prefer this approach, you can use your packet format and follow along. Ideas and techniques are important.
Describing address component of the communication packet
Mainly, object-oriented approach will be used but you can do it your way. Lets start with an Address entity/component.
CONSTRUCTORS | DESCRIPTION
-------------------- | --------------------------------------------------------------
Address() | Create instance of empty address, will have value 0x00000000.
Address(b1,b1,b1,b4) | Create instance of address object, will have value 0xb1b2b3b4.
Address(bytes) | Create instance of address object, will have value 0xbytes. METHODS | DESCRIPTION
------------------------- | --------------------------------------------------
set(b1,b2,b3,b4) | Set value of address to 0xb1b2b3b4.
set(bytes) | Set value of address to 0xbytes.
set(value, byte_position) | Set value of a byte at position byte_position.
isSet() | Returns if address value is set or not.
get() | Return value of address.
get(byte_position) | Return byte at position byte_position.
toString() | Return value of address as an array of characters.
put(*address) | Put address bytes to address value array.Implementation of this in C++ would look something like the code bellow:
// -------------------
// ---- Address.h ----
// -------------------
#ifndef Address_h
#define Address_h
#include "WString.h"
class Address {
public:
Address();
Address(unsigned long bytes);
Address(unsigned char b1, unsigned char b2,
unsigned char b3, unsigned char b4);
void put(unsigned char *address);
unsigned long get();
unsigned char get(unsigned char bytePosition);
void set(unsigned char b1, unsigned char b2,
unsigned char b3, unsigned char b4);
void set(unsigned char bytePosition, unsigned char value);
void set(unsigned long address);
bool isSet();
String toString();
private:
unsigned long addr;
bool created;
};
#endif
// ---------------------
// ---- Address.cpp ----
// ---------------------
#include "Address.h"
Address::Address() { created = false; }
Address::Address(unsigned long bytes) {
addr = bytes;
created = true;
}
Address::Address(unsigned char b1, unsigned char b2,
unsigned char b3, unsigned char b4) {
addr = ((unsigned long)b1 << 24);
addr += ((unsigned long)b2 << 16);
addr += (b3 << 8);
addr += b4;
created = true;
}
void Address::put(unsigned char *address) {
address[0] = (addr >> 24) & 0xFF;
address[1] = (addr >> 16) & 0xFF;
address[2] = (addr >> 8) & 0xFF;
address[3] = addr & 0xFF;
}
unsigned long Address::get() {
return addr;
}
unsigned char Address::get(unsigned char bytePosition) {
switch(bytePosition) {
case 0: {
return ((addr) >> 24);
}
case 1: {
return ((addr >> 16) & 0xFF);
}
case 2: {
return ((addr >> 8) & 0xFF);
}
case 3: {
return (addr & 0xFF);
}
default: {
return 0;
}
}
}
void Address::set(unsigned char b1, unsigned char b2,
unsigned char b3, unsigned char b4) {
addr = ((unsigned long)b1 << 24);
addr += ((unsigned long)b2 << 16);
addr += (b3 << 8);
addr += b4;
created = true;
}
void Address::set(unsigned char bytePosition, unsigned char value) {
switch(bytePosition) {
case 0: {
addr = addr & 0x00FFFFFF;
addr += (long(value) << 24);
}break;
case 1: {
addr = addr & 0xFF00FFFF;
addr += (long(value) << 16);
}break;
case 2: {
addr = addr & 0xFFFF00FF;
addr += (value << 8) & 0xFF00;
}break;
case 3: {
addr = addr & 0xFFFFFF00;
addr += value;
}break;
}
}
void Address::set(unsigned long address) {
addr = address;
created = true;
}
bool Address::isSet() {
return created;
}
String Address::toString() {
String s = "";
s += "" + get(0);
s += ".";
s += "" + get(1);
s += ".";
s += "" + get(2);
s += ".";
s += "" + get(3);
return s;
}
As you can see, we are storing address value in single variable, therefore different approach could be used. Instead of creating object Address, we could have created header file with functions that can manipulate unsigned long variable. For example:
static unsigned long address_set(unsigned char b1, unsigned char b2,
unsigned char b3, unsigned char b4) {
return (((unsigned long)b1 << 24) + ((unsigned long)b2 << 16)
+ (b3 << 8) + b4);
}
...
This would be lass resource intensive but also a little bit harder to use. Since high level code optimization is not a goal of this series, refactoring will be done some other time. It is just important to have in mind that at the end, code should be optimized as mush as possible. In embedded systems you are all most always restricted with hardware resources. If possible, objects should be avoided.
How to user Address object
When you write some piece of a code, it is always a good ides to give some use cases and to explain how it should behave. This will help you, as a person who have written the code when you come back to it after a while, and a person that will use or continue work on the code. This is almost always overlooked because, if not done the right way, it is time consuming and not that interesting to do. But if you want to write stabile and professional application, good documentation is 50% of the job.
// This code can be tested on any Arduino board
#include "avr/io.h"
#include "Address.h"
int main(void) {
Serial.begin(115200);
Address addr;
// Setting each byte of the address
addr.set(192,168,0,1);
// Output: 192.168.0.1
Serial.println(addr.toString());
// Setting all bytes of the address at once
addr.set(3232235521);
// Output: 192.168.0.1
Serial.println(addr.toString());
// Output: 168
Serial.println(addr.get(1));
while(1) {
}
}
This part will end here. In next part we will define communication packet.