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);
}
}