Connection of servo drives in OpenWrt

Keywords for this article (for those to whom they are not familiar, the article will probably be not interesting) are: analogue and digital servo-machine, control of servo drives, hexapod, droid, aircraft model, automodel, robot, generally all associated with robotics.
All the previous articles had an introduction character and showed the hidden possibilities of use of alternative functions of a router. Now I suggest to proceed to the practical part..

When I became interested in robotics, one of the constant questions for me was - "how to control the servo drives? ". For this purpose it is necessary to make a special control board on a processor, which I thought should be more powerful, because you don’t know at the beginning what tasks will be assigned to it. Therefore sequence of words: the servo-machine — a router — OpenWrt sounds very attractively, though some time ago it wouldn’t cross my mind)))

Let’s try to realize the following structure shown on the picture:

To operate with one servo drive is good, but it is not so interesting to us. Much more important is learn how to build a network of servo-drives. In our case, as a master of servo-drives network will be a router. All servo drives will be connected to the master of Uart. I understand that the I2C is the standard de facto, but Uart seems to be easier, more flexible and more familiar for me)))

For building of a servo-drives network we need the following adapter from Usb into Uart (we've already used it in the section Debug Port):

And on each servo-drive we will mount a small board on the chip mega8 for receiving of commands and for control pulse forming of such a type:

We've finished with the hardware. How to operate with a such servo-drives network from OpenWrt? There are a lot of variants. I like for example the script languague Lua. It is enough to know this language at the level HelloWorld for solving of our task.

Control algorithm.

1. A master (router) sends a package into the servo-drives network (over Uart). The package has the following standard structure:
|Length|To|CommandNumber|CommandBody|Control check|
Example:The package of 5 bites was transmitted: 5 1 10 179 195

2. The servo-drive, which is this package destined for, receives it and responses with the next package:
|Length|From|CommandNumber+1|CommandBody|Control check|
The package of 5 bites was received: 5 1 11 0 17
It's very simple.

Software.

I won't write about the firmware for mega8, you can download it from the website and study it out by yourself. It is very simple.

Let's study the Lua script in detail. The script has to do the following:

1. To open the port /dev/ttyUSBx

2. To ask an user to enter the servo-drive number and a new ststus for it

3. To generate a package with new data and to transmit it.

4. To wait for a response from the servo drive.

5. To change the mode to the standby mode of the entering of new data or to cancel the programm by closing of the port /dev/ttyUSBx

This task can be realized with Lua very simply. Therefore I wanted to try using of this language to realize this target.

The only trouble spot in this process is the lack of the Lua library in OpenWrt for a serial port. The library itself can be downloaded from here. Let's try to add it to the OpenWrt.

How to add a new package to OpenWrt, we have considered earlier, but I think we can do it again )))

1. Let's create the directory luars232 in the directory /trunk/package. Let's create the file makefile in it by using any makefile from the directory /trunk/package as a basis. Our makefile looks like this:

#
# Copyright (C) 2007-2009 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:=luars232
PKG_VERSION:=1.0.1

PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_FIXUP:=libtool

include $(INCLUDE_DIR)/package.mk

PKG_INSTALL=1

define Package/luars232
  SECTION:=libs
  CATEGORY:=Libraries
  TITLE:=Lua lib for serial communication over rs-232
endef

define Package/luars232/description
	Lua lib for serial communication over rs-232
endef

TARGET_CFLAGS += -DLUA_USE_LINUX $(FPIC)

define Build/Configure
        $(call Build/Configure/Default, \
        CFLAGS="$(TARGET_CFLAGS)" \
        )
endef

define Build/InstallDev
	$(INSTALL_DIR) $(1)/usr/include
	$(CP) $(PKG_INSTALL_DIR)/usr/include/librs232 $(1)/usr/include/
	$(INSTALL_DIR) $(1)/usr/lib/lua
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/librs232.{a,so*} $(1)/usr/lib/lua/
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/luars232.{a,so*} $(1)/usr/lib/lua/	
	$(INSTALL_DIR) $(1)/usr/lib/pkgconfig
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/pkgconfig/librs232.pc $(1)/usr/lib/pkgconfig/
endef

