自学内容网 自学内容网

ARM编程--->ADC编程实例

ARM 下的 ADC(模数转换器)

  • ADC(Analog to Digital Converter,模数转换器)是嵌入式系统中非常重要的一个模块,负责将连续变化的模拟信号(如温度、电压、电流等)转换为数字信号,供 CPU 和程序进行处理。
  1. ADC 基本概念
    • 模拟量(Analog Quantity):模拟量是连续变化的物理量,比如温度、湿度、光照强度、电压等。它们的变化是连续的,无法直接被数字处理器理解。
    • 数字量(Digital Quantity):数字量是离散的、不连续的。数字处理器只能处理数字量,比如 1 和 0 的组合。
    • 转换:ADC 模块负责将模拟信号(比如电压信号)转换为相应的数字量,使处理器能够读取和处理。
  2. 工作原理
  • 在 ARM 系统中的 ADC 模块通常是一个基于电压的转换器,它通过对输入电压信号的采样,将其转换为对应的数字值。
  • 例如,在 12 位 ADC 中,输入电压的范围通常为 0 - Vref,其中 Vref 是参考电压。输入电压 Vx 可以通过以下公式转换为数字值
    B L = V x V r e f × 4095 BL = \frac{V_x}{V_{ref}} \times 4095 BL=VrefVx×4095
    • BL 是 ADC 转换得到的数字值,12 位 ADC 的最大值是 4095(即 2^12 - 1)
    • Vx 是输入的电压值
    • Vref 是参考电压,通常为 3.3V 或 5V
  1. ARM下的ADC特点
    • 分辨率:ADC 的分辨率决定了它能将模拟量细分为多少个数字值。例如,12 位分辨率的 ADC 能将输入电压分成 4096 个离散的数字量(即 0 - 4095)
    • 采样速度:这是 ADC 每秒能够执行的转换次数,通常以 Samples Per Second(SPS,样本/秒)为单位
    • 参考电压(Vref):这是 ADC 转换时的参考电压,通常由外部或者内部的电压源提
  2. ADC编程步骤
    • 配置 GPIO 引脚为模拟输入模式: 在一些 ARM Cortex-M 系列的 MCU 中,ADC 通道与特定的 GPIO 引脚绑定,在使用 ADC 之前需要将 GPIO 配置为模拟模式。
    • 配置 ADC 模块:
      • 配置分辨率和采样速度(如 12 位,1 MSPS)。
      • 设置参考电压(Vref)。
    • 启动转换: 启动 ADC 转换,等待 ADC 模块完成采样并将结果存储在数据寄存器中。
    • 读取 ADC 数据寄存器: 一旦转换完成,ADC 数据寄存器将存储转换后的数字值,可以通过访问寄存器读取这个值。
  3. ARM 典型寄存器配置(伪代码)
// 伪代码 - ARM ADC 配置与读取
//基于 ARM Cortex-M 系列的 ADC 配置步骤
void ADC_Init(void)
{
    // 1. 配置 GPIO 引脚为模拟输入模式
    GPIO_PinConfig(GPIOA, PIN1, GPIO_MODE_ANALOG);

    // 2. 配置 ADC 模块
    ADC1->CR1 = 0;               // 配置为12位模式
    ADC1->SQR1 = 0;              // 设置规则序列长度为 1
    ADC1->SMPR2 |= ADC_SMPR2_SMP10; // 设置采样时间

    // 3. 启动 ADC
    ADC1->CR2 |= ADC_CR2_ADON;    // 启用 ADC 模块
}

uint16_t ADC_Read(void)
{
    // 4. 启动转换
    ADC1->CR2 |= ADC_CR2_SWSTART; // 启动转换

    // 5. 等待转换完成
    while(!(ADC1->SR & ADC_SR_EOC)); // 等待转换结束

    // 6. 读取 ADC 数据寄存器
    return ADC1->DR; // 返回转换后的值
}

  1. ARM 下的 ADC 实际应用
    • 温度测量:读取热敏电阻输出的模拟电压值,将其转换为数字值,再根据转换结果计算温度。
    • 电压监测:通过电压分压电路,监测电池或电源的电压值,进而判断电源状态。
    • 光强度检测:读取光电二极管或光敏电阻的模拟信号,转换为数字量后判断环境光照强度。

