In my efforts to smartify my house, one of the first things I did was to install an energy meter and connect it to the internet. I connected a TP-link TL-MR3020 to a Modbus capable energy meter to do this.
Here are my efforts this far:
The next involved writing and compiling some native code to interface with the energy meter using Modbus. The Selec MFM383C energy is connected to TL-MR3020 over UART. My TL-MR3020 has a USB hub connected to it. One of the slots has a 8GB USB Flash drive in it with the rootfs on it. Another slot has FT232 and MAX485 based USB-UART bridge. I use this USB-UART communicate with my mfm383c using Modbus.
------------
Problem statement: Cross compile C code into an OpenWrt ipkg in case which requires fetching and compilation of dependent libraries as well.
Detailed problem statement:
- Cross compile a piece of software (native, C program)
- The software depends on a library whose repository is maintained online - a tar archive of which can be fetched during make/
- Target: TL-MR3020 running OpenWRT 15.05 Chaos Calmer
- Development machine: Ubuntu x64 15.10
- Output should be an installable ipkg. We should be able to use opkg to install the software as well as its dependency.
- The software and the library it depends on should be complied using a single make command.
- During make, the sources for the library will be fetched from its online repository automatically.
Bit more details about our software:
Its a C program that uses libmodbus to communicate with a modbus capable energy meter (Selec MFM383C) over USB <> UART (FT232RL) to read specified registers depending on the parameters passed to it via command line.
- Preparation Step 1 - Setting up Ubuntu 15.10 x64 Virtual Machine on your Windows PC
- Download Ubuntu 15.10 x64 virtual machine image from osboxes.org
- Import it into your VirtualBox
- Make sure to share a couple of folder between Ubuntu and Windows. Also share the clipboards. (click here to know how todo this http://www.electronicsfaq.com/2013/04/sharing-folders-between-ubuntu-12041.html)
- Install git and build tools on your VM
sudo apt-get install autoconf build-essential
- Preparation Step 2 - Install toolchain, prepare router, install Chaos Calmer on Router.Click here to kno how to perform all of the following steps: http://www.electronicsfaq.com/2015/12/openwrt-1505-chaos-calmer-on-tl-mr3020.html)
- Install the toolchain
- Open your router and solder a USB - > serial bridge
- Mount an external USB flash drive
- Finally compile and install chaos calmer on it
- Now we need to figure out the folders in which we need to download (git clone) the library (library) and where to place the source files for our own application. We also need to know what should go into the makefiles and where to place them. So here are the details:
- You need to place your source and Makefiles in subfolders created in:
~/OpenWrt-SDK-15.05-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64/package - There will already be a Makefile (last modified 25 July 2015) in there. No need to touch it.
- Create a subfolder in the packages folder for your software (I called mine "mfm383c") and place your .c source file and Makefile in sub-sub-folder named "src" in it.
- Create another subfolder in the packages folder for the library that is going to be downloaded and place your Makefile in it. No need to download any .c file. Instructions for cloning the git are already present in the Makefile. I named this folder "libmodbus"
Folder tree of the makefiles and sources
- You need to place your source and Makefiles in subfolders created in:
- The contents of these C files and Makefile are appended below. The relevant section that take care of downloading and ensuring dependencies are highlighted.
- Lets compile the stuff
- Lets export the variables
export PATH=~/OpenWrt-SDK-15.05-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64/staging_dir/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2/bin:$PATH
export STAGING_DIR=~/OpenWrt-SDK-15.05-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64/staging_dir - cd to ~/OpenWrt-SDK-15.05-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64
- And issue the make command
- The .ipk files will be placed in:~/OpenWrt-SDK-15.05-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64/bin/ar71xx/packages/base
- Install the packages on the router:
- Copy the two .ipk files from Linux VM to Windows and then transfer them to /root of your router using WinSCP
- Then SSH into your router using PuTTY and install the packages:
- opkg install libmodbus_3.0.6-1_ar71xx.ipk
- opkg install mfm383c_1.0.0-1_ar71xx.ipk
- And Done!
Location of the output .ipk files |
Sources - Lets look at the 3 Makefiles first
These Makefiles are based on the ones supplied by OpenWrt on their wiki:
https://wiki.openwrt.org/doc/devel/packages
https://wiki.openwrt.org/doc/devel/packages
/package/libmodbus/Makefile
As you can note, there isn't much here. We are just telling the build system from where to fetch the tar ball of the sources from the internet, extract them and compile them. In this Makefile we are also specifying where to place the .so files (shared library files - they are like the .dlls on Windows) within the root filesystem (/usr/lib) of the router when the .ipk is installed on it. There are two .ipk files that are going to be generated - one for libmodbus and the other for our program that we have written (mfm383c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | # Copyright (C) 2006-2015 OpenWrt.org # # This is free software, licensed under the GNU General Public License v2. # See /LICENSE for more information. # include $(TOPDIR)/rules.mk PKG_NAME:=libmodbus PKG_VERSION:=3.0.6 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=http://libmodbus.org/releases/ PKG_MD5SUM:=c80f88b6ca19cabc4ceffc195ca07771 PKG_MAINTAINER:=Michael Heimpold <mhei@heimpold.de> PKG_LICENSE:=GPL-3.0+ LGPL-2.1+ PKG_LICENSE_FILES:=COPYING COPYING.LESSER PKG_FIXUP:=autoreconf PKG_INSTALL:=1 include $(INCLUDE_DIR)/package.mk define Package/libmodbus SECTION:=libs CATEGORY:=Libraries URL:=http://www.libmodbus.org TITLE:=libmodbus endef define Package/libmodbus/description A Modbus library for Linux, Mac OS X, FreeBSD, QNX and Win32. endef CONFIGURE_ARGS += --without-documentation TARGET_CFLAGS += $(FPIC) define Build/InstallDev $(INSTALL_DIR) $(1)/usr/include $(CP) $(PKG_INSTALL_DIR)/usr/include/modbus $(1)/usr/include/ $(INSTALL_DIR) $(1)/usr/lib $(CP) $(PKG_INSTALL_DIR)/usr/lib/libmodbus.{so*,la} $(1)/usr/lib/ $(INSTALL_DIR) $(1)/usr/lib/pkgconfig $(CP) $(PKG_INSTALL_DIR)/usr/lib/pkgconfig/libmodbus.pc $(1)/usr/lib/pkgconfig/ endef define Package/libmodbus/install $(INSTALL_DIR) $(1)/usr/lib $(CP) $(PKG_INSTALL_DIR)/usr/lib/libmodbus.so* $(1)/usr/lib/ endef $(eval $(call BuildPackage,libmodbus)) |
/package/mfm383c/src/Makefile
This makefile specifies how to build the mfm383c.c and mfm383c.h files contained in the src folder.Notice the LIBS variable specifying that these source have a dependencies on libmodbus.
1 2 3 4 5 6 7 8 9 10 11 | # build mfm383c executable when user executes "make" LIBS=-lmodbus mfm383c: mfm383c.o $(CC) $(LDFLAGS) $(LIBS) mfm383c.o -o mfm383c mfm383c.o: mfm383c.c $(CC) -c $(CFLAGS) mfm383c.c # remove object files and executable when user executes "make clean" clean: rm *.o mfm383c |
/package/mfm383c/Makefile
This Makefile is the most important of the three - it tells the build system to build the libmodbus library before building mfm383c to ensure that the dependencies are met. We also need to make sure that the modbus.h file included in mfm383c.c can be found in the right place.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | ############################################## # OpenWrt Makefile for mfm383c program # # # Most of the variables used here are defined in # the include directives below. We just need to # specify a basic description of the package, # where to build our program, where to find # the source files, and where to install the # compiled program on the router. # # Be very careful of spacing in this file. # Indents should be tabs, not spaces, and # there should be no trailing whitespace in # lines that are not commented. # ############################################## include $(TOPDIR)/rules.mk # Name and release number of this package PKG_NAME:=mfm383c PKG_VERSION:=1.0.0 PKG_RELEASE:=1 TARGET_CFLAGS=-I$(STAGING_DIR)/usr/include/modbus TARGET_LDFLAGS=-L$(STAGING_DIR)/usr/include/modbus PKG_BUILD_DEPENDS:=libmodbus # This specifies the directory where we're going to build the program. # The root build directory, $(BUILD_DIR), is by default the build_mipsel # directory in your OpenWrt SDK directory PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) include $(INCLUDE_DIR)/package.mk # Specify package information for this program. # The variables defined here should be self explanatory. define Package/mfm383c SECTION:=utils CATEGORY:=Utilities DEPENDS:=+libmodbus TITLE:=MFM383C -- prints a snarky message endef define Package/mfm383c/description If you can't figure out what this program does, you're probably brain-dead and need immediate medical attention. endef # Specify what needs to be done to prepare for building the package. # In our case, we need to copy the source files to the build directory. # This is NOT the default. The default uses the PKG_SOURCE_URL and the # PKG_SOURCE which is not defined here to download the source from the web. # In order to just build a simple program that we have just written, it is # much easier to do it this way. define Build/Prepare mkdir -p $(PKG_BUILD_DIR) $(CP) ./src/* $(PKG_BUILD_DIR)/ endef define Build/Configure $(call Build/Configure/Default,--with-linux-headers=$(LINUX_DIR)) endef define Build/Compile $(MAKE) -C $(PKG_BUILD_DIR) \ CFLAGS="$(TARGET_CFLAGS)" \ LDFLAGS="$(TARGET_LDFLAGS)" \ $(TARGET_CONFIGURE_OPTS) endef # We do not need to define Build/Configure or Build/Compile directives # The defaults are appropriate for compiling a simple program such as this one # Specify where and how to install the program. Since we only have one file, # the mfm383c executable, install it by copying it to the /bin directory on # the router. The $(1) variable represents the root directory on the router running # OpenWrt. The $(INSTALL_DIR) variable contains a command to prepare the install # directory if it does not already exist. Likewise $(INSTALL_BIN) contains the # command to copy the binary file from its current location (in our case the build # directory) to the install directory. define Package/mfm383c/install $(INSTALL_DIR) $(1)/bin $(INSTALL_BIN) $(PKG_BUILD_DIR)/mfm383c $(1)/bin/ endef # This line executes the necessary commands to compile our program. # The above define directives specify all the information needed, but this # line calls BuildPackage which in turn actually uses this information to # build a package. $(eval $(call BuildPackage,mfm383c,+libmodbus)) |
Sources - .c and .h
Finally the sources for mfm383c.c and mfm383c.h placed in\package\mfm383c\src:
mfm383c.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | #include <stdio.h> //Defines core input and output functions: printf(), scanf(), getchar(), puchar(), gets(), puts() etc. #include <unistd.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <ctype.h> //Required for isprint() #include <modbus.h> #include "mfm383c.h" #define MFM383C_CONFIG_BAUDRATE_DEFAULT 9600 //Default #define MFM383C_CONFIG_DATA_BITS 8 #define MFM383C_CONFIG_PARITY_BITS 'N' #define MFM383C_CONFIG_STOP_BITS 1 #define MFM383C_CONFIG_PORT "/dev/ttyUSB0" #define MFM383C_CONFIG_SLAVE_ID 1 #define MFM383C_CONFIG_RESPONSE_TIMEOUT_S 1 //Default is 0 #define MFM383C_CONFIG_RESPONSE_TIMEOUT_US 500000 //Default is 500000 #define MFM383C_CONFIG_BYTE_TIMEOUT_S 1 //Default is 0 #define MFM383C_CONFIG_BYTE_TIMEOUT_US 500000 //Default is 500000 #define RETURN_SUCCESS 0 #define RETURN_FAIL_PARAM 1 #define RETURN_FAIL_MODBUS_LIB 2 #define RETURN_FAIL_MODBUS_SERIAL 3 struct timeval response_timeout = {MFM383C_CONFIG_RESPONSE_TIMEOUT_S,MFM383C_CONFIG_RESPONSE_TIMEOUT_US}; struct timeval byte_timeout = {MFM383C_CONFIG_BYTE_TIMEOUT_S,MFM383C_CONFIG_BYTE_TIMEOUT_US}; modbus_t *ctx; int main(int argc, char **argv) { uint16_t register_set[2]={0}; int reg_addr = 0; char *register_name = NULL; int passed_flag; int baud_rate = MFM383C_CONFIG_BAUDRATE_DEFAULT; int debug_flag = 0; while ((passed_flag = getopt(argc, argv, "db:r:")) != -1) { switch (passed_flag) { case 'd': { debug_flag = 1; } break; case 'b': { baud_rate = atol(optarg); switch(baud_rate) { case 300 : case 600 : case 1200 : case 2400 : case 4800 : case 9600 : case 19200 : //These baud rates are supported by Selec MFM383C break; default: fprintf(stderr, "Unsupported or invalid baud rate value passed: %d\n", baud_rate); fprintf(stderr, "Using default value for baud rate: %d bps\n", MFM383C_CONFIG_BAUDRATE_DEFAULT); baud_rate = MFM383C_CONFIG_BAUDRATE_DEFAULT; break; }; } break; case 'r': { register_name = optarg; } break; case '?': { fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt); return RETURN_FAIL_PARAM; } break; default: { fprintf(stderr, "Invalid command line argument.\n"); return RETURN_FAIL_PARAM; } break; }; } if(register_name == NULL) { fprintf(stderr, "Option -r requires an argument.\n"); return RETURN_FAIL_PARAM; } ctx = modbus_new_rtu(MFM383C_CONFIG_PORT, baud_rate, MFM383C_CONFIG_PARITY_BITS, MFM383C_CONFIG_DATA_BITS, MFM383C_CONFIG_STOP_BITS);// Creates new Modbus RTU connection if (ctx == NULL) { fprintf(stderr, "Unable to create the libmodbus context\n"); return RETURN_FAIL_MODBUS_SERIAL; } if(debug_flag == 1) { modbus_set_debug(ctx, TRUE); } if (modbus_connect(ctx) == -1) { fprintf(stderr, "Modbus serial connection failed: %s\n", modbus_strerror(errno)); modbus_free(ctx); return RETURN_FAIL_MODBUS_LIB; } else { modbus_set_byte_timeout(ctx, &response_timeout); modbus_set_response_timeout(ctx, &byte_timeout); //modbus_get_byte_timeout(ctx, &byte_timeout); //modbus_get_response_timeout(ctx, &response_timeout); //printf("\nByte Timeout: %d seconds %d microseconds",byte_timeout.tv_sec, byte_timeout.tv_usec); //printf("\nResponse Timeout: %d seconds %d microseconds\n",response_timeout.tv_sec, response_timeout.tv_usec); if(modbus_set_slave(ctx, MFM383C_CONFIG_SLAVE_ID)==-1){ fprintf(stderr, "Modbus modbus_set_slave() failed: %s\n", modbus_strerror(errno)); modbus_free(ctx); return RETURN_FAIL_MODBUS_LIB; } else { if(strcmp(register_name,"V1N") == 0) { reg_addr = MFM383C_REGR_V1N; } else if(strcmp(register_name,"V2N") == 0) { reg_addr = MFM383C_REGR_V2N; } else if(strcmp(register_name,"V3N") == 0) { reg_addr = MFM383C_REGR_V3N; } else if(strcmp(register_name,"VLN_AVG") == 0) { reg_addr = MFM383C_REGR_VLN_AVG; } else if(strcmp(register_name,"V12") == 0) { reg_addr = MFM383C_REGR_V12; } else if(strcmp(register_name,"V23") == 0) { reg_addr = MFM383C_REGR_V23; } else if(strcmp(register_name,"V31") == 0) { reg_addr = MFM383C_REGR_V31; } else if(strcmp(register_name,"VLL_AVG") == 0) { reg_addr = MFM383C_REGR_VLL_AVG; } else if(strcmp(register_name,"I1") == 0) { reg_addr = MFM383C_REGR_I1; } else if(strcmp(register_name,"I2") == 0) { reg_addr = MFM383C_REGR_I2; } else if(strcmp(register_name,"I3") == 0) { reg_addr = MFM383C_REGR_I3; } else if(strcmp(register_name,"I_AVG") == 0) { reg_addr = MFM383C_REGR_I_AVG; } else if(strcmp(register_name,"KW1") == 0) { reg_addr = MFM383C_REGR_KW1; } else if(strcmp(register_name,"KW2") == 0) { reg_addr = MFM383C_REGR_KW2; } else if(strcmp(register_name,"KW3") == 0) { reg_addr = MFM383C_REGR_KW3; } else if(strcmp(register_name,"KVA1") == 0) { reg_addr = MFM383C_REGR_KVA1; } else if(strcmp(register_name,"KVA2") == 0) { reg_addr = MFM383C_REGR_KVA2; } else if(strcmp(register_name,"KVA3") == 0) { reg_addr = MFM383C_REGR_KVA3; } else if(strcmp(register_name,"PF1") == 0) { reg_addr = MFM383C_REGR_PF1; } else if(strcmp(register_name,"PF2") == 0) { reg_addr = MFM383C_REGR_PF2; } else if(strcmp(register_name,"PF3") == 0) { reg_addr = MFM383C_REGR_PF3; } else if(strcmp(register_name,"PF_AVG") == 0) { reg_addr = MFM383C_REGR_PF_AVG; } else if(strcmp(register_name,"FREQ") == 0) { reg_addr = MFM383C_REGR_FREQ; } else if(strcmp(register_name,"KWH") == 0) { reg_addr = MFM383C_REGR_KWH; } else if(strcmp(register_name,"KVAR1") == 0) { reg_addr = MFM383C_REGR_KVAR1; } else if(strcmp(register_name,"KVAR2") == 0) { reg_addr = MFM383C_REGR_KVAR2; } else if(strcmp(register_name,"KVAR3") == 0) { reg_addr = MFM383C_REGR_KVAR3; } else if(strcmp(register_name,"KW_TOTAL") == 0) { reg_addr = MFM383C_REGR_KW_TOTAL; } else if(strcmp(register_name,"KVA_TOTAL") == 0) { reg_addr = MFM383C_REGR_KVA_TOTAL; } else if(strcmp(register_name,"KVAR_TOTAL") == 0) { reg_addr = MFM383C_REGR_KVAR_TOTAL; } else { fprintf(stderr, "%s is not a valid register name\n",register_name); modbus_free(ctx); return RETURN_FAIL_PARAM; } //Read the specified register if(modbus_read_input_registers(ctx, reg_addr, 2, register_set)==-1){ fprintf(stderr, "Slave Read failed: %s\n", modbus_strerror(errno)); modbus_free(ctx); return RETURN_FAIL_MODBUS_SERIAL; } else { //No byte swapping needed on TL-MR3040 fprintf(stdout,"%f\n", *((float*)®ister_set[0])); modbus_free(ctx); return RETURN_SUCCESS; } modbus_free(ctx); return RETURN_FAIL_MODBUS_SERIAL; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #ifndef _MFM383C_H_ #define _MFM383C_H_ #include <inttypes.h> //Each Modbus Register is 16 bits in length //Writeable Parameters (4xxxxx) //Register Address Offset // Min Value Max Value Length Data Structure #define MFM383C_REGW_CT_PRIMARY (1) // 5 5000 1 Integer #define MFM383C_REGW_RESET_KWH (2) // 0 1 1 Integer //Readable Parameters (3xxxxx) //Register Address Offset // Min Value Max Value Length Data Structure #define MFM383C_REGR_V1N (1) // 0 350.0 2 Float #define MFM383C_REGR_V2N (3) // 0 350.0 2 Float #define MFM383C_REGR_V3N (5) // 0 350.0 2 Float #define MFM383C_REGR_VLN_AVG (7) // 0 350.0 2 Float #define MFM383C_REGR_V12 (9) // 0 607.0 2 Float #define MFM383C_REGR_V23 (11) // 0 607.0 2 Float #define MFM383C_REGR_V31 (13) // 0 607.0 2 Float #define MFM383C_REGR_VLL_AVG (15) // 0 607.0 2 Float #define MFM383C_REGR_I1 (17) // 0 5000.0 2 Float #define MFM383C_REGR_I2 (19) // 0 5000.0 2 Float #define MFM383C_REGR_I3 (21) // 0 5000.0 2 Float #define MFM383C_REGR_I_AVG (23) // 0 5000.0 2 Float #define MFM383C_REGR_KW1 (25) // -1750.00 1750.00 2 Float #define MFM383C_REGR_KW2 (27) // -1750.00 1750.00 2 Float #define MFM383C_REGR_KW3 (29) // -1750.00 1750.00 2 Float #define MFM383C_REGR_KVA1 (31) // 0 1750.00 2 Float #define MFM383C_REGR_KVA2 (33) // 0 1750.00 2 Float #define MFM383C_REGR_KVA3 (35) // 0 1750.00 2 Float #define MFM383C_REGR_PF1 (37) // -0.99 1.00 2 Float #define MFM383C_REGR_PF2 (39) // -0.99 1.00 2 Float #define MFM383C_REGR_PF3 (41) // -0.99 1.00 2 Float #define MFM383C_REGR_PF_AVG (43) // -0.99 1.00 2 Float #define MFM383C_REGR_FREQ (45) // 0 65.0 2 Float #define MFM383C_REGR_KWH (47) // 0 99999999.9 2 Float #define MFM383C_REGR_KVAR1 (49) // -1750.00 1750.00 2 Float #define MFM383C_REGR_KVAR2 (51) // -1750.00 1750.00 2 Float #define MFM383C_REGR_KVAR3 (53) // -1750.00 1750.00 2 Float #define MFM383C_REGR_KW_TOTAL (55) // -5250.00 5250.00 2 Float #define MFM383C_REGR_KVA_TOTAL (57) // 0 5250.00 2 Float #define MFM383C_REGR_KVAR_TOTAL (59) // -5250.00 5250.00 2 Float #define MFM383C_REGR_STATUS (61) // X X 1 Integer //Status Bits #define MFM383C_STATUS_PHASE1_CT_REV 0x0001 #define MFM383C_STATUS_PHASE2_CT_REV 0x0002 #define MFM383C_STATUS_PHASE3_CT_REV 0x0004 #endif //_MFM383C_H_ |
Comments
Post a Comment