2014-11-02 17:40:24 lixiangminghate 阅读数 6084
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    4006 人正在学习 去看看 朱有鹏

STC12C系列增强型单片机片上扩展了基本51单片机的功能,如提供了PCA/PWM接口,定时器能工作在1T模式下(基本51单片机的时钟是Fosc的12分频,1T模式下1分频)。

PCA可以用于脉宽测量,但是,protues暂不支持该系列单片机的仿真功能,反复烧写也挺麻烦,所以还是先用基本51单片机实现该功能,在后面的博文里在实现PCA测量脉宽。

实现思路如下:

TMOD最高位GATEn置位后,Tn启动计数受INTn(Pin3.3)和TRn的共同影响:TRn为1,当INTn引脚输入为高电平时,Tn才允许计数。利用这个功能可测量INTn上正脉冲的宽度。

先上图(原谅我的制图能力):

1):1处 在上升沿之前,初始化TMOD,TRn=1;

2):2处 INTn引脚为高电平,开始计数测量脉宽;

3):3处 INTn引脚为低电平,测量结束停止计数TRn=0


再上仿真图:

1).信号发生器电平选5v方波。注信号发生器的反相端接地,否则正向端只输出2.5v的方波(剩下的2.5v输出反相方波,可以接到示波器上试试),INTn上永远收不到高电平,达不到预期效果。

2).T0定时器做计数器使用,收到一个负脉冲产生溢出,启动T1;

3).T0,T1全工作在方式2自动装载计数值模式。

然后,上代码:

工作频率12Mhz

#include <REG52.H> 
#include <INTRINS.H>

sbit P1_0 = P3^3;

#define MakeByte(target, Hi,Lo) \
do{ \
	target |= (((Hi)<<4)|(Lo)); \	
}while(0); \

#define SetTH(n,val) \
do{ \
	TH##n = val; \
}while(0); \

#define SetTL(n,val)  \
do{ \
	TL##n = val; \
}while(0); \

#define EnableET(n) \
do{ \
	ET##n = 0x01; \
	IE |= 0x80; \
}while(0); \

unsigned int click;  
unsigned int oneMs;
unsigned char getPlusWidth;
int main()
{
	unsigned int totalus=0,maxPlusWidth=0;
	P3 = 0xFF;

	getPlusWidth = 0;
	MakeByte(TMOD,0x0A,0x06);
	SetTH(0,0xff);
	SetTL(0,0xff);
	SetTH(1,0x38);
	SetTL(1,0x38);
	EnableET(0);
	EnableET(1); 
	TR0 = 0x01;
	while(1)
	{
		while(!getPlusWidth);
		//等待INT1至低
		while(INT1==0x01);
		//等待INT1至高电平
		while(INT1==0x00);
		//等待INT1至低电平,脉宽结束
		while(INT1==0x01);
		TR1 = 0x00;

		totalus = 1000*(oneMs+(click*0.2))+(TL1-TH1);	
		oneMs = 0;
	}
	return 0;
}

//T0引脚上接受到负跳变
void IsrT0() interrupt 1
{
	TR1 = 0x00;
	getPlusWidth = 1;		
	TR1 = 0x01;
}

void IsrT1() interrupt 3
{
	//每次进入中断0.2ms
	click++;
	if(click == 5)
	{
		oneMs++;
		click=0;
	}
}

最后 上仿真结果:

500Hz的方波,脉宽981us

1kHz的方波,脉宽587us


2kHz方波,脉宽234us


2017-04-26 17:32:02 little_white__ 阅读数 10232
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    4006 人正在学习 去看看 朱有鹏

今天被老师教育了一下,教育的内容…(省略一万个字),
这里写图片描述
但是学到了一种很精确测量脉冲宽度的方法。
具体思想是:
这里写图片描述
利用定时器的内部资源(当GATE = 1时,计数器的停止和开始受TR和INT的电平共同控制),我们这里用定时器0 ,将外部脉冲接在INT0上,配置定时器0和外部中断0。当脉冲是高电平时,计数器(TH0,TL0)计数,当计数器溢出时,触发定时器中断。当脉冲为下降沿时,触发外部中断,此时停止计数,所记下的时间也就是脉冲的宽度。

代码如下:

#include <reg51.h>
#include <intrins.h>

#define uint unsigned int
#define uLint unsigned long int		//长整型

uLint pulse_w = 0 ;//计算脉冲的时间,用长整型可以达到10的9次方us,如果用uint,最大只能达到65535us(还不到100ms)
sbit in = P3^2 ;

void Int0 (void) interrupt 0
{
    pulse_w += TL0 ;
	TL0 = 0 ;
}

void Time0(void) interrupt 1
{
	pulse_w += 256 ;//计数寄存器溢出,直接加最大值
}

int main()
{
	//初始化
	TMOD = 0xA ; //定时器0,模式2,GATE0 = 1
	TH0 = 0 ;    //填初值
	TL0 = 0 ;
	TR0 = 0 ;     
	ET0 = 1 ;//开定时器0中断
	
	IT0 = 1 ;//外部中断0下降沿触发中断
	EX0 = 1 ;//开外部中断0
	EA = 1 ;//开总中断
	
	while(1)
	{
		if(in == 0)//见下面的解释
			TR0 = 1 ;

	}		
}

