Android DEMO app

The best way to test the URB Project is by running free DEMO app on the Smartphone. Let’s see how it performs.
To transmit commands to control your train from the application the Project uses serial data transmission over Bluetooth an HC-05(06) Module. This is module is recommended for use in conjunction with Arduino NANO. It converts radio signal data back into electrical signals. The main advantage of this approach is possibility of ignoring all of the added complexity of working with Bluetooth software.

After pressing any button, or by moving the slider in the application, a four-character command of the Universal Railroad Bus (URB) Protocol is received by the Arduino RX input. Now comes the simplicity of the project. We need convert these symbols into the rotation of the locomotives electric motor at different speeds. I conceived the URB as a project that Uses the simplest sketch code that any modeler can repeat/duplicate. Therefore, code of Serial Event is used for parsing protocol commands, which is included in the Arduino IDE examples for beginners. If you remember how this code works, this should be enough for implementation of my system to your railway and any devices on it. Please, watch the videos demonstrating these principles for a better understanding.


First circuit

This circuit is a typical version of a robotic control based on Arduino. The only difference is that the output of the motor-driver is connected to the rails, and not directly to the motor. Before the assembly of the circuit, you need to upload a program called a sketch to the Arduino.

Hardware Requirements:
  1. Arduino NANO board
  2. Arduino NANO screw terminal shield
  3. Bluetooth module HC-06 or HC-05
  4. Motor-driver L298
  5. Any USB Cell phone charger
  6. Power supply from your railway set or similar
Download sketch

More fun

Let's add a servo-operated turnout to the railway circle. You can see the options for manufacturing such a drive on my website or create it yourself.

Using the built-in Arduino servo library, you can adjust the angle of rotation of the servo to the Angles/degrees you need. You can change these settings directly in the code by re-updating the sketch. This skill will be useful to you later in the project.

Download sketch

 

All the possibilities of the URB project are revealed when several Arduinos are connected to the mesh. The flexibility of the system lies in the fact that you can use several types of Arduino boards in one project/layout and connect them together using I2C bus. More on page Examples.


I hope your experiments has been successful as well. Next, a little addition information.

Most modelers like a complex layouts/systems. If you look at most layouts or rail lines, there are always lots of wires, switches and devices. But, when the modeler sees a sketch of code over 300 lines for his layout, then this amount of code scares them. Anyway, the amount of code Arduino directly corresponds to the complexity of your track-plan and layout. But the good news is that the more Arduino boards you use in your layout, the simpler and shorter the sketches code is.

A common mistake is the use of microcontrollers that are the hottest trend. Even my friends, professional C++ programmers and first-time microcontrollers programmers, before writing code for modern microcontrollers from ARM, NRF, Intel, AMD and so one start learning world of MCUs with ATmega328 from Microchip (Arduino UNO/NANO). Many modelers, on the contrary (for strange reasons) are buying the complex ESP systems or Cortex-base boards and try to use it right away. This requires the need for you to install additional cores in the Arduino IDE and there are differences in writing the code of sketches.

The frequency (16 MHz) of ATmega328 is sufficient to control any systems used for railway modeling. It makes no sense to use realtime high-speed microcontrollers with frequencies from 100 MHz and above. Also, this MCU operates using 5V logic, which means that compared to the 3.3-volt logic of other microcontrollers, you will not have problems transmitting signals over long distances and have more power on each GPIO.

The basic condition of the URB Project is the maximum simplicity of programming. Therefore, only Arduino NANO (UNO) boards are used in the examples. It is enough for you to understand these four principles: the types of variables, the construction of if-then-else, the structure of the sketch and how GPIOs work.

Types of variables

In the revision of this site for the URB project, I will use only three types of Arduino variables — Char, Boolean and byte.

Type Char will hardly be used to create your sketches. You can simply copy the parsing block from the sketches of examples, you do not need to think about it. But you will insert boolean and byte variables into your code all the time. Boolean and byte variables are processed by the microcontroller almost instantly, since they practically do not take up memory and processor time. Therefore, you will never have problems with the 30 Kbytes program RAM limits of Arduino NANO (UNO).

Most often in a URB Project you will need a Boolean variable. You will use Boolean to checking sensors status, to switching signals, creating custom logic, and activate lines using relays.

When use of this simplest variable, there is a catch that sometimes confuses modelers. The numbers 1 and 0 are sometimes swapped. For example, you can connect a relay module so that it will turn on the line at the state of the variable 0 (HIGH level on pin connected to the relay) and turn off at 1 (LOW). Another similar situation is with signals using LEDs. Usually, the common wire of LEDs is the anode (+), so you will connect the cathode terminals (-) of LEDs to GPIOs. And in this case, you will use the HIGH state to turn off and LOW to turn on, for example, a red LED of signal.

If-then-else and little more

