国学芯用 发表于 2026-7-2 13:17:23

84-USB录放音声卡-TLV320AIC23B-内部36.864M-外接32768Hz晶振,AI8051U实验箱演示程序

84-USB录放音声卡-TLV320AIC23B-内部36.864M-外接32768Hz晶振,AI8051U实验箱演示程序

主程序main.C代码如下(汇编版本在附件中):

/*---------------------------------------------------------------------*/
/* --- Web: www.STCAI.com ---------------------------------------------*/
/* --- BBS: www.STCAIMCU.com-----------------------------------------*/
/* 如果要在程序中使用此代码,请在程序中注明使用了STC的资料及程序            */
/*---------------------------------------------------------------------*/

/*************功能说明    **************



AI8051U-USB录放音声卡:
下行(播放)数据送I2S中断里更新数据,主频分频做MCLK。采样率48KHz,USB数据192字节一帧,1ms一帧。
上行(录音)数据立体声采样率32KHz,USB数据128字节一帧,1ms一帧。

用户请先别修改程序, 直接下载"Obj"里的"usb_audio_i2s_48k_16b_2ch.hex"测试. 下载时选择主频36.864MHZ。
本例程使用AI8051U大学计划实验箱V1.2-2024-8-20验证,MCU内部ROSC工作于36.864MHz。
AI8051U的I2S接口连接TLV320AIC23B播放声音.

只支持16位PCM格式.

=========================== PCM双声道数据格式 ====================================
16位双声道PCM(小端模式,低字节在前):
      双字0(4字节)            |         双字1(4字节)            | .......
             采样1            |            采样2            | .......
    左声道   |   右声道   |   左声道   |   右声道   | .......
字节0   字节1| 字节2   字节3| 字节4   字节5| 字节6   字节7| .......
低字节高字节 | 低字节高字节 | 低字节高字节 | 低字节高字节 | .......
==================================================================================

******************************************/

#include "stc.h"
#include "i2s.h"
#include "usb.h"
#include "clock.h"
#include "TLV320AIC23.h"

#defineCHANNEL2                //设置ADC通道(咪头通道),   取值为0~7, 对应P1.0~P1.7, 使用别的ADC输入口则要修改ADC初始化函数.

/*************        IO口定义        **************/
void         Send_595(u8 dat);
sbit        P_HC595_SER   = P3^4;        //pin 14        SER                data input
sbit        P_HC595_RCLK= P3^5;        //pin 12        RCLk        store (latch) clock
sbit        P_HC595_SRCLK = P3^2;        //pin 11        SRCLK        Shift data clock

//=============== 话筒相关变量 =====================================
#define        VOICE_BUFF_LENGTH        8192        //一定是2^n
#define        VOICE_BUFF_MASK                (VOICE_BUFF_LENGTH-1)
#define S_BUFFER_SIZE                (VOICE_BUFF_LENGTH / 4)
#define S_BUFFER_MASK                (BUFFER_SIZE - 1)
#define S_OVERRUN_POINT                (BUFFER_SIZE / 3 * 2)
#define S_UNDERRUN_POINT        (BUFFER_SIZE / 3)

u16        wr_index, rd_index;
u8        xdata voice_buff;
u16 S_WaveDumpSize;        //已缓存的声音数据, 低于一定值插补一个值, 高于一定值丢去一个值
u16        adc;

bit        B_MicEn;
u8        Trim_UpDown;                //0:无操作, 1:请求增加一个数据,2:请求减少一个数据
//==================================================================


void sys_init();
void clk_trim();
voiddelay_ms(u16 ms);


#ifdef DEBUG
    void show8(BYTE dat);
    void show16(WORD dat);
#endif

u8         LED8;                //显示缓冲
u8        display_index;        //显示位索引
bit        B_1ms;                        //1ms标志
u8        KeyCode;                //给用户使用的键码
u8 IO_KeyState;                //行列键盘变量
u8        cnt20ms;

#define DIS_DOT                0x20
#define DIS_BLACK        0x10
#define DIS_                0x11

#define DIS_S                0x05
#define DIS_T                0x1A
#define DIS_O                0x17
#define DIS_P                0x18
#define DIS_R                0x1D
#define DIS_E                0x0E
#define DIS_C                0x0C
#define DIS_D                0x0D
#define DIS_L                0x15
#define DIS_A                0x0A
#define DIS_Y                0x1F

#define        K0        0x01
#define        K1        0x02
#define        K2        0x04
#define        K3        0x08
#define        K4        0x10
#define        K5        0x20
#define        K6        0x40
#define        K7        0x80