信号函数:


signal void test(double cc) 
{
	port3 &= ~(0x1<<2)  ;
	swatch(1) ;
	port3 |= (0x1<<2) ;
	swatch(cc) ;
	port3 &= ~(0x1<<2) ;
	swatch(0.1) ;

	_break_ = 1 ;
	  
}

输入波形(脉冲高电平1s)
这里写图片描述

查看变量的值(0xF4240 = 1000000)
这里写图片描述

注释1:由于单片机复位后所有port都为高电平,所以如果不做一些措施的话,单片机一复位,计数器就会计数,造成测量误差。我的做法是:开始设TR0= 0,这样port3.2就无法开启计数器。当外部脉冲低电平时,我才让TR0 = 1,这时port3.2才能开启计数器,达到精准计时的要求

注释2:单片机的晶振为12M,所以时钟周期为1us

注释3:计算十六进制的数可以使用win7内部的计算器:使用很简单,自己点一点就会了
这里写图片描述

今天感觉自己好傻,有些伤。。。

最近看到一句话感觉很好,分享一下:
海底月是天上月,眼前人是心上人。
向来心是看客心,奈何人是剧中人。

新手小白,欢迎指教。

2019-05-17 13:16:52 weixin_40773613 阅读数 183
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    4006 人正在学习 去看看 朱有鹏

用外部中断0测量输入的负脉冲宽度。

#include<reg51.h>
sbit si=P3^0;	//定义负脉冲输出端口
sbit mi=P3^2;	//定义外部中断0输入口
void main()
{
TMOD=0x22;	//T0和T1设置为定时方式2
EA=1;
ET0=1;	//开T0中断
EX0=1;	//开外部中断0
PT0=1;	//T0中断设为高优先级
IT0=1;	//外部中断0为下跳沿触发
TH0=256-100;	//T0初值
TL0=256-100;
TH1=0;	//T1初值
TL1=0;
TR1=0;	//关T1
TR0=1;	//启动T0
while(1);	//等待中断
}
void timer0()interrupt 1	//T0中断,用于输出方波
{
si=~si;
}
void int0()interrupt 0	//外部中断0,用于启动T1定时
{
TR1=1;	//启动T1
TL1=0;
while(mi==0);	//输出方波保持低电平时,等待
TR1=0;	//方波低电平结束,关T1
P2=TL1+10; //输出T1定时值到P2口
}
2016-01-25 11:42:45 baidu_33836580 阅读数 1204
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    4006 人正在学习 去看看 朱有鹏

;有人提问:
;利用单片机的定时方式0,实现外部脉冲宽度(在5到250ms)的测量。要求画出电路图。在线等
;问题补充:写出程序,测量结果存储在R0中。
;=================================================
;做而论道对此题给出了答案:
;外部脉冲宽度(在5到250ms)之间,测量存放在R0。程序如下。

    ORG 0000H
    JMP START
    ORG 000BH
    JMP T0_INT
;------------;初始化
START:
    MOV SP, #60H
    MOV TMOD, #01H       ;/*T0定时方式1*/
    MOV TH0, #HIGH(65536 - 1000) ;1ms@12MHz
    MOV TL0, #LOW (65536 - 1000)
    SETB ET0
    SETB EA
    MOV R0, #0
;------------;电路:要把外部脉冲,接在P1.0
    JB   P1.0, $          ;等待高电平结束.
    JNB P1.0, $          ;等待低电平结束.
    SETB TR0              ;到了高电平,就启动T0开始计时.
    JB   P1.0, $          ;等待高电平结束.
    CLR TR0              ;到了低电平,结束T0计时.

    CALL DISPLAY         ;显示

    SJMP $                ;程序到此结束, R0中就是脉宽的毫秒数.
;-------------;1ms执行一次
T0_INT:        
    MOV TL0, #LOW (65536 - 993) ;重新写入初始值.
    MOV TH0, #HIGH(65536 - 993) ;1ms@12MHz
    INC R0        ;1ms加一次一.
    RETI
;---------------------------------

;原题目,并没有提出显示测量结果的要求,这就不便观察。
;为此,下面补充了显示电路(以三个74HC595来驱动数码管)和程序。

    SDAT_595 BIT   P3.0
    SCLK_595 BIT   P3.1
    S_UP_595 BIT   P3.2
;**************************************************
;595发送程序
;**************************************************
OUT_595:MOV   R2, #8
        CPL   A
O_1:    RLC   A
        NOP
        MOV   SDAT_595, C
        NOP
        CLR   SCLK_595
        NOP
        SETB SCLK_595
        NOP
        DJNZ R2, O_1
        RET
;**************************************************
;显示程序
;**************************************************
DISPLAY:
        MOV   A, R0       ;把脉宽数据,分解成
        MOV   B, #100     ;BCD码
        DIV   AB;
        MOV   30H, A      ;百位
        MOV   A, #10;
        XCH   A, B;
        DIV   AB;
        MOV   31H, A      ;十位
        MOV   32H, B      ;个位

        MOV   R0, #32H     ;把30H 31H 32H
        MOV   R7, #3       ;送出去显示