The if… else allows greater control over the flow of code than the basic if statement, by allowing multiple tests to be grouped. An else clause (if at all exists) will be executed if the condition in the if statement results in false. The else can proceed another if test, so that multiple, mutually exclusive tests can be run at the same time.

Each test will proceed to the next one until a true test is encountered. When a true test is found, its associated block of code is run, and the program then skips to the line following the entire if/else construction. If no test proves to be true, the default else block is executed, if one is present, and sets the default behavior.

Note that an else if block may be used with or without a terminating else block and vice versa. An unlimited number of such else if branches are allowed.

Here is a simple example of signal processing from an analog sensor: if (analogRead(A3) > 128) { sensor_A = true } else { sensor_A = false }. To set the trigger threshold of analog sensor, you only need to change the digit.

Again we will not do without a problem with the Boolean variables. Since there are only two comparison states for this variable — equal or unequal, lazy programmers write it briefly and incomprehensibly. Compare two different code snippets that run the same way: if (sensor_A == true) { digitalWrite(LED_BUILTIN, HIGH) } and if (sensor_A) { digitalWrite(LED_BUILTIN, HIGH) }. Another version with inverse operator ! — if (!sensor_A) { // Nothing } else { ditalWrite(LED_BUILTIN, HIGH) }.

In the case of a branched station, there may be much more signals, and there may be several code options for controlling these signals. In order not to get confused, there are rules for creating code for the URB system. All relay and signal status depend primarily on the position of the turnouts, and only after that signals from sensors or automatic commands are taken into account. The turnout has a Boolean state of 1 if its switch rail is in the straight position, otherwise its Boolean state is 0. The linear arrangement of turnouts also matters, since the code in Arduino is processed sequentially. In the Examples I will show these features and the use of operators if… else, &, || and ! in more detail.

 

if (Junction_A) {
    digitalWrite(Signal_1_RED, HIGH); // RED LED OFF  
    digitalWrite(Signal_1_GREEN, LOW); // GREEN LED ON  
    digitalWrite(Signal_2_RED, LOW); // RED LED ON 
    digitalWrite(Signal_2_GREEN, HIGH); // GREEN LED OFF
}
else {
    digitalWrite(Signal_1_RED, LOW); // RED LED ON  
    digitalWrite(Signal_1_GREEN, HIGH); // GREEN LED OFF  
    digitalWrite(Signal_2_RED, HIGH); // RED LED OFF 
    digitalWrite(Signal_2_GREEN, LOW); // GREEN LED ON  
}
                    

GPIOs of Arduino

A general-purpose input/output (GPIO) is an uncommitted digital signal pin on an Arduino board which may be used as an OUTPUT or INPUT, or both, and is controllable by software. In simple terms, in the OUTPUT mode, any pin can have only two states: HIGH or LOW (0 V or 5V) according to the algorithm you set in the sketch. If you need to process an external signal from the sensor, buttons, etc., you can set the pin to INPUT mode. However, it is important to note that you can use the any pin to connect external devices. You set the rules for the operation of pins yourself in the title of the sketch. The URB Project uses the following code for this:

// I/O PINS
#define RELAY_LINE_1 3 // Reminds you that you have connected the Relay Line 1 to pin D3
#define RELAY_LINE_2 10 // Relay Line 2 to pin D10
#define SENSOR_BLOCK_1 7 // Digital sensor on entrance of Block-section 1 to pin D7
#define SENSOR_BARRIER A3 // Analog sensor to pin A3
#define BUTTON_ALARM 2 // Button to pin D2

void setup() {
// Initializing pins
  pinMode(RELAY_LINE_1, OUTPUT);
  pinMode(RELAY_LINE_2, OUTPUT);
  pinMode(SENSOR_BLOCK_1, INPUT);
  pinMode(SENSOR_BARRIER, INPUT);
  pinMode(BUTTON_ALARM, INPUT_PULLUP);
                    

Renaming pins from numbers to a meaningful name will make it easier for you to write sketch code to avoid logical errors. Some of the pins of the Atmega328p microcontroller have various additional features. For example, the PWM signal can be generated only on pins D3, D5, D6, D9, D10 and D11.

The current for each GPIO in OUTPUT mode is limited up to 40mA. Such voltage and current are sufficient only for signal LEDs, while it is necessary to install a series-limiting resistor of near 100 Ohm. You can also directly connect the control contacts of such Arduino modules as relays, sensors, servo, motor drivers.

A more powerful load can be controlled by Arduino using boosters or relays. One of the options for boosters (current and voltage amplifiers) is the motor-driver discussed above. In most cases, a change in polarity is not necessary, so it is enough to assemble a circuit from several transistors. In the URB project, instead of transistors, ULN2X03 chips are used, where these transistors are already assembled into a Darlington Array. You can also use any other current output drivers on chip.

Structure of sketch

In the URB Project, sketches are divided into 4 modules. The first module describes global variables, and, if necessary, their initial values. Declares required for this unit of the library. Within the first part its also mandatory to have the function of reloading a unit on an external command. There is also overriding the Arduino pins using the #define compile component. In the second part, the modes of the Arduino pins are set, the used libraries are initialized and running commands are given to the your layout devices so they took the default position. The third part is the loop, and it is in it that all commands are executed. And the last part is the functions that will be discussed in detail later.

Your layout is quite complex in terms of wakeup control. The position of turnouts and signals, the order and logic of the power switching on of the lines, the state of the global settings at the power-up of the railway layout must be uniquely determined. In this project, this problem is resolved in two ways. The first, obvious, in the void setup(), you describe not only the purpose of the GPIO pins, but also their meaning. Then you, in accordance with them, bring the layout elements to the initial state. The second way is to use the RESET function. This function resets each URB by a common command. Then the same thing happens as in the first method. The reset command in the Protocol has the syntax 999z, and is executed every time the application is successfully connected via Bluetooth. You can also force the layout lines at any time by pressing the DEFAULT button in the app. As soon as in the third part of the sketch is recognized of the reset command, control is transferred to the reset function void(* resetFunc) (void) = 0;

The second part of the code, which causes many difficulties in understanding it, is the function. The code in the ATmega microcontrollers in a void loop() is executed sequentially line by line. One wonders, therefore, how running Arduino libraries if their code is not in a loop. Imagine this situation, you leave to the threshold of your house and saw that it was raining. You return home, take an umbrella and again return on a threshold. That is, you are on the doorstep again, but you have an umbrella. The meaning of the function is the same. Your sketch will be executed line by line with the ATmega microcontroller until it find the instruction yourfunction() (call function), after which it will go to the code block void yourfunction() {...} and then return to the main code.

You can assign any name to your function. Here is a simple example of a modified BLINK sketch code from examples Arduino IDE:

// run once on start or reset MCU
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  yourfavoritname(); 
}

void yourfavoritname() {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);                    
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);  
}                       
                    