ARM的ADC编程实例(main.c)

  1. 第一步:UART初始化
    目的:初始化串口通信模块,使 CPU 可以通过 UART 向外部设备发送字符数据(例如,输出 ADC 转换得到的电压值
#defineGPA1CON  *(volatile long*)0x11400020// GPIO A1 控制寄存器地址
#defineULCON2*(volatile long*)0x13820000// UART 控制寄存器 ULCON2,设置 UART 数据格式
#defineUCON2*(volatile long*)0x13820004// UART 控制寄存器 UCON2,设置 UART 的控制模式(轮询模式)
#defineUTRSTAT2*(volatile long*)0x13820010// UART 状态寄存器,用于查询 UART 是否就绪
#defineUTXH2*(volatile long*)0x13820020// UART 发送寄存器,用于向 UART 发送数据

#defineUBRDIV2*(volatile long*)0x13820028// UART 波特率寄存器,设置波特率
#defineUFRACVAL2 *(volatile long*)0x1382002C// UART 波特率分数寄存器,用于波特率微调

void uart_init(void)
{
GPA1CON = GPA1CON & ~0xF;                  // 清除 GPA1_0 的配置位,确保后续操作正确
GPA1CON = GPA1CON | (1<<1);                // 设置 GPA1_0 为 UART 发送功能
GPA1CON = GPA1CON & ~(0xF<<4);             // 清除 GPA1_1 的配置位
GPA1CON = GPA1CON | (1<<5);                // 设置 GPA1_1 为 UART 接收功能

ULCON2 = ULCON2 | 0x3;                     // 设置 UART 数据格式为 8 位数据,无校验位,1 位停止位
ULCON2 = ULCON2 & ~(1<<2);                 // 确保校验位为无校验

UCON2 &= ~(3);                             // 清除 UART 控制模式位
UCON2 |= 1<<0;                             // 设置 UART 为轮询接收模式
UCON2 &= ~(3<<2);                          // 清除 UART 控制模式位
UCON2 |= 1<<2;                             // 设置 UART 为轮询发送模式

UBRDIV2 = 53;                              // 设置 UART 波特率分频值
UFRACVAL2 = 4;                             // 设置 UART 波特率的分数部分
}

  1. 第二步:ADC初始化
    目的:初始化 ADC,使 CPU 能够从指定的 ADC 通道读取模拟电压值,并转换为数字信号
#defineADCCON  *(volatile long*)0x126C0000    // ADC 控制寄存器地址
#defineADCDAT*(volatile long*)0x126C000C    // ADC 数据寄存器地址
#defineADCMUX*(volatile long*)0x126C001C// ADC 多路复用器寄存器地址,选择 ADC 输入通道

void adc_init(void)
{
ADCCON |= 1<<16;                           // 启用 12 位分辨率
ADCCON |= 1<<14;                           // 使能分频器
ADCCON &= ~(0xFF<<6);                      // 清除原有的分频器设置
ADCCON |= 19<<6;                           // 设置分频器值为 19
ADCCON &= ~(1<<2);                         // 禁用空闲模式
ADCCON |= 1<<1;                            // 启用自动重新转换
ADCCON |= 1<<0;                            // 启动 ADC 转换

ADCMUX = 3;                                // 选择 ADC 输入通道 3

int reg = ADCDAT;                          // 读取一次 ADC 数据寄存器,准备初始化
}

  1. 第三步:获取 ADC 数据并将其转换为电压值
    目的:通过 ADC 读取输入电压并转换为数字值(mV),用于后续的显示。
int adc_get_mv(void)
{
int reg = ADCDAT & 0xFFF;                  // 获取 ADC 数据寄存器中的 12 位有效数据
int mv = reg * 1800 / 4095;                // 将 ADC 数据转换为毫伏(1800mV 为参考电压)
return mv;                                 // 返回计算出的电压值
}

  1. 第四步:通过 UART 输出电压值
    目的:将 ADC 读取的电压值以字符形式通过 UART 发送,显示到外部设备。
void main(void)
{
int mv;
int qian, bai, shi, ge;

uart_init();                               // 初始化 UART,配置波特率和 GPIO
adc_init();                                // 初始化 ADC,设置分频器和输入通道

while(1){
mv = adc_get_mv();                     // 获取 ADC 转换得到的电压值(以 mV 为单位)

// 分离电压值的每一位数字(千、百、十、个位)
qian = mv / 1000;                      // 计算千位
putc(qian + '0');                      // 发送千位数字

bai = mv / 100 % 10;                   // 计算百位
putc(bai + '0');                       // 发送百位数字

shi = mv / 10 % 10;                    // 计算十位
putc(shi + '0');                       // 发送十位数字

ge = mv % 10;                          // 计算个位
putc(ge + '0');                        // 发送个位数字

// 发送 'mv' 后缀,表示毫伏
putc('m');
putc('v');
putc('\r');                            // 发送回车符
putc('\n');                            // 发送换行符

mysleep(200);                          // 延时 200ms
}
}

  1. 第五步:将电压值拆分成千、百、十、个位并输出
    目的:将电压值(以毫伏为单位)拆分成千位、百位、十位和个位,然后逐位通过 UART 输出字符形式的数值,最后附加上 “mv” 单位。
void main(void)
{
int mv;
int qian, bai, shi, ge;

uart_init();                               // 初始化 UART,配置波特率和 GPIO
adc_init();                                // 初始化 ADC,设置分频器和输入通道

while(1){
mv = adc_get_mv();                     // 获取 ADC 转换得到的电压值(以 mV 为单位)

// 分离电压值的每一位数字(千、百、十、个位)
qian = mv / 1000;                      // 计算千位
putc(qian + '0');                      // 发送千位数字

bai = mv / 100 % 10;                   // 计算百位
putc(bai + '0');                       // 发送百位数字

shi = mv / 10 % 10;                    // 计算十位
putc(shi + '0');                       // 发送十位数字

ge = mv % 10;                          // 计算个位
putc(ge + '0');                        // 发送个位数字

// 发送 'mv' 后缀,表示毫伏
putc('m');                             // 发送字符 'm'
putc('v');                             // 发送字符 'v'
putc('\r');                            // 发送回车符 '\r'
putc('\n');                            // 发送换行符 '\n'

mysleep(200);                          // 延时 200 毫秒,避免过快刷新输出
}
}

  1. 第六步:延时函数 mysleep
    目的:通过简单的循环实现延时功能,避免程序过快地进行下一轮 ADC 读取与数据输出
void mysleep(int ms)
{
while(ms--){                               // 每次减少 1 毫秒
int num = 0x1FFF/2;                    // 初始化计数器,数值决定延时时间
while(num--);                          // 空循环,达到延时效果
}
}

  1. 综合示例
//使用 UART 和 ADC 来获取模拟电压值并通过 UART 输出电压读数。它包括 UART 初始化、ADC 初始化、获取 ADC 数据和通过串口输出电压值的过程。
#defineGPA1CON  *(volatile long*)0x11400020  // GPIO A1 控制寄存器
#defineULCON2*(volatile long*)0x13820000  // UART2 控制寄存器,设置数据位、停止位、校验等
#defineUCON2*(volatile long*)0x13820004  // UART2 控制寄存器,设置轮询、发送和接收模式
#defineUTRSTAT2*(volatile long*)0x13820010  // UART2 状态寄存器,检查发送和接收状态
#defineUTXH2*(volatile long*)0x13820020  // UART2 发送缓冲寄存器,发送数据
#defineURXH2*(volatile long*)0x13820024  // UART2 接收缓冲寄存器,接收数据
#defineUBRDIV2*(volatile long*)0x13820028  // UART2 波特率分频寄存器,设置波特率
#defineUFRACVAL2 *(volatile long*)0x1382002C  // UART2 波特率小数部分寄存器

// 初始化 UART
void uart_init(void)
{
GPA1CON = GPA1CON & ~0xF;        // 清除 GPA1_0 的原有配置
GPA1CON = GPA1CON | (1<<1);      // 设置 GPA1_0 为 UART TX (发送)
GPA1CON = GPA1CON & ~(0xF<<4);   // 清除 GPA1_1 的原有配置
GPA1CON = GPA1CON | (1<<5);      // 设置 GPA1_1 为 UART RX (接收)

ULCON2 = ULCON2 | 0x3;           // 配置 UART2:8 位数据,无校验,1 位停止位
ULCON2 = ULCON2 & ~(1<<2);       // 设置 1 位停止位
ULCON2 = ULCON2 & ~(7<<3);       // 禁用校验位
ULCON2 = ULCON2 & ~(1<<6);       // 禁用红外模式

UCON2 &= ~(3);                   // 清除发送接收的控制配置
UCON2 |= 1<<0;                   // 轮询模式接收
UCON2 &= ~(3<<2);                // 清除发送配置
UCON2 |= 1<<2;                   // 轮询模式发送

UBRDIV2 = 53;                    // 设置波特率的整数部分
UFRACVAL2 = 4;                   // 设置波特率的小数部分
}

// 发送单个字符
void putc(char ch)
{
while(  (UTRSTAT2 & (1<<1)) == 0 ) ;   // 等待直到发送缓冲区为空
UTXH2 = ch;                            // 向 UART2 发送寄存器写入字符
}

// 发送字符串
void puts(char *s)
{
int i = 0;
while (s[i]) {            // 遍历字符串
putc(s[i]);           // 逐字符发送
i++;
}
}

#defineADCCON  *(volatile long*)0x126C0000 // ADC 控制寄存器
#defineADCDAT*(volatile long*)0x126C000C // ADC 数据寄存器,存放采样数据
#defineADCMUX*(volatile long*)0x126C001C // ADC 多路复用器,选择输入通道

// 简单的毫秒级延时函数
void mysleep(int ms)
{
while (ms--) {
int num = 0x1FFF / 2;    // 简单的循环延时
while (num--);
}
}

// 初始化 ADC
void adc_init(void)
{
int reg;
ADCCON |= 1<<16;             // 启用 12 比特分辨率
ADCCON |= 1<<14;             // 启用分频值
ADCCON &= ~(0xFF<<6);        // 清除分频值
ADCCON |= 19<<6;             // 设置分频值为 19
ADCCON &= ~(1<<2);           // 禁用空闲模式
ADCCON |= 1<<1;              // 重新转换
ADCCON |= 1<<0;              // 启动 ADC 转换

ADCMUX = 3;                  // 设置 ADC 输入通道为 3

reg = ADCDAT;                // 读取 ADC 数据寄存器,清除第一次转换的值
}

// 获取 ADC 值并转换为毫伏值
int adc_get_mv(void)
{
int reg = ADCDAT & 0xFFF;    // 获取 12 位有效的 ADC 数据
int mv = reg * 1800 / 4095;  // 将 ADC 值转换为电压(1800 毫伏参考电压)
return mv;
}

// 主函数
void main(void)
{
int mv;
int qian, bai, shi, ge;       // 用于存储电压的千位、百位、十位和个位

uart_init();                  // 初始化 UART
adc_init();                   // 初始化 ADC

while (1) {
mv = adc_get_mv();         // 获取电压值(例如 1658 mV)

qian = mv / 1000;          // 获取千位
putc(qian + '0');          // 通过 UART 发送千位数字

bai = mv / 100 % 10;       // 获取百位
putc(bai + '0');           // 通过 UART 发送百位数字

shi = mv / 10 % 10;        // 获取十位
putc(shi + '0');           // 通过 UART 发送十位数字

ge = mv % 10;              // 获取个位
putc(ge + '0');            // 通过 UART 发送个位数字

putc('m');                 // 发送字符 'm'
putc('v');                 // 发送字符 'v'
putc('\r');                // 发送回车符
putc('\n');                // 发送换行符

mysleep(200);              // 延时 200 毫秒
}

return;
}

其余相关代码
  1. start.S
.global  delay1s                     @ 声明全局符号 delay1s,用于定义延时函数
.text                                @ 代码段的开始
.global _start                       @ 声明全局符号 _start,程序的入口点

_start:
breset                  @ 程序启动时跳转到 reset,执行复位后的初始化操作
ldrpc,_undefined_instruction  @ 设置未定义指令异常的处理地址
ldrpc,_software_interrupt     @ 设置软件中断异常的处理地址
ldrpc,_prefetch_abort         @ 设置预取指令异常的处理地址
ldrpc,_data_abort             @ 设置数据访问异常的处理地址
ldrpc,_not_used               @ 没有使用的异常,保留
ldrpc,_irq                    @ 设置普通中断 (IRQ) 的处理地址
ldrpc,_fiq                    @ 设置快速中断 (FIQ) 的处理地址

_undefined_instruction: .word  _undefined_instruction   @ 定义未定义指令异常处理地址
_software_interrupt:.word  _software_interrupt      @ 定义软件中断异常处理地址
_prefetch_abort:.word  _prefetch_abort           @ 定义预取异常处理地址
_data_abort:.word  _data_abort               @ 定义数据异常处理地址
_not_used:.word  _not_used                 @ 保留,未使用的异常
_irq:.word  _irq                      @ 定义 IRQ 处理地址
_fiq:.word  _fiq                      @ 定义 FIQ 处理地址


reset: 
ldrr0,=0x40008000          @ 将异常向量表的起始地址设置为 0x40008000
mcrp15,0,r0,c12,c0,0       @ 将向量基地址 (VBAR) 设置为 r0(即 0x40008000)

init_stack:
ldrr0,=stacktop           @ 获取栈顶指针的值到 r0 中

/******** SVC 模式的栈 ********/
movsp,r0                 @ 将栈顶指针设置为当前栈指针 (sp)
subr0,#128*4            @ 为 IRQ 模式预留 512 字节的栈空间

/**** IRQ 模式的栈 ****/
msrcpsr,#0xd2           @ 切换到 IRQ 模式
movsp,r0                 @ 设置 IRQ 模式的栈指针
subr0,#128*4            @ 为 FIQ 模式预留 512 字节的栈空间

/*** FIQ 模式的栈 ***/
msr cpsr,#0xd1           @ 切换到 FIQ 模式
movsp,r0                 @ 设置 FIQ 模式的栈指针
subr0,#0                @ FIQ 模式不需要更多的栈空间

/*** Abort 模式的栈 ***/
msrcpsr,#0xd7           @ 切换到 Abort 模式
movsp,r0                 @ 设置 Abort 模式的栈指针
subr0,#0                @ Abort 模式不需要更多的栈空间

/*** Undefined 模式的栈 ***/
msrcpsr,#0xdb           @ 切换到 Undefined 模式
movsp,r0                 @ 设置 Undefined 模式的栈指针
subr0,#0                @ Undefined 模式不需要更多的栈空间

/*** System 和 User 模式的栈 ***/
msrcpsr,#0x10           @ 切换到 System/User 模式
movsp,r0                 @ 设置 System/User 模式的栈指针,预留 1024 字节空间

bmain                 @ 跳转到 main 函数,开始执行主程序

delay1s:
ldr      r4,=0x1ffffff         @ 将延时循环计数器的值装入 r4
delay1s_loop:
sub    r4,r4,#1               @ 计数器减 1
cmp   r4,#0                   @ 比较计数器是否归零
bne    delay1s_loop           @ 如果没有归零,继续循环
mov   pc,lr                   @ 如果计数结束,返回调用处

.align4                         @ 对齐到 4 字节边界,优化存储器访问

/****  SWI 中断处理程序  ****/

.data                              @ 数据段的开始

stack:
.space  4*512                 @ 为所有模式分配 512 字节栈空间
stacktop:                           @ 栈顶符号

.end                               @ 汇编文件的结束

  1. Makefile
all:
arm-none-linux-gnueabi-gcc -fno-builtin -nostdinc -c -o start.o start.S
arm-none-linux-gnueabi-gcc -fno-builtin -nostdinc -c -o main.o main.c
arm-none-linux-gnueabi-ld start.o main.o -Tmap.lds -o main.elf
arm-none-linux-gnueabi-objcopy -O binary  main.elf main.bin
arm-none-linux-gnueabi-objdump -D main.elf > main.dis
clean:
rm -rf *.bak start.o main.o  main.elf main.bin main.dis

  1. map.lds

原文地址:https://blog.csdn.net/m0_51830537/article/details/142849814

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!