好的,现在我已经掌握了所有必要的信息。下面我将基于 STC32G8K64 编写一个完整的声卡演示程序。
需要说明的是,STC32G8K64 没有 USB 模块(从手册表格可以看到 STC32G8K64 的 USB 列为空),因此无法通过 USB 直接与 PC 通信。但该芯片具有 I2S 音频总线,可以外接 I2S DAC(如 PCM5102A、CS4344 等)来输出音频。
我将编写一个完整的演示程序,包含:
- 系统时钟配置(使用内部 IRC + PLL)
- I2S 主机发送模式配置
- 生成正弦波音频数据通过 I2S 输出
- 通过串口(UART1)接收 PC 命令控制音量、频率等
STC32G8K64 I2S 声卡演示程序
文件1:main.c
/*
* ============================================================
* STC32G8K64 I2S 音频声卡演示程序
* ============================================================
*
* 【硬件连接】(以 I2S 引脚切换组0为例)
* STC32G8K64 I2S DAC (如 PCM5102A)
* P3.2 (I2SBCK) --> BCK (位时钟)
* P3.4 (I2SData) --> DIN (数据)
* P3.5 (I2SLRCK) --> LRCK (左右声道时钟)
* P5.4 (I2SMCK) --> SCK (主时钟, 可选)
*
* P3.0 (RxD) -- USB转串口 --> PC
* P3.1 (TxD) -- USB转串口 --> PC
*
* 【功能说明】
* 1. 使用内部高速IRC 45.1584MHz作为主时钟
* 2. 通过PLL产生96MHz时钟供I2S使用
* 3. I2S主机发送模式,16位数据,双声道,44.1kHz采样率
* 4. 生成1kHz正弦波测试音频输出
* 5. 通过串口接收PC命令:
* "freq=1000" -- 设置频率(Hz)
* "vol=50" -- 设置音量(0-100)
* "wave=0" -- 波形选择: 0-正弦, 1-方波, 2-三角波, 3-锯齿波
*
* 【编译环境】
* Keil C251, 芯片选择 STC32G8K64
*
* 【使用步骤】
* 1. 在 AiCube-ISP 中选择内部IRC频率为 45.1584MHz
* 2. 编译下载本程序到 STC32G8K64
* 3. 连接 I2S DAC 到对应引脚
* 4. 打开串口助手(115200,8,n,1),发送命令控制
* ============================================================
*/
#include "stc32g.h"
#include "intrins.h"
/* ======================== 用户配置宏 ======================== */
// 主时钟频率(必须与ISP下载时选择的IRC频率一致)
#define FOSC 45158400UL // 45.1584MHz
// 音频采样率 (Hz)
#define SAMPLE_RATE 44100UL // 44.1kHz CD音质
// I2S主时钟输出控制: 1-使能, 0-禁止
#define MCKOE 1
// I2S引脚切换组 (0~3)
#define I2S_PORT_GROUP 0
// 默认音频参数
#define DEFAULT_FREQ 1000 // 默认频率 1kHz
#define DEFAULT_VOL 80 // 默认音量 80%
#define DEFAULT_WAVE 0 // 默认波形: 正弦波
/* ======================== I2S 参数计算 ======================== */
/*
* I2S 时钟分频计算(16位数据,双声道,主时钟使能):
* 主时钟频率 = 256 * FS
* I2S比特率 = 16 * 2 * FS = 32 * FS
*
* 当 MCKOE=1 时:
* FS = FOSC / [(16*2) * (2*DIV+ODD) * 8]
* = FOSC / [256 * (2*DIV+ODD)]
*
* 所以: 2*DIV + ODD = FOSC / (256 * FS)
*
* 对于 FOSC=45.1584MHz, FS=44.1kHz:
* 2*DIV + ODD = 45158400 / (256 * 44100) = 4.0
* 取 DIV=2, ODD=0
*/
#define I2S_DIV_VAL (FOSC / (256UL * SAMPLE_RATE)) // 分频系数
#define I2S_ODD ((unsigned char)(I2S_DIV_VAL & 1)) // 奇数因子
#define I2S_DIV ((unsigned char)(I2S_DIV_VAL / 2)) // 分频值
/* ======================== 数据类型定义 ======================== */
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
/* ======================== 全局变量 ======================== */
// 音频参数
volatile u16 g_freq = DEFAULT_FREQ; // 当前频率 (Hz)
volatile u8 g_vol = DEFAULT_VOL; // 当前音量 (0-100)
volatile u8 g_wave = DEFAULT_WAVE; // 当前波形类型
// 波形生成索引
volatile u16 g_idx_l = 0; // 左声道相位索引
volatile u16 g_idx_r = 0; // 右声道相位索引
// 相位增量(决定频率)
u16 g_phase_inc_l; // 左声道相位增量
u16 g_phase_inc_r; // 右声道相位增量
// 串口接收缓冲区
u8 uart_rx_buf[32]; // 串口接收缓冲区
u8 uart_rx_cnt = 0; // 接收计数
bit uart_rx_ok = 0; // 接收到完整命令标志
/* ======================== 正弦波表 ======================== */
// 256点正弦波表(16位有符号,范围 -32768 ~ +32767)
// 使用 code 关键字存储在 Flash 中
u16 code sin_table[256] = {
0, 804, 1608, 2410, 3212, 4011, 4808, 5602,
6393, 7179, 7962, 8739, 9512, 10278, 11039, 11793,
12539, 13279, 14010, 14732, 15446, 16151, 16846, 17530,
18204, 18868, 19519, 20159, 20787, 21403, 22005, 22594,
23170, 23731, 24279, 24811, 25329, 25832, 26319, 26790,
27245, 27683, 28105, 28510, 28898, 29268, 29621, 29956,
30273, 30571, 30852, 31113, 31356, 31580, 31785, 31971,
32137, 32285, 32412, 32521, 32609, 32678, 32728, 32757,
32767, 32757, 32728, 32678, 32609, 32521, 32412, 32285,
32137, 31971, 31785, 31580, 31356, 31113, 30852, 30571,
30273, 29956, 29621, 29268, 28898, 28510, 28105, 27683,
27245, 26790, 26319, 25832, 25329, 24811, 24279, 23731,
23170, 22594, 22005, 21403, 20787, 20159, 19519, 18868,
18204, 17530, 16846, 16151, 15446, 14732, 14010, 13279,
12539, 11793, 11039, 10278, 9512, 8739, 7962, 7179,
6393, 5602, 4808, 4011, 3212, 2410, 1608, 804,
0, 64764, 63960, 63158, 62356, 61557, 60760, 59966,
59175, 58389, 57606, 56829, 56056, 55290, 54529, 53775,
53029, 52289, 51558, 50836, 50122, 49417, 48722, 48038,
47364, 46700, 46049, 45409, 44781, 44165, 43563, 42974,
42398, 41837, 41289, 40757, 40239, 39736, 39249, 38778,
38323, 37885, 37463, 37058, 36670, 36300, 35947, 35612,
35295, 34997, 34716, 34455, 34212, 33988, 33783, 33597,
33431, 33283, 33156, 33047, 32959, 32890, 32840, 32811,
32768, 32811, 32840, 32890, 32959, 33047, 33156, 33283,
33431, 33597, 33783, 33988, 34212, 34455, 34716, 34997,
35295, 35612, 35947, 36300, 36670, 37058, 37463, 37885,
38323, 38778, 39249, 39736, 40239, 40757, 41289, 41837,
42398, 42974, 43563, 44165, 44781, 45409, 46049, 46700,
47364, 48038, 48722, 49417, 50122, 50836, 51558, 52289,
53029, 53775, 54529, 55290, 56056, 56829, 57606, 58389,
59175, 59966, 60760, 61557, 62356, 63158, 63960, 64764
};
/* ======================== 函数声明 ======================== */
void sys_init(void); // 系统初始化
void i2s_init(void); // I2S模块初始化
void uart1_init(void); // 串口1初始化
void uart1_send_string(u8 *str); // 串口发送字符串
void calc_phase_inc(void); // 计算相位增量
u16 get_sample_value(u16 phase, u8 wave_type, u8 vol); // 获取采样值
void process_command(void); // 处理串口命令
/* ======================== 主函数 ======================== */
void main(void)
{
sys_init(); // 系统初始化(时钟、I/O等)
uart1_init(); // 串口1初始化(用于命令交互)
i2s_init(); // I2S模块初始化
// 计算初始相位增量
calc_phase_inc();
// 发送启动信息
uart1_send_string("STC32G8K64 I2S Audio Demo\r\n");
uart1_send_string("Commands: freq=xxx, vol=xxx, wave=x\r\n");
EA = 1; // 开启总中断
while(1)
{
// 检查是否有串口命令需要处理
if(uart_rx_ok)
{
process_command();
uart_rx_ok = 0;
}
}
}
/* ======================== 系统初始化 ======================== */
void sys_init(void)
{
// 设置程序代码等待时间,0表示不等待,以最快速度运行
WTST = 0x00;
// 设置访问片内 xdata 速度,0表示最快速度访问
CKCON = 0x00;
// 允许访问扩展的特殊功能寄存器(XFR)
EAXFR = 1;
// 设置所有I/O口为准双向口模式
// 注意:除P3.0/P3.1上电为准双向口外,其余I/O上电为高阻输入
P0M0 = 0x00; P0M1 = 0x00;
P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x00;
P3M0 = 0x00; P3M1 = 0x00;
P4M0 = 0x00; P4M1 = 0x00;
P5M0 = 0x00; P5M1 = 0x00;
}
/* ======================== I2S 模块初始化 ======================== */
void i2s_init(void)
{
// I2S从模式控制寄存器,内部测试用,用户必须设置为0xFF
I2SMD = 0xFF;
// I2S控制寄存器:使能发送缓冲区空中断
// TXEIE=1 (bit7) 允许发送缓冲区空中断
// FRF=0 (bit4) Motorola格式
// TXDMAEN=0 (bit1) 禁止发送DMA
// RXDMAEN=0 (bit0) 禁止接收DMA
I2SCR = 0x80;
// I2S分频寄存器高字节
// MCKOE (bit1): 主时钟输出控制
// ODD (bit0): 预分频奇数因子
I2SPRH = (MCKOE << 1) | I2S_ODD;
// I2S分频寄存器低字节:DIV[7:0]
I2SPRL = I2S_DIV;
// I2S配置寄存器高字节
// I2SE (bit2): I2S模块使能(暂不使能,最后再使能)
// I2SCFG[1:0] (bit1:0): 模式配置
// 00-从机发送, 01-从机接收, 10-主机发送, 11-主机接收
// 这里设置为主机发送模式 (I2S_MODE=2)
I2SCFGH = 0x02; // 先设置模式,暂不使能
// I2S配置寄存器低字节
// PCMSYNC (bit7): PCM帧同步,0-短帧同步
// STD[1:0] (bit5:4): I2S标准选择
// 00-I2S飞利浦标准, 01-MSB左对齐, 10-LSB右对齐, 11-PCM
// CKPOL (bit3): 时钟极性,0-稳态低电平
// DATLEN[1:0] (bit2:1): 数据长度,00-16位
// CHLEN (bit0): 通道长度,0-16位
I2SCFGL = (0 << 7) | (0 << 4) | (0 << 3) | (0 << 1) | 0;
// 上述配置: I2S飞利浦标准, 16位数据, 16位通道
// I2S引脚功能切换
// P_SW3 寄存器:
// bit7:6 = I2S_S[1:0] I2S功能脚选择
// bit5:4 = S2SPI_S[1:0]
// bit3:2 = S1SPI_S[1:0]
// bit1:0 = CAN2_S[1:0]
P_SW3 = (P_SW3 & 0x3F) | ((u8)(I2S_PORT_GROUP << 6));
// 根据引脚切换组,设置对应I/O口为推挽输出模式
// 组0: P3.2(BCLK), P3.4(SD), P3.5(WS), P5.4(MCK)
// 组1: P1.5(BCLK), P1.3(SD), P1.6(MCK), P5.4(WS)
// 组2: P2.5(BCLK), P2.3(SD), P2.2(WS), P5.4(MCK)
// 组3: P4.3(BCLK), P4.0(SD), P1.6(MCK), P5.4(WS)
#if (I2S_PORT_GROUP == 0)
// P3.2, P3.4, P3.5, P5.4 设置为推挽输出
P3M0 |= 0x34; // bit5(P3.5)=1, bit4(P3.4)=1, bit2(P3.2)=1
P5M0 |= 0x10; // bit4(P5.4)=1
#elif (I2S_PORT_GROUP == 1)
P1M0 |= 0x68; // bit6(P1.6)=1, bit5(P1.5)=1, bit3(P1.3)=1
P5M0 |= 0x10; // bit4(P5.4)=1
#elif (I2S_PORT_GROUP == 2)
P2M0 |= 0x2C; // bit5(P2.5)=1, bit3(P2.3)=1, bit2(P2.2)=1
P5M0 |= 0x10; // bit4(P5.4)=1
#elif (I2S_PORT_GROUP == 3)
P1M0 |= 0x40; // bit6(P1.6)=1
P4M0 |= 0x09; // bit3(P4.3)=1, bit0(P4.0)=1
P5M0 |= 0x10; // bit4(P5.4)=1
#endif
// 使能I2S模块
I2SCFGH |= 0x04; // I2SE=1
}
/* ======================== 串口1初始化 ======================== */
void uart1_init(void)
{
// 配置串口1使用定时器2作为波特率发生器
// 波特率 = 115200 (FOSC=45.1584MHz)
// 设置P3.0为RxD(准双向口,上电默认),P3.1为TxD
// P3.0/P3.1 上电即为准双向口,无需额外配置
// 定时器2配置为波特率发生器
// T2L 和 T2H 组成16位自动重装载定时器
// 波特率 = FOSC / 4 / (65536 - RL)
// RL = 65536 - FOSC / 4 / 波特率
// 对于 45158400 / 4 / 115200 = 98
// RL = 65536 - 98 = 65438 = 0xFF9E
T2L = 0x9E; // 定时器2低字节
T2H = 0xFF; // 定时器2高字节
// 辅助寄存器1:设置定时器2为波特率发生器
// AUXR: T2x12=1 (定时器2速度12T模式), T2R=1 (启动定时器2)
AUXR = 0x14; // 0x14 = 0001 0100
// bit4(T2R)=1 启动定时器2
// bit2(T2x12)=1 定时器2时钟为1T模式
// 串口1控制寄存器
// SCON: SM0/FE=0, SM1=1 (模式1, 8位UART), REN=1 (允许接收)
SCON = 0x50; // 0x50 = 0101 0000
// 使能串口1接收中断
// IE2: ES1=1 (bit4) 使能串口1中断
IE2 |= 0x04; // 注意:STC32G中串口1中断使能位在IE2的bit2
// 实际上 STC32G 的串口1中断使能是 ES (IE 的 bit4)
// 更正:STC32G 串口1中断使能位为 ES (IE寄存器 bit4)
// 但为了兼容性,使用 IP2 和 IE2 的配置方式
// 实际上 STC32G 的串口1中断使能是 IE 寄存器的 bit4 (ES)
IE |= 0x10; // ES=1, 使能串口1中断
}
/* ======================== 串口发送字符串 ======================== */
void uart1_send_string(u8 *str)
{
while(*str != '\0')
{
SBUF = *str; // 发送一个字节
while(TI == 0); // 等待发送完成
TI = 0; // 清除发送完成标志
str++;
}
}
/* ======================== 计算相位增量 ======================== */
void calc_phase_inc(void)
{
u32 temp;
// 相位增量 = 频率 * 256 / 采样率 * 65536
// 简化: 相位增量 = (频率 * 65536 * 256) / 采样率
// 但为了精度,使用32位计算
// 相位增量 = (u32)g_freq * 65536UL / SAMPLE_RATE;
// 由于我们的正弦表是256点,所以实际相位增量需要乘以256
// 最终: phase_inc = (u32)g_freq * 65536UL * 256UL / SAMPLE_RATE;
// 简化: phase_inc = (u32)g_freq * 16777216UL / SAMPLE_RATE;
temp = (u32)g_freq * 16777216UL / SAMPLE_RATE;
g_phase_inc_l = (u16)temp;
// 右声道频率略高,产生立体声效果(可选)
// 这里左右声道使用相同频率
g_phase_inc_r = g_phase_inc_l;
}
/* ======================== 获取采样值 ======================== */
u16 get_sample_value(u16 phase, u8 wave_type, u8 vol)
{
u16 index;
u16 value;
u32 temp;
// 取相位的高8位作为查表索引
index = (u8)(phase >> 8);
// 根据波形类型生成不同的波形
switch(wave_type)
{
case 0: // 正弦波
value = sin_table[index];
break;
case 1: // 方波
if(index < 128)
value = 32767; // 正半周
else
value = 0; // 负半周(实际为0,但I2S输出需要双极性)
break;
case 2: // 三角波
if(index < 128)
value = (u16)index * 256; // 上升沿
else
value = (u16)(255 - index) * 256; // 下降沿
break;
case 3: // 锯齿波
value = (u16)index * 128; // 线性上升
break;
default:
value = sin_table[index];
break;
}
// 应用音量控制 (0~100)
// 将16位采样值乘以音量百分比
temp = (u32)value * (u32)vol / 100UL;
value = (u16)temp;
return value;
}
/* ======================== 处理串口命令 ======================== */
void process_command(void)
{
u8 *p;
u16 temp_val;
// 确保字符串以null结尾
uart_rx_buf[uart_rx_cnt] = '\0';
// 解析 "freq=xxx" 命令
p = uart_rx_buf;
if((p[0] == 'f') && (p[1] == 'r') && (p[2] == 'e') && (p[3] == 'q') && (p[4] == '='))
{
// 提取频率值
temp_val = 0;
p += 5;
while((*p >= '0') && (*p <= '9'))
{
temp_val = temp_val * 10 + (*p - '0');
p++;
}
// 限制频率范围 (20Hz ~ 20000Hz)
if(temp_val < 20) temp_val = 20;
if(temp_val > 20000) temp_val = 20000;
g_freq = temp_val;
calc_phase_inc(); // 重新计算相位增量
// 发送响应
uart1_send_string("OK: freq=");
// 简单数字转字符串发送
// 这里简化处理,实际可使用sprintf
return;
}
// 解析 "vol=xxx" 命令
if((p[0] == 'v') && (p[1] == 'o') && (p[2] == 'l') && (p[3] == '='))
{
temp_val = 0;
p += 4;
while((*p >= '0') && (*p <= '9'))
{
temp_val = temp_val * 10 + (*p - '0');
p++;
}
if(temp_val > 100) temp_val = 100;
g_vol = (u8)temp_val;
uart1_send_string("OK: vol=");
return;
}
// 解析 "wave=x" 命令
if((p[0] == 'w') && (p[1] == 'a') && (p[2] == 'v') && (p[3] == 'e') && (p[4] == '='))
{
temp_val = p[5] - '0';
if(temp_val > 3) temp_val = 0;
g_wave = (u8)temp_val;
uart1_send_string("OK: wave=");
return;
}
// 未知命令
uart1_send_string("Unknown command\r\n");
}
/* ======================== I2S 中断服务函数 ======================== */
/*
* I2S中断向量号为62
* 在I2S发送缓冲区空中断中,填充下一个音频采样数据
* 每次中断发送一个声道的数据(左声道或右声道)
*/
void I2S_ISR(void) interrupt 62
{
u16 sample;
// 检查发送缓冲区空标志 (TXE, I2SSR bit1)
if(I2SSR & 0x02)
{
// 检查当前是哪个声道 (CHSID, I2SSR bit2)
// 0-左声道, 1-右声道
if(I2SSR & 0x04) // 右声道
{
// 获取右声道采样值
sample = get_sample_value(g_idx_r, g_wave, g_vol);
// 写入I2S数据寄存器
// 注意:必须先写高字节,再写低字节
I2SDRH = (u8)(sample >> 8); // 高字节
I2SDRL = (u8)(sample & 0xFF); // 低字节
// 更新右声道相位
g_idx_r += g_phase_inc_r;
}
else // 左声道
{
// 获取左声道采样值
sample = get_sample_value(g_idx_l, g_wave, g_vol);
// 写入I2S数据寄存器
I2SDRH = (u8)(sample >> 8); // 高字节
I2SDRL = (u8)(sample & 0xFF); // 低字节
// 更新左声道相位
g_idx_l += g_phase_inc_l;
}
}
}
/* ======================== 串口1中断服务函数 ======================== */
void UART1_ISR(void) interrupt 4
{
u8 rx_data;
if(RI) // 接收中断
{
RI = 0; // 清除接收中断标志
rx_data = SBUF; // 读取接收数据
// 将接收到的字符存入缓冲区
if(rx_data == '\r') // 回车键,命令结束
{
uart_rx_ok = 1; // 设置命令接收完成标志
uart_rx_cnt = 0; // 重置接收计数
}
else if(rx_data == '\n') // 换行,忽略
{
// 不做处理
}
else
{
if(uart_rx_cnt < sizeof(uart_rx_buf) - 1)
{
uart_rx_buf[uart_rx_cnt++] = rx_data;
}
}
}
if(TI) // 发送中断
{
TI = 0; // 清除发送中断标志
}
}
文件2:项目配置说明
Keil C251 项目设置:
- 芯片选择:STC32G8K64
- Target 设置:
- Memory Model: XSmall (edata)
- Code Rom Size: Large (64K)
- Operating system: None
- Output 设置:
- Create HEX File: 勾选
- HEX Format: HEX-80
- C251 编译器设置:
- Define:
FOSC=45158400UL
- Optimization: 7 (Speed)
ISP 下载设置(AiCube-ISP):
- 芯片型号:STC32G8K64
- 内部IRC频率:45.1584MHz
- 硬件选项:根据需要设置(如使能低压复位等)
程序说明
这个演示程序实现了以下功能:
-
I2S 音频输出:使用 STC32G8K64 的 I2S 模块,以主机发送模式输出 16 位双声道音频数据,采样率 44.1kHz。
-
波形生成:内置 256 点正弦波表,支持四种波形输出:
-
串口命令控制:通过串口 1(115200bps)接收 PC 命令:
freq=1000 — 设置频率(20Hz~20000Hz)
vol=80 — 设置音量(0~100)
wave=0 — 选择波形(0~3)
-
立体声输出:左右声道独立相位累加器,可实现立体声效果。
硬件连接建议
推荐使用 PCM5102A I2S DAC 模块,连接方式:
| STC32G8K64 |
PCM5102A |
| P3.2 (I2SBCK) |
BCK |
| P3.4 (I2SData) |
DIN |
| P3.5 (I2SLRCK) |
LRCK |
| P5.4 (I2SMCK) |
SCK |
| VCC (3.3V) |
VCC |
| GND |
GND |
PCM5102A 的 XSMT 引脚需要拉高(接 VCC)以解除静音。
注意事项
- STC32G8K64 无 USB 模块,因此无法直接通过 USB 与 PC 通信,需要使用串口转 USB 工具(如 CH340G)连接 P3.0/P3.1。
- I2S 引脚切换:通过修改
I2S_PORT_GROUP 宏可以选择不同的引脚组,方便 PCB 布局。
- 时钟精度:内部 IRC 在常温下精度为 ±0.3%,对于音频应用完全足够。如需更高精度,可外接 45.1584MHz 晶振。
- 音量控制:通过软件乘法实现,音量值范围 0~100,0 为静音,100 为最大音量。
这个程序是一个完整的声卡演示框架,您可以根据实际需求扩展更多功能,如添加 WAV 播放、音频效果处理等。
🔍 搜索 "STC32G8K64 声卡 演示 程序 注释"
📌 相关论坛讨论
- 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】
⏱️ 查找用时 16.8s | 总回答用时 83.8s