Tuesday, July 11, 2017

RC Joystick with FrSky DHT Module and USB Host



I've wanted to try controlling my Rover and Quadcopter with a Joystick, at first I thought I can use the simple potentiometer based joysticks like these.




Attach them to ADC and transmit the values over to the Controller.


Then I've decided its a good opportunity to try one of the USB Host I had



The USB Host module is based on the Maxim MAX3421E (Datasheet), which is a USB Host controller to SPI, accessible to Arduino programming. Oleg Mazurov wrote a very nice library for it and he has some very nice articles on how to use it, as well as explanations on how to patch the module to get 5 volts supplied to the USB device, which I needed for this project.




You will need to cut the wire where the red circle is and attach that wire to the pin marked in green. Otherwise the USB device will get 3.3v and its not enough for most devices.


Another thing to point out is that these modules work on 3.3v and need 3.3v signal, so a level converter might be needed if you're using 5v Arduino, if you're planning to risk it, then from my experience its not going to burn it, but you'll get an unreliable connection.



I've then attached it to a real joystick to see how if its working




As I wrote, Oleg did a very good job and I got a demo up and running in no time.


I've then thought it might be a good idea to send this data directly to a standard RC receiver, I remember I've found FrSky DHT transmitter (Manual) while looking up FrSky protocol.





The FrSky DHT is a DIY module which uses PPM as input and transmit the values to a standard FrSky receiver, it also has a serial port for getting telemetry back but I didn't use it at this stage.


Many people use it to modify their transmitters to FrSky protocol and you can find many guides on how to convert your own transmitter.


I've also ordered RX-F802 but as some people pointed out in the forums, it died quickly.




I've had another receiver which worked perfectly so I didn't get overly excited about it.


So now we have all the hardware we need and the software for reading the Joystick values, we're almost ready.


I've wanted an easy way to see the values as its being decoded, but keeping the serial port connected all the time is not always useful. 


I've used LCD1602, but it has a parallel interface and I didn't want to connect so many pins between it and the Arduino.




To that end there's I2C to Parallel interface module based on PCF8574T (Datasheet), Frank de Brabander wrote a library for controlling LCD1602 with it, but it can be used for other purposes as well.


The interface is pretty simple, you instantiate it with the I2C address and the number of columns and rows and it does the rest.


You can switch the backlight on and off with

- backlight()
- noBacklight()

You can update the same location by:

- setCursor(uint8_t, uint8_t);
- print(value);

And you can even set 8 custom characters!


Pretty useful for such a low cost device!


So now we have all the components for our USB Joystick to PPM, I've hooked it up, added a switch and printed a box and then went ahead to test it.






I've set it up so each axis is displayed as -99 to 99 for 1000-2000μs and M0-M6 as controller mode matching ArduCopter flight mode values. The rest is not working yet, but the plan is to control channels 7-8 with the 'hat' on the joystick.


But having the numeric values in Arduino is not enough, we need to transfer these values to the FrSky transmitter, this is done with PPM, the basics are, there are 8 pulses, each one variable by width (or time), each one represents a channel and then there is a pause and it starts all over again, this is done about 30-50 times per second, so this thing is pretty fast, Joshua Bardwell have some numbers for you.


I've researched a few libraries but none of them were accurate or consistent until I've found ArduinoRCLib, I've used this library for other things and its very consistent. 


You can find my source code here:

https://github.com/drorgl/USBJoystickPPM


Now that we have a working transmitter, we need to check the receiver and see the values actually match.



I've created a simple PPM Display, it listens for PPM on pin 3 and using LCD to display the 8 channels received.




The design is pretty basic, ATMEGA168, 3310 LCD, based on PCD8544 (Datasheet).


Note that the LCD VCC must not exceed 3.3v, so you have to regulate the voltage, I've used A1117 3.3v for that, but its inputs are 5v tolerant (at least from experience) so no need for a logic converter like the USB Host module.

You can find the source code here:
https://github.com/drorgl/PPMDisplay

I've hooked it up to a Mini FrSky Receiver and here are the results:



