目录
在我的上一篇文章讲了基本定时器的用法和内部结构,点击下面的链接可以回顾一下。
文章链接:TIMER基本定时器详解+1毫秒延时例程
而这里将继续深入定时器,讲一讲通用定时器。
下面是GD32各个定时器的差异表。

通用定时器比较特别,它们之间还分了3个不同的版本——L0、L1和L2。大致的区别在于捕获\比较通道数、单脉冲模式支持、正交译码器支持、从设备控制器支持、内部连接支持、DMA支持。
下面就以功能最全面的L0通用定时器为例,详细讲一讲。

通用定时器支持向上计数、向下计数和中央对齐,3种计数模式。
向上计数和向下计数比较简单就不多赘述了,下面详细讲中央对齐模式。

定时器开始向上计数,当数值达到重装载值时定时器产生上溢,这时发出更新事件\中断(若用户使能了相关的寄存器);但这时定时器不再从0开始计数,而是开始向下计数,直到数值减到0时,定时器产生下溢,同时也发出更新事件\中断;接着定时器又重复一开始的步骤,开始从0开始向上计数。
下面是捕获通道的系统结构框图。

捕获模式允许我们测量一个波形时序,频率,周期,占空比等。输入级包括一个数字滤波器,一个通道极性选择,边沿检测和一个通道预分频器。如果在输入引脚上出现被选择的边沿,定时器会捕获计数器当前的值,同时对应的标志位位被置1,如果使能了相关的中断寄存器,则产生通道中断。
以下是配置定时器输入捕获通道的简要步骤:
CHxIF,全称“Channel x Interrupt Flag”,为某一通道的中断标志位。
CHxOF,全称“Channel x Overflow Flag”,为某一通道的溢出标志位。
下面是输出比较模式的系统框图。

在输出比较模式,定时器可以产生时控脉冲,其位置,极性,持续时间和频率都是可编程的。当一个输出通道的比较值寄存器与计数器的值匹配时,根据用户的配置,这个通道的
输出可以被置高电平、被置低电平或者反转。当计数器的值与比较值寄存器的值匹配时,通道中断标志位被置1,如果使能了中断则会产生中断,如果使能了DMA则会产生DMA请求。
以下是配置定时器输出比较通道的简要步骤:
关于3个输出输出模式,下面的时序图可以使大家比较容易理解。

由上图可以看到,输出比较寄存器配置为2,当定时器计数到2时,触发输出。对于置高和置低模式,输出电平分别由低置高和由高置低,但当定时器继续运行,即使计数器再达到比较寄存器的值也不会产生电平变换;但对于翻转模式来说,输出通道的电平可以一直改变,每次计时器达到比较寄存器的值,管脚电平翻转一次。
一般来说,我们会将定时器的输出模式配合PWM模式来使用,来帮助我们输出不同的PWM波形。
在PWM模式下分有PWM模式0和PWM模式1,具体的区别只是在于输出的波形极性不同。
用户对定时器计数模式的不同选择也会导致输出PWM波形的不同,可以分为EAPWM(边沿对齐PWM)和CAPWM(中央对齐PWM)。
下面是EAPWM的时序图。

以PWM模式0、向上计数为例,定时器输出通道开始时为高电平,当计数器的值大于输出比较寄存器的值时,输出通道变为低电平,直到计数器达到重装载寄存器的值,产生溢出并复位。PWM模式1的电平和PWM模式0是互补的,向上计数和向下计数的区别在于输出比较寄存器和重装载寄存器的比较方式是相反的。
下面是CAPWM的时序图。

可以理解为中央对齐PWM是边沿对齐PWM的两种计数方式的结合体。当计数器的值小于输出比较寄存器的值时,通道输出为空闲电平;当计数器的值大于输出比较寄存器的值时,通道输出的是有效电平。
正交译码器主要应用在电机的控制上,电机有两个输出脉冲——A信号和B信号,通过正交编码器识别这两个信号的变化,通过计算我们可以算出电机的旋转位置、速度等物理参数。
但对于正交编码器的工作我并不是很了解,也没有接触相应的项目,所以下面只是简单介绍而已。等以后上手电机控制的相关项目,再单独出一篇文章来详细讲一讲正交编码器的工作原理,包括如何计算得出电机的实时位置、速度等物理量。
通俗来说,定时器中的正交编码器是通过识别CI0、CI1正交输入捕获信号的变化来改变定时器的计数方向。
下面给出了一张表来表示计数方向与编码器信号之间的关系。

下面的时序图表示了CI0和CI1两个正交信号捕获通道中脉冲值的变化是如何改变定时器的计数方向的。

