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.

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

My setup

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 byte

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.

Share on: