在 Atmel AVR studio 中使用 ATMega2560 读取 RC PWM 信号

Read RC PWM signal using ATMega2560 in Atmel AVR studio

我正在尝试将几个 PWM 信号从 RC 接收器读取到 ATMega 2560 中。我无法理解 ICRn 引脚的功能,因为它似乎用于所有三个比较寄存器。

RC PWM 信号的周期为 20ms,其中 2ms 的 HIGH 脉冲是有效上限值,1ms 是有效下限值。因此该值将从 1000us 扫描到 2000us。该周期应从脉冲的上升沿开始。

我已经将 16MHz 时钟按 8 进行了预分频以获得 2MHz 定时器,因此应该能够将信号测量到 0.5us 的精度,这足以满足我的要求。

请注意,我的 PWM 输出没有问题,这个问题专门针对 PWM 输入。

我的代码附在下面。我知道我必须使用 ICR3 和 ISR 来测量 PWM 值,但我不确定执行此操作的最佳程序。我也不知道如何检查测量值是否来自 PE3、PE4 或 PE5。这段代码正确吗?如何获得我正在寻找的值?

如有任何帮助,我们将不胜感激。

// Set pins as inputs
DDRE |= ( 0 << PE3 ) | ( 0 << PE4 ) | ( 0 << PE5 );

// Configure Timers for CTC mode
TCCR3A |=   ( 1 << WGM31 ) | ( 1 << WGM30 ); // Set on compare match
TCCR3B |=   ( 1 << WGM33 ) | ( 1 << WGM32 ) | ( 1 << CS31); // Set on compare match, prescale_clk/8

TCCR3B |=   ( 1 << ICES5 ) // Use rising edge as trigger

// 16 bit register - set TOP value
OCR3A = 40000 - 1;
OCR3B = 40000 - 1;
OCR3C = 40000 - 1;

TIMSK3 |= ( 1 << ICIE3 );

假设您想执行以下操作(我并不是说这可以让您准确测量 PWM 信号,但它可以作为如何设置寄存器的示例)

三个定时器 运行,每 20 毫秒重置一次。这可以通过将它们设置为 OCRnA 的 CTC 模式来完成:wgm3..0 = 0b0100。

//timer 1
TCCR4A = 0;
TCCR1B = (1<<CS11) | (1<<WGM12);
OCR1A = 40000 - 1;
//timer 3 (there's no ICP2)
TCCR3A = 0;
TCCR3B = (1<<CS31) | (1<<WGM32);
OCR3A = 40000 - 1;
//timer 4
TCCR4A = 0;
TCCR4B = (1<<CS41) | (1<<WGM42);
OCR4A = 40000 - 1;

现在将三个 pwm 信号中的每一个连接到它们自己的 ICPn 引脚(其中 n = 定时器)。检查数据表以了解不同 ICPn 引脚的位置(我很确定它不是 PE3、4、5)

假设 pwm 信号在 t=0 时开始为高电平,并在该周期的剩余时间内在高电平时间后变为低电平。您想要测量高电平时间,因此我们会在 ICPn 引脚上出现下降沿时触发每个中断。

TCCRnB 寄存器中的位 ICESn 设置为 0 将 select 下降沿(这已经在前面的代码块中完成)。

要触发中断,请设置相应的中断使能位:

TIMSK1 |= (1<<ICIE1);
TIMSK3 |= (1<<ICIE3);
TIMSK4 |= (1<<ICIE4);
sei();

现在每次触发 ICn 中断时,您都可以获取 ICRn 寄存器以查看下降沿发生的时间(clockperiods/8)。

几个月前我忘记了 post 我的解决方案,所以这里是...

最后我使用了 PPM 接收器,因此可以轻松编辑此代码以读取简单的 PWM。

在我的头文件中,我为我的项目使用的 6 通道接收器制作了一个结构。对于具有更多或更少频道的接收器,这可以根据需要进行更改。

#ifndef _PPM_H_
#define _PPM_H_

// Libraries included
#include <stdint.h>
#include <avr/interrupt.h>

struct orangeRX_ppm {
    uint16_t ch[6];
    };
volatile unsigned char ch_index;
struct orangeRX_ppm ppm;

/* Functions */
void ppm_input_init(void); // Initialise the PPM Input to CTC mode
ISR( TIMER5_CAPT_vect ); // Use ISR to handle CTC interrupt and decode PPM

#endif /* _PPM_H_ */

然后我的 .c 文件中有以下内容。

// Libraries included
#include <avr/io.h>
#include <stdint.h>
#include "ppm.h"

/*      PPM INPUT
 *      ---
 *      ICP5    Pin48 on Arduino Mega
 */
void ppm_input_init(void)
{
    DDRL |= ( 0 << PL1 ); // set ICP5 as an input

    TCCR5A = 0x00; // none
    TCCR5B = ( 1 << ICES5 ) | ( 1 << CS51); // use rising edge as trigger, prescale_clk/8
    TIMSK5 = ( 1 << ICIE5 ); // allow input capture interrupts

    // Clear timer 5
    TCNT5H = 0x00;
    TCNT5L = 0x00;
}

// Interrupt service routine for reading PPM values from the radio receiver.
ISR( TIMER5_CAPT_vect )
{
    // Count duration of the high pulse
    uint16_t high_cnt; 
    high_cnt = (unsigned int)ICR5L;
    high_cnt += (unsigned int)ICR5H * 256;

    /* If the duration is greater than 5000 counts then this is the end of the PPM signal 
     * and the next signal being addressed will be Ch0
     */
    if ( high_cnt < 5000 )
    {
        // Added for security of the array
        if ( ch_index > 5 )
        {
            ch_index = 5;
        }

        ppm.ch[ch_index] = high_cnt; // Write channel value to array

        ch_index++; // increment channel index
    }
    else
    {
        ch_index = 0; // reset channel index
    }

    // Reset counter
    TCNT5H = 0;
    TCNT5L = 0;

    TIFR5 = ( 1 << ICF5 ); // clear input capture flag
}

每当 ICP5 从低电平变为高电平时,此代码将使用一个 ISR 触发器。在此 ISR 中,16 位 ICR5 寄存器 "ICR5H<<8|ICR5L" 保存自上次从低电平变为高电平以来经过的预分频时钟脉冲数。此计数通常小于 2000 us。我已经说过,如果计数大于 2500us(5000 个计数),那么输入无效,下一个输入应该是 ppm.ch[0].

我附上了在我的示波器上看到的 PPM 图像。

这种读取 PPM 的方法非常有效,因为我们不需要保留轮询引脚来检查它们的逻辑电平。

不要忘记使用 sei() 命令启用中断。否则 ISR 永远不会 运行.