D_1:
        MOV   A, @R0
        MOV   DPTR, #TAB
        MOVC A, @A+DPTR
        CALL OUT_595
        NOP
        SETB S_UP_595
        DEC   R0
        CLR   S_UP_595
        DJNZ R7, D_1
        RET
;**************************************************
;七段码表
;**************************************************
TAB:
        DB 00111111B
        DB 00000110B
        DB 01011011B
        DB 01001111B
        DB 01100110B
        DB 01101101B
        DB 01111101B
        DB 00000111B
        DB 01111111B
        DB 01101111B
        DB 00000000B
END
;=================================================
;程序在PROTEUS中运行的图像如下:

利用定时方式0,测量外部脉冲宽度(5到250ms),74HC595输出显示 - 非著名博主 - 电子信息角落


;插图链接:http://hi.baidu.com/%D7%F6%B6%F8%C2%DB%B5%C0/album/item/9ec1211a435ca094ac6e75af.html
;原题网址:http://zhidao.baidu.com/question/164310712.html

2017-10-08 20:28:17 ZLK1214 阅读数 2313
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    4006 人正在学习 去看看 朱有鹏

STM32F1系列的定时器中有DMA Burst Feature,配合参考手册上所讲的PWM输入模式,可以全自动地测量一组脉冲的宽度,期间CPU可做其他的事情。

DHT11传感器是单总线器件,主机端发出一个开始信号后,该器件会反馈给主机42个由高电平+低电平组成的脉冲。主机通过分析这些脉冲的时间宽度解码出器件发来的数据。

类似的器件还有红外遥控接收头,脉冲的个数也是固定的,只不过不需要发送起始信号,数据是随时都可能收到。


本例以DHT11传感器为例,DHT11的VCC接3.3V,数据线外接10kΩ的上拉电阻后接到单片机的PA1口上,对应的通道是定时器2的通道2。

板子完全是笔者自己焊的,接了一个8MHz的HSE晶振,谐振电容为20pF。程序的下载方式为USART1串口下载(通过右下角的开关切换BOOT0=1,PB2接10kΩ下拉电阻到GND),使用的下载软件是STMFlashLoader Demonstrator。BOOT0接10kΩ下拉电阻到GND,再接一个开关直接到VCC。开关闭合后按复位键可进入程序下载模式,开关断开时按复位键运行程序。

左下角的按键为复位按键,复位引脚NRST无需接上拉电阻,直接接一个0.1μF的电容到GND就行了,复位按键并联在电容器两端。最上面那个黑色的3脚器件是5V转3.3V的AMS1117电压转换器。

板子高清图:

程序下载软件:


【寄存器版程序】

#include <stdio.h>
#include <stm32f10x.h>

uint16_t data[84]; // DHT11需要传输84个数据, 即测量42个脉冲

#define DHT11_W0 (GPIOA->BRR = GPIO_BRR_BR1)
#define DHT11_W1 (GPIOA->BSRR = GPIO_BSRR_BS1)
//#define DHT11_R ((GPIOA->IDR & GPIO_IDR_IDR1) != 0) // 配置为开漏输出时可直接读取IDR寄存器, 无需切换为输入模式

// 延时n毫秒(0<n<6553)
void delay(uint16_t nms)
{
	TIM2->ARR = 10 * nms - 1;
	TIM2->PSC = 7199; // 72MHz/7200=10kHz -> 100us
	TIM2->CR1 = TIM_CR1_OPM | TIM_CR1_URS; // OPM=1: 自动关闭定时器, URS=1: UG=1时保持UIF=0
	TIM2->EGR = TIM_EGR_UG;
	TIM2->CR1 |= TIM_CR1_CEN;
	while ((TIM2->SR & TIM_SR_UIF) == 0);
	TIM2->SR &= ~TIM_SR_UIF;
}

// 工程属性里的Use MicroLIB必须打勾
int fputc(int ch, FILE *fp)
{
	if (fp == stdout)
	{
		if (ch == '\n')
		{
			while ((USART1->SR & USART_SR_TXE) == 0);
			USART1->DR = '\r';
		}
		while ((USART1->SR & USART_SR_TXE) == 0);
		USART1->DR = ch;
	}
	return ch;
}

void display(void)
{
	uint8_t i;
	uint8_t n = sizeof(data) / sizeof(uint16_t) - DMA1_Channel7->CNDTR; // 总个数减去DMA未传输的个数 = 成功测量的电平个数
	for (i = 0; i < n; i++)
	{
		printf("[ID%02d] ", i);
		if (i % 2 == 0)
			printf("High: %dus\n", data[i]); // 高电平的宽度 = CCR1
		else
			printf("Low: %dus\n", data[i] - data[i - 1]); // 低电平的宽度 = CCR2 - CCR1
	}
}

