赞
踩
目录
3.3.3 TIM1配置函数 tim1_cfg_init():
3.3.5 ADC配置函数 ADCConfiguration():
3.3.7 DMA搬运完成中断处理函数 DMAC_CH1_2_Handler():
本文将带领大家深入探索ADC(模数转换器)的运作奥秘及其配置技巧。通过详细解读ADC的工作方式,并结合三个生动案例——软件定时触发ADC采样、ADC与DMA联动实现高效数据传输、TIM1精准触发ADC采样及DMA中断处理——我们将一同揭开ADC、DMA与TIM协同工作的神秘面纱。旨在让读者能够更好地理解并掌握ADC的工作原理和应用技巧,为后续的STM32开发奠定坚实基础。
任务 1:软件触发 ADC 采样设计一个1秒定时器,每秒触发ADC采样,检测电位器输出电压,比对ADC转换值和实际电压。实验将帮助优化TIM配置和理解定时器溢出时间,同时深入了解ADC工作模式。任务 2:ADC 和 DMA 联动
定时 10ms,每 10ms 触发一次 adc 采样,将 adc 转换结果通过 DMA 搬运到数组,并进一
次 DMA 搬运完成中断任务 3: TIM1 触发 ADC 采样
TIM1 作为 PWM 输出,PWM 频率 20K。PA11 复用为 TIM1_CH4,设置 CH4 占空比为 99%
比较 – OC4REF 信号被用作触发输出(TRGO)
ADC 采样触发源设置为 T1_TRGO,下降沿触发采样。同时使能 DMA,将采样结果搬运到数组,并使能 DMA 搬运完成中断。
由上图的ADC基本结构,我们可以得知:
GPIO配置:首先需要配置相应的GPIO引脚,将其设置为ADC模式以接收模拟信号。
输入通道:ADC可连接多种模拟信号源,如温度传感器、内部参考电压(VREFINT)等。
分辨率:ADC的分辨率通常为16位,决定了其数字化的精度。
EOC(转换完成标志位):ADC转换完成后会产生EOC标志位,用于指示转换是否完成。
ADC转换器:负责将模拟信号转换为数字信号。
规则组和注入组:ADC可以配置规则组和注入组来同时处理多个通道的转换请求。
START触发控制:ADC可以通过外部触发信号(如定时器触发)启动转换。
CLOCK(时钟控制):ADC的时钟频率可以进行配置,影响转换速度和精度。
RCL(模拟看门狗):ADC可以配置模拟看门狗来监控转换的稳定性和准确性。
AD数据寄存器:存储转换结果的寄存器,包括规则组和注入组的转换结果。
中断控制:通过配置NVIC(Nested Vectored Interrupt Controller)来管理ADC转换完成后的中断。
在ADC中,数据的左右对齐方式影响着数据的存储和解释方式。下面是它们之间的区别:
左对齐(Left Alignment):
- 数据的高位字节存储在ADC数据寄存器的高字节位置,低位字节存储在低字节位置。
- 对于12位的ADC数据,左对齐时,最高4位为0,其余的8位为有效数据,因此可以直接用于计算或显示。
右对齐(Right Alignment):
- 数据的低位字节存储在ADC数据寄存器的高字节位置,高位字节存储在低字节位置。
- 对于12位的ADC数据,右对齐时,最低4位为有效数据,其余的8位为0,需要进行位移操作或者掩码操作才能获取有效数据。
1. 单次转换模式(Single Conversion Mode): 在单次转换模式下,ADC只进行一次转换,即采样完一个通道的数据后就停止转换。 这种模式适用于只需要进行一次采样的情况,例如进行一次温度测量或按下按键时的电压检测。2 .连续转换模式(Continuous Conversion Mode): 在连续转换模式下,ADC会持续不断地进行转换,每转换完一个通道的数据后立即开始下一个通道的转换。 这种模式适用于需要持续监测多个通道的情况,例如采集多路传感器的数据或进行连续的模拟信号采样。3. 断续转换模式(Discontinuous Conversion Mode): 在断续转换模式下,ADC会间断性地进行转换,即在每次转换后会暂停一段时间再进行下一次转换。 这种模式适用于需要定时采样的情况,例如按照一定的时间间隔进行数据采集或与其他设备进行同步采样。
ADC_InitStructure.ADC_Vref = ADC_Vref_Externa_VDD; //参考电压选择
ADC_InitStructure.ADC_ClkMode = ADC_ClockMode_sysnClk; //选择转换时钟源
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //精度
ADC_InitStructure.ADC_SamecMode = DISABLE; //采样模式选择0=连续采样;1=同时采样
顺序采样(Sequential Sampling):
- 在顺序采样模式下,ADC会按照预先设定的顺序依次对多个通道进行采样。
- 每次触发ADC转换时,它会先采样第一个通道,然后按照顺序依次采样后续的通道。
- 这种模式适用于需要按照一定顺序采样多个通道的场景,例如温度传感器、光线传感器等。
同时采样(Simultaneous Sampling):
- 在同时采样模式下,ADC能够同时对多个通道进行采样,即在同一时刻采样多个通道的数据。
- 这种模式通常需要特殊的硬件支持,例如多个ADC通道共享同一个采样保持电路,以确保在同一时刻采样的数据是准确同步的。
- 同时采样模式适用于需要同时获取多个通道数据并确保它们之间的时间同步性的场景,例如需要在多个信号之间进行实时比较或分析的情况。
我们首先需要理解定时器的工作原理。定时器接收一个时钟源 Tclk,通常情况下是系统时钟的频率,例如在这里是 72MHz。然后,我们将这个时钟源进行分频,分频值为 psc,这样定时器的最终频率就是 Tclk / (psc + 1)。
这里的频率表示的是每秒钟记 Tclk / (psc + 1) 个数。每个数的时间为 (psc + 1) / Tclk 秒。换句话说,频率的倒数就是周期,所以每个数的周期是 (psc + 1) / Tclk 秒。
接着,我们设置定时器的计数范围为 arr,那么从 0 记到 arr 就需要 (arr + 1) 个数。所以总的时间就是 (arr + 1) * (psc + 1) / Tclk。
举个例子来说,如果我们设置 arr = 7199,psc = 9999,那么我们将 72MHz 分成了 7200Hz,每个数的时间就是 1 / 7200 秒。定时器更新需要 (7199 + 1) * (1 / 7200) = 1s,也就是每隔 1s 进行一次更新。
MDA即直接内存存取(Direct Memory Access)。DMA 是一种数据传输方式,允许外设(如 ADC、USART 等)直接访问内存,而无需 CPU 的干预。这样可以大大减轻 CPU 的负担,提高数据传输效率。 DMA 的主要作用是在外设和内存之间进行数据传输,而无需 CPU 的直接参与。在 STM32 中,DMA 控制器通常有多个通道,每个通道可以配置为不同的外设和内存地址之间的数据传输。通过配置 DMA 控制器,可以实现各种外设和内存之间的数据传输,如 ADC 数据转移到内存、内存数据传输到 USART 发送缓冲区等。
由上图的DMA基本结构,我们可以得知:
配置参数:
起始地址:
- 外设起始地址:指定DMA传输的源地址或目标地址,通常是外设寄存器的地址(如ADC的数据寄存器、串口数据寄存器等)。
- 存储器起始地址:指定DMA传输的源地址或目标地址,通常是SRAM或FLASH中的地址。
数据宽度:定义了每次DMA传输的数据量大小,可以是字节、半字或字。
地址是否自增:
- 外设地址自增:指定在DMA传输过程中,外设地址是否自动递增。
- 存储器地址自增:指定在DMA传输过程中,存储器地址是否自动递增。
方向:
- DMA传输数据的方向有三个:从外设到存储器(P2M)、从存储器到外设(M2P)以及从存储器到存储器(M2M)。这通过DMA控制寄存器的DIR位进行配置。
传输计数器:用于记录DMA传输的剩余操作数目。每执行一次传输,传输计数器的值会递减。
触发方式:
- 硬件触发:当外设产生特定事件(如中断)时,触发DMA传输。
- 软件触发:通过软件指令触发DMA传输。
外设寄存器:通常指外设的数据寄存器(DR),用于存储和读取外设的数据。
自动重装器:在某些DMA配置中,当传输计数器减至零时,自动重装器可以将预设的值重新加载到传输计数器中,以实现连续的数据传输。
开关控制:DMA的开关控制允许用户启用或禁用DMA传输,从而灵活管理DMA的活动状态。
- #define DMA_ENABLE 1 // DMA使能标志位
- #define ADC_SIZE 16UL // ADC转换数据缓冲区大小
- #define ADC1_DR_Address (0x4001A020) // ADC1数据寄存器地址
-
- unsigned int ADCConvertedRawData[ADC_SIZE] = {0}; // ADC转换结果数据缓冲区,用于存储转换后的数据
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //设置数据传输方向,即决定是从外设读取数据到内存还是从内存读数据到外设,这里我们是从外设ADC_DR寄存器读数据到内存
数据的源地址
- DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //用来设置DMA传输的外设基地址
-
- //另外一种方法
- //DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //外设基地址,给定形参AddrA
数据传输位置的目标地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADCConvertedRawData; //内存基地址
传递数据多少的数据传输量
DMA_InitStructure.DMA_BufferSize = ADC_SIZE; //设置一次传输数据量的大小
- void GPIOConfiguration(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct;
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; // 设置GPIOA的第0个引脚作为模拟输入
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN; // 设置引脚模式为模拟输入模式
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; // 不设置上拉或下拉
- GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化GPIOA的配置
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_1); // 将PA0引脚配置为ADC1模块的模拟通道0
- }
- void ADCConfiguration(void)
- {
- ADC_InitTypeDef ADC_InitStructure;
- DMA_InitTypeDef DMA_InitStructure;
-
- ADC_InitStructure.ADC_ClkMode = ADC_ClockMode_sysnClk; //选择转换时钟源
- ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //精度
- ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //转换模式0=单次;1=连续;表示触发一次转换一个通道或是一个序列
- ADC_InitStructure.ADC_AutoWatiMode = DISABLE; //该位由软件设置和清除来使开启 / 关闭自动延迟转换模式
- ADC_InitStructure.ADC_DMATRIG_LEVEL = FIFO_DMA_NOEMPTY; //软件配置触发 DMA 请求的 FIFO 水平。在 DMAEN 使能前进行配置,避免出现 DMA 在搬运时的错误。
- ADC_InitStructure.ADC_DMA = DISABLE; //是否允许用 DMA 控制器来自动管理转换的结果数据
- ADC_InitStructure.ADC_DiscMode = DISABLE; // 0=断续模式禁止;1=断续模式开启
- ADC_InitStructure.ADC_SamecMode = DISABLE; //采样模式选择0=连续采样;1=同时采样
- ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; //触发方式
- ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_SoftWare; //触发源
- ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐方式
- ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Upward; // 0=向前扫描 (从 CHSEL0 到 CHSEL15);1=向后扫描( 从 CHSEL15 到 CHSEL0)
- ADC_InitStructure.ADC_Vref = ADC_Vref_Externa_VDD; //参考电压选择
- ADC_Init(ADC, &ADC_InitStructure);
-
- ADC_ChannelConfig(ADC, ADC_Channel_0, 15, 7, 7); //配置通道,CONVERT,ADJUST,SMP
- ADC_Cmd(ADC, ENABLE);
- }

