39

主题

2

好友

725

积分

职业侠客 当前离线

Rank: 5Rank: 5

UID
787724
帖子
632
精华
0
经验
725 点
金钱
592 ¥
注册时间
2015-3-20
发表于 2025-6-2 12:46 | 显示全部楼层
目前网上廉价机/套件的采样率获取有两种办法:
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主频是不够用的。

60

主题

1

好友

2130

积分

罗宾汉 当前离线

Rank: 7Rank: 7Rank: 7

UID
186195
帖子
2070
精华
0
经验
2130 点
金钱
2006 ¥
注册时间
2010-3-29
发表于 2025-6-2 14:05 | 显示全部楼层
串入DIX9211应该能解决提到的问题。

2

主题

0

好友

258

积分

业余侠客 当前离线

Rank: 4

UID
618788
帖子
267
精华
0
经验
258 点
金钱
254 ¥
注册时间
2013-9-19
发表于 2025-6-2 16:18 | 显示全部楼层
好方法

2

主题

0

好友

67

积分

论坛游民 当前离线

Rank: 3Rank: 3

UID
28813
帖子
62
精华
0
经验
67 点
金钱
63 ¥
注册时间
2007-10-14
发表于 2025-6-16 20:19 | 显示全部楼层
根据你所提供的方法思路与要求试着让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_DC  P1_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[j] != '\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[0]); i++)
    {
        if(lrck_flag == pcm_rates[i])
        {
            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[0]); i++)
        {
            if(lrck_flag >= dsd_rates[i] * 0.95 && lrck_flag <= dsd_rates[i] * 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)
    {
        // 主循环中可以添加其他任务
        // 音频格式检测和显示在中断中完成
    }
}

242

主题

1

好友

2517

积分
     

罗宾汉 当前在线

Rank: 7Rank: 7Rank: 7

UID
1764
帖子
3794
精华
0
经验
2517 点
金钱
1656 ¥
注册时间
2004-10-6
发表于 2025-6-17 08:42 来自手机端 | 显示全部楼层
大师又指点明津

39

主题

2

好友

725

积分

职业侠客 当前离线

Rank: 5Rank: 5

UID
787724
帖子
632
精华
0
经验
725 点
金钱
592 ¥
注册时间
2015-3-20
 楼主| 发表于 2025-6-17 16:04 | 显示全部楼层
WST 发表于 2025-6-16 20:19
根据你所提供的方法思路与要求试着让AI写了代码
#include
#include

AI的代码已经很不错了,细节可以完善,比如频率判断那一块,用“==”风险非常大,因为有时LRCK不是一个定值,因此可以用一个模糊判断,比如正负5%左右的区间判断,别的问题不大,移植一下可以用了。

3

主题

0

好友

65

积分

论坛游民 当前离线

Rank: 3Rank: 3

UID
73896
帖子
70
精华
0
经验
65 点
金钱
59 ¥
注册时间
2009-1-9
发表于 2025-6-19 22:39 | 显示全部楼层
非常感谢专家解惑。我最近在做9039q2m。测频率算是发了愁了。用的单片机是stm32f030c8,48MHz时钟,在用中断和tim计时测量频率时,自编的程序在pcm的频率较低没有关系,高频发现测量耗时极长,例如dsd64要10秒以上,造成界面卡顿,看门狗重启。没办法接了个CD74HC4024PWR来64分频。结果分频还不准确在40和64分频间乱变。测量频率的程序在低频也不准,经常频率翻倍。大神27m时钟就可以测量。能不能指导一下测量频率的程序啊

3

主题

0

好友

65

积分

论坛游民 当前离线

Rank: 3Rank: 3

UID
73896
帖子
70
精华
0
经验
65 点
金钱
59 ¥
注册时间
2009-1-9
发表于 2025-6-21 11:38 | 显示全部楼层
能否介绍一下Amanero的有采样率/格式显示脚?Amanero网站上没有找到

2

主题

0

好友

67

积分

论坛游民 当前离线

Rank: 3Rank: 3

UID
28813
帖子
62
精华
0
经验
67 点
金钱
63 ¥
注册时间
2007-10-14
发表于 2025-6-21 20:31 | 显示全部楼层
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
希望对你能有所帮助。

3

主题

0

好友

65

积分

论坛游民 当前离线

Rank: 3Rank: 3

UID
73896
帖子
70
精华
0
经验
65 点
金钱
59 ¥
注册时间
2009-1-9
发表于 2025-6-22 08:01 | 显示全部楼层
谢谢。这个显示频率好。测频率效率高多了
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Powered by Discuz! X3.4

© 2001-2012 Comsenz Inc.

返回顶部