Github: All source-files are now made available on Github, zie ebrew_PC

Although a significant amount of hardware / electronics has been created, it is useless without any software controlling it. When I started with the design of my system I wanted to have full flexibility. I could go for a microcontroller based design, with embedded software in it, so you would not need a PC. But I have various PCs hanging around in my house, so why not use one of them (in a network configuration) for controlling your home brewery? In this way, I have lots of flexibility. If I want some new behaviour built into my brewing program, I make an update, test it and distribute it to the PC in the brewery.

This particular setup has worked for years without any problem. The hardware was connected to the PC by means of a parallel port (or printer port). With the transition to a new hardware platform, this was upgraded to a (nowadays) more common USB connection. And then the set-up with the microcontroller becomes interesting again. Lots of old functionality, like the generation of the PWM signal, is easy to realize for any microcontroller. So the amount of hardware components can be reduced, as a result of this. On one side you have the PC-program that communicates through the USB connection with an Arduino Nano / a ATmega328P microcontroller. This microcontroller also needs software, which we call firmware, to distinguish it from the PC-software.

What shall we use as programming language for both systems. Well, for the firmware this is quite clear: the Arduino Nano is used, for which I use Atmel Studio and the C programming language. For embedded systems you almost never have a decent alternative, so this is the preferred set-up. For the PC-program it is less clear because of the large variety of programming languages available. I do not really like the new programming languages like Java and C#/.Net (I never ever used a MS programming language). I normally write my programs in the C programming language, but I definitely needed a nice front-end GUI (windows, menus, all the stuff). C in itself is not really suitable for that. The solution however was to use the C++ programming language, you can use your existing C libraries and also build a nice looking program. And if you use Borland's very very excellent Borland C++ Builder, programming becomes a lot of fun!

So the configuration is chosen, the programming language is set. What to do next? Well, I can tell a bit about the internal structure of my brewing program (which I called e-brew! by the way, which stands for electronic brewing). This page consists of the following paragraphs:

1. Overview / Software-Architecture

The PC-program contains a number of important components which together make up the entire system. Because the C++ language is object oriënted, it is normal to identify a number of objects. Next to this, there are also certain components written in the C programming language. But these C components can easily be called from within any C++ object. The main components for the PC-program are:

The PC-program's main tasks are to allow the brewer to do all kinds of settings and adjustments needed for the brewing process (times, temperatures, parameters) and to send commands to the brewing-hardware on a periodic bas, using a standard communication protocol. The responses from the brewing-hardware are made visible to the various screens. The following screen-shot gives an overview of all project-files and screens used:

The total PC-application contains approximately 4400 lines of C++ code, of which the main-object (in Unit1.cpp) already contains 1600 Lines of Code (LOC).

Next to the PC-program there's a second important software system: the firmware for the ATmega328 on the Arduino Nano. This software-system also contains quite a lot of code: for revision R1.28 this was approximately 3300 lines of C-code. All-in-all: both systems together contain more than 7700 lines of code!

You can download a fully functional version of the PC-program here (.zip file, v1.83). In order to have this working correctly, you also need the hardware with the proper firmware.

Back to the Top of the Page

2. Scheduler

The e-brew! application demands strict timing-requirements. A lot of things need to be done at fixed time-intervals. Commands have to be send to the brewing-hardware, temperatures and volumes need to be read in, the screens need to be updated, etcetera. It would be nice to have a real-time embedded operating system (called an RTOS), but Windows is far from being real-time or embedded. To execute something every 100 milliseconds is already quite demanding. Fortunately, temperatures are not rapidly changing, so controlling this does not have to be very fast. But something is needed to make sure that all tasks are executed in time. For this I have written a separate scheduler component. A scheduler is a piece of software that makes sure that all tasks in a program are executed at the right time. The scheduler used here is non pre-emptive, which means that after a task has been started, it is not interrupted any more by another task. The next task can only start when this task has finished executing. This is sufficient for our brewing system. You just need to have a bit of attention for the duration of every tasks.

