【凌鸥LKS32】基础篇(七)·软件触发ADC采集电位器电压

目录
1. 简介
ADC全称是(Analog-to-Digital Converter)模拟-数字转换器,一般我们把模拟信号(Analog signal) 用A来进行简写,数字信号(digital signal) 用D来表示。主要用于将连续传输的模拟信号转换为数字信号,便于数字系统(如中央处理器CPU、微控制器MCU等)对传输信息进行快速处理和分析。
模拟信号是指用连续变化的物理量所表达的信息,如温度、湿度、压力、电压、电流等。ADC模块所采集的模拟信号是连续变化的电压或电流信号,其数值在一定范围内连续变化,如下所示:

在单片机的使用中,我们可以通过ADC的转换将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。
ADC采样的实现方式主要有两种:外接采样芯片和控制核心内部的采样模块。以前的一些芯片是外挂ADC芯片,不过现在许多MCU和DSP内部集成了ADC模块。
对于LKS32MC07x 系列芯片,其内部集成了 2 个 12bit SAR-ADC 转换核心,14 个模拟 IO 输入信号、4 个 OPA输出、2 个 DAC 输出、温度传感器输出、内部电源 AVDD 均可作为 ADC 的被采样信号。
不过我们还是来了解一下外挂芯片的原理,方便理解。
2. 逐次逼近型ADC(ADC0809)
我们先来了解一下ADC0809的工作原理。
首先是通道选择开关,通过通道选择开关,选择(IN0~IN7)其中的一路,输入到这个点进行转换,对于0809只有8路选:

然后是地址锁存和译码,这里你想要选择哪一路,需要将通道号放到(ADDA、ADDB、ADDC)这三个脚上,然后通过ALE给出锁存信号,对应的通道选择开关就会自动拨好了:

比较器是一个电压比较器,他可以判断两个输入信号电压的大小关系,其中通道选择开关输入的是待测的电压,另一端是DAC(数模转换器)的电压:

这里待测电压是未知的,但是DAC所出的电压是我们通过数模转换来的,其编码是已知的,我们通过比较器进行比较,当待测电压大于DAC所示电压,我们就调大DAC的值,如果待测电压小于DAC所示电压,我们就调小DAC的值,直到待测电压等于DAC所示电压,这样DAC的输入数据就是待测电压的数据,我们就可以知道待测电压的编码(类似于二分法)。
得出待测电压的编码通过三态锁存缓冲器进行输出,8位就有8根线,12位就有12根:


3. 功能框图(LKS32)
每个 ADC 接口包括 16 个数据寄存器(ADC 16 次采样各个通道模拟量对应的数字量),以及若干控制寄存器,14 个常规触发采样寄存器,2 个空闲采样触发寄存器。可以同一时刻进行 2 个通道的采样:

拆分了解一下,首先是输入部分,其中最左侧代表多路输入选择器,其中:
- OPA0/1/2/3:4 路片内运算放大器输出
- ADC Channels:14 路外部模拟 IO 通道
- Temperature Sensor:芯片内置温度传感器
- AVDD:模拟电源内部采样
S/H代表:Sample/Hold,采样/保持。在采集阶段对外部电压采样电容充电;ADC 转换阶段保持电压稳定,保证转换期间输入电压不变。
SAR‑ADC 内核:12 位逐次逼近 ADC,把模拟电压转换成数字量输出。

ADC 的启动触发来源:
- Hardware trigger 硬件触发:来自定时器 UTimer/MCPWM 输出 T0/T1/T2/T3。定时器事件自动启动 ADC 转换,常用于电机控制,PWM 中点同步采样,不需要 CPU 软件干预。
- Software trigger 软件触发:寄存器写位,软件手动启动一次 ADC 转换。

Sampling Control logic 采样控制状态机:
- Sample State Control:采样状态机,管理采样、保持、转换时序
- Trigger counter:触发计数器,统计触发事件
- Channel Select:通道选择,输出信号给左侧模拟多路开关,选哪一路模拟输入

对于最上面几个操作主要为了稳定最终采集数据:
- -16'h8000(0):偏移补偿
- -ADC_DC:直流偏移校准,修正 ADC 零点误差
- ADC_AMC:增益校准,修正 ADC 增益误差
- 乘法器、饱和溢出处理:处理采样结果溢出 / 下溢,防止数值越界。
经过偏移、增益校准之后才输出最终采样数字值。
最右侧部分,是数据寄存器组:
- ADC_IDAT0~IDAT1:中间数据缓存
- ADC_DAT0 ~ ADC_DAT13 共 14 个(加上 IDAT 合计 16 个数据寄存器),对应知识点:每个 ADC 有 16 个数据寄存器保存多次采样结果。
- ADC_IF:转换完成中断标志位。
左侧是存放全部 ADC 配置寄存器:
- ADC_CHN0 ...:通道号配置寄存器,选择要采样的模拟通道
- ADC_TRIG:触发源配置(选择软件触发 / 哪个定时器硬件触发)
- ADC_CFG:ADC 功能配置,采样时间、使能、模式等
- ADC_CHNT:通道计数,配置一轮要采集多少个通道
- ADC_SWT:软件触发寄存器,写该寄存器软件启动 ADC
- AMC/DC:增益、直流偏移校准参数寄存器。

