stm32基本外设使用总结

一、GPIO

二、串口

三、ADC和DMA

四、TIM定时器

五、bootloader升级

 

一、GPIO

  

8种工作模式:

浮空输入:上拉电阻和下拉电阻都断开,引脚电平完全由外部状态决定;

上拉输入:内部上拉电阻接VDD,外部引脚没输入时,默认高电平,外部引脚输入低电压时,呈现低电平;

下拉输入:内部下拉电阻接VSS,外部引脚没输入时,默认低电平,外部引脚接高电压时,呈现高电平;

模拟输入:TTL肖特基触发器断开,外部输入直接通到内部,比如ADC模块;

通用开漏输出:保持P-MOS断开,向引脚写0,下面的N-MOS管导通,对外输出低电平;向引脚写1,下面的N-MOS管断开,此时IO引脚悬空,IO引脚电流为0,无论外部加多大的电压,电流都为0,对外呈现高阻态;

通用推挽输出:向引脚写0,下面的N-MOS管导通,上面的P-MOS管断开,对外输出低电平;向引脚写1,上面的P-MOS管导通,下面的N-MOS管断开,对外输出高电平;

复用开漏输出:引脚给其他模块控制,控制属性和通用开漏输出一样;

复用推挽输出:引脚给其他模块控制,控制属性和通用推挽输出一样;

复用:将IO引脚交给芯片的其他模块控制;通用:直接写寄存器控制IO引脚;

将一个GPIO配置为中断引脚的代码如下:

/* 1、使能NVIC中断控制器的中断通道 */
/* 定义一个 NVIC 结构体 */
NVIC_InitTypeDef nvic_initstruct = {0};
    
/* 开启 AFIO 相关的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

/* 配置中断源 */
nvic_initstruct.NVIC_IRQChannel                     = EXTI0_IRQn;
/* 配置抢占优先级 */
nvic_initstruct.NVIC_IRQChannelPreemptionPriority   =  1;
/* 配置子优先级 */
nvic_initstruct.NVIC_IRQChannelSubPriority          =  0;
/* 使能配置中断通道 */
nvic_initstruct.NVIC_IRQChannelCmd                  =  ENABLE;

NVIC_Init(&nvic_initstruct);
/* 配置引脚为浮空输入状态 */
/* 定义一个 GPIO 结构体 */
GPIO_InitTypeDef gpio_initstruct = {0};

 开启 KEY 相关的GPIO外设/端口时钟 */
RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK_PORT,ENABLE);
    
/* IO输出状态初始化控制 */
GPIO_SetBits(KEY1_GPIO_PORT,KEY1_GPIO_PIN);
    
/*选择要控制的GPIO引脚、设置GPIO模式为 浮空输入、设置GPIO速率为50MHz*/
gpio_initstruct.GPIO_Pin    = KEY1_GPIO_PIN;
gpio_initstruct.GPIO_Mode   = GPIO_Mode_IN_FLOATING;
gpio_initstruct.GPIO_Speed  = GPIO_Speed_50MHz;
GPIO_Init(KEY1_GPIO_PORT,&gpio_initstruct);
/* 配置GPIO到NVIC的中断线,包括触发模式和使能 */
/* 定义一个 EXTI 结构体 */
EXTI_InitTypeDef exti_initstruct = {0};
   
/* 开启 AFIO 相关的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

/* 选择中断信号源*/   GPIO_EXTILineConfig(KEY1_EXTI_PORTSOURCE,KEY1_EXTI_PINSOURCE);
    
/* 选择中断LINE */
exti_initstruct.EXTI_Line       = EXTI_Line0;
/* 选择中断模式*/
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 结构体 */
NVIC_InitTypeDef nvic_initstruct = {0};
    
/* 开启 AFIO 相关的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); 
      
/* 配置中断源 */
nvic_initstruct.NVIC_IRQChannel                     = DEBUG_IRQ;
/* 配置抢占优先级 */
nvic_initstruct.NVIC_IRQChannelPreemptionPriority   =  1;
/* 配置子优先级 */
nvic_initstruct.NVIC_IRQChannelSubPriority          =  0;
/* 使能配置中断通道 */
nvic_initstruct.NVIC_IRQChannelCmd                  =  ENABLE;

