STM32驱动直流无刷电机(BLDC)发声
- ✨需要注意,这是驱动直流无刷电机(BLDC)发出声音,不是直接驱动无刷电机转动。
- 📍内容移植参考:
ttps://github.com/AlkaMotors/AM32-MultiRotor-ESC-firmware
- 📍Arduino平台BLDC Music开源工程:
https://github.com/owennewo/tone-player
- 🔖测试单片机:STM32L431、STM32F446.
🔖在很多商业用的电调中,基本都带,在上电自检或设置以及输入信号检测时,电机会发出提示声音。BLDC电机在PWM调制过程中,电机的绕组在不同开关频率下工作时,产生不同分贝的噪声。
📄弄明白发声原理,驱动BLDC电机发声其实很简单,只需要给BLDC电机任意一相导通,在6步换相中,给6步其中的任意一步,在短时间内,给定一个特定频率,较低占空比(小于3%)的PWM信号即可发声。
- 🌿PWM 频率推荐:1.6KHz - 3KHz之间
- 🌿PWM占空比推荐:1% ,不要超过3%。
- 🧨am32-firmware(
https://github.com/am32-firmware/AM32
)在线调参页面:
https://esc-configurator.com/
可以在线测试不同音乐曲目:
bluejay:b=570,o=4,d=32:4b,p,4e5,p,4b,p,4f#5,2p,4e5,2b5,8b5
- 🌿模拟bluejay旋律音功能测试效果:
BLDC无刷电机模拟音乐旋律
- ✨代码解析:(个人理解)其中字母
b
、e
、f
代表频率,字母p
代表停顿的时间。- 🌿音符与频率对照表:
https://www.gotozhuan.com/cn/tools/tool_pitch_map.php
音符 频率(hz) 波长(cm)
C0 16.35 2109.89
C#0 17.32 1991.47
D0 18.35 1879.69
D#0 19.45 1774.2
E0 20.60 1674.62
F0 21.83 1580.63
F#0 23.12 1491.91
G0 24.50 1408.18
G#0 25.96 1329.14
A0 27.50 1254.55
A#0 29.14 1184.13
B0 30.87 1117.67
C1 32.70 1054.94
C#1 34.65 995.73
D1 36.71 939.85
D#1 38.89 887.1
E1 41.20 837.31
F1 43.65 790.31
F#1 46.25 745.96
G1 49.00 704.09
G#1 51.91 664.57
A1 55.00 627.27
A#1 58.27 592.07
B1 61.74 558.84
C2 65.41 527.47
C#2 69.30 497.87
D2 73.42 469.92
D#2 77.78 443.55
E2 82.41 418.65
F2 87.31 395.16
F#2 92.50 372.98
G2 98.00 352.04
G#2 103.83 332.29
A2 110.00 313.64
A#2 116.54 296.03
B2 123.47 279.42
C3 130.81 263.74
C#3 138.59 248.93
D3 146.83 234.96
D#3 155.56 221.77
E3 164.81 209.33
F3 174.61 197.58
F#3 185.00 186.49
G3 196.00 176.02
G#3 207.65 166.14
A3 220.00 156.82
A#3 233.08 148.02
B3 246.94 139.71
C4 261.63 131.87
C#4 277.18 124.47
D4 293.66 117.48
D#4 311.13 110.89
E4 329.63 104.66
F4 349.23 98.79
F#4 369.99 93.24
G4 392.00 88.01
G#4 415.30 83.07
A4 440.00 78.41
A#4 466.16 74.01
B4 493.88 69.85
C5 523.25 65.93
C#5 554.37 62.23
D5 587.33 58.74
D#5 622.25 55.44
E5 659.25 52.33
F5 698.46 49.39
F#5 739.99 46.62
G5 783.99 44.01
G#5 830.61 41.54
A5 880.00 39.2
A#5 932.33 37
B5 987.77 34.93
C6 1046.50 32.97
C#6 1108.73 31.12
D6 1174.66 29.37
D#6 1244.51 27.72
E6 1318.51 26.17
F6 1396.91 24.7
F#6 1479.98 23.31
G6 1567.98 22
G#6 1661.22 20.77
A6 1760.00 19.6
A#6 1864.66 18.5
B6 1975.53 17.46
C7 2093.00 1
- 📜换算成执行语句:单位:Hz
frequency(4*570),delay(1),frequency(4*659.25),delay(1),frequency(4*570),delay(1),frequency(4*739.99),delay(2),frequency(4*659.25),frequency(2*987.77),frequency(8*987.77)
📘基于AM32-MultiRotor-ESC-firmware
实现发声的主要实现代码
- 🌿sounds.c
/*
* sounds.c
*
* Created on: May 13, 2020
* Author: Alka
*/
#include "sounds.h"
#include "phaseouts.h"
#include "functions.h"
//#include "eeprom.h"
#include "targets.h"
#include "common.h"
uint8_t beep_volume;
//uint8_t blueJayTuneBuffer[128] = {};
void pause(uint16_t ms) {
TIM1->CCR1 = 0; // volume of the beep, (duty cycle) don't go above 25
TIM1->CCR2 = 0;
TIM1->CCR3 = 0;
delayMillis(ms);
TIM1->CCR1 = beep_volume; // volume of the beep, (duty cycle) don't go above 25 out of 2000蜂鸣声的音量(占空比)在2000中不超过25
TIM1->CCR2 = beep_volume;
TIM1->CCR3 = beep_volume;
}
void setVolume(uint8_t volume) {
if (volume > 11) {
volume = 11;
}
if (volume < 0) {
volume = 0;
}
beep_volume = volume * 2; // volume variable from 0 - 11 equates to CCR value of 0-22
}
void setCaptureCompare() {
TIM1->CCR1 = beep_volume; // volume of the beep, (duty cycle) don't go above 25 out of 2000
TIM1->CCR2 = beep_volume;
TIM1->CCR3 = beep_volume;
}
void playBJNote(uint16_t freq, uint16_t bduration) { // hz and ms
uint16_t timerOne_reload = TIM1_AUTORELOAD;
TIM1->PSC = 10;
timerOne_reload = CPU_FREQUENCY_MHZ * 100000 / freq;
TIM1->ARR = timerOne_reload;
TIM1->CCR1 = beep_volume * timerOne_reload / TIM1_AUTORELOAD ; // volume of the beep, (duty cycle) don't go above 25 out of 2000
TIM1->CCR2 = beep_volume * timerOne_reload / TIM1_AUTORELOAD;
TIM1->CCR3 = beep_volume * timerOne_reload / TIM1_AUTORELOAD;
delayMillis(bduration);
}
uint16_t getBlueJayNoteFrequency(uint8_t bjarrayfreq) {
return 10000000 / (bjarrayfreq * 247 + 4000);
}
void playStartupTune() {
__disable_irq();
TIM1->ARR = TIM1_AUTORELOAD;
setCaptureCompare();
comStep(3); // activate a pwm channel
TIM1->PSC = 55; // frequency of beep
delayMillis(200); // duration of beep
comStep(5);
TIM1->PSC = 40; // next beep is higher frequency
delayMillis(200);
comStep(6);
TIM1->PSC = 25; // higher again..
delayMillis(200);
allOff(); // turn all channels low again
TIM1->PSC = 0; // set prescaler back to 0.
TIM1->ARR = TIMER1_MAX_ARR;
__enable_irq();
}
void playBrushedStartupTune() {
__disable_irq();
TIM1->ARR = TIM1_AUTORELOAD;
setCaptureCompare();
comStep(1); // activate a pwm channel
TIM1->PSC = 40; // frequency of beep
delayMillis(300); // duration of beep
comStep(2); // activate a pwm channel
TIM1->PSC = 30; // frequency of beep
delayMillis(300); // duration of beep
comStep(3); // activate a pwm channel
TIM1->PSC = 25; // frequency of beep
delayMillis(300); // duration of beep
comStep(4);
TIM1->PSC = 20; // higher again..
delayMillis(300);
allOff(); // turn all channels low again
TIM1->PSC = 0; // set prescaler back to 0.
// signaltimeout = 0;
TIM1->ARR = TIMER1_MAX_ARR;
__enable_irq();
}
void playDuskingTune() {
setCaptureCompare();
TIM1->ARR = TIM1_AUTORELOAD;
comStep(2); // activate a pwm channel
TIM1->PSC = 60; // frequency of beep
delayMillis(200); // duration of beep
TIM1->PSC = 55; // next beep is higher frequency
delayMillis(150);
TIM1->PSC = 50; // higher again..
delayMillis(150);
TIM1->PSC = 45; // frequency of beep
delayMillis(100); // duration of beep
TIM1->PSC = 50; // next beep is higher frequency
delayMillis(100);
TIM1->PSC = 55; // higher again..
delayMillis(100);
TIM1->PSC = 25; // higher again..
delayMillis(200);
TIM1->PSC = 55; // higher again..
delayMillis(150);
allOff(); // turn all channels low again
TIM1->PSC = 0; // set prescaler back to 0.
TIM1->ARR = TIMER1_MAX_ARR;
}
void playInputTune2() {
TIM1->ARR = TIM1_AUTORELOAD;
__disable_irq();
// LL_IWDG_ReloadCounter(IWDG);
TIM1->PSC = 60;
setCaptureCompare();
comStep(1);
delayMillis(75);
TIM1->PSC = 80;
delayMillis(75);
TIM1->PSC = 90;
// LL_IWDG_ReloadCounter(IWDG);
delayMillis(75);
allOff();
TIM1->PSC = 0;
// signaltimeout = 0;
TIM1->ARR = TIMER1_MAX_ARR;
__enable_irq();
}
void playInputTune() {
__disable_irq();
TIM1->ARR = TIM1_AUTORELOAD;
// LL_IWDG_ReloadCounter(IWDG);
TIM1->PSC = 80;
setCaptureCompare();
comStep(3);
delayMillis(100);
TIM1->PSC = 70;
delayMillis(100);
TIM1->PSC = 40;
delayMillis(100);
allOff();
TIM1->PSC = 0;
// signaltimeout = 0;
TIM1->ARR = TIMER1_MAX_ARR;
__enable_irq();
}
void playDefaultTone() {
TIM1->ARR = TIM1_AUTORELOAD;
TIM1->PSC = 50;
setCaptureCompare();
comStep(2);
delayMillis(150);
// LL_IWDG_ReloadCounter(IWDG);
TIM1->PSC = 30;
delayMillis(150);
allOff();
TIM1->PSC = 0;
// signaltimeout = 0;
TIM1->ARR = TIMER1_MAX_ARR;
}
void playChangedTone() {
TIM1->ARR = TIM1_AUTORELOAD;
TIM1->PSC = 40;
setCaptureCompare();
comStep(2);
delayMillis(150);
// LL_IWDG_ReloadCounter(IWDG);
TIM1->PSC = 80;
delayMillis(150);
allOff();
TIM1->PSC = 0;
// signaltimeout = 0;
TIM1->ARR = TIMER1_MAX_ARR;
}
void playBeaconTune3() {
TIM1->ARR = TIM1_AUTORELOAD;
__disable_irq();
setCaptureCompare();
for (int i = 119 ; i > 0 ; i = i - 2) {
// LL_IWDG_ReloadCounter(IWDG);
comStep(i / 20);
TIM1->PSC = 10 + (i / 2);
delayMillis(10);
}
allOff();
TIM1->PSC = 0;
TIM1->ARR = TIMER1_MAX_ARR;
__enable_irq();
}
- 🌿main测试代码:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_COMP1_Init();
MX_TIM1_Init();
MX_TIM6_Init();
MX_TIM16_Init();
MX_USART2_UART_Init();
// MX_IWDG_Init();
MX_TIM7_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim6);
HAL_TIM_Base_Start(&htim7);
HAL_TIM_Base_Start(&htim16);
HAL_TIM_GenerateEvent(&htim1, TIM_EVENTSOURCE_UPDATE);
PWM_Init();
TIMER1_MAX_ARR = map(35, 24, 48, TIM1_AUTORELOAD, TIM1_AUTORELOAD / 2);//24 - 48范围
// TIMER1_MAX_ARR = map(20, 12, 24, TIM1_AUTORELOAD * 2, TIM1_AUTORELOAD); //12 - 24范围
// TIMER1_MAX_ARR = map(10, 7, 16, TIM1_AUTORELOAD * 3, TIM1_AUTORELOAD / 2 * 3);//7 - 16范围
TIM1->ARR = TIMER1_MAX_ARR;//2570装载值不同,影响声音
setVolume(5);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while(1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
printf("from playInputTune Sound.\r\n");
playInputTune();//
LL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(2500);
printf("from playInputTune2 Sound.\r\n");
playInputTune2();
LL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(2500);
printf("from playInputTune3 Sound.\r\n");
playBeaconTune3();
LL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(2500);
}
/* USER CODE END 3 */
}
📚测试工程
- 🔖STM32L431
链接:https://pan.baidu.com/s/1hdn-vaNXe4UvFgnPtAgJlA?pwd=ikvv
提取码:ikvv
📒自定义声音
void DIY_on_tone(void)
{
__disable_irq();
TIM1->ARR = TIM1_AUTORELOAD;
TIM1->PSC = 30;
TIM1->CCR1 = beep_volume; // volume of the beep, (duty cycle) don't go above 25 out of 2000
TIM1->CCR2 = beep_volume;
TIM1->CCR3 = beep_volume;
comStep(1);
delayMillis(150);
TIM1->PSC = 40;
TIM1->CCR1 = beep_volume; // volume of the beep, (duty cycle) don't go above 25 out of 2000
TIM1->CCR2 = beep_volume;
TIM1->CCR3 = beep_volume;
delayMillis(200);
TIM1->PSC = 50;
TIM1->CCR1 = beep_volume; // volume of the beep, (duty cycle) don't go above 25 out of 2000
TIM1->CCR2 = beep_volume;
TIM1->CCR3 = beep_volume;
delayMillis(250);
allOff();
TIM1->PSC = 0;
TIM1->ARR = TIMER1_MAX_ARR;
__enable_irq();
}
- 🌿comStep()函数所做的事情就是,让六步换相中,只开通一条通路。其余高阻关闭。如果只是让其发出声音,可以是6步中的任意一项。
void comStep (int newStep){
//TIM14->CNT = 0;
switch(newStep)
{
case 1: //A-B
phaseAPWM();
phaseBLOW();
phaseCFLOAT();
break;
case 2: // C-B
phaseAFLOAT();
phaseBLOW();
phaseCPWM();
break;
case 3: // C-A
phaseALOW();
phaseBFLOAT();
phaseCPWM();
break;
case 4:// B-A
phaseALOW();
phaseBPWM();
phaseCFLOAT();
break;
case 5: // B-C
phaseAFLOAT();
phaseBPWM();
phaseCLOW();
break;
case 6: // A-C
phaseAPWM();
phaseBFLOAT();
phaseCLOW();
break;
}
//stop_time = TIM14->CNT;
}
📘Arduino tone-player项目简介
- 🔖工程基于VSCode platformio ,基于Arduino平台。直接使用VSCode打开,如果安装了STM32相关固件,会自动加载并安装相对应的库。(编译工程时,不要有中文路径名,否则会在最终生成文件时报错)
- 🛠上传支持:stlink cmsis-dap, jlink
-🌟 如果点击烧录时,OpenOCD上传报错,可以直接选择对应生成的.bin文件进行烧录。
Error: timed out while waiting for target halted
embedded:startup.tcl:1516: Error: ** Unable to reset target **
in procedure 'program'
in procedure 'program_error' called at file "embedded:startup.tcl", line 1553
at file "embedded:startup.tcl", line 1516
*** [upload] Error 1
========================================= [FAILED] Took 194.56 seconds =========================================
- 👉修改路径下:
C:\Users\Administrator\.platformio\packages\tool-openocd\openocd\scripts\board
相关型号对应的.cfg文件:
- 🔖将cfg里面的
reset_config srst_only
改为reset_config none
.
- 🔧工程依赖
SimpleFOC
、SimpleFOCDrivers
库 - 📄包含以下音乐内容:
- RTTTL_CROATIA
- RTTTL_FRANCE
- RTTTL_WALES
- RTTTL_USA
- RTTTL_SIMPSONS
- RTTTL_PINK_PANTHER
- RTTTL_SUPER_MARIO_BROS
- RTTTL_SUPER_MARIO_BROS_BASS
- RTTTL_SUPER_MARIO_BROS_POLY
- 🌿在运行时,串口2(PA2,PA3)会打印当前频率: