【方法】STM32F103C8单片机通过定时器DMA测量脉冲宽度,无需CPU干预(以DHT11传感器为例)

本文介绍如何利用STM32F1系列单片机的定时器和DMA功能实现DHT11传感器数据的自动测量。通过配置定时器的PWM输入模式和DMA的突发传输功能,能够准确测量DHT11发送的42个高低电平脉冲,进而解析出湿度和温度数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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为温度。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值