Tasks are easy to add with this scheduler component. To illustrate this, an excerpt of a bit of C code from the PC-application is given here:

	// Now add all the tasks for the scheduler
	add_task(task_alive_led     , "alive_led"    ,   0,  500); /* 0, 500, 1000, 1500 */
    add_task(task_read_temps    , "read_temps"   , 100, 2000);
	add_task(task_pid_ctrl      , "pid_control"  , 300, (uint16_t)(pid_pars_hlt.ts * 1000));
    add_task(task_update_std    , "update_std"   , 400, 1000); /* 400, 1400 */
    add_task(task_hw_debug      , "hw_debug"     , 600, 2000);
    add_task(task_read_flows    , "read_flows"   ,1100, 2000);
    add_task(task_log_file      , "wr_log_file"  ,1600, 5000);
    add_task(task_write_pars    , "write_pars"   ,1700, 5000);

Adding tasks is simple and convenient. The task task_update_std() for example executes the state transition diagram function every second (1000 msec.). You can also add an offset: task_update_std() is executed at time stamp 0.40 (400 msec.) and then at timestamps 1.40, 2.40, 3.40 etcetera. The PID controller, task_pid_ctrl(), is controlled in another way: the time-interval between two calls to this function is dependent of the parameter Ts, which is given in seconds. If this parameter is set in the PC-program to 20 seconds, this call here makes sure that the PID controller is executed every 20 seconds. As mentioned before, it is important to make sure that all of these tasks finish in time and do not take too much executing time, since this will delay other tasks. The scheduler is able to measure the time-duration and the maximum time-duration of every task. This information can be used for debugging purposes and looks like (result is from a log-file):

	Task-Name      T(ms) Stat T(ms) M(ms)
alive_led 500 0x02 0.4 0.5 read_temps 2000 0x02 51.3 51.6 pid_control 20000 0x02 0.0 0.0 update_std 1000 0x02 1.2 1.3 hw_debug 2000 0x03 0.0 0.0 read_flows 2000 0x02 49.9 49.9 wr_log_file 5000 0x02 0.9 1.0 write_pars 5000 0x02 0.0 0.0

From this, it appears that task_update_std() had a maximum time-duration of 1.3 milliseconds. Together with the code excerpt before, it gives a good insight in the behaviour of the various tasks. Debugging timing-issues is relatively easy to do. The scheduler itself is called from within an OnTimer() function of a Timer object. This is done every 100 milliseconds. The main function of this function is to update all task-timers and to administrate if a task needs to be enabled of disabled. The execution of a task is not done here, only the administration!

Back to the Top of the Page

3. State Transition Diagram (STD)

According to the free on-line dictionary of Computing, a state transition diagram (STD) is a "A diagram consisting of circles to represent states and directed line segments to represent transitions between the states. One or more actions (outputs) may be associated with each transition.". In terms of my brewing program, a STD is a central control function, that controls the various phases of the brewing process. A state can be a 'mash rest' phase or a 'mash preheat' phase. A transition from one state to another state can be made if a certain condition is TRUE (e.g. the 'temperature is OK'). Conditions in the STD below are given in RED. When a transition is made to another state, a particular action can be programmed (e.g. set pump on). Actions are given in GREEN. Summarized: there are states, transitions between states, conditions and actions. These need to be defined for the brewing program and is give here:

The total STD of the PC-program has become quite complex. A short explanation for every state is given here:

The table (upper right corner) shows, for every state given, which valves are open (1) or closed (0) and whether or not the pump is on (1) or off (1). By designing such a table, the behaviour of the various valves becomes very defined. You always know exactly when a valve (or pump) is open or closed, given the state the STD is in. There are many more details to tell about this state transition diagram, but this will be sufficient (I hope) to give you an idea about the functionality. The STD is called once every second from the scheduler.

Back to the Top of the Page

4. Graphical User Interface (GUI)

The scheduler with all the tasks and the State Transition Diagram are the heart and soul of the brewing program. But to make this flexible and easy to use when brewing, a nice front-end GUI is needed. Well, here is a screen-shot of the ebrew program:

There is a lot of information displayed on this screen, which is ideal when you are brewing and you need some specific information:

If one of the values, as read in from the brewing hardware, is not correct or when communication is not possible, the colour of these numbers change from GREEN to RED. The screen shot above shows that the MLT temperature and the three flow-sensor values are read in correctly.

4.1 The menu-bar of the ebrew program

The menu-bar of the ebrew program contains the following menu-items:

Back to the Top of the Page

5. Firmware voor de Arduino Nano / ATmega328

the firmware contains the C source-code for the ATmega328 microcontroller and has not much to do with the design of the PC-program described above. Both software systems do communicate with each other, using the virtual COM port function of the USB connection. A big advantage of the use of a microcontroller is that all hardware related and often time-critical tasks no longer have to be handled by the PC-program. A microcontroller is much better suited for this. At the PC side, we use the Arduino hardware boards. Prior to use, the Arduino Software Environment needs to be installed. This already installs all the drivers needed for the virtual COM port and the USB connection. At the microcontroller side, the story is a bit different. Fortunately we do not have to dig into USB communication (complex!), but we can use existing routines for serial communication. The microcontroller used (ATmega328P) contains a component called USART, which stands for Universal Synchronous/Asynchronous Receiver/Transmitter). As long as you have the proper USART library, it is relatively easy to start with serial communication. Settings used here are 38400,N,8,1 which means: 38400 Baud, no parity bit, 8 data-bits and 1 stop-bit. This is of course identical to the settings in the PC-program.

Back to the overall design of the microcontroller software. I do not use Arduino sketches and the Arduino Programming Environment, since it is too limited for what I need. From some sketches I made proper C libraries, others I wrote myself. As usual for a C project, for every component a C source-file with a header-file (.h) is made. Together this contains all the necessary routines for such a component. For example: the I2C module (with files i2c.c and i2c.h) contain all functions for I2C communication, including specific routines for the ICs used here, such as the PCA9544, the MCP23017 and the DS2482. The overall architectural design looks like the following:

Low-level routines: As you can see in the picture, there are libraries for I2C, SPI, ADC, PWM, One-Wire and USART. Basically for every low-level hardware component of the microcontroller. In order to really understand these libraries, you need to read the ATmega328P microcontroller data sheet. Especially if you would like to make adjustments. But, since the ATmega328P is a very popular microcontroller, there are many good working examples available.

Interrupt routines: The interrupt routines are time-critical pieces of code that cannot wait until the scheduler has time for them. It needs to be executed as soon as something happens. For the embedded design engineer it is important to keep these routines as small as possible: you just cannot start to wait until something happens in such a routine. A nice example of the use of such an interrupt routine are the flow-sensors. These are connected to two port pins of the microcontroller. As soon as a pulse comes in from a flow-sensor, an interrupt is generated by the microcontroller. The interrupt source routine does nothing more than to increase a software counter. Every flow-sensor has it's own unique interrupt routine. Besides these routines there is also a timer interrupt defined for the scheduler. This one is executed every millisecond. Finally there are two more interrupts defined for the serial communication. If the USART hardware from the microcontroller has received a byte, an interrupt is generated. The interrupt routine merely stores the byte read into a ring-buffer. Something similar happens when there is data in the ring-buffer that needs to be send to the PC-program.

Next to these low-level libraries, there is also a miscellaneous library. This library contain functions that deal with signal processing, such as filtering. It contains a moving-average filter (which is a low pass filter) and a slope-limiter function. Furthermore a ring-buffer for the serial communication and a RS232 command handler. Every command from the serial interface is processed by this function.

Ethernet/Wiz550io: the brewing-hardware has already been prepared for the addition of a WIZ550io Ethernet module. The C-libraries are derived from several Arduino sketches (which were a mess to read). With this addition it becomes possible for the brewing hardware to send/receive commands using the UDP protocol, instead of using the USB connection. This however, is not a standard part of the brewing system, but merely work-in-progress. Adding such a library impacts the resources of the microcontroller (Flash memory and RAM memory), so this should be done with care.

The scheduler from the PC-program is reused here as well. So the software is used on various platforms.

Back to the Top of the Page