鸿蒙FM收音机小工具开发指南 原创

进修的泡芙
发布于 2025-6-23 13:25
浏览
0收藏

鸿蒙FM收音机小工具开发指南

一、系统架构设计

基于HarmonyOS的FM收音机系统采用四层架构:
射频层:FM调谐器控制与信号处理

音频层:音频输出与耳机检测

存储层:频道记忆与用户偏好

交互层:用户界面与多设备同步

!https://example.com/harmony-fmradio-arch.png

二、核心代码实现
射频模块低功耗控制

// RadioController.ets
import driver from ‘@ohos.driver’;
import powerManagement from ‘@ohos.powerManagement’;

class FMRadioController {
private static instance: FMRadioController = null;
private i2cDriver: driver.I2C;
private currentFrequency: number = 87.5; // 默认87.5MHz
private isPoweredOn: boolean = false;
private powerMode: ‘NORMAL’ | ‘LOW_POWER’ = ‘NORMAL’;
private powerManager: powerManagement.PowerManager;

// 射频芯片寄存器配置
private readonly CHIP_ADDRESS = 0x22;
private readonly POWER_REG = 0x00;
private readonly FREQ_REG = 0x01;
private readonly VOLUME_REG = 0x02;

constructor() {
this.initDriver();
this.powerManager = powerManagement.createPowerManager();
this.checkPowerMode();
public static getInstance(): FMRadioController {

if (!FMRadioController.instance) {
  FMRadioController.instance = new FMRadioController();

return FMRadioController.instance;

private initDriver(): void {

try {
  this.i2cDriver = driver.createI2C({
    bus: 'I2C_2',
    address: this.CHIP_ADDRESS
  });

catch (err) {

  console.error('I2C驱动初始化失败:', JSON.stringify(err));

}

// 检查电源模式
private checkPowerMode(): void {
const mode = this.powerManager.getPowerMode();
this.powerMode = mode === powerManagement.PowerMode.POWER_SAVE ? ‘LOW_POWER’ : ‘NORMAL’;
this.adjustPowerSettings();
// 调整电源设置

private adjustPowerSettings(): void {
if (this.powerMode === ‘LOW_POWER’) {
this.writeRegister(this.POWER_REG, 0x1F); // 低功耗模式
else {

  this.writeRegister(this.POWER_REG, 0x3F); // 正常模式

}

// 开机
public powerOn(): void {
this.writeRegister(this.POWER_REG, 0x01); // 启动芯片
this.isPoweredOn = true;
this.setFrequency(this.currentFrequency);
EventBus.emit(‘radioStateChange’, true);
// 关机

public powerOff(): void {
this.writeRegister(this.POWER_REG, 0x00); // 关闭芯片
this.isPoweredOn = false;
EventBus.emit(‘radioStateChange’, false);
// 设置频率

public setFrequency(freq: number): void {
if (freq < 87.5 || freq > 108) return;

this.currentFrequency = freq;
const freqValue = Math.round((freq - 87.5) * 20); // 转换为寄存器值

// 分两次写入16位频率值
this.writeRegister(this.FREQ_REG, freqValue >> 8);
this.writeRegister(this.FREQ_REG + 1, freqValue & 0xFF);

// 低功耗模式下降低更新频率
if (this.powerMode === 'LOW_POWER') {
  this.writeRegister(this.POWER_REG, 0x1F);

EventBus.emit(‘frequencyChanged’, freq);

// 获取当前频率

public getFrequency(): number {
return this.currentFrequency;
// 静音

public mute(): void {
this.writeRegister(this.VOLUME_REG, 0x00);
// 取消静音

public unmute(): void {
this.writeRegister(this.VOLUME_REG, 0x7F);
// 扫描电台

public scan(): Promise<Array<number>> {
return new Promise((resolve) => {
const foundStations: Array<number> = [];

  // 从当前频率开始扫描
  let freq = this.currentFrequency;
  const scanInterval = setInterval(() => {
    this.setFrequency(freq);
    
    // 检查信号强度
    const signal = this.readSignalStrength();
    if (signal > 30) { // 有效信号
      foundStations.push(freq);

freq += 0.1; // 步进0.1MHz

    if (freq > 108) {
      clearInterval(scanInterval);
      resolve(foundStations);

}, 100);

});

// 写入寄存器

private writeRegister(reg: number, value: number): void {
this.i2cDriver.write([reg, value]);
// 读取信号强度

private readSignalStrength(): number {
const value = this.i2cDriver.readRegister(0x05);
return value & 0x7F; // 返回0-127的信号强度
}

export const radioController = FMRadioController.getInstance();

频道记忆功能

// ChannelManager.ets
import preferences from ‘@ohos.data.preferences’;
import distributedData from ‘@ohos.distributedData’;

class ChannelManager {
private static instance: ChannelManager = null;
private pref: preferences.Preferences;
private readonly PREF_NAME = ‘fmradio_channels’;
private readonly MAX_CHANNELS = 20;
private currentChannelIndex: number = 0;

constructor() {
preferences.getPreferences(this.PREF_NAME)
.then((pref) => {
this.pref = pref;
this.initDefaultChannels();
});
public static getInstance(): ChannelManager {

if (!ChannelManager.instance) {
  ChannelManager.instance = new ChannelManager();

return ChannelManager.instance;

// 初始化默认频道

private async initDefaultChannels(): Promise<void> {
const hasChannels = await this.pref.has(‘channels’);
if (!hasChannels) {
const defaultChannels = [87.5, 88.7, 91.5, 93.7, 96.3, 98.7, 101.2, 103.9, 106.5];
await this.pref.put(‘channels’, JSON.stringify(defaultChannels));
await this.pref.flush();
}

// 获取所有频道
public async getChannels(): Promise<Array<number>> {
try {
const channelsStr = await this.pref.get(‘channels’, ‘[]’);
return JSON.parse(channelsStr);
catch (err) {

  console.error('获取频道列表失败:', JSON.stringify(err));
  return [];

}

// 添加频道
public async addChannel(freq: number): Promise<void> {
const channels = await this.getChannels();

// 去重
if (channels.includes(freq)) return;

// 限制最大数量
if (channels.length >= this.MAX_CHANNELS) {
  channels.shift();

channels.push(freq);

await this.saveChannels(channels);

// 删除频道

public async removeChannel(freq: number): Promise<void> {
const channels = await this.getChannels();
const index = channels.indexOf(freq);

if (index >= 0) {
  channels.splice(index, 1);
  await this.saveChannels(channels);

}

// 保存频道列表
private async saveChannels(channels: Array<number>): Promise<void> {
await this.pref.put(‘channels’, JSON.stringify(channels));
await this.pref.flush();

// 同步到其他设备
distributedData.syncData('channel_sync', {
  type: 'channel_update',
  payload: channels
});

// 切换到下一个频道

public async nextChannel(): Promise<number> {
const channels = await this.getChannels();
if (channels.length === 0) return 0;

this.currentChannelIndex = (this.currentChannelIndex + 1) % channels.length;
const freq = channels[this.currentChannelIndex];

radioController.setFrequency(freq);
return freq;

// 切换到上一个频道

public async prevChannel(): Promise<number> {
const channels = await this.getChannels();
if (channels.length === 0) return 0;

this.currentChannelIndex = (this.currentChannelIndex - 1 + channels.length) % channels.length;
const freq = channels[this.currentChannelIndex];

radioController.setFrequency(freq);
return freq;

// 处理同步过来的频道更新

public async handleChannelUpdate(newChannels: Array<number>): Promise<void> {
await this.pref.put(‘channels’, JSON.stringify(newChannels));
await this.pref.flush();

EventBus.emit('channelsUpdated', newChannels);

}

export const channelManager = ChannelManager.getInstance();

耳机插入检测

// AudioManager.ets
import driver from ‘@ohos.driver’;
import audio from ‘@ohos.audio’;

class AudioController {
private static instance: AudioController = null;
private headphoneJack: driver.GPIO;
private audioManager: audio.AudioManager;
private isHeadphonePlugged: boolean = false;

constructor() {
this.initAudio();
this.initJackDetection();
public static getInstance(): AudioController {

if (!AudioController.instance) {
  AudioController.instance = new AudioController();

return AudioController.instance;

private initAudio(): void {

this.audioManager = audio.createAudioManager();

// 设置音频参数
this.audioManager.setParameters({
  sampleRate: 44100,
  channelCount: 2,
  format: audio.AudioFormat.FORMAT_PCM_16BIT
});

private initJackDetection(): void {

try {
  this.headphoneJack = driver.createGPIO({
    bus: 'GPIO_3',
    direction: driver.GPIODirection.IN,
    edge: driver.GPIOEdge.BOTH
  });
  
  this.headphoneJack.on('change', (value) => {
    this.handleJackChange(value === 1);
  });
  
  // 初始状态检测
  this.isHeadphonePlugged = this.headphoneJack.read() === 1;

catch (err) {

  console.error('耳机检测初始化失败:', JSON.stringify(err));

}

// 处理耳机插拔事件
private handleJackChange(plugged: boolean): void {
if (plugged !== this.isHeadphonePlugged) {
this.isHeadphonePlugged = plugged;

  if (plugged) {
    this.switchToHeadphone();

else {

    this.switchToSpeaker();

EventBus.emit(‘headphoneStateChange’, plugged);

}

// 切换到耳机输出
private switchToHeadphone(): void {
this.audioManager.setOutputDevice(audio.AudioDevice.DEVICE_WIRED_HEADSET);
radioController.unmute();
// 切换到扬声器输出

private switchToSpeaker(): void {
this.audioManager.setOutputDevice(audio.AudioDevice.DEVICE_SPEAKER);
radioController.mute();
// 获取当前耳机状态

public isHeadphoneConnected(): boolean {
return this.isHeadphonePlugged;
// 设置音量

public setVolume(level: number): void {
level = Math.max(0, Math.min(100, level));
this.audioManager.setVolume(level / 100);
}

export const audioController = AudioController.getInstance();

主界面实现

// MainScreen.ets
import { radioController } from ‘./RadioController’;
import { channelManager } from ‘./ChannelManager’;
import { audioController } from ‘./AudioController’;

@Component
export struct MainScreen {
@State isRadioOn: boolean = false;
@State currentFreq: number = 87.5;
@State channelList: Array<number> = [];
@State isHeadphonePlugged: boolean = false;
@State volume: number = 50;

build() {
Column() {
// 状态显示
Row() {
Text(this.isRadioOn ? ‘FM收音机’ : ‘已关闭’)
.fontSize(24)
.fontColor(this.isRadioOn ? ‘#4CAF50’ : ‘#F44336’)

    Text(this.isHeadphonePlugged ? '耳机' : '扬声器')
      .margin({ left: 20 })

.padding(10)

  // 频率显示
  Text(${this.currentFreq.toFixed(1)} MHz)
    .fontSize(36)
    .margin({ top: 20 })
  
  // 频道列表
  List({ space: 10 }) {
    ForEach(this.channelList, (channel) => {
      ListItem() {
        Text(${channel.toFixed(1)} MHz)
          .fontSize(18)
          .onClick(() => {
            radioController.setFrequency(channel);
          })

})

.height(200)

  .width('100%')
  .margin({ top: 20 })
  
  // 控制按钮
  Row() {
    Button(this.isRadioOn ? '关闭' : '开启')
      .width(100)
      .onClick(() => {
        this.toggleRadio();
      })
    
    Button('扫描')
      .width(100)
      .margin({ left: 20 })
      .onClick(() => {
        this.scanChannels();
      })

.margin({ top: 30 })

  // 频道切换
  Row() {
    Button('上一个')
      .width(120)
      .onClick(() => {
        this.prevChannel();
      })
    
    Button('下一个')
      .width(120)
      .margin({ left: 20 })
      .onClick(() => {
        this.nextChannel();
      })

.margin({ top: 20 })

  // 音量控制
  Slider({
    value: this.volume,
    min: 0,
    max: 100,
    onChange: (value) => {
      this.setVolume(value);

})

  .width('80%')
  .margin({ top: 30 })

.width(‘100%’)

.height('100%')
.padding(20)

private toggleRadio(): void {

if (this.isRadioOn) {
  radioController.powerOff();

else {

  radioController.powerOn();

this.isRadioOn = !this.isRadioOn;

private async scanChannels(): Promise<void> {

const foundStations = await radioController.scan();
for (const freq of foundStations) {
  await channelManager.addChannel(freq);

this.channelList = await channelManager.getChannels();

private async prevChannel(): Promise<void> {

this.currentFreq = await channelManager.prevChannel();

private async nextChannel(): Promise<void> {

this.currentFreq = await channelManager.nextChannel();

private setVolume(level: number): void {

this.volume = level;
audioController.setVolume(level);

aboutToAppear() {

// 加载频道列表
this.loadChannels();

// 监听收音机状态
EventBus.on('radioStateChange', (isOn: boolean) => {
  this.isRadioOn = isOn;
});

// 监听频率变化
EventBus.on('frequencyChanged', (freq: number) => {
  this.currentFreq = freq;
});

// 监听频道更新
EventBus.on('channelsUpdated', (channels: Array<number>) => {
  this.channelList = channels;
});

// 监听耳机状态
EventBus.on('headphoneStateChange', (plugged: boolean) => {
  this.isHeadphonePlugged = plugged;
});

aboutToDisappear() {

EventBus.off('radioStateChange');
EventBus.off('frequencyChanged');
EventBus.off('channelsUpdated');
EventBus.off('headphoneStateChange');

private async loadChannels(): Promise<void> {

this.channelList = await channelManager.getChannels();

}

三、项目配置与权限

// module.json5
“module”: {

"requestPermissions": [

“name”: “ohos.permission.USE_RADIO”,

    "reason": "控制FM收音机模块"
  },

“name”: “ohos.permission.MANAGE_AUDIO”,

    "reason": "管理音频输出"
  },

“name”: “ohos.permission.USE_DRIVER_I2C”,

    "reason": "控制收音机芯片"
  },

“name”: “ohos.permission.USE_DRIVER_GPIO”,

    "reason": "检测耳机插拔"
  },

“name”: “ohos.permission.DISTRIBUTED_DATASYNC”,

    "reason": "同步频道数据"

],

"abilities": [

“name”: “MainAbility”,

    "type": "page",
    "visible": true

]

}

四、总结与扩展

本FM收音机小工具实现了三大核心功能:
智能电源管理:根据系统状态自动切换低功耗模式

频道记忆:支持20个预设频道存储与同步

自动音频切换:耳机插入自动切换输出设备

扩展方向:
RDS支持:显示电台名称和节目信息

录音功能:录制广播节目

睡眠定时:定时关闭收音机

闹钟功能:指定频率作为闹钟铃声

网络电台:集成网络电台流媒体

语音控制:支持语音换台和音量控制

通过HarmonyOS的分布式能力,该系统可以实现手机、平板、智能手表等多设备的频道同步和控制,为用户提供无缝的收音机体验。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