STM32之ADC详解

一、ADC概述

ADC(模拟量转数字量转换器),在 STM32 开发中,利用 ADC 端口的电压数据,转换为对应的具体数字量数据内容。可通过 ADC 方式获取常用数据内容有:

  • 光敏电阻、电池电量、油箱油量

ADC 转换的数据,可用于执行器控制行为,如低电量警告、油箱油量不足警告(即【阈值警告】 【阈值处理】 ,通过设置阈值,数据达到或超出时触发相应警示或控制逻辑 ) 。

二、ADC 工作原理

1.ADC****的主要特征

2. ADC****内部架构框图

一、整体架构与核心组件

  1. 模拟输入部分
    • 外部输入通道ADCx_IN0 ~ ADCx_IN15 是 16 组外部模拟信号输入引脚,可连接光敏电阻、电压传感器等外设,采集外部模拟电压。
    • 内部传感器 :集成温度传感器(需结合 V_REFINT 参考电压工作 ),用于检测芯片内部温度,方便实现温度补偿、过热保护等功能。
    • 模拟多路开关:最多支持 16 通道规则通道、4 通道注入通道切换,按需选通一路模拟信号送入转换器,实现多通道分时复用。
  2. 转换核心
    • 模拟至数字转换器 :采用逐次逼近法,以 ADCCLK(来自 ADC 预分频器,需配置分频保证时钟稳定 )为基准时钟,将模拟电压与内部基准电压比较,逐位确定数字量,完成 0~3.3VVref+ = 3.3VVref- = 0V 时 )模拟信号到 12 位数字量(范围 0000 0000 0000 ~ 1111 1111 1111 )的转换。
    • 参考电压V_REF+V_REF- 是 ADC 转换基准,决定量程。3.3V 输入时,电压分辨率为 3.3V/4096 ≈ 0.0008V,即 1 个数字量对应约 0.8mV 电压变化。

二、通道与转换模式

  1. 规则通道
    • 通道特点:最多 16 通道,用于常规、连续的多通道采样,像循环采集电池电压、多路传感器数据。
    • 触发控制 :通过 EXTSEL[2:0] 选择定时器触发源(如 TIM1_CH1TIM2_CH2 等 ),实现定时采样;也可由外部中断触发,灵活适配不同场景需求。
    • 数据存储 :转换结果存入 规则通道数据寄存器(16 位),支持 DMA 请求,转换完成后直接通过 DMA 传输数据到内存,减轻 CPU 负担。
  2. 注入通道
    • 通道特点:最多 4 通道,优先级高于规则通道,用于紧急、需快速响应的采样(如安全阈值监测 )。
    • 触发控制 :由 JEXTSEL[2:0] 选定时器触发源(如 TIM1_TRGOTIM4_CH3 等 ),或 JEXTRIG 控制位手动触发。
    • 数据存储 :结果存入 注入通道数据寄存器(4×16 位),可独立处理,快速响应特殊需求。

三、中断与阈值监测(模拟看门狗)

  1. 中断机制
    • 转换结束中断 :规则通道(EOC)、注入通道(JEOC)转换完成时,置标志位并可使能中断,触发 ADC中断 到 NVIC,通知 CPU 读取数据,实现实时处理。
    • 阈值中断 :模拟看门狗比较转换结果与 阈值高限(12 位)阈值低限(12 位),超出范围置 AWD 标志位,使能中断(AWDIE)后触发中断,用于电压超限报警(如电池过压 / 欠压、传感器异常 )。
  2. 模拟看门狗:实时监控 ADC 转换结果,一旦超出设定阈值,立即触发中断或标志位,快速响应异常,保障系统安全。

四、工作流程总结

  1. 信号输入 :外部 / 内部模拟信号经 ADCx_IN 或内部传感器进入,模拟多路开关选通通道。
  2. 触发转换 :规则 / 注入通道通过定时器、外部中断等触发,启动 模拟至数字转换器 工作。
  3. 数据转换:逐次逼近法转换模拟电压为 12 位数字量,存入对应数据寄存器。
  4. 结果处理:可触发中断通知 CPU 读取,或通过 DMA 传输数据;模拟看门狗实时监测,超限触发报警,实现从模拟信号采集到数字信号处理、异常响应的完整流程,支撑 STM32 对模拟量的精准采集与智能控制 。

