Wednesday, June 24, 2020

PCMON (LED-display)

Did you know that the 32x16 dot Red/Green LED display from eBay is almost exactly the same size as two 5.25" half-height drive bays? Well now you know! One day I happened to look at this display that I've had on my table for maybe a year (not connected to anything) and thought that the size could be similar. The reason why I thought about that is that I do have a full-size PC next to my TV for playing games. This means that the PC is quite far away from the viewer and this kind of low resolution display would be quite perfect for PC monitoring system explained in the previous post. Especially when the display can be embedded into the PC case and will not be an external entity. To be honest the display is a little too low resolution and the text can be read from quite far away.. But it will do for this project.

Unfortunately they are not exactly the same size. The display is 152x76 mm (ish) and two 5.25" half-height drive bays (so that's the full-size 5.25" drive bay apparently, honestly didn't know that) are 146x82 mm. This means that the display is a few millimetres wider than the drive bay and somewhat lower. Filling the vertical gap is easy, but the width is a problem. It's fortunately not such a big problem, since the hole for the drive can be even bigger than this. I've measured that the display (LED matrix elements) are only 3 mm wider than the hole in my trusty Nexus Breeze case. So this requires a little bit of very accurate sanding...

The Display

The 32x16 dot Red/Green LED display module is quite simple. It contains 8 8x8 Red/Green LED matrices and some control logic. There are two decoder chips (74HC138D), 16 transistors (one for each line), 8 shift registers (74HC595D), ceramic capacitors (one for each chip) and one electrolytic capacitor. It's quite important to note, that there are no resistors on this board. So there is absolutely nothing limiting the current that goes to the LEDs. This causes some problems that I will explain later. The interface of the display contains 4-bit input for the line selection, two serial inputs for the red and green colours, one input clock for the serial line, output enable and "strobe" to move the serial data to the output of the shift register. The strobe pin allows the device to load the picture data while the previous one is still being displayed. It's also good to note that the output enable pin is active low. This means that this pin should be pulled high for example when the driver board is being reprogrammed.

From this explanation it should be clear that the display will require some kind of driver board and cannot be directly connected to the PC (well perhaps with a parallel port, but let's not go there). However the driver board is easy to make with an AVR microcontroller (an Arduino if you like). The advantage of this display is that it's quite inexpensive in my opinion. Another obvious advantage is that it fits this purpose very well. It has great contrast, it's not too complicated and it seems to be almost perfect in size.

Display driver board

This is a glorious subtitle. However I'm not going present here the driver board that I'm currently using, because it's one of my first designs and it's not very good. However driving the LED display is very easy. According to the previous paragraph, driving the display requires the following steps:
  1. Shift in data of line n (using SPI hardware for example)
  2. Disable output (pull OE high)
  3. Strobe the data in (pull STR high)
  4. Enable the line n (using ABCD inputs)
  5. Enable output (pull OE low)
  6. Wait for some time so that the user can actually see the output
  7. n++ and repeat
For best results, this "loop" should be run in a timer interrupt so that the update intervals are consistent and the driver can do other things rest of the time. The idea is that some line of the display is switched on most of the time and the update takes as little time as possible (for maximum brightness). Another advantage of using a timer is that the same timer could be used to control the brightness. Timers usually have multiple channels for interrupts. The second interrupt could be set to only disable the output of the display and this would happen to each and every line at the same time. This would effectively mean that the display would be less bright like using PWM. Obviously the updating should be done fast enough that the flickering could not be seen by a human eye.

Now one thing that I failed in my driver is having an external pull-up for the OE-pin. Since the driver is in reset while being reprogrammed, the display is on and it's constantly displaying the same line. Now since the ABCD inputs are all low, this means that the uppermost line is switched on. So this line is displaying whatever data is still stored in the shift registers (since it's not emptied or reset in any way). This actually caused the uppermost line to stop working.. I haven't really tried to debug this issue, but I would assume that the respective transistor failed.

Another thing to note in this implementation is that the Red and Green colours have separate inputs, but I did not mention that in this example. Luckily the LED display is designed so that you can daisy-chain multiple displays. So it has the above mentioned inputs and respective outputs. I didn't want to drive the two serial inputs separately, so I simply connected the output of the Green serial line to the input of the Red one. This means that the displays are kind of next to each other. This can be thought of having 64x16 dot display so that the left half of the display is green and the right half is red. The colours are of course not physically next to each other so the pixel at index 0 and the pixel at index 32 are in the same location.

Design

