您的位置:首页 > 其它

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

2017-10-08 20:28 597 查看
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为温度。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息