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
本文仅是为了加深自己的理解做个学习总结,还有其他博客没列出,如有侵权,请联系删除
浙公网安备 33010602011771号