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]