So first of all one has to have some vision of what one wants to do. Since I decided to use this 32x16 dot display, the result has to be quite low resolution. Using 5x8 pixel font the display could fit only around 10 characters. This won't do. Using a smaller 3x5 pixel font makes a little bit more sense. Using this font we can still fit only 2 lines of text since there has to be a gap between lines. Since I wanted to fit as much (readable) information on the display as possible, I've decided to make three lines of text without gaps between them. The text should however be interleaved so that it's still possible to read. The rest of the space could be used for bar graphs. The design that I've imagined is shown below. It's also adjusted so that the upper line is unused since it doesn't work in my display..


The display is designed to represent CPU, memory and network utilisation with graph bars. Since the display has two colours, it can naturally display more colours by mixing these two. It is used here to represent orange and red at absolutely imaginary values of 60% and 80% (to have a cool effect). The orange colour is created simply by switching on both the green and red LEDs. It happens quite nicely so that the bars are exactly 20 pixels long which means that every pixel represents 5%. This simplifies the maths quite a lot..

Now there is at least one problem with this design. It is caused by the lack of resistors in the display as mentioned above. This causes the lines of the display with lots of active pixels to appear dimmer than the ones with less active pixels. When the CPU bar is full, the upper pixels of the CPU text are very noticeably brighter than the ones in the middle of the text. The first solution to this problem was to use a less solid bar. I've decided to show only every second pixel which can be seen from the picture later on. Unfortunately this is not quite enough and I will present a solution to this problem later on.

Programming

The program in this project is quite simple:
  1. Wait for the data from the UART block
  2. Update the picture buffer according to the input
  3. Repeat
Basically there are three interrupt handlers in this program. One is to receive the input data one character at a time and two timer interrupts to control the display. One timer interrupt is to output one line of data at a time and another to switch off the display for brightness control. Receiving the data is quite simple. All that is needed is an endless loop to receive one character at a time. The characters are stored in a temporary value until the '\n' is received. Then the '\n' is replaced with an '\0' and an atoi() function is called on the value. The value is then stored to a corresponding variable according to the first character. I've decided to implement it so, that the display is updated when a character U is sent to the device (with the termination character '\n' of course), and that's when the above mentioned endless loop should end until the display is updated and the loop starts over. This relies on the fact that no new data will be sent before the display routine has finished. Here's an example:

while(1) {
if(uart_rx_read_ptr != uart_rx_write_ptr) {
data_buf[data_ind++] = uart_rx_buf[uart_rx_read_ptr++];

if(data_buf[data_ind-1] == '\n'){
data_buf[data_ind-1] = '\0'; // Terminate properly
data_ind = 0; // Reset incoming data index

if(data_buf[0] == 'C')
cpu = atoi(data_buf+1)/5;

if(data_buf[0] == 'U')
break;
}
}
}

Updating of the display is also quite easy. First the picture buffer should be cleared for simplicity sake and then the new picture should be formed. First the text and then the bars. So first clean the buffer:

uint8_t update_buffer[SCREEN_SIZE/8];
memset(update_buffer, 0, SCREEN_SIZE/8);

Then add texts to respective locations (first two letters as an example, starting from line one):

update_buffer[1*8]  = 0b01101100;
update_buffer[2*8]  = 0b10001010;
update_buffer[3*8]  = 0b10001100;
update_buffer[4*8]  = 0b10001000;
update_buffer[5*8]  = 0b01101000;

Then add the bars to respective locations (there are two loops for two colours, note that they overlap for 4 pixels == 20% as explained before):

void draw_bar(uint8_t x, uint8_t y, uint8_t val) {
for(uint8_t i = 0; i < (val < 16 ? val : 16); i++) {
pset(x+i, y+0, 0);
pset(x+i, y+1, 0);
pset(x+i, y+2, 0);
}

for(uint8_t i = 12; i < val; i++) {
pset(x+i, y+0, 1);
pset(x+i, y+1, 1);
pset(x+i, y+2, 1);
}
}

And lastly the function to add pixels to the buffer (notice complicated maths which should include constants instead of random numbers):

void pset(uint8_t x, uint8_t y, uint8_t color) {  // color: 0->green, 1->red
update_buffer[y * 8 + color * 4 + x / 8] |= (1 << (7 - (x & 7)));
}

And that's it actually.

Optimisation

