使用 C 中的旋转编码器控制 PWM 占空比

问题描述 投票:0回答:2

我目前正在开展一个项目,我正在设计一个嵌入式AVR系统,使用ATmega88作为控制器。

我正在尝试输出 PWM 信号,并且想使用旋转编码器(使用中断)控制其占空比。

PWM 信号按预期输出,但尽管添加了功能,但编码器输入似乎没有执行任何操作。那么,假设我的 AVR 和编码器板工作正常,我的 C 代码有什么问题吗?

我使用 PC4 和 PC5(PCINT12 和 PCINT13)作为编码器输入,并尝试控制 PD7 处的 PWM 信号。我使用的IDE是Microchip studio。

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>

// headers
//#include "LEDs.h"
//#include "PWM.h"
//#include "Encoder.h"


unsigned char duty;
bool up;

int main(void) {

    up = true;
    duty = 0;
    OCR0A = duty;
    
    pin_init();
    PWM_init();
    interrupt_init();
    PD7_on();
    
    while(1) {
        OCR0A = duty;
    }
}

int pin_init() {
    
    DDRD = 0xFF;
    
    DDRC = 0x00;
    
    return 1;
}

int PWM_init() {
    
    //setting correct bits for the pwm waveform
    TCCR0A |= (1<<COM0A1) | (1<<COM0A0) | (1<<COM0B1)| (1<<COM0B0) | (1<<WGM01)| (1<<WGM00);


    //setting prescaler to 8
    TCCR0B |= (1<< CS01);
    
    return 1;
}

int interrupt_init() {
    
    PCICR = (1 << PCIE1); //enabling pin change interrupts for group
    PCMSK1 |= (1 << PCINT12) | (1 << PCINT13);
    
    sei(); //setting global interrupts
    
    return 1;
}

int PD7_on() {
    
    PORTD = (1<<PD7);   
    return 1;
}

int PD7_swich() {
    
    if(PORTD == (1<<PD7)) {
        
        PORTD = (0 << PD7);
    }else{
        
        PORTD = (1 << PD7);
    }
    
    return 1;
}

int inc_duty() {
    
    if(duty == 255) {
        up = false;
    }else if(duty == 0) {
        up = true;
    }
    
    if(up) {
        duty++;
    }else {
        duty--;
    }
    
    return 1;
}

ISR(PCINT1_vect, ISR_BLOCK) {
    
    PD7_swich();
    inc_duty();
}
c avr microchip encoder
2个回答
0
投票

引脚更改中断的初始化代码看起来合法。考虑到,这是一些测试代码,您并不期望它能够正确处理编码器旋转。

但不清楚电气连接是如何进行的?你使用外部上拉电阻吗?因为代码中没有启用它们。

变量

duty
正在
main()
中使用,并且也从中断例程中进行更改。因此必须标记为
volatile
。否则,编译器可能会在循环中重用相同的值(在
main()
中),而不从内存中读取实际值。

使用引脚更改中断可能不是处理编码器的最佳方法,因为编码器容易弹跳。您可能必须实施某种防抖方法。


0
投票

我正在尝试控制 PD7 处的 PWM 信号。

ATmega88 数据表指定 OC0A 位于 PD6OC0B 位于 PD5。我没有看到任何连接到 PD7 的 OutputCompare (PWM) 功能!

除此之外,你的代码还有很多小问题:

缺少
volatile
duty

的资格

duty
由多个线程(主线程和 ISR)使用,因此应声明为
volatile

但是,缺失的

volatile
不会在中造成问题

    while (1)
    {
        OCR0A = duty;
    }

因为这会写入

uint8_t
(即
unsigned char
),并且由于严格的别名规则,编译器必须假设此写入可能会更改
duty
,这也是
unsigned char
。而且缺失的
volatile
也不会在 ISR 中引起问题,所以问题一定是在其他地方......

切换 I/O 端口

在 ATmega88 上,您可以通过将

1
写入相应的
PIN
寄存器来切换端口位:

void PD7_swich (void)
{
    PIND = 1 << PD7; // *NOT* PIND |= 1 << PD7
}

c.f. ATmega88 手册中的“13.2.2 切换引脚”

如果该功能不可用,人们会

if (PORTD & (1 << PD7))
而不是
if (PORTD == 1 << PD7)

使用正确的原型

编译代码会产生大量警告,因为您在提供原型之前调用函数。拥有产生警告的代码总是一件坏事,即使真正的问题可能隐藏在良性警告中。您可以通过以下方式修复它们:

  • 在使用之前定义函数,或者
  • 在使用之前
  • 提供原型。
  • 例如,您可以将
main

放在模块末尾,或者在 main 之前添加原型,例如:

void PD7_swich (void); // In a header it it's supposed to be global.
static void PD7_swich (void); // In the module if it's supposed to be local.

avr-gcc ABI 通常需要正确的原型才能正确传递参数,因此建议您在命令行选项中添加 
-Wmissing-prototypes

-Wstrict-prototypes
,甚至更好的
-Werror=missing-prototypes
-Werror=strict-prototypes
到处返回 

int

也很奇怪,对于所有这些函数来说,最自然的返回类型是

void
。此外,参数列表
()
与 C 中的
(void)
不一样,因此您需要使用
(void)
而不是
()
输入 I/O 去抖

旋转编码器与普通开关一样,通常需要去抖。这在软件中最容易执行,因为它是可调节的并且不需要额外的部件。实现此目的的一种方法是在定时器 ISR 中每隔 5 毫秒采样一次相关端口信号。这比 I/O 信号上的 IRQing 可靠得多。

额外的好处是您在原理图以及如何连接控制器和编码器方面有更多的自由。

© www.soinside.com 2019 - 2024. All rights reserved.