const u8t_display[]={                                                //标准字库
//       0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
        0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//black       -   H    J       K          L           N        o   P       U   t    G    Q    r   M    y
        0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e,
        0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46};        //0. 1. 2. 3. 4. 5. 6. 7. 8. 9. -1

const u8T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};                //位码

u8                KeyState, KeyCode;        //按键

u16                HeadPhoneVol;                //耳机音量, 0~80, 0->mute, 1->-73db, 80->+6db, 74->0db, 1db/step.
void        SetHeadPhoneVolume(u16 vol);
void        IO_KeyScan(void);        //50ms call
void        DisplayScan(void);
void        ADC_config(void);
void         PWMA_config(void);


u8 xdata buf;


void main()
{
        u16        j;

        P0M0 = 0;        //用于输出测试信号
        P0M1 = 0;

    sys_init();
    clock_init();
    i2s_init();
    usb_init();
    EA = 1;

        for(j=0; j<8; j++)        LED8 = DIS_BLACK;        //上电消隐

        HeadPhoneVol = 80 - 20;        //默认音量60, 最大音量80
        AIC23_Init();
        delay_ms(50);
        AIC32_InitSet();
        LED8 = HeadPhoneVol / 10;        //显示音量
        LED8 = HeadPhoneVol % 10;
        SetHeadPhoneVolume(HeadPhoneVol);        //设置音量

    for (j = 0; j < 128; j++)        buf = 0;        //空数据
        wr_index = 0;
        rd_index = 0;
        S_WaveDumpSize = 0;                //已缓存的声音数据, 低于一定值插补一个值, 高于一定值丢去一个值
        B_MicEn = 0;
        Trim_UpDown = 0;                //0:无操作, 1:请求增加一个数据,2:请求减少一个数据
        ADC_config();
        PWMA_config();


    while (1)
    {
      if (DeviceState != DEVSTATE_CONFIGURED)
            continue;

      clock_trim();        //实时动态微调IRC频率

      if (UsbReq)                        //请求上传下一包录音数据
      {
            UsbReq = 0;

                        if (B_MicEn)
                        {
                                j = (wr_index - rd_index) & VOICE_BUFF_MASK;        //已有缓冲长度
                                if(j > 256)                //有足够多的数据才上传
                                {
                                        USB_SendData(voice_buff+rd_index);        //上传录音数据
                                        rd_index = (rd_index + 128) & VOICE_BUFF_MASK;
                                        S_WaveDumpSize -= 32;        // 32个采样点 * 2字节采样 *2通道 = 128字节
                                }
                                else        USB_SendData(buf);        //上传录音数据(空包)

                                if (S_WaveDumpSize < S_UNDERRUN_POINT)      //缓存数据偏/少
                                {
                                        Trim_UpDown = 1;                //0:无操作, 1:请求增加一个数据,2:请求减少一个数据
                                }
                                else if (S_WaveDumpSize > S_OVERRUN_POINT)//缓存数据偏多
                                {
                                        Trim_UpDown = 2;                //0:无操作, 1:请求增加一个数据,2:请求减少一个数据
                                }
                        }
                        else        USB_SendData(buf);        //上传录音数据(空包)
      }

      if(B_2ms)        //2ms定时时隙
      {
                        B_2ms = 0;
                        DisplayScan();        //1ms扫描显示一位

                        if(++cnt20ms == 20)        //20ms时隙
                        {
                                cnt20ms = 0;
                                IO_KeyScan();                //扫描按键
                                if(KeyCode != 0)        //有键按下
                                {
                                        if(KeyCode == K6)        //音量+
                                        {
                                                if(++HeadPhoneVol > 80)        HeadPhoneVol = 80;        //最大音量
                                                SetHeadPhoneVolume(HeadPhoneVol);        //设置音量
                                                LED8 = HeadPhoneVol / 10;        //显示音量
                                                LED8 = HeadPhoneVol % 10;
                                        }
                                        else if(KeyCode == K7)        //音量-
                                        {
                                                if(HeadPhoneVol != 0)        HeadPhoneVol--;        //最小音量
                                                SetHeadPhoneVolume(HeadPhoneVol);        //设置音量
                                                LED8 = HeadPhoneVol / 10;        //显示音量
                                                LED8 = HeadPhoneVol % 10;
                                        }

                                        KeyCode = 0;
                                }
                        }
                }

#ifdef DEBUG
      show16(WaveDumpSize);
      show8(IRTRIM);

      if (WaveOverrun)
      {
            WaveOverrun = 0;
            P20 = ~P20;
      }
      if (WaveUnderrun)
      {
            WaveUnderrun = 0;
            P21 = ~P21;
      }
#endif
    }
}

