HIDE NAV

Rasberry Pi Remote Receiver with I2C Pico Hardware Control

A demonstration of distribted system control where a Raspberry Pi handles human interface with SDL Game Library GameController
while a Pi Pico handles hardware control. An I2C bus transmits from the RPi to the Pico.

RASPBERRY PI SIDE



main.c


#include <SDL.h>
#include <lgpio.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

//Note: I2C pins are on GP Pins adjacent to 5v/Gnd
#define I2C_PICO_ADDRESS 0x31
#define FLOATS_PER_PACKET 2

//Global vars
int chip_handle;
int i2c_handle;
double loop_delay_sec = .01;
float servo_level;
float led_dimmer_duty;

//funcitons
void i2c_send_floats(int i2c_handle, float* data);

int main(){
	//initialize lgpio services
	chip_handle = lgGpiochipOpen(0);
	if(chip_handle < 0) {
		puts("Error in initialization, handle is negative");
		return 0;
	}

	//initialize I2C Link
	i2c_handle = lgI2cOpen(1, I2C_PICO_ADDRESS, 0);
	if( i2c_handle <= 0){
		puts("Error opening I2C Bus, handle is invalid");
		return 0;
	}


	//initialize SDL game library and setup for game controller input only
	if( SDL_Init(SDL_INIT_GAMECONTROLLER) < 0 ){
		puts("SDL initialization failed");
	} else {
		puts("SDL_Init success");
	}
	if(SDL_NumJoysticks() <= 0) {
		puts("No joysticks detected. Quitting...");
		return 0;
	}
	SDL_GameController* controller = SDL_GameControllerOpen(0);
	if(controller == NULL) {
		puts("Error opening game controller 0. Quitting...");
		return 0;
	}

	puts("Game Controller Setup Complete.\n \t To Quit, Press Button 0 [South Button].");

	//Primary Control Loop: SDL event handling of game controller
	while(1) {
		//process all input events
		SDL_Event event;
		bool send_update_enable = false;
		while(SDL_PollEvent(&event)){
			switch(event.type){
				case SDL_QUIT: puts("Quitting..."); SDL_Quit(); return 0; break;
				case SDL_CONTROLLERAXISMOTION:

					       int axis_no = event.caxis.axis;
					       int axis_value = event.caxis.value;
					       float axis_fraction = (float)event.caxis.value  / AXIS_MAX;
					       if(axis_no == 0){
						       //left stick x-axis						       
							servo_level = axis_fraction;
							//printf("servo level %+.2f\n", servo_level);
							send_update_enable = true;
					       } else if(axis_no == 3){
						       //right stick y
							//-1 multiplier to reverse it
							 led_dimmer_duty = axis_fraction * -1.0f;
						      	 //printf("led level %+.2f\n", led_dimmer_duty);
							 send_update_enable = true;
					       }
					       break;

				case SDL_CONTROLLERBUTTONDOWN:
						//button 0 quits
					       int button_dn_no = event.cbutton.button;
					       if(button_dn_no == 0){
						        puts("button 0 pressed, exiting");
						        SDL_Quit();
							return(0);
					       }
					       break;

				case SDL_CONTROLLERBUTTONUP:
						//no actions for button release
					       break;
			
			}
		}

		//send results to pico if an update is needed
		if(send_update_enable) {
			float data[] = {servo_level, led_dimmer_duty};
			i2c_send_floats(i2c_handle, data);
		}

		//delay until next cycle
		lguSleep(loop_delay_sec);
	}

	return 0;
}

void i2c_send_floats(int i2c_handle, float* data) {
	unsigned int dataSize = sizeof(float)* FLOATS_PER_PACKET;
	lgI2cWriteDevice(i2c_handle,(char*)data, dataSize);
}

makefile


CC = gcc

PNAME = sdl-gamecon-servo-led-dim

