跳至主要內容

页面配置

Mr.Lexon大约 6 分钟embedded

Rust嵌入式开发-蜂鸣器

这几天突然对嵌入式有了兴趣,但是又不想接触C语言和C++的大坑(主要是arduino的引导坏了,程序无法烧录进去,不会修),后面发现rust正在逐渐支持嵌入式开发,所以来尝试一手。

提示

主要器件以及依赖: 器件:

  1. 芯片:stm32f103c8t6
  2. 无源蜂鸣器
  3. led二极管
  4. 1K电阻

软件依赖:

  1. rust 1.85.0-nightly
  2. cortex-m =
  3. cortex-m-rt = "0.7.5"
  4. cortex-m-semihosting = "0.5"
  5. panic-halt = "1.0.0"
  6. stm32f1xx-hal =
  7. openocd 0.12.0+dev-01557-gdd1758272-dirty
  8. arm-none-eabi-addr2line 2.36.1

软件具体的环境可以参照视频:rust嵌入式编程open in new window

之所以选择stm32f103c8t6,是因为这个板子便宜,并且支持的依赖库也多。

电路图

大致的电路图是这样的,实际上的电路是使用面包板连接的。这里并联了一个二极管,作用是判断蜂鸣器是否在工作,在代码中有体现。(因为电路图是一晚上现学现画的,所以可能存在器件或者连线画错的情况,但大体上的电路逻辑就是如此了)

电路图
电路图

有源蜂鸣器和无源蜂鸣器之间的区别(这里使用无源蜂鸣器是因为手头上就只有这个): GPT的解释: 有源蜂鸣器和无源蜂鸣器的区别主要体现在工作原理和驱动方式上:

  1. 工作原理
    • 有源蜂鸣器:内部自带振荡电路,当电流通过时,蜂鸣器会自动发出声音。只需要提供直流电源或特定的电压信号即可工作。
    • 无源蜂鸣器:没有内建振荡电路,需要外部驱动信号(如方波信号)才能发出声音。它的工作依赖于输入的交流信号。
  2. 驱动方式
    • 有源蜂鸣器:只需接通电源,蜂鸣器会根据电压大小或频率自动发声,无需其他外部电路。
    • 无源蜂鸣器:需要通过控制器(如单片机)产生方波或脉冲信号来驱动蜂鸣器工作。蜂鸣器的频率和音调由控制信号的频率决定。
  3. 声音特点
    • 有源蜂鸣器:声音通常比较稳定,适合产生固定频率的声音,例如警报、提示音等。
    • 无源蜂鸣器:可以通过调节外部驱动信号的频率生成不同的音调,因此可以实现更丰富的音效。
  4. 电路复杂性
    • 有源蜂鸣器:电路简单,使用时只需要连接电源,适合需要简单音响输出的应用。
    • 无源蜂鸣器:需要额外的驱动电路或控制器,电路相对复杂,但可以实现更灵活的声音控制。

总结来说,有源蜂鸣器适合简单的警示功能,而无源蜂鸣器则更适用于需要变化音调的场景。

因为这里使用了无源蜂鸣器,所以需要给无源蜂鸣器输出一个频率以驱动其工作

总体逻辑

无源蜂鸣器的使用需要模拟频率输出,那么就要寻找支持频率输出的引脚,而频率输出是由 TIM 定时器 产生的,也就是说要寻找支持 TIM 定时器 的引脚,然后将其与蜂鸣器连接。一般情况下,STM32 微控制器的 TIM 定时器可以配置为输出 PWM 信号,PWM 信号的频率决定了蜂鸣器发出的声音频率,而占空比则控制声音的响度。具体步骤如下:

  1. 选择支持 TIM 定时器的引脚
    • 查找 STM32 微控制器的引脚映射表,确定哪些引脚支持 TIM 定时器的输出功能。每个定时器(如 TIM1、TIM2 等)通常都有多个通道(如 TIM1_CH1、TIM1_CH2 等),并且每个通道都对应一个引脚。
    • 使用 STM32 的工具,如 STM32CubeMX,可以帮助你查看每个定时器通道和 GPIO 引脚之间的映射关系。
  2. 配置 TIM 定时器
    • 选择合适的 TIM 定时器通道,设置其频率和占空比。频率决定了蜂鸣器的音调(Hz),占空比则影响声音的强弱。
    • 配置定时器为 PWM 输出模式,并选择合适的频率来驱动蜂鸣器。例如,要产生 1 kHz 的频率,可以将定时器的频率设置为 1 kHz。
  3. 连接蜂鸣器
    • 将选择的引脚连接到蜂鸣器的正极端,蜂鸣器的负极端可以连接到地(GND)。
    • 通过定时器输出的 PWM 信号驱动蜂鸣器。
  4. 调整频率与占空比
    • 根据需要调整定时器的频率来控制蜂鸣器发出的声音频率(如 1 kHz、2 kHz 等),也可以调整占空比来改变声音的音质或响度。

