AM2320单总线温湿度传感器使用AT32单片机中断读取及DMA读取的实现

AM2320单总线温湿度传感器常规驱动

AM2302,AM2320以及DHT22常规驱动都是采用IO引脚模拟时序读出的方式实现的。AM2320还可以使用I2C驱动,本文主要介绍单总线方式。

背景

目前的项目中,由于使用了RTOS,致模拟时序的方式会被其他线程或中断打断,导致读取失败,虽然偶尔失败一两次,不影响后续温湿度结果,同时也可以使用禁用调度或中断的方式来保证单总线的读取时序,但项目使用的AT32F437单片机,性能强劲,应该有更好的实现方式。下面将介绍中断DMA两种驱动的实现方式。

AM2320时序

AM2320单总线时序

中断驱动方式

实现思路

通过从机发送数据的下降沿来触发外部中断,中断里面记录当前的SysTick->VAL,一共41次下降沿完成数据传输,第一次下降沿为从机的响应的结束时刻,后续为数据。根据两次中断的间隔计算出脉冲的周期,判断是0或1。

  1. 主机发送完起始信号后,使用单总线的IO口来触发中断,下降沿一次,进入中断一次,在中断里面记录当前的SysTick->VAL
  2. 一共需传输41次,根据记录到的数值,计算脉冲周期并还原出温湿度数据。
  3. 具体实现关键代码如下:

/**
 * 函数功能: 使AM2302-DATA引脚变为上拉输入模式
 * 输入参数: 无
 * 返 回 值: 无
 * 说    明:无
 */
static void AM2302_Mode_IPU(void)
{
    gpio_init_type gpio_init_struct;

    gpio_default_para_init(&gpio_init_struct);
    gpio_init_struct.gpio_pins = AM2302_Dout_PIN;
    gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
    gpio_init_struct.gpio_pull = GPIO_PULL_UP;
    gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_init(AM2302_Dout_PORT, &gpio_init_struct);
}

/**
 * 函数功能: 使AM2302-DATA引脚变为开漏输出模式
 * 输入参数: 无
 * 返 回 值: 无
 * 说    明:无
 */
static void AM2302_Mode_Out_OD(void)
{
    gpio_init_type gpio_init_struct;

    gpio_default_para_init(&gpio_init_struct);
    gpio_init_struct.gpio_pins = AM2302_Dout_PIN;
    gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
    gpio_init_struct.gpio_out_type = GPIO_OUTPUT_OPEN_DRAIN;
    gpio_init_struct.gpio_pull = GPIO_PULL_UP;
    gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_init(AM2302_Dout_PORT, &gpio_init_struct);
}
// 一共下降沿41次,中断41次
#define FALLING_CNT (41)
__IO static uint8_t isr_nums = 0;
// 读出结果
__IO static uint8_t result[5] = {0};
// 读出结果
__IO static uint8_t us_result[FALLING_CNT] = {0};
// 暂存定时器计数值
__IO uint32_t dst_buffer[FALLING_CNT] = {0};
static void result_reset(void)
{
    rt_memset(result, 0, 5);
    rt_memset(us_result, 0, FALLING_CNT);
    rt_memset(dst_buffer, 0, FALLING_CNT * sizeof(uint32_t));
    isr_nums = 0;
}
/**
 * 函数功能: AM2302数据引脚中断函数,记录SysTick->VAL
 * 输入参数: 无
 * 返 回 值: 无
 * 说    明:无
 */
static uint8_t AM2302_Read_Isr(void)
{
    dst_buffer[isr_nums++] = SysTick->VAL;
}
void printf_bin_8(unsigned char num)
{
    int k;
    unsigned char *p = (unsigned char *)#

    for (int k = 7; k >= 0; k--) // 处理8个位
    {
        if (*p & (1 << k))
            rt_kprintf("1");
        else
            rt_kprintf("0");
    }
    rt_kprintf("\r\n");
}
/**
 * 函数功能: 一次完整的数据传输为40bit,高位先出
 * 输入参数: AM2302_Data:AM2302数据类型
 * 返 回 值: ERROR:  读取出错
 *           SUCCESS:读取成功
 * 说    明:8bit 湿度整数 + 8bit 湿度小数 + 8bit 温度整数 + 8bit 温度小数 + 8bit 校验和
 */