void measure(void)
{
	// 起始信号: 先拉低总线18ms, 然后释放总线
	DHT11_W0;
	delay(18);
	DHT11_W1;
	// 释放总线后可通过IDR寄存器直接读取总线上的电平(判断是否被器件拉低), 无需改变CRL中的配置
	
	DMA1_Channel7->CMAR = (uint32_t)data;
	DMA1_Channel7->CPAR = (uint32_t)&TIM2->DMAR;
	DMA1_Channel7->CNDTR = sizeof(data) / sizeof(uint16_t);
	DMA1_Channel7->CCR = DMA_CCR7_MSIZE_0 | DMA_CCR7_PSIZE_0 | DMA_CCR7_MINC | DMA_CCR7_EN; // 16位传输模式
	
	TIM2->ARR = 199; // 超时时间(高电平+低电平)定义为200us
	TIM2->PSC = 71; // 72MHz/72=1MHz -> 1us
	TIM2->CR1 = TIM_CR1_URS; // OPM必须为0, 否则只能测量一个脉冲
	TIM2->EGR = TIM_EGR_UG;
	
	// 因为总线最终会回到高电平, 所以最后一次采集肯定是通道2
	TIM2->CCMR1 = TIM_CCMR1_CC1S_1 | TIM_CCMR1_CC2S_0; // 通道1~2都连接到TIM2_CH2(PA1)引脚上
	TIM2->SMCR = TIM_SMCR_TS_2 | TIM_SMCR_TS_1 | TIM_SMCR_SMS_2; // 通道2上的事件使定时器清零
	TIM2->CCER = TIM_CCER_CC1E | TIM_CCER_CC1P | TIM_CCER_CC2E; // 通道1负责下降沿, 通道2负责上升沿
	
	TIM2->DCR = TIM_DCR_DBL_0 | (((uint8_t *)&TIM2->CCR1 - (uint8_t *)TIM2) >> 2); // 每次传输CCR1和CCR2两个寄存器的内容
	TIM2->DIER = TIM_DIER_CC2DE; // 打开输入捕获通道2的DMA请求
	
	TIM2->CR1 |= TIM_CR1_CEN; // 打开定时器, 开始测量
	while ((TIM2->SR & TIM_SR_UIF) == 0 && (DMA1->ISR & DMA_ISR_TCIF7) == 0); // 这期间CPU可以做其他事情
	
	TIM2->CR1 &= ~TIM_CR1_CEN; // 关闭定时器
	DMA1_Channel7->CCR &= ~DMA_CCR7_EN; // 关闭DMA
	if (DMA1->ISR & DMA_ISR_TCIF7)
		DMA1->IFCR = DMA_IFCR_CTCIF7; // 成功
	else
	{
		// 失败: 若脉冲长度超过允许值(TIM2->ARR), 则会导致定时器溢出, 此时给出错误信息
		TIM2->SR &= ~TIM_SR_UIF;
		printf("Timeout!!! Remaining: %d bytes\n", DMA1_Channel7->CNDTR);
	}
	display(); // 显示实际测量的数据
	
	// 完毕后恢复寄存器设置
	TIM2->SMCR = 0;
	TIM2->CCER = 0;
	TIM2->DIER = 0;
}

int main(void)
{
	RCC->AHBENR |= RCC_AHBENR_DMA1EN;
	RCC->APB1ENR = RCC_APB1ENR_TIM2EN;
	RCC->APB2ENR = RCC_APB2ENR_IOPAEN | RCC_APB2ENR_USART1EN;
	
	DHT11_W1; // 防止配置为输出模式后总线被拉低
	GPIOA->CRL = 0x44444464; // PA1接DHT11引脚, 设为开漏输出, 必须外接10K上拉电阻
	GPIOA->CRH = 0x444444b4; // PA9为USART1发送引脚, 设为复用推挽输出
	
	// 串口波特率设为110592
	USART1->BRR = 625;
	USART1->CR1 = USART_CR1_UE | USART_CR1_TE;
	
	while (1)
	{
		printf("-------------------------------------------------\n");
		measure();
		delay(5000);
	}
}
【程序运行结果】