What happens is, 
- First, monitor/receiver is turned on without a transmitter, you can see the values are in the middle (~1500)
- Transmitter is turned on, values are what the transmitter sends, note channel 6 is ~950, which means a flight mode wasn't set yet (M0).
- Roll is tested
- Pitch is tested
- Yaw is tested
- Throttle is tested
- Flight modes are tested
- USB Disconnected Failsafe is tested
- Transmitter power off failsafe is tested

I didn't have the guts to test it on a quad yet, I'll continue testing it on my new rover until I have enough confidence its working properly :-)

Todo:
- Joystick 'Hat' to channels 7-8
- FrSky Telemetry Monitoring, a source code for the telemetry protocol, RSSI Configuration and hookup:
Source https://www.rcgroups.com/forums/showpost.php?p=26378136&postcount=465

Saturday, July 8, 2017

C Closures

I've started writing this article about C Closures while doing research for a project that needed them, eventually I've failed to provide a working code on Visual Studio and decided to keep the article as a lesson learned rather than a how to.


Callbacks are great language construct, no matter which language, but adding data to each callback is sometimes necessary. While in C++ you can provide a std::function callback which can include user data and even use lambda with captured variables, in C its a bit different.

#include <functional>

int test_function(int n1)
{
 printf("value %d", n1);
 return n1;
}

int main()
{
 auto f1 = std::bind(&test_function, 42 );
 f1();

 int n1 = 42;

 auto f2 = [=]() {return n1; };

 f2();

    return 0;
}


Since you can't create a function at runtime, Many C APIs provide a way to include data pointer and this pointer is passed to the callback.

typedef void (fn)(int value, void* data);
int function(fn* f, void* v) {
 f(1, v);
 return 0;
}

APIs that do not provide userdata void * pointer are a bit trickier to call with user data. To overcome this problem, the developer can use something called Closures. C closures are not part of the language but are still possible. Two of the common libraries that provide this functionality are libffcall and libffi, both of these libraries generate a function on the fly and provide the new function's address.

To actually generate these functions, these libraries needs to know the CPU architecture and compiler used because they need to implement a compatible call.

Lets start with libffcall, to compile it you can start by cloning https://github.com/libffcall/libffcall
You can find the documentation here: https://www.gnu.org/software/libffcall/

If you're compiling for linux, you should read the readme file, if you're compiling for windows, you should read readme.win32.

For some reason, the compilation failed on my machine and I've had to add _WIN32 and _WIN64 to the #ifdef __i386__ and #ifdef __x86_64__ like so:

#if defined(__i386__ ) || defined(_WIN32)
#define TRAMP_LENGTH 15
#define TRAMP_ALIGN 16  /* 4 for a i386, 16 for a i486 */
#endif

and

#if defined(__x86_64__) || defined(_WIN64)
#define TRAMP_LENGTH 32
#define TRAMP_ALIGN 16
#endif

In the end, I couldn't get the project working on Visual Studio, I've then proceeded to try libffi with Visual Studio as well and after fixing and workarounding more than a dozen errors I gave up.

I have no doubt that these two projects work in more than one environment, but perhaps because its not very simple to build and use might point to a weak link, it works by creating assembly code that encapsulates the userdata and the function pointer, completely ignoring the compiler (though it should use the same calling conventions though a provided generator). On top of that, because its not using the compiler directly, its cross-platform-ness is not as robust across compilers and CPU architectures as portable C should be. To strengthen my point, libffi for example, supports only 64bit visual c++ builds according to the build scripts.

I'm not sure if its my own fault for not being able to compile these libraries successfully on Windows/Visual Studio but in any case I see it as an important lesson about C API Design, no matter how ridiculous it might look at first, if you're expecting a callback, a void * user data should be provided as well.

I'm including my build batch for libffi/Windows, if anyone is successful using any of these libraries, share your knowledge, if you find a different method to implement closures in a cross-platform way, even better.

set CYG_ROOT=%CD%/cygwin
set CYG_CACHE=%CD%/cygwin/var/cache/setup
set CYG_MIRROR=http://mirrors.kernel.org/sourceware/cygwin/

rem libffi is not supported on x64/visual c++
rem set VCVARS_PLATFORM=x86
rem set BUILD=x86-pc-cygwin
rem set HOST=x86-pc-winnt

