完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
小明妈妈跟小明说:“10分钟后,你再不给我去做作业我就揍你!”,接着,小明妈妈看着手表,1秒钟数1下,0,1,2,3,……,599。看看小明有没有做作业,根据情况判断要不要揍他。
接着,小明妈妈又从0数起,到599,继续看看小明有没有做作业…… ……再数数…… …… 于是,小明妈妈就是一个每隔10分钟监视一下小明有没有做作业的定时器,手表,就是小明妈妈定时器的时钟,小明妈妈数600个周期后(0~599),触发定时器中断,定时器中断里,判断小明有没有去做作业,没有,就揍他,有,就啥事也不做。 STM32上的定时器,也是跟小明妈妈一样,是通过数数计时的,比如,数1个数是1秒,从0数到599,就是600秒(10分钟)了。 STM32上的数数,可以正着数,从0~599,也可以倒着数,从599~0,还可以先正着数后倒着数,当然,小明妈妈也是可以的。 那能不能不数600个数?数别的?可以的,只需要设置就行了。 还能不能不1秒数一次,长一点或者短一点?也是可以的,只需要设置就行了。 RM0033告诉我们,STM32F207有8个小明妈妈,TIM1~TIM8,资源非常丰富,当然,它也不仅仅只能用于数数,功能非常强大。 为什么叫基本定时器呢?因为它们不仅用来计时,还有其他非常强大的功能,如PWM,脉宽测量等等。定时器,只是它的基本功能。 我们今天,只讲数数功能,数到一定数后,就触发定时器中断,处理事件。换句话说,每隔一段特定的时间,触发特定的事件。 我们就来做一个一秒的定时器吧,每隔1秒打印定时器开启时间。 先打开Stm32CubeMx,新建一个工程 建完之后,看TIMx(x=1,2,3……),这就是定时器。 这里面有各种需要设置的地方,Slave Mode、Trigger Source、Clock Source、等等等。 这是啥?看不懂,怎么办(黑人问号)??? 没关系,问一下RM0033,找到相关的部分,看介绍。 它告诉我们,定时器包含16Bit自动重载计数器,由可编程预分频器组成。抱歉,翻译不是我的特长,大家大概看得懂意思就行啦。它还告诉我们,这些定时器有很多作用,像量输入信号脉冲宽度或者输出波形等等。还讲了脉宽测量的范围,从微秒级到毫秒级的。还说这几个小明妈妈是完全独立的,不过她们可以同步,具体的要参考一下Section 13.3.20 点到13.3.20,定时器可以连在一块,具体参考14.3.15。 点到14.3.15,当一个定时器配置成Master Mode时,它能够复位,开始,停止,或者作为另外一个被配置成Slave Mode的定时器的时钟。 由此可见,Slave Mode,主要用在两个以上的定时器上,具体有兴趣的朋友可以继续往下看资料,这里就不截图出来。 它有Reset Mode、GateMode、Trigger Mode、External Clock Mode几种模式。 我们这章节,不需要用到Slave Mode,所以,就选择 Disable。 Trigger Source 选择,里面有ITR0、1、2、3等项目,怎么选择呢? ClockSource 选择,里面有Internal Clock,ETR,怎么选择呢? Channel1、2、3、4,以及以下的,如何设置呢? 先来看一个图: 图中标的1、2、3,就是Timer的ClockSource。 图中标的4、5,是定时器的其它功能,如Pwm,脉宽测量等功能,我们这节课暂时不需要关注它。 再来看看 Clock selection 的说明: 我这板子呢,也没有外部引脚的时钟源,也没有用到Slave Mode,所以, ClockSource,就选 Internal Clock,Trigger Clock,就选Disable。 Channel1、2、3、4、及以下的呢,我们全都Disable掉,不需要选。 设置完的定时器如下图: 基本设置完成,再先择顶上的标签页“Configuration”,再点TIM1,旁边有个时钟标志,弹出菜单: 这里密密麻麻的一大垞,怎么设置呢? 小明妈妈跟小明说:“10分钟后,你再不给我去做作业我就揍你!”,接着,小明妈妈看着手表,1秒钟数1下,0,1,2,3,……,599。 小明妈妈的ClockSource就是手表,周期是1秒钟,频率就是1Hz。 小明妈妈觉得1秒钟数一下,10分钟得数600下,这时,小明妈妈想,那我2秒数一下,我只需要数300下,我3秒数一下,我只需要数200下,…… 这个“x秒数一下”,就是预分频(Prescalar),作用是,把超高超高的频率降低下来。 预分频为0,分频后的频率就是1Hz/(0+1)=1Hz,要数600下,Count Preiod=600-1; 预分频为1,分频后的频率就是1Hz/(1+1)=0.5Hz,要数300下,Count Preiod=300-1; 预分频为2,分频后的频率就是1Hz/(2+1)=1/3Hz,要数200下,Count Preiod=200-1; …… …… 预分频为n,分频后的频率就是1Hz/(n+1) Hz,…………自个算去; 小明妈妈想计时10分钟,她的ClockSource=手表,手表频率是1Hz, Prescaler=0,Count Preiod=599; Prescaler=1,Count Preiod=299; Prescaler=2,Count Preiod=199; 说完小明妈妈,我们来看一下Stm32,Stm32 ClockSource我们选的是Internal Clock,那么,我们这个Internal Clock的频率是多少呢? 首先,得了解一下TIM1的Internal Clock从何而来? 看一下DataSheet里面的系统总框图,TIM1挂在APB2总线上,于是,它的Clock由APB2提供; 再回忆一下我们刚用Stm32时,设置Clock的时候是怎么设置的? 打开Clock Configuration标签页: 看到没,这个APB2 timer clock = 120Mhz = 120,000,000Hz。 120,000,000Hz,也就是计时1秒,要数1.2亿次啊,那Count Preiod设置 120,000,000-1可以不? 那是不行的,因为设置不下去,Count Period是16bit的,所以它的最大值就是 65535(2的16次方-1)。 不用担心,我们有Prescaler,这个东西它也能把Internal Clock分频,它的范围是 0~65535,所以,它能把clock的频率变成 120MHz/(0+1)~120MHz/(65535+1),也就是120,000,000Hz~1831Hz之间,我们不要选得这么极端,我们选择个11999,也就是把频率分成 120,000,000/(11999+1) = 10000Hz,计时一秒,只需要10000次就够了。 那么,我们的Prescaler设置为120000-1=119999,Count Period设置为10000-1=9999 Counter Mode,管正着数,还是倒着数,还是先正着数,再倒着数。 正着数:从0~9999,数1000次,产生上溢事件。 倒着数:从9999~0,数1000次,产生下溢事件。 先正着数,再倒着数:从0~9998,数9999次,再从9999~1,数9999次。 请详细阅读RM0033第13.3.2,在这里,我们就设置个Up吧。 其他的,在基本定时器中不需要用到,所以就不用设置,这样,一个1秒钟的定时器就完成了!如图所示: 每次计数器溢出(不管上溢还是下溢)时,会产生一个update event(UEV)。 所以,记得把NVIC Settings里面的中断钩上,我们后续会对这个事件进行处理。 接下来,生成代码后,看看它生成了什么东西,我们把这些东西,挪到上一篇的@命令的项目中 在一个项目中,有时候要增加外设,但项目已经完成了,我们不可能用Stm32CubeMx重新走一遍配置,再把我们自己写的代码挪过去,这样太麻烦了。 比较简便的办法,就是用Stm32CubeMx生成配置,然后把这些配置文件及代码,移植到原有的项目中。 对STM32比较熟悉的同学还可以直接用HAL库直接写配置,或者直接写寄存器。 所以后面我们就比较少用Stm32CubeMx直接生成代码直接用了,我们更多用它,来生成一些配置,把这些配置移植到我们原有的项目中,以及结合文档,理解STM32的寄存器。 Stm32CubeMx生成了tim.c和tim.h文件,在main()里面用MX_TIM1_Init();进行初始化,以及在stm32f2xx_it.c里面,生成void TIM1_UP_TIM10_IRQHandler(void)中断服务例程。 好了,依葫芦画瓢,把它挪过来吧。 tim.c/tim.h,直接复制到对应的文件夹下,tim.c要加入工程编译。 在main.c里面,增加初始化MX_TIM1_Init();,记得把tim.h #include进来。 #include “main.h” #include “stm32f2xx_hal.h” #include “dma.h” #include “i2c.h” #include “tim.h” // 包含的tim.h文件 #include “usart.h” #include “gpio.h” /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); MX_UART4_Init(); MX_TIM1_Init(); // 初始化 /* USER CODE BEGIN 2 */ USR_UartInit(); /* USER CODE END 2 */ 在stm32f2xx_it.c里面,增加中断服务例程,因为用到htim1这个变量,所以还要把这个变量extern进来 /* External variables --------------------------------------------------------*/ extern TIM_HandleTypeDef htim1; // 就是这个变量 extern DMA_HandleTypeDef hdma_uart4_rx; extern UART_HandleTypeDef huart4; /** * @brief This function handles TIM1 update interrupt and TIM10 global interrupt. */ void TIM1_UP_TIM10_IRQHandler(void) { /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 0 */ /* USER CODE END TIM1_UP_TIM10_IRQn 0 */ HAL_TIM_IRQHandler(&htim1); /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 1 */ /* USER CODE END TIM1_UP_TIM10_IRQn 1 */ } 还有记得在stm32f2xx_hal_conf.h里面,把#define HAL_TIM_MODULE_ENABLED打开,之前是注释掉的。 /*#define HAL_MMC_MODULE_ENABLED */ /*#define HAL_SPI_MODULE_ENABLED */ #define HAL_TIM_MODULE_ENABLED // 一定要打开喔 ^_^ #define HAL_UART_MODULE_ENABLED /*#define HAL_USART_MODULE_ENABLED */ /*#define HAL_IRDA_MODULE_ENABLED */ /*#define HAL_SMARTCARD_MODULE_ENABLED */ 挪好了,那接下来,我们要做什么? 开启TIM中断,处理UEV; 我们就在初始化后开启TIM中断。 void MX_TIM1_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig; TIM_MasterConfigTypeDef sMasterConfig; htim1.Instance = TIM1; // Tim1 htim1.Init.Prescaler = 29999; // 预分频 htim1.Init.CounterMode = TIM_COUNTERMODE_UP; // 正着数 htim1.Init.Period = 3999; // 数多少个周期 htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim1) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } HAL_TIM_Base_Start_IT(&htim1); // 在这里,开开开!^_^ } 我们看一下中断服务例程, 上面讲过,每次计数器溢出(不管上溢还是下溢)时,会产生一个update event(UEV)。 也就是说,每隔1秒钟(我们设置的),会产生一次中断。 // 简单地理解,定时器每计1秒会进一次这个函数喔^_^~~~ void TIM1_UP_TIM10_IRQHandler(void) { /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 0 */ /* USER CODE END TIM1_UP_TIM10_IRQn 0 */ HAL_TIM_IRQHandler(&htim1); /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 1 */ /* USER CODE END TIM1_UP_TIM10_IRQn 1 */ } 点进去,看一下HAL_TIM_IRQHandler(&htim1);这个函数做了什么。 里面有各种不同的事件处理,但我们只关注 Update Event 就行了。 /* TIM Update event */ if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) != RESET) { if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_UPDATE) !=RESET) { __HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE); HAL_TIM_PeriodElapsedCallback(htim); // 就是你了!实现它! } } 再看一下回调函数。 它的NOTE告诉我们,这个函数不能改,如果需要,就在重写它就行了。 我们现在就来重写它。 /** * @brief Period elapsed callback in non blocking mode * @param htim pointer to a TIM_HandleTypeDef structure that contains * the configuration information for TIM module. * @retval None */ __weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { /* Prevent unused argument(s) compilation warning */ UNUSED(htim); /* NOTE : This function Should not be modified, when the callback is needed, the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file */ } 既然我们需要打印定时器启动后的秒数,那我们首先需要这个秒数的变量。 变量名为 tim1StartCntS 变量类型用 unsigned int,能计时 2的32位 秒,也就是 4294967296 秒 = 1193046小时 = 49710天 = 136年,系统一般运行不了这么长时间,所以 unsigned int 就足够了。 /* Includes ------------------------------------------------------------------*/ #include “tim.h” /* USER CODE BEGIN 0 */ volatile unsigned int tim1StartCntS; // 这个够定时器运行 136 年啊^_^ /* USER CODE END 0 */ TIM_HandleTypeDef htim1; 这个函数,就是重写的回调函数。 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { /* Prevent unused argument(s) compilation warning */ if(htim-》Instance==htim1.Instance) { tim1StartCntS++; // 每一秒钟自加。 } /* NOTE : This function Should not be modified, when the callback is needed, the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file */ } 接下来,能不能直接在回调函数里面打印一下 tim1StartCnS? 最好不要, 记住中断服务例程设计原则其中之一:中断尽量简短,请不要在中断服务例程里执行比较耗费时间的事,比如,printf,延时等需要大量时间的事。 那么,我们就设计一个 Handler,在main()主循环里面,打印定时器启动时间吧。 每隔一秒钟,tim1StartCntS会增加1,而tim1CntTmp不变,所以,每一秒钟,tim1StartCntS 和tim1CntTmp的值是不同的,每次不同,都要更新一下tim1CntTmp的值,然后打印。 这样,就每一秒都打印出 tim1StartCntS 的值了。 /* USER CODE BEGIN 1 */ void TIM1_Handler(void) { static unsigned int tim1CntTmp = 0; if(tim1CntTmp!=tim1StartCntS) { tim1CntTmp = tim1StartCntS; printf(“Tim1 Start %d seconds.rn”, tim1CntTmp); } } 记得,这个TIM1_Handler(),要在tim.h里面声明一下,要在main()主循环里面调用一下。 // 在tim.h里面声明一下 extern void _Error_Handler(char *, int); void MX_TIM1_Init(void); void TIM1_Handler(void); // 在 main()的主环里调用 while (1) { USR_LedHandler(); SERDEB_Handler(); TIM1_Handler(); // 就这了! /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } 编译,运行,烧录,来看一下运行结果:这个,运行很久了喔。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1609 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1540 浏览 1 评论
970 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
681 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1587 浏览 2 评论
1861浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
643浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
515浏览 3评论
528浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
503浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-20 21:40 , Processed in 0.830707 second(s), Total 77, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号