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 -> 100usTIM2->CR1 = TIM_CR1_OPM | TIM_CR1_URS; // OPM=1: 自动关闭定时器, URS=1: UG=1时保持UIF=0TIM2->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]); // 高电平的宽度 = CCR1elseprintf("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; // 超时时间(高电平+低电平)定义为200usTIM2->PSC = 71; // 72MHz/72=1MHz -> 1usTIM2->CR1 = TIM_CR1_URS; // OPM必须为0, 否则只能测量一个脉冲TIM2->EGR = TIM_EGR_UG;// 因为总线最终会回到高电平, 所以最后一次采集肯定是通道2TIM2->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; // 关闭DMAif (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发送引脚, 设为复用推挽输出// 串口波特率设为110592USART1->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=0TIM_TimeBaseStructInit(&tim);tim.TIM_Period = 10 * nms - 1;tim.TIM_Prescaler = 7199; // 72MHz/7200=10kHz -> 100usTIM_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]); // 高电平的宽度 = CCR1elseprintf("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 Featuredma.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);// 因为总线最终会回到高电平, 所以最后一次采集肯定是通道2tim_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); // 关闭DMAif (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=0TIM_TimeBaseStructInit(&tim);tim.TIM_Period = 10 * nms - 1;tim.TIM_Prescaler = 7199; // 72MHz/7200=10kHz -> 100usTIM_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 Featuredma.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);// 因为总线最终会回到高电平, 所以最后一次采集肯定是通道2tim_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); // 关闭DMAif (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]); // 显示湿度和温度, 忽略小数部分elseprintf("Error!\n"); // 数据校验错误}}}elseTIM_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为温度。

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

  1. 蓝桥杯单片机555定时器频率测量 非常简单的教程 能直接运行

    首先推荐B站小蜜蜂老师的视频课程,讲的蛮好,看完定时器前两章再看555定时器模块瞬间就悟了. 以上大概花费一个来小时,时间不够或者是暂时比较急的话也可以将就看我下边简易版的内容. 原理简介 原理其实非 ...

  2. 基于单片机的云台姿态测量系统设计(二)

    本文接上文基于单片机的云台姿态测量系统设计(一),主要对选用的姿态传感器进行介绍和简单的原理分析,涉及到MEMS加速度计和陀螺仪的原理.卡尔曼滤波的原理等. 基于单片机的云台姿态测量系统设计(一) 基 ...

  3. 51单片机定时器及其应用(2)(测量脉冲宽度)

    51单片机定时器及其应用(2)(测量脉冲宽度) 上一篇文章讲了如何使用51单片机的定时器功能制作一个简易的数字钟,上次有一个GATE位没有涉及到,因此这次来介绍一下定时器的这个GATE位的应用,也就是 ...

  4. STM32CubeMX | 使用STM32定时器的PWM输入模式测量脉冲宽度和周期

    STM32CubeMX | 使用STM32定时器的PWM输入模式测量脉冲宽度和周期 目录 STM32CubeMX | 使用STM32定时器的PWM输入模式测量脉冲宽度和周期 1.介绍 2.STM32C ...

  5. [STM32F10x] 利用定时器测量脉冲宽度

    转载http://www.cnblogs.com/mr-bike/p/4199751.html 硬件:STM32F103C8T6 平台: ARM-MDk V5.11 前面一篇文章讲过如何利用定时器测量 ...

  6. 单片机软件定时器的使用方法

    单片机软件定时器的使用方法 特别声明:文章是原创但是本文讲述的思想是在国外的开源代码中借鉴的 初学者在编写单片机程序时经常会用到延时函数,但是当系统逐步复杂以后(没有复杂到使用操作系统)延时会因为延时 ...

  7. 单片机定时器精准定时_PIC单片机的定时器精准计时的计算

    关于PIC单片机的定时器精准计时的计算 在此用了16C711单片机的TMR0做定时中断,希望实现精准计时,在程序中,TMR0用了晶振的32分频,初值#0FCH,因此POPBEAR兄弟计算出每个定时中断 ...

  8. 单片机定时器_51单片机的定时器如何计算初值?

    在学习单片机的时候,我们发现很多功能都是通过中断来实现的.之前也举过烧水的例子来阐述中断,今天就讲解一下定时器赋初值的方法.8位的定时器最大可计数2的8次方为256,16位的定时器最大可计数2的16次 ...

  9. 以中断方法设计单片机秒、分脉冲发生器

    以中断方法设计单片机秒.分脉冲发生器. 假定P1.0每秒产生一个机器周期的正脉冲,P1.1每分钟产生一个机器周期的正脉冲. 单片机是89C51. 悬赏分:50 - 解决时间:2010-6-21 18: ...

最新文章

  1. 水平,垂直居中的15种方法
  2. tf.boolean_mask
  3. [20171227]表的FULL_HASH_VALUE值的计算2
  4. 晚安科大20211130
  5. Git——git push 错误[error: src refspec master does not match any]解决方案
  6. 找不到php的版本,php – 在任何版本中都找不到请求的包…
  7. Typora图片上传和加载问题解决方案
  8. DOM对象与Jquery对象区别
  9. Linux虚拟化KVM-Qemu分析(十一)之virtqueue
  10. 4K视频在线看,网速跟不上怎么办?
  11. linux目录蓝色,前言linux系统默认目录颜色是蓝色的,在黑背景下看不清楚,可以通过以下2种方法修改ls查看的颜色。方法:1、拷贝/etc/DIR_COLORS文件为...
  12. 深入解析Windows操作系统(笔记4)
  13. 白山搜索引擎优化收费_白山SEO优化_专业搜索引擎优化、整站优化、快速排名公司...
  14. 实时应用监控平台CAT
  15. 摘抄“GPU Programming And Cg Language Primer 1rd Edition” 中文名“GPU编程与CG语言之阳春白雪下里巴人”
  16. 采集全国疫情数据(Python)
  17. 计算机硬件 OR CX 1,计算机硬件复习提纲
  18. [开发]resin+spring+struts配搭在线上常见的三个问题
  19. 在微信小游戏中实现语音互动
  20. GC是什么?为什么要用GC?

热门文章

  1. 【从0到1搭建LoRa物联网】2、终端设备开发方式
  2. (转)flex dataGrid 编辑
  3. 五款PPT的素材黑科技
  4. eMMC(二)——分区管理
  5. electron-updater 自动更新
  6. HP3055提示 soanner bulb warming up
  7. Word2019输入(码字)或删除操作出现卡顿问题
  8. Velocity中使用FCKeditor(FCKeditor for java)
  9. php859微电影短视频分享网站
  10. 深度linux系统任务栏毛玻璃,操作系统中常见的「毛玻璃」效果是怎么设计出来的?...