uint8_t AM2302_Read_TempAndHumidity(AM2302_Data_TypeDef *AM2302_Data)
{
    uint8_t temp;
    uint16_t humi_temp;
    uint16_t cnt = 0;
    uint8_t humi_high8bit; // 原始数据:湿度高8位
    uint8_t humi_low8bit;  // 原始数据:湿度低8位
    uint8_t temp_high8bit; // 原始数据:温度高8位
    uint8_t temp_low8bit;  // 原始数据:温度高8位
    uint8_t check_sum;     // 校验和
    /*输出模式*/
    AM2302_Mode_Out_OD();
    /*主机拉低*/
    AM2302_Dout_LOW();
    /*延时2ms*/
    rt_thread_mdelay(2);

    /*总线拉高 主机延时30us*/
    AM2302_Dout_HIGH();
    rt_hw_us_delay(30); // 延时30us
    /*主机设为输入 判断从机响应信号*/
    AM2302_Mode_IPU();
    /*判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行*/
    if (AM2302_Data_IN() == FALSE)
    {
        result_reset();
        // rt_kprintf("%d",get_system_us()-us_tick);

        /* 绑定中断,下降沿模式,回调函数名为PowOff_Isr */
        rt_pin_attach_irq(AM2302_Data_IN_PIN, PIN_IRQ_MODE_FALLING, AM2302_Read_Isr, RT_NULL);
        /* 使能中断 */
        rt_pin_irq_enable(AM2302_Data_IN_PIN, PIN_IRQ_ENABLE);
    }
    else
    {
        return ERROR;
    }

    cnt = 0;
    do
    {
        rt_thread_mdelay(2);
        if (++cnt > 3)
        {
            rt_pin_irq_enable(AM2302_Data_IN_PIN, PIN_IRQ_DISABLE);
            rt_kprintf("isr_nums1=%d", isr_nums);
            return ERROR;
        }
    } while (isr_nums < FALLING_CNT);
    rt_pin_irq_enable(AM2302_Data_IN_PIN, PIN_IRQ_DISABLE);
    // 打印中断读出的数据。
    for (int k = 0; k < FALLING_CNT; k++) //
    {
        rt_kprintf("%d ", dst_buffer[k]);
    }
    rt_kprintf("\r\n");
    // 计算每次响应的时间间隔,单位us
    rt_uint32_t reload = SysTick->LOAD;
    for (int k = 1; k < FALLING_CNT; k++) //
    {
        // 使用的SysTick向下计数,主频为240M故除以240,转为us
        if (dst_buffer[k] < dst_buffer[k - 1])
        {
            us_result[k] = ( dst_buffer[k - 1] - dst_buffer[k] ) / 240;
        }
        else
        {
            us_result[k] = (reload - dst_buffer[k] + dst_buffer[k - 1]) / 240;
        }
        rt_kprintf("%d ", us_result[k]);
    }
    rt_kprintf("\r\n");
    // 0 为从机响应时间 1到40为从机发出的脉冲周期时间 单位us
    for (int k = 1; k < FALLING_CNT; k++) //
    {
        if (us_result[k] >= 116) // 大于116us,判定为信号1
        {
            result[(k - 1) >> 3] |= 1 << (7 - ((k - 1) & 0x07));
            rt_kprintf("1 ");
        }
        else
            rt_kprintf("0 ");
    }
    rt_kprintf("\r\n");
    rt_kprintf("result %x %x %x %x %x\r\n", result[0], result[1], result[2], result[3], result[4]);
    printf_bin_8(result[0]);
    printf_bin_8(result[1]);
    printf_bin_8(result[2]);
    printf_bin_8(result[3]);
    printf_bin_8(result[4]);

    rt_kprintf("\r\n");
    humi_high8bit = result[0];
    humi_low8bit = result[1];
    temp_high8bit = result[2];
    temp_low8bit = result[3];
    check_sum = result[4];

    /*读取结束,引脚改为输出模式*/
    AM2302_Mode_Out_OD();
    // /*主机拉高*/
    AM2302_Dout_HIGH();

    /* 对数据进行处理 */
    humi_temp = humi_high8bit * 256 + humi_low8bit;
    AM2302_Data->humidity = (float)humi_temp / 10;
    humi_temp = temp_high8bit * 256 + temp_low8bit;
    AM2302_Data->temperature = (float)humi_temp / 10;

    /*检查读取的数据是否正确*/
    temp = humi_high8bit + humi_low8bit +
           temp_high8bit + temp_low8bit;
    if (check_sum == temp)
    {
        return SUCCESS;
    }
    else
        return ERROR;
}

