第一步:定义全局变量
- 包括歌曲和音符数组。
- 定义控制LED灯和按键的GPIO地址(这里你需要提供准确的硬件位置信息)。
- 定义用于ADC旋钮调节音量的变量。
c
// 全局变量定义
char song0[] = {2, 3, 4, 3, 2, 3, 2, 3, 2, 4, 2, 1, 4, 3, 2}; // 两只老虎
char song1[] = {1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1}; // 生日快乐
char song2[] = {4, 4, 5, 5, 6, 6, 7, 7, 6, 6, 5, 5}; // abcdefg
char song3[] = {7, 6, 5, 4, 3, 2, 1}; // 祝你平安
char yf[] = {0, 191, 172, 159, 144, 135, 120, 107}; // 音符对应的频率表
char song_index = 0; // 当前播放的歌曲索引
char play_pos = 0; // 当前音符位置
// LED 和按键的 GPIO 地址(根据你的实际硬件位置调整)
#define GPIO_LED1 *(volatile long*)0x11400000 // LED 1 的 GPIO 地址
#define GPIO_LED2 *(volatile long*)0x11400004 // LED 2 的 GPIO 地址
#define GPIO_LED3 *(volatile long*)0x11400008 // LED 3 的 GPIO 地址
#define GPIO_LED4 *(volatile long*)0x1140000C // LED 4 的 GPIO 地址
#define BUTTON_PIN *(volatile long*)0x11000020 // 按键的 GPIO 地址,用于切换歌曲
// ADC 的 GPIO 地址,用于旋钮控制音量
#define ADCCON *(volatile long*)0x126C0000 // ADC 控制寄存器
#define ADCDAT *(volatile long*)0x126C000C // ADC 数据寄存器
#define ADCMUX *(volatile long*)0x126C001C // ADC 多路复用器
第二步:UART 串口初始化
- 初始化 UART 用于调试输出。
- 实现串口发送字符和字符串的功能。
c
// 串口初始化及调试输出相关定义
#define GPA1CON *(volatile long*)0x11400020 // GPIO A1 控制寄存器
#define ULCON2 *(volatile long*)0x13820000 // UART2 控制寄存器
#define UCON2 *(volatile long*)0x13820004 // UART2 控制寄存器
#define UTRSTAT2 *(volatile long*)0x13820010 // UART2 状态寄存器
#define UTXH2 *(volatile long*)0x13820020 // UART2 发送寄存器
#define UBRDIV2 *(volatile long*)0x13820028 // UART2 波特率分频寄存器
#define UFRACVAL2 *(volatile long*)0x1382002C // UART2 波特率小数部分寄存器
// 串口初始化函数
void uart_init(void) {
GPA1CON = GPA1CON & ~0xF;
GPA1CON = GPA1CON | (1 << 1); // UART TX
GPA1CON = GPA1CON & ~(0xF << 4);
GPA1CON = GPA1CON | (1 << 5); // UART RX
ULCON2 = 0x03; // 8 位数据,无校验,1 位停止位
UCON2 = 0x05; // 轮询模式
UBRDIV2 = 53; // 设置波特率为 115200
UFRACVAL2 = 4;
}
// 串口发送单个字符
void putc(char ch) {
while (!(UTRSTAT2 & (1 << 1))); // 等待发送缓冲区空
UTXH2 = ch;
}
// 串口发送字符串
void puts(char *s) {
int i = 0;
while (s[i]) {
putc(s[i]);
i++;
}
}
第三步:ADC 初始化和音量读取
通过 ADC 获取旋钮的电压值,并将其用于调节音量大小。
c
// ADC 初始化函数
void adc_init(void) {
ADCCON |= 1 << 16; // 12 比特分辨率
ADCCON |= 1 << 14; // 使能分频
ADCCON &= ~(0xFF << 6);
ADCCON |= 19 << 6; // 设置分频值
ADCCON &= ~(1 << 2); // 禁用空闲模式
ADCCON |= 1 << 0; // 启动 ADC
ADCMUX = 3; // 使用第 3 个 ADC 通道
}
// 获取 ADC 值并转换为毫伏值
int adc_get_mv(void) {
int reg = ADCDAT & 0xFFF;
return reg * 1800 / 4095; // 将 ADC 数据转换为毫伏
}
第四步:PWM 定时器配置用于音符播放
c
// PWM 定时器初始化
#define TCFG0 *(volatile long*)0x139D0000 // 定时器配置寄存器 0
#define TCFG1 *(volatile long*)0x139D0004 // 定时器配置寄存器 1
#define TCNTB0 *(volatile long*)0x139D000C // 定时器计数寄存器,用于设置周期
#define TCMPB0 *(volatile long*)0x139D0010 // 定时器比较寄存器,用于设置占空比
#define TCON *(volatile long*)0x139D0008 // 定时器控制寄存器
void pwm_init(void) {
// 配置预分频器
TCFG0 = (TCFG0 & ~(0xFF)) | 99; // 预分频器1,设置为99+1=100分频
TCFG1 = (TCFG1 & ~(0xF)) | 0x2; // 分频器2,设置为PWM模式的1/4分频
// 设置PWM占空比为50%,即一半时间高电平,一半时间低电平
TCMPB0 = TCNTB0 / 2; // 设置比较寄存器,产生50%的占空比
// 设置定时器控制寄存器,启用自动重载并开始PWM输出
TCON |= (1 << 1); // 手动更新 TCNTB0 和 TCMPB0 的值
TCON &= ~(1 << 1); // 清除手动更新位
TCON |= (1 << 0) | (1 << 3); // 启用定时器0并启动PWM
}
// 播放音符函数,调整PWM频率以产生不同音符
void play_yinfu(char note) {
if (note >= 1 && note <= 7) {
TCNTB0 = yf[note]; // 根据音符调整PWM频率
TCMPB0 = TCNTB0 / 2; // 设置50%占空比
TCON |= (1 << 1); // 手动更新TCNTB0和TCMPB0的值
TCON &= ~(1 << 1); // 清除手动更新位
TCON |= (1 << 0); // 启动PWM输出
}
}
第五步:按键切换歌曲及 LED 显示
不断轮询 ADC 的值用于调节音量,并根据当前的歌曲播放音符。
c
// 切换歌曲和控制灯光的中断处理函数
void do_irq() {
song_index = (song_index + 1) % 4; // 切换到下一首歌
play_pos = 0; // 从头开始播放
// 切换灯光逻辑
switch (song_index) {
case 0:
GPIO_LED1 = 1;
GPIO_LED2 = 0;
GPIO_LED3 = 0;
GPIO_LED4 = 0;
break;
case 1:
GPIO_LED1 = 0;
GPIO_LED2 = 1;
GPIO_LED3 = 0;
GPIO_LED4 = 0;
break;
case 2:
GPIO_LED1 = 0;
GPIO_LED2 = 0;
GPIO_LED3 = 1;
GPIO_LED4 = 0;
break;
case 3:
GPIO_LED1 = 0;
GPIO_LED2 = 0;
GPIO_LED3 = 0;
GPIO_LED4 = 1;
break;
}
puts("Song changed!\r\n);
}
// 定义按键中断
void handle_button_press() {
if (BUTTON_PIN & 0x01) { // 假设按键按下触发低电平
do_irq(); // 处理按键按下,切换歌曲
}
}
第六步: 主程序逻辑
主程序将持续运行,轮询按键的状态,控制 LED 和音符的播放,并根据 ADC 旋钮调节音量和播放速度。
c
// 主程序
int main(void) {
int mv;
int qian, bai, shi, ge;
uart_init(); // 初始化串口
adc_init(); // 初始化 ADC
pwm_init(); // 初始化 PWM
puts("System initialized!\r\n");
while (1) {
// 检查按键是否按下
handle_button_press(); // 检查是否有按键按下,处理歌曲切换
// 获取当前的音量值(通过 ADC 读取旋钮值)
mv = adc_get_mv(); // 获取当前电压值
// 将电压值转换为千位、百位、十位和个位
qian = mv / 1000;
bai = (mv / 100) % 10;
shi = (mv / 10) % 10;
ge = mv % 10;
// 通过串口发送电压值,用于调试
putc(qian + '0');
putc(bai + '0');
putc(shi + '0');
putc(ge + '0');
puts(" mV\r\n");
// 根据当前歌曲索引和音符位置播放音符
switch (song_index) {
case 0:
play_yinfu(song0[play_pos]);
play_pos++;
if (play_pos >= sizeof(song0)) {
play_pos = 0; // 一首歌曲播放完毕,重头开始
}
break;
case 1:
play_yinfu(song1[play_pos]);
play_pos++;
if (play_pos >= sizeof(song1)) {
play_pos = 0;
}
break;
case 2:
play_yinfu(song2[play_pos]);
play_pos++;
if (play_pos >= sizeof(song2)) {
play_pos = 0;
}
break;
case 3:
play_yinfu(song3[play_pos]);
play_pos++;
if (play_pos >= sizeof(song3)) {
play_pos = 0;
}
break;
}
// 使用 ADC 值(mv)调节播放速度,音量越大,播放速度越快
mdelay(600 - mv / 3); // 根据音量值调整播放速度
}
return 0;
}
第七步:关键函数:延时函数 mdelay
为了实现不同音符的播放速度,我们需要使用一个简单的延时函数。
c
// 简单的延时函数
void mdelay(int ms) {
while (ms--) {
int num = 0x1FFF; // 延时循环
while (num--);
}
}
总结
c
歌曲数组和音符频率:定义了4首歌曲,并通过 yf[] 数组存储了不同音符对应的频率。play_yinfu() 函数使用 PWM 调节频率,实现音符播放。
按键切换歌曲:handle_button_press() 函数负责检测按键是否按下,并通过 do_irq() 切换当前播放的歌曲。同时对应的 LED 指示灯显示当前播放的歌曲。
ADC 调节音量:ADC 通过旋钮获取电压值,用于调节播放速度。较高的电压值(即较大的旋钮值)会加快播放速度。
串口调试信息输出:通过 UART 发送调试信息,输出当前的 ADC 值(音量信息)和系统状态。
主循环逻辑:主程序不断轮询按键状态和 ADC 值,控制 LED 和播放音符,并根据音量值调节播放速度。
完整代码
c
// 全局变量定义
char song0[] = {2, 3, 4, 3, 2, 3, 2, 3, 2, 4, 2, 1, 4, 3, 2}; // 两只老虎
char song1[] = {1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1}; // 生日快乐
char song2[] = {4, 4, 5, 5, 6, 6, 7, 7, 6, 6, 5, 5}; // abcdefg
char song3[] = {7, 6, 5, 4, 3, 2, 1}; // 祝你平安
char yf[] = {0, 191, 172, 159, 144, 135, 120, 107}; // 音符对应的频率表
char song_index = 0; // 当前播放的歌曲索引
char play_pos = 0; // 当前音符位置
// LED 和按键的 GPIO 地址(根据实际硬件调整)
#define GPIO_LED1 *(volatile long*)0x11400000 // LED 1 的 GPIO 地址
#define GPIO_LED2 *(volatile long*)0x11400004 // LED 2 的 GPIO 地址
#define GPIO_LED3 *(volatile long*)0x11400008 // LED 3 的 GPIO 地址
#define GPIO_LED4 *(volatile long*)0x1140000C // LED 4 的 GPIO 地址
#define BUTTON_PIN *(volatile long*)0x11000020 // 按键的 GPIO 地址,用于切换歌曲
// ADC 的 GPIO 地址,用于旋钮控制音量
#define ADCCON *(volatile long*)0x126C0000 // ADC 控制寄存器
#define ADCDAT *(volatile long*)0x126C000C // ADC 数据寄存器
#define ADCMUX *(volatile long*)0x126C001C // ADC 多路复用器
// 串口初始化及调试输出相关定义
#define GPA1CON *(volatile long*)0x11400020 // GPIO A1 控制寄存器
#define ULCON2 *(volatile long*)0x13820000 // UART2 控制寄存器
#define UCON2 *(volatile long*)0x13820004 // UART2 控制寄存器
#define UTRSTAT2 *(volatile long*)0x13820010 // UART2 状态寄存器
#define UTXH2 *(volatile long*)0x13820020 // UART2 发送寄存器
#define UBRDIV2 *(volatile long*)0x13820028 // UART2 波特率分频寄存器
#define UFRACVAL2 *(volatile long*)0x1382002C // UART2 波特率小数部分寄存器
// PWM 定时器相关定义
#define TCFG0 *(volatile long*)0x139D0000 // 定时器配置寄存器 0
#define TCFG1 *(volatile long*)0x139D0004 // 定时器配置寄存器 1
#define TCNTB0 *(volatile long*)0x139D000C // 定时器计数寄存器,用于设置周期
#define TCMPB0 *(volatile long*)0x139D0010 // 定时器比较寄存器,用于设置占空比
#define TCON *(volatile long*)0x139D0008 // 定时器控制寄存器
// 串口初始化函数
void uart_init(void) {
GPA1CON = GPA1CON & ~0xF;
GPA1CON = GPA1CON | (1 << 1); // UART TX
GPA1CON = GPA1CON & ~(0xF << 4);
GPA1CON = GPA1CON | (1 << 5); // UART RX
ULCON2 = 0x03; // 8 位数据,无校验,1 位停止位
UCON2 = 0x05; // 轮询模式
UBRDIV2 = 53; // 设置波特率为 115200
UFRACVAL2 = 4;
}
// 串口发送单个字符
void putc(char ch) {
while (!(UTRSTAT2 & (1 << 1))); // 等待发送缓冲区空
UTXH2 = ch;
}
// 串口发送字符串
void puts(char *s) {
int i = 0;
while (s[i]) {
putc(s[i]);
i++;
}
}
// ADC 初始化函数
void adc_init(void) {
ADCCON |= 1 << 16; // 12 比特分辨率
ADCCON |= 1 << 14; // 使能分频
ADCCON &= ~(0xFF << 6);
ADCCON |= 19 << 6; // 设置分频值
ADCCON &= ~(1 << 2); // 禁用空闲模式
ADCCON |= 1 << 0; // 启动 ADC
ADCMUX = 3; // 使用第 3 个 ADC 通道
}
// 获取 ADC 值并转换为毫伏值
int adc_get_mv(void) {
int reg = ADCDAT & 0xFFF;
return reg * 1800 / 4095; // 将 ADC 数据转换为毫伏
}
// PWM 定时器初始化
void pwm_init(void) {
// 配置预分频器
TCFG0 = (TCFG0 & ~(0xFF)) | 99; // 预分频器1,设置为99+1=100分频
TCFG1 = (TCFG1 & ~(0xF)) | 0x2; // 分频器2,设置为PWM模式的1/4分频
// 设置PWM占空比为50%,即一半时间高电平,一半时间低电平
TCMPB0 = TCNTB0 / 2; // 设置比较寄存器,产生50%的占空比
// 设置定时器控制寄存器,启用自动重载并开始PWM输出
TCON |= (1 << 1); // 手动更新 TCNTB0 和 TCMPB0 的值
TCON &= ~(1 << 1); // 清除手动更新位
TCON |= (1 << 0) | (1 << 3); // 启用定时器0并启动PWM
}
// 播放音符函数,调整PWM频率以产生不同音符
void play_yinfu(char note) {
if (note >= 1 && note <= 7) {
TCNTB0 = yf[note]; // 根据音符调整PWM频率
TCMPB0 = TCNTB0 / 2; // 设置50%占空比
TCON |= (1 << 1); // 手动更新TCNTB0和TCMPB0的值
TCON &= ~(1 << 1); // 清除手动更新位
TCON |= (1 << 0); // 启动PWM输出
}
}
// 切换歌曲和控制灯光的中断处理函数
void do_irq() {
song_index = (song_index + 1) % 4; // 切换到下一首歌
play_pos = 0; // 从头开始播放
// 切换灯光逻辑
switch (song_index) {
case 0:
GPIO_LED1 = 1;
GPIO_LED2 = 0;
GPIO_LED3 = 0;
GPIO_LED4 = 0;
break;
case 1:
GPIO_LED1 = 0;
GPIO_LED2 = 1;
GPIO_LED3 = 0;
GPIO_LED4 = 0;
break;
case 2:
GPIO_LED1 = 0;
GPIO_LED2 = 0;
GPIO_LED3 = 1;
GPIO_LED4 = 0;
break;
case 3:
GPIO_LED1 = 0;
GPIO_LED2 = 0;
GPIO_LED3 = 0;
GPIO_LED4 = 1;
break;
}
puts("Song changed!\r\n"); // 通过串口打印调试信息
}
// 定义按键中断
void handle_button_press() {
if (BUTTON_PIN & 0x01) { // 假设按键按下触发低电平
do_irq(); // 处理按键按下,切换歌曲
}
}
// 简单的延时函数
void mdelay(int ms) {
while (ms--) {
int num = 0x1FFF; // 延时循环
while (num--);
}
}
// 主程序
int main(void) {
int mv;
int qian, bai, shi, ge;
uart_init(); // 初始化串口
adc_init(); // 初始化 ADC
pwm_init(); // 初始化 PWM
puts("System initialized!\r\n");
while (1) {
// 检查按键是否按下
handle_button_press(); // 检查是否有按键按下,处理歌曲切换
// 获取当前的音量值(通过 ADC 读取旋钮值)
mv = adc_get_mv(); // 获取当前电压值
// 将电压值转换为千位、百位、十位和个位
qian = mv / 1000;
bai = (mv / 100) % 10;
shi = (mv / 10) % 10;
ge = mv % 10;
// 通过串口发送电压值,用于调试
putc(qian + '0');
putc(bai + '0');
putc(shi + '0');
putc(ge + '0');
puts(" mV\r\n");
// 根据当前歌曲索引和音符位置播放音符
switch (song_index) {
case 0:
play_yinfu(song0[play_pos]);
play_pos++;
if (play_pos >= sizeof(song0)) {
play_pos = 0; // 一首歌曲播放完毕,重头开始
}
break;
case 1:
play_yinfu(song1[play_pos]);
play_pos++;
if (play_pos >= sizeof(song1)) {
play_pos = 0;
}
break;
case 2:
play_yinfu(song2[play_pos]);
play_pos++;
if (play_pos >= sizeof(song2)) {
play_pos = 0;
}
break;
case 3:
play_yinfu(song3[play_pos]);
play_pos++;
if (play_pos >= sizeof(song3)) {
play_pos = 0;
}
break;
}
// 使用 ADC 值(mv)调节播放速度,音量越大,播放速度越快
mdelay(600 - mv / 3); // 根据音量值调整播放速度
}
return 0;
}
相关代码
- start.S 去前面章节找一个带有中断的汇编即可
- Makefile 和map.lds也在前面有
- Linux下执行make 烧录bin文件即可。