- void NVIC_Configuration()
- {
- NVIC_InitTypeDef NVIC_InitStructure;
-
- NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; // 设置TIM3的中断通道
- NVIC_InitStructure.NVIC_IRQChannelPriority = 0; // 设置中断优先级为0
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能TIM3的中断
- NVIC_Init(&NVIC_InitStructure); // 初始化NVIC配置
- }
-
- static void tim3_cfg_init()
- {
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
-
- TIM_TimeBaseStructure.TIM_Prescaler = 1599; // 预分频系数设置为1599,即TIM时钟=16MHz/(1599+1)=10kHz
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 设置计数模式为向上计数
- TIM_TimeBaseStructure.TIM_Period = 10000; // 设置自动重载寄存器的值为10000
- TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 时钟分频系数不分频
- TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; // 重复计数器值为0
- TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // 初始化TIM3的时间基本配置
-
- TIM_ARRPreloadConfig(TIM3, ENABLE); // 使能TIM3的自动重装载功能
- TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); // 使能TIM3更新中断
- TIM_Cmd(TIM3, ENABLE); // 使能TIM3
- }

- void TIM3_Handler(void)
- {
-
- if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
- {
- TIM_ClearITPendingBit(TIM3, TIM_IT_Update); // 清除中断标志
-
- // 在这里进行ADC采样
- ADC_StartOfConversion(ADC1); // ADC 开始转换命令
- while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); // 等待转换完成
- uint16_t adc_value = ADC_GetConversionValue(ADC1); // 读取转换结果
-
- // 处理采样值,这里你可以根据需要进行其他操作,比如显示在OLED上
- OLED_ShowNum(2, 9, adc_value, 4);
- }
- }