NVIC_Init(&nvic_initstruct);
/* 配置串口的波特率和数据帧格式 */
/* 定义一个 USART 结构体 */
USART_InitTypeDef usart_initstruct = {0};
   
/* 开启 DEBUG 相关的GPIO外设/端口时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    
/* 配置串口的工作参数 */
usart_initstruct.USART_BaudRate                 = 115200;
usart_initstruct.USART_HardwareFlowControl      = USART_HardwareFlowControl_None;
usart_initstruct.USART_Mode                     = USART_Mode_Tx|USART_Mode_Rx;
usart_initstruct.USART_Parity                   = USART_Parity_No;
usart_initstruct.USART_StopBits                 = USART_StopBits_1;
usart_initstruct.USART_WordLength               = USART_WordLength_8b;

USART_Init(USART1,&usart_initstruct);
USART_ITConfig(DEBUG_USARTX,USART_IT_RXNE,ENABLE);//开启串口数据接收中断
/* 将两个引脚分别配置为复用推挽输出和上拉输入模式 */
/* 定义一个 GPIO 结构体 */
GPIO_InitTypeDef gpio_initstruct = {0};
   
/* 开启 DEBUG 相关的GPIO外设/端口时钟 */    RCC_APB2PeriphClockCmd(DEBUG_TX_GPIO_CLK_PORT,ENABLE);
/*选择要控制的GPIO引脚、设置GPIO模式为 推挽复用、设置GPIO速率为50MHz*/
gpio_initstruct.GPIO_Pin    = DEBUG_TX_GPIO_PIN;
gpio_initstruct.GPIO_Mode   = GPIO_Mode_AF_PP;
gpio_initstruct.GPIO_Speed  = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_TX_GPIO_PORT,&gpio_initstruct);

/* 开启 DEBUG 相关的GPIO外设/端口时钟 */
RCC_APB2PeriphClockCmd(DEBUG_RX_GPIO_CLK_PORT,ENABLE);

/*选择要控制的GPIO引脚、设置GPIO模式为 上拉输入/浮空输入、设置GPIO速率为50MHz*/
gpio_initstruct.GPIO_Mode   = GPIO_Mode_IPU;
gpio_initstruct.GPIO_Pin    = DEBUG_RX_GPIO_PIN;
gpio_initstruct.GPIO_Speed  = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_RX_GPIO_PORT,&gpio_initstruct);

/* 使能串口 */
USART_Cmd(USART1,ENABLE);

三、ADC和DMA

  使用ADC1采样GPIO的模拟输入,ADC1只使用单通道,独立模式;使用软件触发ADC转换的模式,转换完成后,ADC1会触发DMA请求,DMA将转换结果搬移到SRAM;软件周期性的触发ADC转换;

/* 配置ADC1为独立单通道模式,取消连续采样,ADC时钟设置为PLL的8分频,也就是9MB,每次采用周期设置为最大239个周期 */
/* ADC控制器初始化 */
/* 定义一个ADC结构体 */
ADC_InitTypeDef adc_initstruct = {0};
    
/* 开启ADC相关的GPIO外设/端口时钟 */
ADCX_APBXCLKCMD(ADCX_CLK_PORT, ENABLE);
    
/* ADC 模式配置 */
adc_initstruct.ADC_Mode = ADC_Mode_Independent;                     //只有一个ADC,属于独立模式
adc_initstruct.ADC_ScanConvMode = ENABLE;                           //开启扫描模式,单通道不需要
adc_initstruct.ADC_ContinuousConvMode =  DISABLE;                   //取消连续扫描模式
adc_initstruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;    //不需要外部触发转换,使用软件开启
adc_initstruct.ADC_DataAlign = ADC_DataAlign_Right;                 //转换结构右对齐
adc_initstruct.ADC_NbrOfChannel = ADCX_CHANNEL_NUM;
    
ADC_Init(ADCX,&adc_initstruct);                       //初始化ADC 
    
