Skyduino:~#
Articles
arduino, programmation, python

[Arduino w/ python] Streamer un fichier via le port série

Bonjour tout le monde !

Cet article vient en complément de mon article d’hier sur mon lecteur midi C++.

Pour ceux qui on regardé le code générique vous avez dû remarquer qu’il y avait un certain nombre d’instructions de debug.

Or lors du debug du parseur en version arduino, l’utilisation d’une carte SD était assez inadéquate, le but étant de vérifier le bon fonctionnement du système en mode pas à pas.
C’est pourquoi je n’ai pas utilisé une carte SD comme source de données, mais le port série avec un script maison.

Le principe est simple, côté ordinateur j’ai un script python qui stream un fichier de mon choix et côté arduino des fonctions contrôlant le streaming.

Je publie mon code source brut de coffre, il pourrait être utile à d’autres personnes, on ne sait jamais 😉
Je tient à préciser que streamer un fichier de cette manière est très lent, mais mon but n’était pas de tester la justesse des timings mais le bon fonctionnement du parseur.

Bref voila le code arduino :

/* Includes */
#include <SoftwareSerial.h>
#include "GenericMidiParser.h"

SoftwareSerial synth(A0, A1);

#define FILE_READ 'R'
#define FILE_SEEK 'S'
#define FILE_TELL 'T'
#define FILE_EOF 'E'
#define DEBUG_MSG 'D'

/* Low-level functions */
uint8_t file_read_fnct(void) {
  Serial.write(FILE_READ);
  while(!Serial.available());
  return Serial.read();
}

void file_fseek_fnct(uint32_t address) {
  Serial.write(FILE_SEEK);
  Serial.write(address & 0xFF);
  Serial.write((address & 0xFF00) >> 8);
  Serial.write((address & 0xFF0000) >> 16);
  Serial.write((address & 0xFF000000) >> 24);
}

uint32_t file_ftell_fnct(void) {
  Serial.write(FILE_TELL);
  while(Serial.available() < 4);
  return ((uint32_t)Serial.read() << 24) | ((uint32_t)Serial.read() << 16) | ((uint32_t)Serial.read() << 8) | Serial.read();
}

uint8_t file_eof_fnct(void) {
  Serial.write(FILE_EOF);
  while(!Serial.available());
  return Serial.read();
}

void us_delay_fnct(uint32_t us) {
  delay(us / 1000);
}

void assert_error_callback(uint8_t errorCode) {
  digitalWrite(13, HIGH);
  Serial.write(DEBUG_MSG);
  Serial.print("Error ");
  Serial.println(errorCode, HEX);
}

GenericMidiParser midi(file_read_fnct, file_fseek_fnct, file_ftell_fnct, file_eof_fnct, us_delay_fnct, assert_error_callback);

/* Callback function */
void note_on_callback(uint8_t channel, uint8_t key, uint8_t velocity) {
  synth.write(0x90 | channel);
  synth.write(key);
  synth.write(velocity);
}

void note_off_callback(uint8_t channel, uint8_t key, uint8_t velocity) {
  synth.write(0x80 | channel);
  synth.write(key);
  synth.write(velocity);
}

void patch_change_callback(uint8_t channel, uint8_t instrument) {
  synth.write(0xC0 | channel);
  synth.write(instrument);
}

void setup()
{
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);

  Serial.begin(115200);
  synth.begin(31250);

  midi.setNoteOnCallback(note_on_callback);
  midi.setNoteOffCallback(note_off_callback);
  midi.setPatchChangeCallback(patch_change_callback);

  midi.play();
}

void loop()
{

}

Et le script python

# Imports
import serial, sys, struct

# Serial Commands
FILE_READ = 'R'
FILE_SEEK = 'S'
FILE_TELL = 'T'
FILE_EOF = 'E'
DEBUG_MSG = 'D'

# Args Check
if len(sys.argv) < 3:
    print "USAGE %s: <serial port> <file name> [<baud rate>]" % sys.argv[0]
    exit(1)

# Open Serial Port
if len(sys.argv) == 4:
    ser = serial.Serial(sys.argv[1], sys.argv[3], timeout = 1)
else:
    ser = serial.Serial(sys.argv[1], 115200, timeout = 1)

# Open File
fi = open(sys.argv[2], 'r+b')

# Process remote commands
try:
    while(1):
        command = ser.read(1)

        if command == DEBUG_MSG:
            print "DBG : %s" % ser.readline()

        if command == FILE_READ:
            ser.write(fi.read(1))

        if command == FILE_SEEK:
            fi.seek(struct.unpack('<I', ser.read(4))[0])

        if command == FILE_TELL:
            ser.write(struct.pack('>I', fi.tell()))

        if command == FILE_EOF:
            ser.write(chr(0))

except KeyboardInterrupt:
    # Close ressource
    fi.close()
    ser.close()

