1 Star 0 Fork 9

Chenry/裸机版基于立创梁山派的21年电赛F题智能送药小车

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
1_底层驱动介绍.md 13.67 KB
一键复制 编辑 原始数据 按行查看 历史
yzh 提交于 2023-08-25 09:20 . Merge remote-tracking branch 'origin/master'

bsp_led

详情查看工程的BSP目录下对应的文件

c文件

文件首先定义了三个常量数组,方便程序的初始化和修改,后面想要再加入其他需要控制的灯,只需要在这下面三个数组中再定义就可以了。

  • LED_PORT[]: 常量数组,包含了不同LED灯对应的GPIO端口。例如,LED1_GPIO_PORTLED2_GPIO_PORT,等。
  • LED_PIN[]: 常量数组,包含了不同LED灯对应的GPIO引脚。例如,LED1_PINLED2_PIN,等。
  • LED_CLK[]: 常量数组,包含了不同LED灯对应的GPIO时钟。例如,LED1_GPIO_CLKLED2_GPIO_CLK,等。

各个函数的作用:

  • bsp_led_gpio_init(led_type_def led): 这个函数初始化一个指定的LED灯。首先开启对应的GPIO时钟,然后设置GPIO模式为输出,并且将引脚的电平设置为低电平。这样就对LED的GPIO口进行了初始化。
  • bsp_led_init(void): 这个函数初始化所有的LED灯,并将们全部设置为关闭状态。通过一个循环调用bsp_led_gpio_init来初始化每一个LED灯,然后调用bsp_led_off将LED关闭。有这个循环和.h文件中的枚举类型配合,我们可以不需要在增加LED灯时修改init函数了。
  • bsp_led_on(led_type_def led): 这个函数将指定的LED灯打开。通过调用gpio_bit_set函数将GPIO引脚电平设置为高电平,这样LED灯就会打开。
  • bsp_led_off(led_type_def led): 这个函数将指定的LED灯关闭。通过调用gpio_bit_reset函数将GPIO引脚电平设置为低电平,这样LED灯就会关闭。
  • bsp_led_toggle(led_type_def led): 这个函数会切换指定LED灯的状态。如果LED灯是打开的,那么就会关闭;如果LED灯是关闭的,那么它就会打开。
  • bsp_led_get_status(led_type_def led): 这个函数返回指定LED灯的状态。如果LED灯是打开的,那么就会返回1;如果LED灯是关闭的,那么就会返回0。

使用这些函数,我们就可以方便的操作立创梁山派上的LED灯了。可以通过调用bsp_led_init()函数来初始化所有的LED灯,然后通过调用bsp_led_on(LED1)函数来打开第一个LED灯。当前是只控制了立创梁山派上的四个LED灯,电赛送药小车需要的三个红黄绿灯还没加上去。

h文件

这个文件里面文件有枚举和宏定义,主要是为了方便再添加LED灯时,改动较少:

首先是类型定义:

  • led_type_def:这是一个枚举类型,代表了四个LED灯,LED1LED2LED3LED4,以及一个代表LED数量的LED_MAX_NUM

接下来看一下宏定义:

  • LEDx_PIN: 这些宏定义了每个LED的GPIO引脚号。例如,LED1_PIN定义了LED1的引脚号是GPIO_PIN_3
  • LEDx_GPIO_PORT: 这些宏定义了每个LED连接的GPIO端口。例如,LED1_GPIO_PORT定义了LED1连接的是GPIOE端口。
  • LEDx_GPIO_CLK: 这些宏定义了每个LED对应的GPIO时钟。例如,LED1_GPIO_CLK定义了LED1对应的是RCU_GPIOE时钟。

最后就是一些函数原型了,具体介绍看上面.c文件的介绍。

bsp_uart

详情查看工程的BSP目录下对应的文件

c文件

首先,还是定义了一系列的常量数组,比如COM_UARTCOM_UART_IRQnCOM_UART_CLK等,这些数组分别定义了各个UART通道的基础信息,比如串口基地址、中断号、串口时钟、GPIO时钟、复用功能,引脚等。