/*****************************************************
        行列键扫描程序
        使用XY查找4x4键的方法,只能单键,速度快

   Y   P00      P01      P02      P03
          |      |      |      |
X         |      |      |      |
P07 ---- K00 ---- K01 ---- K02 ---- K03 ----
          |      |      |      |
P06 ---- K04 ---- K05 ---- K06 ---- K07 ----
          |      |      |      |
******************************************************/

void IO_KeyDelay(void)
{
        u8 i;
        i = 40;
        while(--i)        ;
}

void        IO_KeyScan(void)        //50ms call
{
        u8        j;

        j = IO_KeyState;        //保存上一次状态

        P06 = 0;
        IO_KeyDelay();
        IO_KeyState = P0 & 0x0f;
        P06 = 1;

        P07 = 0;
        IO_KeyDelay();
        IO_KeyState |= (P0 << 4) & 0xf0;
        IO_KeyState ^= 0xff;        //取反
        P07 = 1;

        KeyCode |= (j ^ IO_KeyState) & IO_KeyState;
}
/**************** 向HC595发送一个字节函数 ******************/
void Send_595(u8 dat)
{
        u8        i;
        for(i=0; i<8; i++)
        {
                dat <<= 1;
                P_HC595_SER   = CY;
                NOP(1);
                P_HC595_SRCLK = 1;
                NOP(1);
                P_HC595_SRCLK = 0;
        }
}

/********************** 显示扫描函数 ************************/
void DisplayScan(void)
{
        Send_595(t_display]);        //输出段码
        Send_595(~T_COM);                        //输出位码

        P_HC595_RCLK = 1;
        NOP(2);
        P_HC595_RCLK = 0;                                                        //锁存输出数据
        if(++display_index >= 8)        display_index = 0;        //8位结束回0
}



//=================== 设置耳机音量 ==========================================
void        SetHeadPhoneVolume(u16 vol)
{
        AIC23_WriteCmd(R_HeadphoneVolume_L, (LinVol_LRS | LinVol_Mute | (vol+47)));        //耳机输出音量
        AIC23_WriteCmd(R_HeadphoneVolume_R, (RinVol_RLS | RinVol_Mute | (vol+47)));

//        WriteASCII(66,5,Vol/10,0);
//        WriteASCII(72,5,Vol%10,0);
}



//========================================================================
// 函数: voiddelay_ms(u16 ms)
// 描述: 延时函数。
// 参数: ms,要延时的ms数, 1~65535ms. 自动适应主时钟.
// 返回: none.
// 版本: VER1.0
// 日期: 2013-4-1
// 备注:
//========================================================================
voiddelay_ms(u16 ms)
{
   u16 i;
       do
       {
               i = FOSC / 6000;
                while(--i)        ;
   }while(--ms);
}


void sys_init()
{
        CKCON = 0;
        EAXSFR();        //SFR enable

    P0M0 = 0x00; P0M1 = 0x00;
    P1M0 = 0x00; P1M1 = 0x00;
    P2M0 = 0x00; P2M1 = 0x00;
    P3M0 = 0x00; P3M1 = 0x00;
    P4M0 = 0x00; P4M1 = 0x00;
    P5M0 = 0x00; P5M1 = 0x00;
    P6M0 = 0x00; P6M1 = 0x00;
    P7M0 = 0x00; P7M1 = 0x00;
    P4 = 0xff;
    P3 = 0xff;

    P3M0 &= ~0x03;
    P3M1 |= 0x03;

    IRC48MCR = 0x80;
    while (!(IRC48MCR & 0x01));

    USBCLK = 0x00;
    USBCON = 0x90;
}

#ifdef DEBUG
void show8(BYTE dat)
{
    dat = ~dat;
    P37 = dat & 0x01;
    P36 = dat & 0x02;
    P35 = dat & 0x04;
    P51 = dat & 0x08;
    P50 = dat & 0x10;
    P34 = dat & 0x20;
    P33 = dat & 0x40;
    P32 = dat & 0x80;
}

void show16(WORD dat)
{
    dat = ~dat;
    P46 = dat & 0x0001;
    P00 = dat & 0x0002;
    P01 = dat & 0x0004;
    P02 = dat & 0x0008;
    P03 = dat & 0x0010;
    P04 = dat & 0x0020;
    P52 = dat & 0x0040;
    P53 = dat & 0x0080;
    P05 = dat & 0x0100;
    P06 = dat & 0x0200;
    P07 = dat & 0x0400;
    P10 = dat & 0x0800;
    P11 = dat & 0x1000;
    P47 = dat & 0x2000;
    P14 = dat & 0x4000;
    P15 = dat & 0x8000;
}
#endif



