117 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			117 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // Copyright 2022 Stefan Kerkmann
 | |
| // Copyright 2020 Jack Humbert
 | |
| // Copyright 2020 JohSchneider
 | |
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| 
 | |
| // Audio Driver: PWM the duty-cycle is always kept at 50%, and the pwm-period is
 | |
| // adjusted to match the frequency of a note to be played back. This driver uses
 | |
| // the chibios-PWM system to produce a square-wave on specific output pins that
 | |
| // are connected to the PWM hardware. The hardware directly toggles the pin via
 | |
| // its alternate function. see your MCUs data-sheet for which pin can be driven
 | |
| // by what timer - looking for TIMx_CHy and the corresponding alternate
 | |
| // function.
 | |
| 
 | |
| #include "audio.h"
 | |
| #include "ch.h"
 | |
| #include "hal.h"
 | |
| 
 | |
| #if !defined(AUDIO_PIN)
 | |
| #    error "Audio feature enabled, but no pin selected - see docs/feature_audio under the ARM PWM settings"
 | |
| #endif
 | |
| 
 | |
| #if !defined(AUDIO_PWM_COUNTER_FREQUENCY)
 | |
| #    define AUDIO_PWM_COUNTER_FREQUENCY 100000
 | |
| #endif
 | |
| 
 | |
| extern bool    playing_note;
 | |
| extern bool    playing_melody;
 | |
| extern uint8_t note_timbre;
 | |
| 
 | |
| static PWMConfig pwmCFG = {.frequency = AUDIO_PWM_COUNTER_FREQUENCY, /* PWM clock frequency  */
 | |
|                            .period    = 2,
 | |
|                            .callback  = NULL,
 | |
|                            .channels  = {[(AUDIO_PWM_CHANNEL - 1)] = {.mode = PWM_OUTPUT_ACTIVE_HIGH, .callback = NULL}}};
 | |
| 
 | |
| static float channel_1_frequency = 0.0f;
 | |
| 
 | |
| void channel_1_set_frequency(float freq) {
 | |
|     channel_1_frequency = freq;
 | |
| 
 | |
|     if (freq <= 0.0) {
 | |
|         // a pause/rest has freq=0
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     pwmcnt_t period = (pwmCFG.frequency / freq);
 | |
|     chSysLockFromISR();
 | |
|     pwmChangePeriodI(&AUDIO_PWM_DRIVER, period);
 | |
|     pwmEnableChannelI(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1,
 | |
|                       // adjust the duty-cycle so that the output is for 'note_timbre' duration HIGH
 | |
|                       PWM_PERCENTAGE_TO_WIDTH(&AUDIO_PWM_DRIVER, (100 - note_timbre) * 100));
 | |
|     chSysUnlockFromISR();
 | |
| }
 | |
| 
 | |
| float channel_1_get_frequency(void) {
 | |
|     return channel_1_frequency;
 | |
| }
 | |
| 
 | |
| void channel_1_start(void) {
 | |
|     pwmStop(&AUDIO_PWM_DRIVER);
 | |
|     pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
 | |
| }
 | |
| 
 | |
| void channel_1_stop(void) {
 | |
|     pwmStop(&AUDIO_PWM_DRIVER);
 | |
| }
 | |
| 
 | |
| static virtual_timer_t audio_vt;
 | |
| static void            audio_callback(virtual_timer_t *vtp, void *p);
 | |
| 
 | |
| // a regular timer task, that checks the note to be currently played and updates
 | |
| // the pwm to output that frequency.
 | |
| static void audio_callback(virtual_timer_t *vtp, void *p) {
 | |
|     float freq; // TODO: freq_alt
 | |
| 
 | |
|     if (audio_update_state()) {
 | |
|         freq = audio_get_processed_frequency(0); // freq_alt would be index=1
 | |
|         channel_1_set_frequency(freq);
 | |
|     }
 | |
| 
 | |
|     chSysLockFromISR();
 | |
|     chVTSetI(&audio_vt, TIME_MS2I(16), audio_callback, NULL);
 | |
|     chSysUnlockFromISR();
 | |
| }
 | |
| 
 | |
| void audio_driver_initialize(void) {
 | |
|     pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
 | |
| 
 | |
|     // connect the AUDIO_PIN to the PWM hardware
 | |
| #if defined(USE_GPIOV1) // STM32F103C8, RP2040
 | |
|     palSetLineMode(AUDIO_PIN, AUDIO_PWM_PAL_MODE);
 | |
| #else // GPIOv2 (or GPIOv3 for f4xx, which is the same/compatible at this command)
 | |
|     palSetLineMode(AUDIO_PIN, PAL_MODE_ALTERNATE(AUDIO_PWM_PAL_MODE));
 | |
| #endif
 | |
| 
 | |
|     chVTObjectInit(&audio_vt);
 | |
| }
 | |
| 
 | |
| void audio_driver_start(void) {
 | |
|     channel_1_stop();
 | |
|     channel_1_start();
 | |
| 
 | |
|     if ((playing_note || playing_melody) && !chVTIsArmed(&audio_vt)) {
 | |
|         // a whole note is one beat, which is - per definition in
 | |
|         // musical_notes.h - set to 64 the longest note is
 | |
|         // BREAVE_DOT=128+64=192, the shortest SIXTEENTH=4 the tempo (which
 | |
|         // might vary!) is in bpm (beats per minute) therefore: if the timer
 | |
|         // ticks away at 64Hz (~16.6ms) audio_update_state is called just often
 | |
|         // enough to not miss any notes
 | |
|         chVTSet(&audio_vt, TIME_MS2I(16), audio_callback, NULL);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void audio_driver_stop(void) {
 | |
|     channel_1_stop();
 | |
|     chVTReset(&audio_vt);
 | |
| }
 |