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

240 lines
9.3 KiB
Markdown
Raw Normal View History

2023-01-01 18:21:01 +03:00
---
title: "Простой частотомер на AVR - курсовая работа"
categories: ["mcu", "archive"]
date: 2015-08-16T00:00:00+03:00
draft: true
featured_image: miniature.jpg
---
Итак, пришло время время делать курсовые работы. Мне попалась тема "Частотомер с передачей данных по последовательному порту и динамической индикацией". Значит, будем использовать таймер, внешние прерывания и динамическую индикацию. Использовал семисегментный индикатор с четырьмя разрядами и общим анодом. Микроконтроллер выберем Atmega16. Именно с ним я не чувствовал дефицита ножек. Приступим.
<!--more-->
> :warning: Этот текст древний стыд. Не нужно принимать его всерьёз. Сейчас бы такой бред не сделал.
Работа заключается в следующем: нужно спроектировать схему устройства, развести печатку, а также изготовить корпус для готового устройства. В КОМПАС 3D, разумеется.
Для начала нам нужно определиться как именно считать частоту. Я решил использовать таймер, тактированный часовым кварцем и засекать количество внешних прерываний за секунду. Прерывания использовал по нарастающему фронту. Возможно, я не прав, но, во благо, на железе собирать ничего не требуется.
Делаем всё в протеусе. Схема получилась такая:
![Схема|786](freq_sch.png)
Пишем программу. Микроконтроллер должен считать импульсы в секунду и выводить их на семисегментный индикатор. А также отдавать данные по UART.
main.h:
```c
#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:
```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](pcb.png)
Вот такую красоту можно понаблюдать:
![|300](3d1.png)
Результат:
![|226](pcb1.png)
Теперь самое главное - сделать корпус. Отталкиваться нужно от размера самой печатки. Делаем чертежи каждой части корпуса, потом варганим из них модели. Затем собираем всё воедино.
![|300](draft.png) ![|300](compas1.png)![|300](compas2.png) ![|300](compas3.png)
Три отверстия сзади для проводов: вход для сигнала, питание и UART. В прямоугольный вырез спереди вставляется семисегментный индикатор и шлейфом соединяется с печатной платой. Сама плата прикручивается маленькими саморезами/болтами. Крышка тоже.
##### Ссылки:
[Чертежи и модели в КОМПАС-3D](freq-meter-KOMPAS.zip)
[Чертежи в Corel Draw](7seg-freq-corel.zip)
[Сама курсовая работа (docx)](avr-7seg-freq-curse.docx)
[Внешние прерывания МК AVR (samou4ka.net)](http://samou4ka.net/page/vneshnie-preryvanija-mk-avr)
[Таймеры МК AVR (samou4ka.net)](http://samou4ka.net/page/tajmer-schetchik-mikrokontrollerov-avr)
[Асинхронный режим таймера AVR (easyelectronics.ru)](http://easyelectronics.ru/avr-uchebnyj-kurs-asinxronnyj-rezhim-tajmera.html)
[Самые простые часы на AVR (easyelectronics.ru)](http://we.easyelectronics.ru/antonluba/samye-prostye-chasy-na-avr.html)
[Работа с UART на примере ATmega16 (alex-exe.ru)](http://alex-exe.ru/radio/avr/avr-uart/)