~npisanti/rpiezos

0ce30364f9e0316ca0d80cf9fbe7f47c26d095c8 — Nicola Pisanti 4 months ago
initial version
8 files changed, 529 insertions(+), 0 deletions(-)

A .clang-format
A .gitignore
A LICENSE.md
A Makefile
A README.md
A hardware/README.md
A hardware/rPiezos_sketch.fzz
A src/main.c
A  => .clang-format +5 -0
@@ 1,5 @@
BasedOnStyle: LLVM
IndentWidth: 8
UseTab: ForIndentation
BreakBeforeBraces : Linux
ColumnLimit: 0
\ No newline at end of file

A  => .gitignore +4 -0
@@ 1,4 @@

/bin/*
/src/**/*.d
/src/**/*.o

A  => LICENSE.md +21 -0
@@ 1,21 @@
MIT License

Copyright (c) 2020 Nicola Pisanti

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

A  => Makefile +49 -0
@@ 1,49 @@

# this is a lazy generic makefile 
# mostly from : 
# https://spin.atomicobject.com/2016/08/26/makefile-c-projects/

TARGET ?= bin/$(shell basename $(CURDIR))
SRC_DIRS ?= ./src
DESTDIR?=/usr/local/bin

CFLAGS += -std=c99 -pipe -finput-charset=UTF-8 -g -D_POSIX_C_SOURCE=200809L
CFLAGS += -Wall -Wwrite-strings 
CFLAGS += -Wconversion -Wshadow -Wstrict-prototypes
CFLAGS += -Werror=implicit-function-declaration -Werror=implicit-int
CFLAGS += -Werror=incompatible-pointer-types -Werror=int-conversion

# links for system libraries, for example liblo
CFLAGS += -llo

SRCS := $(shell find $(SRC_DIRS) -name *.cpp -or -name *.c -or -name *.s)
OBJS := $(addsuffix .o,$(basename $(SRCS)))
DEPS := $(OBJS:.o=.d)

INC_DIRS := $(shell find $(SRC_DIRS) -type d)
INC_FLAGS := $(addprefix -I,$(INC_DIRS))

CFLAGS += $(INC_FLAGS) -MMD -MP

all: release

release: CFLAGS += -Os -DNDEBUG
release: $(TARGET)

debug: CFLAGS += -DDEBUG -Wpedantic -Wextra 
debug: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $@ $(LOADLIBES) $(LDLIBS)

-include $(DEPS)

install: all
	install -d $(DESTDIR)
	install $(TARGET) $(DESTDIR)

clean:
	$(RM) $(TARGET) $(OBJS) $(DEPS)

format:
	clang-format-8 -i $(SRCS)

A  => README.md +69 -0
@@ 1,69 @@
# rpiezos

This is a system composed by a shield to mount on the raspberry pi and a server application for translating piezo sensors signals into OSC messages.

## Usage
```
rpiezos [options]
```
    
option list:

```
-i [destination IP] : sets the destination IP address, localhost if not given
-p [destination port] : sets the destination port. This is mandatory to set.
-a [destination address] : sets the destination OSC address. defaults to /piezo
-p [piezos options] : sets the active piezos and range, see below     
```

after the `-p` option you can give a list of number from 0 to 5, to activate the correlated sensors, optionally you can add some `:` to set ranges, value outside the ranges won't be sent. For example:

```
rpiezos -p 12345 -s 012345
```

sends all the sensors values to localhost:12345

```
rpiezos -i 192.168.0.44 -p 3333 -s 01:10:512
```

sends sensors 0 and 1 values to 192.168.0.44:3333, only if the values are from 10 to 512

```
rpiezos -a /test -p 3333 -s 2:10:1024 -s 3:5:1024
```

sends messages to localhost:3333, at the OSC address /test, sends just the values from 10 to 1024 of sensor 2 and from 5 to 1024 of sensor 3.


## Building 

you need `liblo` installed (included dev packages) in your system, then you can build with make. 
On raspian buster for example:

