ARM综合编程--LED--ADC--UART--中断--PWM综合示例按键和旋钮控制音乐

第一步:定义全局变量

  • 包括歌曲和音符数组。
  • 定义控制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;
}

相关代码

  1. start.S 去前面章节找一个带有中断的汇编即可
  2. Makefile 和map.lds也在前面有
  3. Linux下执行make 烧录bin文件即可。
相关推荐
etcix4 分钟前
implement copy file content to clipboard on Windows
windows·stm32·单片机
谱写秋天10 分钟前
在STM32F103上进行FreeRTOS移植和配置(STM32CubeIDE)
c语言·stm32·单片机·freertos
globbo4 小时前
【嵌入式STM32】I2C总结
单片机·嵌入式硬件
玖別ԅ(¯﹃¯ԅ)5 小时前
SysTick寄存器(嘀嗒定时器实现延时)
stm32·单片机·嵌入式硬件
limitless_peter5 小时前
集成运算放大器(反向比例,同相比例)
嵌入式硬件·硬件工程
Blossom.1186 小时前
把 AI 推理塞进「 8 位 MCU 」——0.5 KB RAM 跑通关键词唤醒的魔幻之旅
人工智能·笔记·单片机·嵌入式硬件·深度学习·机器学习·搜索引擎
桃源学社(接毕设)7 小时前
基于人工智能和物联网融合跌倒监控系统(LW+源码+讲解+部署)
人工智能·python·单片机·yolov8
玖別ԅ(¯﹃¯ԅ)7 小时前
PID学习笔记6-倒立摆的实现
笔记·stm32·单片机
YLAD8 小时前
gnu arm toolchain中的arm-none-eabi-gdb.exe的使用方法?
arm开发
饶宇航9 小时前
嵌入式硬件——ARM
arm开发