-------------------------------------------------
[ID00] High: 10us
[ID01] Low: 83us
[ID02] High: 86us
[ID03] Low: 54us
[ID04] High: 23us
[ID05] Low: 54us
[ID06] High: 23us
[ID07] Low: 54us
[ID08] High: 70us
[ID09] Low: 54us
[ID10] High: 23us
[ID11] Low: 54us
[ID12] High: 70us
[ID13] Low: 54us
[ID14] High: 70us
[ID15] Low: 54us
[ID16] High: 23us
[ID17] Low: 54us
[ID18] High: 23us
[ID19] Low: 54us
[ID20] High: 23us
[ID21] Low: 54us
[ID22] High: 23us
[ID23] Low: 54us
[ID24] High: 23us
[ID25] Low: 54us
[ID26] High: 23us
[ID27] Low: 54us
[ID28] High: 23us
[ID29] Low: 54us
[ID30] High: 23us
[ID31] Low: 54us
[ID32] High: 23us
[ID33] Low: 54us
[ID34] High: 25us
[ID35] Low: 54us
[ID36] High: 23us
[ID37] Low: 54us
[ID38] High: 23us
[ID39] Low: 54us
[ID40] High: 23us
[ID41] Low: 54us
[ID42] High: 70us
[ID43] Low: 54us
[ID44] High: 70us
[ID45] Low: 54us
[ID46] High: 23us
[ID47] Low: 54us
[ID48] High: 23us
[ID49] Low: 54us
[ID50] High: 23us
[ID51] Low: 54us
[ID52] High: 23us
[ID53] Low: 54us
[ID54] High: 23us
[ID55] Low: 54us
[ID56] High: 23us
[ID57] Low: 54us
[ID58] High: 23us
[ID59] Low: 54us
[ID60] High: 23us
[ID61] Low: 54us
[ID62] High: 23us
[ID63] Low: 54us
[ID64] High: 23us
[ID65] Low: 54us
[ID66] High: 25us
[ID67] Low: 54us
[ID68] High: 23us
[ID69] Low: 54us
[ID70] High: 70us
[ID71] Low: 54us
[ID72] High: 23us
[ID73] Low: 54us
[ID74] High: 23us
[ID75] Low: 54us
[ID76] High: 23us
[ID77] Low: 54us
[ID78] High: 70us
[ID79] Low: 54us
[ID80] High: 23us
[ID81] Low: 54us
[ID82] High: 21us
[ID83] Low: 56us

由程序运行结果可知,主机发出起始信号后,隔了10μs后,DHT将总线拉低83μs,再释放86μs作为应答信号。最后连续发送40位数据,并以56μs的低电平结束,释放总线。

位数据0格式:50μs低电平 + 26~28μs高电平

位数据1格式:50μs低电平 + 70μs高电平

display函数中采用向下舍入的方式计算时间。若CNT=0,则认为是0μs;若CNT=10,则认为是10μs(实际持续的时间应该是10~11μs之间)。


【库函数版程序】

#include <stdio.h>
#include <stm32f10x.h>

uint16_t data[84]; // DHT11需要传输84个数据, 即测量42个脉冲

// 延时n毫秒(0<n<6553)
void delay(uint16_t nms)
{
	TIM_TimeBaseInitTypeDef tim;
	TIM_SelectOnePulseMode(TIM2, TIM_OPMode_Single); // 自动关闭定时器
	TIM_UpdateRequestConfig(TIM2, TIM_UpdateSource_Regular); // UG=1时保持UIF=0
	
	TIM_TimeBaseStructInit(&tim);
	tim.TIM_Period = 10 * nms - 1;
	tim.TIM_Prescaler = 7199; // 72MHz/7200=10kHz -> 100us
	TIM_TimeBaseInit(TIM2, &tim);
	TIM_Cmd(TIM2, ENABLE);
	while (TIM_GetFlagStatus(TIM2, TIM_FLAG_Update) == RESET);
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
}

// 工程属性里的Use MicroLIB必须打勾
int fputc(int ch, FILE *fp)
{
	if (fp == stdout)
	{
		if (ch == '\n')
		{
			while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
			USART_SendData(USART1, '\r');
		}
		while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
		USART_SendData(USART1, ch);
	}
	return ch;
}

void display(void)
{
	uint8_t i;
	uint8_t n = sizeof(data) / sizeof(uint16_t) - DMA_GetCurrDataCounter(DMA1_Channel7); // 总个数减去DMA未传输的个数 = 成功测量的电平个数
	for (i = 0; i < n; i++)
	{
		printf("[ID%02d] ", i);
		if (i % 2 == 0)
			printf("High: %dus\n", data[i]); // 高电平的宽度 = CCR1
		else
			printf("Low: %dus\n", data[i] - data[i - 1]); // 低电平的宽度 = CCR2 - CCR1
	}
}

