ARM 下的 ADC(模数转换器)
ADC(Analog to Digital Converter,模数转换器)是嵌入式系统中非常重要的一个模块,负责将连续变化的模拟信号(如温度、电压、电流等)转换为数字信号,供 CPU 和程序进行处理。
ADC 基本概念
模拟量(Analog Quantity):模拟量是连续变化的物理量,比如温度、湿度、光照强度、电压等。它们的变化是连续的,无法直接被数字处理器理解。 数字量(Digital Quantity):数字量是离散的、不连续的。数字处理器只能处理数字量,比如 1 和 0 的组合。 转换:ADC 模块负责将模拟信号(比如电压信号)转换为相应的数字量,使处理器能够读取和处理。 工作原理
在 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
B L = V re f V x × 4095
BL 是 ADC 转换得到的数字值,12 位 ADC 的最大值是 4095(即 2^12 - 1) Vx 是输入的电压值 Vref 是参考电压,通常为 3.3V 或 5V
ARM下的ADC特点
分辨率:ADC 的分辨率决定了它能将模拟量细分为多少个数字值。例如,12 位分辨率的 ADC 能将输入电压分成 4096 个离散的数字量(即 0 - 4095) 采样速度:这是 ADC 每秒能够执行的转换次数,通常以 Samples Per Second(SPS,样本/秒)为单位 参考电压(Vref):这是 ADC 转换时的参考电压,通常由外部或者内部的电压源提 ADC编程步骤
配置 GPIO 引脚为模拟输入模式: 在一些 ARM Cortex-M 系列的 MCU 中,ADC 通道与特定的 GPIO 引脚绑定,在使用 ADC 之前需要将 GPIO 配置为模拟模式。 配置 ADC 模块:
配置分辨率和采样速度(如 12 位,1 MSPS)。 设置参考电压(Vref)。 启动转换: 启动 ADC 转换,等待 ADC 模块完成采样并将结果存储在数据寄存器中。 读取 ADC 数据寄存器: 一旦转换完成,ADC 数据寄存器将存储转换后的数字值,可以通过访问寄存器读取这个值。 ARM 典型寄存器配置(伪代码)
void ADC_Init ( void )
{
GPIO_PinConfig ( GPIOA, PIN1, GPIO_MODE_ANALOG) ;
ADC1-> CR1 = 0 ;
ADC1-> SQR1 = 0 ;
ADC1-> SMPR2 |= ADC_SMPR2_SMP10;
ADC1-> CR2 |= ADC_CR2_ADON;
}
uint16_t ADC_Read ( void )
{
ADC1-> CR2 |= ADC_CR2_SWSTART;
while ( ! ( ADC1-> SR & ADC_SR_EOC) ) ;
return ADC1-> DR;
}
ARM 下的 ADC 实际应用
温度测量:读取热敏电阻输出的模拟电压值,将其转换为数字值,再根据转换结果计算温度。 电压监测:通过电压分压电路,监测电池或电源的电压值,进而判断电源状态。 光强度检测:读取光电二极管或光敏电阻的模拟信号,转换为数字量后判断环境光照强度。
ARM的ADC编程实例(main.c)
第一步:UART初始化 目的:初始化串口通信模块,使 CPU 可以通过 UART 向外部设备发送字符数据(例如,输出 ADC 转换得到的电压值
# define GPA1CON * ( volatile long * ) 0x11400020
# define ULCON2 * ( volatile long * ) 0x13820000
# define UCON2 * ( volatile long * ) 0x13820004
# define UTRSTAT2 * ( volatile long * ) 0x13820010
# define UTXH2 * ( volatile long * ) 0x13820020
# define UBRDIV2 * ( volatile long * ) 0x13820028
# define UFRACVAL2 * ( volatile long * ) 0x1382002C
void uart_init ( void )
{
GPA1CON = GPA1CON & ~ 0xF ;
GPA1CON = GPA1CON | ( 1 << 1 ) ;
GPA1CON = GPA1CON & ~ ( 0xF << 4 ) ;
GPA1CON = GPA1CON | ( 1 << 5 ) ;
ULCON2 = ULCON2 | 0x3 ;
ULCON2 = ULCON2 & ~ ( 1 << 2 ) ;
UCON2 &= ~ ( 3 ) ;
UCON2 |= 1 << 0 ;
UCON2 &= ~ ( 3 << 2 ) ;
UCON2 |= 1 << 2 ;
UBRDIV2 = 53 ;
UFRACVAL2 = 4 ;
}
第二步:ADC初始化 目的:初始化 ADC,使 CPU 能够从指定的 ADC 通道读取模拟电压值,并转换为数字信号
# define ADCCON * ( volatile long * ) 0x126C0000
# define ADCDAT * ( volatile long * ) 0x126C000C
# define ADCMUX * ( volatile long * ) 0x126C001C
void adc_init ( void )
{
ADCCON |= 1 << 16 ;
ADCCON |= 1 << 14 ;
ADCCON &= ~ ( 0xFF << 6 ) ;
ADCCON |= 19 << 6 ;
ADCCON &= ~ ( 1 << 2 ) ;
ADCCON |= 1 << 1 ;
ADCCON |= 1 << 0 ;
ADCMUX = 3 ;
int reg = ADCDAT;
}
第三步:获取 ADC 数据并将其转换为电压值 目的:通过 ADC 读取输入电压并转换为数字值(mV),用于后续的显示。
int adc_get_mv ( void )
{
int reg = ADCDAT & 0xFFF ;
int mv = reg * 1800 / 4095 ;
return mv;
}
第四步:通过 UART 输出电压值 目的:将 ADC 读取的电压值以字符形式通过 UART 发送,显示到外部设备。
void main ( void )
{
int mv;
int qian, bai, shi, ge;
uart_init ( ) ;
adc_init ( ) ;
while ( 1 ) {
mv = adc_get_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' ) ;
putc ( 'm' ) ;
putc ( 'v' ) ;
putc ( '\r' ) ;
putc ( '\n' ) ;
mysleep ( 200 ) ;
}
}
第五步:将电压值拆分成千、百、十、个位并输出 目的:将电压值(以毫伏为单位)拆分成千位、百位、十位和个位,然后逐位通过 UART 输出字符形式的数值,最后附加上 “mv” 单位。
void main ( void )
{
int mv;
int qian, bai, shi, ge;
uart_init ( ) ;
adc_init ( ) ;
while ( 1 ) {
mv = adc_get_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' ) ;
putc ( 'm' ) ;
putc ( 'v' ) ;
putc ( '\r' ) ;
putc ( '\n' ) ;
mysleep ( 200 ) ;
}
}
第六步:延时函数 mysleep 目的:通过简单的循环实现延时功能,避免程序过快地进行下一轮 ADC 读取与数据输出
void mysleep ( int ms)
{
while ( ms-- ) {
int num = 0x1FFF / 2 ;
while ( num-- ) ;
}
}
综合示例
# define GPA1CON * ( volatile long * ) 0x11400020
# define ULCON2 * ( volatile long * ) 0x13820000
# define UCON2 * ( volatile long * ) 0x13820004
# define UTRSTAT2 * ( volatile long * ) 0x13820010
# define UTXH2 * ( volatile long * ) 0x13820020
# define URXH2 * ( volatile long * ) 0x13820024
# define UBRDIV2 * ( volatile long * ) 0x13820028
# define UFRACVAL2 * ( volatile long * ) 0x1382002C
void uart_init ( void )
{
GPA1CON = GPA1CON & ~ 0xF ;
GPA1CON = GPA1CON | ( 1 << 1 ) ;
GPA1CON = GPA1CON & ~ ( 0xF << 4 ) ;
GPA1CON = GPA1CON | ( 1 << 5 ) ;
ULCON2 = ULCON2 | 0x3 ;
ULCON2 = ULCON2 & ~ ( 1 << 2 ) ;
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;
}
void puts ( char * s)
{
int i = 0 ;
while ( s[ i] ) {
putc ( s[ i] ) ;
i++ ;
}
}
# define ADCCON * ( volatile long * ) 0x126C0000
# define ADCDAT * ( volatile long * ) 0x126C000C
# define ADCMUX * ( volatile long * ) 0x126C001C
void mysleep ( int ms)
{
while ( ms-- ) {
int num = 0x1FFF / 2 ;
while ( num-- ) ;
}
}
void adc_init ( void )
{
int reg;
ADCCON |= 1 << 16 ;
ADCCON |= 1 << 14 ;
ADCCON &= ~ ( 0xFF << 6 ) ;
ADCCON |= 19 << 6 ;
ADCCON &= ~ ( 1 << 2 ) ;
ADCCON |= 1 << 1 ;
ADCCON |= 1 << 0 ;
ADCMUX = 3 ;
reg = ADCDAT;
}
int adc_get_mv ( void )
{
int reg = ADCDAT & 0xFFF ;
int mv = reg * 1800 / 4095 ;
return mv;
}
void main ( void )
{
int mv;
int qian, bai, shi, ge;
uart_init ( ) ;
adc_init ( ) ;
while ( 1 ) {
mv = adc_get_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' ) ;
putc ( 'm' ) ;
putc ( 'v' ) ;
putc ( '\r' ) ;
putc ( '\n' ) ;
mysleep ( 200 ) ;
}
return ;
}
其余相关代码
start.S
. global delay1s @ 声明全局符号 delay1s,用于定义延时函数
. text @ 代码段的开始
. global _start @ 声明全局符号 _start,程序的入口点
_start:
b reset @ 程序启动时跳转到 reset,执行复位后的初始化操作
ldr pc, _undefined_instruction @ 设置未定义指令异常的处理地址
ldr pc, _software_interrupt @ 设置软件中断异常的处理地址
ldr pc, _prefetch_abort @ 设置预取指令异常的处理地址
ldr pc, _data_abort @ 设置数据访问异常的处理地址
ldr pc, _not_used @ 没有使用的异常,保留
ldr pc, _irq @ 设置普通中断 ( IRQ) 的处理地址
ldr pc, _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:
ldr r0, = 0x40008000 @ 将异常向量表的起始地址设置为 0x40008000
mcr p15, 0 , r0, c12, c0, 0 @ 将向量基地址 ( VBAR) 设置为 r0(即 0x40008000 )
init_stack:
ldr r0, = stacktop @ 获取栈顶指针的值到 r0 中
mov sp, r0 @ 将栈顶指针设置为当前栈指针 ( sp)
sub r0, #128 * 4 @ 为 IRQ 模式预留 512 字节的栈空间
msr cpsr, #0xd2 @ 切换到 IRQ 模式
mov sp, r0 @ 设置 IRQ 模式的栈指针
sub r0, #128 * 4 @ 为 FIQ 模式预留 512 字节的栈空间
msr cpsr, #0xd1 @ 切换到 FIQ 模式
mov sp, r0 @ 设置 FIQ 模式的栈指针
sub r0, #0 @ FIQ 模式不需要更多的栈空间
msr cpsr, #0xd7 @ 切换到 Abort 模式
mov sp, r0 @ 设置 Abort 模式的栈指针
sub r0, #0 @ Abort 模式不需要更多的栈空间
msr cpsr, #0xdb @ 切换到 Undefined 模式
mov sp, r0 @ 设置 Undefined 模式的栈指针
sub r0, #0 @ Undefined 模式不需要更多的栈空间
msr cpsr, #0x10 @ 切换到 System/ User 模式
mov sp, r0 @ 设置 System/ User 模式的栈指针,预留 1024 字节空间
b main @ 跳转到 main 函数,开始执行主程序
delay1s:
ldr r4, = 0x1ffffff @ 将延时循环计数器的值装入 r4
delay1s_loop:
sub r4, r4, #1 @ 计数器减 1
cmp r4, #0 @ 比较计数器是否归零
bne delay1s_loop @ 如果没有归零,继续循环
mov pc, lr @ 如果计数结束,返回调用处
. align 4 @ 对齐到 4 字节边界,优化存储器访问
. data @ 数据段的开始
stack:
. space 4 * 512 @ 为所有模式分配 512 字节栈空间
stacktop: @ 栈顶符号
. end @ 汇编文件的结束
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
map.lds