右侧是中断产生逻辑,转换完成后,若中断使能打开,就向 NVIC 申请中断,通知 CPU 读取采样结果。
- ADC_IE[n]:中断使能位;ADC_IF[n]:中断标志位
- 与门逻辑:标志位 & 使能位 → 输出IRQ中断信号送给 NVIC。
完整的工作流程:
- CPU 通过总线配置adc_reg寄存器:设置采样通道列表、选择触发源(定时器硬件触发 / 软件触发)、开启中断。
- 触发事件到来:
- 硬件:UTimer/MCPWM 定时器事件;或者
- 软件:写ADC_SWT软件触发位。
- Sampling Control logic状态机工作,输出Channel Select,选通一路模拟输入;控制 S/H 采样保持电路。
- SAR‑ADC 完成模数转换;数字结果做 DC 偏移、AMC 增益校准、饱和处理。
- 转换结果存入ADC_DATx数据寄存器。置ADC_IF中断标志。
- 如果中断使能位打开,产生 IRQ 信号给到 NVIC,进入 ADC 中断服务函数。
- CPU/DMA 通过 BUS 读取ADC_DATx拿到采样值;软件写对应标志位,清除中断标志。
4. ADC基本结构
4.1 量词含义约定
1 次采样:完成对应的一次模拟信号量到数字信号量的采样转换并存储至 ADC_DATx 寄存器;
1 段采样:可能包含 1 次或若干次采样,若干次采样可以是相同的模拟信号,也可以是不同的模拟信号。采样开始通常由MCPWM、TIMER 或软件进行触发,一个触发信号完成一段采样,采样完成后产生相应的段采样完成中断。
| 名词 | 定义 | 关键要点 |
|---|---|---|
| 次采样 | 一次模拟→数字转换,结果存入 1 个ADC_DATx | ADC 最小执行单元;N 次采样结果存ADC_DAT[N] |
| 段采样 | 1 个触发信号启动的一组采样任务 | 一段内可 1 次 / 多次次采样;可同通道重复采样;整段全部次采样做完才产生段完成中断 |
4.2 触发方式
ADC 触发分为常规触发(NT: Normal Trigger)和空闲触发(IT: Idle Trigger)两种触发。常规触发比空闲触发有更高优先级。
- 如果 ADC 正在进行常规采样转换,此时发生了空闲触发,则 ADC 会继续完成常规触发的通道采样转换,完成后再进行空闲触发采样转换;
- 如果 ADC 正在进行空闲采样转换,此时发生了常规触发,则常规触发会打断空闲采样转换,等常规触发采样完成后再进行空闲触发。
NT 常规触发 Normal Trigger 高优先级,用于业务必需采样,电机电流、传感器信号等实时任务。 对应硬件,14 个常规触发采样寄存器,由 MCPWM/UTimer 硬件触发或者软件触发。
IT 空闲触发 Idle Trigger 低优先级,后台非紧急采样,芯片温度、AVDD 电源电压等,不要求严格实时。 对应硬件,2 个空闲采样触发寄存器;一般在系统空闲的时候执行。
| 触发类型 | 优先级 | 寄存器资源 | 行为特性 | 适用场景 |
|---|---|---|---|---|
| NT 常规触发 | 高 | 14 个常规采样寄存器 | 可抢占空闲触发;空闲采样运行时,NT 到来立刻打断空闲,先执行 NT,之后恢复空闲;NT 运行时 IT 到来,IT 排队等待 | 电机电流、实时传感器 |
| IT 空闲触发 | 低 | 2 个空闲采样寄存器 | 不能抢占常规触发;仅系统空闲时执行 | 后台监测温度、AVDD 电压 |
触发方式:
- 支持单段触发和两段触发
- 可以设置采样触发事件发生次数从而实现间隔触发
- 可以软件触发
- 每段采样触发完成可产生对应中断标志,如果使能中断请求,则会向 NVIC 产生中断服务请求
- 触发指示信号可以通过 GPIO 送出用于调试
- 支持空闲触发采样
其手册配置寄存器如下:

对于常规触发部分我们细化一下:
软件触发:软件触发,向 ADC_SWT 写入 0x5AA5 可以产生一次软件触发信号。
UTIME 触发:硬件触发,ADC 的触发来源为 UTimer 的比较事件,ADC 四段触发采样事件对应 UTimer_T0/UTimer_T1/UTimer_T2/UTimer_T3;其依次对应 Timer2 通道 0/1、Timer3 通道 0/1 的比较事件。
MCPWM 触发:硬件触发,MCPWM 可以提供 ADC 采样控制。当计数器计数到 MCPWM_TMR0/MCPWM_TMR1/MCPWM_TMR2/MCPWM_TMR3 时,可产生定时事件 MCPWM_T0/MCPWM_T1/MCPWM_T2/MCPWM_T3,触发 ADC 采样。该触发信号可通过 ADC_TRIGGER 输出到 GPIO,便于调试之用。ADC 触发信号在芯片内部为一个 ADC 时钟周期的窄脉冲。每发生一次 ADC 触发,ADC_TRIGGER 信号翻转一次,以便于输出捕捉。
| 触发源 | 触发方式 | 关键说明 |
|---|---|---|
| 软件触发 | 写寄存器ADC_SWT = 0x5AA5 | 写魔法字,产生 1 次触发,启动一段常规采样 |
| UTIMER 硬件触发 | 定时器比较事件 | UTimer_T0~T3 依次对应:Timer2_CH0、Timer2_CH1、Timer3_CH0、Timer3_CH1 比较事件 |
| MCPWM 硬件触发 | PWM 计到 MCPWM_TMR0‑3 产生事件 | 1 个 ADC 时钟窄脉冲;可输出 GPIOADC_TRIGGER调试;每触发一次引脚电平翻转 |
需要注意,ADC 无论被配置为几段转换模式,每一段都需要一个触发事件才能进行开始采样转换,否则 ADC 处于等待状态。单段触发模式可以是一次硬件事件即触发,也可以是达到一定次数的硬件事件才触发采样,4 个 MCPWM/UTimer 触发事件均可触发或参与计数;但两段触发和四段触发,4 个 MCPWM/UTimer 触发事件只能按顺序触发 ADC 采样。
| 工作模式 | 触发规则说明 |
|---|---|
| 单段模式 | 一次硬件事件直接触发;支持累计 N 次硬件事件后才触发;4 路 MCPWM/UTimer 均可参与计数 |
| 两段模式 | 硬件触发必须严格按顺序触发各段,不允许乱序;每一段需要收到对应触发才开始采样,无触发则等待 |
| 四段模式 | 硬件触发必须严格按顺序 T0→T1→T2→T3;来一次触发只执行对应一段,不会自动跑完全部 4 段;无触发 ADC 处于等待 |
无论几段模式,每一段都必须收到对应触发事件,才会开始采样转换,否则 ADC 等待。
4.3 ADC 通道选择
每个 ADC 模块有 5 个通道信号来源寄存器,控制采样序列 0~15 的信号选择。
| 寄存器 | 管控的次采样序号 | 归属类型 |
|---|---|---|
| ADC_CHN0 | 第 0~3 次采样 | 常规 NT |
| ADC_CHN1 | 第 4~7 次采样 | 常规 NT |
| ADC_CHN2 | 第 8~11 次采样 | 常规 NT |
| ADC_CHN3 | 第 12~13 次采样 | 常规 NT |
| ADC_ICHN | 空闲段第 0~1 次采样 | 空闲 IT |
每次采样通道值选择范围都是 0~15,对应通道 0~15, 也就是可以对某一个通道进行多次采样。每一个采样对应一个结果寄存器,转换结束后,可以直接到对应结果寄存器中获取到 ADC 采样结果。

4.4 中断配置
ADC 一共有七种中断:
| 序号 | 中断名称 | 含义说明 |
|---|---|---|
| 1 | 第一段采样完成中断 | 第 1 段采样全部次采样执行完毕,段完成中断 |
| 2 | 第二段采样完成中断 | 第 2 段采样全部次采样执行完毕,段完成中断 |
| 3 | 第三段采样完成中断 | 第 3 段采样全部次采样执行完毕,段完成中断 |
| 4 | 第四段采样完成中断 | 第 4 段采样全部次采样执行完毕,段完成中断 |
| 5 | 软件触发发生在非空闲状态中断 | ADC正在工作(非空闲)时,又来了软件触发;此时触发无法执行,产生报错中断 |
| 6 | 硬件触发发生在非空闲状态中断 | ADC正在工作(非空闲)时,又来了硬件触发;触发无法执行,产生报错中断 |
| 7 | ADC0_DAT0 超阈值中断 | ADC0 的 DAT0 采样结果高于 / 低于设定阈值,触发告警中断(监控模拟信号) |
其寄存器配置如下:


4.5 ADC 输出数制
ADC 输出数据为 12bit 补码,输入信号 0 对应 12’b0000_0000_0000。 ADC 有两种量程设置 3.6V 和 7.2V。 以 3.6V 量程为例:
| 输入电压 | 12bit 补码 | 十进制 |
|---|---|---|
| ‑3.6V | 12'b1000_0000_0000 | ‑2048 |
| 0V | 12'b0000_0000_0000 | 0 |
| +3.6V | 12'b0111_1111_1111 | +2047 |
ADC 转换后的 12bit 补码需要符号扩展存入 16bit 的ADC_DAT寄存器;支持左对齐、右对齐配置。
左对齐:把 12bit 数据向 16bit 寄存器高位靠齐,低位补 0,这样符号位在 16bit 的最高 bit,做数值运算时,直接对 16 位数右移 4 位,即可还原原始 12bit 补码;
12bit: 1000 0000 1101
左对齐: 1000 0000 1101 0000 (低4位补0)
右对齐:2bit 向 16bit 寄存器低位靠齐,高位填充符号位(符号扩展),
- 如果原 12bit 最高位是 1(负数),所以高 4 位全部填 1;
- 如果原始是正数,符号位 0,则高 4 位全部填 0。
12bit: 1000 0000 1101
右对齐: 1111 1000 0000 1101 (高4位复制符号位1)
4.6 ADC 量程
ADC 在 2.4V 的默认基准电压配置下,有两种量程设置:3.6V 和 7.2V。
| ADC 量程 | 基准电压 | 可承受输入幅度 | 适用场景 |
|---|---|---|---|
| 3.6V | 2.4V | ±3.6V | 运放 OPA 输出信号(电机电流采样,推荐) |
| 7.2V | 2.4V | ±5V | 外部大幅度差分信号输入 |
注意:7.2V 量程虽然叫 7.2V 量程,但芯片实际允许最大输入是±5V,不要输入超过 ±5V,损坏芯片。
4.7 ADC 阈值监测
ADC 阈值监测,又叫做模拟看门狗,硬件实时监控 ADC 采样结果;不需要 CPU 不停轮询读取寄存器,一旦采样值超出设定上下边界,硬件直接置超限中断标志。
每个 ADC 模块拥有1 组阈值监测电路,同时配置:
- 上阈值(高门限,12bit 有符号补码)
- 下阈值(低门限,12bit 有符号补码)
阈值本身是 12 位有符号补码,和 ADC 原始转换数据位宽一致。
比较逻辑不受左 / 右对齐影响,无论配置左对齐还是右对齐,硬件自动提取有效的 12bit 采样数值做对比,软件不用因为对齐方式修改阈值参数。
阈值是 12bit 有符号数:
- 左对齐模式:ADCx_DATx[15:4](高 12 位,真正的 12bit 采样补码)拿出来和阈值比较,忽略低 4 位填充 0;
- 右对齐模式:ADCx_DATx[11:0](低 12 位,符号扩展后的有效采样数据)拿出来和阈值比较;
比较触发时机:ADC 完成一次次采样,经过增益、偏移校正,结果写入ADCx_DATx寄存器的瞬间,硬件自动做阈值比较:
- 采样结果 > 上阈值 → 置阈值超限中断标志
- 采样结果 < 下阈值 → 置阈值超限中断标志
- 结果介于上下阈值之间:不动作。