void measure(void)
{
	DMA_InitTypeDef dma;
	TIM_ICInitTypeDef tim_ic;
	TIM_TimeBaseInitTypeDef tim;
	
	// 起始信号: 先拉低总线18ms, 然后释放总线
	GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_RESET);
	delay(18);
	GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_SET);
	// 释放总线后可通过IDR寄存器直接读取总线上的电平(判断是否被器件拉低), 无需改变CRL中的配置
	
	dma.DMA_BufferSize = sizeof(data) / sizeof(uint16_t); // 要测量的脉冲个数
	dma.DMA_DIR = DMA_DIR_PeripheralSRC;
	dma.DMA_M2M = DMA_M2M_Disable;
	dma.DMA_MemoryBaseAddr = (uint32_t)data;
	dma.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // CCR寄存器为16位寄存器
	dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
	dma.DMA_Mode = DMA_Mode_Normal;
	dma.DMA_PeripheralBaseAddr = (uint32_t)&TIM2->DMAR; // 使用定时器DMA Burst Feature
	dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	dma.DMA_Priority = DMA_Priority_Low;
	DMA_Init(DMA1_Channel7, &dma);
	DMA_Cmd(DMA1_Channel7, ENABLE);
	
	TIM_SelectOnePulseMode(TIM2, TIM_OPMode_Repetitive); // 不自动关闭定时器, 否则只能测出第一个脉冲的宽度
	TIM_TimeBaseStructInit(&tim);
	tim.TIM_Period = 199; // 定义超时时间: 每组高电平+低电平持续的时间总和不得超过200us, 否则认为采集失败
	tim.TIM_Prescaler = 71; // 72MHz/72=1MHz -> 1us, 以微秒为单位
	TIM_TimeBaseInit(TIM2, &tim);
	
	// 因为总线最终会回到高电平, 所以最后一次采集肯定是通道2
	tim_ic.TIM_Channel = TIM_Channel_2;
	tim_ic.TIM_ICFilter = 0;
	tim_ic.TIM_ICPolarity = TIM_ICPolarity_Rising; // 通道1负责下降沿, 通道2负责上升沿
	tim_ic.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	tim_ic.TIM_ICSelection = TIM_ICSelection_DirectTI; // 通道1~2都连接到TIM2_CH2(PA1)引脚上
	TIM_PWMIConfig(TIM2, &tim_ic);
	
	// 通道2上的事件使定时器清零
	TIM_SelectInputTrigger(TIM2, TIM_TS_TI2FP2);
	TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset);
	
	// 每次触发通道2输出比较时产生两个16位的DMA请求, 分别传输CCR1和CCR2寄存器的内容
	TIM_DMAConfig(TIM2, TIM_DMABase_CCR1, TIM_DMABurstLength_2Transfers);
	TIM_DMACmd(TIM2, TIM_DMA_CC2, ENABLE);
	
	// 开始测量
	TIM_Cmd(TIM2, ENABLE); // 打开定时器
	while (TIM_GetFlagStatus(TIM2, TIM_FLAG_Update) == RESET && DMA_GetFlagStatus(DMA1_FLAG_TC7) == RESET); // 这期间CPU可以做其他事情
	TIM_Cmd(TIM2, DISABLE); // 关闭定时器
	DMA_Cmd(DMA1_Channel7, DISABLE); // 关闭DMA
	
	if (DMA_GetFlagStatus(DMA1_FLAG_TC7) == SET)
		DMA_ClearFlag(DMA1_FLAG_TC7); // 成功
	else
	{
		// 失败: 若脉冲长度超过允许值(TIM2->ARR), 则会导致定时器溢出, 此时给出错误信息
		TIM_ClearFlag(TIM2, TIM_FLAG_Update);
		printf("Timeout!!! Remaining: %d bytes\n", DMA_GetCurrDataCounter(DMA1_Channel7));
	}
	display(); // 显示实际测量的数据
	
	// 完毕后恢复定时器设置
	TIM_InternalClockConfig(TIM2); // 关闭定时器的Slave模式
	TIM_CCxCmd(TIM2, TIM_Channel_1, TIM_CCx_Disable); // 关闭输入捕获模式
	TIM_CCxCmd(TIM2, TIM_Channel_2, TIM_CCx_Disable);
	TIM_DMACmd(TIM2, TIM_DMA_CC2, DISABLE); // 关闭DMA请求
}

int main(void)
{
	GPIO_InitTypeDef gpio;
	USART_InitTypeDef usart;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
	
	// PA1接DHT11引脚, 设为开漏输出, 必须外接10K上拉电阻
	// 可直接读取端口电平, 无需切换为输入模式
	GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_SET); // 防止配置为输出模式后总线被拉低
	gpio.GPIO_Mode = GPIO_Mode_Out_OD;
	gpio.GPIO_Pin = GPIO_Pin_1;
	gpio.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOA, &gpio);
	
	// PA9为USART1发送引脚, 设为复用推挽输出
	gpio.GPIO_Mode = GPIO_Mode_AF_PP;
	gpio.GPIO_Pin = GPIO_Pin_9;
	gpio.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio);
	
	USART_StructInit(&usart);
	usart.USART_BaudRate = 115200;
	usart.USART_Mode = USART_Mode_Tx;
	USART_Init(USART1, &usart);
	USART_Cmd(USART1, ENABLE);
	
	while (1)
	{
		printf("-------------------------------------------------\n");
		measure();
		delay(5000);
	}
}
【程序运行结果】
-------------------------------------------------
[ID00] High: 1us
[ID01] Low: 82us
[ID02] High: 86us
[ID03] Low: 54us
[ID04] High: 23us
[ID05] Low: 54us
[ID06] High: 23us
[ID07] Low: 54us
[ID08] High: 70us
[ID09] Low: 54us
[ID10] High: 70us
[ID11] Low: 54us
[ID12] High: 23us
[ID13] Low: 54us
[ID14] High: 23us
[ID15] Low: 54us
[ID16] High: 23us
[ID17] Low: 54us
[ID18] High: 23us
[ID19] Low: 54us
[ID20] High: 23us
[ID21] Low: 54us
[ID22] High: 23us
[ID23] Low: 54us
[ID24] High: 23us
[ID25] Low: 54us
[ID26] High: 23us
[ID27] Low: 54us
[ID28] High: 23us
[ID29] Low: 54us
[ID30] High: 23us
[ID31] Low: 54us
[ID32] High: 23us
[ID33] Low: 54us
[ID34] High: 25us
[ID35] Low: 54us
[ID36] High: 23us
[ID37] Low: 54us
[ID38] High: 23us
[ID39] Low: 54us
[ID40] High: 23us
[ID41] Low: 54us
[ID42] High: 70us
[ID43] Low: 54us
[ID44] High: 70us
[ID45] Low: 54us
[ID46] High: 23us
[ID47] Low: 54us
[ID48] High: 23us
[ID49] Low: 54us
[ID50] High: 23us
[ID51] Low: 54us
[ID52] High: 23us
[ID53] Low: 54us
[ID54] High: 23us
[ID55] Low: 54us
[ID56] High: 23us
[ID57] Low: 54us
[ID58] High: 23us
[ID59] Low: 54us
[ID60] High: 23us
[ID61] Low: 54us
[ID62] High: 23us
[ID63] Low: 54us
[ID64] High: 23us
[ID65] Low: 54us
[ID66] High: 25us
[ID67] Low: 54us
[ID68] High: 23us
[ID69] Low: 54us
[ID70] High: 70us
[ID71] Low: 54us
[ID72] High: 23us
[ID73] Low: 54us
[ID74] High: 23us
[ID75] Low: 54us
[ID76] High: 70us
[ID77] Low: 54us
[ID78] High: 23us
[ID79] Low: 54us
[ID80] High: 23us
[ID81] Low: 54us
[ID82] High: 21us
[ID83] Low: 56us