Et pour les amoureux du C :
(Pour windows avec la librairie : https://github.com/waynix/SPinGW/blob/master/serialport.h)

#include <windows.h>
#include <stdio.h>
#include "serialport.h"

// Serial Commands
#define FILE_READ 'R'
#define FILE_SEEK 'S'
#define FILE_TELL 'T'
#define FILE_EOF 'E'
#define DEBUG_MSG 'D'

int main(int argc, char *argv[]) {
	HANDLE ser;
	FILE *fi;
	
    // Args Check
    if(argc < 3) {
        printf("USAGE %s: <serial port> <file name> [<baud rate>]", argv[0]);
        return 1;
	}
	
	// Open Serial Port
	if(argc == 4)
	    ser = openSerialPort(argv[1], atoi(argv[3]), one, off);
	else
	    ser = openSerialPort(argv[1], 115200, one, off);
	
	// Open File
    fi = fopen(argv[2], "r+b");
	if(fi == NULL) {
        puts("Open file error");
		return 1;
	}

	for(;;) {
		unsigned char command;
		if(readFromSerialPort(ser, &command, 1) != 1)
		    continue;
		
		if(command == DEBUG_MSG){
			char c;
			//putchar('\n');
			puts("DBG :");
			readFromSerialPort(ser, &c, 1);
			while(c != '\n') {
                putchar(c);
			    readFromSerialPort(ser, &c, 1);
			}
			putchar(c);
		}

        if(command == FILE_READ) {
			char c;
			fread(&c, 1, 1, fi);
			//printf("R:%x ", c);
            writeToSerialPort(ser, &c, 1);
		}

        if(command == FILE_SEEK) {
			unsigned char buf[4];
			readFromSerialPort(ser, buf, 4);
			unsigned int address = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
			//printf("S:%d ", address);
			fseek(fi, address, SEEK_SET);
		}

        if(command == FILE_TELL) {
			unsigned int address = ftell(fi);
			//printf("T:%d ", address);
			unsigned char buf[4];
			buf[0] = (address & 0xFF000000) >> 24;
			buf[1] = (address & 0xFF0000) >> 16;
			buf[2] = (address & 0xFF00) >> 8;
			buf[3] = address & 0xFF;
            writeToSerialPort(ser, buf, 4);
		}

        if(command == FILE_EOF) {
			char c = feof(fi);
            writeToSerialPort(ser, &c, 1);
		}
	}
	
	fclose(fi); // Fermeture du prog avec SIGINT, c'est pas propre mais osef
    closeSerialPort(ser);

    return 0;
}

Discussion

2 réflexions sur “[Arduino w/ python] Streamer un fichier via le port série

  1. Pas mal du tout, l’idée d’utiliser des fichiers du pc depuis l’arduino est super intéressante.

    Par contre en python, quelques conseils:
    – fais une fonction avec ton code et appelle la avec « if _name__== « __main__ » », comme ca, tu peux décomposer ton code et en faire une lib appelable depuis d’autres scripts 🙂
    – pour les arguments en ligne de commande, pas besoin de t’embeter à faire un « usage » etc, tu peux instancier un ArgumentParser (module « argparse ») qui fera le boulot et gérera même les arguments optionnels etc.
    – le try/except keyboard est pas assez général et prend pas tout en compte… du coup il vaut mieux utiliser « with » (« from future import with_statement » avant python 2.6), ca ferme les trucs ouverts en cas de fermeture 🙂

    Publié par Jonathan de HackSpark | 20 avril 2012, 19 h 12 min
    • >> – fais une fonction avec ton code et appelle la avec “if _name__== “__main__” », comme ca, tu peux
      >> décomposer ton code et en faire une lib appelable depuis d’autres scripts
      Le __main__ je l’utilise que quand je fait des classes, sinon je n’y vois aucun intérêt perso.

      >> – pour les arguments en ligne de commande, pas besoin de t’embeter à faire un “usage” etc, tu peux
      >> instancier un ArgumentParser (module “argparse”) qui fera le boulot et gérera même les arguments
      >> optionnels etc.
      Je suis un amoureux du C/C++, j’aime avoir mon argc et argv 🙂
      Du reste le code python avec pyserial était tellement lent que ça ma soulé et que j’ai codé une version en C 🙂

      >> – le try/except keyboard est pas assez général et prend pas tout en compte… du coup il vaut mieux utiliser
      >> “with” (“from future import with_statement” avant python 2.6), ca ferme les trucs ouverts en cas de fermeture
      Le try c’est juste pour fermer le prog en ctrl+c, je me contre fou royalement de gérer les erreurs ^^
      C’est qu’un script codé en 5 minutes pour faire mes test.

      Pour le « with » je vois pas vraiment comment tu l’utiliserais pour récupérer le KeyboardInterrupt ?
      J’utilise with avec des objets / fichiers / etc .. mais pour de la gestion d’erreur je vois pas …

      Publié par skywodd | 20 avril 2012, 20 h 15 min

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

Skyduino devient Carnet du Maker

Le site Carnet du Maker remplace désormais le blog Skyduino pour tout ce qui touche à l'Arduino, l'informatique et au DIY.