The Raspberry Pi is a pretty useful tool for quickly prototyping some IoT product, simulating an embedded environment, running a small Kubernetes cluster, etc; there are probably lots of reasons why you’d want to use one (or maybe not?).
As for developing software that can run on it, if you’re writing it in C or C++, there’s many ways to go about it:
Sync the code changes you make on the Pi and build it natively on it (via rsync, scp, SSHFS, git, etc)
Use crosstool-ng to setup a toolchain and use it to cross-compile your software; then copy the resulting binaries onto the Pi
Use a precompiled toolchain to cross-compile the software; then copy the binaries onto the Pi
And you can probably find many other creative ways developers came up with.
This mean that choosing which way to do it can be difficult, unless you have some clear project requirements.
For most of the projects that I’ve been working on, the requirements have been:
No source code on the Pi (and consequently no build tools installed on it)
Cannot run code inside containers on the Pi (the latency cost for communicating with peripherals is too high - especially when accessing the camera)
Anyone (on any platform) can clone the source code and build the binaries on their machine
A CI pipeline that builds everything in the cloud can be integrated later on
Builds don’t take more than a few minutes (ideally less than a minute)
And after trying most of the options I could find out there, I’ve found that using Docker and cross-compilation works best. And with the release of buildx, it’s even easier to build and dump the binaries on the host system.
By default, the image is going to be available to use on the host as cross-stretch. If docker images doesn’t show it, add the --load flag when building.
Tip
To bust the cache, use --no-cache.
Now create a base image w/ some common libs usually available on the Pi:
If you copy the binary over to the Pi and run it, you should be seeing your Pi’s model and revision, e.g:
While the above example might be good enough to illustrate how we can use Docker to cross-compile, it’s probably incomplete without illustrating how to use some of the libs that are usually available on the Pi.
// hello-pi.c
/*
Copyright (c) 2012, Broadcom Europe Ltd
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/#include<stdio.h>#include<stdlib.h>#include<string.h>#include"interface/vmcs_host/vc_vchi_gencmd.h"#include"interface/vmcs_host/vc_gencmd_defs.h"#include<wiringPi.h>#define TEMP_KEY_SIZE 5
voidchop(char*str,size_tn);intmain(){// Get board model
intm,rev,mem,maker,ov;piBoardId(&m,&rev,&mem,&maker,&ov);printf("Raspberry Pi: %s\n",piModelNames[m]);printf("Revision: %s\n",piRevisionNames[rev]);// Get SoC temperature
// https://github.com/raspberrypi/userland/blob/master/host_applications/linux/apps/gencmd/gencmd.c
// vcgencmd measure_temp
VCHI_INSTANCE_Tvchi_instance;VCHI_CONNECTION_T*vchi_connection=NULL;vcos_init();if(vchi_initialise(&vchi_instance)!=0){fprintf(stderr,"VCHI init failed\n");return1;}// Create a vchi connection
if(vchi_connect(NULL,0,vchi_instance)!=0){fprintf(stderr,"VCHI connect failed\n");return1;}vc_vchi_gencmd_init(vchi_instance,&vchi_connection,1);charbuffer[GENCMDSERVICE_MSGFIFO_SIZE];size_tbuffer_offset=0;intret;// Reset the buffer
buffer[0]='\0';// Set the cmd
buffer_offset=vcos_safe_strcpy(buffer,"measure_temp",sizeof(buffer),buffer_offset);// Send the gencmd for the argument
if((ret=vc_gencmd_send("%s",buffer))!=0){printf("vc_gencmd_send returned non-zero code: %d\n",ret);return1;}// Get + print out the response
if((ret=vc_gencmd_read_response(buffer,sizeof(buffer)))!=0){printf("vc_gencmd_read_response returned a non-zero code: %d\n",ret);return1;}buffer[sizeof(buffer)-1]=0;if(buffer[0]!='\0'){if(strncmp(buffer,"temp=",TEMP_KEY_SIZE)==0){chop(buffer,TEMP_KEY_SIZE);printf("Temperature: %s\n",buffer);}elseputs(buffer);}vc_gencmd_stop();// Close the vchi connection
if(vchi_disconnect(vchi_instance)!=0){fprintf(stderr,"VCHI disconnect failed\n");return1;}return0;}voidchop(char*str,size_tn){size_tlen=strlen(str);if(n>len)return;memmove(str,str+n,len-n+1);}
# CMakeLists.txt
cmake_minimum_required(VERSION3.16)project(hello-pi)set(CMAKE_MODULE_PATH${CMAKE_MODULE_PATH}${CMAKE_INSTALL_PREFIX}/lib/cmake//usr/lib/cmake)set(VC_INST_PATH${CMAKE_PREFIX_PATH}/opt/vc)set(VC_LIBS${VC_INST_PATH}/lib)set(VC_INCLUDES${VC_INST_PATH}/include)set(VCOS_PLATFORMpthreads)add_definitions(-Wall-Werror)find_library(mmalcore_LIBSNAMESmmal_corePATHS"${VC_LIBS}")find_library(mmalutil_LIBSNAMESmmal_utilPATHS"${VC_LIBS}")find_library(mmal_LIBSNAMESmmalPATHS"${VC_LIBS}")if((NOTmmal_LIBS)OR(NOTmmalutil_LIBS)OR(NOTmmalcore_LIBS))message(FATAL_ERROR"Could not find mmal libs")endif()find_path(vmcs_host_INCNAMESvmcs_hostPATHS"${VC_INCLUDES}/interface")if(NOTvmcs_host_INC)message(FATAL_ERROR"Could not find vmcs_host interfaces")endif()find_library(vchostif_LIBSNAMESvchostifPATHS"${VC_LIBS}")find_library(vchiq_arm_LIBSNAMESvchiq_armPATHS"${VC_LIBS}")find_library(vcos_LIBSNAMESvcosPATHS"${VC_LIBS}")if((NOTvcos_LIBS)OR(NOTvchiq_arm_LIBS)OR(NOTvchostif_LIBS))message(FATAL_ERROR"Could not find vcos/vchiq_arm/vchostif libs")endif()find_library(wiringPi_LIBNAMESwiringPi)if(NOTwiringPi_LIB)message(FATAL_ERROR"Could not find wiringPi")endif()include_directories(${VC_INCLUDES}${VC_INCLUDES}/interface/vcos${VC_INCLUDES}/interface/vcos/${VCOS_PLATFORM})add_executable(hello-pihello-pi.c)target_link_libraries(hello-pioptimizedpthread${vcos_LIBS}${vchiq_arm_LIBS}${vchostif_LIBS}wiringPi)