打印输出如下:

191895 172811 153729 134641 115561 96475 77387 58301 39453 5841 226755 196383 177295 146923 127839 108751 89903 67577 48491 29405 10319 231233 212149 193067 162935 140609 121523 91151 72067 52979 33891 3519 224673 191659 161291 142205 123117 92747 73659 43287 13153 
79 79 79 79 79 79 79 78 140 79 126 79 126 79 79 78 93 79 79 79 79 79 79 125 93 79 126 79 79 79 126 78 137 126 79 79 126 79 126 125 
0 0 0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 1 0 1 1 0 0 1 0 1 1 
result 0 a8 1 22 cb
00000000
10101000
00000001
00100010
11001011
[I/drv.am2302] 读取AM2302成功!-->湿度为16.8 %RH ,温度为 29.0

优缺点

1.中断方式不受RTOS线程调度影响,但受中断响应速度影响,当RTOS频繁开关中断或程序中中断较多时,计时误差较大,会导致温湿度数据读取失败。
2.中断方式可以访问SysTick,无需使用定时器。

DMA驱动方式

实现思路

通过从机发送数据的下降沿来触发DMA记录当前的定时器周期数,一共41次下降沿完成数据传输,第一次下降沿为从机的响应的结束时刻,后续为数据。根据时钟周期数计算出脉冲的周期,判断是0或1。

  1. 主机发送完起始信号后,使用单总线的IO口来触发DMA,下降沿一次,将定时器计数值记录一次,代码中使用TMR4->cval,TMR4正计数。
  2. 一共需传输41次,根据记录到的数值,计算脉冲周期并还原出温湿度数据。
  3. 具体实现关键代码如下:
//一共下降沿41次,DMA传输41次
#define FALLING_CNT (41)
// 读出结果
static uint8_t result[5] = {0};
// us周期
static uint8_t us_result[FALLING_CNT] = {0};
//暂存定时器计数值
uint16_t dst_buffer[FALLING_CNT] = {0};
/**
 * 函数功能: 使AM2302-DATA引脚变为上拉输入模式
 * 输入参数: 无
 * 返 回 值: 无
 * 说    明:无
 */
static void AM2302_Mode_IPU(void)
{
    gpio_init_type gpio_init_struct;

    gpio_default_para_init(&gpio_init_struct);
    gpio_init_struct.gpio_pins = AM2302_Dout_PIN;
    gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
    gpio_init_struct.gpio_pull = GPIO_PULL_UP;
    gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_init(AM2302_Dout_PORT, &gpio_init_struct);
    scfg_exint_line_config(SCFG_PORT_SOURCE_GPIOD, SCFG_PINS_SOURCE11);
}

/**
 * 函数功能: 使AM2302-DATA引脚变为开漏输出模式
 * 输入参数: 无
 * 返 回 值: 无
 * 说    明:无
 */