五、举例讲解

一、场景与需求

  • 常规任务 :每隔 100ms 采集 3 路信号
    • 电池电压(ADC1_IN0,规则通道)
    • 车外温度(ADC1_IN1,规则通道,接温度传感器)
    • 光照强度(ADC1_IN2,规则通道,接光敏电阻)
  • 紧急任务 :实时监测电池电压,一旦超出 2.8V~3.6V 范围,立即触发报警
    • 复用电池电压信号到 ADC1_IN8(注入通道,优先级更高)

二、工作流程拆解(多转换触发逻辑)

1. 硬件连接与通道准备
  • 外部信号接入
    • 电池电压、温度传感器、光敏电阻的模拟信号,分别接到 ADC1_IN0/IN1/IN2(规则通道);
    • 电池电压同时接到 ADC1_IN8(注入通道,用于紧急阈值监测)。
  • 多路开关配置
    • 规则通道:使能 IN0/IN1/IN2,共 3 路,用于循环采样;
    • 注入通道:使能 IN8,共 1 路,用于紧急监测。
2. 触发转换的两种方式

(1) 规则通道触发(常规采集)

  • 触发源 :定时器触发(如 TIM3_TRGO,配置为 100ms 触发一次)。
  • 流程
    ① 定时器每 100ms 产生一个触发信号 → 触发规则通道转换;
    ② 模拟多路开关按顺序选通 IN0IN1IN2
    ③ ADC 依次对 3 路信号进行转换(逐次逼近法),结果存入规则数据寄存器

(2) 注入通道触发(紧急监测)

  • 触发源:软件触发 + 模拟看门狗(双重保障)。
  • 流程
    初始触发 :系统启动时,手动触发一次注入通道转换(读取初始电池电压);
    持续监测 :ADC 转换后,模拟看门狗自动比较结果与阈值(2.8V~3.6V):
    • 若在范围内:不触发中断,等待下一次规则通道触发时,顺带重新触发注入转换(或定时触发);
    • 若超出范围:立即置 AWD 标志位 → 触发注入中断 → CPU 跳转到中断函数处理(如点亮故障灯、记录日志)。
3. 数据处理与响应
  • 规则通道数据

    转换完成后,通过 DMA 自动将 3 路结果搬运到内存数组 → 程序读取数组,计算电池电量、温度值、光照强度,更新仪表盘显示。

  • 注入通道数据

    若触发中断(电压超限):

    ① 中断函数中读取注入数据寄存器 → 获取实时电池电压;

    ② 执行紧急逻辑(如:点亮红色故障灯、发送 CAN 报警帧、限制非关键用电设备)。

4. 多转换并行的关键逻辑
  • 优先级:注入通道优先级 > 规则通道。若规则通道转换中触发注入中断,ADC 会暂停规则转换,优先处理注入通道,保障紧急任务响应。
  • 资源复用:同一模拟信号(如电池电压)可接入多个通道(规则 + 注入),实现 "常规轮询 + 紧急监测" 的差异化需求。

3. ADC****数据转换规则

