MAX6675 - РАБОТА С ТЕРМОПАРОЙ НА МИКРОКОНТРОЛЛЕРАХ АРХИТЕКТУРЫ AVR

Кстате, я тут следует отметить, что MAX6675 - это микросхема для работы именно с термопарами K-типа. А у меня на работе повсеместно используется J-тип. То есть, я наебался, заказав пару десятков таких чипов, однако мне не придется исправлять PCB-проекты, поскольку есть аналоги, вроде MAX31855JASA, с такой же распиновкой:

MAX thermocouple 1Здесь плюсик - это, видимо, обозначение первого пина. Так-то, очевидно, VCC снизу, на четвертом.

Описание выводов MAX6675
Имя выводаФункция
1 GND Земля (общий провод)
2 T– Минусовой (алюмелевый) провод термопары типа K. Также должен быть подключён к земле.
3 T+ Плюсовой (хромелевый) провод термопары типа K
4 VCC Питание. Рекомендуется подключить байпасный конденсатор 0.1 мкФ к GND.
5 SCK Вход тактового сигнала SPI (Serial Clock Input)
6 CS Выбор микросхемы (Chip Select). Активный уровень — низкий. Установите в 0 для начала передачи.
7 SO Серийный выход данных SPI (Serial Output)
8 N.C. Не подключен (No Connection)

🧪 Термоад — как узнать, что паяльник уже не шутит

Иногда нужно узнать температуру быстро, точно и без запаха палёной кожи. Для таких случаев существует микросхема MAX6675 — она дружит с термопарой типа K и может выдать температуру с точностью до 0.25°C, что примерно столько же, сколько у вас терпения настраивать SPI вручную. А мы как раз этим и займёмся.

⚠️ Пример работает на честных I/O, без аппаратного SPI. Потому что мы не из тех, кто ищет лёгкие пути. SPI эмулируем вручную, а пины — настраиваем через #define, так что хочешь — подключай к PB3/PB5/PD6, хочешь — к чему душа пожелает.

🧠 А если я хочу другие пины?

Без паники, для этого есть магия #define. В начале кода ты можешь переопределить всё, что хочешь:

#define MAX6675_SO_PORT  PORTC
#define MAX6675_SO_DDR   DDRC
#define MAX6675_SO_PIN   PINC
#define MAX6675_SO_BIT   PC2

Так ты говоришь микроконтроллеру: "Эй, дружок, теперь читай данные не с PB3, а вот отсюда". Точно так же настраиваются и пины CS и SCK.

📌 Это значит, что код — переносимый, гибкий и не привязан к определённой ноге. Хочешь SPI на ногтях — пожалуйста.

🔌 Распиновка и подключения (по умолчанию)

Пин на MAX6675НазначениеAVR-пин по умолчаниюМакрос
SO Serial Out PB3 (MISO) MAX6675_SO_*
SCK Serial Clock PB5 (SCK) MAX6675_SCK_*
CS Chip Select PD6 MAX6675_CS_*

Остальное — питание и земля, как всегда. Не перепутай.

⚙️ Что делает код

  • Настраивает GPIO как Open Drain, вход с подтяжкой и прочие извращения по вкусу (см. init_pins()).
  • Читает данные бит за битом вручную (да, это медленно, но зато понятно как школьная столовка).
  • Проверяет, не отвалилась ли термопара, и если всё норм — считает температуру и шлёт её по UART (9600 бод, 8N1, как в старые добрые).
  • Умеет ругаться в терминал, если термопара не на месте.

🖥️ И что в итоге?

Подключи адаптер USB-UART, открой minicom, putty или madCOM, и ты увидишь что-то вроде:

Raw data: 0x1A28
Temp: 104.50 C

Если термопары нет, будет:

Raw data: 0x0004
Error: Thermocouple not connected

🧯 Зачем это всё?

  • Чтобы делать вещи, которые греются.
  • Чтобы контролировать то, что не должно перегреться.
  • Чтобы понять, что твоя паяльная станция греет не туда.
  • Чтобы просто почувствовать себя инженером, а не монтажником из TikTok.

🖥️ Общий вариант кода для MAX6675:

#define F_CPU 16000000UL
 
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <string.h>
 
// Макросы для переопределения пинов MAX6675
#ifndef MAX6675_SO_PORT
#define MAX6675_SO_PORT PORTB
#define MAX6675_SO_DDR DDRB
#define MAX6675_SO_PIN PINB
#define MAX6675_SO_BIT PB3 // Serial Out (MISO)
#endif
 
#ifndef MAX6675_CS_PORT
#define MAX6675_CS_PORT PORTD
#define MAX6675_CS_DDR DDRD
#define MAX6675_CS_PIN PIND
#define MAX6675_CS_BIT PD6 // Chip Select (Open Drain)
#endif
 