/* ADC的转化配置 */
RCC_ADCCLKConfig(RCC_PCLK2_Div8);                                   //配置ADC的时钟为PLL2的8分频,9MHz
        ADC_RegularChannelConfig(ADCX,POTENTIOMETER_ADC_CHANNEL,1,ADC_SampleTime_239Cycles5);    //配置ADC通道的采样顺序和时间
ADC_Cmd(ADCX,ENABLE);                                 //开启ADC转换
    
/* 进行校准 */
ADC_ResetCalibration(ADCX);                   //选择需要校准的ADC初始化
while(ADC_GetResetCalibrationStatus(ADCX));   //等待校准初始化完成
ADC_StartCalibration(ADCX);                   //开始校准
while(ADC_GetCalibrationStatus(ADCX));        //等待校准完成
/* 配置DMA从ADC1转换结果寄存器到SRAM搬移数据 */
/* 定义一个DMA结构体 */
DMA_InitTypeDef dma_initstructure = {0};

/*开启ADC_DMA相关的DMA外设/端口时钟*/
RCC_AHBPeriphClockCmd(ADCX_DMA_CLK_PORT,ENABLE);

/*复位DMA控制器*/
DMA_DeInit(ADCX_DMA_CHANNEL);
    
dma_initstructure.DMA_PeripheralBaseAddr = ADC1_DR_ADDRESS;                         //外设基地址
dma_initstructure.DMA_MemoryBaseAddr = (uint32_t)&adc_source_convertedvalue;        //AD转换值所存放的内存基地址
dma_initstructure.DMA_DIR = DMA_DIR_PeripheralSRC;                                  //外设作为数据传输的来源
dma_initstructure.DMA_BufferSize = ADCX_CHANNEL_NUM;                                //定义指定DMA通道 DMA缓存的大小
dma_initstructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                    //外设地址寄存器不变
dma_initstructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                             //内存地址寄存器递增
dma_initstructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;         //数据位宽16
dma_initstructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;                 //数据位宽16
dma_initstructure.DMA_Mode = DMA_Mode_Normal;                                       //工作模式
dma_initstructure.DMA_Priority = DMA_Priority_High;                                 //高优先级
dma_initstructure.DMA_M2M = DMA_M2M_Disable;                                        //禁止内存到内存
DMA_Init(ADCX_DMA_CHANNEL,&dma_initstructure);                       //初始化ADC
    
DMA_ITConfig(ADCX_DMA_CHANNEL,DMA_IT_TC,ENABLE);                 //使能注入转换完成中断,用于读取转换值
/* GPIO设置为模拟输入 */
/* 定义一个GPIO结构体 */
GPIO_InitTypeDef gpio_initstruct = {0};

/* 开启POTENTIOMETER相关的GPIO外设/端口时钟 */   
RCC_APB2PeriphClockCmd(POTENTIOMETER_SIG_GPIO_CLK_PORT,ENABLE);
/*选择要控制的GPIO引脚、设置GPIO模式为模拟输入、设置GPIO速率为50MHz*/ gpio_initstruct.GPIO_Mode = GPIO_Mode_AIN; gpio_initstruct.GPIO_Pin = POTENTIOMETER_SIG_GPIO_PIN; gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(POTENTIOMETER_SIG_GPIO_PORT,&gpio_initstruct);
/* NVIC使能DMA中断 */
/* 定义一个中断控制器结构体 */
NVIC_InitTypeDef nvic_initstructure;

// 配置中断优先级
nvic_initstructure.NVIC_IRQChannel = ADCX_INT_DMA_IRQ;
nvic_initstructure.NVIC_IRQChannelPreemptionPriority = 1;
nvic_initstructure.NVIC_IRQChannelSubPriority = 0;
nvic_initstructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic_initstructure);
/* FreeRTOS创建一个任务周期性开启ADC转换 */
DMA_Cmd(ADCX_DMA_CHANNEL,ENABLE);                   //使能DMA
ADC_DMACmd(ADCX,ENABLE);                            //使能DMA请求
ADC_SoftwareStartConvCmd(ADCX,ENABLE);              //由于没有采用外部触发,所以配置软件触发ADC转换
/* 转换完成,触发DMA完成数据搬移后,触发DMA中断,在中断里面设置ADC转换停止,停止DMA请求,重新设置每次DMA传输的数据量,清除DMA中断标志位 */
ADC_SoftwareStartConvCmd(ADCX,DISABLE);             //由于没有采用外部触发,所以配置软件触发ADC转换
DMA_Cmd(ADCX_DMA_CHANNEL,DISABLE);                   //使能DMA
ADC_DMACmd(ADCX,DISABLE);                            //使能DMA请求
DMA_SetCurrDataCounter(ADCX_DMA_CHANNEL,ADCX_CHANNEL_NUM);//重新设置DMA传输计数值,必须在DMA失能下进行
DMA_ClearITPendingBit(DMA1_IT_TC1);

