Portapack应用开发教程 (十六) Debug程序 C 声卡芯片wm8731和ak4951

我前面改了max2875的驱动代码,可以从max2875驱动输出不同内容到debug屏幕上了。验证了之前看的调用关系都是对的。

但是max2875芯片和rffc507x芯片的代码我还看不太懂。无法仿照他们来实现flash芯片的驱动(都是spi的)。

我还是决定从声卡芯片入手,因为他们是iic的,我在调试小四轴飞控时对mpu6050的操作也是经过iic的。另外,前段时间加入内置喇叭和麦克风时我也对声卡芯片比较熟悉了。

其实Portapack声卡芯片有两种,老款和新款板子的声卡芯片是不一样的。对于两种芯片,portapack固件都有支持。

左边是老款的右边是新款的板子,可以看到高亮部分的声卡芯片名称是不一样的。

 

点进去以后如下:

 

左边老款芯片的寄存器只有10个,右边有大约50个。

接下来看程序

firmware/application/portapack.cpp

WM8731 audio_codec_wm8731 { i2c0, 0x1a };
AK4951 audio_codec_ak4951 { i2c0, 0x12 };

enum class PortaPackModel {
	R1_20150901,
	R2_20170522,
};

static PortaPackModel portapack_model() {
	static Optional<PortaPackModel> model;

	if( !model.is_valid() ) {
		if( audio_codec_wm8731.detected() ) {
			model = PortaPackModel::R1_20150901;
		} else {
			model = PortaPackModel::R2_20170522;
		}
	}

	return model.value();
}

static audio::Codec* portapack_audio_codec() {
	return (portapack_model() == PortaPackModel::R2_20170522)
		? static_cast<audio::Codec*>(&audio_codec_ak4951)
		: static_cast<audio::Codec*>(&audio_codec_wm8731)
		;
}

这个代码开头两行指定了2种芯片的iic地址。

然后判断是哪个声卡芯片,看看能否检测到wm8731来判断portapack板子型号。

接下来可以看看firmware/common/下的wm8731.cpp wm8731.hpp和ak4951.cpp ak4951.hpp

先看wm8731.cpp看看detected函数是如何工作的

bool WM8731::detected() {
	return reset();
}

bool WM8731::reset() {
	return write(0x0f, 0);
}

bool WM8731::write(const Register reg) {
	return write(toUType(reg), map.w[toUType(reg)]);
}

bool WM8731::write(const address_t reg_address, const reg_t value) {
	const uint16_t word = (reg_address << 9) | value;
	const std::array<uint8_t, 2> values {
		static_cast<uint8_t>(word >> 8),
		static_cast<uint8_t>(word & 0xff),
	};
	return bus.transmit(bus_address, values.data(), values.size());
}

detected调用reset,reset调用write,write最终会调用到bus.transmit,第一个参数bus_address就是前面提到的iic地址。

如果声卡芯片指定iic地址能发送成功,代表能找到这个声卡芯片。

 

注意看一下上面代码里有2个write,其实reset里直接调用了下边这个write。上面这个write内部也在调用下边的write。

下面的write有2个参数,一个是寄存器地址,另一个是要写入寄存器的值,从这里可以理解map.w是什么意思。

map.w[toUType(reg)],应该是可以从内存的map变量里把寄存器对应的值读出来,然后从iic总线上发给要控制的芯片。toUType只是类型转换。

在wm8731.hpp里面,有代码在创建这个map变量,代码如下:

struct Register_Type {
	LeftLineIn					left_line_in;
	RightLineIn					right_line_in;
	LeftHeadphoneOut			left_headphone_out;
	RightHeadphoneOut			right_headphone_out;
	AnalogAudioPathControl		analog_audio_path_control;
	DigitalAudioPathControl		digital_audio_path_control;
	PowerDownControl			power_down_control;
	DigitalAudioInterfaceFormat	digital_audio_interface_format;
	SamplingControl				sampling_control;
	ActiveControl				active_control;
};

static_assert(sizeof(Register_Type) == reg_count * sizeof(reg_t), "Register_Type wrong size");

struct RegisterMap {
	constexpr RegisterMap(
		Register_Type values
	) : r(values)
	{
	}

	union {
		Register_Type r;
		std::array<reg_t, reg_count> w;
	};
};

static_assert(sizeof(RegisterMap) == reg_count * sizeof(reg_t), "RegisterMap type wrong size");

