STM32常见外设初始化步骤详解(GPIO、EXTI、TIM、ADC)
害,真的是忙里偷闲啊,这段时间临近multiple ddls,好久没更新了,水一篇stm32的。
几种常用外设的初始化示例
也是顺便存个档
GPIO
void my_GPIO_Init()
{
/* GPIO */
//RCC开启GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//结构体初始化GPIO
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_3; // 开启引脚
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 所选pin脚的速度
GPIO_Init(GPIOA, &GPIO_InitStruct);
}
EXTI
中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更紧急的中断源
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
Extern Interrupt-外部中断
EXTI可以监测指定GPIO口的电平信号,电平变化时EXTI立即向NVIC发出中毒那申请,经NVIC裁决发送CPU。即引脚电平变化——》申请中断
支持的触发方式:上升沿/下降沿/双边沿/软件触发
支持的GPIO口:all,但是相同pin口不能同时触发中断(GPIOA和GPIOB的pin3口不能同时触发)
通道数:16个GPIO_Pin,+PVD输出,RTC闹钟,USB唤醒,以太网唤醒
触发响应方式:中断响应(触发CPU)/事件响应(触发别的外设)
中断执行流程:
中断代码封装在一个子函数里,由硬件在符合中断条件的时候自动调用,不需要我们在代码中调用。
void my_EXTI_Init()
{
/* RCC */
//开启GPIOB和AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
/* GPIO */
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
/* AFIO */
// 配置中断源
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
/* EXTI */
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line = EXTI_Line14; // EXTI的14th通道
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; // 配置成中断触发
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
EXTI_InitStruct.EXTI_LineCmd = ENABLE; // 开启
EXTI_Init(&EXTI_InitStruct);
/* NVIC */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 优先级分组
NVIC_InitTypeDef NVIC_InitStruct; // 结构体初始化NVIC
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn; // 选择中断请求通道
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // 使能
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; // 配置抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; // 配置响应优先级
NVIC_Init(&NVIC_InitStruct);
}
TIM(普通定时器)
TIM(Timer)定时器
定时器可以对输入的时钟进行计数(stm32主频72Mhz,如果对72MHz计72000个数,那就是1ms的时间),并在计数器的数值达到设定值时触发中断
components of TIM: 时基单位:
16位计数器(CNT-Counter) – 每来一个时钟,计数器+1 ————0~65535
预分频器(PSC-Prescaler) – 对计数器时钟进行分频,flexible
*自动重装寄存器(ARR-AutoReloadRigister) – 计数的目标值
under72MHz下实现最大59.95s的计时 ———— = 72MHz / 65536 / 65536
TIM计算公式:
- 计数器CNT计数频率:CK_CNT=CK_PSC/(PSC+1) % 计算单位计时时间
- 计数器溢出频率:CK_CNT_OV = CK_PSC / (PSC+1) / (ARR+1) % 计算定时时间
void my_TIM_Init()
{
/* RCC */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 开启TIM2时钟
/* TIM2 */
TIM_InternalClockConfig(TIM2); // TIM2使用内部时钟
// 时基单元初始化
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
// By CK_CNT_OV = CK_PSC / (PSC+1) / (ARR+1)
TIM_TimeBaseInitStruct.TIM_Period = 10000 - 1; // ARR 自动重装寄存器
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1; // RSC分频 -> 10kHz
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; // 高级TIM才用
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
// 清除更新中断位 -> 不然TIM更新事件结束就会进入中断
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
// 使能TIM_IT -> NVIC
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
/* NVIC */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct; // 优先级分组
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn; // 选择TIM2中断请求通道
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
/* 使能TIM2 */
TIM_Cmd(TIM2, ENABLE);
}
TIM OC
OC(Output Compare)输出比较 -> 一般用于输出PWM波
输出比较可以通过比较CNT与CCR寄存器(Compare Capture Rigister)值得关系,来对输出电平进行置1、置0或者翻转得操作,用于输出有点频率和占空比得PWM波型
每个高级定时器和通用定时器都拥有4个输出比较通道
高级定时器得前3个通道额外拥有死去生成和互补输出得功能
void my_PWM_Init()
{
/* ¿ªÆôRCCʱÖÓ */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 开启TIM2时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 开启GPIOA时钟
// TIM-OC重映射
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
/* GPIO */
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出,片上外设-》输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
/* TIM2 */
TIM_InternalClockConfig(TIM2); // TIM2使用内部时钟
// 时基单元初始化
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
// By CK_CNT_OV = CK_PSC / (PSC+1) / (ARR+1)
TIM_TimeBaseInitStruct.TIM_Period = 720- 1; // ARR 自动重装寄存器
TIM_TimeBaseInitStruct.TIM_Prescaler = 1 - 1; // RSC分频
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; // 高级TIM才用
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
// 清除更新中断位 -> 不然TIM更新事件结束就会进入中断
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
// 使能TIM_IT -> NVIC
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
/* TIM OC */
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct); // 结构体参数中部分参数不使用,为结构体赋初始值
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; // 输出模式为PWM1
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; // HIGH极性不翻转,ref直接输出
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; // output ENA
TIM_OCInitStruct.TIM_Pulse = 50; // set CCR-> dutyCycle = CCR/(ARR+1)
TIM_OC1Init(TIM2, &TIM_OCInitStruct);
/* ¿ªÆôTIM2ʱÖÓ */
TIM_Cmd(TIM2, ENABLE);
}
TIM IC
对于同一个定时器,因为引脚共用OC功能与IC功能同时只能启用一个
输入捕获模式下,当通道输入引脚出现指定电平跳变,当前CNT的值将被锁存到CCR中:
—>可用于测量PWM波型的Freq,Duty,脉冲间隔,电平持续时间等参数
每个高级定时器和通用定时器都拥有4个输入捕获通道
可配置为PWMI模式,同时测量Freq和duty(两通道同时捕获一个引脚)
可配合主从触发模式,实现硬件全自动测量
TIPS:这俩配合起来,可以实现硬件全自动测量PWM
主从触发模式:
- 主模式(Master Mode):可以将定时器内部的信号,映射到TRGO引脚。用于触发其他外设
- 从模式(Slave Mode):用于接受其他外设或者自身外设的一些信号,用于控制自身定时器的运行,从模式可以在从模式列表中,选择一项操作自动执行。即被别的信号触发
- 触发源选择(Trigger Selection):用于选择从模式的触发信号源,选择指定的一个信号,得到TRGI,TRGI去触发从模式。
可以实现对输入PWM波的硬件端自动测量
void my_IC_Init(void)
{
/* RCC */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* GPIO */
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
/* TIM */
TIM_InternalClockConfig(TIM3);
// Init
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
// By CK_CNT_OV = CK_PSC / (PSC+1) / (ARR+1)
TIM_TimeBaseInitStruct.TIM_Period = 65536 - 1; // ARR - allow full CNT range (affect Lowest Bound)
TIM_TimeBaseInitStruct.TIM_Prescaler = (FSYS / FMeasure)- 1; // PSC - f_c = 1MHz
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
/* TIM_IC */
TIM_ICInitTypeDef TIM_ICInitStruct;
TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
TIM_ICInitStruct.TIM_ICFilter = 0xF;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI; // Config data Selector
TIM_ICInit(TIM3, &TIM_ICInitStruct);
/* Master-Slave Config*/
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); // Set TRGI
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); // Choose slave mode
TIM_Cmd(TIM3, ENABLE);
}
void PWMI_Init(void)
{
/* RCC */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* GPIO */
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
/* TIM */
TIM_InternalClockConfig(TIM3);
// Init
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
// By CK_CNT_OV = CK_PSC / (PSC+1) / (ARR+1)
TIM_TimeBaseInitStruct.TIM_Period = 65536 - 1; // ARR - allow full CNT range (affect Lowest Bound)
TIM_TimeBaseInitStruct.TIM_Prescaler = (FSYS / FMeasure)- 1; // PSC - f_c = 1MHz
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
/* TIM_IC */
TIM_ICInitTypeDef TIM_ICInitStruct;
TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
TIM_ICInitStruct.TIM_ICFilter = 0xF;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI; // Config data Selector
// Config another channel
TIM_PWMIConfig(TIM3, &TIM_ICInitStruct);
/* Master-Slave Config*/
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); // Set TRGI
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); // Choose slave mode
TIM_Cmd(TIM3, ENABLE);
}
ADC
ADC(Analog-Digital Converter)模拟-数字转换器
ADC可以讲引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。
12位逐次逼近型ADC,1us转换时间(1MHz)
输入电压范围:03.3V,转换结果范围:04095
18个输入通道,可测量16个外部和2个内部信号源
规则组和注入组两个转换单元
模拟看门狗自动检测输入电压范围
逐次逼近型ADC
以典型ADC芯片ADC0809为例
通过比较器、DAC、逐次逼近寄存器SAR:
原理是从高位到低位逐位比较,首先将缓冲寄存器各位清零;转换开始后,先将寄存器最高位置1,把值送入D/A转换器,经D/A转换后的模拟量送入比较器,称为 Vo,与比较器的待转换的模拟量Vi比较,若Vo<Vi,该位被保留,否则被清0。然后,再置寄存器次高位为1,将寄存器中新的数字量送D/A转换器,输出的 Vo再与Vi比较,若Vo<Vi,该位被保留,否则被清0。循环此过程,直到寄存器最低位,得到数字量的输出。
void my_AD_Init(void)
{
/* RCC */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // Config ADC
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // Config ADCCLK(only 6 or 8)
/* GPIO */
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; // Analog IN (only for ADC)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
/* Regular Channel */
// channel 1 - rank 1 - Sampling time:55.5 ADC Periods
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
/* ADC */
ADC_InitTypeDef ADC_InitStruct;
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // ext_trg_src_for_regular_channels_conversion -> use software trigger
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;
ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; // Continuous or Single mode -> Single
ADC_InitStruct.ADC_ScanConvMode = DISABLE; //Scan (multichannels) or Single (one channel) mode -> Single (one channel)
ADC_InitStruct.ADC_NbrOfChannel = 1; // number of ADC channels(if Single mode lose effect)
ADC_Init(ADC1, &ADC_InitStruct); //
//if need
/* IT | AWD */
ADC_Cmd(ADC1, ENABLE);
/* Calibration-У */
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1) == SET); // Wait till done calibration registers RESET
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1) == SET); // Wait till done calibration
}
作者:孩子饿饿想被v50吃疯狂星期四