使用库函数后,第一个测量数据出现了很明显的误差。这显然是因为在配置DMA和定时器输入捕获的时候浪费了很多时间。

在dma.DMA_BufferSize语句前打开定时器4,TIM_DMACmd语句后读取定时器4的CNT值,上述两步直接通过操作寄存器完成。定时器4的分频系数配置为0,测量出来的时间值为CNT=783。因此,库函数总共浪费的时间为784÷72≈10.9μs。


【根据测量的结果显示湿度和温度数据】

注意:DHT11测量结果的小数部分始终为0,所以本程序只显示整数结果。

#include <stdio.h>
#include <stm32f10x.h>

// 延时n毫秒(0<n<6553)
void delay(uint16_t nms)
{
	TIM_TimeBaseInitTypeDef tim;
	TIM_SelectOnePulseMode(TIM2, TIM_OPMode_Single); // 自动关闭定时器
	TIM_UpdateRequestConfig(TIM2, TIM_UpdateSource_Regular); // UG=1时保持UIF=0
	
	TIM_TimeBaseStructInit(&tim);
	tim.TIM_Period = 10 * nms - 1;
	tim.TIM_Prescaler = 7199; // 72MHz/7200=10kHz -> 100us
	TIM_TimeBaseInit(TIM2, &tim);
	TIM_Cmd(TIM2, ENABLE);
	while (TIM_GetFlagStatus(TIM2, TIM_FLAG_Update) == RESET);
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
}

// 工程属性里的Use MicroLIB必须打勾
int fputc(int ch, FILE *fp)
{
	if (fp == stdout)
	{
		if (ch == '\n')
		{
			while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
			USART_SendData(USART1, '\r');
		}
		while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
		USART_SendData(USART1, ch);
	}
	return ch;
}

