[STM32F1] 分享一个FFT实现频谱仪的例子

[复制链接]
1096|11
 楼主| gejigeji521 发表于 2023-4-27 22:29 | 显示全部楼层 |阅读模式
  1. #include "main.h"
  2. #include "arm_math.h"

  3. #define N 256 // FFT长度
  4. #define Fs 10000 // 采样率
  5. #define ADC_BUF_LEN 512 // ADC缓存长度
  6. #define OLED_WIDTH 128 // OLED宽度
  7. #define OLED_HEIGHT 64 // OLED高度

  8. uint16_t ADC_Buffer[ADC_BUF_LEN]; // ADC采样缓存
  9. uint16_t FFT_Input[N]; // FFT输入缓存
  10. float32_t FFT_Output[N]; // FFT输出缓存
  11. float32_t FFT_Mag[N/2]; // FFT幅值缓存
  12. uint8_t OLED_Buffer[OLED_WIDTH*OLED_HEIGHT/8]; // OLED显示缓存

  13. extern SPI_HandleTypeDef hspi1; // OLED所用SPI口

  14. void SystemClock_Config(void);
  15. static void MX_GPIO_Init(void);
  16. static void MX_DMA_Init(void);
  17. static void MX_ADC1_Init(void);
  18. static void MX_SPI1_Init(void);

  19. void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { // ADC完成一次转换的回调函数
  20.     uint32_t i, j;

  21.     for(i=0, j=0; i<N; i+=2, j++) { // 交错采样,同时处理两个ADC采样值
  22.         FFT_Input[j] = ADC_Buffer[i];
  23.     }

  24.     arm_rfft_fast_instance_f32 S; // 初始化FFT结构体
  25.     arm_rfft_fast_init_f32(&S, N);

  26.     arm_rfft_fast_f32(&S, FFT_Input, FFT_Output, 0); // 执行FFT
  27.     arm_cmplx_mag_f32(FFT_Output, FFT_Mag, N/2); // 计算幅值

  28.     for(i=0; i<OLED_WIDTH; i++) { // 将频谱转换为显示格式
  29.         uint8_t row_data = 0;
  30.         for(j=i*OLED_HEIGHT/OLED_WIDTH; j<(i+1)*OLED_HEIGHT/OLED_WIDTH; j++) {
  31.             if(FFT_Mag[j] > 20000) { // 限制幅值范围
  32.                 FFT_Mag[j] = 20000;
  33.             }
  34.             uint8_t bit = (FFT_Mag[j] * OLED_HEIGHT / 20000.0) * 255 / 8; // 将幅值映射到OLED高度
  35.             row_data |= (bit << (j % 8)); // 存储到OLED缓存中
  36.         }
  37.         OLED_Buffer[i*OLED_HEIGHT/8] = row_data;
  38.     }

  39.     HAL_SPI_Transmit(&hspi1, OLED_Buffer, OLED_WIDTH*OLED_HEIGHT/8, 10); // 将OLED缓存发送到OLED显示屏
  40. }

  41. int main(void) {
  42.     HAL_Init();
  43.     SystemClock_Config();
  44.     MX_GPIO_Init();
  45.     MX_DMA_Init();
  46.     MX_ADC1_Init();
  47.     MX_SPI1_Init();
  48.     HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ADC_Buffer, ADC_BUF_LEN); // 启动ADC采样

  49.     while (1) {

  50.     }
  51. }

  52. void SystemClock_Config(void) {
  53.     RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  54.     RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  55. RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  56. /** Initializes the RCC Oscillators according to the specified parameters
  57. * in the RCC_OscInitTypeDef structure.
  58. */
  59. RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  60. RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  61. RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  62. RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  63. RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  64. RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  65. if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
  66.     Error_Handler();
  67. }

  68. /** Initializes the CPU, AHB and APB buses clocks
  69. */
  70. RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
  71.                             |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  72. RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  73. RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  74. RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  75. RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  76. if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
  77.     Error_Handler();
  78. }
  79. PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC|RCC_PERIPHCLK_SPI1;
  80. PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
  81. PeriphClkInit.Spi1ClockSelection = RCC_SPI1CLKSOURCE_PCLK2;
  82. if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) {
  83.     Error_Handler();
  84. }
  85. }

  86. static void MX_ADC1_Init(void) {
  87. ADC_ChannelConfTypeDef sConfig = {0};
  88. /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
  89. */
  90. hadc1.Instance = ADC1;
  91. hadc1.Init.ScanConvMode = ENABLE;
  92. hadc1.Init.ContinuousConvMode = ENABLE;
  93. hadc1.Init.DiscontinuousConvMode = DISABLE;
  94. hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  95. hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  96. hadc1.Init.NbrOfConversion = 1;
  97. if (HAL_ADC_Init(&hadc1) != HAL_OK) {
  98.     Error_Handler();
  99. }

  100. /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
  101. */
  102. sConfig.Channel = ADC_CHANNEL_0;
  103. sConfig.Rank = ADC_REGULAR_RANK_1;
  104. sConfig.SamplingTime = ADC_SAMPLETIME_13CYCLES_5;
  105. if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
  106.     Error_Handler();
  107. }
  108. }

  109. static void MX_DMA_Init(void) {
  110. /* DMA controller clock enable */
  111. __HAL_RCC_DMA1_CLK_ENABLE();
  112. /* DMA interrupt init */
  113. /* DMA1_Channel1_IRQn interrupt configuration */
  114. HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
  115. HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
  116. HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
  117. }

  118. static void MX_SPI1_Init(void) {
  119. /* SPI1 parameter configuration*/
  120. hspi1.Instance = SPI1;
  121. hspi1.Init.Mode= SPI_MODE_MASTER;
  122. hspi1.Init.Direction = SPI_DIRECTION_1LINE;
  123. hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  124. hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  125. hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  126. hspi1.Init.NSS = SPI_NSS_SOFT;
  127. hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
  128. hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  129. hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  130. hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  131. hspi1.Init.CRCPolynomial = 10;
  132. if (HAL_SPI_Init(&hspi1) != HAL_OK) {
  133. Error_Handler();
  134. }
  135. }

  136. void Error_Handler(void) {
  137. /* USER CODE BEGIN Error_Handler_Debug /
  138. / User can add his own implementation to report the HAL error return state /
  139. while(1) {
  140. }
  141. / USER CODE END Error_Handler_Debug */
  142. }

  143. #ifdef USE_FULL_ASSERT

  144. void assert_failed(uint8_t file, uint32_t line) {
  145. / USER CODE BEGIN 6 /
  146. / User can add his own implementation to report the file name and line number,
  147. ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) /
  148. / USER CODE END 6 */
  149. }

  150. #endif


 楼主| gejigeji521 发表于 2023-4-27 22:42 | 显示全部楼层