- int main()
- {
- SystemInit();
- SetSysClock(); //主频配置
-
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_ADC, ENABLE); //开时钟
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
- RCC_APB0PeriphClockCmd(RCC_APB0Periph_TIM3, ENABLE);
- OLED_Init();
- OLED_ShowString(1,1,"hello world");//测试OLED是否正常
- OLED_ShowString(2, 1, "ADValue:");
-
- GPIOConfiguration(); // GPIO设置
- ADCConfiguration(); // ADC初始化
- ADC_StartOfConversion(ADC1); // ADC 开始转换命令
- tim3_cfg_init(); // 初始化定时器
- NVIC_Configuration(); // 配置中断
-
- while (1)
- {
-
- }
- }

在程序的开头定义全局变量,用于记录定时器中断触发次数以及其他需要跨函数访问的变量。
- #define DMA_ENABLE 1 // DMA使能标志位
- #define ADC_SIZE 16UL // ADC转换数据缓冲区大小
- #define ADC1_DR_Address (0x4001A020) // ADC1数据寄存器地址
-
- unsigned int ADCConvertedRawData[ADC_SIZE] = {0}; // ADC转换结果数据缓冲区,用于存储转换后的数据
-
- volatile uint32_t TIM_interrupt_count = 0; // 定义全局变量,用于记录定时器中断触发次数
配置 STM32 的 GPIO 引脚,使其与 ADC 和其他外设连接,并设置引脚的工作模式和上下拉电阻。
- void GPIOConfiguration(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct;
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_1); // PA0(AN1)=ADCIN[0]
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_1); // PA1(AN1)=ADCIN[1]
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_2); // PA2(AN2)=ADCIN[2]
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_1); // PA3(AN1)=ADCIN[3]
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_2); // PA4(AN2)=ADCIN[4]
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_1); // PA5(AN1)=ADCIN[5]
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_1); // PA6(AN1)=ADCIN[6]
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_1); // PA7(AN1)=ADCIN[7]
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOB, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOB, GPIO_PinSource0, GPIO_AF_2); // PB0(AN2)=ADCIN[8]
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOB, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOB, GPIO_PinSource1, GPIO_AF_1); // PB1(AN1)=ADCIN[9];
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_1); // PA8(AN1)=ADCIN[10]
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOF, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOF, GPIO_PinSource6, GPIO_AF_1); // PF6(AN1)=ADCIN[11]
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOF, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOF, GPIO_PinSource7, GPIO_AF_1); // PF7(AN1)=ADCIN[12]
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOB, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_1); // PB5(AN1)=ADCIN[13]
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1); // PA9(AN1)=ADCIN[14]
-
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1); // PA10(AN1)=ADCIN[15]
- }