set VCVARS_PLATFORM=amd64
set BUILD=x86_64-pc-cygwin
set HOST=x86_64-pc-winnt

curl -O http://cygwin.com/setup-x86.exe

setup-x86.exe -qnNdO -R "%CYG_ROOT%" -s "%CYG_MIRROR%" -l "%CYG_CACHE%" -P dejagnu
setup-x86.exe -qnNdO -R "%CYG_ROOT%" -s "%CYG_MIRROR%" -l "%CYG_CACHE%" -P Devel,autoconf,automake,make,libtool

%CYG_ROOT%/bin/bash -lc "cygcheck -dc cygwin"

rem %comspec% /k ""C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"" x86
%comspec% /k ""C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"" amd64

%CYG_ROOT%\bin\sh -lc "(cd $OLDPWD; ./autogen.sh;)"
%CYG_ROOT%\bin\sh -lc "(cd $OLDPWD; ./configure CC=''$PWD'/msvcc.sh' CXX=''$PWD'/msvcc.sh' CXXCPP=''$PWD'/msvcc.sh' LD=link CPP='cl -nologo -EP' --build=$BUILD --host=$HOST; cp src/x86/ffitarget.h include; make;)"

rem %CYG_ROOT%\bin\sh -lc "(cd $OLDPWD; ./configure CC='./msvcc.sh -m64' CXX='./msvcc.sh -m64' LD=link CPP='cl -nologo -EP' --build=$BUILD --host=$HOST; cp src/x86/ffitarget.h include; make;)"

Tuesday, June 27, 2017

RTK Rover and Base Configuration Web Interface

So I've been working on this for the past few weeks




When I first started to mess around with RTKLIB I've wanted to have a nice UI to do that, while researching it I came across a product from drotek.com which already has a nice UI and I was glad to find its an open source (!!).




So I decided to sit down and take a look at it, at first I was a little dissapointed to see it worked on Intel Edison Only as I didn't find this board particularly interesting and now after reading that Intel discontinued it I think its even more important to find generic SBCs.

So I started to dig in the source code and I was glad to find out its written in Node Js as Node was one of my primary development environments for the past couple of years. I went over the source code and it seemed simple enough to work on, so I npm install 'ed it and went to work.

First, I've converted the whole project to typescript and cleaned up some Edison specific code, then I went ahead and ported rtkrcv to windows but since windows doesn't have a tty console, I've decided to disable that functionality and worked on making the tcp console work. I also wanted this project to work on M8N so I've had to upgrade rtklib to 2.4.3 as well., I've found a bug or two and went ahead to create an web interface for the services.

I've added loggers, PM2 and split the str2str and rtkrcv to their own hosted services so I've had better control over the service lifecycle and the ability to monitor it in realtime in any way possible, so monitoring console output for errors, monitoring logs for messages to be exposed as events and monitor rtkrcv monitor port and control port to get even more useful information, such as streams statistics, satellites, observations, navigation data and ofcurse the latest position and SNR and exposed all that information as APIs.

In the UI, I've converted the project to typescript which helped me to clean up a few issues, add a toastr for better error reporting, add socket.io to get realtime notifications and implemented a periodic update of all the statistics I wanted as well as add the ability for the station to work as both base and rover, which in essence means it can start str2str and rtkrcv and have complete control over the inputs and outputs. I've even added a com port detection API for the UI.








I've updated the chart.js version to get better animation for the satellite SNR graphs and added a finer resolution in the Map section.







For the rtkrcv and str2str I've added a gyp build configuration so it can be compiled in almost any environment, so far I've tested it on Windows, Linux x64 and Linux Arm and seems to be working without a hitch even on Orange PI Zero.

Now, I'll probably start testing it in a small scale, originally I wanted it for my Rover project, but this project was a learning experience as well as fun.

I'm looking for someone else to take over this project, I see great potential for it to be used across the navigation hobbyist community.

https://github.com/drorgl/SMARTNAV-RTK

Wednesday, June 21, 2017

Emulating COM Ports on Windows

My computer is not near a window, but I do need to get data from my GPS to RTKLIB hosting program I'm rebuilding.

So after trying a long wire, a wireless relay and even going outside, I've decided to try a COM port emulation solution.

