我最近编写了代码,旨在测量 arduino uno 超声波模块 HC-SR04 检测到的范围。然而,打印的范围值总是错误的。范围值从来都不准确,经常会跳动 3000 厘米到 5 厘米。
我非常感谢您对这个 DIY 项目的帮助。谢谢大家! :D
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <avr/interrupt.h>
// Define macros for bit manipulation
#define bitSet(reg, n) (reg |= 1 << n)
#define bitRead(reg, n) (reg & (1 << n))
#define bitClear(reg, n) (reg &= ~(1 << n))
// Define constants for clock frequency and baud rate
#define FOSC 16000000
#define BAUD 9600
#define MYUBRR FOSC / 16 / BAUD - 1
// Define pin assignments
#define TRIG_PIN PB1
#define ECHO_PIN PB2
// Declare volatile variables for storing time and distance
volatile unsigned long startTime = 0;
volatile unsigned long endTime = 0;
volatile unsigned long duration = 0;
volatile int distance = 0;
// Function prototypes
void USART_Init(unsigned int ubrr);
void USART_Transmit(unsigned char data);
void txString(const char *pStr);
// Function to initialize USART communication
void USART_Init(unsigned int ubrr)
{
// Set baud rate
UBRR0H = (unsigned char)(ubrr >> 8);
UBRR0L = (unsigned char)ubrr;
// Enable receiver and transmitter
UCSR0B = (1 << RXEN0) | (1 << TXEN0);
// Set frame format: 8 data bits, 1 stop bit
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}
// Function to transmit a single character over USART
void USART_Transmit(unsigned char data)
{
// Wait for empty transmit buffer
while (!(UCSR0A & (1 << UDRE0)))
;
// Put data into buffer, sends the data
UDR0 = data;
}
// Function to transmit a string of characters over USART
void txString(const char *pStr)
{
while (*pStr != '\0')
{
USART_Transmit(*pStr);
pStr++;
}
}
// Interrupt service routine for Timer 1 capture event
ISR(TIMER1_CAPT_vect)
{
if (bitRead(PINB, ECHO_PIN)) {
startTime = ICR1;
} else {
endTime = ICR1;
duration = endTime - startTime;
distance = duration * 0.017 / 2;
}
}
int main(void)
{
// Initialize USART communication
USART_Init(MYUBRR);
// Set trigger pin as output and echo pin as input
DDRB |= (1 << TRIG_PIN);
DDRB &= ~(1 << ECHO_PIN);
// Set timer 1 for input capture mode with noise canceler and prescaler of 8
TCCR1B |= (1 << ICNC1) | (1 << ICES1) | (1 << CS11);
// Enable input capture interrupt
TIMSK1 |= (1 << ICIE1);
// Enable global interrupts
sei();
// Main loop
while (1)
{
// Trigger ultrasonic sensor
PORTB |= (1 << TRIG_PIN);
_delay_us(10);
PORTB &= ~(1 << TRIG_PIN);
// Transmit distance over USART
char formattedDistance[20];
sprintf(formattedDistance, "Distance: %d cm\n", distance);
txString(formattedDistance);
_delay_ms(500); // Wait for 1 second
}
}
问题出在中断例程上:
ICR1
是一个16位寄存器。但你将它分配给 unsigned long
,它是 32 位的。如果您有计时器溢出,则持续时间测量不会自然溢出。
您必须将
startTime
、endTime
和 duration
声明为 unsigned int
。最佳做法是使用 #include stdint.h
,以便您可以指定整数的大小,该大小可能因平台而异。
正如 Lundin 指出的,让 ISR 尽可能短,例如
#include "stdint.h"
uint16_t duration = 0;
uint16_t startTime = 0;
uint16_t endTime = 0;
// Interrupt service routine for Timer 1 capture event
ISR(TIMER1_CAPT_vect)
{
if (bitRead(PINB, ECHO_PIN)) {
startTime = ICR1;
} else {
endTime = ICR1;
duration = endTime - startTime;
}
}
然后在主循环中转换为距离。通常,在没有浮点单元 (FPU) 的 MCU 上,您需要使用整数算术进行计算。为了简单起见,您可以将它们保留为浮点算术,但请注意它们将花费更长的时间。
// Convert to distance
float twice_the_distance = (float)duration * 0.017f;
uint16_t distance = ((uint16_t)twice_the_distance) >> 1; // Divide by two is a simple shift operation
顺便说一句,当你想要测量时,我还会明确地重置、启动和停止计时器,这样你就可以确保在正确的时刻测量到时间。