配置 ADC 模块,包括时钟、精度、转换模式等参数,并配置 DMA 控制器以使 ADC 数据能够直接传输到内存。
- void ADCConfiguration(void)
- {
- ADC_InitTypeDef ADC_InitStructure;
- DMA_InitTypeDef DMA_InitStructure;
-
- ADC_InitStructure.ADC_ClkMode = ADC_ClockMode_SynClkDiv4; //选择转换时钟源
- ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //精度
- ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //转换模式0=单次;1=连续;表示触发一次转换一个通道或是一个序列
- ADC_InitStructure.ADC_AutoWatiMode = ENABLE; //该位由软件设置和清除来使开启 / 关闭自动延迟转换模式
- ADC_InitStructure.ADC_DMATRIG_LEVEL = FIFO_DMA_NOEMPTY; //软件配置触发 DMA 请求的 FIFO 水平。在 DMAEN 使能前进行配置,避免出现 DMA 在搬运时的错误。
- ADC_InitStructure.ADC_DMA = ENABLE; //是否允许用 DMA 控制器来自动管理转换的结果数据
- ADC_InitStructure.ADC_DiscMode = DISABLE; // 0=断续模式禁止;1=断续模式开启
- ADC_InitStructure.ADC_SamecMode = DISABLE; //采样模式选择0=连续采样;1=同时采样
- ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; //触发方式
- ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_SoftWare; //触发源
- ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐方式
- ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Upward; // 0=向前扫描 (从 CHSEL0 到 CHSEL15);1=向后扫描( 从 CHSEL15 到 CHSEL0)
- ADC_InitStructure.ADC_Vref = ADC_Vref_Internal_3V3; //参考电压选择
- ADC_Init(ADC, &ADC_InitStructure);
-
- ADC_ChannelConfig(ADC, ADC_Channel_0, 15, 7, 7); //配置通道,CONVERT,ADJUST,SMP
- ADC_ChannelConfig(ADC, ADC_Channel_1, 15, 7, 7);
- ADC_ChannelConfig(ADC, ADC_Channel_2, 15, 7, 7);
- ADC_ChannelConfig(ADC, ADC_Channel_3, 15, 7, 7);
- ADC_ChannelConfig(ADC, ADC_Channel_4, 15, 7, 7);
- ADC_ChannelConfig(ADC, ADC_Channel_5, 15, 7, 7);
- ADC_ChannelConfig(ADC, ADC_Channel_6, 15, 7, 7);
- ADC_ChannelConfig(ADC, ADC_Channel_7, 15, 7, 7);
- ADC_ChannelConfig(ADC, ADC_Channel_8, 15, 7, 7);
- ADC_ChannelConfig(ADC, ADC_Channel_9, 15, 7, 7);
- ADC_ChannelConfig(ADC, ADC_Channel_10, 15, 7, 7);
- ADC_ChannelConfig(ADC, ADC_Channel_11, 15, 7, 7);
- ADC_ChannelConfig(ADC, ADC_Channel_12, 15, 7, 7);
- ADC_ChannelConfig(ADC, ADC_Channel_13, 15, 7, 7);
- ADC_ChannelConfig(ADC, ADC_Channel_14, 15, 7, 7);
- ADC_ChannelConfig(ADC, ADC_Channel_15, 15, 7, 7);
- ADC_Cmd(ADC, ENABLE);
-
- #if DMA_ENABLE
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA, ENABLE);
- /*DMA1 channel1 configuration ----------------------------------------------*/
- DMA_DeInit(DMA1_Channel1);
- DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //用来设置DMA传输的外设基地址
- DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADCConvertedRawData; //内存基地址
- DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //设置数据传输方向,即决定是从外设读取数据到内存还是从内存读数据到外设,这里我们是从外设ADC_DR寄存器读数据到内存
- DMA_InitStructure.DMA_BufferSize = ADC_SIZE; //设置一次传输数据量的大小
- DMA_InitStructure.DMA_MSIZE = DMA_MSIZE_1; //突发事务传输长度
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //设置传输数据的时候 (外设) 地址是不变还是递增。
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //设置传输数据的时候 (内存 )地址是不变还是递增
- DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; //设置外设的数据长度是为字节传输(8bits)还是半字(16bits)还是字传输(32bits)
- DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; //设置内存的数据长度,同上。
- DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //设置是否进行循环采集
- DMA_InitStructure.DMA_Priority = DMA_Priority_High; //设置优先级
- DMA_InitStructure.DMA_M2M = DMA_P2M_Enable;
- DMA_Init(DMA1_Channel1, &DMA_InitStructure); //设置是否是存储器到存储器模式传输
- /* Enable DMA1 channel1 TC interupt*/
- DMA_ITConfig(DMA1_Channel1, DMA1_FLAG_TC1, ENABLE); //配置DMA发送完成后中断
- DMA_RemapConfig(DMA1_Channel1, DMA_ReqNum5, DMA_REQ_ADC);
- /* Enable DMA1 channel1 */
- DMA_Cmd(DMA1_Channel1, ENABLE);
- #endif
- }