5. 库函数
都有注释应该还是很容易理解的。
5.1 ADC_TypeDef
这里主要是寄存器地址映射结构体,编译器会根据结构体成员顺序、偏移对应芯片实际寄存器地址,ADC0/ADC1是外设基地址强制转换的结构体指针:
typedef struct
{
__IO uint32_t DAT0; /**< 偏移地址: 0x00 ADCx 第 0 次常规采样数据*/
__IO uint32_t DAT1; /**< 偏移地址: 0x04 ADCx 第 1 次常规采样数据*/
__IO uint32_t DAT2; /**< 偏移地址: 0x08 ADCx 第 2 次常规采样数据*/
__IO uint32_t DAT3; /**< 偏移地址: 0x0C ADCx 第 3 次常规采样数据*/
/**< */
__IO uint32_t DAT4; /**< 偏移地址: 0x10 ADCx 第 4 次常规采样数据*/
__IO uint32_t DAT5; /**< 偏移地址: 0x14 ADCx 第 5 次常规采样数据*/
__IO uint32_t DAT6; /**< 偏移地址: 0x18 ADCx 第 6 次常规采样数据*/
__IO uint32_t DAT7; /**< 偏移地址: 0x1C ADCx 第 7 次常规采样数据*/
/**< */
__IO uint32_t DAT8; /**< 偏移地址: 0x20 ADCx 第 8 次常规采样数据*/
__IO uint32_t DAT9; /**< 偏移地址: 0x24 ADCx 第 9 次常规采样数据*/
__IO uint32_t DAT10; /**< 偏移地址: 0x28 ADCx 第 10 次常规采样数据 */
__IO uint32_t DAT11; /**< 偏移地址: 0x2C ADCx 第 11 次常规采样数据 */
__IO uint32_t DAT12; /**< 偏移地址: 0x30 ADCx 第 12 次常规采样数据*/
__IO uint32_t DAT13; /**< 偏移地址: 0x34 ADCx 第 13 次常规采样数据 */
__IO uint32_t IDAT0; /**< 偏移地址: 0x38 ADCx 第 0 次空闲采样数据*/
__IO uint32_t IDAT1; /**< 偏移地址: 0x3C ADCx 第 1 次空闲采样数据*/
__IO uint32_t ICHN; /**< 偏移地址: 0x40 ADCx 空闲采样通道配置*/
__IO uint32_t null; /**< 偏移地址: 0x44 地址对齐,无意义*/
__IO uint32_t null1; /**< 偏移地址: 0x48 地址对齐,无意义*/
__IO uint32_t null2; /**< 偏移地址: 0x4C 地址对齐,无意义*/
__IO uint32_t CHN0; /**< 偏移地址: 0x50 ADCx 第 0~ 3 次常规采样输入信号选择 Signal 0 Signal 0 Signal 0 Signal 0 Signal 0 Signal 0*/
__IO uint32_t CHN1; /**< 偏移地址: 0x54 ADCx 第 4~ 7 次常规采样输入信号选择*/
__IO uint32_t CHN2; /**< 偏移地址: 0x58 ADCx 第 8~11 次常规采样输入信号选择*/
__IO uint32_t CHN3; /**< 偏移地址: 0x5C ADCx 第 12~15 次常规采样输入信号选择*/
__IO uint32_t CHNT; /**< 偏移地址: 0x60 ADCx 各种触发模式下采样次数*/
__IO uint32_t GAIN; /**< 偏移地址: 0x64 ADCx 增益选择*/
__IO uint32_t null3; /**< 偏移地址: 0x68 地址对齐,无意义*/
__IO uint32_t null4; /**< 偏移地址: 0x6C 地址对齐,无意义*/
__IO uint32_t null5; /**< 偏移地址: 0x70 地址对齐,无意义*/
__IO uint32_t CFG; /**< 偏移地址: 0x74 ADCx 模式配置*/
__IO uint32_t TRIG; /**< 偏移地址: 0x78 ADCx 采样触发配置*/
__IO uint32_t SWT; /**< 偏移地址: 0x7C ADCx 软件触发*/
__IO uint32_t DC0; /**< 偏移地址: 0x80 ADCx 增益为 0 时 DC offset*/
__IO uint32_t AMC0; /**< 偏移地址: 0x84 ADCx 增益为 0 时增益校正系数*/
__IO uint32_t DC1; /**< 偏移地址: 0x88 ADCx 增益为 1 时 DC offset*/
__IO uint32_t AMC1; /**< 偏移地址: 0x8C ADCx 增益为 1 时增益校正系数*/
__IO uint32_t IE; /**< 偏移地址: 0x90 ADCx 中断使能*/
__IO uint32_t IF; /**< 偏移地址: 0x94 ADCx 中断标志*/
__IO uint32_t null6; /**< 偏移地址: 0x98 地址对齐,无意义*/
__IO uint32_t null7; /**< 偏移地址: 0x9C 地址对齐,无意义*/
__IO uint32_t null8; /**< 偏移地址: 0xA0 地址对齐,无意义*/
__IO uint32_t null9; /**< 偏移地址: 0xA4 地址对齐,无意义*/
__IO uint32_t null10; /**< 偏移地址: 0xA8 地址对齐,无意义*/
__IO uint32_t null11; /**< 偏移地址: 0xAC 地址对齐,无意义*/
__IO uint32_t null12; /**< 偏移地址: 0xB0 地址对齐,无意义*/
__IO uint32_t null13; /**< 偏移地址: 0xB4 地址对齐,无意义*/
__IO uint32_t null14; /**< 偏移地址: 0xB8 地址对齐,无意义*/
__IO uint32_t null15; /**< 偏移地址: 0xBC 地址对齐,无意义*/
__IO uint32_t null16; /**< 偏移地址: 0xCC 地址对齐,无意义*/
__IO uint32_t LTH; /**< 偏移地址: 0xC4 ADCx 数据低阈值*/
__IO uint32_t HTH; /**< 偏移地址: 0xC8 ADCx 数据高阈值*/
__IO uint32_t GEN; /**< 偏移地址: 0xCC ADCx 阈值监测使能*/
} ADC_TypeDef;
5.2 ADC_InitTypeDef
始化结构体,调用ADC_Init()时,把这里成员写入硬件寄存器,集中配置 ADC 参数:
typedef struct
{
u16 IE; /**< 中断使能*/
u16 RE; /**< DMA请求使能*/
u16 NSMP; /**< 两段采样使能*/
u16 DATA_ALIGN; /**< DAT右对齐使能*/
u16 CSMP; /**< 连续采样使能*/
u16 TCNT; /**< 触发一次采样所需的事件数*/
u16 TROVS; /**< 手动触发过采样使能,开启后一次采样需要多次触发*/
u16 OVSR; /**< 过采样率*/
u16 TRIG; /**< 触发信号*/
u16 S1; /**< 第一段常规采样次数*/
u16 S2; /**< 第二段常规采样次数*/
u16 IS1; /**< 空闲采样次数*/
u16 GAIN; /**< 低增益(7.2V量程)使能*/
u16 LTH; /**< ADC 模拟看门狗 0 下阈值*/
u16 HTH; /**< ADC 模拟看门狗 0 上阈值*/
u16 GEN; /**< ADC 模拟看门狗 0 对应使能位*/
} ADC_InitTypeDef;
5.3 API库函数
| 函数原型 | 功能简述 | 注意事项 |
|---|---|---|
| ADC_Init(ADC_TypeDef *ADCx, ADC_InitTypeDef *ADC_InitStruct) | 根据结构体配置初始化 ADC 外设 | 先结构体初始化,再修改参数,再调用本函数 |
| ADC_StructInit(ADC_InitTypeDef *ADC_InitStruct) | 结构体填充默认参数 | 必须最先调用,避免随机垃圾值 |
| ADC_GetIRQFlag(ADC_TypeDef *ADCx, uint16_t INT_flag) | 读取中断标志位 | 中断服务函数内判断中断事件 |
| ADC_ClearIRQFlag(ADC_TypeDef *ADCx, uint16_t INT_flag) | 清除中断标志 | 必须软件清除,否则重复进中断 |
| ADC_CHN_GAIN_CFG(ADC_TypeDef *ADCx, CHNx CHNum, u8 nChannel, ADC_GAINx GAIN3V6_or_7V2) | 配置某一次采样:通道号、ADC 量程 | 每一次次采样可独立选择量程 |
| s16 ADC_GetConversionValue(ADC_TypeDef *ADCx, DATx DATNum) | 读取 DATx 采样结果 | 返回s16有符号,存在负数 |
| ADC_SoftTrgEN(ADC_TypeDef *ADCx, FuncState state) | 开启 / 关闭软件触发功能 | 使能后才可以写 ADC_SWT 软件触发 |
6. 软件触发代码编写
我们从上面了解到三种触发方式:软件触发、UTIME 触发和 MCPWM 触发。这里先用软件触发。
6.1 引脚初始化
我想要做的是读取电位器电压值的变化,这里初始化引脚P0.5,也就是通道9,可以查看数据手册:
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_ANA;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStruct.GPIO_PuPd = DISABLE;
GPIO_Init(GPIO0, &GPIO_InitStruct);
6.2 ADC初始化
初始化代码非常简单,把我们上方给出的初始化结构体全部写下来,这里只初始化了一次常规采样,用于软件触发采样:
void ADC0_init(void)
{
ADC_InitTypeDef ADC_InitStructure;
ADC_StructInit(&ADC_InitStructure);
ADC_InitStructure.IE = 0; //中断使能
ADC_InitStructure.RE = 0; //DMA请求使能
ADC_InitStructure.NSMP = DISABLE; //两段采样使能
ADC_InitStructure.DATA_ALIGN = DISABLE; //DAT右对齐使能
ADC_InitStructure.CSMP = DISABLE; //连续采样使能
ADC_InitStructure.TCNT = 0; //触发一次采样所需的事件数
ADC_InitStructure.TROVS = DISABLE; //手动触发过采样使能,开启后一次采样需要多次触发
ADC_InitStructure.OVSR = 0; //过采样率
ADC_InitStructure.TRIG = 0; //触发信号
ADC_InitStructure.S1 = 1; //第一段常规采样次数
ADC_InitStructure.S2 = 0; //第二段常规采样次数
ADC_InitStructure.IS1 = 0; //空闲采样次数
ADC_InitStructure.LTH = 0; //ADC 模拟看门狗 0 下阈值
ADC_InitStructure.HTH = 0; //ADC 模拟看门狗 0 上阈值
ADC_InitStructure.GEN = 0; //ADC 模拟看门狗 0 对应使能位
ADC_InitStructure.GAIN = 0; //低增益(7.2V量程)使能
ADC_Init(ADC0, &ADC_InitStructure);
ADC_ClearIRQFlag(ADC0, ADC_ALL_IF); //清除所有中断标志位
ADC_CHN_GAIN_CFG(ADC0, CHN0, ADC_CHANNEL_9, ADC_GAIN3V6);//配置ADC通道9的增益为3.6V量程
}
然后初始化通道9电压。
6.3 主函数部分
首先是采样触发部分,我们实验软件触发采样,更具手册,需要向SWT发送0x5AA5,然后轮询等待采样完成,清楚相关中断标志位:
ADC0->SWT = 0x5AA5; // 写0x5AA5,软件触发ADC0执行一次采样转换
// 轮询等待SF1第一段ADC采样转换完成,等待转换完成标志位置1
while((ADC0->IF & ADC_SF1_IF) == 0);
ADC0->IF = ADC_SF1_IF; // 软件写1清除ADC转换完成中断标志位
之后调用ADC_GetConversionValue()函数读取ADC采集值,注意此时的值是左移四位的结果,要想得到实际值,需要向右移四位:
adc_raw = ADC_GetConversionValue(ADC0, DAT0);
adc_val = (u16)(adc_raw >> 4);

