⚠️裸机仓库:https://gitee.com/simonchina_carel_li/mini2440-bare-metal.git
⚠️Tag:
19-adc
1. 这次要做什么?
板上有一个可调电阻器,连接在SOC的AIN0通道,

我们要实现采样值的打印,并且要以中断的方式实现
2. 方案分析
2.1 片上外设

-
SOC集成一个10bits ADC
-
8通道多路复用,部分通道是支持线性触摸信号处理的
-
最高工作在 2.5MHz 时钟下,此时转换率可以达到 500 KSPS
转换时间 = 1 / ( x M H z / 5 周期 ) 转换时间 = 1/(xMHz / 5周期) 转换时间=1/(xMHz/5周期)
2.2 核心寄存器
| REG | 说明 |
|---|---|
| ADCCON | 设置预分频器、选择输入通道、启动转换以及查询转换完成状态 |
| ADCDAT0 | 转换完成后的 10位结果会保存在该寄存器的低 10位中 |
2.3 操作思路
-
配置分频相关
-
选择通道
-
启动转换
-
等待转换完成(轮询ADCON[15]或者中断处理函数设置的标志位)
-
打印
-
循环♻️
-
2.4 子中断

ADC转换对应的中断源为INT_ADC,内部包含两个子中断INST_ADC_S和INT_TC,
前者就是我们要关心的ADC中断,
我们实现ADC中断处理函数,还要在其内部实现子中断源的判断分发,并清除子中断挂起
3. 代码实现
新建common/adc.c,实现中断服务函数、初始化、轮询方式接口、中断方式接口,
具体细节、原理见注释,
C
#include "s3c2440a.h"
// 全局变量,用于保存中断中读取的 ADC 结果
volatile int g_adc_val = 0;
volatile int g_adc_done = 0; // 转换完成标志
/**
* @brief ADC 中断处理函数
*/
void irq_int_adc_handler(void)
{
// ADC转换完成
if (SUBSRCPND & (1 << IRQ_SUB_INT_ADC_S)) {
SUBSRCPND = (1 << IRQ_SUB_INT_ADC_S);
// 读取 ADC 转换结果 (保留低 10 位)
g_adc_val = ADCDAT0 & 0x3FF;
// 设置转换完成标志,通知主程序
g_adc_done = 1;
}
// 触摸
else if (SUBSRCPND & (1 << IRQ_SUB_INT_TC)) {
SUBSRCPND = (1 << IRQ_SUB_INT_TC);
// TODO
}
}
/**
* @brief ADC 初始化函数
*/
void adc_init(bool irqEn)
{
// 配置 ADCCON 寄存器
// [14] PRSCEN = 1: 使能预分频器
// [13:6] PRSCVL = 49: 预分频值设为 49 (假设 PCLK = 50.625MHz, 则 ADC clk = 50.625MHz / (49 + 1) = 1.0125MHz)
// [5:3] SEL_MUX = 0: 默认选择通道 AIN0
// [2] STDBM = 0: 正常工作模式
// [1] READ_START = 0: 禁用读启动模式
// [0] ENABLE_START = 0: 不启动转换
ADCCON = (1 << 14) | (49 << 6) | (0 << 3);
if (!irqEn)
return ;
irq_src_enable(IRQ_INT_ADC, IRQ_SUB_INT_ADC_S, true);
}
/**
* @brief 读取指定通道的 ADC 转换值
* @param channel 要读取的通道号 (0 ~ 7)
* @return 10位的 ADC 转换结果
*/
int adc_read(int channel)
{
// 选择转换通道 (先清除原本的通道设置,再设置新通道)
ADCCON = (ADCCON & ~(7 << 3)) | (channel << 3);
// 启动 ADC 转换
ADCCON |= (1 << 0);
// 等待 ADC 转换完成 (ADCCON 的第15位为1时表示完成)
while (!(ADCCON & (1 << 15)));
// 读取并返回ADC转换数据 (ADCDAT0的低10位有效)
return (ADCDAT0 & 0x3FF);
}
/**
* @brief 启动一次 ADC 转换(中断方式)
*/
void adc_start(int channel)
{
// 标志位清零
g_adc_done = 0;
// 选择转换通道 (先清除原本的通道设置,再设置新通道)
ADCCON = (ADCCON & ~(7 << 3)) | (channel << 3);
// 启动 ADC 转换
ADCCON |= (1 << 0);
}
/**
* @brief 检查 ADC 转换是否完成并获取结果
* @param out 指向存储 ADC 转换结果的变量
* @return true 如果有新的 ADC 转换结果
* @return false 如果没有新的 ADC 转换结果
*/
bool adc_poll(int *out)
{
if (g_adc_done)
{
*out = g_adc_val;
g_adc_done = 0;
return true;
}
return false;
}
可以看到,中断使能函数变成了
C
void irq_src_enable(irq_src_t src, irq_subsrc_t subsrc, bool enable)
增加了子中断的参数项,
对应的实现修改,
C
/// @brief 中断源使能控制
void irq_src_enable(irq_src_t src, irq_subsrc_t subsrc, bool enable)
{
unsigned long _save = disable_irq_save();
if (subsrc != IRQ_SUB_INT_NONE)
SUBSRCPND = (1 << subsrc);
SRCPND = (1 << src);
INTPND = (1 << src);
if (enable) {
INTMSK &= ~(1 << src);
if (subsrc != IRQ_SUB_INT_NONE)
INTSUBMSK &= ~(1 << subsrc);
} else {
INTMSK |= (1 << src);
if (subsrc != IRQ_SUB_INT_NONE)
INTSUBMSK |= (1 << subsrc);
}
restore_irq_mask(_save);
}
宏定义也增加IRQ_SUB_INT_NONE枚举,以兼容没有子中断的中断源,
C
///@enum 中断子源寄存器位定义 (SUBSRCPND)
typedef enum {
IRQ_SUB_INT_NONE = 0xFF,
IRQ_SUB_INT_RXD0 = 0, // UART0 接收中断
IRQ_SUB_INT_TXD0 = 1, // UART0 发送中断
IRQ_SUB_INT_ERR0 = 2, // UART0 错误中断
所以,还要将之前诸如,
C
rq_src_enable(IRQ_INT_TIMER4, true);
改成,
C
rq_src_enable(IRQ_INT_TIMER4, IRQ_SUB_INT_NONE, true);
另外实现测试代码,
新建adc/main.c,
C
#include "s3c2440a.h"
#include <stdio.h>
int main(void)
{
int adc_val;
// --- 轮询方式 ---
#if 0
// 初始化 ADC 外设
adc_init(false);
while (1) {
easy_delay_ms(100);
// 循环读取通道0的ADC值
adc_val = adc_read(0);
// 将 adc_val 通过UART串口打印输出
printf("ADC Channel 0 Value: %d\r\n", adc_val);
}
#else // --- 中断方式 ---
adc_init(true);
while (1) {
adc_start(0);
if (adc_poll(&adc_val)) {
printf("ADC Channel 0 Value: %d\r\n", adc_val);
}
}
#endif
return 0;
}
我们支持两种方式使用ADC,软件轮询和中断循环
4. 运行
make adc, 烧录、运行,
扭动变阻器,可以看到采样值在0 ~ 1023变化,