配置定时器 TIM,用于定时触发 ADC 转换,并设置定时器的时钟源、分频系数、计数模式等参数。
- static void tim3_cfg_init()
- {
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
- NVIC_InitTypeDef NVIC_InitStructure;
-
- TIM_TimeBaseStructure.TIM_Prescaler = 1599; //预分频系数设置位1599,即TIM_CLK=16/(1599+1)=10K;
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
- TIM_TimeBaseStructure.TIM_Period = 100;
- TIM_TimeBaseStructure.TIM_ClockDivision = 0;
- TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
- TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
-
- TIM_ARRPreloadConfig(TIM3, ENABLE);
- TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
- TIM_Cmd(TIM3, ENABLE);
-
-
- NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
- NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- NVIC_Init(&NVIC_InitStructure);
- }

配置相应的中断函数,包括 DMA 中断函数和定时器 TIM 的中断函数,用于处理数据传输完成和定时器触发事件。
- void DMAC_CH1_2_Handler(void) // 40us
- {
- DMA_ClearITPendingBit(DMA1_FLAG_TC1);
- }
- void TIM3_Handler(void)
- {
- // 清除定时器中断标志位
- TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
-
- // 记录定时器中断触发次数
- TIM_interrupt_count++;
-
- // 每 10 次定时器中断触发,即 10ms,执行 ADC 转换
- if (TIM_interrupt_count == 10)
- {
- // 启动 ADC 转换
- ADC_StartOfConversion(ADC1);
-
- // 清零定时器中断计数
- TIM_interrupt_count = 0;
- }
-
-
- }