The library code is called in the same way. That is, the library is a function! That is, the principle of line-by-line code execution is always respected for Arduino NANO, UNO, MEGA and etc.


URB

After several successful experiments railway modelers will eventually face the problem of lack of free I/O pins (GPIO) on Arduino. Many people try to solve this problem directly by changing from their current board (UNO or NANO) to a bigger Arduino board (DUE, MEGA). This to me seems like the wrong approach because the problem still remains. The best way — to unite the microcontrollers to the network, allow scale the number of pins of the microcontroller to almost endlessly. The I2C bus is a good fit for this. It has native support with the Wire library in the Arduino, and has addressing in contrast to the serial connection solution. The second way to use many serial connections between Arduino boards, but for this, need use Arduino MEGA as a COMM unit. The URB Project uses the method of combining a many of Arduino into a single network via a wire I2C bus. Together with the 5-volt power wires, a Universal Railway Bus (URB) is obtained. I also developed a wireless WRU project for advanced users.

In the URB Project, Arduino boards with devices connected to them are called units. There is always one main COMM unit with Bluetooth modules connected to it. The COMM unit distributes incoming commands from the application to Local units. Usually, a motor-driver is also connected to it.

The I2C bus allows you to transfer data to the specific address of the URB unit, but, unlike a Serial connection, it can transfer it only as bytes. That is, it is necessary to translate alphanumeric (String variables) commands into numbers (Byte variables). I have introduced restrictions on the transmission of only one byte on the bus, that is, any digits from 0 to 255, this is more than enough.

Here is a fragment of code that sends the rotate command of a servo on Local unit from the COMM unit:

if (inputString.charAt(0) =='j') { 
  // Junction A button on app
  if (inputString.charAt(1) =='a') { 
    if (inputString.charAt(2) =='0') {
      addressI2C = 4; dataToI2C = 30; sendDataViaI2C();
    }
    if (inputString.charAt(2) =='1') { 
      addressI2C = 4; dataToI2C = 31; sendDataViaI2C();
    } 
  } 
}                      
                    

Fragment of code the Local unit #4 which rotates the servo connected to it, by command from COMM.

// COMMAND PARSING
  if (dataFromI2C != 0) {
    if (dataFromI2C == 30) MyServo.write(0);
    if (dataFromI2C == 31) MyServo.write(180);
  }                 

See more in detail in the video.

Download sketches from video

 

Resume:
The main advantage of my project is the ability to combine many microcontrollers (Arduino boards) to each other, as well as to external computers or other digital devices. Thus, there are no hardware restrictions on the number and types of devices using on your layout (turnouts, sensors, signals, lights, any mechanisms and other). This is a clear difference to other railway control systems.