#ifndef MAX6675_SCK_PORT
#define MAX6675_SCK_PORT PORTB
#define MAX6675_SCK_DDR DDRB
#define MAX6675_SCK_PIN PINB
#define MAX6675_SCK_BIT PB5 // Serial Clock
#endif
 
// Настройка UART (для 16 МГц, baud 9600)
#define BAUD 9600
#define MYUBRR ((F_CPU / (16UL * BAUD)) - 1)
 
// Инициализация пинов
void init_pins() {
    // SO как вход с подтяжкой к GND для предотвращения шума
    MAX6675_SO_DDR &= ~(1 << MAX6675_SO_BIT);
    MAX6675_SO_PORT &= ~(1 << MAX6675_SO_BIT); // Подтяжка к GND (если нужна к VCC, замените на |=)
 
    // SCK как выход
    MAX6675_SCK_DDR |= (1 << MAX6675_SCK_BIT);
    MAX6675_SCK_PORT &= ~(1 << MAX6675_SCK_BIT); // Начально низкий уровень
 
    // CS как вход с подтяжкой к VCC для Open Drain
    MAX6675_CS_DDR &= ~(1 << MAX6675_CS_BIT);
    MAX6675_CS_PORT |= (1 << MAX6675_CS_BIT);
}
 
// Инициализация UART
void init_uart() {
    UBRR0 = MYUBRR; // Скорость передачи
    UCSR0B = (1 << TXEN0); // Включить передатчик
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); // 8 бит, без паритета, 1 стоп-бит
}
 
// Отправка байта по UART
void uart_transmit(uint8_t data) {
    while (!(UCSR0A & (1 << UDRE0))); // Ждать, пока буфер пуст
    UDR0 = data;
}
 
// Отправка строки по UART
void uart_print(const char *str) {
    while (*str) {
        uart_transmit(*str++);
    }
}
 
// Чтение 16 бит с MAX6675
uint16_t read_max6675() {
    uint16_t data = 0;
 
    // Эмуляция Open Drain для CS: пин как выход, низкий уровень
    MAX6675_CS_DDR |= (1 << MAX6675_CS_BIT);
    MAX6675_CS_PORT &= ~(1 << MAX6675_CS_BIT);
    _delay_us(10);
 
    // Чтение 16 бит через программный SPI
    for (uint8_t i = 0; i < 16; i++) {
        MAX6675_SCK_PORT |= (1 << MAX6675_SCK_BIT); // SCK высокий
        _delay_us(1);
        data <<= 1;
        if (MAX6675_SO_PIN & (1 << MAX6675_SO_BIT)) { // Чтение SO
            data |= 1;
        }
        MAX6675_SCK_PORT &= ~(1 << MAX6675_SCK_BIT); // SCK низкий
        _delay_us(1);
    }
 
    // Отпускаем CS: пин как вход с подтяжкой
    MAX6675_CS_DDR &= ~(1 << MAX6675_CS_BIT);
    MAX6675_CS_PORT |= (1 << MAX6675_CS_BIT);
    return data;
}
 
// Преобразование данных MAX6675 в температуру (градусы Цельсия)
float get_temperature() {
    uint16_t data = read_max6675();
    // Отладочный вывод сырых данных
    char buffer[32];
    snprintf(buffer, sizeof(buffer), "Raw data: 0x%04X\r\n", data);
    uart_print(buffer);
 
    if (data & (1 << 2)) { // Проверка на ошибку
        return -1;
    }
    data >>= 3; // Сдвиг для 12-битного значения
    return data * 0.25;
}
 
// Вывод температуры
void print_temperature(float temp) {
    char buffer[16];
    if (temp == -1) {
        uart_print("Error: Thermocouple not connected\r\n");
    } else {
        int whole = (int)temp;
        int frac = (int)((temp - whole) * 100);
        snprintf(buffer, sizeof(buffer), "Temp: %d.%02d C\r\n", whole, frac);
        uart_print(buffer);
    }
}
 
int main() {
    init_pins();
    init_uart();
 
    while (1) {
        float temp = get_temperature();
        print_temperature(temp);
        _delay_ms(1000); // Задержка 1 секунда
    }
    return 0;
}

🖥️ Вариант кода для MAX6675, оптимизированный для термопар J-типа:

#define F_CPU 16000000UL
 
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <string.h>
 
// Макросы для переопределения пинов MAX6675
#ifndef MAX6675_SO_PORT
#define MAX6675_SO_PORT PORTB
#define MAX6675_SO_DDR DDRB
#define MAX6675_SO_PIN PINB
#define MAX6675_SO_BIT PB3 // Serial Out (MISO)
#endif
 