代码

src/main.rs

#![no_std]  
#![no_main]  
  
// pick a panicking behavior  
use panic_halt as _; // you can put a breakpoint on `rust_begin_unwind` to catch panics  
// use panic_abort as _; // requires nightly  
// use panic_itm as _; // logs messages over ITM; requires ITM support  
// use panic_semihosting as _; // logs messages to the host stderr; requires a debugger  
  
use cortex_m::asm;  
use cortex_m_rt::entry;  
use stm32f1xx_hal::{pac,prelude::*};  
use stm32f1xx_hal::time::{ms, Hertz};  
use stm32f1xx_hal::timer::{Channel, Tim2NoRemap};  
  
#[entry]  
fn main() -> ! {  
    asm::nop(); // To not have main optimize to abort in release mode, remove when you add code  
    let cp = cortex_m::Peripherals::take().unwrap();  
    
    let mut gpioc = dp.GPIOC.split();  
    let mut pc13 = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);  
  
    let mut flash = dp.FLASH.constrain();  
      
    let rcc = dp.RCC.constrain();
    let clocks = rcc.cfgr.freeze(&mut flash.acr);
    let mut delay = cp.SYST.delay(&clocks);
  
    let mut afio = dp.AFIO.constrain();  
    let mut gpioa = dp.GPIOA.split();  
  
    let mut pa0 = gpioa.pa0.into_push_pull_output(&mut gpioa.crl);  
      
    let pwm_pin = gpioa.pa1.into_alternate_push_pull(&mut gpioa.crl);  
    
    let mut pwm = dp  
        .TIM2  
        .pwm_hz::<Tim2NoRemap, _, _>(pwm_pin, &mut afio.mapr, 1.kHz(), &clocks);  

    pwm.enable(Channel::C2);   
    pwm.set_period(1.kHz());  
  
    let max = pwm.get_max_duty();  
  
    loop {  
  
        pc13.toggle();  
        pa0.set_high();  
        pwm.set_duty(Channel::C2, max);  
        
        delay.delay_ms(3000_u16);  
        
        pwm.set_duty(Channel::C2, max/16);  
        
        delay.delay_ms(1000_u16);  
        pa0.set_low();  
        delay.delay_ms(1000_u16);  
    }  
}

代码详解:

  1. 交替功能
let pwm_pin = gpioa.pa1.into_alternate_push_pull(&mut gpioa.crl);

这一段功能是将PA1引脚设置为交替功能,这个是用于引脚支持频率输出。因为我们需要输出时钟频率,所有要使用支持TIM2_CH的引脚

  1. 时钟频率输出:
let mut pwm = dp  
    .TIM2  
    .pwm_hz::<Tim2NoRemap, _, _>(pwm_pin, &mut afio.mapr, 1.kHz(), &clocks); //配置 PWM 输出(脉宽调制)信号

pwm.enable(Channel::C2);  // 启用通道C2 对应TIM2_CH2
pwm.set_period(1.kHz());  //设置 PWM 的频率为 1 kHz,对应的周期是 1 毫秒
let max = pwm.get_max_duty(); //获取 PWM 输出信号 的最大 占空比
pwm.set_duty(Channel::C2, max); //通道输出

这里需要注意一点就是如果没通过pwm.enable启用通道,但是在下面调用通道其他通道如:Channel::C3的话,会有板载报错,从而中断程序(编译会通过,并且会成功上传)