void measure(void)
{
	uint8_t data[5] = {0};
	uint8_t i, j;
	uint16_t timing[84]; // DHT11需要传输84个数据, 即测量42个脉冲
	
	DMA_InitTypeDef dma;
	TIM_ICInitTypeDef tim_ic;
	TIM_TimeBaseInitTypeDef tim;
	
	// 起始信号: 先拉低总线18ms, 然后释放总线
	GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_RESET);
	delay(18);
	GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_SET);
	// 释放总线后可通过IDR寄存器直接读取总线上的电平(判断是否被器件拉低), 无需改变CRL中的配置
	
	dma.DMA_BufferSize = sizeof(timing) / sizeof(uint16_t); // 要测量的脉冲个数
	dma.DMA_DIR = DMA_DIR_PeripheralSRC;
	dma.DMA_M2M = DMA_M2M_Disable;
	dma.DMA_MemoryBaseAddr = (uint32_t)timing;
	dma.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // CCR寄存器为16位寄存器
	dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
	dma.DMA_Mode = DMA_Mode_Normal;
	dma.DMA_PeripheralBaseAddr = (uint32_t)&TIM2->DMAR; // 使用定时器DMA Burst Feature
	dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	dma.DMA_Priority = DMA_Priority_Low;
	DMA_Init(DMA1_Channel7, &dma);
	DMA_Cmd(DMA1_Channel7, ENABLE);
	
	TIM_SelectOnePulseMode(TIM2, TIM_OPMode_Repetitive); // 不自动关闭定时器, 否则只能测出第一个脉冲的宽度
	TIM_TimeBaseStructInit(&tim);
	tim.TIM_Period = 199; // 定义超时时间: 每组高电平+低电平持续的时间总和不得超过200us, 否则认为采集失败
	tim.TIM_Prescaler = 71; // 72MHz/72=1MHz -> 1us, 以微秒为单位
	TIM_TimeBaseInit(TIM2, &tim);
	
	// 因为总线最终会回到高电平, 所以最后一次采集肯定是通道2
	tim_ic.TIM_Channel = TIM_Channel_2;
	tim_ic.TIM_ICFilter = 0;
	tim_ic.TIM_ICPolarity = TIM_ICPolarity_Rising; // 通道1负责下降沿, 通道2负责上升沿
	tim_ic.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	tim_ic.TIM_ICSelection = TIM_ICSelection_DirectTI; // 通道1~2都连接到TIM2_CH2(PA1)引脚上
	TIM_PWMIConfig(TIM2, &tim_ic);
	
	// 通道2上的事件使定时器清零
	TIM_SelectInputTrigger(TIM2, TIM_TS_TI2FP2);
	TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset);
	
	// 每次触发通道2输出比较时产生两个16位的DMA请求, 分别传输CCR1和CCR2寄存器的内容
	TIM_DMAConfig(TIM2, TIM_DMABase_CCR1, TIM_DMABurstLength_2Transfers);
	TIM_DMACmd(TIM2, TIM_DMA_CC2, ENABLE);
	
	// 开始测量
	TIM_Cmd(TIM2, ENABLE); // 打开定时器
	while (TIM_GetFlagStatus(TIM2, TIM_FLAG_Update) == RESET && DMA_GetFlagStatus(DMA1_FLAG_TC7) == RESET); // 这期间CPU可以做其他事情
	TIM_Cmd(TIM2, DISABLE); // 关闭定时器
	DMA_Cmd(DMA1_Channel7, DISABLE); // 关闭DMA
	
	if (DMA_GetFlagStatus(DMA1_FLAG_TC7) == SET)
	{
		// 成功
		DMA_ClearFlag(DMA1_FLAG_TC7);
		if (timing[1] - timing[0] >= 70 && timing[1] - timing[0] <= 90 && timing[2] >= 70 && timing[2] <= 90) // 判断应答信号是否正确
		{
			// 分析收到的40位数据
			for (i = 0; i < 40; i++)
			{
				j = 2 * i + 3;
				if (timing[j] - timing[j - 1] < 40 || timing[j] - timing[j - 1] > 60)
					break;
				
				data[i >> 3] <<= 1;
				if (timing[j + 1] > 40)
					data[i >> 3] |= 1;
			}
			if (i == 40)
			{
				if (((data[0] + data[1] + data[2] + data[3]) & 0xff) == data[4]) // 数据校验
					printf("H:%d%% T:%d\n", data[0], data[2]); // 显示湿度和温度, 忽略小数部分
				else
					printf("Error!\n"); // 数据校验错误
			}
		}
	}
	else
		TIM_ClearFlag(TIM2, TIM_FLAG_Update); // 失败: 若脉冲长度超过允许值(TIM2->ARR), 则会导致定时器溢出, 此时给出错误信息
	
	// 完毕后恢复定时器设置
	TIM_InternalClockConfig(TIM2); // 关闭定时器的Slave模式
	TIM_CCxCmd(TIM2, TIM_Channel_1, TIM_CCx_Disable); // 关闭输入捕获模式
	TIM_CCxCmd(TIM2, TIM_Channel_2, TIM_CCx_Disable);
	TIM_DMACmd(TIM2, TIM_DMA_CC2, DISABLE); // 关闭DMA请求
}

int main(void)
{
	GPIO_InitTypeDef gpio;
	USART_InitTypeDef usart;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
	
	// PA1接DHT11引脚, 设为开漏输出, 必须外接10K上拉电阻
	// 可直接读取端口电平, 无需切换为输入模式
	GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_SET); // 防止配置为输出模式后总线被拉低
	gpio.GPIO_Mode = GPIO_Mode_Out_OD;
	gpio.GPIO_Pin = GPIO_Pin_1;
	gpio.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOA, &gpio);
	
	// PA9为USART1发送引脚, 设为复用推挽输出
	gpio.GPIO_Mode = GPIO_Mode_AF_PP;
	gpio.GPIO_Pin = GPIO_Pin_9;
	gpio.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio);
	
	USART_StructInit(&usart);
	usart.USART_BaudRate = 115200;
	usart.USART_Mode = USART_Mode_Tx;
	USART_Init(USART1, &usart);
	USART_Cmd(USART1, ENABLE);
	
	while (1)
	{
		measure();
		delay(1000);
	}
}
【程序运行结果】
H:44% T:25
H:45% T:24
H:44% T:25
H:45% T:24
H:44% T:25
H:45% T:24
H:45% T:24
H:44% T:25
H:45% T:24
H:45% T:24
H:45% T:24
H:44% T:25
H:44% T:25
H为湿度,T为温度。

脉宽测量51单片机定时器计数器

博文 来自: HopesunIce

52单片机定时器2

阅读数 2218

没有更多推荐了,返回首页