
鸿蒙FM收音机小工具开发指南 原创
鸿蒙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的分布式能力,该系统可以实现手机、平板、智能手表等多设备的频道同步和控制,为用户提供无缝的收音机体验。