实现对音乐频谱显示的操作可以分为以下几个步骤:
  • 初始化ADC:将ADC的输入通道和采样频率等参数配置好。
  • 初始化DMA:将DMA的通道和缓冲区大小等参数配置好。
  • 初始化FFT:使用相应的库函数对FFT进行初始化。
  • 在ADC采样中断中启动DMA:每次ADC采样完成后,启动DMA进行数据传输。
  • 在DMA传输完成中断中进行FFT变换:每次DMA传输完成后,触发FFT变换并将变换结果存入数组中。
  • 将FFT变换结果转化为频谱分量:根据FFT的变换结果计算出各个频率分量的振幅大小,并将其存入一个数组中。
    • 使用SPI协议将频谱数据发送到OLED上显示:将存储频谱分量的数组通过SPI协议发送到OLED驱动芯片中,驱动芯片将其显示在OLED屏幕上。


帛灿灿 发表于 2024-6-11 07:20 | 显示全部楼层

它是由两个尺寸相同、匝数相同的线圈对称地绕制在同一个铁氧体环形磁芯
Bblythe 发表于 2024-6-11 08:23 | 显示全部楼层

镀半孔或c形孔是在板的边缘上镀半个半孔的一半。
周半梅 发表于 2024-6-11 10:19 | 显示全部楼层

这种电路结构的特点是:由四只相同的开关管接成电桥结构驱动脉冲变压器原边。
Pulitzer 发表于 2024-6-11 11:22 | 显示全部楼层

这种技术称为板对板焊接
童雨竹 发表于 2024-6-11 13:18 | 显示全部楼层

模块电源灌封操作之所以重要,主要是由于其涉及到模块电源的防护及热设计
Wordsworth 发表于 2024-6-11 14:21 | 显示全部楼层

得到不同测试条件下的输出电流和电压值,分析数据并进行比较
Clyde011 发表于 2024-6-11 15:24 | 显示全部楼层

这样可以获得更光滑的表面。
公羊子丹 发表于 2024-6-11 16:17 | 显示全部楼层

根据电荷守恒:Qinitial=Qfinal
万图 发表于 2024-6-11 17:20 | 显示全部楼层

是因为它作用是起到抑制,多应用于开关电源电路中
Uriah 发表于 2024-6-11 18:23 | 显示全部楼层

对于标准PCB设计,c形孔的最小直径为0.5mm,
您需要登录后才可以回帖 登录 | 注册

本版积分规则

196

主题

2465

帖子

8

粉丝
快速回复 在线客服 返回列表 返回顶部