四、TIM定时器

  配置TIM3_CH2输出PWM波形,对应GPIO引脚是PB5;配置TIM4_CH3为输入捕获模式,对应GPIO引脚是PB8,将PB5输出的PWM波形连接到PB8,那么就可以用TIM4_CH3测试PWM的周期;

/* 配置TIM3_CH2输出PWM波形 */
/* PB5引脚配置重映射到TIM3_CH2 */
/* 定义一个GPIO结构体 */
GPIO_InitTypeDef gpio_initstruct = {0};

/* 开启LED相关的GPIO外设/端口时钟 */
RCC_APB2PeriphClockCmd(W_LED_GPIO_CLK_PORT,ENABLE);
    
/* 开启重映射 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
    
/* 初始化io状态 */
GPIO_SetBits(W_LED_GPIO_PORT, W_LED_GPIO_PIN);

/*选择要控制的GPIO引脚、设置GPIO模式为 复用推挽输出、设置GPIO速率为50MHz*/
gpio_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
gpio_initstruct.GPIO_Pin = W_LED_GPIO_PIN;
gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(W_LED_GPIO_PORT,&gpio_initstruct);

/* 输入定时器的时钟是72MB,配置TIM3_CH2的预分频寄存器为72-1,重装载寄存器为1000-1,这样定时器的计数器可以计数1000次,周期是1ms;配置为PWM模式1,使能通道2 *、
/* 定义一个 GENERALTIM 结构体 */
TIM_TimeBaseInitTypeDef tim_timebaseinitstruct = {0};
    
/* 定义一个 PWM输出配置 结构体 */
TIM_OCInitTypeDef  tim_ocinitstructure;    
    
/* 开启 GENERALTIM 相关的GPIO外设/端口时钟 */
GENERAL_TIM_APBXCLKCMD(RCC_APB1Periph_TIM3,ENABLE);
     
/* 通用定时器配置 */
tim_timebaseinitstruct.TIM_Period               = PWM_LED_PERIOD;           // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
tim_timebaseinitstruct.TIM_Prescaler            = (72-1);                   // 设置预分频----1us
tim_timebaseinitstruct.TIM_ClockDivision        = TIM_CKD_DIV1;             //设置时钟分频系数:不分频(这里用不到)
tim_timebaseinitstruct.TIM_CounterMode          = TIM_CounterMode_Up;       //向上计数模式
//    tim_timebaseinitstruct.TIM_RepetitionCounter    = 0;                      //重复计数器的值,没用到不用管
TIM_TimeBaseInit(PWM_LED_TIM,&tim_timebaseinitstruct);// 初始化定时器
      
//例如向上计数时
//PWM模式1下,TIMx_CNT<TIMx_CCRn时,输出有效电平
//            TIMx_CNT>TIMx_CCRn时,输出无效电平 
//PWM模式2下,TIMx_CNT<TIMx_CCRn时,输出无效电平
//            TIMx_CNT>TIMx_CCRn时,输出有效电平
    
/* PWM模式配置 */
tim_ocinitstructure.TIM_OCMode = TIM_OCMode_PWM1;                //配置为PWM模式1
tim_ocinitstructure.TIM_OutputState = TIM_OutputState_Enable;    //使能输出
tim_ocinitstructure.TIM_Pulse = PWM_LED_PULSE;                    //设置初始PWM脉冲宽度    
tim_ocinitstructure.TIM_OCPolarity = TIM_OCPolarity_Low;          //当定时器计数值小于CCR_Val时为低电平

