Электроника / Atmel AVR
Добавить в избранное

Примеры на Си для микроконтроллеров Atmel AVR

Примеры различных программ на языке Си для микроконтроллеров Atmel AVR
картинка к записи Примеры на Си для микроконтроллеров Atmel AVR

Примеры на Си для микроконтроллеров Atmel AVR

Здесь представлены примеры различных программ на языке Си для микроконтроллеров Atmel AVR. Все примеры написаны под микроконтроллер ATmega16, поэтому при переносе на другие МК семейства AVR это нужно учитывать. Тактовая частота микроконтроллера во всех примерах 8 МГц (используется тактирование от внутреннего генератора). Код примеров разбит на блоки и снабжен комментариями. Проекты написаны в среде Eclipse. К сожалению настроек и материала по установке и настройке не сохранилось, Вы можете почитать об альтернативных IDE для языков C и C++ по ссылке или можете попробовать IDE Code::Blocks, о которой я уже рассказал в другом материале. Также можно использовать данные проекты и в среде AVR studio (изменится только структура файлов проекта).

Документация по ATmega16 смотреть PDF

Документация по ATmega16 краткая сводка смотреть PDF.

изображение микроконтроллера atmega16 atmel avr


Blink

– Самый простой пример. К порту C подключены 8 светодиодов. Светодиоды зажигаются логической единицей на линии порта. В цикле светодиоды порта включаются и выключаются. Свеобразный аналог Hello World в мире встраиваемых систем.

Для микроконтроллера ATmega16 с использованием Atmel AVR, вот простой пример кода на языке C, который мигает 8 светодиодами, подключенными к порту C:

#include <avr/io.h>
#include <util/delay.h>

int main(void) {
    // Настроим порт C как выход
    DDRC = 0xFF;  // 0xFF в двоичной форме: 11111111

    while (1) {
        // Включаем светодиоды
        PORTC = 0xFF;
        _delay_ms(500); // Задержка 500 миллисекунд

        // Выключаем светодиоды
        PORTC = 0x00;
        _delay_ms(500); // Задержка 500 миллисекунд
    }

    return 0;
}

В этом коде мы настраиваем порт C (порты C0-C7) как выход, и затем в бесконечном цикле включаем и выключаем светодиоды с интервалом в 500 миллисекунд.

Не забудьте подключить светодиоды к соответствующим выводам порта C микроконтроллера и добавить необходимые библиотеки для AVR (avr/io.h и util/delay.h). Этот код является базовым примером для начала работы с микроконтроллером ATmega16 и светодиодами.


IO Ports

– В данном примере рассматривается работа с портами ввода-вывода. К порту C подключены 8 светодиодов (линии 0-7). К линии 2 порта D подключена кнопка, с подтяжкой на землю. При нажатии кнопка выдает на линию 0 порта С уровень логической единицы. Цикл программы организован следующим образом: при запуске включается бегущий огонь, сначала загорается светодиод на линии 0 порта C, затем на линии 1 и т.д. По достижении линии 7 направление бегущего огня меняется (от 7 к 0). При нажатии на кнопку бегущий огонь останавливается и загораются одновременно все светодиоды. После повторного нажатия на кнопку бегущий огонь продолжает перемещаться с места остановки.

Примерный код может выглядеть вот так:

#include <avr/io.h>
#include <util/delay.h>

// Определения портов и битов для светодиодов
#define LED_PORT C
#define LED_DDR DDRC
#define LED_PIN PINC

// Определения для кнопки
#define BUTTON_PORT D
#define BUTTON_DDR DDRD
#define BUTTON_PIN PIND
#define BUTTON_BIT 2

void initIO() {
    // Инициализация порта C для светодиодов
    LED_DDR = 0xFF; // Установить все линии порта C как выходы

    // Инициализация порта D для кнопки
    BUTTON_DDR &= ~(1 << BUTTON_BIT); // Установить линию 2 порта D как вход
    BUTTON_PORT |= (1 << BUTTON_BIT); // Включить подтяжку к +5V
}

int main() {
    initIO();

    uint8_t leds = 1; // Начинаем с первого светодиода
    int8_t direction = 1; // Направление движения бегущего огня (1 - вправо, -1 - влево)
    uint8_t buttonState = 0; // Состояние кнопки
    uint8_t lastButtonState = 0; // Предыдущее состояние кнопки

    while (1) {
        // Чтение состояния кнопки
        buttonState = (BUTTON_PIN & (1 << BUTTON_BIT)) ? 0 : 1;

        // Проверка на нажатие кнопки
        if (buttonState && !lastButtonState) {
            direction *= -1; // Изменение направления бегущего огня
            _delay_ms(100); // Задержка для устранения дребезга кнопки
        }
        lastButtonState = buttonState;

        // Управление светодиодами для бегущего огня
        LED_PORT = (1 << leds);

        if (leds == 0) {
            direction = 1;
        } else if (leds == 7) {
            direction = -1;
        }

        leds += direction;

        _delay_ms(100); // Задержка между шагами бегущего огня
    }

    return 0;
}

Этот код инициализирует порты, управляет светодиодами и реагирует на состояние кнопки. Бегущий огонь движется по светодиодам, и его направление меняется при нажатии кнопки. Пожалуйста, убедитесь, что вы правильно подключили светодиоды и кнопку к соответствующим портам и линиям микроконтроллера ATmega16.


Dynamic Indication

– В данном примере рассматривается работа с 7-сегментным индикатором. В моём случае он имеет 4 разряда (цифры). Поскольку у меня на плате установлены транзисторы для управления разрядами, то управление осуществляется выводом логической единицы и на разряды и на сегменты. Схема подключения следующая: к линиям 0-7 порта C подключены сегменты индикатора, а к линиям 0-3 порта В разряды индикатора. При запуске на индикатор выводятся цифры 1 2 3 4.

Для управления 4-разрядным 7-сегментным индикатором на микроконтроллере ATmega16 от Atmel с использованием C, вам потребуется подключить индикатор к портам В и С микроконтроллера. Вот пример программы, которая выводит цифры с 1 по 4 на индикатор:

#include <avr/io.h>
#include <util/delay.h>

