donutsorelse
Created November 4, 2023

PSoC Theremin

A code driven solution to utilize the PSoC's built in capsense capabilities to turn it into a Theremin-type musical instrument via PC USB.

21
PSoC Theremin

Things used in this project

Hardware components

Infineon CY8CPROTO-062-4343W
×1

Software apps and online services

ModusToolbox™ Software
Infineon ModusToolbox™ Software
Audacity

Story

Read more

Code

The PSoC Theremin

C/C++
/***code by donutsorelse for Hackster Connect Things with Code contest***/
#include "cybsp.h"
#include "cyhal.h"
#include "cycfg.h"
#include "cycfg_capsense.h"
#include "led.h"
#include "cy_retarget_io.h"
#include "rtos.h"
#include "audio_app.h"
#include "cy_capsense_lib.h"

// Define the min and max frequency as per your Theremin settings
#define MIN_FREQUENCY 220
#define MAX_FREQUENCY 880
#define MAX_SLIDER_VALUE 100

void StartTone(uint32_t frequency);
void StopTone(void);
// This function maps slider position to frequency
uint32_t MapSliderPositionToFrequency(uint16_t position);

#define TONE_TIMER_CLOCK_HZ   (SystemCoreClock) // Adjust as needed to get accurate tones
#define TONE_TIMER_MAX_PERIOD (0xFFFF) // Maximum period for 16-bit timer

/* Add global declaration for capsense_scan_complete flag */
volatile bool capsense_scan_complete = false; // Flag indicating Capsense scan is complete

/* Forward declaration of the TouchTask function */
void TouchTask(void *arg);

// Add definitions for tone generation
cyhal_timer_t tone_timer;
bool toneActive = false; // Flag to control tone generation

// Forward declaration for the ISR
static void isr_tone_timer(void *callback_arg, cyhal_timer_event_t event);


#define TONE_GEN_PIN P13_7 // You don't need to plug anything in here

#define TONE_GEN_PORT GPIO_PRT13 


// Function to map slider position to frequency
uint32_t MapSliderPositionToFrequency(uint16_t position) {
    if (position > MAX_SLIDER_VALUE) {
        position = MAX_SLIDER_VALUE; // Cap the position to the maximum slider value if out of bounds
    }
    // Convert the slider position to a value between 0 and 1, then scale to frequency range
    float positionRatio = (float)position / (float)MAX_SLIDER_VALUE;
    float frequencyRange = (float)(MAX_FREQUENCY - MIN_FREQUENCY);
    return (uint32_t)(positionRatio * frequencyRange + MIN_FREQUENCY);
}

/* Main function */
int main(void) {
    cy_rslt_t result;

    /* Initialize the device and board peripherals */
    result = cybsp_init();
    cyhal_gpio_init(TONE_GEN_PIN, CYHAL_GPIO_DIR_OUTPUT, CYHAL_GPIO_DRIVE_STRONG, 0); // Initializes the pin as a strong drive output, starts at logic low.

    if (result != CY_RSLT_SUCCESS) {
        CY_ASSERT(0); // Halt on initialization failure
    }

    /* Enable global interrupts */
    __enable_irq();

    /* Initialize retarget-io to use the debug UART port */
    result = cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX, CY_RETARGET_IO_BAUDRATE);
    if (result != CY_RSLT_SUCCESS) {
        CY_ASSERT(0); // Halt on initialization failure
    }

    /* Clear screen and print startup message */
    printf("\x1b[2J\x1b[;H"); // ANSI ESC sequence for clear screen
    printf("****************** emUSB-Device: Audio recorder ******************\r\n\n");

    /* ... Capsense and other initialization code ... */

    /* Initialize the audio application */
    audio_app_init();

    /* Create the touch sensing task */
    xTaskCreate(TouchTask, "Touch Task", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL);

    /* Start the RTOS scheduler */
    vTaskStartScheduler();

    /* Infinite loop to keep main function running */
    for (;;) {
        // Should not reach here, FreeRTOS scheduler takes over
    }

    return 0; // Redundant return for the main function
}