主要有两个函数:

  1. bsp_uart_periph_init(uart_type_def _com_uart):这个函数用于初始化指定的UART。首先,开启GPIO和USART的时钟。然后,将GPIO引脚复用到USART的Tx和Rx。配置GPIO引脚为推挽输出和上拉,并设置了USART的各项参数,比如波特率、接收使能、发送使能等。最后,根据不同的UART设备,配置不同的中断优先级。
  2. bsp_uart_init(void):此函数初始化所有的UART设备,遍历所有的UART设备,并调用bsp_uart_periph_init(uart_type_def _com_uart)函数进行初始化。

最后,对于printf函数的重定向:

  1. 条件编译
    • #if !defined(__MICROLIB):这一行检查是否定义了__MICROLIB宏。如果没有定义(即不使用微库),就编译下面的代码。
    • #if (__ARMCLIB_VERSION <= 6000000):这一行检查ARM编译器的版本。如果版本小于或等于6.0.0,就编译下面的代码。
  2. 定义__FILE结构体
    • 如果编译器是AC5(ARM Compiler 5),则需要定义一个__FILE结构体。在这里只包含一个句柄,没啥实际作用,主要是防止编译器报错。
  3. 定义__stdout变量
    • FILE __stdout;定义了一个全局变量,代表标准输出。这个变量将被用于重定向printf等函数的输出。
  4. 定义_sys_exit函数
    • 这个函数是为了避免使用半主机模式。半主机模式是一种允许嵌入式系统与主机计算机交互的模式。简单来说,他就是让单片机能通过仿真器和电脑连接,实现在电脑上的输入和输出,当去掉仿真器后,单片机就可能会卡死在程序里。
  5. 定义fputc函数
    • 这个函数是C库中的标准函数,用于写入一个字符到给定的文件。在这里,它被重定义以将字符发送到USART。
    • usart_data_transmit(COM_UART[0], (uint8_t)ch);调用一个函数将字符发送到USART的数据寄存器。
    • while(RESET == usart_flag_get(COM_UART[0], USART_FLAG_TBE));等待直到USART的发送缓冲区为空。
    • return ch;返回写入的字符。

微库和标准库的差别

  • 微库经过深度优化,有很高的代码优势。
  • 微库没有标准库的某些功能。
  • 微库以运行效率换代码大小的优化,比如memcpy(),用工作效率换来了代码空间。
  • 微库不支持C++,操作系统,互斥锁等等.
  • 微库的移植也是个问题,它可能不使用于所有的编译器,比如后面如果做GCC的工程可能会有兼容性问题。

现在的单片机flash都挺大的,如果flash够用的话,还是推荐大家直接用标准库,不要太为难自己。

h文件

就是对不同的串口进行宏定义,波特率等参数可以直接在这里修改生效。

实际使用

这里其实就只是初始化,除了printf也没有什么能主动调用的函数了。可以给大家演示一下:

  • AC5和AC6全编译的时间对比。
  • AC5和AC6全编译的固件大小对比。
  • 用微库和不用微库的固件尺寸大小。

之前也提到过,如果不用微库也不处理芯片的半主机模式的话,芯片在不连单片机时会出现卡死的情况,现在来模拟一下这个情况。

实际串口数据的处理还是要在其他文件实现,比如中断接收,串口DMA接收等。

bsp_beep

详情查看工程的BSP目录下对应的文件

什么是蜂鸣器?

蜂鸣器是一种常见的声音输出设备,可以发出各种声音或音调。它们广泛应用于家用电器、电子设备、汽车、安全系统等领域。以下是蜂鸣器的主要种类及其使用场景。

蜂鸣器主要分为两大类:有源蜂鸣器和无源蜂鸣器。

有源蜂鸣器(Active Buzzer)