// Макросы для управления выводами портов В и С
#define SEGMENT_PORT PORTC
#define DIGIT_PORT   PORTB
#define SEGMENT_DDR  DDRC
#define DIGIT_DDR    DDRB

// Функция для отображения цифры на индикаторе
void displayDigit(uint8_t digit) {
    // Массив, который содержит шаблоны для каждой цифры (0-9)
    // Порядок сегментов: a, b, c, d, e, f, g
    const uint8_t segments[] = {
        0b00111111, // 0
        0b00000110, // 1
        0b01011011, // 2
        0b01001111, // 3
        0b01100110, // 4
        0b01101101, // 5
        0b01111101, // 6
        0b00000111, // 7
        0b01111111, // 8
        0b01101111  // 9
    };

    SEGMENT_PORT = segments[digit];
}

int main(void) {
    // Настройка портов В и С на вывод
    SEGMENT_DDR = 0xFF; // Устанавливаем все линии порта C как выходы
    DIGIT_DDR = 0x0F;   // Устанавливаем нижние 4 линии порта В как выходы

    while (1) {
        // Отображаем цифры с 1 по 4 на индикаторе
        for (uint8_t digit = 1; digit <= 4; digit++) {
            DIGIT_PORT = (1 << (digit - 1)); // Выбираем текущий разряд
            displayDigit(digit);             // Отображаем текущую цифру
            _delay_ms(1000);                 // Ждем 1 секунду
        }
    }
}

Обратите внимание, что в этом примере используется макрос _delay_ms из библиотеки util/delay.h для создания задержек между цифрами. Вы должны также удостовериться, что настройка портов и выводов индикатора соответствует вашей конкретной схеме подключения.


UART

– В данном примере рассматривается периферийного модуля UART (универсальный асинхронный приёмопередатчик). Модуль UART можно настроить как на работу с прерываниями, так и без них (вручную, путём работы с флагами). Пример работает следующим образом: при получении байта, МК переходит в обработчик прерывания (используется только прерывание по приёму данных) и разбирает численное значение байта (0-255) на цифры, которые и выводятся на 7-сегментный индикатор. Схема подключения аналогична предыдущему примеру. Передача осуществляется по двум линиям UART (порт D линии 0-1), к которым необходимо подключить линии RX и TX преобразователя USB-UART. Для настройкки без прерываний необходимо обнулить бит RXCIE в регистре UCSRB и вручную опрашивать интерфейс в основном цикле программы.

Пример ниже демонстрирует использование модуля UART на микроконтроллере ATmega16 от Atmel для приема данных и отображения их на 7-сегментном индикаторе. В этом примере используется прерывание при приеме данных.

#include <avr/io.h>
#include <avr/interrupt.h>

// Макросы для управления выводами портов
#define SEGMENT_PORT PORTC
#define DIGIT_PORT   PORTB
#define SEGMENT_DDR  DDRC
#define DIGIT_DDR    DDRB

// Функция для отображения цифры на индикаторе (аналогична предыдущему примеру)
void displayDigit(uint8_t digit) {
    const uint8_t segments[] = { /* ... ваш массив с шаблонами сегментов ... */ };
    SEGMENT_PORT = segments[digit];
}

// Инициализация UART
void UART_Init(uint16_t baud_rate) {
    uint16_t ubrr_value = F_CPU / 16 / baud_rate - 1;

    // Установка скорости передачи данных
    UBRRH = (uint8_t)(ubrr_value >> 8);
    UBRRL = (uint8_t)ubrr_value;

    // Включение приема и передачи данных
    UCSRB = (1 << RXEN) | (1 << TXEN);

    // Установка формата кадра: 8 бит данных, 1 стоп-бит
    UCSRC = (1 << URSEL) | (1 << UCSZ1) | (1 << UCSZ0);
}

// Обработчик прерывания при приеме данных
ISR(USART_RXC_vect) {
    uint8_t received_byte = UDR; // Чтение принятого байта

    // Разбор численного значения байта и отображение на индикаторе
    uint8_t digit = received_byte % 10;
    displayDigit(digit);

    // Выполните здесь дополнительные действия при приеме данных, если необходимо
}

int main(void) {
    // Инициализация портов и индикатора, а также инициализация UART
    SEGMENT_DDR = 0xFF;
    DIGIT_DDR = 0x0F;
    UART_Init(9600);

    // Включение прерываний
    sei();

    while (1) {
        // Выполните здесь другие действия, если необходимо
    }

    return 0;
}

Этот пример настраивает UART для приема данных от внешнего источника, а затем использует обработчик прерывания для разбора принятых байтов и отображения их на 7-сегментном индикаторе. Обратите внимание, что настройка скорости передачи (baud_rate) должна соответствовать настройкам вашего внешнего устройства, которое отправляет данные по UART.


Clock

– В данном примере рассматривается реализация простых часов с 7-сегментным индикатором и парой кнопок. Только здесь уже требуется 6 разрядов, хотя секунды можно опустить. Кнопки с подтяжкой на землю. При нажатии кнопка выдает на линию высокий логический уровень. Индикатор подключается как и в предыдущих примерах (сегменты к порту C, разряды к порту B), а кнопки к линиям 2-3 порта D. Кнопка используется PD2 для установки минут, а PD3 для установки часов. По нажатию каждой из кнопок увеличивается значение соответствующего разряда (минуты или часы).

Для реализации простых часов с 7-сегментным индикатором и двумя кнопками на микроконтроллере ATmega16, вы можете использовать следующий пример кода на языке C:

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

// Макросы для управления выводами портов
#define SEGMENT_PORT PORTC
#define DIGIT_PORT   PORTB
#define SEGMENT_DDR  DDRC
#define DIGIT_DDR    DDRB
#define BUTTON_PIN   PIND

// Глобальные переменные для часов и минут
uint8_t hours = 0;
uint8_t minutes = 0;

// Функция для отображения числа на 7-сегментном индикаторе (аналогична предыдущим примерам)
void displayNumber(uint8_t number) {
    const uint8_t segments[] = { /* ... ваш массив с шаблонами сегментов ... */ };
    SEGMENT_PORT = segments[number];
}

