极客巢V5A收音机是一款多功能、DIY友好的嵌入式收音机产品,它具备以下核心特性:
- 全波段接收: 支持调频 (FM)、调幅 (AM)、短波 (SW)、单边带 (SSB)、航空波段等多种无线电波段。
- 网络收音机 (固件升级): 通过刷固件的方式,可以扩展支持网络收音机功能,接收互联网上的音频流。
- 用户友好界面: 配备显示屏和按键/旋钮,提供直观的操作界面。
- 可扩展性: 预留硬件接口和软件架构,方便用户进行功能扩展和定制。
- 商业化产品: 已经商业化,意味着需要考虑产品的稳定性、可靠性和用户体验。
系统架构设计
针对极客巢V5A收音机的需求,我将采用分层架构的设计模式,这种架构模式能够有效地组织代码,提高代码的可维护性、可扩展性和可重用性。系统架构主要分为以下几个层次:
- 硬件抽象层 (HAL - Hardware Abstraction Layer)
- 板级支持包 (BSP - Board Support Package)
- 操作系统层 (OS - Operating System)
- 中间件层 (Middleware)
- 应用层 (Application)
1. 硬件抽象层 (HAL)
HAL层是系统架构的最底层,直接与硬件交互。它向上层提供统一的、抽象的硬件接口,屏蔽底层硬件的差异性。HAL层的主要职责包括:
- 初始化硬件外设: 例如,GPIO、SPI、I2C、UART、ADC、DAC、LCD控制器、音频编解码器、射频芯片等。
- 提供硬件操作接口: 例如,GPIO的读写操作、SPI/I2C的通信操作、ADC/DAC的采样和输出、LCD的显示控制、音频编解码器的音频数据传输、射频芯片的频率设置和模式切换等。
- 中断管理: 处理硬件中断,并将中断事件传递给上层。
HAL层代码示例 (hal_layer.h 和 hal_layer.c)
hal_layer.h
#ifndef HAL_LAYER_H
#define HAL_LAYER_H
#include <stdint.h>
#include <stdbool.h>
// GPIO 定义
typedef enum {
GPIO_PIN_0,
GPIO_PIN_1,
GPIO_PIN_2,
// ... 更多GPIO引脚定义
GPIO_PIN_MAX
} GPIO_PinTypeDef;
typedef enum {
GPIO_MODE_INPUT,
GPIO_MODE_OUTPUT
} GPIO_ModeTypeDef;
typedef enum {
GPIO_STATE_RESET,
GPIO_STATE_SET
} GPIO_StateTypeDef;
// SPI 定义
typedef enum {
SPI_INSTANCE_1,
SPI_INSTANCE_2,
SPI_INSTANCE_MAX
} SPI_InstanceTypeDef;
typedef enum {
SPI_MODE_MASTER,
SPI_MODE_SLAVE
} SPI_ModeTypeDef;
typedef struct {
SPI_InstanceTypeDef Instance;
SPI_ModeTypeDef Mode;
uint32_t BaudRate;
// ... 其他SPI配置参数
} SPI_InitTypeDef;
// 函数声明
// GPIO 函数
void HAL_GPIO_Init(GPIO_PinTypeDef Pin, GPIO_ModeTypeDef Mode);
void HAL_GPIO_WritePin(GPIO_PinTypeDef Pin, GPIO_StateTypeDef State);
GPIO_StateTypeDef HAL_GPIO_ReadPin(GPIO_PinTypeDef Pin);
// SPI 函数
bool HAL_SPI_Init(SPI_InitTypeDef *SPI_InitStruct);
bool HAL_SPI_Transmit(SPI_InstanceTypeDef Instance, uint8_t *pData, uint16_t Size);
bool HAL_SPI_Receive(SPI_InstanceTypeDef Instance, uint8_t *pData, uint16_t Size);
// I2C 函数 (示例,根据实际硬件添加)
// ...
// UART 函数 (示例,根据实际硬件添加)
// ...
// ADC 函数 (示例,根据实际硬件添加)
// ...
// DAC 函数 (示例,根据实际硬件添加)
// ...
// LCD 控制器函数 (示例,根据实际硬件添加)
// ...
// 音频编解码器函数 (示例,根据实际硬件添加)
// ...
// 射频芯片函数 (示例,根据实际硬件添加)
// ...
#endif // HAL_LAYER_H
hal_layer.c
#include "hal_layer.h"
// 硬件相关的头文件,例如 STM32 的头文件
// #include "stm32xxx.h" // 假设使用 STM32 平台
// GPIO 函数实现
void HAL_GPIO_Init(GPIO_PinTypeDef Pin, GPIO_ModeTypeDef Mode) {
// 根据 Pin 和 Mode 配置 GPIO 寄存器
// 例如,配置 GPIO 的方向、上下拉电阻、速度等
// 具体实现需要参考硬件平台的 GPIO 寄存器定义和操作方法
// 这里仅为示例,实际代码会更复杂
if (Mode == GPIO_MODE_OUTPUT) {
// 设置为输出模式
// ... 硬件寄存器操作
} else if (Mode == GPIO_MODE_INPUT) {
// 设置为输入模式
// ... 硬件寄存器操作
}
}
void HAL_GPIO_WritePin(GPIO_PinTypeDef Pin, GPIO_StateTypeDef State) {
// 根据 Pin 和 State 控制 GPIO 输出高低电平
// 例如,控制 LED 灯的亮灭
if (State == GPIO_STATE_SET) {
// 设置为高电平
// ... 硬件寄存器操作
} else {
// 设置为低电平
// ... 硬件寄存器操作
}
}
GPIO_StateTypeDef HAL_GPIO_ReadPin(GPIO_PinTypeDef Pin) {
// 读取 GPIO 引脚的电平状态
// 返回 GPIO_STATE_SET (高电平) 或 GPIO_STATE_RESET (低电平)
// ... 硬件寄存器操作
// 假设读取到的电平状态为 level
GPIO_StateTypeDef level = GPIO_STATE_RESET; // 示例,实际读取硬件状态
if (/* level is high */ 1) {
// 示例条件,根据实际硬件判断高电平条件
level = GPIO_STATE_SET;
}
return level;
}
// SPI 函数实现
bool HAL_SPI_Init(SPI_InitTypeDef *SPI_InitStruct) {
// 根据 SPI_InitStruct 配置 SPI 寄存器
// 例如,配置 SPI 的时钟频率、模式、数据位宽、极性、相位等
// 具体实现需要参考硬件平台的 SPI 寄存器定义和操作方法
// 这里仅为示例
if (SPI_InitStruct->Instance == SPI_INSTANCE_1) {
// 初始化 SPI1
// ... 硬件寄存器操作配置 SPI1
return true; // 初始化成功
} else if (SPI_InitStruct->Instance == SPI_INSTANCE_2) {
// 初始化 SPI2
// ... 硬件寄存器操作配置 SPI2
return true; // 初始化成功
} else {
return false; // 不支持的 SPI 实例
}
}
bool HAL_SPI_Transmit(SPI_InstanceTypeDef Instance, uint8_t *pData, uint16_t Size) {
// 通过指定的 SPI 实例发送数据
// 将 pData 指向的数据发送 Size 个字节
// 需要处理 SPI 的发送 FIFO、状态寄存器等
// 这里仅为示例
if (Instance == SPI_INSTANCE_1) {
// 使用 SPI1 发送数据
for (uint16_t i = 0; i < Size; i++) {
// 等待发送缓冲区空
// ... 硬件寄存器操作,等待 TXE 标志位
// 发送数据
// ... 硬件寄存器操作,写入数据到数据寄存器
}
return true; // 发送成功
} else if (Instance == SPI_INSTANCE_2) {
// 使用 SPI2 发送数据
// ... SPI2 发送实现
return true; // 发送成功
} else {
return false; // 不支持的 SPI 实例
}
}
bool HAL_SPI_Receive(SPI_InstanceTypeDef Instance, uint8_t *pData, uint16_t Size) {
// 通过指定的 SPI 实例接收数据
// 接收 Size 个字节的数据,存储到 pData 指向的缓冲区
// 需要处理 SPI 的接收 FIFO、状态寄存器等
// 这里仅为示例
if (Instance == SPI_INSTANCE_1) {
// 使用 SPI1 接收数据
for (uint16_t i = 0; i < Size; i++) {
// 等待接收缓冲区非空
// ... 硬件寄存器操作,等待 RXNE 标志位
// 接收数据
// ... 硬件寄存器操作,从数据寄存器读取数据
pData[i] = /* 读取到的数据 */;
}
return true; // 接收成功
} else if (Instance == SPI_INSTANCE_2) {
// 使用 SPI2 接收数据
// ... SPI2 接收实现
return true; // 接收成功
} else {
return false; // 不支持的 SPI 实例
}
}
// ... 其他 HAL 函数的实现 (I2C, UART, ADC, DAC, LCD, 音频编解码器, 射频芯片)
// ... 这些函数的实现方式类似,都需要直接操作硬件寄存器
// ... 具体实现需要参考硬件平台的手册和寄存器定义
2. 板级支持包 (BSP)
BSP层构建在HAL层之上,它针对具体的硬件平台进行定制,提供更高级别的、与硬件平台相关的驱动和服务。BSP层的主要职责包括:
- 系统时钟初始化: 配置系统时钟,为各个模块提供时钟源。
- 外设驱动初始化: 初始化板载外设,例如,LCD屏幕、按键、旋钮、音频接口、射频模块等。 这层会调用 HAL 层的函数来初始化和配置硬件。
- 提供板级功能接口: 例如,LCD屏幕的初始化和显示、按键的扫描和事件处理、旋钮的编码值读取、音频接口的输入输出控制、射频模块的初始化和控制等。
BSP层代码示例 (bsp_layer.h 和 bsp_layer.c)
bsp_layer.h
#ifndef BSP_LAYER_H
#define BSP_LAYER_H
#include "hal_layer.h"
// LCD 驱动定义
typedef struct {
uint16_t Width;
uint16_t Height;
// ... 其他 LCD 相关参数
} LCD_InitTypeDef;
// 按键定义
typedef enum {
BUTTON_KEY1,
BUTTON_KEY2,
BUTTON_KEY3,
// ... 更多按键定义
BUTTON_KEY_MAX
} Button_TypeDef;
// 旋钮定义 (假设是编码器)
typedef enum {
ENCODER_1,
ENCODER_2,
ENCODER_MAX
} Encoder_TypeDef;
// 音频接口定义 (简化示例)
typedef enum {
AUDIO_OUTPUT_SPEAKER,
AUDIO_OUTPUT_HEADPHONE
} AudioOutput_TypeDef;
// 射频模块定义 (简化示例)
typedef enum {
RF_MODULE_SI4735, // 假设使用 SI4735 芯片
// ... 其他射频模块类型
RF_MODULE_MAX
} RFModule_TypeDef;
// 函数声明
// 系统时钟初始化
void BSP_SystemClock_Init(void);
// LCD 驱动函数
bool BSP_LCD_Init(LCD_InitTypeDef *LCD_InitStruct);
void BSP_LCD_DisplayString(uint16_t x, uint16_t y, const char *str);
void BSP_LCD_ClearScreen(uint16_t color);
void BSP_LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color);
// 按键驱动函数
void BSP_Button_Init(Button_TypeDef Button);
bool BSP_Button_IsPressed(Button_TypeDef Button);
// 旋钮驱动函数
void BSP_Encoder_Init(Encoder_TypeDef Encoder);
int32_t BSP_Encoder_GetValue(Encoder_TypeDef Encoder);
// 音频接口函数
bool BSP_Audio_OutputSelect(AudioOutput_TypeDef Output);
bool BSP_Audio_SetVolume(uint8_t volume); // 0-100%
// 射频模块函数
bool BSP_RFModule_Init(RFModule_TypeDef Module);
bool BSP_RFModule_SetFrequency(RFModule_TypeDef Module, float frequency_MHz);
bool BSP_RFModule_SetMode(RFModule_TypeDef Module, const char *mode_str); // 例如 "FM", "AM", "SSB"
float BSP_RFModule_GetSignalStrength(RFModule_TypeDef Module);
#endif // BSP_LAYER_H
bsp_layer.c
#include "bsp_layer.h"
// 宏定义,根据实际硬件平台定义
#define LCD_SPI_INSTANCE SPI_INSTANCE_1
#define LCD_CS_PIN GPIO_PIN_0 // 假设 LCD CS 引脚
#define LCD_RS_PIN GPIO_PIN_1 // 假设 LCD RS 引脚
#define LCD_RESET_PIN GPIO_PIN_2 // 假设 LCD RESET 引脚
#define BUTTON1_PIN GPIO_PIN_3 // 假设 Button 1 引脚
#define BUTTON2_PIN GPIO_PIN_4 // 假设 Button 2 引脚
#define BUTTON3_PIN GPIO_PIN_5 // 假设 Button 3 引脚
#define ENCODER1_PIN_A GPIO_PIN_6 // 假设 Encoder 1 A 相引脚
#define ENCODER1_PIN_B GPIO_PIN_7 // 假设 Encoder 1 B 相引脚
#define AUDIO_OUTPUT_CTRL_PIN GPIO_PIN_8 // 假设音频输出切换控制引脚
#define RF_MODULE_SPI_INSTANCE SPI_INSTANCE_2
#define RF_MODULE_CS_PIN GPIO_PIN_9 // 假设 RF 模块 CS 引脚
#define RF_MODULE_RESET_PIN GPIO_PIN_10 // 假设 RF 模块 RESET 引脚
// 系统时钟初始化
void BSP_SystemClock_Init(void) {
// 配置系统时钟,例如使用外部晶振、内部RC振荡器等
// 并配置 PLL 倍频,设置 CPU 主频、外设时钟频率
// ... 具体实现根据硬件平台和时钟树配置
// 示例代码,假设使用外部晶振 8MHz,倍频到 72MHz
// ... 配置 RCC 寄存器
}
// LCD 驱动函数
bool BSP_LCD_Init(LCD_InitTypeDef *LCD_InitStruct) {
// LCD 初始化
// 1. 初始化 LCD 控制器 SPI 接口
SPI_InitTypeDef spi_init;
spi_init.Instance = LCD_SPI_INSTANCE;
spi_init.Mode = SPI_MODE_MASTER;
spi_init.BaudRate = /* 根据 LCD 规格设置 SPI 频率 */ 10000000; // 10MHz
// ... 其他 SPI 配置
if (!HAL_SPI_Init(&spi_init)) {
return false; // SPI 初始化失败