MAX6675 - РАБОТА С ТЕРМОПАРОЙ НА МИКРОКОНТРОЛЛЕРАХ АРХИТЕКТУРЫ AVR
Кстате, я тут следует отметить, что MAX6675 - это микросхема для работы именно с термопарами K-типа. А у меня на работе повсеместно используется J-тип. То есть, я наебался, заказав пару десятков таких чипов, однако мне не придется исправлять PCB-проекты, поскольку есть аналоги, вроде MAX31855JASA, с такой же распиновкой:
Здесь плюсик - это, видимо, обозначение первого пина. Так-то, очевидно, 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 здесь появится позже. Когда они приедут из Китая. То есть, после должичка в четверг. Или когда рак на горе свиснет.