一、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_CNT>TIMx_CCRn时,输出无效电平 //PWM模式2下,TIMx_CNT // 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 本文仅是为了加深自己的理解做个学习总结,还有其他博客没列出,如有侵权,请联系删除