Lecteur MP3 à base de PIC32MX

From Eric

Jump to: navigation, search

Contents

Objectifs

Réaliser un lecteur MP3 sur la base d'un décodeur hard, utiliser un écran graphique tactile, utiliser un PIC32.

Réalisation

L'unité de traitement

Un PIC32 est utilisé comme unité centrale. J'utilise une version vendue sur plaquette (30$). C'est cher mais ça m'évite les soucis de soudage de CMS (et ça me permet d'avoir les capas de découplage au plus près du chip).

Chip pic32.jpg
.

J'utilise un clone chinois du Pickit3 pour programmer le chip et debuguer mes programmes in-situ. Coût de la chose : une trentaine de dollars :

Tool pickit3.jpg
.

Le debugging in-situ fonctionne assez bien pour ce qui concerne la mise en oeuvre de points d'arrêt ; ça ne marche pas très bien pour ce qui concerne la visualisation des variables (parfois ça marche, parfois pas, et quand ça fonctionne, les capacités de navigation dans l'ensemble des variables est très très réduite [ou bien je ne sais pas l'utiliser...]).

L'architecture matérielle

La carte ne présente aucune originalité : elle reprend les indications données dans les différentes datasheets.

Quelques extraits de la documentation permettent de comprendre la structure du montage.

Schéma d'utilisation du STA013 :

Mp3 sta013.jpg
.

Schéma d'utilisation du DAC (CS4334) :

Mp3 cs4334.jpg

Schéma d'utilisation de l'ampli de sortie (TDA1308):

Mp3 tda1308.jpg
.

L'architecture logicielle

Comme pour ma précédente carte à caméra OV7670, j'utilise l'OS temps-réel FreeRTOS.

L'application comprend trois tâches :

  • une tâche de gestion des dispositifs de saisie (pour l'instant, le seul dispositif est l'écran tactile)
  • une tâche de gestion de l'IHM
  • une tâche de gestion de la lecture MP3 et du tuner FM.

Ces tâches sont créées dans le programme principal (fichier "main.c").

Elles communiquent au moyen de deux files (définies dans le module "comm")

  • la file "hQVGAQueue" reçoit les messages relevant de l'IHM émis par la tâche d'IHM à destination d'elle-même,
  • la file "hPlayQueue" reçoit les messages relevant du contrôle de la lecture MP3 et du tuner FM ; ils sont émis par la tâche d'IHM.

La structure des messages est définie dans le fichier "comm.h" :

  • stucture "GRAPHICS_MSG pour les messages d'IHM
  • structure "PLAY_MSG" pour les messages de contrôle MP3/tuner

D'une manière générale, les tâches d'IHM et de décodage MP3/tuner attendent la réception d'un message pour réaliser les traitement afférents. Quelques détails supplémentaires sont donnés plus loin.

La gestion de l'écran

L'IHM est réalisée au moyen de la bibliothèque graphique de Microchip.

Driver d'écran

Quelques lignes de codes ont été nécessaires pour piloter l'écran ILI. Elles se trouvent dans les fichiers "Microchip\Graphics\Drivers\ILI9325P_16BIT.c/.h".

Le driver se contente d'initialiser le périphérique (fonction "ResetDevice") et d'implémenter quelques fonctions très simples ("PutPixel", "Bar",...). L'implémentation est actuellement très très peu performante car je ne suis pas parvenu à utiliser le mode PMP du PIC32MX, qui permet d'accéder de façon très simple à des périphériques se comportant comme une mémoire (en gros, ça émule le comportement des bus adresses/données d'un microprocesseur classique).

Pour l'instant, j'accède à l'écran en utilisant les opérations d'IOs standards. En outre, les bits du PMP étant "distribués" sur plusieurs ports, les bits d'adresse ne peuvent pas être transmis en une seule opération.

Ainsi, voici un extrait de la macro "SetIndex" (fichier "ILI9325P_16BIT.h") dont le rôle est de positionner l'adresse du prochain accès :

LATGbits.LATG0 =  (index & 0b0000000100000000) >> 8; \
LATGbits.LATG1 =  (index & 0b0000001000000000) >> 9; \
LATFbits.LATF1 =  (index & 0b0000010000000000) >> 10; \
LATFbits.LATF0 =  (index & 0b0000100000000000) >> 11; \
LATDbits.LATD12 = (index & 0b0001000000000000) >> 12; \
LATDbits.LATD13 = (index & 0b0010000000000000) >> 13; \
LATDbits.LATD6 =  (index & 0b0100000000000000) >> 14; \
LATDbits.LATD7 =  (index & 0b1000000000000000) >> 15; \

C'est éviedmment très inefficace! Il me faudra donc tôt ou tard regarder de plus près la gestion du PMP.

Utilisation de la bibliothèque graphique

Sans rentrer dans le détails, la gestion de l'IHM procède comme décrit ci-après.

L'IHM est gérée par la tâche "task_hmi" dont la structure est la suivante :

void task_hmi(void* pvParameter)
{
	// Variables de stockage des points de calibration.
	static unsigned short xc[2], yc[2];
	static GRAPHICS_MSG msg;
	GOL_MSG* pMsg;

	// Création des styles et démarrage du GOL.
	create_schemes();
	
	// Tâche IHM
	while (1) {
		// On met à jour l'écran.
		while (GOLDraw() != TRUE);
		
		// Puis on traite les éventuels messages en provenance de l'application.
		// Ces messages peuvent venir du touchscreen ou de toute autre tâche.
		if (xQueueReceive(hQVGAQueue, &msg, portMAX_DELAY) == pdTRUE) {
			// Traitement des messages
			switch (msg.cmd) {
				case <type du message>:
					<action>
					break;
				default:
					break;
			}
		}	
	}
}

Un exemple de traitement de message :

case MSG_CLICK_TITLE:
// Traitement du clic sur un titre
do_click_title();
break;

(Nota : la fonction "do_click_tiltle" est une fonction "utilisateur", non une fonction de la bibliothèque graphique.)

Les widgets sont créés par des fonctions dédiées à une page donnée. Voici par exemple la fonction de création de la page de configuration :

void create_screen_config(void)
{
	GOLFree();
	SetColor(BLACK);
	ClearDevice();
		
	// Création de la barre de titre
	BtnCreate(ID_MP3_BTN, 0, 0, GetMaxX()/3, 39, 0,
	   BTN_DRAW, NULL, "Music", greenScheme);
	BtnCreate(ID_RADIO_BTN, (2*GetMaxX())/3, 0, GetMaxX(), 39, 0,
	   BTN_DRAW, NULL, "Radio", blueScheme);
	BtnCreate(ID_CONFIG_BTN, GetMaxX()/3, 0, (2*GetMaxX())/3, 39, 0,
	   BTN_DRAW, NULL, "Config", redScheme);

	// Création du bouton de calibration
	BtnCreate(ID_CALIB_BTN, 0, 60, GetMaxX(), 99, 0,
	   BTN_DRAW , NULL, "Calibration", whiteScheme);

}

Cette page comprend quatre boutons.

Les fonctions de création des pages sont appelées par la fonction "GOLDrawCallBack". Cette fonction est elle-même appelée par le GOL (fonction "GOLDraw") une fois que ce dernier a fait ses propres mises à jour de l'écran.

Dans notre cas, la fonction "GOLDrawCallBack" a la structure suivante :

WORD GOLDrawCallback(void)
{
	// Traitement des messages de redessin en fonction de l'état de l'écran
	// (c'est-à-dire : de la page affichée).
	switch (screenState) {
		case CREATE_SCREEN_MP3:
			// Création de l'écran MP3
			create_screen_main();
			screenState = DISPLAY_SCREEN_MAIN;
			break;

		case CREATE_SCREEN_CONFIG:
			// Création de l'écran de configuration
			create_screen_config();
			screenState = DISPLAY_SCREEN_CONFIG;
			break;
[...]
		default:
			break;
	}
	
	return 1;	
}

Le traitement des événements (appui bouton,...) est quant à lui réalisé par la fonction "GOLMsgCallback" qui reçoit en paramètre l'objet concerné par le message et le message lui-même. Celle-ci détermine l'action à réaliser en fonction de l'élément concerné par le message et par le contenu de ce dernier...

WORD GOLMsgCallback(WORD objMsg, OBJ_HEADER* pObj, GOL_MSG* pMsg)
{
	static GRAPHICS_MSG msg;

	WORD objectID = GetObjID(pObj);
	switch (objectID ) 
	{
		case ID_MP3_BTN:
			screenState = CREATE_SCREEN_MP3;
			msg.cmd = MSG_UPDATE_DISPLAY;
			xQueueSend ( hQVGAQueue, &msg, 0); 	
			return 0;
			break;
[...]
		default:
			// Par défaut, on laisse GOL gérer l'objet
			return 1;	
		
	}
}

La gestion de la carte SD

La gestion de la carte SD est réalisé au moyen de la bibliothèque FatFs. Je n'ai eu qu'à configurer les quelques signaux d'accès à la SDCARD (bus SPI) pour que ça fonctionne, out-of-the-box.

Voici, par exemple, la page d'accueil sur laquelle on voit la liste des fichiers contenus dans la carte SD :

Mp3 lecture sdcard.jpg
.

Le décodeur MP3

Le décodage du flux MP3 est réalisé par le chip ST013 de STMicro et le convertisseur DAC CS4334 sur I2C. Un ampli TDA1308 permet de piloter les écouteurs (ou les hauts-parleurs actifs).

A noter que le signal en priovenance du tuner FM est simplement additionné au signal du lecteur MP3, en entrée de l'amplificateur (au travers d'une capacité de couplage).

Voici un gros plan de la carte de décompression MP3 et tuner FM :
Mp3-avec-tuner.jpg
.

Configuration

Le STA013 nécessite d'être configuré avant de pouvoir être utilisé. Cette configuration consiste à charger "certaines" valeurs dans "certains" registres du chip. La liste des paires (registre, valeur) est fournie par ST, sans explications.

En vérité, cette liste comprend deux parties, l'une très obscure et fixe, l'autre destinée à programmer la PLL et dont les valeur dépendent de la fréquence du quartz utilisé. Ces couples (adresse,valeur) sont donnés dans la datasheet du STA013 pour certaines fréquences d'horloge ; dans les autres cas, il faut utiliser le programme fourni par ST ("cpll.exe").

Le fichier "sta013_config.c' contient les deux séquences d'initialisation.

Décodage

Le décodage d'un fichier MP3 est on ne peut plus simple puisqu'il suffit d'en transmettre le contenu complet (y compris les tags) via la ligne SPI. Le contrôle de flux est réalisé par le STA013 au moyen d'une sortie dédiée. Cette sortie passe à 1 lorsque le STA requiert des données.

La boucle de décodage est la suivante :

static portTASK_FUNCTION( play_task, pvParameters )
{
	// Pour éviter les warnings...
	( void ) pvParameters;
	for(;;)
	{
		// Attente (bloquante) d'un message en provenance de la queue.
		if (xQueueReceive(hPlayQueue, &msg, portMAX_DELAY) == pdTRUE) {
			// Traitement des messages
			switch (msg.cmd) {
				case MSG_PLAY:
					// On coupe le tuner
					tea5767_mute_on();
					tea5767_send_config();
					// On joue le morceau...
					sta013_play(msg.data.filename);
					msg.cmd = 0;
					break;
[...] 
				default:
					break;
			}
		}
	}
} 

La fonction de décodage proprement dite est simplement :

void sta013_play( char* file_name) {
	FIL mp3_file;      	
	BYTE data;			// Buffer de transmission
	FRESULT res;       	// Code de retour des fonctions FatFs
	UINT br;         	// Nombre d'octets lus
	FATFS fatfs;

	// Reset du STA (on ne sait jamais...)
	reset();

	// Montage du système de fichiers
	f_mount(0, &fatfs);	

	res = f_open(&mp3_file,file_name, FA_OPEN_EXISTING | FA_READ);
	if (res) die(__FILE__, 111);

	abort_play = FALSE;	

	while (!abort_play) {
		// Lecture d'un octet
		res = f_read(&mp3_file, &data, sizeof(data), &br);
		if (res || br == 0) break;
		// Transmission de l'octet
		SpiChnPutC(MP3_SPI_CHANNEL, data);
		// On attend la disponibilité du chip.
		while ( MP3_DATA_REQ_IO	== 0 );
	}

	// Fermeture du fichier
	f_close(&mp3_file);
}

La radio FM

Le lecteur dispose d'une fonction radio FM, réalisée par le TEA5767 de Philips. Pour information, le chip est vendu sur une petite carte pour une bouchée de pain (moins de 3 euros en 2011!!!), la voici :

Mp3 radio.jpg
.

Voici la carte TEA5767 montée sur la carte décodeur :

Mp3-tuner.jpg

Ce chip est très puissant : il ne requiert que très peu de composants annexes et offre même une fonction de recherche automatique de stations. Pour ma part, je me suis contenté de définir une liste de stations FM (celles que l'on capte à Toulouse) et à la stocker sur la SDCARD (fichier "stations.txt").

Ce fichier contient une série ("nom de station", "donnée de configuration PLL", "nom de station", "donnée de configuration PLL",...). La donnée de configuration est calculée à partir de la fréquence à syntoniser conformément aux formules données dans la datasheet. (Voir feuille excel, "stations.xls").

Voilà un extrait du fichier de définition des stations :

France Inter
10782
RTL2
10855
France Culture
11075
France Musique
11148
Rire et Chansons
11551
Le Mouv'
11649
RFM	12125
Nostalgie
12173
Skyrock	
12234

A lire


Conclusions et leçons

De l'art de faire un monstrueux lecteur MP3 alors que ça coûte au plus 10 euros sur eBay... A noter que le chip VS1003 est plus simple à mettre en œuvre, pour peu que l'on parvienne à le souder correctement (j'ai essayé, sans succès)...