void TouchTask(void *arg) {
    (void)arg; // Avoid compiler warning about unused parameter

    for(;;) {
        if (capsense_scan_complete) {
        	printf("Capsense scan %s.\n", capsense_scan_complete ? "complete" : "pending");
        	Cy_CapSense_ProcessAllWidgets(&cy_capsense_context); // Process all widgets after scan completion
            Cy_CapSense_RunTuner(&cy_capsense_context); // Optional: Run tuner for debugging

            cy_stc_capsense_touch_t *sliderTouchInfo = Cy_CapSense_GetTouchInfo(CY_CAPSENSE_LINEARSLIDER0_WDGT_ID, &cy_capsense_context);

            if(sliderTouchInfo->numPosition > 0) { // Check if the slider is touched
                uint16_t sliderPosition = sliderTouchInfo->ptrPosition->x; // Get the slider touch position
                printf("Slider touched at position: %d\n", sliderPosition);
                uint32_t frequency = MapSliderPositionToFrequency(sliderPosition);
                StartTone(frequency); // Start or update tone generation with the mapped frequency
                printf("Frequency to start: %lu Hz\n", frequency);
            } else {
                StopTone(); // Stop tone generation when no touch is detected
            }

            Cy_CapSense_UpdateAllBaselines(&cy_capsense_context); // Update the baseline for next scan
            Cy_CapSense_ScanAllWidgets(&cy_capsense_context); // Start next scan
            capsense_scan_complete = false;
        }

        vTaskDelay(pdMS_TO_TICKS(10)); // Delay to allow other tasks to run
    }
}

// Function to initialize tone generation timer
void tone_timer_init(void) {
    const cyhal_timer_cfg_t tone_timer_cfg = {
        .compare_value = 0,                // Not used
        .period = TONE_TIMER_MAX_PERIOD,   // Set to maximum period initially
        .direction = CYHAL_TIMER_DIR_UP,   // Timer counts up
        .is_compare = false,               // Don't use compare mode
        .is_continuous = true,             // Run timer indefinitely
        .value = 0                         // Initial value of counter
    };

    cy_rslt_t result = cyhal_timer_init(&tone_timer, NC, NULL);
    CY_ASSERT(result == CY_RSLT_SUCCESS);

    cyhal_timer_configure(&tone_timer, &tone_timer_cfg);
    cyhal_timer_set_frequency(&tone_timer, TONE_TIMER_CLOCK_HZ);
    cyhal_timer_register_callback(&tone_timer, isr_tone_timer, NULL);
    cyhal_timer_enable_event(&tone_timer, CYHAL_TIMER_IRQ_TERMINAL_COUNT, 7, true);
    // Note: Do not start the timer here; it will be started when a tone is needed
}

// ISR for tone timer
static void isr_tone_timer(void *callback_arg, cyhal_timer_event_t event) {
    // Check if tone generation is active
    if (toneActive) {
        // Toggle the pin configured for tone output
        cyhal_gpio_toggle(CYBSP_USER_LED); // Replace with the actual pin to be used for tone output
    }
}

void StartTone(uint32_t frequency) {
    // Calculate the timer period corresponding to the half period of the desired tone frequency
    uint32_t timerPeriod = (TONE_TIMER_CLOCK_HZ / frequency / 2) - 1;

    // Stop the timer before re-configuring
    cyhal_timer_stop(&tone_timer);
    printf("Starting tone at %lu Hz with a timer period of %lu.\n", frequency, timerPeriod);

    // Set up the timer configuration structure with the new period
    const cyhal_timer_cfg_t tone_timer_cfg = {
        .compare_value = 0,                // Not used
        .period = timerPeriod,             // Set to the calculated period
        .direction = CYHAL_TIMER_DIR_UP,   // Timer counts up
        .is_compare = false,               // Don't use compare mode
        .is_continuous = true,             // Run timer indefinitely
        .value = 0                         // Initial value of counter
    };

    // Apply the new configuration - effectively changing the period
    cy_rslt_t result = cyhal_timer_configure(&tone_timer, &tone_timer_cfg);
    if (result != CY_RSLT_SUCCESS) {
        printf("Error configuring timer with period %lu for frequency %lu Hz.\n", timerPeriod, frequency);
    } else {
        printf("Timer configured successfully for frequency %lu Hz with a period of %lu.\n", frequency, timerPeriod);
    }


    // Start the timer with the new period
    cyhal_timer_start(&tone_timer);

    toneActive = true; // Set the tone active flag to start tone generation in the ISR
}

void StopTone(void) {
    cyhal_timer_stop(&tone_timer); // Stop the timer
    toneActive = false; // Disable tone generation
    // Ensure the pin is low after stopping the tone
    cyhal_gpio_write(CYBSP_USER_LED, 0);
}

Credits

donutsorelse

donutsorelse

16 projects • 16 followers
I make different stuff every week of all kinds. Usually I make funny yet useful inventions.

Comments