定时器的从控制器模式下,定时器允许接收外部的触发,当外部触发来临时执行相关的操作。
通用定时器的从控制器支持3种模式——复位模式、暂停模式和事件模式。
用户可以调整触发信号的极性(上升沿触发或下降沿触发),但每个触发源之间会有轻微差别,具体看用户手册的介绍。
下面是复位模式下的时序图。

复位模式下,定时器外部触发(ITI0)有效,计数器的值将会复位至0。
由上面可以看到,当触发源(ITI0)发出有效信号时,定时器并不会立即响应(TRGIF),这一延迟为“内部同步延迟”。
下面的例子也是存在这种延迟。
下面是暂停模式的时序图。

暂停模式下,定时器外部触发(CI0FE0)有效,定时器将暂停计数,相当于关闭了定时器。
下面是事件模式的时序图。

事件模式下,定时器外部触发(ETIFP)有效,定时器将继续计数,相当于使能了定时器。
在这个例程中,我们使能定时器1,配置其为PWM输出,使能定时器2,配置为输入捕获,使用串口每秒输出PWM波的周期和占空比。
这个例程还是比较复杂的,所以详细解释一下是如何配置的。
首先是负责输出PWM波的定时器1,设置为边沿PWM模式0,周期根据自己需求设置就好,使能定时器通道1输出。输出PWM波比较简单,难的是如何捕获并计算它的周期和占空比。
接着是负责捕获PWM波的定时器2,周期最好设置为最高值(65535),这样是为了避免计数时计数器溢出而导致数据不正确。配置输入捕获为定时器通道0,通道极性为上升沿,即在遇到上升沿时,定时器会捕获计数器的值,同时我们设置了中断,所以定时器一旦捕获,我们就可以在中断服务函数中开始计算对应的周期和频率。接着配置从模式,设置为复位模式,这样一旦发生捕获,计数器的值就会归零,重新开始计数。
timer.c文件
void TIM_PwmInit(void)
{
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_AF);
/* PWM输出管脚为复用推挽模式 */
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);
/* PWM输入管脚为浮空输入模式 */
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
/* TIMER1初始化 */
timer_oc_parameter_struct timer_ocintpara = {0};
timer_parameter_struct timer_initpara = {0};
rcu_periph_clock_enable(RCU_TIMER1);
timer_deinit(TIMER1);
timer_initpara.prescaler = (108 - 1); // 预分频:108MHz / 108 = 1MHz
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边沿对齐计数
timer_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数
timer_initpara.period = (16000 - 1); // 周期:1MHz / 16000 = 62.5Hz
timer_init(TIMER1, &timer_initpara);
/* 配置所有通道为PWM模式0 */
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; // 通道输出极性高,即高电平有效
timer_ocintpara.outputstate = TIMER_CCX_ENABLE; // 使能输出通道
timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW; // 输出通道空闲低电平
timer_channel_output_config(TIMER1,TIMER_CH_1, &timer_ocintpara);
/* 配置通道1为50%占空比 */
timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_1, (8000 - 1)); // 8000 / 16000 = 50%
timer_channel_output_mode_config(TIMER1,TIMER_CH_1, TIMER_OC_MODE_PWM0); // 配置为PWM模式0
timer_channel_output_shadow_config(TIMER1,TIMER_CH_1, TIMER_OC_SHADOW_DISABLE); // 关闭输出影子
timer_auto_reload_shadow_enable(TIMER1); // 使能重装载影子
timer_enable(TIMER1); // 使能定时器1
/* 初始化TIMER2 */
timer_ic_parameter_struct timer_icinitpara = {0};
rcu_periph_clock_enable(RCU_TIMER2);
timer_deinit(TIMER2);
timer_initpara.prescaler = (108 - 1); // 预分频:108MHz / 108 = 1MHz
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边沿对齐计数
timer_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数模式
timer_initpara.period = (65536 - 1); // 周期最好设置为最高,以免计数器溢出
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; // 输入时钟1分频
timer_init(TIMER2,&timer_initpara);
timer_icinitpara.icpolarity = TIMER_IC_POLARITY_RISING; // 输入极性为上升沿,即上升沿有效
timer_icinitpara.icselection = TIMER_IC_SELECTION_DIRECTTI; // 输入捕获通道连接至CIx
timer_icinitpara.icprescaler = TIMER_IC_PSC_DIV1; // 时钟1分频
timer_icinitpara.icfilter = 0x0;
timer_input_pwm_capture_config(TIMER2, TIMER_CH_0, &timer_icinitpara);
timer_input_trigger_source_select(TIMER2, TIMER_SMCFG_TRGSEL_CI0FE0); // 输入触发源为通道0
timer_slave_mode_select(TIMER2, TIMER_SLAVE_MODE_RESTART); // 从模式选择为复位模式
timer_master_slave_mode_config(TIMER2, TIMER_MASTER_SLAVE_MODE_ENABLE); // 使能从模式
timer_auto_reload_shadow_enable(TIMER2); // 使能重装载影子
nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0); // 抢占优先级4位,响应优先级0位
nvic_irq_enable(TIMER2_IRQn, 1, 0); // 使能中断服务,抢占优先级为1
timer_interrupt_flag_clear(TIMER2, TIMER_INT_CH0); // 清除通道0中断标志位
timer_interrupt_enable(TIMER2, TIMER_INT_CH0); // 使能通道0中断
timer_enable(TIMER2); // 使能定时器2
}
gd32f10x_it.c文件
#include "gd32f10x_it.h"
#include "main.h"
#include "systick.h"
#include "usart.h"
#include "timer.h"
#include <stdio.h>
#include <string.h>
uint32_t ic1value = 0, ic2value = 0;
__IO uint16_t dutycycle = 0;
__IO uint16_t frequency = 0;
void TIMER2_IRQHandler(void)
{
if(SET == timer_interrupt_flag_get(TIMER2, TIMER_INT_CH0))
{
timer_interrupt_flag_clear(TIMER2, TIMER_INT_CH0); // 清除中断标志位
ic1value = timer_channel_capture_value_register_read(TIMER2, TIMER_CH_0) + 1;
if(0 != ic1value)
{
ic2value = timer_channel_capture_value_register_read(TIMER2, TIMER_CH_1) + 1;
dutycycle = (ic2value * 100) / ic1value; // 计算占空比
frequency = (float)1000000 / ic1value; // 计算频率
}
else
{
dutycycle = 0;
frequency = 0;
}
}
}
main.c文件
#include "gd32f10x.h"
#include "main.h"
#include "systick.h"
#include "usart.h"
#include "timer.h"
#include <stdio.h>
#include <string.h>
extern __IO uint16_t dutycycle;
extern __IO uint16_t frequency;
int main(void)
{
systick_config();
USART_Config();
TIM_PwmInit();
while(1)
{
printf("dutycycle: %d%%, frequency: %dHz\n", dutycycle, frequency);
delay_ms(1000);
}
}
将定时器1的输出通道1管脚(PA1)与定时器2的输入通道0管脚(PA6)相连,并下载程序,打开串口就能看到下面的结果了。

