mmote.ru/content/posts/avr-freq-meter/index.md

9.3 KiB
Raw Permalink Blame History

title categories date draft featured_image
Простой частотомер на AVR - курсовая работа
mcu
archive
2015-08-16T00:00:00+03:00 true miniature.jpg

Итак, пришло время время делать курсовые работы. Мне попалась тема "Частотомер с передачей данных по последовательному порту и динамической индикацией". Значит, будем использовать таймер, внешние прерывания и динамическую индикацию. Использовал семисегментный индикатор с четырьмя разрядами и общим анодом. Микроконтроллер выберем Atmega16. Именно с ним я не чувствовал дефицита ножек. Приступим.

⚠️ Этот текст древний стыд. Не нужно принимать его всерьёз. Сейчас бы такой бред не сделал.

Работа заключается в следующем: нужно спроектировать схему устройства, развести печатку, а также изготовить корпус для готового устройства. В КОМПАС 3D, разумеется.

Для начала нам нужно определиться как именно считать частоту. Я решил использовать таймер, тактированный часовым кварцем и засекать количество внешних прерываний за секунду. Прерывания использовал по нарастающему фронту. Возможно, я не прав, но, во благо, на железе собирать ничего не требуется.

Делаем всё в протеусе. Схема получилась такая:

Схема|786

Пишем программу. Микроконтроллер должен считать импульсы в секунду и выводить их на семисегментный индикатор. А также отдавать данные по UART.

main.h:

#ifndef __MAIN_H_
#define __MAIN_H_
 
#include <stdint.h>
#include <stdbool.h>
 
typedef uint8_t byte;
 
byte buf[16];
 
uint32_t freq;
uint32_t freq_max;
uint32_t measure_buf;
 
// порт для сегментов
#define SEGMENTS_DDR DDRA
#define SEGMENTS_PORT PORTA
 
 
// порт для разрядов
#define DIGITS_DDR DDRC
#define DIGITS_PORT PORTC
 
 
#define SWITCH_TIME 45 // время между переключениями разрядов; чем меньше, тем меньше мерцает
 
#define INPUT 0x00
#define OUTPUT 0xFF
 
 
#define SYMBOLS_SIZE 11 // количество символов в таблице
byte symbols[SYMBOLS_SIZE] = //состояния пинов для символов
        {
                0b00111111, // 0
                0b00000110, // 1
                0b01011011, // 2
                0b01001111, // 3
                0b01100110, // 4
                0b01101101, // 5
                0b01111101, // 6
                0b00000111, // 7
                0b01111111, // 8
                0b01101111, // 9
                0b10000000 // .
        };
 
int main(void);
 
void switchDigit(byte digit); // показать нужный разряд, остальные погасить; значения от 0 до 3
void showDigit(byte number, bool dot); // вывести символ на разряд, с точкой или без; значения от 0 до SYMBOLS_SIZE
void initTimer(); // инициализация таймера (для нас - часового)
void initInterrupts(); //инициализация прерываний
void initUART(); //инициализация последовательного порта
void sendByte(byte b); //отправка байта по UART
void sendString(byte *str); //отправка строки по UART
 
#endif //__MAIN_H_

main.c:

#include "main.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdio.h>
 
 
int main(void) {
    SEGMENTS_DDR = OUTPUT; // настраиваем порты ввода-вывода
    DIGITS_DDR = OUTPUT;
    DIGITS_PORT = 0xFF;
 
    initTimer();
    initInterrupts();
    initUART();
 
    byte i;
    uint32_t num;
 
    sprintf(buf, "? Stabilization...\r\n");
    sendString(buf);
    _delay_ms(100); // ожидаем стабилизации таймера
    sprintf(buf, "? Init finished!\r\n");
    sendString(buf);
 
    while (1) {
 
        num = freq > 9999 ? 9999 : freq;
 
        for (i = 0; i < 4; ++i) { // перебираем все разряды, разбираем частоту на цифры
            switchDigit(i);
            switch (i) {
                case 0:
                    showDigit((byte) ((num / 1000) % 10), false);
                    break;
                case 1:
                    showDigit((byte) ((num / 100) % 10), false);
                    break;
                case 2:
                    showDigit((byte) ((num / 10) % 10), false);
                    break;
                case 3:
                    showDigit(num % 10, false);
                    break;
 
                default:
                    break;
            }
            _delay_ms(SWITCH_TIME);
        }
 
    }
}
 
ISR(TIMER2_OVF_vect) { // прерывание таймера при переполнении
    freq = measure_buf;
    if( freq > freq_max) freq_max = freq;
    measure_buf = 0;
 
 
    sprintf(buf, "> Freq: %dHz, ", freq); // отправляем данные
    sendString(buf);
    sprintf(buf, "Max: %dHz\r\n", freq_max);
    sendString(buf);
 
 
}
 
ISR(INT0_vect) { //внешнее прерывание
    measure_buf++;
}
 
void showDigit(byte digit, bool dot) {
    SEGMENTS_PORT = ~symbols[digit > SYMBOLS_SIZE - 1 ? 10 : digit] | (dot ? symbols[10] : 0);
}
 
void switchDigit(byte number) {
    DIGITS_PORT = number < 4 ? (byte) (1 << number) : 0x00;
}
 
void initTimer() {
    ASSR |= _BV(AS2); // асинхронный режим, тактируемся от часового кварца
    TCCR2 = _BV(CS20) | _BV(CS22); //предделитель 128, одна секунда
    TIMSK |= _BV(TOIE2); // включаем таймер
}
 
void initInterrupts() {
    MCUCR = (1 << ISC01) | (1 << ISC00); //прерывание по растущему форонту
    GICR = (1 << INT0); //включаем прерывание на INT0
    sei(); // разрешаем прерывания
}
 
void initUART() {
    // выставляем скорость: 9600 при частоте 8МГц
    // UBRR=8000000/(16*9600)-1=51.0833, округляем = 51 (0x33)
    UBRRH = 0x00;
    UBRRL = 0x33;
 
    // Разрешаем приём и передачу
    UCSRB = (1 << RXEN) | (1 << TXEN);
    UCSRB |= (1 << RXCIE);
 
    // устанавливаем формат: 8 бит данных, 2 стоп бита
    UCSRC = (1 << URSEL) | (1 << USBS) | (3 << UCSZ0);
}
 
void sendByte(byte b) {
    while ( !(UCSRA & (1<<UDRE)) ); // ожидаем завершения передачи
    UDR = b; // записываем байт в буфер
}
 
void sendString(byte * str) {
    while (*str != 0) sendByte(*str++); // побайтно отправляем строку
}

Разводим печатку:

|300

Вот такую красоту можно понаблюдать:

|300

Результат:

|226

Теперь самое главное - сделать корпус. Отталкиваться нужно от размера самой печатки. Делаем чертежи каждой части корпуса, потом варганим из них модели. Затем собираем всё воедино.

|300 |300|300 |300

Три отверстия сзади для проводов: вход для сигнала, питание и UART. В прямоугольный вырез спереди вставляется семисегментный индикатор и шлейфом соединяется с печатной платой. Сама плата прикручивается маленькими саморезами/болтами. Крышка тоже.

Ссылки:

Чертежи и модели в КОМПАС-3D

Чертежи в Corel Draw

Сама курсовая работа (docx)

Внешние прерывания МК AVR (samou4ka.net)

Таймеры МК AVR (samou4ka.net)

Асинхронный режим таймера AVR (easyelectronics.ru)

Самые простые часы на AVR (easyelectronics.ru)

Работа с UART на примере ATmega16 (alex-exe.ru)