//========================================================================
// 函数: void PWMA_config(void)
// 描述: PWM配置函数。
// 参数: noe.
// 返回: none.
// 版本: V1.0, 2022-3-15
// 备注:
//========================================================================
void PWMA_config(void)
{
        PWMA_ENO    = 0;        // IO输出禁止
        PWMA_IER    = 0;        // 禁止中断
        PWMA_SR1    = 0;        // 清除状态
        PWMA_SR2    = 0;        // 清除状态

        PWMA_PSCRH = 0x00;                // 预分频寄存器, 分频 Fck_cnt = Fck_psc/(PSCR[15:0}+1), 边沿对齐PWM频率 = SYSclk/((PSCR+1)*(AAR+1)), 中央对齐PWM频率 = SYSclk/((PSCR+1)*(AAR+1)*2).
        PWMA_PSCRL = 0x00;
        PWMA_DTR   = 0;                        // 死区时间配置, n=0~127: DTR= n T,   0x80 ~(0x80+n), n=0~63: DTR=(64+n)*2T,
                                                        //                                0xc0 ~(0xc0+n), n=0~31: DTR=(32+n)*8T,   0xE0 ~(0xE0+n), n=0~31: DTR=(32+n)*16T,
        PWMA_ARRH   = (u8)((FOSC/32000-1)/256);        // 自动重装载寄存器,控制PWM周期
        PWMA_ARRL   = (u8)((FOSC/32000-1)%256);
        PWMA_CR2    = 0x20;                // 更新事件为TRG0, 用于触发ADC

        PWMA_CCER1= 0;        // 捕获/比较使能寄存器1
        PWMA_CCER2= 0;        // 捕获/比较使能寄存器2

        PWMA_BKR    = 0x80;                // 主输出使能 相当于总开关
        PWMA_CR1    = 0x81;                // 使能计数器, 允许自动重装载寄存器缓冲, 边沿对齐模式, 向上计数,bit7=1:写自动重装载寄存器缓冲(本周期不会被打扰), =0:直接写自动重装载寄存器本(周期可能会乱掉)
        PWMA_EGR    = 0x01;                // 产生一次更新事件, 清除计数器和预分频计数器, 装载预分频寄存器的值
}

//        PWMA_PS   = (0<<6)+(0<<4)+(0<<2)+0;        //选择IO, 4项从高到低(从左到右)对应PWM1 PWM2 PWM3 PWM4, 0:选择P1.x, 1:选择P2.x, 2:选择P6.x,
//PWMA_PS    PWM4N PWM4P    PWM3N PWM3P    PWM2N PWM2P    PWM1N PWM1P
//    00       P1.7P1.6   P1.5P1.4   P1.3P1.2   P1.1P1.0
//    01       P0.7P0.6   P0.5P0.4   P0.3P0.2   P0.1P0.0
//    02       P2.7P2.6   P2.5P2.4   P2.3P2.2   P2.1P2.0
//    03      --    --       --    --       --    --       --    --

//========================================================================
// 函数: void        ADC_config(void)
// 描述: ADC初始化函数
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2024-8-1
// 备注:
//========================================================================
#define D_ADC_POWER        (1<<7)        /* ADC电源,1开启,0关闭 */
#define D_ADC_START        (1<<6)        /* 启动转换,自动清0 */
#define D_ADC_FLAG        (1<<5)        /* 完成标志,软件清0 */
#define D_ADC_EPWMT        (1<<4)        /* 允许PWMA触发ADC */

#define        D_ADC_SPEED        5                /* 0~15, ADC时钟 = SYSclk/2/(n+1) */
#define        D_RES_FMT        (1<<5)        /* ADC结果格式 0: 左对齐, ADC_RES: D9 D8 D7 D6 D5 D4 D3 D2, ADC_RESL: D1 D0 000000 */
                                                        /*             1: 右对齐, ADC_RES: 000000D9 D8, ADC_RESL: D7 D6 D5 D4 D3 D2 D1 D0 */
#define CSSETUP                (0<<7)        /* 0~1,ADC通道选择时间      0: 1个ADC时钟, 1: 2个ADC时钟,默认0(默认1个ADC时钟)        */
#define CSHOLD                (0<<5)        /* 0~3,ADC通道选择保持时间(n+1)个ADC时钟, 默认1(默认2个ADC时钟)                                        */
#define SMPDUTY                10                /* 10~31, ADC模拟信号采样时间(n+1)个ADC时钟, 默认10(默认11个ADC时钟)                                */
                                                        /* ADC转换时间: 10位ADC固定为10个ADC时钟, 12位ADC固定为12个ADC时钟.                                 */