#ifndef MAX6675_CS_PORT
#define MAX6675_CS_PORT PORTD
#define MAX6675_CS_DDR DDRD
#define MAX6675_CS_PIN PIND
#define MAX6675_CS_BIT PD6 // Chip Select
#endif
 
#ifndef MAX6675_SCK_PORT
#define MAX6675_SCK_PORT PORTB
#define MAX6675_SCK_DDR DDRB
#define MAX6675_SCK_PIN PINB
#define MAX6675_SCK_BIT PB5 // Serial Clock
#endif
 
// Настройка UART (для 16 МГц, baud 9600)
#define BAUD 9600
#define MYUBRR ((F_CPU / (16UL * BAUD)) - 1)
 
// Инициализация пинов
void init_pins() {
    // SO как вход, без подтяжки (MAX6675 активно управляет SO)
    MAX6675_SO_DDR &= ~(1 << MAX6675_SO_BIT);
 
    // SCK как выход, низкий уровень
    MAX6675_SCK_DDR |= (1 << MAX6675_SCK_BIT);
    MAX6675_SCK_PORT &= ~(1 << MAX6675_SCK_BIT);
 
    // CS как выход, высокий уровень (неактивный)
    MAX6675_CS_DDR |= (1 << MAX6675_CS_BIT);
    MAX6675_CS_PORT |= (1 << MAX6675_CS_BIT);
}
 
// Инициализация UART
void init_uart() {
    UBRR0 = MYUBRR; // Скорость передачи
    UCSR0B = (1 << TXEN0); // Включить передатчик
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); // 8 бит, без паритета, 1 стоп-бит
}
 
// Отправка байта по UART
void uart_transmit(uint8_t data) {
    while (!(UCSR0A & (1 << UDRE0))); // Ждать, пока буфер пуст
    UDR0 = data;
}
 
// Отправка строки по UART
void uart_print(const char *str) {
    while (*str) {
        uart_transmit(*str++);
    }
}
 
// Чтение 16 бит с MAX6675
uint16_t read_max6675() {
    uint16_t data = 0;
 
    MAX6675_CS_PORT &= ~(1 << MAX6675_CS_BIT); // CS низкий
    _delay_us(10); // Задержка для стабилизации
 
    // Чтение 16 бит через программный SPI
    for (uint8_t i = 0; i < 16; i++) {
        MAX6675_SCK_PORT |= (1 << MAX6675_SCK_BIT); // SCK высокий
        _delay_us(2); // Увеличенная задержка для надёжности
        data <<= 1;
        if (MAX6675_SO_PIN & (1 << MAX6675_SO_BIT)) {
            data |= 1;
        }
        MAX6675_SCK_PORT &= ~(1 << MAX6675_SCK_BIT); // SCK низкий
        _delay_us(2);
    }
 
    MAX6675_CS_PORT |= (1 << MAX6675_CS_BIT); // CS высокий
    return data;
}
 
// Преобразование данных MAX6675 в температуру для типа J
float get_temperature() {
    uint16_t data = read_max6675();
    char buffer[32];
    snprintf(buffer, sizeof(buffer), "Raw data: 0x%04X\r\n", data);
    uart_print(buffer);
 
    // Проверка ошибок
    if (data & (1 << 2)) { // Обрыв термопары
        return -1;
    }
    if (data & (1 << 1)) { // Неверный ID устройства
        return -2;
    }
 
    data >>= 3; // Сдвиг для 12-битного значения
    // Коррекция для типа J: термо-ЭДС ~55 мкВ/°C (K: ~41 мкВ/°C)
    float temp = (data * 0.25) * (41.0 / 55.0);
    // Компенсация смещения из-за незаземлённого T- (примерно +2°C)
    temp -= 2.0; // Эмпирическая коррекция, подстрой по тестам
    return temp;
}
 
// Вывод температуры
void print_temperature(float temp) {
    char buffer[16];
    if (temp == -1) {
        uart_print("Error: Thermocouple not connected\r\n");
    } else if (temp == -2) {
        uart_print("Error: Invalid device ID\r\n");
    } else {
        int whole = (int)temp;
        int frac = (int)((temp - whole) * 100);
        snprintf(buffer, sizeof(buffer), "Temp: %d.%02d C\r\n", whole, frac);
        uart_print(buffer);
    }
}
 
int main() {
    init_pins();
    init_uart();
 
    while (1) {
        float temp = get_temperature();
        print_temperature(temp);
        _delay_ms(250); // Минимальная задержка 220 мс для MAX6675
    }
    return 0;
}

Ну, а код для MAX31855JASA здесь появится позже. Когда они приедут из Китая. То есть, после должичка в четверг. Или когда рак на горе свиснет.