The first problem with this display is that the picture buffer is potentially updated at the same time as the display is updated (from the buffer). This appears as "tearing" on the display, some kind of glitches that are very fast but still noticeable to the human eye. I could try to take a slow motion video of this, but just trust me on this one. The solution is of course to use double buffering. It works in such a way that there are two picture buffers and two pointers respectively. One pointer points to one buffer and another to the second buffer. The display is always updated from one pointer and the picture is updated using the second one. When the update of the picture is ready, the pointers are swapped so that the update process is not seen on the display. For example clearing of the picture shown above cannot be seen on the display after this modification. Updating of the buffers can be done very easily (notice the same variable names as above):

void update_display() {
uint8_t *temp = show_buffer;
show_buffer = update_buffer;
update_buffer = temp;
}

As I've already mentioned, there is another problem with this display. The problem is that brightness of the lines differ depending on the number of active pixels. Using non-solid bar graphs helps this a little, but in my opinion it's not enough. The solution to this problem is to compensate this by adjusting the brightness of each line while the display is being updated. This can be done very easily by counting the number of active pixels in each line, converting this number to some kind of compensation value and storing it in an array. This array can then be used for each line when the display is being updated. Calculation of the compensation values should obviously be done before hand, so that the display update routine is as fast as possible. This calculation could be done for example in the "update_display" function shown above. The only problem here is that this compensation will limit the maximum brightness, because the lines with lots of active pixels need to be brighter than the rest.

When the user does not want the display to be on, the Python script could be simply stopped. This however will leave the display stuck with the last status that it was displaying. The Python script could obviously have some kind of command to disable the display, but this is needlessly complicated in my opinion. Also this wouldn't work if the Python script were to crash. Instead the display could simply have an idle counter, which is repeatedly decreased after some time interval and reset every time the display receives some data from the PC. When the PC no longer sends any data to the display, the idle counter will go to zero and at this point the display should clear itself.

The picture quality of the display could be improved further. One problem with the display is that the Green and Red LEDs have somewhat different brightness. This can be resolved by displaying Green and Red pixels separately with different brightness. This complicates things quite a bit and reduces the maximum brightness. Another problem is that the Orange colour (Red and Green LEDs on together) is significantly brighter than the other colours. This is obviously because there are two LEDs switched on instead of one. This could of course be resolved by splitting the Red, Green and Orange to three different phases.. This will of course reduce the maximum brightness even further.. I suppose one solution to all this is to make three separate picture buffers for Red, Green and Orange colours. Then have respective brightness values with the above mentioned compensation. Then switch on all the LEDs that should be active and only disable them one by one depending on the brightness. This however might make it very complicated, still reduce maximum brightness.

Installation

Installation of the device was not as simple as I would have hoped. Sanding the display down to fit into the slot did not really work. It would require sanding so much that it could damage either the LED modules or the PCB that is the "module". I've noticed that there are actually some tracks that are quite on the edge of the PCB. However the Nexus Breeze case has an outer groove in the slot so that a cover could be installed for each slot. This groove is only few millimetres in size but it was enough so that I didn't need to sand down the display much more. This means that the display is not really in the case but kind of attached to it. Additionally it was such a nice fit that I only needed to push the display into the groove and it just stays there without any other means.

Installation required also a special cable. Since I wanted to make this display as integrated as possible (the text above states otherwise..) I had to make a special cable for this device. My driver board has a USB-serial adaptor integrated and also a micro USB port. This means that I needed a cable from PC motherboard to micro USB. Luckily I had a USB cable that I wasn't very fond of, so I simply cut the USB-A connector and soldered such a connector that could be connected to the PC motherboard header instead. I also cut the cable to a proper length so I don't have an extra meter of USB cable inside of the case. The motherboard side of the cable is shown in the picture below. It's not properly insulated but not much is properly insulated on the motherboard in any case.


Result

As I tried to explain above, the display is not actually in line with the front panel. This however is not such a big deal. One option would be to cut the front panel of the PC case a little, but I didn't want to do that. Also the display could be attached in a sturdier manner with the holes that are in the LED module. The holes however should be populated before soldering the LED displays in place.. I didn't do that and I don't fancy desoldering four LED modules, solder wick is not that cheap. In any case this LED module is broken as I also explained above, so I've already ordered a new one just in case. Regardless of that, the display looks quite good when you think about how much it took to build it. The display is not very visible in bright conditions, but I don't play with the lights on anyway. Some coloured filter could improve the visibility, but I don't have anything suitable and don't consider it necessary in this case.