ADC 转换核心信息:

  • 转换方法:ADC 转换器采用逐次逼近法进行数据转换
  • 采样精度 :12 位,数值范围 0000 0000 0000 ~ 1111 1111 1111(对应十进制 0 ~ 4095
  • 参考电压Vref+ = 3.3VVref- = 0V
  • 电压分辨率3.3V / 4096 ≈ 0.0008V(即 1 个 ADC 数值对应约 0.8mV 电压 )
  • 示例场景 :假设 ADC 读取到电压为 1.83V ,可基于上述参数换算数字量

三、ADC 编程实现和相关寄存器

1. ADC 时钟问题

  • 分析依据:根据原理图进行分析
  • 引脚与通道对应:当前引脚为 PF8 引脚,其对应的 ADC 通道是 ADC3_IN6
  • 时钟归属:ADC3 所在的时钟为 APB2 总线时钟,即 ADC3 的时钟由 APB2 总线提供 ,在 STM32 中,不同的外设会挂载在不同的总线(如 APB1、APB2 等 )上,其时钟由对应总线时钟源分频等配置后提供,这里明确了 ADC3 依赖 APB2 总线时钟来进行工作时序的驱动 。

2.ADC_CR1****寄存器

3. ADC_CR2****寄存器

当前寄存器主要控制,ADC 采用数据通道,通信触发规则,数据对齐方式,校验和 ADC开启。

ADC_CR2寄存器的配置内容:

  • SWSTART(位 22):置 1,用于开始规则通道的转换
  • EXTTRIG(位 20):置 1,使能规则通道的外部触发转换模式
  • EXTSEL(位 19 - 17):设为 111,选择启动规则通道组转换的外部事件
  • ALIGN(位 11):置 0,配置数据右对齐
  • RSTCAL(位 3):置 1,执行复位校准操作
  • CAL(位 2):置 1,进行 A/D 校准
  • CONT(位 1):置 1,使能连续转换模式
  • ADON(位 0):置 1,开启 A/D 转换器 ,各配置项共同定义了 ADC 的转换启动、触发、校准、数据对齐及运行模式等关键参数 。

总结理解:

EXTRIG 位置为 0 的时候,转换完全是由软件也就是手动触发,每次将 SWSTART 置为 1 的时候触发一次转换,转换的配置按照预设的通道序列(ADC_SQRx)、采样时间(ADC_SMPRx)、分辨率(ADC_CR1.RES)等位配置执行,硬件会在启动转换的同时自动清除 SWSTART 位。

而当 SWSTART 置 1、EXTRIG 也为 1,且 EXTSEL 为 111 时,转换的触发源仍为软件写 SWSTART=1,但触发信号需先进入外部触发选择系统,再由该系统转发给 ADC 以触发转换;转换配置同样遵循预设的通道、采样时间等位配置,硬件也会在启动转换时自动清除 SWSTART 位,核心是通过 "外部触发框架" 实现软件触发,可复用外部触发的联动功能。

两种软件触发场景的关键差异对比表

维度 EXTRIG=0 + 写 SWSTART(纯软件触发) EXTRIG=1 + EXTSEL=111 + 写 SWSTART(软件模拟外部触发)
触发路径 软件 → 直接发送触发信号给 ADC,无中间环节 软件 → 写 SWSTART 生成信号 → 外部触发选择系统(EXTSEL 控制)→ 转发信号给 ADC
硬件联动能力 仅执行基础转换,结果需软件手动读取(或单独配置中断) 可复用外部触发的配套功能,如转换后自动启动 DMA 传输、触发特定中断等
触发延迟 低(信号直达 ADC,无额外逻辑开销) 略高(多一层外部触发系统的信号转发,存在微小延迟)
核心适用场景 简单手动触发需求,如初始化检测、按键触发单次采集,无需额外硬件联动 需软件触发 + 外部触发配套功能的场景,如软件手动触发后希望 DMA 自动搬运数据,或统一触发逻辑框架
SWSTART 清除时机 硬件启动转换的同时自动清 0 硬件启动转换的同时自动清 0(与纯软件触发一致)
外部事件干扰 无(外部触发系统已关闭,定时器、GPIO 等无法触发) 无(EXTSEL=111 仅响应软件 SWSTART 信号,其他外部事件不干扰)

4. ADC_SMPR****寄存器

5. ADC_SQR****寄存器

在 ADC 规则通道配置中,需两个寄存器配合实现:

  • ADC_SQR1:用于配置规则通道开启的数量(即决定要转换的规则通道总数 )。
  • ADC_SQR3 :用于配置 SQ1(规则通道序列中的第 1 个转换通道 )寄存器位对应的具体通道,此处为 ADC3_IN6(指定第 1 个转换的规则通道是 ADC3IN6 通道 ) 。

总结理解:

当配置了多个规则序列(如 SQ1 = 通道 A、SQ2 = 通道 B)并开启连续转换(CONT=1)时,扫描模式(SCAN)的状态决定了 ADC 的采集行为:开启扫描模式(SCAN=1)时,ADC 会按 SQ1→SQ2 的顺序循环采集所有配置通道,转换结果交替存入 ADC_DR(前序数据会被覆盖);关闭扫描模式(SCAN=0)时,ADC 仅重复采集 SQ1 通道,完全忽略其他序列配置,ADC_DR 中始终存储 SQ1 通道的最新数据。简言之,SCAN 位是多通道序列执行的总开关,控制 ADC 是否按完整序列顺序采集,还是仅固定采集第一个通道。

四、示例代码:

adc.c:

cpp 复制代码
#include "adc.h"

void LSEN_Init(void)
{
	// 1. RCC 时钟使能,需要提供 GPIOF 和 ADC3 
	RCC->APB2ENR |= (0x01 << 7) | (0x01 << 15);
	
	// 2. GPIOF --> PF8 模拟输入模式 ==> 0000
	GPIOF->CRH &= ~(0x0F);
	
	/*
	3. ADC 配置
	*/
	/*
	3.1 ADC 预分配倍数配置
		因为当前 STM32F103ZET6 对应 72 MHz,
		ADCCLK 不得大于 14 MHz,预分频倍数最小可以选择 6
	*/
	RCC->CFGR &= ~(0x03 << 14);
	RCC->CFGR |= (0x02 << 14);
	
	/*
	3.2 配置 ADC 的工作通道
		选择工作通道为 ADC3_IN6,规则通道打开一个,配置 SQ1
	*/
	ADC3->SQR1 &= ~(0x0F << 20);
	ADC3->SQR3 &= ~(0x1F);
	ADC3->SQR3 |= 0x06;
	
	/*
	3.3 ADC 采用周期
		采样周期选择 239.5 + 12.5 最大 ADC 采样周期,可以
		获取到更大的数据精度。
	*/
	ADC3->SMPR2 |= (0x07 << 18);
	
	/*
	3.4. 配置 ADC CR 寄存器相关内容
		CR1
			- DUALMOD位 [19:16] : ADC 独立模式 ==> 0000
			- SCAN [位8] :  扫描模式关闭 ==> 0
		CR2
			- SWSTART [位22] : 开始转换规则通道 ==> 1
			- EXTTRIG [位20]:规则通道的外部触发转换模式 ==> 1
			- EXTSEL [位19:17]: 选择启动规则通道组转换的外部事件 ==> 111
			- ALIGN [位11]::数据对齐(Data alignment)  ==> 0 右对齐
			- CONT [位1]::连续转换(Continuous conversion) ==> 1
	*/
	ADC3->CR1 &= ~(0x0F << 16);
	ADC3->CR1 &= ~(0x01 << 8);
	
	ADC3->CR2 &= ~(0xFFFFFFFF);
	ADC3->CR2 |= (0x01 << 22);  // SWSTART [位22] : 开始转换规则通道 ==> 1
	ADC3->CR2 |= (0x01 << 20);  // EXTTRIG [位20]:规则通道的外部触发转换模式 ==> 1
	ADC3->CR2 |= (0x07 << 17);  // EXTSEL [位19:17]: 选择启动规则通道组转换的外部事件 ==> 111
	ADC3->CR2 &= ~(0x01 << 11); // ALIGN [位11]::数据对齐(Data alignment)  ==> 0 右对齐
	ADC3->CR2 |= (0x01 << 1);
	
	/*
	3.5 ADC 复位 + 校准
	ADC 自校准 + 重启过程
		- RSTCAL [位3]::复位校准(Reset calibration) ==> 1	
		- CAL [位2]::A/D校准(A/D Calibration) ==> 1
		- ADON [位0]::开/关A/D转换器(A/D converter ON/ OFF) ==> 1
	*/
	ADC3->CR2 &= ~(0x01); // 关闭 ADC
	Delay_ms(10);         // 延时 10 ms
	ADC3->CR2 |= 0x01;    // 打开 ADC
	
	/*
	开始复位校准,给予对应寄存器标志位 1,如果 ADC 复位校准结束
	对应寄存器位置硬件清除为 0
	*/
	ADC3->CR2 |= (0x01 << 3); 
	// while 循环是等待当前复位校准结束 
	while ((ADC3->CR2 & (0x01 << 3)));
	
	Delay_ms(10); 
	
	/*
	开始 A/D 校准,给予对应寄存器标志位 1,ADC A/D 校准之后
	对应寄存器位置硬件清除为 0
	*/
	ADC3->CR2 |= (0x01 << 2);
	// while 循环是等待当前 A/D 校准结束 
	while ((ADC3->CR2 & (0x01 << 2)));
	
	ADC3->CR2 |= 0x01;    // 打开 ADC
}

u16 LSEN_GetValue(void)
{
	/*
	ADC->SR 状态寄存器 EOC [位1] 位置,如果数据未转换完成
		EOC 为 0 ,转换完成 EOC 为 1
	*/
	while (!(ADC3->SR & (0x01 << 1)));
	
	return ADC3->DR;
	
}

adc.h:

cpp 复制代码
#ifndef _ADC_H
#define _ADC_H

#include "stm32f10x.h"

#include "delay.h"

/**
 * @brief 光敏电阻初始化函数
 */
void LSEN_Init(void);

u16 LSEN_GetValue(void);


#endif

delay.c:

cpp 复制代码
#include "delay.h"

void Delay_us(u32 us)
{
	while (us--) 
	{
		// 利用 __NOP() 操作占用 MCU 一次执行周期特征,调用 72 个 __NOP
		// 不建议使用 for 循环或者 while 循环,循环判断都需要占用一个 MCU 执行周期
		__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
		__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
		__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
		__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
		__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
		__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
		__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
		__NOP();__NOP();
	}
}

void Delay_ms(u32 ms)
{
	Delay_us(ms * 1000);
}

delay.h:

cpp 复制代码
#ifndef _DELAY_H
#define _DELAY_H

#include "stm32f10x.h"

/**
 * @brief 延时微秒控制函数,延时单位是 us
 *
 * @param us 延时微秒时间
 */
void Delay_us(u32 us);

/**
 * @brief 延时毫秒控制函数,延时单位是 ms
 *
 * @param ms 延时毫秒时间
 */
void Delay_ms(u32 ms);

#endif

usart.c:

cpp 复制代码
#include "usart1.h"

USART1_Data usart1_val = {0};

void USART1_Init(u32 brr)
{
	/*
	1. 时钟使能 GPIOA 和 USART1,两者都在 APB2 时钟控制
		USART1 对应位 14,GPIOA 对应位2
	*/
	RCC->APB2ENR |= (0x01 << 2) | (0x01 << 14);
	
	/*
	2. PA9 和 PA10 GPIO 配置
		PA9 是 MCU 的 TX 数据发送端,GPIO 工作模式选择【复用推挽输出模式】
		PA10 是 MCU 的 RX 数据发送端,GPIO 工作模式选择【浮空输入模式】
	*/
	GPIOA->CRH &= ~(0x00FF << 4);
	GPIOA->CRH |= 0x0B << 4;  // PA9 -->  TX【复用推挽输出模式】
	GPIOA->CRH |= 0x04 << 8;  // PA10 --> RX【浮空输入模式】
	
	/*
	3. USART1 串口配置
		3.1 8n1 配置,NRZ 数据格式配置,8个数据位,0 个校验位,1 个停止位
		3.2 USART1 对应 TE 和 RE 开启,打开 USART1 的发送数据和读取数据能力
		3.3 USART1 BRR 波特率配置
	*/
	// 3.1 8n1 配置,如果仅使用寄存器方式配置当前代码,可以省略一下过程
	// 当前代码是为了后续的 【标准库】和【Hal库】,也是代码逻辑的一部分
	USART1->CR1 &= ~(0x01 << 12); // 【8】USART1->CR1 控制寄存器对应 M (位12) 明确当前数据字长为 8 数据位
	USART1->CR1 &= ~(0x01 << 10); // 【n】USART1->CR1 控制寄存器对应 PCE (位10), 明确当前不使用校验位
	USART1->CR2 &= ~(0x03 << 12); // 【1】USART1->CR2 控制寄存器对应 STOP (位13,12), 限制当前数据停止位为 1
	
	// 3.2 USART1 对应 TE 和 RE 开启
	USART1->CR1 |= (0x03 << 2); // TE(位3) RE(位2) 进行赋值 1 开始操作
	
	// 3.3 USART1 BRR 波特率配置

	// 假设波特率是 115200 ==> USARTDIV 数据
	float usart_div = 72 * 1000 * 1000 / (16 * brr);
	// usart_div == 39.0625
	/*
	将 usart_div 进行拆解,分别对应整数部分和小数部分内容,提供给当前 USART1 中用于
	计算波特率对应寄存器位。
	*/
	int usart_div_Mantissa = (u32)usart_div;
	int usart_div_fraction = (u32)((usart_div - usart_div_Mantissa) * 16);

	// 两个数据进行组合 提供给 USART1 波特率寄存器的数据为 
	USART1->BRR |= (usart_div_Mantissa << 4) | usart_div_fraction;
	
	// 4. 启动 USART1 
	USART1->CR1 |= (0x01 << 13);
}

void USART1_SendByte(u8 byte)
{
	/*
	利用 USART1_SR 寄存器,判断之前的数据内容是否发送完成,如果没有
	发送完成,本次发送操作进入【阻塞状态】
		如果 USART1_SR TC ==> 0 表示之前的数据发送未完成
		如果 USART1_SR TC ==> 1 表示之前的数据发送完毕
	TC Transmission Complete
	*/
	while (0 == (USART1->SR & (0x01 << 6)));
	
	/*
	将需要发送的数据存储到 USART1->DR 数据寄存器中,
	DR 会将数据直接提供给 TDR 寄存器,TDR 寄存器会将
	数据提供给移位寄存器,SR 寄存器 TC TC 寄存器位置 0
	发送完毕会将 SR 寄存器的中,TC 寄存器位置修改为 1
	*/
	USART1->DR = byte;
}


void USART1_SendBuffer(u8 *buffer, u16 count) 
{
	while (count--)
	{
		USART1_SendByte(*buffer);
		buffer++;
	}
}

void USART1_SendString(const char * str) 
{
	while (*str)
	{
		USART1_SendByte(*str);
		str++;
	}
}

u8 USART1_ReceiveByte(void)
{
	u8 data = 0;
	
	/*
	判断在 USART1->SR 寄存器中,对应的 RXNE (Read data register not empty) 标志位
		如果没有数据可以收到,RXNE 为 0
		如果有数据可以读取,RXNE 为 1
	
	while 进行 RXNE 标志位判断,如果没有数据当前循环【阻塞后续代码】
	*/
	while (0 == (USART1->SR & (0x01 << 5)));
	
	data = (u8)USART1->DR; 
	
	return data;
}

/*
非重要知识点,仅实现 printf 函数功能重定向,可以实现
printf 打印操作数据 USART1 发送到 PC
*/
int fputc(int c, FILE *stream)
{
	USART1_SendByte(c);
	
	return c;
}

void USART1_Interrupt_Enable(void)
{
	/*
	当前 USART1 的控制寄存器中,
		打开 IDLEIE 数据总线空闲中断使能
		打开 RXNEIE 数据总线空闲中断使能
	*/
	USART1->CR1 |= (0x01 << 4) | (0x01 << 5);
	
	/*
	设置当前 USART1 对应的中断优先级为 0001 在
	全局优先级设置为 2 的情况下  占先 0 次级 1
	*/
	NVIC_SetPriority(USART1_IRQn, 1); // 0001 占先 0 次级 1
	
	/*
	告知当前 MCU 使能对应的 USART1_IRQn 中断
	*/
	NVIC_EnableIRQ(USART1_IRQn);
}

/*
完成 USART1 对应的 USART1_IRQn 对应的中断处理函数
当前中断处理函数是用于接收的数据内容进行处置操作,将接收的数据
存储到 USART1_Data 结构体中,对应的 u8 data[DATA_SIZE] 数组
*/
void USART1_IRQHandler(void)
{
	u32 val = 0;
	
	/*
	usart1_val.flag ==> 1 表示当前数据接收完毕,同时已经回显到
	PC 端 USART 工具
	*/
	if (usart1_val.flag)
	{
		// 对当前数据空间进行擦除,
		memset(&usart1_val, 0, sizeof(USART1_Data));
	}
		
	/*
	如果当前触发的中断为 【RXNE 中断】,表示数据在通过串口
	传递到 MCU 中
	*/
	if (USART1->SR & (0x01 << 5))
	{
		usart1_val.data[usart1_val.count++] = USART1->DR;
		
		/*
		当前接收到的有效字节个数 == DATA_SIZE,当前数据缓冲区数组已满
		*/
		if (DATA_SIZE == usart1_val.count)
		{
			USART1_SendBuffer(usart1_val.data, usart1_val.count);
			usart1_val.flag = 1;
		}
	}

	/*
	如果当前数据总线空闲 【IDLE 中断】,表示数据传递完毕
	*/
	if (USART1->SR & (0x01 << 4))
	{
		/*
		表示当前数据接收已完成
		*/
		usart1_val.flag = 1;
		
		/*
		需要完成对于当前 USART1->SR IDLE 数据总线空闲
		中断标志位进行清除操作。
		【官方要求】
			1. 读取 USART1->SR 寄存器
			2. 读取 USART1->DR 寄存器
		*/
		val = USART1->SR;
		val = USART1->DR;
		
		
		// 将数据回显到 PC 端 USART 调试工具
		USART1_SendBuffer(usart1_val.data, usart1_val.count);
	}
}

usart.h:

cpp 复制代码
#ifndef _USART1_H
#define _USART1_H

#include "stm32f10x.h"

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

#define DATA_SIZE (256)

typedef struct usart1_data
{
	u8 data[DATA_SIZE]; // 接受数据缓冲区
	u8 flag;            // 数据处理标志位
	u16 count;           // 读取到的有效字节个数
} USART1_Data;

extern USART1_Data usart1_val;

/**
 * @brief USART1 初始化函数,需要完成
 *     1. PA9 和 PA10 GPIO 配置
 *     2. USART1 配置
 *
 * @param brr 用户提供的对应当前 USART1 的波特率
 */
void USART1_Init(u32 brr);

/**
 * @brief USART1 发送一个字节数据到其他设备
 *
 * @param byte 发送的字节数据。
 */
void USART1_SendByte(u8 byte);

void USART1_SendBuffer(u8 *buffer, u16 count);

void USART1_SendString(const char * str);

/**
 * @brief USART1 接受外部输入的数据内容,当前函数是接收一个字节数据
 *
 * @return 返回值是接收到的数据内容
 */
u8 USART1_ReceiveByte(void);

/*
后续代码中需要利用【中断】对代码内容进行优化
	1. 接收数据终止条件
	2. 提供外部可以持续使用数据内容
	3. 数据发送中断判断
*/

/**
 * @brief USART1 串口中断使能函数
 */
void USART1_Interrupt_Enable(void);

#endif

main.c:

cpp 复制代码
#include "stm32f10x.h"

//#include "led.h"
//#include "key.h"
#include "delay.h"
//#include "beep.h"
#include "usart1.h"
#include "adc.h"

int main(void)
{
	//Led_Init();
	LSEN_Init();
	USART1_Init(115200);
	USART1_Interrupt_Enable();
	
	//Led1_Ctrl(1);

	while (1)
	{
		u16 adc_data = LSEN_GetValue();

		printf("adc_data : %d\r\n", adc_data);
		printf("U_LSEN : %f\r\n", 3.3 / 4096 * adc_data);
	
		Delay_ms(500);
	}
}

https://github.com/0voice