序
关乎STM32,在使用官方库的情况下,你想要的各种功能的配置,无非就是你对于相应外设的每个参数的设置
库函数介绍
STM32的库函数,相关的一些参数,参数有效值,都可以在相关库文件** .c / .h **看到详细解释,就以ADC的初始化函数 ADC_Init 为例
/ **
* @brief根据ADC_InitStruct中的指定参数
* 初始化ADCx外设。
* @param ADCx:其中x可以是1、2或3以选择ADC外设。
* @param ADC_InitStruct:指向ADC_InitTypeDef结构的指针,该结构包含
* 指定ADC外设的配置信息。 *
* @retval无 *
* /
ADC_Init ( ADC_TypeDef * ADCx , ADC_InitTypeDef * ADC_InitStruct ){
uint32_t tmpreg1 = 0 ;
uint8_t tmpreg2 = 0 ;
/ *检查参数* /
assert_param (IS_ADC_ALL_PERIPH ( ADCx ));
assert_param (IS_ADC_MODE ( ADC_InitStruct- > ADC_Mode ));
assert_param (IS_FUNCTIONAL_STATE ( ADC_InitStruct- > ADC_ScanConvMode ));
assert_param (IS_FUNCTIONAL_STATE ( ADC_InitStruct- > ADC_ContinuousConvMode ));
assert_param (IS_ADC_EXT_TRIG (ADC_InitStruct- > ADC_ExternalTrigConv ));
assert_param (IS_ADC_DATA_ALIGN ( ADC_InitStruct- > ADC_DataAlign ));
assert_param (IS_ADC_REGULAR_LENGTH ( ADC_InitStruct- > ADC_NbrOfChannel ));
/ * ---------------------------- ADCx CR1配置----------------- * /
/ *获取使用ADCx CR1值* /
tmpreg1 = ADCx - > CR1 ;
/ *清除DUALMOD和SCAN位* /
tmpreg1 &==CR1_CLEAR_Mask ;
/ *配置ADCx:双模式和扫描转换模式* /
/ *根据ADC_Mode值设置DUALMOD位* /
/ *根据ADC_ScanConvMode值设置SCAN位* /
tmpreg1 | = ( uint32_t )( ADC_InitStruct- > ADC_Mode | (( uint32_t ) ADC_InitStruct- > ADC_ScanConvMode << 8 ));
/ *写入ADCx CR1 * /
ADCx - > CR1 = tmpreg1 ;
/ * ---------------------------- ADCx CR2配置----------------- * /
/ *获取使用ADCx CR2值* /
tmpreg1 = ADCx - > CR2 ;
/ *清除CONT,ALIGN和EXTSEL位* /
tmpreg1 &= CR2_CLEAR_Mask ;
/ *配置ADCx:外部触发事件和连续转换模式* /
/ *根据ADC_DataAlign值设置ALIGN位* /
/ *根据ADC_ExternalTrigConv值设置EXTSEL位* /
/ *根据ADC_ContinuousConvMode值设置CONT位* /
tmpreg1 | = ( uint32_t )( ADC_InitStruct- > ADC_DataAlign | ADC_InitStruct- > ADC_ExternalTrigConv | (( uint32_t ) ADC_InitStruct- > ADC_ContinuousConvMode << 1 ));
/ *写入ADCx CR2 * /
ADCx - > CR2 = tmpreg1 ;
/ * ---------------------------- ADCx SQR1配置----------------- * /
/ *获取使用ADCx SQR1值* /
tmpreg1 = ADCx - > SQR1 ;
/ *清除L位* /
tmpreg1 &= SQR1_CLEAR_Mask ;
/* Configure ADCx: regular channel sequence length */
/* Set L bits according to ADC_NbrOfChannel value */
tmpreg2 |= (uint8_t) (ADC_InitStruct->ADC_NbrOfChannel - (uint8_t)1);
tmpreg1 | = ( uint32_t ) tmpreg2 << 20 ;
/ *写入ADCx SQR1 * /
ADCx - > SQR1 = tmpreg1 ;
}
其中,有很多代码,但是无非就是根据你需要的功能,实际配置datasheet上面所写的adc相关的寄存器
在官方库函数,前面有几句注释,什么意思呢?
这个函数有两个参数,其中ADCx的意思是,你需要配置哪个ADC,比如:ADC1 或者 ADC2 ADC3;而ADC_InitStruct的意思是,你需要将外设的功能定义以ADC_InitTypeDef类型的结构体指针的方式传递过去。
这就不得不提到C语言的精髓, 结构体 & 指针
至于结构体,我自己的理解就是: 一个特殊的数组,只不过这个数组的成员之间的数据长度可能不太一样长,但是用法还有一些其他的东西,完全和数组类似,具体的解释,我会重新开一个博客来具体介绍(本人有关C语言中结构体一点不简单的理解),这里就先谈到这里。
简而言之,就是,你需要先定义一个保存很多数据的数据类型,来将你的对于这个ADC的使用功能告诉给这函数,然后,他来一条一条分析你这个数据类型的每个数据,当然,你也可以把这些参数一个一个的加在库函数的输入参数里面,比如说:
ADC_Mode,ADC_ScanConvMode,ADC_ContinuousConvMode,ADC_ExternalTrigConv,ADC_DataAlign,ADC_NbrOfChannel
相对于的,在库函数里,有相同的参数来调取和使用他们,当然,万一输入的值,可能和规定的值不一样怎么办,别担心,ST官方库也在函数中有输入参数有效性判断函数:
assert_param(IS_ADC_MODE(ADC_InitStruct->ADC_Mode));
assert_param(IS_FUNCTIONAL_STATE(ADC_InitStruct->ADC_ScanConvMode));
assert_param(IS_FUNCTIONAL_STATE(ADC_InitStruct->ADC_ContinuousConvMode));
assert_param(IS_ADC_EXT_TRIG(ADC_InitStruct->ADC_ExternalTrigConv));
assert_param(IS_ADC_DATA_ALIGN(ADC_InitStruct->ADC_DataAlign));
assert_param(IS_ADC_REGULAR_LENGTH(ADC_InitStruct->ADC_NbrOfChannel));
随后,检查完参数,函数就会根据你所输入的参数对相关寄存器进行赋值。
至于说这个保存很多数据的数据类型,在库里面,人家也很贴心的给你准备了(.h文件)
typedef struct
{
uint32_t ADC_Mode; /*!< Configures the ADC to operate in independent or dual mode.
This parameter can be a value of @ref ADC_mode */
FunctionalState ADC_ScanConvMode; /*!< Specifies whether the conversion is performed in
Scan (multichannels) or Single (one channel) mode.
This parameter can be set to ENABLE or DISABLE */
FunctionalState ADC_ContinuousConvMode; /*!< Specifies whether the conversion is performed in Continuous or Single mode.
This parameter can be set to ENABLE or DISABLE. */
uint32_t ADC_ExternalTrigConv; /*!< Defines the external trigger used to start the analog to digital conversion of regular channels. This parameter
can be a value of @ref ADC_external_trigger_sources_for_regular_channels_conversion */
uint32_t ADC_DataAlign; /*!< Specifies whether the ADC data alignment is left or right.
This parameter can be a value of @ref ADC_data_align */
uint8_t ADC_NbrOfChannel; /*!< Specifies the number of ADC channels that will be converted using the sequencer for regular channel group.
This parameter must range from 1 to 16. */
}ADC_InitTypeDef;
所以,一切的一切,在库函数你都可以找到相关的说明,接下来,你就会问了,那这些参数,我要怎么配置,才会让程序来识别我想要的功能,Look down:
这时候,你只需要在.h文件里面继续往下翻就会看到
#define ADC_Mode_Independent ((uint32_t)0x00000000)
#define ADC_Mode_RegInjecSimult ((uint32_t)0x00010000)
#define ADC_Mode_RegSimult_AlterTrig ((uint32_t)0x00020000)
#define ADC_Mode_InjecSimult_FastInterl ((uint32_t)0x00030000)
#define ADC_Mode_InjecSimult_SlowInterl ((uint32_t)0x00040000)
#define ADC_Mode_InjecSimult ((uint32_t)0x00050000)
#define ADC_Mode_RegSimult ((uint32_t)0x00060000)
#define ADC_Mode_FastInterl ((uint32_t)0x00070000)
#define ADC_Mode_SlowInterl ((uint32_t)0x00080000)
#define ADC_Mode_AlterTrig((uint32_t)0x00090000)
……
OK,介绍了这么多,咱们开始步入正题:
初始化函数配置
首先,你得知道你要使用的是哪个ADC引脚?至于引脚配置,在官方的芯片手册中第三节的引脚功能定义中找到。
图中可以看到,ADC12_IN10在PC0引脚,ADC12_IN11在PC1引脚.。。。
其次,将你需要使用的引脚进行初始化,记得把引脚配置为 模拟输入(GPIO_Mode_AIN)
以ADC12_IN10 / ADC12_IN11 / ADC12_IN12 / ADC12_IN13为例
void GPIOC_Init(){ //GPIO引脚初始化
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //使能外设时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //输出模式配置,模拟输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOC, &GPIO_InitStructure); //根据设定参数初始化GPIOC
}
然后,就是对你所需要的ADC功能的配置了
Void ADC1_Init(){ //ADC1初始化
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //使能外设时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC的分频因子,APB2的6分频 72M/6 = 12M,如果ADC频率超过12MHz,可能导致采样值误差
ADC_DeInit(ADC1); //设置ADC1为默认值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //独立模式,此模式表示只是单独使用ADC1,不与ADC2进行联合使用
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //开启扫描 扫描模式下,才可以连续采集信号
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //软件触发,需要调用库函数才可以使用
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 4; //顺序进行转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_55Cycles5 ); //ADC规则通道配置
ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 2, ADC_SampleTime_55Cycles5 ); //ADC规则通道配置
ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 3, ADC_SampleTime_55Cycles5 ); //ADC规则通道配置
ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 4, ADC_SampleTime_55Cycles5 ); //ADC规则通道配置
ADC_Cmd(ADC1, ENABLE); //使能ADC1
ADC_ResetCalibration(ADC1); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待ADC校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待AD校准结束
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //进行一次采集
/* 等待转换结束 */
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
}
至于具体每个参数的作用,可以参考这个博文:STM32之ADC配置,ADC_Mode模式理解
OK,完成了ADC配置,你还需要创建一个数组,以保证ADC采集到的数据可以有存储的地方
/* 转换值 */
unsigned int adc_data[4];
这个建议做一个全局变量,以方便被其他文件调取使用
你已经有了采集的接口,存放采集数据的地方,最后,你要做的就是为他们两者之间建立一个通道,这就是DMA存在的意义
STM32共有两个DMA控制器,其中DMA1共有7个通道,DMA2有5个通道,在参考手册中DMA章节有具体说明
这次我们使用的是ADC1的传输,所以,需要选择DMA1的通道1
void DMA1_Init(void)
{
DMA_InitTypeDef DMA_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA外设时钟
DMA_DeInit(DMA1_Channel1); /将DMA1通道1重设为默认值
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//外设基地址
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)&adc_data;//存储器基地址
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向 外设到存储器
DMA_InitStruct.DMA_BufferSize = 4; //通道传输数据量
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //不开启外设增量模式
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //开启储存器地址增量模式
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据长度(16位)
DMA_InitStruct.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord; //存储器数据长度
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; //传输是否循环 不循环
DMA_InitStruct.DMA_Priority = DMA_Priority_Medium; //DMA优先级 中等
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //是否存储器到存储器方式 不是
DMA_Init(DMA_CHx, &DMA_InitStruct);
DMA_Cmd(DMA1_Channel1, ENABLE); //使能DMA1的通道1传输
}
最后的最后,就是搞一个main函数,将各种初始化集合在一起使用
int main(void)
{
GPIOC_Init(); //GPIO配置
DMA1_Init(); //DMA1配置
ADC1_Init(); //ADC1配置
while(1)
{
……
}
}