static void AM2302_Mode_Out_OD(void)
{
    gpio_init_type gpio_init_struct;

    gpio_default_para_init(&gpio_init_struct);
    gpio_init_struct.gpio_pins = AM2302_Dout_PIN;
    gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
    gpio_init_struct.gpio_out_type = GPIO_OUTPUT_OPEN_DRAIN;
    gpio_init_struct.gpio_pull = GPIO_PULL_UP;
    gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_init(AM2302_Dout_PORT, &gpio_init_struct);
}
static void result_reset(void)
{
    rt_memset(result, 0, 5);
    rt_memset(us_result, 0, FALLING_CNT);
    rt_memset(dst_buffer, 0, FALLING_CNT * sizeof(uint16_t));
}
static void DMA_eline_start(void)
{
    crm_periph_clock_enable(CRM_DMA2_PERIPH_CLOCK, TRUE);
    gpio_init_type gpio_init_struct = {0};
    dma_init_type dma_init_struct;
    dmamux_gen_init_type dmamux_gen_init_struct;
    dmamux_sync_init_type dmamux_sync_init_struct;
    exint_init_type exint_init_struct;
    /* config pd11 for input mode */
    gpio_init_struct.gpio_pins = AM2302_Dout_PIN;
    gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
    gpio_init_struct.gpio_out_type = GPIO_OUTPUT_OPEN_DRAIN;
    gpio_init_struct.gpio_pull = GPIO_PULL_UP;
    gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_init(AM2302_Dout_PORT, &gpio_init_struct);

    scfg_exint_line_config(SCFG_PORT_SOURCE_GPIOD, SCFG_PINS_SOURCE11);

    /* dma2 channel4 configuration */
    dma_reset(DMA2_CHANNEL4);
    dma_init_struct.buffer_size = FALLING_CNT;
    dma_init_struct.direction = DMA_DIR_PERIPHERAL_TO_MEMORY;
    dma_init_struct.memory_base_addr = (uint32_t)&dst_buffer[0];
    dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_HALFWORD;
    dma_init_struct.memory_inc_enable = TRUE;
    dma_init_struct.peripheral_base_addr = (uint32_t)&TMR4->cval; 
    dma_init_struct.peripheral_data_width = DMA_MEMORY_DATA_WIDTH_HALFWORD;
    dma_init_struct.peripheral_inc_enable = FALSE;
    dma_init_struct.priority = DMA_PRIORITY_MEDIUM;
    dma_init_struct.loop_mode_enable = FALSE;
    dma_init(DMA2_CHANNEL4, &dma_init_struct);

    /* genertor1 configuration */
    dmamux_generator_default_para_init(&dmamux_gen_init_struct);
    dmamux_gen_init_struct.gen_polarity = DMAMUX_GEN_POLARITY_FALLING;
    dmamux_gen_init_struct.gen_request_number = 1;//AT32F437新版驱动2.1.4,这里为1,老版应该为0
    dmamux_gen_init_struct.gen_signal_sel = DMAMUX_GEN_ID_EXINT11;
    dmamux_gen_init_struct.gen_enable = TRUE;
    dmamux_generator_config(DMA2MUX_GENERATOR1, &dmamux_gen_init_struct);

    /* dmamux function enable */
    dmamux_enable(DMA2, TRUE);
    dmamux_init(DMA2MUX_CHANNEL4, DMAMUX_DMAREQ_ID_REQ_G1);

    /* enable dma channe4 */
    dma_channel_enable(DMA2_CHANNEL4, TRUE);
}
void printf_bin_8(unsigned char num)
{
    int k;
    unsigned char *p = (unsigned char *)&num;

    for (int k = 7; k >= 0; k--) // 处理8个位
    {
        if (*p & (1 << k))
            rt_kprintf("1");
        else
            rt_kprintf("0");
    }
    rt_kprintf("\r\n");
}
/**
 * 函数功能: 一次完整的数据传输为40bit,高位先出
 * 输入参数: AM2302_Data:AM2302数据类型
 * 返 回 值: ERROR:  读取出错
 *           SUCCESS:读取成功
 * 说    明:8bit 湿度整数 + 8bit 湿度小数 + 8bit 温度整数 + 8bit 温度小数 + 8bit 校验和
 */