在主函数中完成初始化和程序的主要逻辑,美化语言以提高可读性,包括系统初始化、时钟配置、中断使能等。
- int main()
- {
- SystemInit();
- SetSysClock(); //主频配置
-
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_ADC, ENABLE); //开时钟
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOF, ENABLE);
- RCC_APB0PeriphClockCmd(RCC_APB0Periph_TIM3, ENABLE);
- NVIC_ClearPendingIRQ(DMAC_CH1_2_IRQn);
- NVIC_EnableIRQ(DMAC_CH1_2_IRQn); //使能DMA中断
-
- tim3_cfg_init();
- GPIOConfiguration(); // GPIO设置
- ADCConfiguration(); // ADC初始化
- // ADC_StartOfConversion(ADC1); // ADC 开始转换命令
- OLED_Init();
- OLED_ShowString(1,1,"helloworld task2");
- OLED_ShowString(2, 1, "ADValue:");
- while (1)
- {
- OLED_ShowNum(2, 9, ADCConvertedRawData[0], 4);
- delay10us(10000);
- }
- }

定义全局变量TimerPeriod、Channel1Pulse等。
- uint16_t TimerPeriod = 0; // 定时器周期
- uint16_t Channel4Pulse = 0; // 通道4的脉冲宽度
-
- unsigned int ADCConvertedRawData[] = {0}; // ADC转换后的原始数据数组
配置GPIO引脚,包括将PA0配置为模拟输入模式,PA11配置为TIM1_CH4的复用功能,以及PA12配置为输出模式。
- void GPIO_Config()
- {
- GPIO_InitTypeDef GPIO_InitStructure;
-
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_1); // PA0(AN1)=ADCIN[0]
-
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_2;
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_2); // tim1_ch4 pA11
-
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_2;
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
- GPIO_Init(GPIOA, &GPIO_InitStructure); //传输完后翻转测试
- }

