Setting up
Install some required libraries, and get some tools as follows [1].$ sudo apt install gnupg dirmngr curl sbuild \ ubuntu-dev-tools qemu-user-static binfmt-support
Check id for the current user and add it to sbuild group. Edit sudoers if you do not want password prompting. Logout and login again.
$ id $ sudo usermod -a -G sbuild $USER $ sudo sh -c 'echo "$SUDO_USER ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers'
Get public key for raspbian archive and use gpg (OpenPGP encryption and signing tool) to create keyring used by debootstrap. Debootstrap is a tool which will install a Debian base system into a subdirectory of another, already installed system.
$ curl -sL http://archive.raspbian.org/raspbian.public.key | gpg --import - $ gpg --export 9165938D90FDDD2E > $HOME/raspbian-archive-keyring.gpg
Create 'rpi.sources' file with a list as follows.
$ cat > $HOME/rpi.sources <<EOF deb http://archive.raspbian.org/raspbian/ RELEASE main contrib non-free rpi deb-src http://archive.raspbian.org/raspbian/ RELEASE main contrib non-free rpi EOF
And setup mk-sbuild defining chroots directory, debootstrap keyring, and sources list.
$ cat > $HOME/.mk-sbuild.rc <<EOF SOURCE_CHROOTS_DIR="$HOME/chroots" DEBOOTSTRAP_KEYRING="$HOME/raspbian-archive-keyring.gpg" TEMPLATE_SOURCES="$HOME/rpi.sources" SKIP_UPDATES="1" SKIP_PROPOSED="1" SKIP_SECURITY="1" EATMYDATA="1" EOF
Create a full sbuild-with-schroot environment to do builds.
$ mk-sbuild --name stretch-rpi --arch=armhf \ --debootstrap-mirror=http://archive.raspbian.org/raspbian/ stretch
After the building is done, and you will see a message as shown below.
Use stream editor command 'sed' to replace 'union-type=aufs' with 'union-type=overlay' in 'sbuild-stretch-rpi-armhf' file in place.
$ sudo sed -i 's/union-type=aufs/union-type=overlay/g' \ /etc/schroot/chroot.d/sbuild-stretch-rpi-armhf
Using schroot
To bind a local directory into schroot, edit '/etc/schroot/sbuild/fstab' in nano,$ sudo nano /etc/schroot/sbuild/fstab
and add a line similar to the one shown below.
/home/yan/ws /home/yan/ws none rw,bind 0 0
List all schroot sessions you have as follows [2].
$ schroot -la
And begin a new schroot instance:
$ schroot -b -c stretch-rpi-armhf
That creats a new schroot environment and echos the session name like
stretch-rpi-armhf-7fedd5e2-c7aa-46fe-bcfc-b804606d5ba3
Then enter the created schroot environment above as a 'root' user.
$ schroot -r -u root -c stretch-rpi-armhf-7fedd5e2-c7aa-46fe-bcfc-b804606d5ba3
Now you are in a "sandbox" clean environment with the cross compilers installed as illustrated below.
You can exit from the environment using 'exit' command, and you can re-enter the session. It is also possible to create multiple schroot sessions. To clean up, you can end a session using '-e'. In that case, you will lose all files that you have added in that session.
$ schroot -e -c stretch-rpi-armhf-7fedd5e2-c7aa-46fe-bcfc-b804606d5ba3
To install software packages in the environment, you can use apt as shown below. You can append ':armhf' behind the package name also.
# apt update # apt -y install cmake git pkg-config # apt -y install libgtk-3-dev libavcodec-dev libavformat-dev libswscale-dev
As we are cross compiling, we want to use 'arm-linux-gnueabihf-gcc' or 'arm-linux-gnueabihf-g++'' instead of gcc or g++.
# which arm-linux-gnueabihf-gcc # arm-linux-gnueabihf-gcc -v
Building a Simple Program
Let us start building a simple program, cc1.cpp, which is listed below.#include <stdio.h> int main (void) { printf("Hello cross compiling!\n"); return 0; }
Save the file in our shared directory as "/home/yan/ws/cc1/cc1.cpp" so that we have access to it in both Ubuntu and schroot environment. Also make the "CMakeLists.txt" as usual.
cmake_minimum_required(VERSION 3.0) project(cc1) add_executable(cc1 cc1.cpp)
Then, create a tool chain file with a name like "pi.cmake" where we set compilers [3].
SET(CMAKE_SYSTEM_NAME Linux) SET(CMAKE_SYSTEM_VERSION 1) SET(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc) SET(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabihf-g++)
Thereafter, we can cross compile the program with the option for the tool chain file.
# cd /home/yan/ws/cc1 # mkdir build # cd build # cmake -D CMAKE_TOOLCHAIN_FILE=../pi.cmake .. # make
When you run the program, you can see the output of the program because we already have installed QEMU earlier.
# ls # ./cc1 # objdump -l cc1 # objdump -i cc1 # ldd cc1 # cd ..
OpenCV
If we use shared libraries, the version on build machine and the host must be the same. Therefore, we will get a specific version of opencv, build from source, and install as follows. The cmake command might get error when you enter it first time and it should be OK if you enter the command again. Add "-D SOFTFP=ON" option if you want to use soft floating point. And if you want to build OpenCV as a shared library, change it to '-D BUILD_SHARED_LIBS=ON'.# cd /home/yan/ws # wget https://github.com/opencv/opencv/archive/4.2.0.tar.gz # mv 4.2.0.tar.gz opencv-4.2.0.tar.gz # tar zxvf opencv-4.2.0.tar.gz # wget https://github.com/opencv/opencv_contrib/archive/4.2.0.tar.gz # mv 4.2.0.tar.gz opencv_contrib-4.2.0.tar.gz # tar zxvf opencv_contrib-4.2.0.tar.gz # cd opencv-4.2.0/platforms/linux # mkdir -p build # cd build # cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr \ -D OPENCV_EXTRA_MODULES_PATH=../../../../opencv_contrib-4.2.0/modules \ -D CMAKE_TOOLCHAIN_FILE=../arm-gnueabi.toolchain.cmake \ -D BUILD_SHARED_LIBS=OFF ../../.. # make # make install # echo /usr/lib/ > /etc/ld.so.conf.d/opencv.conf # ldconfig
As an example, "cc2.cpp" uses opencv imread to load an image file, thiri.jpg, from the source directory, and shows the image.
#include <stdio.h> #include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; //----------------------------------------------------------------------------- int main(int argc, char** argv) { Mat a = (Mat_<uchar>(2,3)<<0,1,2,3,4,5); cout << a << endl; Mat f = Mat_<float>(a); f = f * 2.0; cout << f << endl; Mat c; normalize(f,f,0,255, NORM_MINMAX); c = Mat_<uchar>(f); cout << c << endl; return 0; } //-----------------------------------------------------------------------------
Its "CMakeLists.txt" is as follows.
cmake_minimum_required(VERSION 2.8) project(cc2) find_package( OpenCV REQUIRED ) add_executable( cc2 cc2.cpp ) target_link_libraries( cc2 ${OpenCV_LIBS} )
The same tool chain file in the previous example, "pi.cmake", is used again. And build:
# cd /home/yan/ws/cc2 # mkdir build # cd build # cmake -D CMAKE_TOOLCHAIN_FILE=../pi.cmake .. # make
Copy the executable file from our build machine to the host Raspberry Pi, and run the program. The program should run properly even if OpenCV is not installed in the host. Because we built OpenCV as a static library using '-D BUILD_SHARED_LIBS=OFF' option.
wxWidgets
Similarly, we will download specific version of wxWigets and build it as static library. Change '--disable-shared' to '--enable-shared' if you want to build as shared library.# cd /home/yan/ws # wget https://github.com/wxWidgets/wxWidgets/releases/download/v3.0.4/wxWidgets-3.0.4.tar.bz2 # tar xjvf wxWidgets-3.0.4.tar.bz2 # cd wxWidgets-3.0.4 # mkdir gtk-build # cd gtk-build # ../configure --help # ../configure --enable-unicode --disable-shared --with-gtk=3 --prefix=/usr \ --host=arm-unknown-linux-gnueabi --build=arm-unknown-linux-gnueabi # make # make install
A simple wxWidgets program, "cc3.cpp", and its "CMakeLists.txt" are shown in the following lists. The tool chain file "pi.cmake" from previous examples can be copied to the source folder also.
#includeclass Simple : public wxFrame { public: Simple(const wxString& title); }; Simple::Simple(const wxString& title) : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 150)) { Centre(); } class MyApp : public wxApp { public: virtual bool OnInit(); }; IMPLEMENT_APP(MyApp) bool MyApp::OnInit() { Simple *simple = new Simple(wxT("Simple")); simple->Show(true); return true; }
cmake_minimum_required(VERSION 2.8) project(cc3) find_package(wxWidgets COMPONENTS core net base REQUIRED) include("${wxWidgets_USE_FILE}") add_executable(cc3 cc3.cpp) target_link_libraries(${PROJECT_NAME} ${wxWidgets_LIBRARIES})
Build it as follows.
# cd /home/yan/ws/cc3 # mkdir build # cd build # cmake -D CMAKE_TOOLCHAIN_FILE=../pi.cmake .. # make
The resulting executable can be now copied to the host Raspberry Pi and run on it.
Acknowledgements
I would like to express my thanks to Michael Walton for his advice and guidance.References
[1] R4SAS. Building sbuild environment for cross-building raspbian packages on amd64/i386 machines. 2018 MAR 05.url: https://i2p.rocks/blog/building-sbuild-environment-for-cross-building-raspbian-packages-on-amd64i386-machines.html.
[2] Debian Wiki. CrossCompiling. 2020-03-12.
url: https://wiki.debian.org/CrossCompiling.
[3] Alex C.U. The Useful RaspberryPi Cross Compile Guide. 2017-08-01.
url: https://medium.com/@au42/the-useful-raspberrypi-cross-compile-guide-ea56054de187.
No comments:
Post a Comment
Comments are moderated and don't be surprised if your comment does not appear promptly.