The other problem here is that the display takes up to 8 seconds to power off after it stops receiving input through the serial line. This is because of how the code was implemented on my driver board and I was too lazy to change that. This is not really a problem, but even after some time it's still quite confusing since the PC itself shuts down quite quickly. Overall I would still say that the gain/effort ratio is quite good for this project..

ps. What I forgot to discuss here is the current consumption of the display. As already mentioned, the module does not include current limiting resistors. This however doesn't mean that unlimited current flows through the LEDs. The current is instead limited by other components, meaning the used logic chips and transistors. There are no specs for the current consumption of the module, but I have tested that it uses 500 mA when all the LEDs are switched on. This was measured with the refresh rate of around 122 Hz and without the feature to control the brightness. If the interrupt is enabled for the brightness control, the maximum current consumption drops to around 400 mA because of the overhead. This value is well within the spec of the USB so there is absolutely no problem regarding the current consumption when using the display from a USB port.

Tuesday, June 23, 2020

PCMON (Python code)

As my first post in this blog I shall be very imaginative and describe a PC-monitor software. I've also decided to call the said software PCMON which is also very imaginative and should not be confused with multitude of other software with the same name. The reason for this project is that I spend a lot of time in front of the computer and would like to know what it's currently doing. You know, knowledge is power and so on. There is of course a performance monitor in each and every modern operating system, but it has to be explicitly open unless you use Conky or Rainmeter or some Windows Widgets. None of these options really work while playing games or using any other software in full screen. This is why I want to have an external physical display to show such info.

I suppose a single most amazing piece of software that I have ever used is LCDSirReal. This piece of software was made for the Logitech G15 keyboard that had an LCD display embedded into the keyboard. Now the display by itself is quite useless even with the software provided by Logitech. LCDSirReal however was a game-changer. It was designed to provide support for showing at least the following info on the display: clock, date, CPU usage, memory usage, network usage, music player track info, Fraps fps info, TeamSpeak info, speaker configuration and whatnot. It's also amazingly small, it's very light resource wise and it had lots of different configurations for each feature. For the CPU usage it could be set to display the usage of up to two cores or just the "most utilised" and "least utilised" cores. However the keyboard is quite outdated and is not on sale anymore. Moreover the LCDSirReal is not opensource, so we can only borrow ideas from it.

I've been planning to implement this kind of physical display for a long time already (something like 5 years). Not really planning how to do it, just planning to start doing it.. In any case, this calls for a quick n dirty solution. I've decided to implement the required PC software using Python 2.7 (I personally don't care that it's outdated). I don't enjoy writing software in Python and hope to eventually rewrite this code in C++. Python has however the advantage of ready made packages that are easy to install. In this case we can use the psutil package. This package contains useful functions like cpu_percent (CPU usage), virtual_memory (memory usage), disk_usage, disk_io_counters and net_io_counters. As a bonus, this Python code should also work in Linux. That however is a topic for another day.

So how implement this in practice? First you have to install Python 2.7 and psutil. Psutil can be installed with the command python pip install psutil. Then you can import each of the needed functions with a command from psutil import cpu_percent,virtual_memory etc. After the import the functions can simply be used in the code. It's important to note that cpu_percent has an optional parameter to specify the measurement length. Measuring CPU utilisation with zero time interval does not make any sense and by default the function will measure the utilisation since the last function call. Since we are going to measure values continuously, specifying this time interval here is equivalent to adding a delay with the same interval. virtual_memory on the other hand returns many values, and we actually want to specify that we want the percent value. disk_usage has a mandatory path to a drive that needs to be monitored, 'C:\\' works for windows in this case.

Disk and net IO counters are a little bit more complicated. They measure cumulative values. So to get the net speed, you need to compare the new return value to the previous one and divide by the time interval between measurements. It's also good to note that these counters return values in bytes. Internet speeds on the other hand are usually measured in bits so you need to take that into account if you want to get the network usage as a percentage. Lastly we want to put this in an endless loop to read the values until the PC is shut down. Another thing we want to do at this point is to decide a format for the values that will be sent via a serial port to the display. I've decided to just use the following format: one or multiple characters, integer value and a newline ('\n') as termination. So for example "C75\n" would mean that the CPU is running at 75%. Here's an example for the project that I'm currently working on.

import serial
from psutil import cpu_percent,virtual_memory,net_io_counters