初始化TIM1的基本设置和PWM输出通道4的设置。
- static void tim1_cfg_init()
- {
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
- TIM_OCInitTypeDef TIM_OCInitStructure;
-
- TimerPeriod = 800;
- Channel4Pulse = 792;
-
- /* Time Base configuration */
- TIM_TimeBaseStructure.TIM_Prescaler = 0;
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
- TIM_TimeBaseStructure.TIM_Period = TimerPeriod;
- TIM_TimeBaseStructure.TIM_ClockDivision = 0;
- TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
- TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
-
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
- TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
- TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
- TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
- TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
- TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
-
- TIM_OCInitStructure.TIM_Pulse = Channel4Pulse;
- TIM_OC4Init(TIM1, &TIM_OCInitStructure);
- TIM_CtrlPWMOutputs(TIM1, ENABLE);
-
- TIM_ITConfig(TIM1, TIM_DIER_TDE | TIM_DIER_CC4DE, ENABLE); // tim dma使能
- TIM_Cmd(TIM1, ENABLE);
- }

配置DMA1通道1,将ADC1的数据传输到ADCConvertedRawData数组中,并将DMA触发源设置为TIM1_CH4。
- void TIM_DMA_CFG(void)
- {
-
- DMA_InitTypeDef DMA_InitStructure;
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA, ENABLE);
-
- /*DMA1 channel1 configuration ----------------------------------------------*/
- DMA_DeInit(DMA1_Channel1);
- DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//用来设置DMA传输的外设基地址
-
- DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADCConvertedRawData; //内存基地址
- DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //设置数据传输方向,即决定是从外设读取数据到内存还是从内存读数据到外设,这里我们是从外设ADC_DR寄存器读数据到内存
- DMA_InitStructure.DMA_BufferSize = 1; //设置一次传输数据量的大小
- DMA_InitStructure.DMA_MSIZE = DMA_MSIZE_1; //突发事务传输长度
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //设置传输数据的时候 (外设) 地址是不变还是递增。
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; //设置传输数据的时候 (内存 )地址是不变还是递增
- DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; //设置外设的数据长度是为字节传输(8bits)还是半字(16bits)还是字传输(32bits)
- DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; //设置内存的数据长度,同上。
- DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //设置是否进行循环采集
- DMA_InitStructure.DMA_Priority = DMA_Priority_High; //设置优先级
- DMA_InitStructure.DMA_M2M = DMA_P2M_Enable;
- DMA_Init(DMA1_Channel1, &DMA_InitStructure); //设置是否是存储器到存储器模式传输
- /* Enable DMA1 channel1 TC interupt*/
- DMA_ITConfig(DMA1_Channel1, DMA1_FLAG_TC1, ENABLE); //配置DMA发送完成后中断
- DMA_RemapConfig(DMA1_Channel1, DMA_ReqNum5, DMA_REQ_TIM1_CH4);
- /* Enable DMA1 channel1 */
- DMA_Cmd(DMA1_Channel1, ENABLE);
-
-
- }

配置ADC1的转换模式、分辨率、转换通道等参数。
- void ADCConfiguration()
- {
- ADC_InitTypeDef ADC_InitStructure;
-
- ADC_InitStructure.ADC_ClkMode = ADC_ClockMode_SynClkDiv4; //选择转换时钟源
- ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //精度
- ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //转换模式0=单次;1=连续;表示触发一次转换一个通道或是一个序列
- ADC_InitStructure.ADC_AutoWatiMode = ENABLE; //该位由软件设置和清除来使开启 / 关闭自动延迟转换模式
- ADC_InitStructure.ADC_DMATRIG_LEVEL = FIFO_DMA_NOEMPTY; //软件配置触发 DMA 请求的 FIFO 水平。在 DMAEN 使能前进行配置,避免出现 DMA 在搬运时的错误。
- ADC_InitStructure.ADC_DMA = ENABLE; //是否允许用 DMA 控制器来自动管理转换的结果数据
- ADC_InitStructure.ADC_DiscMode = DISABLE; // 0=断续模式禁止;1=断续模式开启
- ADC_InitStructure.ADC_SamecMode = DISABLE; //采样模式选择0=连续采样;1=同时采样
- ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Falling; //触发方式
- ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC4; //触发源
- ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐方式
- ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Upward; // 0=向前扫描 (从 CHSEL0 到 CHSEL15);1=向后扫描( 从 CHSEL15 到 CHSEL0)
- ADC_InitStructure.ADC_Vref = ADC_Vref_Externa_VDD; //参考电压选择
- ADC_Init(ADC, &ADC_InitStructure);
-
- ADC_ChannelConfig(ADC, ADC_Channel_0, 15, 7, 7); //配置通道,CONVERT,ADJUST,SMP
-
- ADC_Cmd(ADC, ENABLE);
-
- }