//使能通道2和预装载
TIM_OC2Init(PWM_LED_TIM, &tim_ocinitstructure);                                 
TIM_OC2PreloadConfig(PWM_LED_TIM, TIM_OCPreload_Enable);                        
TIM_ARRPreloadConfig(PWM_LED_TIM, ENABLE);//使能重载寄存器ARR
/* 使能 TIM */
TIM_Cmd(PWM_LED_TIM,ENABLE);

/* 周期性改变PWM的占空比 */
TIM_SetAutoreload(PWM_LED_TIM, pwm_cycle); //自动重装载寄存器
TIM_SetCompare2(PWM_LED_TIM, pwm_pulse); //CCR_Val寄存器,计数值小于这个就输出低电平,否则输出高电平
/* 配置TIM4_CH3为输入捕获模式,对应GPIO引脚是PB8 */
/* PB8设置为浮空输入模式 */
gpio_InitIntput(RCC_APB2Periph_GPIOB, GPIOB, GPIO_Pin_8, GPIO_Mode_IN_FLOATING);
    
/* 打开TIM4的中断 */
NVIC_InitTypeDef NVIC_InitStructure;

/* Enable the TIM4 global Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
    
/* 设置TIM4的CH3为输入捕获模式 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
    
TIM_ICInitTypeDef  TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_3;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x0;

TIM_ICInit(TIM4, &TIM_ICInitStructure);
  
/* TIM enable counter */
TIM_Cmd(TIM4, ENABLE);

/* Enable the CC3 Interrupt Request */
TIM_ITConfig(TIM4, TIM_IT_CC3, ENABLE);

/* 捕获到上升沿会触发TIM4定时器中断 */
void TIM4_IRQHandler(void)

五、bootloader升级

  实现原理:把bootloader和应用程序APP分成两个工程实现,bootlaoder加载在FLASH起始地址0x08000000的地方,应用程序APP加载在FLASH的另外一个地方,比如0x08008000;bootloader通过跳转到应用程序的复位中断向量执行函数从而跳转到应用程序APP执行(复位中断向量函数在程序文件起始偏移4字节的地方,bootloader的复位向量地址是0x080000004,应用程序的复位向量地址是0x08008004);

/* Bootloader程序跳转应用程序的代码 */
#define APP_ADDR                                        0x08008000        //自定义的APP程序头地址
typedef void (*pFunction)(void);        //函数指针,用来调用同一类型的函数

/*****************************************************************************
[函数名称]BootLoader_JumpToApp
[函数功能]BootLoader跳转到APP函数
[参    数]app_addr:APP程序入口地址
*****************************************************************************/
void BootLoader_JumpToApp (uint32_t app_addr)
{
    pFunction jumo_to_application;        //跳转函数指针,指向APP运行函数头地址后调用函数
    uint32_t jump_address;                        //跳转地址变量
   
    jump_address = *(__IO uint32_t*)(app_addr + 4);        //计算APP运行函数头地址,为MSP主堆栈指针+4个地址偏移量
    jumo_to_application = (pFunction)jump_address;        //函数指针指向APP运行函数头地址
   
    __set_MSP(*(__IO uint32_t*)app_addr);        //设置主堆栈指针
    jumo_to_application();                                        //跳转到APP应用程序,开始运行应用主程序
}

/* 调用跳转函数 */
BootLoader_JumpToApp(APP_ADDR);

应用程序设置:Keil工程设置FLASH的加载地址

 

应用程序main函数的入口处重新设置中断向量表的位置:SCB->VTOR = APP_ADDR;

 

参考资料:

<野火视频>

https://www.bilibili.com/video/BV1C4421Z7t8/?vd_source=eb04ac3759f85a5dd795269e17334fee&spm_id_from=333.788.videopod.episodes&p=25

 <铁头山羊>

https://www.bilibili.com/video/BV1Sy41187Rt/?vd_source=eb04ac3759f85a5dd795269e17334fee&spm_id_from=333.788.player.switch

https://blog.csdn.net/2401_83606346/article/details/144164248

本文仅是为了加深自己的理解做个学习总结,还有其他博客没列出,如有侵权,请联系删除

 

posted @ 2025-07-12 23:04  小小的番茄  阅读(176)  评论(0)    收藏  举报