SRCS = $(wildcard src/*.c)

LINKS = -lrt -I/usr/include/SDL2 -D_GNU_SOURCE=1 -D_REENTRANT -lSDL2 -llgpio

build:
	${CC} -o bin/${PNAME} ${SRCS} ${LINKS}

run:
	@bin/${PNAME}




PICO SIDE



i2c-link.h


#include "pico/stdlib.h"
#include "hardware/i2c.h"

#define I2C_LINK_SDA_GP 16
#define I2C_LINK_SCL_GP 17
#define I2C_LINK_MODULE i2c0
#define I2C_SELF_ADDRESS 0x31
#define I2C_LINK_DATA_RATE 4000000

#define FLOATS_PER_PACKET 2

void i2c_link_init();
void i2c_link_read_floats_blocking(float data[FLOATS_PER_PACKET]);


i2c-link.c


#include "i2c-link.h"

void i2c_link_init(){
  gpio_pull_up(I2C_LINK_SDA_GP);
  gpio_pull_up(I2C_LINK_SCL_GP);
  i2c_init(I2C_LINK_MODULE, I2C_LINK_DATA_RATE);
  i2c_set_slave_mode(I2C_LINK_MODULE, true, I2C_SELF_ADDRESS);
  gpio_set_function(I2C_LINK_SDA_GP, GPIO_FUNC_I2C);
  gpio_set_function(I2C_LINK_SCL_GP, GPIO_FUNC_I2C);
}

void i2c_link_read_floats_blocking(float data[FLOATS_PER_PACKET]){
  float value;
  for(int i = 0; i < FLOATS_PER_PACKET; i++){
    uint8_t buffer[sizeof(float)];
    i2c_read_raw_blocking(I2C_LINK_MODULE, buffer, sizeof(float));
    value = 0;
    for(int k = 0; k < sizeof(float); k++){
      *(int*)&value |= ((int)buffer[k] << (8*k));
    }
    data[i] = value;
  }
}


main.c



#include "pico/stdlib.h"
#include "i2c-link.h"
#include "hardware/pwm.h"
#include <stdlib.h>
#include <math.h>

#define LED_ONB_GP 25
//10 kHz     (8 ns CPU period) * (5 div) * (1250 wrap count) = .00005s
#define LED_WRAP 1250
#define LED_DIV 5

#define SERVO_GP 15
//50 Hz.      (8 ns CPU period) * (250 div) * (10000 wrap count) = .02s
#define SERVO_WRAP 10000
#define SERVO_DIV 250

// Time per level count:    (8 ns) * (250div) = .000002s = 2 us

//2.5ms.    (2 us) * (1250) = 2500 us = 2.5ms.
const uint16_t SERVO_LEVEL_MAX = 1250;
//1.5ms. Center of range
const uint16_t SERVO_LEVEL_MIDDLE = 750;
//0.5 ms
const uint16_t SERVO_LEVEL_MIN = 250;

int main() {
	// setup &  initialize

	// power on blink routine
	gpio_init(LED_ONB_GP);
	gpio_set_dir(LED_ONB_GP, GPIO_OUT);
	for(int i = 0; i < 10; i++){
		gpio_put(LED_ONB_GP, 1);
		sleep_ms(50);
		gpio_put(LED_ONB_GP, 0);
		sleep_ms(50);
	}

	// I2C Serial Link
	i2c_link_init();
	float rx_buffer[FLOATS_PER_PACKET];

	// SERVO - PWM Freq. set to 50Hz
	gpio_set_function(SERVO_GP, GPIO_FUNC_PWM);
	const uint SERVO_PWM_SLICE = pwm_gpio_to_slice_num(SERVO_GP);
	pwm_set_enabled(SERVO_PWM_SLICE, true);
	pwm_set_wrap(SERVO_PWM_SLICE, SERVO_WRAP);
	pwm_set_clkdiv_int_frac(SERVO_PWM_SLICE, SERVO_DIV, 0);
	// set to Center
	pwm_set_gpio_level(SERVO_GP, SERVO_LEVEL_MIDDLE);


	//Onboard LED - PWM Freq. set to 20 kHz
	gpio_set_function(LED_ONB_GP, GPIO_FUNC_PWM);
	const uint LED_PWM_SLICE = pwm_gpio_to_slice_num(LED_ONB_GP);
	pwm_set_enabled(LED_PWM_SLICE, true);
	pwm_set_wrap(LED_PWM_SLICE, LED_WRAP);
	pwm_set_clkdiv_int_frac(LED_PWM_SLICE, LED_DIV, 0);
	//  set to 0% duty
	pwm_set_gpio_level(LED_ONB_GP, 0);



	// primary loop
		while (true) {
			//wait for communication, store in receive buffer
			i2c_link_read_floats_blocking(rx_buffer);

			//set servo angle with first buffer float value
			float servo_level = rx_buffer[0];
			float servo_half_range = SERVO_LEVEL_MAX - SERVO_LEVEL_MIDDLE;
			uint16_t servo_pwm_level = (int)(servo_level * servo_half_range)
				+ SERVO_LEVEL_MIDDLE;
			pwm_set_gpio_level(SERVO_GP, servo_pwm_level);

			//set LED's PWM Duty Cycle with second float's absolute value
			float led_level = fabs(rx_buffer[1]);
			uint16_t led_pwm_level = (uint16_t)(led_level * LED_WRAP);
			pwm_set_gpio_level(LED_ONB_GP, led_pwm_level);
		}
}