constexpr RegisterMap default_after_reset { Register_Type {
	.left_line_in = {
		.linvol		= 0b10111,
		.reserved0	= 0b00,
		.linmute	= 0b1,
		.lrinboth	= 0b0,
		.reserved1 	= 0,
	},
	.right_line_in = {
		.rinvol		= 0b10111,
		.reserved0	= 0b00,
		.rinmute	= 0b1,
		.rlinboth	= 0b0,
		.reserved1 	= 0,
	},
	.left_headphone_out = {
		.lhpvol		= 0b1111001,
		.lzcen		= 0b0,
		.lrhpboth	= 0b0,
		.reserved0	= 0,
	},
	.right_headphone_out = {
		.rhpvol		= 0b1111001,
		.rzcen		= 0b0,
		.rlhpboth	= 0b0,
		.reserved0	= 0,
	},
	.analog_audio_path_control = {
		.micboost	= 0b0,
		.mutemic	= 0b1,
		.insel		= 0b0,
		.bypass		= 0b1,
		.dacsel		= 0b0,
		.sidetone	= 0b0,
		.sideatt	= 0b00,
		.reserved0	= 0,
	},
	.digital_audio_path_control = {
		.adchpd		= 0b0,
		.deemp		= 0b00,
		.dacmu		= 0b1,
		.hpor		= 0b0,
		.reserved0	= 0,
	},
	.power_down_control = {
		.lineinpd	= 0b1,
		.micpd		= 0b1,
		.adcpd		= 0b1,
		.dacpd		= 0b1,
		.outpd		= 0b1,
		.oscpd		= 0b0,
		.clkoutpd	= 0b0,
		.poweroff	= 0b1,
		.reserved0	= 0,
	},
	.digital_audio_interface_format = {
		.format		= 0b10,
		.iwl		= 0b10,
		.lrp		= 0b0,
		.lrswap		= 0b0,
		.ms			= 0b0,
		.bclkinv	= 0b0,
		.reserved0	= 0,
	},
	.sampling_control = {
		.usb_normal	= 0b0,
		.bosr		= 0b0,
		.sr			= 0b0000,
		.clkidiv2	= 0b0,
		.clkodiv2	= 0b0,
		.reserved0	= 0,
	},
	.active_control = {
		.active		= 0b0,
		.reserved0	= 0,
	},
} };


	RegisterMap map { default_after_reset };

这种做法其实有点奇怪的,map.w是从map变量里读取而map.r是往map变量里写入。跟常人理解是反的。

接下来看看wm8731.cpp代码

代码不多,先看init也就是初始化函数,是在设置芯片的各部分。结构都类似,我找出其中一段,看看怎样调用

write(PowerDownControl {
		.lineinpd = 1,	
		.micpd = 0,
		.adcpd = 0,
		.dacpd = 0,
		.outpd = 0,
		.oscpd = 1,
		.clkoutpd = 1,
		.poweroff = 0,
		.reserved0 = 0,
	});

这个init里调用的write函数其实不是前面提到的那2个。

而是下面这段代码第一个void WM8731::write(const PowerDownControl value) 函数,然后第一个函数再调用第二个write函数,最后再调用第三个wriite函数。

void WM8731::write(const PowerDownControl value) {
	map.r.power_down_control = value;
	write(Register::PowerDownControl);
}

bool WM8731::write(const Register reg) {
	return write(toUType(reg), map.w[toUType(reg)]);
}

bool WM8731::write(const address_t reg_address, const reg_t value) {
	const uint16_t word = (reg_address << 9) | value;
	const std::array<uint8_t, 2> values {
		static_cast<uint8_t>(word >> 8),
		static_cast<uint8_t>(word & 0xff),
	};
	return bus.transmit(bus_address, values.data(), values.size());
}

而且第一个write函数,先要把值往内存里的map变量里写,到第二个write函数时再从map里读出来,发给第三个write函数。这样的实现好奇怪,不知道为什么一定要经过map这一步,而不是直接在iic总线上发给芯片。

uint32_t WM8731::reg_read(const size_t reg_address) {
	return map.w[reg_address];
}

这里还设有个读取寄存器的函数,它其实也是从内存里的map变量中读出来的,而不是直接在iic总线上访问对应芯片读出来的。

我估计map变量的作用应该还是为了读以前的配置用的,毕竟直接从mcu的内存里读比从别的芯片里读方便多了。

接下来看看第二种声卡芯片,打开ak4951.hpp

可以看到它比第一种芯片(wm8731)复杂一些,但是芯片设置部分的代码实现思路是一样的。

ak4951的init函数里直接在设置map.r,所以相当于不需要wm8731的第一行的write函数了,然后ak芯片里的update函数,相当于wm芯片里的第二行write函数。

而ak芯片里的write函数和wm芯片的第三行write函数是一样的。

void AK4951::write(const address_t reg_address, const reg_t value) {
	const std::array<uint8_t, 2> tx { reg_address, value };
	bus.transmit(bus_address, tx.data(), tx.size());
}

tx变量就相当于之前的values变量,只是写法更简洁。

不过ak芯片的寄存器读取功能的实现方式区别就比较大了。

reg_t AK4951::read(const address_t reg_address) {
	const std::array<uint8_t, 1> tx { reg_address };
	std::array<uint8_t, 1> rx { 0x00 };
	bus.transmit(bus_address, tx.data(), tx.size());
	bus.receive(bus_address, rx.data(), rx.size());
	return rx[0];
}

这回是在iic总线上先发出要查询的寄存器地址,然后再从iic总线上接收来自ak芯片的回答。而不是直接从内存里的map变量得到结果。

芯片初始化的时候就已经有了bus_address的数据。然后是在bus.transmit(bus_address,...,...)函数调用的时候,把这个iic的bus变量与bus_address联系在了一起,bus.receive同理。

这样声卡芯片的驱动代码就看得差不多了,接下去可以看看spi总线是不是也用差不多方式实现通信的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值