解码器采样率/位深是如何得到的?
目前网上廉价机/套件的采样率获取有两种办法:1·前端模块/芯片有采样率指示
主要是USB模块——SA9227、Amanero、XMOS均有采样率/格式显示脚,单片机监控这几个脚位上拉下拉电位就行了。
2·前端模块/芯片没有采样率指示
主要是SPDIF这里有问题——CS8416仅有>96K脚指示,残疾。DIR9001仅有44/48指示,方便使用外部晶振切换,但实测DIR9001能接收352K你能信?WM8805/8804可以读里面的寄存器值自己算,非常愚蠢。所以SPDIF/DOP采样率指示一直是个难题。
还有一类,是类似SA9227/CT7601这类USB-Audio模块,也能SPDIF解码,当然也就能使用这几种芯片的采样率指示了,但固件不太完美,允许切换信号源的非常少,得手搓信号输入切换开关,同样非常愚蠢。
那行业内一般是如何处理的?
大家看到产品DAC里一般都会有个FPGA放在DAC芯片前边,这个玩意一般是起到多功能MCU处理器的作用,付款即可以买到各种算法模块刷进去,比如开机后初始化、控制静音、屏幕刷写、按键输入,还有最重要的信号路由(对,很多机器FPGA仅负责路由信号)等。当然还有个别的把ASRC异步采样也做进去,不论你给的信号是多少采样率/格式,最后统一ASRC成PCM 44.1K喂给DAC芯片,这也是商品DAC性能指标能做高的原因——DAC芯片只有在PCM 44.1K下才能发挥最高指标。一般来说FPGA做的还不如专用ASRC芯片自己的PLL准,比如古老的CS8421+低Jitter时钟生成芯片。
当然喽,顺便计算一下输入采样率/位宽也就是理所当然的事。
隔了个FPGA除了看起来牛逼,还能把FPGA固件当作解决方案包采购,非常节约团队资源,可以只安排一两个人负责测试、外包沟通等,核心的东西只由一个工程师或老板掌握,安全性很高,其余雇员都负责进销存。
然而这种模式对于DIY几乎没有意义。
实际应用中是用单片机的定时器/计数器监控LRCK/BCLK两个脚,但这个解决方案不好卖,同行没有喜欢用的。自己DIY,干脆就用单片机监控LRCK/SCLK比较实用,它连DSD/PCM指示脚都不需要。处理逻辑非常傻瓜化,而且能实现源码直接路由给DAC,只是代码外行不好写。
处理逻辑是这样的:
使用单片机某两个GPIO空脚做监控脚,隔电阻接到LRCK和BCLK上,鉴于LRCK在DSD码中会变成DSD数据脚,因此处理逻辑是BCLK/DSDSCK判断优先。
单片机内部计数器设定为满1024次就清零重计,同时触发一次中断,中断语句执行计算频率,频率值放一个简单的数字变量flag与上一次暂存的flag对比,只要比值变化了超过3次(<1ms),就证明输入信号格式变化了,拉静音。
然后看一下LRCK脚计数器,如果LRCK是典型的PCM频率,比如44.1K、48K、88.2K、96K、176.4K、192K、352.8K、384K,则判断为PCM,丢Switch判断查表。如果LRCK数值不是典型值,但频率远远高于384K,就判断为DSD,查表可知3种DSD频率。
查表后置DAC状态,执行之后隔1ms静音解除,再执行LCD/OLED屏幕显示程序。
我爱用国产STC8H8K64做这个工作,因为太便宜了,2块钱不到1片,内部RC总时钟虽然不太准,但最高可以跑到27M不带喘气的,省了一个晶振,内置64K内存,可以足够储存OLED 25664显示的数据(可能图像会有点吃力)。总有人说ESP32太便宜了,Arduino太便宜了,国产ARM M0太便宜了,但具体使用起来,不是脚位不够用,就是频率不够高,比如Arduino nano默认的频率就是16M,判断11M主频是不够用的。 串入DIX9211应该能解决提到的问题。 好方法 根据你所提供的方法思路与要求试着让AI写了代码
#include <STC8H.H>
#include <intrins.h>
// 定义OLED相关引脚
#define OLED_SCL P1_0// SCL时钟线
#define OLED_SDA P1_1// SDA数据线
#define OLED_RST P1_2// 复位引脚
#define OLED_DCP1_3// 数据/命令选择引脚
// 定义监控引脚
#define LRCK_PIN P2_0// 左/右时钟引脚
#define BCLK_PIN P2_1// 位时钟引脚
// 定义音频格式类型
#define FORMAT_UNKNOWN 0
#define FORMAT_PCM 1
#define FORMAT_DSD 2
// 定义静音控制引脚
#define MUTE_PIN P3_0
// 全局变量
unsigned int bclk_counter = 0; // BCLK计数器
unsigned int lrck_counter = 0; // LRCK计数器
unsigned int bclk_flag = 0; // BCLK频率标志
unsigned int lrck_flag = 0; // LRCK频率标志
unsigned int prev_bclk_flag = 0; // 上一次BCLK频率标志
unsigned int format_changed_count = 0; // 格式变化计数
unsigned char audio_format = FORMAT_UNKNOWN; // 当前音频格式
unsigned long sample_rate = 0; // 当前采样率
unsigned char mute_flag = 0; // 静音标志
unsigned char oled_init_flag = 0; // OLED初始化标志
// PCM典型采样率列表
const unsigned long pcm_rates[] = {44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000};
// DSD采样率列表
const unsigned long dsd_rates[] = {2822400, 5644800, 11289600};
// OLED相关函数声明
void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowString(unsigned char x, unsigned char y, char *str);
void OLED_ShowNum(unsigned char x, unsigned char y, unsigned long num, unsigned char len);
void OLED_Refresh(void);
void OLED_DisplayAudioInfo(void);
// 延时函数
void Delay10us(unsigned int t)
{
unsigned int i, j;
for(i = 0; i < t; i++)
for(j = 0; j < 3; j++);
}
// IIC通信函数
void IIC_Start(void)
{
OLED_SDA = 1;
OLED_SCL = 1;
Delay10us(1);
OLED_SDA = 0;
Delay10us(1);
OLED_SCL = 0;
}
void IIC_Stop(void)
{
OLED_SDA = 0;
OLED_SCL = 1;
Delay10us(1);
OLED_SDA = 1;
Delay10us(1);
}
bit IIC_WaitAck(void)
{
bit ackbit;
OLED_SDA = 1;
OLED_SCL = 1;
Delay10us(1);
ackbit = OLED_SDA;
OLED_SCL = 0;
Delay10us(1);
return ackbit;
}
void IIC_SendByte(unsigned char byt)
{
unsigned char i;
for(i = 0; i < 8; i++)
{
OLED_SCL = 0;
Delay10us(1);
if(byt & 0x80) OLED_SDA = 1;
else OLED_SDA = 0;
Delay10us(1);
OLED_SCL = 1;
byt <<= 1;
Delay10us(1);
}
OLED_SCL = 0;
}
// OLED命令和数据写入函数
void OLED_WriteCmd(unsigned char cmd)
{
IIC_Start();
IIC_SendByte(0x78);// 设备地址
IIC_SendByte(0x00);// 写命令
IIC_SendByte(cmd);
IIC_Stop();
}
void OLED_WriteData(unsigned char data)
{
IIC_Start();
IIC_SendByte(0x78);// 设备地址
IIC_SendByte(0x40);// 写数据
IIC_SendByte(data);
IIC_Stop();
}
// OLED初始化函数
void OLED_Init(void)
{
OLED_RST = 0;
Delay10us(200);
OLED_RST = 1;
OLED_WriteCmd(0xAE); // 关闭显示
OLED_WriteCmd(0xD5); // 设置显示时钟分频/振荡频率
OLED_WriteCmd(0x80);
OLED_WriteCmd(0xA8); // 设置多路复用率
OLED_WriteCmd(0x3F);
OLED_WriteCmd(0xD3); // 设置显示偏移
OLED_WriteCmd(0x00);
OLED_WriteCmd(0x40); // 设置显示开始行
OLED_WriteCmd(0x8D); // 电荷泵设置
OLED_WriteCmd(0x14);
OLED_WriteCmd(0x20); // 内存寻址模式
OLED_WriteCmd(0x00);
OLED_WriteCmd(0xA1); // 段重映射
OLED_WriteCmd(0xC8); // COM输出扫描方向
OLED_WriteCmd(0xDA); // COM硬件配置
OLED_WriteCmd(0x12);
OLED_WriteCmd(0x81); // 对比度控制
OLED_WriteCmd(0xCF);
OLED_WriteCmd(0xD9); // 预充电周期
OLED_WriteCmd(0xF1);
OLED_WriteCmd(0xDB); // VCOMH取消选择级别
OLED_WriteCmd(0x40);
OLED_WriteCmd(0xA4); // 整个显示打开
OLED_WriteCmd(0xA6); // 正常/反转显示
OLED_WriteCmd(0xAF); // 开启显示
OLED_Clear();
oled_init_flag = 1;
}
// OLED清屏函数
void OLED_Clear(void)
{
unsigned char i, n;
for(i = 0; i < 8; i++)
{
OLED_WriteCmd(0xB0 + i);
OLED_WriteCmd(0x00);
OLED_WriteCmd(0x10);
for(n = 0; n < 128; n++)
OLED_WriteData(0x00);
}
}
// OLED显示字符串函数
void OLED_ShowString(unsigned char x, unsigned char y, char *str)
{
unsigned char j = 0;
while(str != '\0')
{
// 这里应该有字符显示的具体实现
// 简化处理,实际应用中需要添加字模数据
j++;
x += 8;
if(x > 120)
{
x = 0;
y += 2;
}
}
}
// OLED显示数字函数
void OLED_ShowNum(unsigned char x, unsigned char y, unsigned long num, unsigned char len)
{
unsigned char t, temp;
for(t = 0; t < len; t++)
{
temp = (num / OLED_Pow(10, len - t - 1)) % 10;
// 这里应该有数字显示的具体实现
// 简化处理,实际应用中需要添加字模数据
x += 8;
}
}
// 计算10的n次方
unsigned long OLED_Pow(unsigned char m, unsigned char n)
{
unsigned long result = 1;
while(n--) result *= m;
return result;
}
// 刷新OLED显示
void OLED_Refresh(void)
{
// 实际应用中需要根据具体的OLED库来实现
}
// 显示音频信息
void OLED_DisplayAudioInfo(void)
{
if(!oled_init_flag) return;
OLED_Clear();
OLED_ShowString(0, 0, "Audio Format:");
if(audio_format == FORMAT_PCM)
{
OLED_ShowString(0, 2, "PCM");
}
else if(audio_format == FORMAT_DSD)
{
OLED_ShowString(0, 2, "DSD");
}
else
{
OLED_ShowString(0, 2, "Unknown");
}
OLED_ShowString(0, 4, "Sample Rate:");
if(sample_rate >= 1000000)
{
OLED_ShowNum(0, 6, sample_rate / 1000000, 1);
OLED_ShowString(8, 6, ".");
OLED_ShowNum(16, 6, (sample_rate % 1000000) / 100000, 1);
OLED_ShowString(24, 6, "MHz");
}
else if(sample_rate >= 1000)
{
OLED_ShowNum(0, 6, sample_rate / 1000, 3);
OLED_ShowString(24, 6, "kHz");
}
else
{
OLED_ShowNum(0, 6, sample_rate, 3);
OLED_ShowString(24, 6, "Hz");
}
OLED_Refresh();
}
// 设置静音
void SetMute(unsigned char state)
{
if(state)
{
MUTE_PIN = 1;
mute_flag = 1;
}
else
{
MUTE_PIN = 0;
mute_flag = 0;
}
}
// 判断音频格式
void DetectAudioFormat(void)
{
unsigned char i;
unsigned char is_pcm = 0;
unsigned char is_dsd = 0;
// 先判断是否为PCM典型频率
for(i = 0; i < sizeof(pcm_rates)/sizeof(pcm_rates); i++)
{
if(lrck_flag == pcm_rates)
{
audio_format = FORMAT_PCM;
sample_rate = lrck_flag;
is_pcm = 1;
break;
}
}
// 如果不是PCM典型频率,判断是否为DSD
if(!is_pcm && lrck_flag > 384000)
{
for(i = 0; i < sizeof(dsd_rates)/sizeof(dsd_rates); i++)
{
if(lrck_flag >= dsd_rates * 0.95 && lrck_flag <= dsd_rates * 1.05)
{
audio_format = FORMAT_DSD;
sample_rate = lrck_flag;
is_dsd = 1;
break;
}
}
}
// 如果都不是,设置为未知格式
if(!is_pcm && !is_dsd)
{
audio_format = FORMAT_UNKNOWN;
sample_rate = 0;
}
}
// 系统初始化
void SystemInit(void)
{
// 配置GPIO
P2M0 = 0x00;// 设置P2.0和P2.1为输入模式
P2M1 = 0x03;
P3M0 = 0x01;// 设置P3.0为推挽输出模式
P3M1 = 0x00;
// 配置定时器0,用于1ms定时
TMOD = 0x01;// 定时器0工作在模式1
TH0 = 0xFC; // 定时1ms
TL0 = 0x18;
ET0 = 1; // 使能定时器0中断
TR0 = 1; // 启动定时器0
// 配置外部中断0,用于检测BCLK上升沿
IT0 = 1; // 设置为下降沿触发
EX0 = 1; // 使能外部中断0
// 配置外部中断1,用于检测LRCK上升沿
IT1 = 1; // 设置为下降沿触发
EX1 = 1; // 使能外部中断1
EA = 1; // 开总中断
// 初始化OLED
OLED_Init();
// 初始化为静音状态
SetMute(1);
// 初始化音频格式为未知
audio_format = FORMAT_UNKNOWN;
sample_rate = 0;
// 显示初始信息
OLED_DisplayAudioInfo();
}
// 定时器0中断服务程序(1ms)
void Timer0_ISR() interrupt 1
{
TH0 = 0xFC; // 重新加载定时值
TL0 = 0x18;
// 计算频率
bclk_flag = bclk_counter * 1000;// 假设计数器每1ms清零一次
lrck_flag = lrck_counter * 1000;
// 清零计数器
bclk_counter = 0;
lrck_counter = 0;
// 检查频率是否变化
if(bclk_flag != prev_bclk_flag)
{
format_changed_count++;
if(format_changed_count >= 3)
{
// 检测到格式变化,拉静音
SetMute(1);
// 检测音频格式
DetectAudioFormat();
// 重置变化计数
format_changed_count = 0;
}
}
else
{
format_changed_count = 0;
}
prev_bclk_flag = bclk_flag;
// 如果静音标志为1且已经静音了一段时间,解除静音
if(mute_flag)
{
static unsigned int mute_counter = 0;
mute_counter++;
if(mute_counter >= 1000)// 1ms * 1000 = 1s
{
SetMute(0);
mute_counter = 0;
// 更新OLED显示
OLED_DisplayAudioInfo();
}
}
}
// 外部中断0服务程序(BCLK)
void INT0_ISR() interrupt 0
{
bclk_counter++;
if(bclk_counter >= 1024)
{
bclk_counter = 0;
}
}
// 外部中断1服务程序(LRCK)
void INT1_ISR() interrupt 2
{
lrck_counter++;
if(lrck_counter >= 1024)
{
lrck_counter = 0;
}
}
// 主函数
void main()
{
SystemInit();
while(1)
{
// 主循环中可以添加其他任务
// 音频格式检测和显示在中断中完成
}
}
大师又指点明津 WST 发表于 2025-6-16 20:19
根据你所提供的方法思路与要求试着让AI写了代码
#include
#include
AI的代码已经很不错了,细节可以完善,比如频率判断那一块,用“==”风险非常大,因为有时LRCK不是一个定值,因此可以用一个模糊判断,比如正负5%左右的区间判断,别的问题不大,移植一下可以用了。 非常感谢专家解惑。我最近在做9039q2m。测频率算是发了愁了。用的单片机是stm32f030c8,48MHz时钟,在用中断和tim计时测量频率时,自编的程序在pcm的频率较低没有关系,高频发现测量耗时极长,例如dsd64要10秒以上,造成界面卡顿,看门狗重启。没办法接了个CD74HC4024PWR来64分频。结果分频还不准确在40和64分频间乱变。测量频率的程序在低频也不准,经常频率翻倍。大神27m时钟就可以测量。能不能指导一下测量频率的程序啊 能否介绍一下Amanero的有采样率/格式显示脚?Amanero网站上没有找到 Amanero显示模块的资料分享
该模块使用 amanero 上的引脚 F0-F3 显示有关当前采样率的信息。必须在 oemtools“启用 F0、F1、F2、F3”中启用该选项。
PCM DSDOE= 0
0 (F3), 0 (F2), 0 (F1), 0 (F0) - 32kHz
0 (F3), 0 (F2), 0 (F1), 1 (F0) - 44 .1kHz
0 ( F3), 0 (F2), 1 (F1), 0 (F0) - 48kHz
0 (F3), 0 (F2), 1 (F1), 1 (F0) - 88 .2kHz
0 (F3), 1 (F2 ) ), 0 (F1), 0 (F0) - 96kHz
0 (F3), 1(F2), 0 (F1), 1 (F0) - 176 .4kHz
0 (F3), 1 (F2), 1 (F1), 0 (F0) - 192kHz
0 (F3), 1 (F2), 1 ( F1), 1 (F0) - 352 .8kHz
1 (F3), 0 (F2), 0 (F1), 0 (F0) - 384kHz
DSD DSDOE= 1
1 (F3), 0 (F2), 0 (F1), 1 (F0) - DSD64
1 (F3), 0 (F2), 1 (F1), 0 (F0) - DSD128
1 (F3) , 0 (F2), 1 (F1), 1 (F0) - DSD256
1 (F3), 1 (F2), 0 (F1), 0 (F0) - DSD512
作为显示器,使用了最常见的 LCD 1602A,它可以 2 行显示 ASCII 字符(1 行 16 个字符),每个字符以 5x7 像素的矩阵形式显示。
显示器本身很容易通过控制器连接到该板上。
https://bbs.elecfans.com/jishu_2304262_1_1.html
希望对你能有所帮助。 谢谢。这个显示频率好。测频率效率高多了 感谢大佬们的分享! 测试了几款国产的Amanero,引脚 F0-F3没有那么多的指示变化,也不用在 oemtools启用 ,国产的只用F0-F2,F3用作USB插入指示。分别是:
0 (F2), 0 (F1), 0 (F0) - 44 .1kHz
0 (F2), 0(F1), 1 (F0) - 48kHz
0(F2 ) ), 1 (F1), 0 (F0) - 96kHz
0(F2), 1 ( F1), 1 (F0) - 352 .8kHz
1(F2), 0 (F1), 0 (F0) - dsd64
1(F2), 0 (F1), 1 (F0) - dsd128
1(F2), 1 (F1), 0 (F0) - dsd256
1(F2), 1 (F1), 1 (F0) - dsd512 F0-F2的C51读出代码:
sbit am_F2=P1^4; //界面卡F0-F2 接MCU引脚
sbit am_F1=P1^5;
sbit am_F0=P1^6;
u8 read_pinx(void) //从界面卡F0-F2 取得音频格式和采样率函数
{
u8 result = 0;
result |= (u8)am_F0; // 从最低位F0到F2最高位依次读取
result |= ((u8)am_F1 << 1);
result |= ((u8)am_F2 << 2);
return result; //根据返回值可知输入的音频格式和采样率
}
这是一个好帖子。留名
重要的不是逻辑,重要的是相位关系,抖动 支持!,人工智能写代码,高效好用,可以搞定细节。 做了个es9039q2m,扑佢个街,居然无寄器可以读采样率和采样位数。AK也是没有
页:
[1]