After looking around I've found an old unsigned driver of com0com, but since its not signed, its a hassle to install and I don't really want to open up my computer to these kinds of security risks.

After looking around I've found a signed version at  https://code.google.com/archive/p/powersdr-iq/downloads

Once I've installed the driver, I have access to com0com setup

Basically, you'll need to mark the class checkbox and it will become just like a regular com port.



After that, you should download the com port data generator from aggsoft


Although you can generate random data, its best to use real data recorded previously, if you're interested in RTK GPS data, you can download the raw bytes from rtkexplorer.


Once you start playback, you can attach the appropriate RTKLIB application to the 2nd com port and see how its doing



Saturday, June 17, 2017

Powering Orange PI Zero with a Power Bank

You might ask yourself why wouldn't it work?
I asked myself the same question when I saw the tiny thing won't turn on, or worse, turn on and freeze all the time.

The answer lies in the way power banks work and some of the power converters, as well as usb power supplies.

In the good old days when you wanted to take a power source like 220v or 110v and down convert it to 5v, you would use a transformer, then a rectifier and lastly filter out the noise with a capacitor and optionally an inductor. This works great and if used on audio applications the noise is inaudible or very close to it (at least not from the power source) but the biggest drawback of this method is weight, high power transformers weight a lot.

These days there is a need to make things smaller and weight less and so we have all sorts of switching power supplies and as their name implies, they switch the power on and off to achieve the same goal.

In general there are 3 types of switching power supplies:
- step-up, also referred to as boost converters where the input voltage is lower than the output.
- step-down, also referred to as buck converters where the input voltage is higher than the output.
- buck-boost converters where the input voltage can either be lower or higher.

In my particular case it is step-up converter from a Lithium Ion battery so 3.7v - 4.2v up to 5v




But lets look how bad it is


If you look at the ripple size, it pretty much covers the whole voltage output range, a very fast ripple but still.

Looking at the Orange PI Zero, there seem to be another relatively large inductor next to the USB connector where power comes in:



Which led me to believe another type of switching is going on the PI and common sense dictactates that two switching power supplies without some type of smoothing or filtering is a bad idea.

So I decided to experiment with a small 220uf capacitor and solder it straight on the power bank




And voilà !



124mv !


But the best part is, its actually working and stable.

Friday, June 16, 2017

OpenSprinkler using ESP8266

"If necessity is the mother of invention, then laziness is the father." - attributed to Allen Dale

Sometime ago I've read about OpenSprinkler (source code) and I had to try it, I was a bit dissapointed when first I saw it worked on Raspberry PI Zero, but then I saw other boards it might work on, until I've found a port to ESP8266 and decided to give it a go, but I should also note there's an official ESP8266 OpenSprinkler with Source Code.

The design I chose to start with is pretty primitive, I didn't want any LCD or RTC, ESP8266 can get the time though NTP so I didn't see much use to it, for the solenoid drive I chose L293D, which should be more than enough for home valves with 600ma output (and 1.2A for peak current).

Throw in some terminals, a few leds, a pcb and a voltage regulator and you have a working prototype.

Which ESP8266 you ask? there are so many, if you need less than 8 stations, you can use the cheaper ones for about $2.

Just remember to modify the pins used for each station, put a mosfet or a driver between the MCU, the pins are configurable through Pins.h under PIN_STN_S01 - 16 (on board with more pins, the version I have, has only 8).

When I first tried to build OpenSprinkler I've encountered a few issues, first, I had a very old NewLiquidCrystal library, which doesn't work on ESP8266, trying do download a new version through downloads doesn't solve the problem because its not there, you'll need to download either through Download Repository or through Mercuial clone.

After fixing that problem, I've started to configure the build to my needs, I've fixed the OPENSPRINKLER_ARDUINO_FREEMEM flag so it will report how much memory is available, I've reduced the number of stations to 4, changed the location and remove the default NTP IP since it wasn't working.

To save some water its recommended you get your own weather key from weather underground. the developer key is sufficient for getting your prototype going but you will most definitely need a commercial key if you're planning to sell it.

