【方法】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电压转换器。
![](http://img.blog.csdn.net/20171008203840290?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWkxLMTIxNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
板子高清图:
![](http://img.blog.csdn.net/20171008203845123?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWkxLMTIxNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
程序下载软件:
![](http://img.blog.csdn.net/20171008204427861?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWkxLMTIxNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
【寄存器版程序】
由程序运行结果可知,主机发出起始信号后,隔了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之间)。
【库函数版程序】
使用库函数后,第一个测量数据出现了很明显的误差。这显然是因为在配置DMA和定时器输入捕获的时候浪费了很多时间。
在dma.DMA_BufferSize语句前打开定时器4,TIM_DMACmd语句后读取定时器4的CNT值,上述两步直接通过操作寄存器完成。定时器4的分频系数配置为0,测量出来的时间值为CNT=783。因此,库函数总共浪费的时间为784÷72≈10.9μs。
【根据测量的结果显示湿度和温度数据】
注意:DHT11测量结果的小数部分始终为0,所以本程序只显示整数结果。
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:25H为湿度,T为温度。
相关文章推荐
- 利用51系列单片机定时器功能实现测量脉冲宽度
- [STM32F10x] 利用定时器测量脉冲宽度
- 单片机笔记---测量计算输入的脉冲宽度
- C#通过重写Panel改变边框颜色与宽度的方法
- 奶爸业余单片机学习之:定时器以工作方式1运行时的初值的计算方法(51单片机)
- 【程序】STM32F1单片机I2C中Packet Error Checking(PEC)校验功能和DMA的使用方法
- 奶爸业余单片机学习之:定时器使用方法——非中断,TF0(TF1)值软件重置法
- 单片机测量方波的频率、占空比及相位差的方法
- C#测量程序运行时间及cpu使用时间实例方法
- 无需密码通过ssh执行rsync来同步文件的方法
- 单片机软件定时器的使用方法
- 英特尔® Performance Counter Monitor(PCM)——测量 CPU 利用率的更好方法
- MCS-51测量输入脉冲宽度,精度1ms
- 利用STM32F103R8T6内部的温度传感器测量单片机的温度
- 【电子基础】单片机定时器实用方法总结
- 【STM库应用】stm32 之 TIM (详解二 脉冲宽度、周期测量)
- 利用定时方式0,测量外部脉冲宽度(5到250ms),74HC595输出显示
- 【电子基础】单片机定时器实用方法总结
- 英特尔® Performance Counter Monitor(PCM)--测量 CPU 利用率的更好方法
- STM32串口DMA超时接收方法,可大大节约CPU时间