我目前正在开展一个项目,我正在设计一个嵌入式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();
}
引脚更改中断的初始化代码看起来合法。考虑到,这是一些测试代码,您并不期望它能够正确处理编码器旋转。
但不清楚电气连接是如何进行的?你使用外部上拉电阻吗?因为代码中没有启用它们。
变量
duty
正在 main()
中使用,并且也从中断例程中进行更改。因此必须标记为volatile
。否则,编译器可能会在循环中重用相同的值(在main()
中),而不从内存中读取实际值。
使用引脚更改中断可能不是处理编码器的最佳方法,因为编码器容易弹跳。您可能必须实施某种防抖方法。
我正在尝试控制 PD7 处的 PWM 信号。
ATmega88 数据表指定 OC0A 位于 PD6,OC0B 位于 PD5。我没有看到任何连接到 PD7 的 OutputCompare (PWM) 功能!
除此之外,你的代码还有很多小问题:
volatile
duty
duty
由多个线程(主线程和 ISR)使用,因此应声明为 volatile
。
但是,缺失的
volatile
不会在中造成问题
while (1)
{
OCR0A = duty;
}
因为这会写入
uint8_t
(即unsigned char
),并且由于严格的别名规则,编译器必须假设此写入可能会更改duty
,这也是unsigned char
。而且缺失的 volatile
也不会在 ISR 中引起问题,所以问题一定是在其他地方......
在 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 去抖
额外的好处是您在原理图以及如何连接控制器和编码器方面有更多的自由。