define Package/luars232/install
	$(INSTALL_DIR) $(1)/usr/lib/lua
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/librs232.so* $(1)/usr/lib/
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/luars232.so* $(1)/usr/lib/	
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/librs232.so* $(1)/usr/lib/lua
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/luars232.so* $(1)/usr/lib/lua	
endef

$(eval $(call BuildPackage,luars232))

2. Let's go into make menuconfig, select the necessary modules and then start make V=99.
LuCI :
Collections:
<M> luci
Libraries:
<M> luars232 (Lua lib for serial communication over rs-232)

Everything seems to be built, but not the library in luars232/bindings/lua/. libs/luars232. so . Checking the building log at the moment of execution of configure:

checking whether stripping libraries is possible... yes 
checking if libtool supports shared libraries... yes 
checking whether to build shared libraries... yes 
checking whether to build static libraries... yes 
checking for pkg-config... pkg-config 
checking for Lua headers and librairies with pkg-config... 
checking for Lua compiling and linking... no 
configure: WARNING: 
*** Lua (>=5.0) headers and/or librairies couldn't be found in your system. 
*** Try to install liblua, liblualib and liblua-dev with your software package manager. 
*** librs232 will be built without Lua bindings library. 
./configure: line 11780: 1: command not found 
configure: creating ./config.status 
config.status: creating Makefile 
config.status: creating src/Makefile 
config.status: creating bindings/Makefile

As we can see the configure didn't find the Lua headers and/or librairies. At first we check if they exist. If we check the log, we can see the paht where they are placed.
headers:"-I/trunk/staging_dir/target-mipsel_uClibc-0.9.32/usr/include"
There should be the files there: lua.h, luaconf.h, lualib.h
librairies:"-L/trunk/staging_dir/target-mipsel_uClibc-0.9.32/usr/lib"
There should be the files there:liblua.a, liblua.so, liblua.so.5.1.4, liblualib.so.

I have these files, but the configure didn't see them.

We have to find out why the configure can not find them in spite of their presence and all the written paths. Because the configure is generated in the configure.in, let's try to serch the cause of error in it....

I found the following line in the file configure.in:

LUA_TEST=`LT=luatest.c ; echo "#include........
if test "x$LUA_TEST" != "x0" ; then
        AC_MSG_RESULT(no)
        AC_MSG_WARN([

To tell the truth it is difficult for me to understand what's going on here ))). . . If I’m not mistaking there is a file created for checking which tries to be build and executed. And of course if it has been built and executed, it means that there are the necessary headers and libraries there. Therefore I do not really understand how a file can be executed on a computer if the library is built by the cross-compiler for the processor mips. Though I am certainly mistaken))). But nevertheless I suggest executing the following.

It should look like this:
if !(test "x$LUA_TEST" != "x0") ; then

Let's start the make V=99 ahain and check the building log:

checking for Lua headers and librairies with pkg-config... 
checking for Lua compiling and linking... yes 
./configure: line 11780: 1: command not found 
configure: creating ./config.status 
config.status: creating Makefile 
config.status: creating src/Makefile 
config.status: creating bindings/Makefile 
config.status: creating bindings/lua/Makefile 
config.status: creating include/Makefile 
config.status: creating include/librs232/Makefile 
config.status: creating src/librs232.pc 
config.status: executing depfiles commands 
config.status: executing libtool commands 
configure: WARNING: unrecognized options: --disable-nls 

 librs232 version 0.0.1 configured successfully.

As we can see, everything is OK now and the library has been built. There are my working sources (all the rest files you can find in the section «Download»).

Before writing of the script we should do some important things. ))

Let's check the example file(doc/example.lua)from the libraryб namely these lines:

-- write without timeout 
err, len_written = p:write("test") 
assert(e == rs232.RS232_ERR_NOERROR)

As you can see the line is transmitted into the function write. And I do not really understand how to transmit the protocol into this function that was created at the design stage. Surely this can berealized, but unfortunately I do not know how! ((( In principle it is possible to change the protocol and to work with the lines, but it's not so good. Although maybe not . . . . In general, a problem has appeared there where I did not expect!
The easiest solution for me was to change the write function in the library to make it receive the data array instead of lines.
How to do this? There is the function int lua_port_write(lua_State *L) in the file bindings/lua/luars232. c . This function executes the write method from Lua. Let's update it:

/*
 * error, written_len = port:write(data [, timeout_ms])
 */
static int lua_port_write(lua_State *L)
{
	int ret = 0;
	int argc = 0;
	//unsigned int timeout = 0;
	unsigned int wlen = 0;
	//size_t len = 0;
	//const char *data;
	struct rs232_port_t *p = NULL;

	p = (struct rs232_port_t*) luaL_checkudata(L, 1, MODULE_NAMESPACE);
	lua_remove(L, 1);

	if (p == NULL || !rs232_port_open(p)) {
		lua_pushinteger(L, RS232_ERR_PORT_CLOSED);
		lua_pushinteger(L, 0);
		return 2;
	}

	argc = lua_gettop(L);
	
	/* моя правка. Из луа должна передоваться таблица, а не строка.
	 * Здесь происходит разбор элементов таблицы */
	switch (argc) {
		case 1:
			{
				/* заведем две переменные. длина массива и сам массив, куда
				 * будем складывать элементы таблицы из луа */
				unsigned char array_len=0, lua_array[20]={0};
				
				lua_pushnil(L);
				while (lua_next(L, argc) != 0)
				{ 
					lua_array [array_len] = (int)(lua_tonumber(L,-1));
					//printf ("value=%d\n",lua_array [array_len]);					
					array_len++;
					lua_pop(L, 1); 
				}
				ret = rs232_write(p, lua_array, array_len, &wlen);	
			}
		break;
					
		default:
			lua_pushinteger(L, RS232_ERR_CONFIG);
			lua_pushinteger(L, 0);
		return 2;		
	}

	lua_pushinteger(L, ret);
	lua_pushinteger(L, wlen);
	return 2;
}

It turned out, it is not so bad. I would like to thank the author oa the library again.

Well, one more digression, mostly for myself, not to make me forget . . . Having built the library, it could be useful to test it, it means, to check the programming interface of a dynamic library.

1. Let’s test the response of dlopen by using of a small program:

#include 
#include 

int main (int argc, char **argv){
		void *handle;
		char *error;

		printf ("str=%s\n",argv[1]);
		handle = dlopen (argv[1], RTLD_LAZY);
		if (!handle) {
				fputs (dlerror(), stderr);
				printf ("\n");
				return (1);
		}
		printf ("handle=%p\n",handle);
		dlclose (handle);
}

root@OpenWrt:/# /home/test_dlopen /usr/lib/luars232.so 
str=/usr/lib/luars232.so 
handle=0x9dc170 

The result: the library has been loaded.

2. Let's check the dependencies:

$ ldd luars232.so 
	linux-gate.so.1 =>  (0x00d58000) 
	librs232.so.0 => /usr/local/lib/librs232.so.0 (0x00ed9000) 
	liblua5.1.so.0 => /usr/lib/liblua5.1.so.0 (0x00a34000) 
	libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00260000) 
	libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0x00960000) 
	libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0x00110000) 
	/lib/ld-linux.so.2 (0x0071f000) 

	$ ldd librs232.so 
	linux-gate.so.1 =>  (0x00375000) 
	libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x008f1000) 
	/lib/ld-linux.so.2 (0x00f76000) 

The result: the dependencies exist.

That's about it!!! Now we can write the Lua script for controlling of our servo-drive network.

After some time the script is ready ))). It is very simple and has comments almost in each line:

--[[ Подключаем библиотеку для работы с com портом. Можно былоб
			написать просто require("luars232"), то тогда обращение бы шло
			по имени модуля luars232.RS232_BAUD_115200 ]]
rs232 = require("luars232")

local SET_NEW_STATE					=		10
local SET_NEW_STATE_ANSWER	=		11

-- Определяем имя порта
port_name = "/dev/ttyUSB1"

-- определим канал для вывода сообщений
local out = io.stderr

-- определим канал для ввода данных с клавиатуры
local user_in = io.stdin

--[[ Вызываем функцию open для открытия порта.Эта функция возвращает 
		 два значения, которые присваиваются переменным e и p
		 e - тип ошибки, p - дескриптор порта ]]
local e, p = rs232.open(port_name)

-- если ошибка открытия
if e ~= rs232.RS232_ERR_NOERROR then
	-- handle error
	out:write(string.format("can't open serial port '%s', error: '%s'\n",
			port_name, rs232.error_tostring(e)))
	return
end

-- Настраиваем порт, вызывая соотвествующие методы.Методы описаны в luars232.c
assert(p:set_baud_rate(rs232.RS232_BAUD_115200)	== rs232.RS232_ERR_NOERROR)
assert(p:set_data_bits(rs232.RS232_DATA_8)			== rs232.RS232_ERR_NOERROR)
assert(p:set_parity(rs232.RS232_PARITY_NONE)		== rs232.RS232_ERR_NOERROR)
assert(p:set_stop_bits(rs232.RS232_STOP_1)			== rs232.RS232_ERR_NOERROR)
assert(p:set_flow_control(rs232.RS232_FLOW_OFF)	== rs232.RS232_ERR_NOERROR)

out:write(string.format("OK, port open with values '%s'\n", tostring(p)))

--[[ Порт настроен. Можно осуществлять обмен данными. Алгоритм управления 
   сервой следующий: 1. Мастер посылает пакет в котором указывается номер
   сервы и то состояние в которое она должна перейти. 2. Серва отвечает
   мастеру что пакет получен и команда исполнена
   Протокол общения:                                      
		Запрос:                                                
		|Длина|Кому|№команды|Тело|Кс|
		Ответ:                                                 
		|Длина|ОтКого|№команды+1|Тело|Кс| ]]

-- бесконечный цикл		
while true do		
	local input_error
	
	repeat
		out:write (string.format("Введите номер сервы(0-10) и положение(0-180) или q для выхода\n"))
		out:write (string.format("Пример:0 90 (выставить серву №0 в положение 90 градусов)\n"))

		-- Считаем данные которые ввел пользователь
		local keyboard_input = user_in:read()

		-- Разберем данные которые ввел пользователь
		input_args = {}
		local cnt=0
		for next_word in string.gmatch(keyboard_input, "%w+") do
			-- поместим в таблицу введеный символ
			table.insert (input_args,next_word)
			cnt = cnt + 1

			if (cnt >= 2) then
				break
			end
		end
	
		-- проверим корректность введеных данных
		if cnt < 2 or tonumber(input_args[1]) == nil or tonumber(input_args[2]) == nil then
			if input_args[1] == "q" then
				input_error = 0
				exit_flag   = 1;	-- выставим флаг что пользователь хочет выйти из проги
			else
				out:write (string.format("Ошибка ввода - нужно повторить ввод!\n"))
				input_error = 1
			end
		else
			exit_flag   = 0
			input_error = 0
		end
		
	until input_error == 0
	
	if (exit_flag == 1) then
		out:write (string.format("Выходим из программы...\n"))
		break;
	end
	
	--[[ сформируем буфер для отправки
		Запрос:                                                
		|Длина|Кому|№команды|Тело|Кс| ]]
		
	local tx_buf={5}
	table.insert (tx_buf,input_args[1])
	table.insert (tx_buf,SET_NEW_STATE)
	table.insert (tx_buf,input_args[2])
	table.insert (tx_buf,(tx_buf[1]+tx_buf[2]+tx_buf[3]+tx_buf[4])%0xff)

	-- Посылаем пакет
	err, len_written = p:write(tx_buf)
	assert(e == rs232.RS232_ERR_NOERROR)
	
	-- выведем на экран то что послали
	out:write (string.format(">Передан пакет %d байт:",len_written))
	for i=1,len_written,1 do
		out:write(string.format(" %d",tx_buf[i])) 
	end
	out:write(string.format("\n"))	

	-- Ждем ответа

	-- предположим что максимальный пакет не может быть больше 100 байт
	local read_len = 100 

	-- ждать ответа будем 100мс
	local timeout = 100

	-- запускаем функцию чтения данных из порта
	local err, data_read, size = p:read(read_len, timeout,1)
	assert(e == rs232.RS232_ERR_NOERROR)

	-- выведем на экран то что получили через уарт
	out:write(string.format("<Принят пакет %d байт:",size))
	for i=1,size,1 do
		out:write(string.format(" %d",string.byte(data_read,i))) 
	end
	out:write(string.format("\n"))

end

-- close
assert(p:close() == rs232.RS232_ERR_NOERROR)

Let's load and install the packages.

root@OpenWrt:/# opkg install luci luars232 
Installing luci (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci_0.10+svn6982-1_brcm47xx.ipk. 
Installing uhttpd (22) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/uhttpd_22_brcm47xx.ipk. 
Installing luci-mod-admin-full (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-mod-admin-full_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-mod-admin-core (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-mod-admin-core_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-lib-web (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-lib-web_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-lib-core (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-lib-core_0.10+svn6982-1_brcm47xx.ipk. 
Installing lua (5.1.4-7) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/lua_5.1.4-7_brcm47xx.ipk. 
Installing liblua (5.1.4-7) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/liblua_5.1.4-7_brcm47xx.ipk. 
Installing libuci-lua (2011-03-27.2-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/libuci-lua_2011-03-27.2-1_brcm47xx.ipk. 
Installing luci-lib-sys (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-lib-sys_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-lib-nixio (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-lib-nixio_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-sgi-cgi (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-sgi-cgi_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-lib-lmo (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-lib-lmo_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-i18n-english (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-i18n-english_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-lib-ipkg (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-lib-ipkg_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-theme-openwrt (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-theme-openwrt_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-theme-base (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-theme-base_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-app-firewall (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-app-firewall_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-app-initmgr (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-app-initmgr_0.10+svn6982-1_brcm47xx.ipk. 
Installing libiwinfo (13) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/libiwinfo_13_brcm47xx.ipk. 
Installing luars232 (1.0.1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luars232_1.0.1_brcm47xx.ipk. 
Configuring luci-lib-sys. 
Configuring liblua. 
Configuring libuci-lua. 
Configuring lua. 
Configuring luci-lib-core. 
Configuring luci-lib-nixio. 
Configuring luci-sgi-cgi. 
Configuring luci-lib-lmo. 
Configuring luci-lib-web. 
Configuring luci-i18n-english. 
Configuring luci-mod-admin-core. 
Configuring libiwinfo. 
Configuring luci-theme-base. 
Configuring luci-theme-openwrt. 
Configuring luci-app-firewall. 
Configuring luci-lib-ipkg. 
Configuring luci-mod-admin-full. 
Configuring luars232. 
Configuring uhttpd. 
Configuring luci-app-initmgr. 
Configuring luci. 

Let's try to test it. Starting it:

root@OpenWrt:/# lua /home/ServoManager/ServoManager.lua 
OK, port open with values 'device: /dev/ttyUSB0, baud: 115200, data bits: 8, parity: none, stop bits: 1, flow control: off' 
Введите номер сервы(0-10) и положение(0-180) или q для выхода 
Пример:0 90 (выставить серву №0 в положение 90 градусов) 
1 179 
>Передан пакет 5 байт: 5 1 10 179 195 
<Принят пакет 5 байт: 5 1 11 0 17 
Введите номер сервы(0-10) и положение(0-180) или q для выхода 
Пример:0 90 (выставить серву №0 в положение 90 градусов) 
^[[A 
Ошибка ввода - нужно повторить ввод! 
Введите номер сервы(0-10) и положение(0-180) или q для выхода 
Пример:0 90 (выставить серву №0 в положение 90 градусов) 
1 10 
>Передан пакет 5 байт: 5 1 10 10 26 
<Принят пакет 5 байт: 5 1 11 0 17 
Введите номер сервы(0-10) и положение(0-180) или q для выхода 
Пример:0 90 (выставить серву №0 в положение 90 градусов) 
q 
Выходим из программы... 
root@OpenWrt:/# 

It works and it is cool!



To buy servo-drives from us:

Servo-drives