Программная реализация SPI интерфейса, на примере подключения термопары K-типа, через адаптер MAX6675 к микроконтроллеру ATmega8

разделы: SPI , MAX6675 , AVR , дата: 23 октября 2015г.


SPI? Легко!

В одном из предыдущих постов, я рассказывал про подключение термопары к Arduino. Теперь настало время рассмореть подключение к AVR микроконтроллерам на чистом Си, благо, как выяснилось, это совсем не сложно.

Напомню, что термопара подключается через адаптер MAX6675, который в свою очередь подключается к микроконтроллеру через SPI интерфейс.

После I2C, SPI интерфейс скорее всего покажется очень простым. Если расмотреть аппаратную реализацию SPI, то там есть регистр данных SPDR, записью или чтением с которого и осуществляется вся работа c SPI.

Программная реализация, как выяснилось, не намного сложнее, но сначала о том, что такое SPI. Фирменный аппнот к SPI можо найти на сайте Atmel: AVR151: Setup And Use of The SPI

рассмотрим схему подключения взятую оттуда:

    Основные моменты:
  1. К интерфейсу возможно подключение нескольких устройств, но единовременно возможна работа только с одним из них.
  2. Используется концепция master-slave(дирижер <==> оркестр). Мастер генерирует синхроимпульсы и переключает устройства.
  3. В минимальном варианте, имеется две управляющие линии: SCK по которой генерируется синхронизирующие импульсы(она общая для всех), и линия выбора устройства SS(Slave Select). Другое ее обозначение: CS(Chip Select).
  4. Каждое добавленное SPI устройство увеличивает интерфейс на еще одну SS/CS линию.
  5. Интерфейс полнодуплексный, линия MISO (Master In Slave Out) отвечает за прием(относительно мастера), линия MOSI отвечает за предачу.
  6. Имеется четыре режима работы SPI. Фаза SCK изначально имеет низкий уровень в mode 0, и высокий в mode 3. Чтение осуществляется по нарастающему фронту.
  7. В режимах 1 и 3 чтение осуществляется по падающему фронту. Встречаются редко, если верить книге "Практическое программирование микроконтроллеров AVR" Юрия Ревича, глава 11.
  8. Упрощеный вариант SPI состоит из трех линий. В таком случае связь идет только на прием или только на передачу.

Вот именно такой упрощеный режим используется в MAX6675. Документация:MAX6676 datasheet

Предлагаю еще раз взглянуть на формат передачи:

Данные передаются пакетами по два байта. Под температуру отводится 12 бит. 15-й бит, бит знака. LSB - младший значащий бит, MSB - старший значащий бит. Оффициальная легенда:

Значение температуры передается в градусах Цельсия, в диапазоне от 0 до 1024. 1024 - это десять бит, в то время как данные передаются 12-битным форматом. Значит, чтобы получить значение в градусах, нужно будет полученое число разделить на 4. Меня немного смутил второй бит, device ID. В смысле я не очень понимаю как может один бит служить идентификатором. Его скорее можно назвать битом корректности.

Если припомнить, то в случае с Arduino я использовал библиотеку: MAX6675-library

На проверку, там оказался довольно простой код:

Я решил не изобретать велосипед, и переписать код на чистом Си. В качестве шаблона использовал проект из предыдущего поста. Я отбросил файлы относящиеся к TWI, после чего осталось переписать main.c в соответсвии со своими целями:

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

#define pSCLK		PD6
#define pCS 		PD5
#define pMISO		PD4
#define ddrPORT		DDRD
#define csPORT		PORTD
#define sclkPORT	PORTD
#define misoPIN		PIND

static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
uint8_t spi_read(void);

int main(void)
{
	//---- init
	init_uart();
        DDRB |= (1<<PB5); // led indicator

	//  CS:chip select
	ddrPORT |= (1<<pCS);
	csPORT  |= (1<<pCS); // set VCC
	// clock line
	ddrPORT |= (1<<pSCLK);	// set GND
	// data line
	ddrPORT &= ~(1<<pMISO); // high impedance state

	uint16_t data=0;

	//---- ready indication
        blink13(3); //ready indication
	stdout = &mystdout;
	printf("Ok, I'm ready!\n");

	//---- main body
	for (;;)
	{
		csPORT &=~(1<<pCS);  //digitalWrite(cs, LOW);
		_delay_ms(1);

		data=(uint16_t)spi_read();
		data <<= 8;
		data |= (uint16_t)spi_read();

		if (data & 0x4)
		{
			printf("Thermocouple don't attached!\n");
		} else {
			data >>= 3;
			data /= 4;
			printf("temp: %d\n", data);
		}

		csPORT |=(1<<pCS);  //digitalWrite(cs, HIGH);
		_delay_ms(1000);
	};

        return 0;
}


uint8_t spi_read(void)
{

	uint8_t i,ret ,value;
	ret=0;
	i=8;

	do {
		i--;

		sclkPORT &=~(1<<pSCLK);// digitalWrite(sclk, LOW);
		_delay_ms(1);

		value=misoPIN &(1<<pMISO);
		if (value)
		{
			//set the bit to 0 no matter what
			ret |= (1 << i);
		}

		sclkPORT |=(1<<pSCLK); // digitalWrite(sclk, HIGH);
		_delay_ms(1);

	} while (i > 0);

	return ret;
}

Результат работы программы:

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

├── examples
│   └── MAX6675
│       ├── CMakeLists.txt
│       ├── Makefile
│       └── main.c
├── hex
│   ├── READ.ME
│   ├── max6675.elf
│   └── max6675.hex
└── libs
    ├── uart.c
    └── uart.h