I wanted to test how the PSOC 4700S together with the coil board could work as a midi controller. It would have been convenient if the 4700S would have directly worked as a MIDI device through USB, but unfortunately the board doesn't support MIDI.
But what helps here is some software, which deals with the MIDI traffic between the board and the computer. What happens is the following:
1. The board sends midi data through the UART serial, the very same way the board can send text to be viewed in a terminal window in your computer. But instead of text, the board sends binary MIDI data. For an example, if the board wants the computer to play C4 on MIDI cannel 1, the sent bytes would be 0x90, 0x3C, 0x50. This is not text, no line endings, just three bytes. The speed of the serial is 115200 baud.
2. The computer runs one program called HairlessMIDI, which reads the serial port and transfers it further to MIDI software. It has a logical MIDI in and MIDI out, which further connects to the next software needed.
3. loopMIDI is a program, which acts as a MIDI port, which is visible for DAW programs on your computer. Set your DAW to read and write the MIDI stream from and to loopMIDI.
With this setup, proper binary MIDI data sent as serial data from the 4700S, will reach your DAW program.
Even though the MIDI standard requires a baud rate of 31250, this setup works nicely with 115200, which is what the UART baud rate is on the 4700S and the HairlessMIDI.
How the DAW reactsI have hardcoded the board to send control change messages on MIDI channel 4. The program reads both linear encoders on the coil board. LE1 creates a control change #1, which is the "modulation" (the upper knob), while LE2 creates a control change #7, which is the channel volume (the slider).
To be honest, the controls don't change smooth enough, and it would need a tremendous amount of testing and calibrating before this could turn into something very useful.
What I did in the softwareI modified the sample project "Linear Encoder", which originally kind of only outputted one slider value, whichever gave a stronger signal, LE1 or LE2. But I made the program output both.
Here's the modified code:
/******************************************************************************
* File Name: main.c
*
* Version 1.20
*
* Description: Demonstrates how to use the MagSense Component with a sensor coil
* that linearly decreases in sensor area. Uses coils LE1 and/or LE2 on the
* CY8CKIT-148-COIL kit board.
*
* Related Document: CE225409_PSoC4700S_MagSense_Examples.pdf
*
* Hardware Dependency: This project uses the CY8CKIT-148 kit board as well as the
* CY8CKIT-148-COIL kit. Connect the FPC connector from header J6 on the CY8CKIT-148
* to header J2 on the CY8CKIT-148-COIL board.
*
* *******************************************************************************
* Copyright (2019-2020), Cypress Semiconductor Corporation. All rights reserved.
*******************************************************************************
* This software, including source code, documentation and related materials
* (“Software”), is owned by Cypress Semiconductor Corporation or one of its
* subsidiaries (“Cypress”) and is protected by and subject to worldwide patent
* protection (United States and foreign), United States copyright laws and
* international treaty provisions. Therefore, you may use this Software only
* as provided in the license agreement accompanying the software package from
* which you obtained this Software (“EULA”).
*
* If no EULA applies, Cypress hereby grants you a personal, nonexclusive,
* non-transferable license to copy, modify, and compile the Software source
* code solely for use in connection with Cypress’s integrated circuit products.
* Any reproduction, modification, translation, compilation, or representation
* of this Software except as specified above is prohibited without the express
* written permission of Cypress.
*
* Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Cypress
* reserves the right to make changes to the Software without notice. Cypress
* does not assume any liability arising out of the application or use of the
* Software or any product or circuit described in the Software. Cypress does
* not authorize its products for use in any products where a malfunction or
* failure of the Cypress product may reasonably be expected to result in
* significant property damage, injury or death (“High Risk Product”). By
* including Cypress’s product in a High Risk Product, the manufacturer of such
* system or application assumes all risk of such use and in doing so agrees to
* indemnify Cypress against all liability.
*******************************************************************************/
#include <project.h>
#include <stdio.h>
#define NUM_STATES 127 /* PWM period value + 1 */
/* Integer division macro */
#define DIV_ROUND_CLOSEST(n, d) ((((n) + (d)/2)/(d)))
/* widget status changes */
#define WDGT_STATUS_CHANGED (1u)
#define WDGT_STATUS_UNCHANGED (0u)
/* global variables */
uint32 maxCounts[2] = { 0, 0 }; /* Used for initial tuning, set low */
uint32 minCounts[2] = {12000, 12000}; /* Initial tuning, set high */
uint16_t last_val[2];
// averaging arrays
#define array_size 32
uint8_t arr[2][array_size];
uint8_t pt[2];
uint16_t sum[2];
/* Function Declaration */
uint8 GetLEDState(uint32 currCounts, uint8 channel);
/******************************************************************************
* Function name: main
*******************************************************************************
* Summary: Initializes MagSense and EZI2C. Checks if sensors are active and
* updates positional data. Turns on the appropriate LEDs.
*
******************************************************************************/
int main()
{
uint8_t changedFlag = WDGT_STATUS_UNCHANGED;
uint32_t rawCounts1 = 0;
uint32_t rawCounts2 = 0;
CyGlobalIntEnable; /* Enable global interrupts */
UART_Start(); /* Start the UART */
PWM_Start(); /* Start PWM Component */
EZI2C_Start(); /* Start EZI2C Component */
EZI2C_EzI2CSetBuffer1(sizeof(MagSense_dsRam), sizeof(MagSense_dsRam),
(uint8 *)&MagSense_dsRam);
MagSense_Start(); /* Initialize Component */
MagSense_ScanAllWidgets(); /* Scan all widgets */
for (int i = 0; i < 2; i++)
{
pt[i] = 0;
sum[i] = 0;
for (int j = 0; j < array_size; j++)
arr[i][j] = 0;
}
for(;;)
{
if(MagSense_NOT_BUSY == MagSense_IsBusy())
{
MagSense_ProcessAllWidgets(); /* Process all widgets */
MagSense_RunTuner(); /* To sync with Tuner application */
if (MagSense_IsAnyWidgetActive()) /* Scan result verification */
{
changedFlag = WDGT_STATUS_CHANGED;
rawCounts1 = MagSense_LE1_RX0_LX0_DIFF_VALUE; /* rawCounts typ. ~10000-11700 */
rawCounts2 = MagSense_LE2_RX0_LX0_DIFF_VALUE;
//rawCounts3 = MagSense_PROXIMITY0_RX0_LX0_DIFF_VALUE;
PWM_WriteCompare( GetLEDState(rawCounts1, 0) ); /* Convert rawCounts to values 0-127 */
PWM_WriteCompare( GetLEDState(rawCounts2, 1) );
}
else /* No Widgets Active */
{
/* Only print 0 after a state change */
if(WDGT_STATUS_CHANGED == changedFlag)
{
//UART_UartPutString("\x1b[2J\r0"); /*Clear screen and print 0*/
changedFlag = WDGT_STATUS_UNCHANGED;
}
//PWM_WriteCompare( GetLEDState(0, 1 /* No sensor signal */) ); /* Turn off LED */
}
MagSense_ScanAllWidgets(); /* Start next scan */
}
}
}
/******************************************************************************
* Function name: GetLEDState
*******************************************************************************
* Summary: Checks if there is a new maximum sensor value. Scales the sensor
* signal range into 100 states by: (scaledRange = maxCounts / NUM_STATES)
* Sensor signal is then fit into the range by: (scaled_val = currCounts / scaledRange)
*
* Args: currCounts - the current value of the sensor
*
* Returns: A value between 0-100
*
*******************************************************************************/
uint8 GetLEDState(uint32 currCounts, uint8 channel)
{
char buf[16];
char buf1[4];
uint16_t newVal = 0;
uint16_t scaledRange = 0;
uint8_t cc_type[2] = { 1, 7 };
uint16_t thisVal;
buf1[3] = 0;
/* Get newest max */
if(currCounts > maxCounts[channel])
{
maxCounts[channel] = currCounts;
}
scaledRange = DIV_ROUND_CLOSEST( maxCounts[channel] , NUM_STATES); /* Calculate the divisor for scaling */
newVal = DIV_ROUND_CLOSEST( currCounts , scaledRange); /* scale down sensor data */
sum[channel] -= arr[channel][pt[channel]];
sum[channel] += newVal;
arr[channel][pt[channel]] = newVal;
thisVal = sum[channel] / array_size;
thisVal &= 0x7f;
pt[channel]++;
pt[channel] %= array_size;
if (thisVal == 0) thisVal = 1;
/* If the sensor signal changed, print the new value */
//if( PWM_ReadCompare() != newVal )
if (last_val[channel] != thisVal)
{
buf1[0] = 0xB3;
buf1[1] = cc_type[channel];
buf1[2] = thisVal;
//sprintf(buf, " %d, ", newVal);
UART_UartPutString(buf1);
last_val[channel] = thisVal;
}
return thisVal;
}
This section:
// averaging arrays
#define array_size 32
uint8_t arr[2][array_size];
uint8_t pt[2];
uint16_t sum[2];
...as well as...
sum[channel] -= arr[channel][pt[channel]];
sum[channel] += newVal;
arr[channel][pt[channel]] = newVal;
thisVal = sum[channel] / array_size;
thisVal &= 0x7f;
pt[channel]++;
pt[channel] %= array_size;
...takes care of smoothening the control movements. I tested an array size of 32 measurements to be averaged. And only if the new calculated value differed from the previous, it was outputted to the data stream. Mut still it jumped back and forth.
ConclusionThe PSOC 4700S together with the coil kit works technically as a MIDI controller, outputting a data stream, which a DAW program on a computer can handle. But much more testing is needed before one can tell whether there could be more value in it.
Comments