void        ADC_config(void)
{
//        EAXSFR();        //SFR enable
        P1n_pure_input(1<<CHANNEL);        //设置要做ADC的IO做高阻输入
        ADC_CONTR = D_ADC_POWER + CHANNEL;        //ADC on + channel
        ADCCFG = D_RES_FMT + D_ADC_SPEED;
        ADCTIM = CSSETUP + CSHOLD + SMPDUTY;
//          ADC_CONTR |= ADC_START;        //启动ADC转换,完成后自动清零
//          ADC_CONTR &= ~ADC_FLAG;        //清除ADC完成(中断)标志
          ADC_CONTR |= ADC_EPWMT;        //允许PWM触发ADC
        EADC= 1;        //允许ADC中断
        IPH |= PADCH;        //ADC 中断优先级高位
        PADC= 1;        //ADC 中断优先级
}


#define        HighPassEnable        1                // 1: 允许高通滤波,消除直流偏移, 0: 禁止高通滤波

                //        32741, -32741, 0, -32715, 0                        //1阶巴特沃斯高通8Hz/32000Hz
#define        COEF_A0               32741
#define        COEF_B0                -32715
#if (HighPassEnable ==1)
long        Xin1, Xin0, Yout0, out;
#endif

void ADC_ISR (void) interrupt ADC_VECTOR        //@36.864MHz无高通滤波 2us,加一阶高通滤波4.2us
{
          ADC_CONTR &= ~ADC_FLAG;        //清除ADC完成(中断)标志

        #if (HighPassEnable ==1)        //允许高通滤波,消除直流偏移
                adc = (u16)ADC_RES * 256 + (u16)ADC_RESL;        //读取ADC值,转成双极性
                Xin1 = Xin0;        Xin0 = (long)adc - 2048;        //输入ADC值为12位, 转为双极性
                out = (COEF_A0 * (Xin0 - Xin1) - COEF_B0 * Yout0)/32768;        //一阶高通
                Yout0 = out;
                if(out >=2048)        out =2047;        //输出限幅
                if(out <= -2048)        out = -2047;        //输出限幅
                adc = (u16)out;
        #else
                adc = ((u16)ADC_RES * 256 + (u16)ADC_RESL)-2048;        //读取ADC值,转成双极性
        #endif

        if(S_WaveDumpSize < (S_BUFFER_SIZE-4))        //避免溢出
        {
                voice_buff = (u8)(adc % 256);        //小端模式
                voice_buff = (u8)(adc / 256);
                voice_buff = (u8)(adc % 256);        //小端模式
                voice_buff = (u8)(adc / 256);
                wr_index &= VOICE_BUFF_MASK;        //防止溢出
                S_WaveDumpSize++;        //已缓存的声音数据, 低于一定值插补一个值, 高于一定值丢去一个值

                if(S_WaveDumpSize >= ( S_BUFFER_SIZE/2))        B_MicEn = 1;        //缓冲过半才启动传输
                if(S_WaveDumpSize < 256)                                        B_MicEn = 0;        //缓冲过少关闭传输

                if(Trim_UpDown == 1)                //0:无操作, 1:请求增加一个数据,2:请求减少一个数据
                {
                        Trim_UpDown = 0;
                        voice_buff = (u8)(adc % 256);        //小端模式
                        voice_buff = (u8)(adc / 256);
                        voice_buff = (u8)(adc % 256);        //小端模式
                        voice_buff = (u8)(adc / 256);
                        wr_index &= VOICE_BUFF_MASK;        //防止溢出
                        S_WaveDumpSize++;        //已缓存的声音数据, 低于一定值插补一个值, 高于一定值丢去一个值
                }
                if(Trim_UpDown == 2)                //0:无操作, 1:请求增加一个数据,2:请求减少一个数据
                {
                        Trim_UpDown = 0;
                        wr_index -= 4;
                        wr_index &= VOICE_BUFF_MASK;        //防止溢出
                        if(S_WaveDumpSize != 0)                S_WaveDumpSize--;                //已缓存的声音数据, 低于一定值插补一个值, 高于一定值丢去一个值
                }
        }
}


页: [1]
查看完整版本: 84-USB录放音声卡-TLV320AIC23B-内部36.864M-外接32768Hz晶振,AI8051U实验箱演示程序