uint8_t AM2302_Read_TempAndHumidity(AM2302_Data_TypeDef *AM2302_Data)
{
    uint8_t temp;
    uint16_t humi_temp;
    uint16_t cnt = 0;
    uint8_t humi_high8bit; // 原始数据:湿度高8位
    uint8_t humi_low8bit;  // 原始数据:湿度低8位
    uint8_t temp_high8bit; // 原始数据:温度高8位
    uint8_t temp_low8bit;  // 原始数据:温度高8位
    uint8_t check_sum;     // 校验和
    /*输出模式*/
    AM2302_Mode_Out_OD();
    /*主机拉低*/
    AM2302_Dout_LOW();
    /*延时2ms*/
    rt_thread_mdelay(2);

    /*总线拉高 主机延时30us*/
    AM2302_Dout_HIGH();
    rt_hw_us_delay(30); // 延时30us
    AM2302_Mode_IPU();
    /*判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行*/
    if (AM2302_Data_IN() == FALSE)
    {
        result_reset();
        DMA_eline_start();
    }
    else
    {
        rt_kprintf("从机无响应 退出\r\n");
        return ERROR;
    }
    cnt = 0;
    do
    {
        rt_thread_mdelay(2);
        if (++cnt > 3)
        {
            // 超时后,关闭DMA,并禁用发生器,避免从机响应时触发DMA
            dma_channel_enable(DMA2_CHANNEL4, FALSE);
            /* genertor1 configuration */
            dmamux_generator_default_para_init(&dmamux_gen_init_struct);
            dmamux_generator_config(DMA2MUX_GENERATOR1, &dmamux_gen_init_struct);
            // 超时退出
            rt_kprintf("DMA 超时退出\r\n");
            rt_kprintf("dtcnt %d \r\n", DMA2_CHANNEL4->dtcnt);

            return ERROR;
        }
    } while (!dma_flag_get(DMA2_FDT4_FLAG));
    rt_kprintf("dtcnt %d \r\n", DMA2_CHANNEL4->dtcnt);
    //关闭DMA,并禁用发生器,避免从机响应时触发DMA
    dma_channel_enable(DMA2_CHANNEL4, FALSE);
    /* genertor1 configuration */
    dmamux_generator_default_para_init(&dmamux_gen_init_struct);
    dmamux_generator_config(DMA2MUX_GENERATOR1, &dmamux_gen_init_struct);
    // 打印dma从定时器读出的数据。
    for (int k = 0; k < FALLING_CNT; k++) //
    {
        rt_kprintf("%d ", dst_buffer[k]);
    }
    rt_kprintf("\r\n");
    // 计算每次响应的时间间隔,单位us
    for (int k = 1; k < FALLING_CNT; k++) //
    {
        // 使用的定时器向上计数,主频为240M故除以240,转为us
        if (dst_buffer[k] < dst_buffer[k - 1])
        {
            us_result[k] = (65535 - dst_buffer[k - 1] + dst_buffer[k] ) / 240;
        }
        else
        {
            us_result[k] = (dst_buffer[k] - dst_buffer[k - 1]) / 240;
        }
        rt_kprintf("%d ", us_result[k]);
    }
    rt_kprintf("\r\n");
    // 0 为从机响应时间 1到40为从机发出的脉冲周期时间 单位us
    for (int k = 1; k < FALLING_CNT; k++) //
    {
        if (us_result[k] >= 116) // 大于116us,判定为信号1
        {
            result[(k - 1) >> 3] |= 1 << (7 - ((k - 1) & 0x07));
            rt_kprintf("1 ");
        }
        else
            rt_kprintf("0 ");
    }
    rt_kprintf("\r\n");
    rt_kprintf("result %x %x %x %x %x\r\n", result[0], result[1], result[2], result[3], result[4]);

    printf_bin_8(result[0]);
    printf_bin_8(result[1]);
    printf_bin_8(result[2]);
    printf_bin_8(result[3]);
    printf_bin_8(result[4]);

    rt_kprintf("\r\n");
    humi_high8bit = result[0];
    humi_low8bit = result[1];
    temp_high8bit = result[2];
    temp_low8bit = result[3];
    check_sum = result[4];

    // /*读取结束,引脚改为输出模式*/
    AM2302_Mode_Out_OD();
    // /*主机拉高*/
    AM2302_Dout_HIGH();

    /* 对数据进行处理 */
    humi_temp = humi_high8bit * 256 + humi_low8bit;
    AM2302_Data->humidity = (float)humi_temp / 10;
    humi_temp = temp_high8bit * 256 + temp_low8bit;
    AM2302_Data->temperature = (float)humi_temp / 10;

    /*检查读取的数据是否正确*/
    temp = humi_high8bit + humi_low8bit +
           temp_high8bit + temp_low8bit;
    if (check_sum == temp)
    {
        return SUCCESS;
    }
    else
        return ERROR;
}

打印输出如下:

dtcnt 0 
17991 37087 56187 9751 28849 47947 1509 20609 39467 7565 37953 57051 21903 40999 60095 13657 43807 613 19711 38809 57907 11469 30563 49655 2975 36599 1449 31837 62223 27071 46163 11011 29865 62889 27739 46833 391 30775 49871 14721 44865 
79 79 79 79 79 79 79 78 140 126 79 126 79 79 79 125 93 79 79 79 79 79 79 78 140 126 126 126 126 79 126 78 137 126 79 79 126 79 126 125 
0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 1 0 0 0 0 0 0 0 0 1 1 1 1 1 0 1 0 1 1 0 0 1 0 1 1 
result 0 d1 0 fa cb
00000000
11010001
00000000
11111010
11001011

[I/drv.am2302] 读取AM2302成功!-->湿度为20.9 %RH ,温度为 25.0

优缺点

1.DMA方式不受RTOS线程调度影响,不受中断影响,稳定可靠。
2.DMA无法访问SysTick,只能读取定时器的计数值。
3.要占用DMA,在DMA使用频繁的项目中要考虑DMA冲突。

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值