CH32V003 : mini-orgue

Ce que vous allez faire

Je vous avais dit que le travail que vous avez fait sur le clavier matriciel resservirait, le moment est venu. :) Jusqu'à maintenant, nous avons fait varier le rapport cyclique de notre signal PWM, nous allons maintenant faire varier sa fréquence.

Vous allez réaliser un mini-orgue avec les consignes suivantes :

  • Les touches numériques du clavier correspondent à 12 notes successives.

  • Une note est jouée tant que sa touche est appuyée. Si aucune touche numérique n'est appuyée, on arrête le compteur.

  • Une seule touche peut être appuyée à la fois (on prend la première trouvée dans l'ordre du balayage), donc pas de polyphonie.

  • Vous trouverez les fréquences des notes sur cette page Wikipedia.

  • Vous relierez la sortie PWM que vous aurez choisie au circuit suivant pour pouvoir entendre vos notes :

Pour changer sa valeur, vous aurez besoin d'accéder directement au registre du prescaler en écrivant TIMx->PSC. Si vous regardez son code source, c'est ce que fait la fonction TIM_TimeBaseInit(), par exemple.

La modification de la valeur du prescaler n'est prise en compte que si le compteur est en fonction, c'est-à-dire que TIM_Cmd(TIMx, ENABLE) a été appelée.

Au cas où vous auriez galéré pour gérer le clavier matriciel, je vous propose une solution minimale ci-dessous pour que vous puissiez vous concentrer sur l'utilisation du timer :

#include <ch32v00x.h>
#include <debug.h>

static const char keys[][3] = {
	{ '1', '2', '3', },
	{ '4', '5', '6', },
	{ '7', '8', '9', },
	{ '*', '0', '#', },
};

int main() {
	USART_Printf_Init(115200);

	// On initialise le GPIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	GPIO_InitTypeDef gpioInit = { 0 };
	// PC0..3 correspondent aux rangées (entrées), de haut en bas.
	gpioInit.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	gpioInit.GPIO_Mode = GPIO_Mode_IPD;
	GPIO_Init(GPIOC, &gpioInit);
	// PC5..7 correspondent aux colonnes (sorties), de gauche à droite.
	gpioInit.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
	gpioInit.GPIO_Mode = GPIO_Mode_Out_PP;
	gpioInit.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &gpioInit);

	int currentColumn = 0;

	while (1) {
		// Le principe est d'envoyer un niveau logique haut alternativement
		// sur chaque sortie. Si on retrouve ce niveau sur une entrée, c'est
		// que la touche correspondante est appuyée.
		switch (currentColumn) {
		case 0:
			GPIO_Write(GPIOC, 0x20);
			break;

		case 1:
			GPIO_Write(GPIOC, 0x40);
			break;

		case 2:
			GPIO_Write(GPIOC, 0x80);
			break;
		}

		// La lecture du port renvoie l'état de toutes les lignes,
		// donc sorties comprises.
		int key = GPIO_ReadInputData(GPIOC);

		// On regarde si on trouve un niveau logique haut sur une des entrées.
		if (key & 0x0f) {
			// On recherche la position du "1" dans les entrées pour trouver
			// la rangée.
			int row = 0;
			for (int v = key; !(v & 1); v >>= 1, row++);
			// On recherche la position du "1" dans les sorties pour trouver
			// la colonne, en ignorant PC4 que nous n'utilisons pas.
			int column = 0;
			for (int v = key; !(v & 0x20); v >>= 1, column++);
			// On affiche rangée, colonne et caractère correspondant.
			printf("rangee = %d, colonne = %d, touche = %c\r\n", row, column, keys[row][column]);
		}

		// On passe à la colonne suivante.
		currentColumn++;

		if (currentColumn > 2) {
			currentColumn = 0;
		}
	}
}

Copyright (c) 2024 Vincent DEFERT - Tous droits réservés