I've also found a few problems with WifiManager, it seems the development was not complete, so I've added the debug messages, a reset command, renamed the AP name and finally add ESP.eraseConfig and dump ESP info into the debug log.

Another modification I made was to remove all the unneeded buttons since it was mostly needed for the LCD, I did add one button so when the board starts with the button pressed it will reset all setting to defaults.





A few things about this build:

- Voltage regulator - I didn't have a 3.3v voltage regulator that could work on 12v, I've had to get it down to 5v (with 7805) and then again to 3.3v (with A1117).

- ESP8266 does have a breakout board, I didn't have one available so I decided to solder it straight on some pins.


Now for the fun part, testing it :-)

Source - https://github.com/drorgl/OpenESP8266_Sprinkler/tree/ModAfterUpload/OpenSprinkler_Arduino_V_2_1_6

Wednesday, May 31, 2017

Getting Started with Orange PI Zero

Orange PI Zero is a small SBC (single board computer) with 256/512MB memory, its similar to Raspberry PI Zero with some minor differences:

Raspberry PI Zero
Orange PI Zero


BCM2835 1GHz, Single-core CPU
H2+ Quad-core Cortex-A7 1.2GHz
512MB RAM
256MB/512MB
None
Ethernet 10/100 (PoE) + WiFi
Mini-HDMI port
None
Micro-USB OTG port
1 micro-USB and 1 USB 2.0
Micro-USB power

HAT-compatible 40-pin header
26 Pins Header, compatible with Raspberry Pi B+
13 Pins Header, with 2x USB, IR pin, AUDIO(MIC, AV)
Composite video and reset headers
Via 13 pins header
Optional CSI camera connector (v1.3 only) (w $20/ wo $5)
None
Realistic Price: ~$30 
Limited to 1 per order but not on AliExpress
Realistic Price ~$15 (256/512)



First you’ll need to get an image to flash on your SD card, I’ve used Ubuntu server from armbian.

Then I've used 7zip to decompress the image and write it on SD card with Win32DiskImager.

- Configure Ethernet NIC as static IP 192.168.0.1
- You can use DualServer to start up a DHCP server on the Ethernet port, you can either install the server from sourceforce or download just the executable from here, you can start a temporary instance by running RunStandAlone.bat.

- Insert the SD Card, connect the Orange PI Zero to power and connect the network and wait for about a minute, you'll see the DHCP request in the console:
DHCPREQUEST for ee:77:55:88:44:dd (orangepizero) from interface 192.168.0.1 received
Host ee:77:55:88:44:dd (orangepizero) allotted 192.168.0.2 for 36000 seconds

Alternatively if you power up the Orange PI Zero with a PC you get a COM port, fire up putty, and set to 9600 and you're ready to go!

use root and 1234 as default login credentials and follow up the instructions to change the password and create a default account.

when that is done, you can configure the wifi with nmcli:

- list wifi networks:
nmcli dev wifi list
- create a new connection:
nmcli con add con-name SSIDName ifname wlan0 type wifi ssid SSIDName
- set password:
nmcli con modify SSIDName wifi-sec.key-mgmt wpa-psk
nmcli con modify SSIDName wifi-sec.psk PASSWORD

- set up avahi to get mDNS, its easier than going into the DHCP/Router each time:
sudo apt-get install avahi-daemon
sudo apt-get install avahi-utils

sudo nano /etc/avahi/avahi-daemon.conf
publish-addresses=yes
publish-domain=yes

- to set up hostname resolution - install nss mdns resolution, another unexplored option is to use libnss-resolve, purge/install):
sudo apt-get install libnss-mdns

- edit nsswitch.conf file:
sudo nano /etc/nsswitch.conf

- modify hosts to:
hosts:          files dns myhostname mdns4_minimal mdns4

you should be able to ping the mdns record name now:
ping <hostname>.local

To enable mdns on windows, you can download Bonjour from Apple.

And last but not least, mDNS uses Multicast, which needs router support but its usually disabled, You should find a way to enable it, I can't really guide you on your particular router, in my Netgear it was something else, unchecking "Disable IGMP Proxying" fixed it.

You can monitor your device temperature with:
sudo armbianmonitor -m



References:
Luc Small did a more thorough beginners guide.