为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
我正在使用puppet为ruby程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这
这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][
我想使用spawn(针对多个并发子进程)在Ruby中执行一个外部进程,并将标准输出或标准错误收集到一个字符串中,其方式类似于使用Python的子进程Popen.communicate()可以完成的操作。我尝试将:out/:err重定向到一个新的StringIO对象,但这会生成一个ArgumentError,并且临时重新定义$stdxxx会混淆子进程的输出。 最佳答案 如果你不喜欢popen,这是我的方法:r,w=IO.pipepid=Process.spawn(command,:out=>w,:err=>[:child,:out])
运行bundleinstall后出现此错误:Gem::Package::FormatError:nometadatafoundin/Users/jeanosorio/.rvm/gems/ruby-1.9.3-p286/cache/libv8-3.11.8.13-x86_64-darwin-12.gemAnerroroccurredwhileinstallinglibv8(3.11.8.13),andBundlercannotcontinue.Makesurethat`geminstalllibv8-v'3.11.8.13'`succeedsbeforebundling.我试试gemin
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时
我想知道Ruby用来在命令行打印这些东西的输出流:irb(main):001:0>a="test"=>"test"irb(main):002:0>putsatest=>nilirb(main):003:0>a=>"test"$stdout是否用于irb(main):002:0>和irb(main):003:0>?而且,在这两次调用之间,$stdout的值是否有任何变化?另外,有人能告诉我打印/写入这些内容的Ruby源代码吗? 最佳答案 是的。而且很容易向自己测试/证明。在命令行试试这个:ruby-e'puts"foo"'>test.
我在使用自定义RailsFormBuilder时遇到了问题,从昨天晚上开始我就发疯了。基本上我想对我的构建器方法之一有一个可选block,以便我可以在我的主要content_tag中显示其他内容。:defform_field(method,&block)content_tag(:div,class:'field')doconcatlabel(method,"Label#{method}")concattext_field(method)capture(&block)ifblock_given?endend当我在我的一个Slim模板中调用该方法时,如下所示:=f.form_field:e
考虑一下:现在这些情况:#output:http://domain.com/?foo=1&bar=2#output:http://domain.com/?foo=1&bar=2#output:http://domain.com/?foo=1&bar=2#output:http://domain.com/?foo=1&bar=2我需要用其他字符串输出URL。我如何保证&符号不会被转义?由于我无法控制的原因,我无法发送&。求助!把我的头发拉到这里:\编辑:为了澄清,我实际上有一个像这样的数组:@images=[{:id=>"fooid",:url=>"http://