```sh
sudo apt-get install build-essential
sudo apt-get install liblo7 liblo-dev
git clone https://git.sr.ht/~npisanti/rpiezos
cd rpiezos 
make

```

then you will have an executable in `bin/rpiezos`. 


## Hardware
For more info on how to build the hardware, go to the [hardware](https://git.sr.ht/~npisanti/rpiezos/tree/master/hardware/README.md) readme.

perfboard prototype:
<p align="center">
  <img src="http://npisanti.com/data/repos/rpiezos/prototype.jpg" width="700">
</p>

## Thanks to 
Dario Longobardi's  [ofxGPIO](https://github.com/kashimAstro/ofxGPIO), most of the code is ported from the `mcp.h` file.

## License 
Nicola Pisanti MIT License 2020.

A  => hardware/README.md +53 -0
@@ 1,53 @@

These are some notes on the used harware:
   
A the moment you need those component for one shield:   

```
1x MCP3008
6x 5.1v zener diode
6x 1M ohm resistor
6x 1.8k ohm resistor
6x 560 ohm resistor
6x 3.5mm female jack socket - 3 pole stereo
1x 20x2 pin header

```
<br>

This is a breadboard prototype for one of the sensor input (you need 5.1 zener diodes, 1M, 1.8k and 560 ohm resistors and an MCP3008 DAC), basically the 5v piezo input has to go into a voltage divider before going into the MCP3008 input, as the MP3008 runs at 3.3v:   
<p align="center">
  <img src="http://npisanti.com/data/repos/rpiezos/breadboard.jpg" width="700">
</p>
   
The rPi 40pin GPIO has to be connected to the MCP3008 making these connections:  
 
```
(rPI)------->(MCP3008)   
GROUND------>DGND   
CE0--------->CS   
MOSI-------->DIN   
MISO-------->DOUT   
CLK--------->CLK   
GROUND------>AGND   
3.3V-------->VREF   
3.3V-------->VDD   
```
<br>

For the piezo cables you need 3.5mm mono jacks, and coaxial mono cables. The inner part of the cables goes to the inner part of the piezo and to the shorter of the two jack connections. You could also put some hot glue on the piezos for making the connections stronger.   
<p align="center">
  <img src="http://npisanti.com/data/repos/rpiezos/jack.jpg" width="700">
</p>
    
   
Here is my messy perfboard prototype of the shield, if you want to do somethings like this remember that from above the rPi GPIO are the same, but the MCP3008 pin will be mirrored.  
<p align="center">
  <img src="http://npisanti.com/data/repos/rpiezos/perfboard.jpg" width="700">
</p>   
   
   
PS: if someone would ever try to make a better schematic file from those notes, i'm happy of every contribution.   
   
   
MCP3008 datasheet: https://cdn-shop.adafruit.com/datasheets/MCP3008.pdf   

A  => hardware/rPiezos_sketch.fzz +0 -0

A  => src/main.c +328 -0
@@ 1,328 @@
/*
 * [rpiezos]
 * Nicola Pisanti, MIT License 2020
 */

#include <fcntl.h>
#include <linux/spi/spidev.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <time.h>
#include <unistd.h>

#include <lo/lo.h>

#define NUM_PIEZOS 6

typedef struct piezo_t {
	int enabled;
	int z1;
	int min;
	int max;
	int mark;
} piezo_t;

typedef struct config_t {
	int verbose;
	const char *ip;
	const char *port;
	const char *address;
} config_t;

void quit_handler(int signo);
int parse_args(int argc, char *argv[], piezo_t *piezos, config_t *config);
void usec_sleep(long int usecs);

int running = 1;

int main(int argc, char *argv[])
{
	int rc = 1;

	piezo_t piezos[NUM_PIEZOS];
	for (unsigned i = 0; i < NUM_PIEZOS; ++i) {
		piezos[i].enabled = 0;
		piezos[i].min = 0;
		piezos[i].max = 1024;
		piezos[i].mark = 0;
		piezos[i].z1 = -1;
	}

	config_t config;
	config.ip = NULL;
	config.port = NULL;
	config.address = "/piezo";
	config.verbose = 0;

	// --------- args -------------------------
	rc = parse_args(argc, argv, piezos, &config);
	if (rc != 0) {
		return rc;
	}

	for (int i = 0; i < NUM_PIEZOS; ++i) {
		if (piezos[i].enabled) {
			printf("[rpiezos] piezo %d enabled, range %d-%d\n", i,
			       piezos[i].min, piezos[i].max);
		}
	}

	// --------- signal handling -------------
	if (signal(SIGINT, quit_handler) == SIG_ERR) {
		printf("[rpiezos] Error on assigning signal handler\n");
		return 2;
	}
	if (signal(SIGTERM, quit_handler) == SIG_ERR) {
		printf("[rpiezos] Error on assigning signal handler\n");
		return 2;
	}

	// --------- mcp init --------------------
	int spi_fd;
	unsigned char mode;
	unsigned char bits_per_word;
	unsigned speed;

	spi_fd = open("/dev/spidev0.0", O_RDWR);
	if (spi_fd < 0) {
		printf("[rpiezos] Error! opening spi device failed\n");
		return 1;
	}

	mode = SPI_MODE_0;
	bits_per_word = 8;
	speed = 1000000;

	rc = ioctl(spi_fd, SPI_IOC_WR_MODE, &mode);
	if (rc != 0) {
		printf("[rpiezos] Error! ioctl failed, cound not set SPI mode (WR)\n");
		return rc;
	}
	rc = ioctl(spi_fd, SPI_IOC_RD_MODE, &mode);
	if (rc != 0) {
		printf("[rpiezos] Error! ioctl failed, cound not set SPI mode (RD)\n");
		return rc;
	}
	rc = ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &bits_per_word);
	if (rc != 0) {
		printf("[rpiezos] Error! ioctl failed, cound not set SPI bits per word (WR)\n");
		return rc;
	}
	rc = ioctl(spi_fd, SPI_IOC_RD_BITS_PER_WORD, &bits_per_word);
	if (rc != 0) {
		printf("[rpiezos] Error! ioctl failed, cound not set SPI bits per word (RD) \n");
		return rc;
	}
	rc = ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
	if (rc != 0) {
		printf("[rpiezos] Error! ioctl failed, cound not set SPI speed (WR) \n");
		return rc;
	}
	rc = ioctl(spi_fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
	if (rc != 0) {
		printf("[rpiezos] Error! ioctl failed, cound not set SPI speed (RD) \n");
		return rc;
	}

	// --------- osc client setup -------------
	lo_address dest = lo_address_new(config.ip, config.port);

	// --------- main loop --------------------
	while (running) {
		for (unsigned p = 0; p < NUM_PIEZOS; ++p) {

			unsigned char data[3];
			data[0] = 1;
			data[1] = (unsigned char)(0b10000000 | (((p & 7) << 4)));
			data[2] = 0;

			struct spi_ioc_transfer spi[3];
			int i = 0;
			int ret_val = -1;
			memset(spi, 0, sizeof(spi)); // ioctl struct must be zeroed
			for (i = 0; i < 3; i++) {
				spi[i].tx_buf = (unsigned long)(data + i); // transmit from "data"
				spi[i].rx_buf = (unsigned long)(data + i); // receive into "data"
				spi[i].len = sizeof(data[i]);
				spi[i].delay_usecs = 0;
				spi[i].speed_hz = speed;
				spi[i].bits_per_word = bits_per_word;
				spi[i].cs_change = 0;
			}

			ret_val = ioctl(spi_fd, SPI_IOC_MESSAGE(3), &spi);
			if (ret_val < 0) {
				printf("[rpiezos] Error! ioctl failed durint spi data transfer\n");
				return 1;
			}

			int a2d_val = 0;
			a2d_val = (data[1] << 8) & 0b1100000000;
			a2d_val |= (data[2] & 0xff);

			if (piezos[p].enabled) {
				if (a2d_val >= piezos[p].min &&
				    a2d_val <= piezos[p].max &&
				    a2d_val != piezos[p].z1) {
					lo_send(dest, config.address, "ii", p, a2d_val);
					piezos[p].z1 = a2d_val;
					if (config.verbose) {
						printf("[rpiezos] n %d = %d\n", p, a2d_val);
					}
				}
			}
		}
		usec_sleep(500);
	}

	return 0;
}

int args_are_not_valid(int argc, char *argv[])
{
	(void)argc;
	(void)argv;
	return 0;
}

void quit_handler(int signo)
{
	if (signo == SIGINT || signo == SIGTERM) {
		printf("\nreceived SIGINT or SIGTERM, quitting...\n");
		running = 0;
	}
}

void usec_sleep(long int usecs)
{
	struct timespec tim, tim2;
	tim.tv_sec = 0;
	tim.tv_nsec = usecs * 1000;
	nanosleep(&tim, &tim2);
}

int parse_piezo(char *arg, piezo_t *piezos)
{
	unsigned len = strlen(arg);
	int mode = 0;
	int min = 0;
	int max = 1024;
	for (int i = 0; i < NUM_PIEZOS; ++i) {
		piezos[i].mark = 0;
	}

	for (unsigned i = 0; i < len; ++i) {
		int c = (int)arg[i];
		switch (mode) {
		case 0:
			if (c >= '0' && c <= '5') {
				int p = c - '0';
				piezos[p].mark = 1;
			}
			if (c == ':') {
				mode = 1;
				min = 0;
			}
			break;
		case 1:
			if (c >= '0' && c <= '9') {
				int n = c - '0';
				min = n + min * 10;
			}
			if (c == ':') {
				mode = 2;
				max = 0;
			}
		case 2:
			if (c >= '0' && c <= '9') {
				int n = c - '0';
				max = n + max * 10;
			}
			if (c == ':') {
				mode = 2;
			}
		}
	}

	if (min >= 1024) {
		printf("[rpiezos] min value should be lower than 1024");
		return 1;
	}
	if (max > 1024) {
		printf("[rpiezos] min value should not be greater than 1024");
		return 1;
	}

	for (int i = 0; i < NUM_PIEZOS; ++i) {
		if (piezos[i].mark) {
			piezos[i].enabled = 1;
			piezos[i].min = min;
			piezos[i].max = max;
		}
	}

	return 0;
}

int parse_args(int argc, char *argv[], piezo_t *piezos, config_t *config)
{
	int has_port = 0;
	int has_piezos = 0;
	int c;
	for (c = 0; c < argc; ++c) {
		if (argv[c][0] == '-') {
			switch (argv[c][1]) {
			case 'i':
				if (c < argc - 1) {
					config->ip = argv[c + 1];
					c++;
				}
				break;
			case 'p':
				if (c < argc - 1) {
					config->port = argv[c + 1];
					has_port = 1;
					c++;
				}
				break;
			case 'a':
				if (c < argc - 1) {
					config->address = argv[c + 1];
					c++;
				}
				break;
			case 's':
				if (c < argc - 1) {
					int rc = parse_piezo(argv[c + 1], piezos);
					if (rc != 0) {
						return rc;
					}
					has_piezos = 1;
					c++;
				}
				break;
			case 'v':
				config->verbose = 1;
				break;
			default:
				break;
			}
		}
	}

	if (!has_port) {
		printf("[rpiezos] destination port not given! use the -p argument to set it\n");
		return 1;
	}
	if (!has_piezos) {
		printf("[rpiezos] no piezo sensor activated, use -s to set it\n"
		       "          for example -s 124 to activate sensors 1, 2 and 4\n");
		return 1;
	}

	return 0;
}