Не скажу что я имею большой опыт конструирования цветомузык. Просто решил немного попрактиковаться и заодно получить опыт программирования STM32. Были у меня мысли написать программу на mikroBasic, но посмотрев справку и примеры так и не разобрался как работать с таймерами (похоже нужно взаимодействовать непосредственно с регистрами МК) и DMA. Поэтому писал на Си, где большую часть кода сгенерировала программа STM32Cube.
Программа работает следующим образом. По таймеру каждые 50 микросекунд запускается АЦП и по окончанию измерения с помощью DMA (по нашему ПДП - прямой доступ к памяти) результат записывается в буфер в памяти. При заполнении буфера происходит прерывание (примерно каждые 50 миллисекунд (20 раз в секунду)) и производится копирование данных в другой буфер (чтобы не спеша обрабатывать данные пока в основной буфер записываются новые данные).

Код:
void ADC_CopyAdata(void) // Копирование данных из DMA буфера. Выззывается из DMA прерывания.
{
    if (ADC_FlagData != 1) // Ожидается копирование новых данных.
    {
        uint16_t i;
        for (i=0; i<ADC_BuffSize; i++)
        {
            ADC_Buff[i].Real = (short)(ADC_Buff_DMA[i] - 0x8000);
        }
        ADC_FlagData = 1; // В буфер скопированы новые данные.
    }
}

Размер буфера (массива) 1024 элемента (2 байта каждый, т. к. АПЦ 12-ти битный) и соответственно БПФ (быстрое преобразование) рассчитывается на основании 1024 точек (часть кода из функции main).

Код:
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

        if (ADC_FlagData == 1) // В буфер скопированы новые данные с АЦП.
        {
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // Мигаем светодиодом.
            ClearMemory32((uint32_t*)&FFT_Buff, ADC_BuffSize); // Обнуляем память под результат БПФ.
            fftR4((short*)&FFT_Buff, (short*)&ADC_Buff, ADC_BuffSize); // Выполняем БПФ.
            ClearMemory32((uint32_t*)&ADC_Buff, ADC_BuffSize);         // Обнуляем память.
            ADC_FlagData = 0;                                          // Т. к. БПФ провели, разрешаем копирование новых данных.

            for (i=0;i<16;i++) SpectrChannel[i]=0; // Обнуляем память для данных спектра.

            GetSpectr((FFT_Data*)&FFT_Buff, ADC_BuffSize, (uint16_t*)&SpectrChannel, 16);
            SetPWM((uint16_t*)&SpectrChannel);
        }

  }
  /* USER CODE END 3 */

После этого вычисляются амплитуды для 16 точек (1024 делится на 16 путем вычисления среднего арифметического)

Код:
// Получение спектра из результата БПФ.
void GetSpectr(FFT_Data *Data, uint16_t Size, uint16_t *OutData, uint16_t OutSize)
{
  uint16_t x=0;
  uint16_t s=Size/OutSize;
  uint16_t i;
  double tmp=0;

  for (i=0;i<Size;i++)
  {
      tmp=tmp+sqrt(Data[i].Real*Data[i].Real+Data[i].Imag*Data[i].Imag);

      if (i % s == s-1)
      {
          tmp = tmp / s;
          if (tmp<10) tmp=0;  // Это шум.
          OutData[x] = (uint16_t)tmp;
          tmp = 0;
          x++;
      }
  }
}

а затем загружается в ШИМ регистры таймеров (4 таймера по 4 канала в каждом, т. е. 16 каналов ШИМ).

Код:
void SetPWM_Tim(TIM_TypeDef *htim, uint16_t *Spectr)
{
  htim->CCR1 = Spectr[0];
  htim->CCR2 = Spectr[1];
  htim->CCR3 = Spectr[2];
  htim->CCR4 = Spectr[3];
}

void SetPWM(uint16_t *Spectr) // Загрузка данных ШИМ в таймеры.
{
  SetPWM_Tim(TIM1, &Spectr[0]);
  SetPWM_Tim(TIM2, &Spectr[4]);
  SetPWM_Tim(TIM3, &Spectr[8]);
  SetPWM_Tim(TIM4, &Spectr[12]);
}

Звуковой сигнал нужно подавать на вывод A0 (амплитуда до трех вольт).
Светодиоды нужно подключить к выводам A8, A9, A10, A11, A15, B3, A2, A3, A6, A7, B0, B1, B6, B7, B8, B9.
Файлы. http://pure-basic.narod.ru/forum_files/ … Lights.zip