// Функция для обработки кнопок
void handleButtons() {
    if ((BUTTON_PIN & (1 << PD2)) == 0) {
        // Кнопка PD2 нажата (установка минут)
        minutes = (minutes + 1) % 60;
        _delay_ms(250); // Задержка для подавления дребезга кнопки
    }

    if ((BUTTON_PIN & (1 << PD3)) == 0) {
        // Кнопка PD3 нажата (установка часов)
        hours = (hours + 1) % 24;
        _delay_ms(250); // Задержка для подавления дребезга кнопки
    }
}

int main(void) {
    // Инициализация портов, индикатора и кнопок
    SEGMENT_DDR = 0xFF;
    DIGIT_DDR = 0x0F;
    DDRD &= ~((1 << PD2) | (1 << PD3)); // Устанавливаем PD2 и PD3 как входы
    PORTD |= (1 << PD2) | (1 << PD3);   // Подтягиваем PD2 и PD3 к VCC

    while (1) {
        // Обработка кнопок для настройки времени
        handleButtons();

        // Отображение времени (часы и минуты) на индикаторе
        displayNumber(hours / 10);
        DIGIT_PORT = (1 << 0);
        _delay_ms(5);
        DIGIT_PORT = 0x00;

        displayNumber(hours % 10);
        DIGIT_PORT = (1 << 1);
        _delay_ms(5);
        DIGIT_PORT = 0x00;

        displayNumber(minutes / 10);
        DIGIT_PORT = (1 << 2);
        _delay_ms(5);
        DIGIT_PORT = 0x00;

        displayNumber(minutes % 10);
        DIGIT_PORT = (1 << 3);
        _delay_ms(5);
        DIGIT_PORT = 0x00;
    }

    return 0;
}

В этом примере часы устанавливаются с использованием двух кнопок, одна из которых увеличивает минуты, а другая - часы. Пожалуйста, обеспечьте корректное подключение 7-сегментного индикатора и кнопок к соответствующим портам микроконтроллера ATmega16.


DS18B20

– В данном примере рассматривается работа с цифровым датчиком температуры DS18B20. Показания температуры выводятся на 7-сегментный индикатор. Вывод DQ датчика поключен к ноге (пину) PD5. Линия должна быть подтянута к плюсу питания резистором на 4.7-10 кОм (согласно документации). Датчик опрашивается каждые 5 секунд. Температура выводится на 4-разрядный индикатор: знак, два разряда на целуюю часть и один на вещественную. Документация к датчику здесь.

Для работы с датчиком температуры DS18B20 на микроконтроллере ATmega16 без использования библиотеки OneWire (но никто не запрещает её Вам использовать), Вам нужно реализовать протокол взаимодействия с датчиком вручную. Вот пример кода на C, который опрашивает DS18B20 и отображает температуру на 4-разрядном 7-сегментном индикаторе:

#include <avr/io.h>
#include <util/delay.h>

// Макросы для управления выводами портов
#define SEGMENT_PORT PORTC
#define DIGIT_PORT   PORTB
#define SEGMENT_DDR  DDRC
#define DIGIT_DDR    DDRB
#define DQ_PIN      PIND
#define DQ_PORT     PORTD
#define DQ_DDR      DDRD
#define DQ_BIT      PD5

// Функция для инициализации OneWire
void OneWire_Init() {
    DQ_DDR |= (1 << DQ_BIT);
    DQ_PORT |= (1 << DQ_BIT); // Подтяжка к плюсу
}

// Функция для отправки бита
void OneWire_WriteBit(uint8_t bit) {
    DQ_DDR |= (1 << DQ_BIT);
    DQ_PORT &= ~(1 << DQ_BIT); // Преамбула
    _delay_us(2);
    if (bit) {
        DQ_PORT |= (1 << DQ_BIT); // Отправка 1
    }
    _delay_us(60); // Ожидание окончания бита
    DQ_PORT |= (1 << DQ_BIT); // Отпускание линии
}

// Функция для чтения бита
uint8_t OneWire_ReadBit() {
    uint8_t bit = 0;
    DQ_DDR |= (1 << DQ_BIT);
    DQ_PORT &= ~(1 << DQ_BIT); // Преамбула
    _delay_us(2);
    DQ_DDR &= ~(1 << DQ_BIT); // Переключаем линию на вход
    _delay_us(10); // Задержка перед чтением
    if (DQ_PIN & (1 << DQ_BIT)) {
        bit = 1;
    }
    _delay_us(50); // Ожидание окончания бита
    return bit;
}

// Функция для отправки байта
void OneWire_WriteByte(uint8_t byte) {
    for (uint8_t i = 0; i < 8; i++) {
        OneWire_WriteBit(byte & 1);
        byte >>= 1;
    }
}

// Функция для чтения байта
uint8_t OneWire_ReadByte() {
    uint8_t byte = 0;
    for (uint8_t i = 0; i < 8; i++) {
        byte |= (OneWire_ReadBit() << i);
    }
    return byte;
}

// Функция для запуска конверсии температуры
void DS18B20_StartConversion() {
    OneWire_Init();
    OneWire_WriteByte(0xCC); // Пропустить поиск устройств
    OneWire_WriteByte(0x44); // Запустить конверсию
    OneWire_Init();
}

// Функция для чтения температуры
double DS18B20_ReadTemperature() {
    OneWire_Init();
    OneWire_WriteByte(0xCC); // Пропустить поиск устройств
    OneWire_WriteByte(0xBE); // Прочитать данные

    uint16_t raw_temperature = OneWire_ReadByte() | (OneWire_ReadByte() << 8);

    // Расчет температуры
    int16_t temp_int = raw_temperature >> 4;
    uint8_t temp_frac = (raw_temperature & 0x0F) * 625 / 100;

    return (double)temp_int + (double)temp_frac / 100.0;
}

// Функция для отображения числа на 7-сегментном индикаторе (аналогична предыдущим примерам)
void displayNumber(uint8_t number) {
    const uint8_t segments[] = { /* ... ваш массив с шаблонами сегментов ... */ };
    SEGMENT_PORT = segments[number];
}