之后将所得的实际值,根据公式转换成电压,在OLED屏幕上显示出来:
float v_srn = (float)adc_val * 3.6f / 2048.0f;
voltage = v_srn * 3.0f / 2.0f;
OLED_ShowNum(1, 1,adc_val, 6);
OLED_ShowNum(2, 1,adc_raw, 6);
uint16_t v_int = (uint16_t)voltage;
uint16_t v_dec = (uint16_t)((voltage - v_int) * 100);
OLED_ShowNum(3, 1, v_int, 2);
OLED_ShowString(3,3,".");
OLED_ShowNum(3,4, v_dec, 2);
也可以根据所得的值进行一些其他判断,例如开关LED:
if(adc_val > 1500)
{
LED1_ON;
}
else
{
LED1_OFF;
}
if(adc_val > 1000)
{
LED2_ON;
}
else
{
LED2_OFF;
}
if(adc_val > 500)
{
LED3_ON;
}
else
{
LED3_OFF;
}
SoftDelay(0x80000);
此时完整主函数代码:
#include "lks32mc07x_lib.h"
#include "hardware_init.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "OLED.h"
s16 adc_raw; // ADC原始采样寄存器值(LKS32 ADC左对齐16位)
u16 adc_val; // 转换后12位ADC有效值 0~4095
float voltage; // 最终换算得到的实际输入电压
int main(void)
{
Hardware_init(); // 系统硬件初始化:GPIO、ADC、OLED、按键、LED等外设
while(1)
{
ADC0->SWT = 0x5AA5; // 写0x5AA5,软件触发ADC0执行一次采样转换
// 轮询等待SF1第一段ADC采样转换完成,等待转换完成标志位置1
while((ADC0->IF & ADC_SF1_IF) == 0);
ADC0->IF = ADC_SF1_IF; // 软件写1清除ADC转换完成中断标志位
adc_raw = ADC_GetConversionValue(ADC0, DAT0); // 读取DAT0通道原始ADC寄存器数据
adc_val = (u16)(adc_raw >> 4); // 左对齐数据右移4位,得到12位有效ADC值(0~4095)
// 计算采样点电压:参考电压3.6V,12位ADC满量程2048(注意:这里是单极性半量程)
float v_srn = (float)adc_val * 3.6f / 2048.0f;
voltage = v_srn * 3.0f / 2.0f; // 硬件分压补偿,还原电位器实际输入电压
OLED_ShowNum(1, 1,adc_val, 6); // OLED第1行第1列,打印12位ADC有效值,占6位
OLED_ShowNum(2, 1,adc_raw, 6); // OLED第2行第1列,打印ADC原始寄存器值,占6位
// 将浮点数电压拆分为整数部分、小数后两位,OLED不支持直接打印float
uint16_t v_int = (uint16_t)voltage; // 电压整数部分
uint16_t v_dec = (uint16_t)((voltage - v_int) * 100); // 电压小数点后2位
OLED_ShowNum(3, 1, v_int, 2); // 打印电压整数
OLED_ShowString(3,3,"."); // 打印小数点
OLED_ShowNum(3,4, v_dec, 2); // 打印电压小数部分
// ADC阈值判断,ADC数值大于1500点亮LED1
if(adc_val > 1500)
{
LED1_ON;
}
else
{
LED1_OFF;
}
// ADC数值大于1000点亮LED2
if(adc_val > 1000)
{
LED2_ON;
}
else
{
LED2_OFF;
}
// ADC数值大于500点亮LED3
if(adc_val > 500)
{
LED3_ON;
}
else
{
LED3_OFF;
}
SoftDelay(0x80000); // 软件延时,降低采样刷新速度,防止OLED刷屏过快
}
}
烧录可以看到电压正常采集,可以扭动电位器旋钮,看一下效果:

完整工程:
6.4 拓展:ISR触发
上面只是通过简单的轮询进行软件触发,我们也可以通过中断进行标志位的发送,触发ADC采集数据,由于我们初始化ADC的通道0的中断,因此只需要对ADC初始化代码进行第一采集中断使能即可:
void ADC0_init(void)
{
ADC_InitTypeDef ADC_InitStructure;
ADC_StructInit(&ADC_InitStructure);
ADC_InitStructure.IE = ADC_SF1_IE;
ADC_InitStructure.RE = 0;
ADC_InitStructure.NSMP = DISABLE;
ADC_InitStructure.DATA_ALIGN = DISABLE;
ADC_InitStructure.CSMP = DISABLE;
ADC_InitStructure.TCNT = 0;
ADC_InitStructure.TROVS = DISABLE;
ADC_InitStructure.OVSR = 0;
ADC_InitStructure.TRIG = 0;
ADC_InitStructure.S1 = 1;
ADC_InitStructure.S2 = 0;
ADC_InitStructure.IS1 = 0;
ADC_InitStructure.LTH = 0;
ADC_InitStructure.HTH = 0;
ADC_InitStructure.GEN = 0;
ADC_Init(ADC0, &ADC_InitStructure);
ADC_ClearIRQFlag(ADC0, ADC_ALL_IF);
ADC_CHN_GAIN_CFG(ADC0, CHN0, ADC_CHANNEL_9, ADC_GAIN7V2);
}
然后在Hardware_init()初始化NVIC:
void Hardware_init(void)
{
__disable_irq();
SYS_WR_PROTECT = 0x7a83;
IWDG_DISABLE();
GPIO_init();
GPIO_init_key();
ADC0_init();
OLED_Init();
TIMER2_init();
NVIC_SetPriority(ADC0_IRQn, 1);
NVIC_EnableIRQ(ADC0_IRQn);
SoftDelay(100);
__enable_irq();
}
然后来到中断服务函数部分,判读ADC0是否发生第一段常规采样完成中断,如果是则读取数据,中断标志位置1:
volatile s16 ADCSample;
volatile u8 adc_data_ready = 0;
void ADC0_IRQHandler(void)
{
if(ADC_GetIRQFlag(ADC0,ADC_SF1_IF))
{
ADC_ClearIRQFlag(ADC0,ADC_SF1_IF);
ADCSample = ADC_GetConversionValue(ADC0,DAT0) ;
adc_data_ready = 1;
}
else
{
ADC_ClearIRQFlag(ADC0,ADC_ALL_IF);
}
}
来到主函数,如果触发中断则进行数据显示:
#include "lks32mc07x_lib.h"
#include "hardware_init.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "OLED.h"
extern volatile s16 ADCSample;
extern volatile u8 adc_data_ready;
s16 adc_raw;
u16 adc_val;
float voltage;
float v_srn;
int main(void)
{
Hardware_init();
while(1)
{
//ADC_SoftTrgEN(ADC0, ENABLE);
ADC0->SWT = 0x5AA5; //软件触发
if(adc_data_ready == 1) // 检查ISR是否已完成
{
adc_data_ready = 0; // 清除通知标志
adc_raw = ADCSample;
adc_val = (u16)(adc_raw >> 4);
v_srn = (float)adc_val * 7.2f / 2048.0f;
voltage = v_srn * 3.0f / 2.0f;
uint16_t v_int1 = (uint16_t)v_srn;
uint16_t v_dec1 = (uint16_t)((v_srn - v_int1) * 100);
uint16_t v_int = (uint16_t)voltage;
uint16_t v_dec = (uint16_t)((voltage - v_int) * 100);
OLED_ShowNum(1, 1,adc_raw, 6);
OLED_ShowNum(2, 1,adc_val, 6);
OLED_ShowNum(3, 1, v_int1, 2);
OLED_ShowString(3,3,".");
OLED_ShowNum(3,4, v_dec1, 2);
OLED_ShowNum(4, 1, v_int, 2);
OLED_ShowString(4,3,".");
OLED_ShowNum(4,4, v_dec, 2);
if(adc_val > 700)
{
LED1_ON;
}
else
{
LED1_OFF;
}
if(adc_val > 500)
{
LED2_ON;
}
else
{
LED2_OFF;
}
if(adc_val > 300)
{
LED3_ON;
}
else
{
LED3_OFF;
}
}
}
}
这里为了方便观察实验现象,采用凌鸥官方给的一个图形化工具:

安装,按照下图操作:

想要观察什么参数直接拖拽即可,然后点击开始运行:

旋转电位器可以观察到上图电压的变换。
完整工程:
对于UTIME 触发和 MCPWM 触发,由于涉及到二者知识点,等把二者文章基础部分写完,后续在做补充,补充部分会存放到下方博客当中。


更多推荐
所有评论(0)