有源蜂鸣器内部集成了一个振荡电路,当直接接入电源时就可以发出声音。由于内部已经集成了振荡电路,有源蜂鸣器的控制相对简单,只需要提供一个恒定的电源电压即可。但是,这种蜂鸣器的音调和音量调节较为有限。

无源蜂鸣器(Passive Buzzer)

与有源蜂鸣器不同,无源蜂鸣器没有内置振荡电路。要使无源蜂鸣器发声,需要提供一个外部的交流信号(如方波或PWM信号)。这种蜂鸣器的优点是可以通过调整外部信号的频率和占空比来实现更丰富的音调和音量控制。

我这里选用的无源蜂鸣器,他的驱动频率是4000Hz。

具体代码配置

蜂鸣器的PWM用的是定时器12,他的时钟频率是240MHz,在下面的代码中,无源蜂鸣器需要的发声最佳频率是4000Hz,最佳频率是器件手册给的,通常情况下是蜂鸣器发声声音最大时频率。

想输出确定PWM频率的信号,首先要知道当前所用定时器的时钟频率,查看GD32F4xx用户手册中的4-2.时钟树

image-20230629115836505

送药小车项目中,用的是外部时钟,APB1和APB2配置如下所示:

//截取至system_gd32f4xx.c

system_clock_240m_25m_hxtal()
    
/* AHB = SYSCLK */
RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
/* APB2 = AHB/2 */
RCU_CFG0 |= RCU_APB2_CKAHB_DIV2;
/* APB1 = AHB/4 */
RCU_CFG0 |= RCU_APB1_CKAHB_DIV4;

image-20230629121121537

可知AHB为240Mhz,定时器的时钟有两个路线,一路定时器的时钟从APB1过来,又因为上面这个TIMERSEL时钟源选择这个位,APB1是四分频,定时器的时钟要是APB时钟的两倍。所以APB1下时钟频率为120MHz,也就是TIMER1,2,3,4,5,6,11,12,13是120MHz。另一路定时器时钟从APB2过来,这些定时器也要乘以2,所以TIMER0,7,8,9,10时钟频率为240MHz。

//定时器12时钟频率配置关键代码
timer_param_type.period = 1000-1;   //4000Hz
timer_param_type.prescaler = 30-1;   //120Mhz/30=4000000Hz
  1. beep_pwm_gpio_init(): 这个函数负责初始化控制蜂鸣器的GPIO引脚和复用为PWM输出模式。
  2. beep_timer_init(): 这个函数负责初始化PWM的定时器。首先启动蜂鸣器定时器的时钟,然后初始化定时器的参数,包括计数方式(向上计数)、预分频器值(用来降低定时器的频率)、计数器的最大值(用来设置PWM的频率),然后初始化定时器通道的参数,设置PWM的极性(高电平有效)、使能输出、禁止反向输出,最后配置定时器通道为PWM模式并使能定时器。
  3. buzzer_beep_set(uint16_t _tone_freq, uint8_t _volume): 这个函数用来设置蜂鸣器的音调和音量。参数_tone_freq用来设置PWM的频率,也就是音调;参数_volume用来设置PWM的占空比,也就是音量。这个函数内部会重新计算PWM的周期和脉冲值,并重新配置定时器的参数。
  4. beep_init(): 这个函数负责初始化蜂鸣器,它会调用上面的两个初始化函数,初始化GPIO引脚和PWM定时器。

简单用法是这样的:首先调用beep_init()函数来初始化蜂鸣器,然后调用buzzer_beep_set()函数来设置蜂鸣器的音调和音量。但是这个蜂鸣器驱动还不能做到让蜂鸣器随意发出设置的音调和时间(这里指的是单纯通过延时,调整占空比和频率这个实现起来麻烦也不容易理解),需要再实现一个上层的驱动(有了这个驱动就可以实现一个简单的蜂鸣器播放器了),会在下一篇文档中介绍。

简单使用如下:

int main(void)
{
    board_init();
	beep_init();

	uint16_t _freq = 4000;
	uint8_t _volume = 50;
	uint32_t _on_time = 200000;
	uint32_t _off_time = 1000000;
	
    while (1)
    {
		buzzer_beep_set(_freq,_volume);
		delay_us(_on_time);
		buzzer_beep_set(0,0);
		delay_us(_off_time);
    }
}

大家可以实际试试,改变PWM频率和占空比,蜂鸣器的叫声会怎样变化。

  • 音色主要由频率决定。可以通过改变驱动蜂鸣器的信号频率来改变音色。
  • 音量由信号的振幅决定。占空比越大,音量越大。

用无源蜂鸣器就可以做一些有趣的实验了:

比如让他播放一首小星星:

// 定义音符频率
#define NOTE_C4 261
#define NOTE_D4 293
#define NOTE_E4 329
#define NOTE_F4 349
#define NOTE_G4 392
#define NOTE_A4 440
#define NOTE_B4 493

// 定义"小星星"的音符和节奏,这些值代表了歌曲的旋律
int melody[] = {
  NOTE_C4, NOTE_C4, NOTE_G4, NOTE_G4, NOTE_A4, NOTE_A4, NOTE_G4,
  NOTE_F4, NOTE_F4, NOTE_E4, NOTE_E4, NOTE_D4, NOTE_D4, NOTE_C4,
  NOTE_G4, NOTE_G4, NOTE_F4, NOTE_F4, NOTE_E4, NOTE_E4, NOTE_D4,
  NOTE_G4, NOTE_G4, NOTE_F4, NOTE_F4, NOTE_E4, NOTE_E4, NOTE_D4,
  NOTE_C4, NOTE_C4, NOTE_G4, NOTE_G4, NOTE_A4, NOTE_A4, NOTE_G4,
  NOTE_F4, NOTE_F4, NOTE_E4, NOTE_E4, NOTE_D4, NOTE_D4, NOTE_C4
};
//定义每个音符的节奏,这些值代表了每个音符的时长
int noteDurations[] = {
  4, 4, 4, 4, 4, 4, 2,
  4, 4, 4, 4, 4, 4, 2,
  4, 4, 4, 4, 4, 4, 2,
  4, 4, 4, 4, 4, 4, 2,
  4, 4, 4, 4, 4, 4, 2,
  4, 4, 4, 4, 4, 4, 2
};

int main(void)
{
    board_init();//初始化开发板,配置所需的时钟和硬件资源
	beep_init(); // 初始化蜂鸣器,其实就是配置PWM

    // 计算歌曲的长度,即音符的数量
	int songLength = sizeof(melody) / sizeof(melody[0]);

    while (1)// 主循环,不断重复播放歌曲
    {
        // 遍历歌曲中的每个音符
        for (int thisNote = 0; thisNote < songLength; thisNote++)
        {
            // 计算当前音符的时长,单位为毫秒
            int noteDuration = 1000 / noteDurations[thisNote];
            // 设置蜂鸣器的频率和音量,播放当前音符
            buzzer_beep_set(melody[thisNote], 100);

            // 为了区分音符,每个音符之间有短暂的停顿
            // 使用微秒延时函数,因此乘以1000将毫秒转换为微秒
            delay_us(noteDuration * 1000);
        }
    }
}

好像有点难听,不确定的话再听一听。

上面用了delay_us,执行到这个函数的时候cpu就阻塞了。在这个基础上再添加别的逻辑代码就很难维护了,所以后面会再实现一个非阻塞的蜂鸣器驱动,我们只需要定义好要让蜂鸣器工作的频率,占空比,响的时间,停的时间,然后开一个软件定时器定时调用它就行了。会在下一节进行介绍。

马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
C
1
https://gitee.com/Chenry-gitee/public_bare_medical_car.git
git@gitee.com:Chenry-gitee/public_bare_medical_car.git
Chenry-gitee
public_bare_medical_car
裸机版基于立创梁山派的21年电赛F题智能送药小车
master

搜索帮助