int main(void) {
    // Инициализация портов и индикатора
    SEGMENT_DDR = 0xFF;
    DIGIT_DDR = 0x0F;

    while (1) {
        // Запустить конверсию температуры
        DS18B20_StartConversion();
        _delay_ms(750); // Дайте датчику время на конверсию (по документации DS18B20)

        // Чтение и отображение температуры
        double temperature = DS18B20_ReadTemperature();
        uint8_t temp_int = (uint8_t)temperature;
        uint8_t temp_frac = (uint8_t)((temperature - temp_int) * 100.0);

        displayNumber(temp_int / 10);
        DIGIT_PORT = (1 << 0);
        _delay_ms(5);
        DIGIT_PORT = 0x00;

        displayNumber(temp_int % 10);
        DIGIT_PORT = (1 << 1);
        _delay_ms(5);
        DIGIT_PORT = 0x00;

        displayNumber(temp_frac / 10);
        DIGIT_PORT = (1 << 2);
        _delay_ms(5);
        DIGIT_PORT = 0x00;

        displayNumber(temp_frac % 10);
        DIGIT_PORT = (1 << 3);
        _delay_ms(5);
        DIGIT_PORT = 0x00;
    }

    return 0;
}

В этом примере кода датчик DS18B20 инициализируется, запускается конверсия, и затем температура считывается и отображается на 7-сегментном индикаторе. Пожалуйста, убедитесь, что вы правильно подключили DS18B20 к вашему микроконтроллеру и добавили массив segments, содержащий шаблоны сегментов для 7-сегментного индикатора.


DHT11

– В данном примере рассматривается работа с датчиком температуры и влажности DHT11. Показания температуры выводятся на 7-сегментный индикатор. Вывод DATA (также SDA) датчика поключен к ноге (пину) PD5. Линия должна быть подтянута к плюсу питания резистором на 4.7-10 кОм (согласно документации). Датчик опрашивается каждые 5 секунд. Измеряются температура и влажность, но на дисплей выводится только влажность (целое двухзначное число). Документация к датчику здесь.

Для работы с датчиком температуры и влажности DHT11 на микроконтроллере ATmega16 без использования дополнительных библиотек, Вам нужно реализовать протокол взаимодействия с датчиком вручную. Вот пример кода на языке C, который опрашивает DHT11 и выводит влажность на 7-сегментный индикатор:

#include <avr/io.h>
#include <util/delay.h>

// Макросы для управления выводами портов
#define SEGMENT_PORT PORTC
#define DIGIT_PORT   PORTB
#define SEGMENT_DDR  DDRC
#define DIGIT_DDR    DDRB
#define DATA_PIN    PIND
#define DATA_PORT   PORTD
#define DATA_DDR    DDRD
#define DATA_BIT    PD5

// Функция для задержки в миллисекундах
void delay_ms(uint16_t ms) {
    while (ms--) {
        _delay_ms(1);
    }
}