ser = serial.Serial('COM1', 9600, timeout=1)
previous_net_down_total = net_io_counters().bytes_recv
while True:
cpu_usage = cpu_percent(interval=1.0)
mem_usage = virtual_memory().percent
net_down_total = net_io_counters().bytes_recv
net_down = (net_down_total - previous_net_down_total) / 1.0
previous_net_down = net_io_counters().bytes_recv
text  = "C%d\n" % (cpu_usage)
text += "M%d\n" % (mem_usage)
text += "ND%d\n" % (net_down * 100 * 8 / (1 * 1024 * 1024 * 1024))
ser.write(text)
print "%s\r\n" % (text)

The example above scans the CPU, memory and network usage and prints them out as percentage in the format mentioned above. These values are also sent out via the serial port COM1, which is incidentally the only part of the code that is platform specific (AFAIK). Some other notes here. The cpu_percent function can return values per CPU for multi-core systems. This was not used in the example for simplicity. Three values chosen in the example are actually the ones that I'm going to use with the display that I will present in the next post. The display will be the 32x16 pixel Red/Green LED display from eBay.

In my opinion this could be considered as a finished product. The last thing to do is to make it start automatically with the PC and preferably in such a way that there is no visible terminal open. This can be easily done by using pythonw.exe. Adding a shortcut to the windows start menu under "startup" with the target "C:\Python27\pythonw.exe pcmon.py" where pcmon.py is the script we want to run. Additionally the "working directory" should be set to a folder where the script is located. Specifying the working directory is a good practice if we want to save some logs into the same folder as the Python script. Unfortunately pythonw seems to crash from time to time in this use-case (pyserial & psutil) and I currently have no idea why. In that case the only option is to simply use python.exe and set the window to be minimised at startup. This is slightly annoying and I know there are apps that could allow hiding this terminal window, but I haven't come up with anything simple yet. One option would be to use some simple app, or set up the script as a Windows service.

Although I would say that this is a finished product, it's not very nice.. There are some hard-coded values like the port and the time interval, there is no error checking for opening the serial port and there is also no check for when the port is forcefully closed. These are not a problem when this script is used for only one machine with one display and the display is attached to a real or a virtual serial port. However if the script is used for multiple machines, it would be nice to be able to choose the serial port with command line arguments. The last two problems on the other hand are very important if the device is sometimes disconnected or if it's connected via serial over a Bluetooth link. I've noticed with another project that after starting my PC, the Bluetooth serial link might take up to two minutes to open up. Additionally it seems that the Bluetooth connection is also sometimes lost. This happens regardless of the fact that the device that is connected to the PC via Bluetooth is less than one meter away from the PC and neither of these are moved...

ps. The PCMON name might be related to the fact that I was watching Futurama again at the moment I was designing this project. After imagining how Hermes Conrad would pronounce this name, it's quite difficult to un-imagine that.

Monday, June 22, 2020

About this blog

So I've decided to start a blog with the most imaginative name ever. Well the name was free and I kind of like it. It sums up quite well the way I do stuff. I couldn't come up with a proper name and I needed to tell some stuff so there you have it. I'm not sure why I wanted to start a blog especially now but here are some plausible reasons.

Thesis

One of the most obvious reasons is that I've recently finished writing my thesis and I'm stuck in some kind of writing mode. I want to write down and share stuff that I've come up with and stuff that I've created. There are two additional reasons. While writing my thesis I come up with lots of ideas about stuff I want to build and possibly share here. The other reason is that writing a thesis is very annoying because of the strict formatting rules, deadlines and requirement of sources. Writing a free form blog is a much nicer experience.

Sharing

As already mentioned above, I want to share some of the stuff that I come up with. Someone might even find useful the stuff that I'm going to write down. I guess I didn't start a blog earlier since I was afraid of making mistakes. Now I simply don't care anymore. There's always a possibility of making a mistake and there's always someone who is better at something. The idea is to learn from own mistakes and also learn from others. I personally often search internet for solutions to specific problems and also descriptions of finished and projects. These are the two things that I will try to provide in this blog. Hopefully.

Examples

I will try to add my projects to GitHub, share videos on YouTube and share schematics and PCBs on EasyEDA later on. Right now I'm not completely ready for it. Obviously sharing snippets of code and pictures is better than nothing, but sharing full projects would be also nice.

Benefits

I suppose one of the best things for now has been the fact that I'm able to organise and prioritise my projects a little bit better than before. This is simply because I want to finish writing a blog post so I need to finish the project to some sensible level. And obviously I can do them only one at a time. Perhaps starting to write a post before the project is finished might help me in that way (I think it already has).

Internet of Crosstrainers, part 2

Introduction As mentioned in the original post here , there were some issues in the described implementation. I had some difficulties to fin...