Close

March 1, 2016

A project log for AquaPic - Aquarium Controller

Reef tank controller used to monitor various parameters such as temp and pH, and also control equipment such as pump and lights.

skyler-brandtSkyler Brandt 03/01/2016 at 23:110 Comments

If I’d known what was all involved with 9bit communication, I probably would have chosen a different option. However, despite all the bugs, issues, and lack of support, I finally have a fairly reliable communication protocol. After my last post I realized that I could test my hypothesis that it was the USB hub on the RPi. I could simply unplugged the WiFi dongle, and see if the problem persistent. As expected, there was no improvement. The USB hub wasn’t my issue. I don’t remember exactly what all I did after that but there was a lot of beating my head against my desk. At some point I realized I could reproduce communication faults on my Windows PC when I was using the parity hack, counting the bits in each byte and setting either even or odd parity to get the desired mark or space parity. I also found that the power strip slave was hanging, and restarting it would get it working for a while. I concluded that both the parity hack and the slave code were causing issues and set out to address both.

I expected that fixing the hanging slave module would be an easy fix, and for the most part it was except for one small detail I overlooked. To address the issue I added a simple check of the 9th bit as part of the received byte handler to double check that it wasn’t an address byte. However, I introduced one seemly small bug when implementing that. When I first made the change I was reading the 9th bit after I got the received byte off the microcontroller’s internal FIFO buffer. This is a big oversight as the microcontroller updates the status word containing the state of the 9th bit as the FIFO buffer is read, so when I checked the 9th bit it was actually for the next byte. That took me a while to find. Also, since I’m checking every byte now for the 9th bit, I removed the auto-address detection.

Fixing the parity hack was a chore. I decided that I needed to get mark and space parity to work in the Mono framework. The reason mark and space parity aren’t currently implemented in the Mono framework is because it requires the CMSPAR flag to be defined, which it isn’t with the POSIX source. Since I didn’t want to recompile the MonoPosixHelper library, I set out writing my own helper library that could be used to set the parity. It was actually pretty easy to write, pretty much copy and paste. I used a lot of Mono’s existing C code and a blog post by Thomas Lochmatter to modified a few lines to include the CMSPAR, mark/space parity, flag. Code after break.

I built the parity library and using Mono’s documentation for PInvoking native libraries, tried to get it working on my Linux machine, but was only met with DllNotFoundExpection. I had pulled a rooky mistake and compiled the source as an object instead of a shared library. I also didn’t realize that you have to include a lib prefix to the library file name in order for the Mono framework to find that it. Running the application with the log level set to debug help find both these issues pretty quick.

$ MONO_LOG_LEVEL="debug" mono AquaPic.exe
I also find out some other useful information. I had gone through all the steps that the docs lists to add a shared library so that dlopen() can find, but that isn’t required. The first place that the Mono framework searches is the application launch directory. Simply add the library in the same directory as the application .exe. After I fixed the lib prefix and built the source as a shared library, Mono was able to use my parity library, but it still didn’t work

At this point I was completely lost and went back to my Windows machine and using the parity hack. I wanted to try to get the parity hack to work, since it still wasn’t working under either Linux or Windows. Backing up a little bit, when I was first looking though the serial C code for Mono, I noticed that the termios flags are set immediately instead of waiting for the write buffer to be empty. Based on that code, I made the assumption that Windows closed magic probably also set the parity immediately, so I added a 10ms delay in the thread. That small delay fixed the hack parity. I don’t know why I didn’t think about that early, since it was such a no brainer easy fix. I think this was also the point in the story when I fixed the 9th bit bug in the slave module mention early. With Windows reliable working, I went back to Linux to see if I could get something work there, but still nothing worked. Not even the parity hack which only used the more common odd and even parity.

RS485 is an old technology and apparently everyone just agrees that it’s dead because there’s very little in the way of anything on the internet. I was Googling anything and everything that I thought might lead me down the path to figuring out what was the issue. On a whim I did a search about the ch341 Linux driver and parity and sure enough that wild stab in the dark was my issue. Linux includes the driver for the ch341 transceiver chip, but the driver doesn’t support parity. Luckily, Karl Palsson had already had this issue and provides a patch to add parity support. Patching the Linux source was a learning experience, but once I had the driver patched, everything worked. I was finally able to talk to the slave modules with the Mono framework on Linux.

I’m currently in the middle of a move so I haven’t had the chance to test any of this on the RPi. I’m fairly positive it will work though, and even if my library doesn’t work, the parity hack reliable works now. I can’t wait for the next big issue to rear its ugly head.

---------- more ---------

/******************************************************************
 * Helper library to set Serial port parity in the Mono framework 
 * on Linux.
 *
 * Largely based upon Mono framework's serial.c and the 
 *   set_attributes() function: 
 *   https://github.com/mono/mono/blob/master/support/serial.c 
 *   and Thomas  Lochmatter's blog post about mark and space parity 
 *   on Linux:  https://viereck.ch/linux-mark-space-parity/
 * 
 * Build command:
 *   gcc -shared -rdynamic -fPIC AquaPicParityHelper.c -o libAquaPicParityHelper.so
 * Also the lib prefix in the output file name seems to be required 
 *   for the Mono framework to find the library.
 * 
 * CMSPAR is define locally because I figured that would lead to  
 *   less issues. Another option is to define _BSD_SOURCE or 
 *   _SVID_SOURCE before including the termios and unistd headers.
 *****************************************************************/

#include <termios.h>
#include <unistd.h>

#define CMSPAR 010000000000	/* mark or space (stick) parity */

/* This is a copy of System.IO.Ports.Parity */
typedef enum {
    NoneParity = 0,
    Odd = 1,
    Even = 2,
    Mark = 3,
    Space = 4
} LinuxParity_t;

int SetLinuxParity (int fd, LinuxParity_t parity);

int SetLinuxParity (int fd, LinuxParity_t parity) {
    struct termios newtio;
    
    if (tcgetattr (fd, &newtio) == -1){
        return 0; /* false */
    }
    
    switch (parity) {
    case NoneParity: /* None */
        newtio.c_cflag &= ~(PARENB | PARODD | CMSPAR);
        break;
    case Odd: /* Odd */
        newtio.c_cflag |= PARENB | PARODD;
        newtio.c_cflag &= ~CMSPAR;
        break;
    case Even: /* Even */
        newtio.c_cflag |= PARENB;
        newtio.c_cflag &= ~(CMSPAR | PARODD);
        break;
    case Mark: /* Mark */
        newtio.c_cflag |= PARENB | PARODD | CMSPAR;
        break;
    case Space: /* Space */
        newtio.c_cflag |= PARENB | CMSPAR;
        newtio.c_cflag &= ~PARODD;
        break;
    }

    if (tcsetattr (fd, TCSANOW, &newtio) < 0) {
        return 0; /* false */
    }

    return -1; /* true */
}

Discussions