// Функция для отправки сигнала старта на DHT11
void DHT11_Start() {
    // Устанавливаем линию данных в 0 и удерживаем ее в этом состоянии в течение 18 мс
    DATA_DDR |= (1 << DATA_BIT);
    DATA_PORT &= ~(1 << DATA_BIT);
    delay_ms(18);
    DATA_PORT |= (1 << DATA_BIT);
    _delay_us(30);

    // Переключаем линию данных на вход и ждем, пока датчик начнет передачу данных
    DATA_DDR &= ~(1 << DATA_BIT);
    while (DATA_PIN & (1 << DATA_BIT));
    while (!(DATA_PIN & (1 << DATA_BIT));
}

// Функция для чтения данных с DHT11
uint8_t DHT11_ReadByte() {
    uint8_t byte = 0;
    for (uint8_t i = 0; i < 8; i++) {
        while (!(DATA_PIN & (1 << DATA_BIT))); // Ждем начала передачи бита
        _delay_us(30); // Ждем 30 мкс для определения бита
        if (DATA_PIN & (1 << DATA_BIT)) {
            byte |= (1 << (7 - i)); // Устанавливаем бит в 1
        }
        while (DATA_PIN & (1 << DATA_BIT)); // Ждем окончания передачи бита
    }
    return byte;
}

// Функция для чтения данных температуры и влажности с DHT11
void DHT11_ReadData(uint8_t *humidity, uint8_t *temperature) {
    uint8_t data[5];
    
    // Отправляем запрос на чтение данных
    DHT11_Start();
    
    // Читаем 40 бит данных (5 байт)
    for (uint8_t i = 0; i < 5; i++) {
        data[i] = DHT11_ReadByte();
    }
    
    // Проверяем контрольную сумму данных
    if (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) {
        *humidity = data[0];
        *temperature = data[2];
    }
}

// Функция для отображения числа на 7-сегментном индикаторе (аналогична предыдущим примерам)
void displayNumber(uint8_t number) {
    const uint8_t segments[] = { /* ... ваш массив с шаблонами сегментов ... */ };
    SEGMENT_PORT = segments[number];
}

int main(void) {
    // Инициализация портов и индикатора
    SEGMENT_DDR = 0xFF;
    DIGIT_DDR = 0x0F;
    
    uint8_t humidity = 0;
    
    while (1) {
        // Чтение данных с DHT11
        DHT11_ReadData(&humidity, NULL);
        
        // Отображение влажности на индикаторе (двузначное число)
        displayNumber(humidity / 10);
        DIGIT_PORT = (1 << 0);
        _delay_ms(5);
        DIGIT_PORT = 0x00;
        
        displayNumber(humidity % 10);
        DIGIT_PORT = (1 << 1);
        _delay_ms(5);
        DIGIT_PORT = 0x00;
        
        // Пауза перед следующим опросом
        delay_ms(5000); // Опрашиваем каждые 5 секунд
    }
    
    return 0;
}

Пожалуйста, убедитесь, что вы правильно подключили DHT11 к пину PD5 и добавили массив segments, содержащий шаблоны сегментов для 7-сегментного индикатора.


DHT22

– В данном примере рассматривается работа с датчиком температуры и влажности DHT22. По сравнению с DHT11 данный датчик обладает большей точностью и более широким диапазоном измерений. Показания температуры выводятся на 7-сегментный индикатор. Вывод DATA (также SDA) датчика поключен к ноге (пину) PD5. Линия должна быть подтянута к плюсу питания резистором на 4.7-10 кОм (хотя согласно документации это и необязательно). Датчик опрашивается каждые 5 секунд. Измеряются температура и влажность, но на дисплей выводится только влажность (вещественное двухзначное число с одним знаком после запятой). Документация к датчику здесь.

Для работы с датчиком температуры и влажности DHT22 на микроконтроллере ATmega16 без использования дополнительных библиотек, вам нужно реализовать протокол взаимодействия с датчиком вручную. Вот пример кода на языке C, который опрашивает DHT22 и выводит влажность на 7-сегментный индикатор с одним знаком после запятой:

#include <avr/io.h>
#include <util/delay.h>

// Макросы для управления выводами портов
#define SEGMENT_PORT PORTC
#define DIGIT_PORT   PORTB
#define SEGMENT_DDR  DDRC
#define DIGIT_DDR    DDRB
#define DATA_PIN    PIND
#define DATA_PORT   PORTD
#define DATA_DDR    DDRD
#define DATA_BIT    PD5

// Функция для задержки в миллисекундах
void delay_ms(uint16_t ms) {
    while (ms--) {
        _delay_ms(1);
    }
}

// Функция для отправки сигнала старта на DHT22
void DHT22_Start() {
    // Устанавливаем линию данных в 0 и удерживаем ее в этом состоянии в течение 1 мс
    DATA_DDR |= (1 << DATA_BIT);
    DATA_PORT &= ~(1 << DATA_BIT);
    delay_ms(1);
    DATA_PORT |= (1 << DATA_BIT);
    _delay_us(30);

    // Переключаем линию данных на вход и ждем, пока датчик начнет передачу данных
    DATA_DDR &= ~(1 << DATA_BIT);
    while (DATA_PIN & (1 << DATA_BIT));
    while (!(DATA_PIN & (1 << DATA_BIT));
}

// Функция для чтения данных с DHT22
uint8_t DHT22_ReadByte() {
    uint8_t byte = 0;
    for (uint8_t i = 0; i < 8; i++) {
        while (!(DATA_PIN & (1 << DATA_BIT))); // Ждем начала передачи бита
        _delay_us(30); // Ждем 30 мкс для определения бита
        if (DATA_PIN & (1 << DATA_BIT)) {
            byte |= (1 << (7 - i)); // Устанавливаем бит в 1
        }
        while (DATA_PIN & (1 << DATA_BIT)); // Ждем окончания передачи бита
    }
    return byte;
}

// Функция для чтения данных температуры и влажности с DHT22
void DHT22_ReadData(uint16_t *humidity, int16_t *temperature) {
    uint8_t data[5];
    
    // Отправляем запрос на чтение данных
    DHT22_Start();
    
    // Читаем 40 бит данных (5 байт)
    for (uint8_t i = 0; i < 5; i++) {
        data[i] = DHT22_ReadByte();
    }
    
    // Проверяем контрольную сумму данных
    if (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) {
        *humidity = (data[0] << 8) | data[1];
        *temperature = (data[2] << 8) | data[3];
    }
}

// Функция для отображения числа на 7-сегментном индикаторе (аналогична предыдущим примерам)
void displayNumber(uint8_t number) {
    const uint8_t segments[] = { /* ... ваш массив с шаблонами сегментов ... */ };
    SEGMENT_PORT = segments[number];
}

int main(void) {
    // Инициализация портов и индикатора
    SEGMENT_DDR = 0xFF;
    DIGIT_DDR = 0x0F;
    
    uint16_t humidity = 0;
    int16_t temperature = 0;
    
    while (1) {
        // Чтение данных с DHT22
        DHT22_ReadData(&humidity, &temperature);
        
        // Отображение влажности на индикаторе (двухзначное число с одним знаком после запятой)
        uint16_t humidity_int = humidity / 10;
        uint8_t humidity_frac = (humidity % 10);
        
        displayNumber(humidity_int);
        DIGIT_PORT = (1 << 0);
        _delay_ms(5);
        DIGIT_PORT = 0x00;
        
        displayNumber(humidity_frac);
        DIGIT_PORT = (1 << 1);
        _delay_ms(5);
        DIGIT_PORT = 0x00;
        
        // Пауза перед следующим опросом
        delay_ms(5000); // Опрашиваем каждые 5 секунд
    }
    
    return 0;
}

Пожалуйста, убедитесь, что вы правильно подключили DHT22 к пину PD5 и добавили массив segments, содержащий шаблоны сегментов для 7-сегментного индикатора.


BMP180

– В данном примере рассматривается работа с цифровым датчиком температуры и атмосферного давления BMP180. Показания атмосферного давления выводятся на 7-сегментный индикатор. Датчик подключаетсяпо интерфейсу I2C. Линии SDA и SCL должны быть подтянуты к плюсу питания резисторами на 4.7-10 кОм. Датчик опрашивается каждые 10 секунд. Измеряются температура и давление, но на дисплей выводится только атмосферное давление в мм. ртутного столба (целое число). Документация к датчику здесь.

Скачать пример


BH1750

– В данном примере рассматривается работа с цифровым датчиком освещенности BH1750. Показания освещенности выводятся на 7-сегментный индикатор. Датчик подключается по интерфейсу I2C. Линии SDA и SCL должны быть подтянуты к плюсу питания резисторами на 4.7-10 кОм. Датчик опрашивается каждые 5 секунд. Документация к датчику здесь.

Для работы с цифровым датчиком освещенности BH1750 на микроконтроллере ATmega16 с использованием интерфейса I2C, вам потребуется библиотека для I2C-коммуникации, так как реализация I2C-протокола вручную довольно сложна. В этом примере я использую библиотеку, написанную на чистом C, чтобы взаимодействовать с BH1750 по I2C. Здесь предполагается, что у вас уже есть функции для управления 7-сегментным индикатором.

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "i2c.h"

// Макросы для управления выводами портов
#define SEGMENT_PORT PORTC
#define DIGIT_PORT   PORTB
#define SEGMENT_DDR  DDRC
#define DIGIT_DDR    DDRB

// Адрес BH1750 в режиме записи (нулевой бит - бит записи)
#define BH1750_ADDR_WRITE 0x46

// Адрес BH1750 в режиме чтения (нулевой бит - бит чтения)
#define BH1750_ADDR_READ 0x47

// Команда для начала измерения
#define BH1750_START 0x10

// Глобальная переменная для хранения данных освещенности
uint16_t lightLevel = 0;

// Функция для инициализации I2C
void I2C_Init() {
    // Настройка скорости I2C (100 kHz)
    TWSR = 0; // Делитель равен 1
    TWBR = ((F_CPU / 100000) - 16) / 2;
    // Включение I2C
    TWCR = (1 << TWEN);
}

// Функция для передачи команды на BH1750
void BH1750_SendCommand(uint8_t command) {
    I2C_Start(BH1750_ADDR_WRITE);
    I2C_Write(command);
    I2C_Stop();
}

// Функция для чтения данных освещенности
void BH1750_ReadData() {
    uint8_t rawData[2];
    I2C_Start(BH1750_ADDR_READ);
    rawData[0] = I2C_ReadAck();
    rawData[1] = I2C_ReadNak();
    I2C_Stop();

    lightLevel = (rawData[0] << 8) | rawData[1];
}

// Функция для отображения числа на 7-сегментном индикаторе (аналогична предыдущим примерам)
void displayNumber(uint8_t number) {
    const uint8_t segments[] = { /* ... ваш массив с шаблонами сегментов ... */ };
    SEGMENT_PORT = segments[number];
}

int main(void) {
    // Инициализация портов и индикатора
    SEGMENT_DDR = 0xFF;
    DIGIT_DDR = 0x0F;

    // Инициализация I2C
    I2C_Init();

    while (1) {
        // Отправка команды на начало измерения
        BH1750_SendCommand(BH1750_START);

        // Пауза перед чтением данных
        _delay_ms(180);

        // Чтение данных освещенности
        BH1750_ReadData();

        // Отображение данных освещенности на индикаторе
        uint16_t lightValue = lightLevel / 10; // Для удобства выводим только первые два знака
        displayNumber(lightValue / 10);
        DIGIT_PORT = (1 << 0);
        _delay_ms(5);
        DIGIT_PORT = 0x00;

        displayNumber(lightValue % 10);
        DIGIT_PORT = (1 << 1);
        _delay_ms(5);
        DIGIT_PORT = 0x00;

        // Пауза перед следующим опросом
        _delay_ms(5000); // Опрашиваем каждые 5 секунд
    }

    return 0;
}

Этот пример включает инициализацию I2C, отправку команды на начало измерения BH1750, чтение данных освещенности и отображение их на 7-сегментном индикаторе. Пожалуйста, удостоверьтесь, что вы правильно подключили BH1750 к вашему микроконтроллеру и добавили массив segments, содержащий шаблоны сегментов для 7-сегментного индикатора.


ADC Indication

– Данный пример аналогичен примеру с UART. Отличие в том, что байт берется с линии 0 порта А (линия 0 АЦП, ADC0). Микроконтроллер по таймеру производит аналого-цифровое преобразование напряжения на линии 0 порта А, (младшие 2 бита отбрасываются как шум). При измерении используется внутренняя опора 5 В. К линии PD2 порта D подключена кнопка, которая определяет режим вывода показаний. При нажатии на кнопку выводится результат измерений в виде числа от 0 до 255. Если кнопка не нажата, то результат измерений переводится в вольты и выводится на индикатор (с точностью до десятых).

Скачать пример


Fast PWM

– В данном примере показана настройка аппаратного ШИМ (широтно-импульсная модуляция, англ. PWM). К линиям 4 и 5 порта D подключены светодиоды, а к линиям 2-3 и 6-7 порта D – кнопки каналов A и B соответственно. Кнопки с подтяжкой на землю (при нажатии кнопка выдает на линию порта уровень логической единицы) Кнопки на линях 2 и 3 соответственно увеличивают и уменьшают коэффициент заполнения ШИМ (меняется яркость светодиода) канала А. Кнопки на линях 6 и 7 соответственно увеличивают и уменьшают коэффициент заполнения ШИМ канала B. Число сравнения для каждого из каналов меняется в диапазоне от 0 до 255. Для канала А шаг изменения равен 10, для канала В шаг равен 5.

Вот пример кода на языке C для микроконтроллера ATmega16, который демонстрирует аппаратное ШИМ управление яркостью двух светодиодов с использованием кнопок для изменения коэффициента заполнения ШИМ. Для этого примера будут использоваться каналы A и B для двух светодиодов.

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

// Макросы для управления выводами портов
#define LED_PORT     PORTD
#define BUTTON_PIN   PIND
#define BUTTON_PORT  PORTD

// Определения для настройки ШИМ
#define F_PWM 490   // Частота ШИМ (490 Гц)
#define MAX_VALUE_A 255 // Максимальное значение для канала A
#define MAX_VALUE_B 255 // Максимальное значение для канала B
#define STEP_A 10   // Шаг изменения для канала A
#define STEP_B 5    // Шаг изменения для канала B

// Функция инициализации ШИМ на каналах A и B
void PWM_Init() {
    // Установка режима Fast PWM для канала A и B
    TCCR0 |= (1 << WGM00) | (1 << WGM01);
    TCCR2 |= (1 << WGM20) | (1 << WGM21);
    
    // Установка предделителя (64)
    TCCR0 |= (1 << CS00) | (1 << CS01);
    TCCR2 |= (1 << CS20) | (1 << CS21);
    
    // Установка значения сравнения для канала A и B
    OCR0 = 0; // Начальное значение для канала A
    OCR2 = 0; // Начальное значение для канала B
    
    // Включение сравнения на каналах A и B
    TIMSK |= (1 << OCIE0) | (1 << OCIE2);
}

// Функция для изменения яркости светодиода на канале A
void ChangeBrightnessA(int8_t step) {
    int16_t new_value = OCR0 + step;
    if (new_value >= 0 && new_value <= MAX_VALUE_A) {
        OCR0 = new_value;
    }
}

// Функция для изменения яркости светодиода на канале B
void ChangeBrightnessB(int8_t step) {
    int16_t new_value = OCR2 + step;
    if (new_value >= 0 && new_value <= MAX_VALUE_B) {
        OCR2 = new_value;
    }
}

int main(void) {
    // Инициализация портов
    LED_PORT = 0x00; // Выходы для светодиодов
    BUTTON_PORT = 0xFC; // Входы для кнопок (PD2, PD3, PD4, PD5)
    
    // Инициализация ШИМ
    PWM_Init();
    
    // Включение прерываний
    sei();
    
    while (1) {
        // Чтение состояния кнопок
        uint8_t buttons = BUTTON_PIN & 0xFC;
        
        // Обработка кнопок для изменения яркости
        if (buttons & (1 << PD2)) {
            ChangeBrightnessA(STEP_A);
        }
        if (buttons & (1 << PD3)) {
            ChangeBrightnessA(-STEP_A);
        }
        if (buttons & (1 << PD4)) {
            ChangeBrightnessB(STEP_B);
        }
        if (buttons & (1 << PD5)) {
            ChangeBrightnessB(-STEP_B);
        }
        
        // Пауза
        _delay_ms(10);
    }
    
    return 0;
}

// Прерывание при совпадении для канала A
ISR(TIMER0_COMP_vect) {
    LED_PORT |= (1 << PD4); // Включение светодиода на PD4
}

// Прерывание при совпадении для канала B
ISR(TIMER2_COMP_vect) {
    LED_PORT |= (1 << PD5); // Включение светодиода на PD5
}

Этот пример демонстрирует использование аппаратного ШИМ для управления яркостью двух светодиодов с использованием кнопок для изменения коэффициента заполнения ШИМ на каналах A и B. Убедитесь, что вы правильно подключили светодиоды и кнопки к вашему микроконтроллеру ATmega16.


HCSR04

– В данном примере рассматривается работа с ультразвуковым датчиком расстояния HCSR04. К линии PD3 подключен вывод Trigger датчика, а к линии PD2 вывод Echo. Поключение 7-сегментного индикатора аналогично предыдущим примерам. МК периодически опрашивает датчик и определяет расстояние до препятсвия в сантиметрах. После этого число разбивается на цифры и выводится на дисплей. Документация к датчику здесь.

Для работы с ультразвуковым датчиком расстояния HC-SR04 на микроконтроллере ATmega16 без использования дополнительных библиотек, вы можете использовать следующий пример кода на языке C. В этом примере используется порт D для подключения HC-SR04, и расстояние в сантиметрах выводится на 7-сегментный индикатор.

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

// Макросы для управления выводами портов
#define SEGMENT_PORT PORTC
#define DIGIT_PORT   PORTB
#define SEGMENT_DDR  DDRC
#define DIGIT_DDR    DDRB
#define TRIGGER_PIN  PIND3
#define ECHO_PIN     PIND2

// Функция для задержки в миллисекундах
void delay_ms(uint16_t ms) {
    while (ms--) {
        _delay_ms(1);
    }
}

// Функция для отправки импульса на Trigger датчика
void HCSR04_Trigger() {
    PORTD |= (1 << TRIGGER_PIN);
    _delay_us(10);
    PORTD &= ~(1 << TRIGGER_PIN);
}

// Функция для измерения расстояния с помощью HC-SR04
uint16_t HCSR04_MeasureDistance() {
    // Отправляем импульс на Trigger
    HCSR04_Trigger();
    
    // Ожидаем начало импульса на Echo
    while (!(PIND & (1 << ECHO_PIN)));
    
    // Запускаем таймер
    TCCR1B |= (1 << CS11);
    
    // Ждем окончания импульса на Echo
    while (PIND & (1 << ECHO_PIN));
    
    // Останавливаем таймер
    TCCR1B &= ~(1 << CS11);
    
    // Рассчитываем расстояние в сантиметрах
    uint16_t pulse_width = TCNT1;
    uint16_t distance_cm = (pulse_width / 58);
    
    // Сбрасываем счетчик
    TCNT1 = 0;
    
    return distance_cm;
}

// Функция для отображения числа на 7-сегментном индикаторе (аналогична предыдущим примерам)
void displayNumber(uint8_t number) {
    const uint8_t segments[] = { /* ... ваш массив с шаблонами сегментов ... */ };
    SEGMENT_PORT = segments[number];
}

int main(void) {
    // Инициализация портов и индикатора
    SEGMENT_DDR = 0xFF;
    DIGIT_DDR = 0x0F;
    
    // Инициализация таймера для измерения импульсов
    TCCR1B = 0;
    
    // Инициализация портов D2 (Echo) и D3 (Trigger)
    DDRD |= (1 << TRIGGER_PIN);
    PORTD &= ~(1 << TRIGGER_PIN);
    
    // Включение прерываний
    sei();
    
    while (1) {
        // Измерение расстояния
        uint16_t distance_cm = HCSR04_MeasureDistance();
        
        // Отображение расстояния на индикаторе (двузначное число)
        displayNumber(distance_cm / 10);
        DIGIT_PORT = (1 << 0);
        _delay_ms(5);
        DIGIT_PORT = 0x00;
        
        displayNumber(distance_cm % 10);
        DIGIT_PORT = (1 << 1);
        _delay_ms(5);
        DIGIT_PORT = 0x00;
        
        // Пауза перед следующим измерением
        delay_ms(500); // Измеряем каждые 0.5 секунды
    }
    
    return 0;
}

В этом примере используется таймер для измерения ширины импульса на пине Echo датчика, и на основе этой ширины вычисляется расстояние в сантиметрах. Пожалуйста, убедитесь, что вы правильно подключили HC-SR04 к вашему микроконтроллеру ATmega16 и добавили массив segments, содержащий шаблоны сегментов для 7-сегментного индикатора.


Matrix Keyboard

– В данном примере показана работа с матричной клавиатурой. Микроконтроллер динамически опрашивает клавиатуру, а затем определяет номер нажатой клавиатуры. Размер поля 3 на 3 – получаем 9 кнопок. Нажатие первых 8-ми приводит к зажиганию светодиода на соответствующей линии порта А, нажатие 9-ой кнопки зажигает все светодиоды порта А. Матричная клавиатура подключается к линиям 0-5 порта С (три столбца и три строки). В архиве схема и печатная плата матричной клавиатуры (Diptrace).

Скачать пример


Shift Register

– В данном примере рассматривается работа с модулем SPI на примере сдвигового регистра 74HC595. К регистру подключены светодиоды, в качестве линии CS используется линия 4 порта B (вывод not SS). Линия DS (14 нога) регистра идет к MOSI (PB5), линия SHCP (11 нога) к линии SCK (PB7), линия STCP (12 нога) к линии SS (PB4). Линии MR (10 нога) и OE (13 нога) должны быть подтянуты к высокому и низкому логическим уровням соответственно. По таймеру микроконтроллер меняет состояние светодиодов: поочерёдно горят то чётные светодиоды, то нечётные. Если при этом передать байт по UART'у, то он будет выведен в порт на светодиоды. Чтобы обратно переключиться в режим мигания необходимо послать по UART'у 0x00 (ноль). Документация к микросхеме 74HC595 здесь.

Скачать пример


SG-90 Servo

– В данном примере рассматривается работа с сервоприводом SG-90. Используется аппаратный ШИМ. Линия ШИМ сервпопривода подключена к каналу А аппаратного ШИМ. Кнопки поворота подключены к линиям PD2 и PD3. Кнопка на линии PD2 увеличивает длительность импульса, кнопка на линии PD3 уменьшает длительность импульса. Длительность импульса меняется от 1 до 2 мс. Описание сервомотора здесь.

Скачать пример


RGB Lamp

– В данном примере рассматривается работа с трехцветным RGB-светодиодом. Реализовано плавное переливание цветов с использованием программного ШИМ. Линии красного, зеленого и синего цветов подключаются соответственно к линиям 2, 3 и 4 порта D.

Скачать пример


TSOP4836 NEC

– В данном примере рассматривается работа с фотоприемником TSOP4836 и протоколом передачи NEC, который широко используется в инфракрасных пультах дистанционного управления. При получении команды на дисплей выводится ее код. Поключение 7-сегментного индикатора аналогично предыдущим примерам. Описание фотоприемника здесь.

Скачать пример


WS2812 Ring

– В данном примере рассматривается работа со светодиодами WS2812 с встроенным ШИМ-контроллером. К контроллеру подключено такое кольцо из 16 светодиодов (количество светодиодов в кольце можно указать в коде). Библиотека для работы с WS2812 не моя (взята на гитхабе и немного допилена, копирайт сохранён). В программе сначала задается массив цветов (красный, зеленый, синий), а затем в цикле реализуется их сдвиг и плавным изменением интенсивности. Линия IN первого светодиода подключается к линии PD2 порта D. Описание светодиодов здесь.

Скачать пример


MFRC522 RFID

– В данном примере рассматривается работа со считывателем RFID карточек MFRC522. Cчитыватель подключён к контроллеру по стандартной схеме. Библиотека для работы с MFRC522 не моя (взята на гитхабе и немного допилена, копирайт сохранён). При запуске контроллер определяет тип ридера и отправляет данные в UART. Затем идет непрерывная проверка обнаружения RFID устройств. При поднесении карточки или брелка считывается его адрес и отправляется в UART (адрес 32 бита, 4 байта). Описание считывателя здесь.

Скачать пример

Поделиться
Понравился материал?

Комментарии

Добавить комментарий

Спасибо, добрая работа

Нравится: 0Не нравится: 0

Ответить

Перезалейте файлы

Привет, автору спасибо за проделланую работу, но у меня не скачиваются примеры в конце документа которые, не могли бы Вы их перезалить. Спасибо ещё раз

Нравится: 0Не нравится: 0

Ответить

Подключал к портам ввода-вывода, диоды не работают.

Нравится: 0Не нравится: 0

Ответить

BMP 180

Не могу скачать код bmp180 - может кто-нибудь дать, спасибо

Нравится: 0Не нравится: 0

Ответить

От меня плюс

Нравится: 0Не нравится: 0

Ответить

СПАСИБО

Спасибо за проделанную работу. Сохранил себе на будущее.

Нравится: 0Не нравится: 0

Ответить

Мне кажется у Вас ошибка в коде DHT22, могу ошибаться, не профессионал

Нравится: 0Не нравится: 0

Ответить

Не могу прочитать код

У меня не подгружается код, обновлял несколько раз страницу, не помогло, у всех так?

Нравится: 0Не нравится: 0

Ответить

Добрый день! Откуда вы код брали, сами писали?

Нравится: 0Не нравится: 0

Ответить

Спасибо за примеры

Нравится: 0Не нравится: 0

Ответить

Нужна помощь

Не совсем понял, в DHT11 куда это вписать? Подскажите пожалуйста

Нравится: 0Не нравится: 0

Ответить

Спасибо

Привет, решения точно все рабочие?

Нравится: 0Не нравится: 0

Ответить

закинул себе в избранное, респект

Нравится: 0Не нравится: 0

Ответить

выдает ошибку в 6-м примере

у меня похоже не работает шестой пример, можете подсказать в чём проблема? у кого также? всем спасибо

Нравится: 0Не нравится: 0

Ответить

Добрый день! Не заметил, вроде исправно работает, в чем конкретно проблема?
Нравится: 0Не нравится: 0
можно немного позаимствовать?

жиза пару строк взял себе в код, если не против

Нравится: 0Не нравится: 0

Ответить

ЗАЧЕТ

автору работы моё почтенье

Нравится: 0Не нравится: 0

Ответить

Сергей

Привет, отличная подборка

Нравится: 0Не нравится: 0

Ответить

порадовался за Вас

Нравится: 0Не нравится: 0

Ответить

отлично

Нравится: 0Не нравится: 0

Ответить

Спасибо

Нравится: 0Не нравится: 0

Ответить

Что это за примеры?

Откуда столько примеров?

Нравится: 0Не нравится: 0

Ответить

спасибо, зачитался в примерах

Нравится: 0Не нравится: 0

Ответить