初始化系统、时钟、GPIO、TIM1、DMA、ADC等模块。启动ADC转换。在循环中,通过OLED显示ADC转换结果,并添加延时。
- uint16_t ceshi;
- int main()
- {
- SystemInit();
- SetSysClock(); //主频配置
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM1, ENABLE);
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_ADC, ENABLE); //开时钟
-
- GPIO_Config(); // PWM GPIOSET
- TIM_DMA_CFG();
- tim1_cfg_init(); // TIM1 SET
-
- NVIC_ClearPendingIRQ(DMAC_CH1_2_IRQn);
- NVIC_EnableIRQ(DMAC_CH1_2_IRQn);
-
- ADCConfiguration();
- ADC_StartOfConversion(ADC1); // ADC 开始转换命令
- OLED_Init();
- OLED_ShowString(1,1,"hello world");
-
-
- while (1)
- {
- // ceshi = ADC_GetConversionValue(ADC1); //测试ADC
- // OLED_ShowNum(3,1,ceshi,4);
- OLED_ShowNum(2,1,ADCConvertedRawData[0],4);
- delay10us(100000);
-
- }
- }

清除DMA传输完成中断标志位。翻转PA12引脚的输出电平。
在DMA搬运完成中断中翻转IO口的目的是为了提供一个可视化的指示,以确认DMA搬运已经完成。这种操作可以作为一种调试手段,帮助验证DMA搬运是否按预期进行。
- void DMAC_CH1_2_Handler(void) // 40us
- {
- DMA_ClearITPendingBit(DMA1_FLAG_TC1);
-
- // 翻转PA12引脚的输出电平
- GPIO_BitToggle(GPIOA, GPIO_Pin_12);
- }
-
TIM触发ADC
由于任务1、2和3的实验效果相似,因此只展示了一个视频演示。
在这篇文章中,我们深入探索了STM32的ADC(模数转换器)的运作原理及其与DMA和TIM的协同工作方式。通过三个实验案例的详细解读,我们了解了如何使用STM32来实现不同的ADC应用场景。
在任务1中,我们学习了如何通过软件定时器触发ADC采样,从而检测电位器输出电压,并比对ADC转换值和实际电压。这一实验帮助我们优化了TIM的配置,并加深了对定时器溢出时间的理解。
在任务2中,我们探索了ADC和DMA的联动机制。通过定时10ms触发ADC采样,并通过DMA将采样结果搬运到数组,我们实现了高效的数据传输,并且学会了如何处理DMA搬运完成中断。
最后,在任务3中,我们使用TIM1作为PWM输出,利用OC4REF信号触发ADC采样,并通过T1_TRGO触发源实现下降沿触发采样。同时,我们使能了DMA,并在DMA搬运完成中断中翻转了一个IO口的输出电平。这一实验帮助我们深入理解了TIM1的精准触发功能以及DMA和ADC的协同工作方式。
提供的代码可能不适用于所有情况,但其逻辑结构值得借鉴。它演示了初始化定时器、配置PWM输出、触发ADC采样以及使用DMA搬运数据的基本步骤。理解这些步骤后,您可以根据自己的需求进行修改和优化,以满足特定的应用场景。
希望本文能对读者有所帮助,欢迎大家探索和实践!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。