U-M
Магистр
MSK
210 39
Ой, он же обычный текстовый, просто расширение .с
Скрытый текст
//-----------------------------------------------------------------------------
// Температурный сервер на базе ATMega328P и датчков DS18B20
// Файл main.c
// OldBean, 27.05.2017
//-----------------------------------------------------------------------------
// Прилагаемый makefile ориентирован на компилятор avr-gcc, "прошивка" - при
// помощи USBAsp-программатора и утилиты avrdude.
// При работе с makefile можно использовать следующие команды:
// Компиляция и подготовка hex-файла: make
// Компиляция и последующая прошивка ATMega328P: sudo make flash
//-----------------------------------------------------------------------------
// Температурный сервер представляет собой микроконтроллер ATMega328P
// с подключенными к пинам порта PD датчиками температуры DS18B20 (не более
// одного на каждый пин, т.е. всего до 8 датчиков).
//
// Каждый пин PD, к которому подключен датчик, имеет подтягивающий резистор
// 4.7k и образует, таким образом, независимую шину 1-Wire с единственным
// ведомым устройством. Управление всеми этими шинами происходит синхронно.
// ID - датчиков DS18B20 не используются. Никак. Датчики нумеруются
// соответственно номерам пинов порта PD (от 0 до 7), к которым они подключены.
// Т.е. датчик, поключенный, например, к PD0 имеет номер 0.
//
// Tемпературный сервер связан с внешним миром по шине I2C как ведомое (slave)
// устройство с адресом 0x07 (может быть изменен перед компиляцией - константа
// ADDR).
//
// Протокол прикладного уровня выглядит следующим образом:
//
// 1. Мaster на шине I2C (например, малинка) отправляет по адресу сервера 0x07
// байт, равный 0x44. Это число интерпретируется сервером как команда.
// 2. Сервер, получив эту команду, запускает цикл преобразования всех
// подключенных к нему датчиков DS18B20 и, на время пока происходит
// преобразования, перестает откликаться на запросы к нему по шине I2C.
// 3. После завершения преобразования, сервер разрешает доступ к нему по шине
// I2C для считывания данных (измеренных температур) или - для очередного
// запуска преобразования датчиков.
// 4. В этой версии сервера, данные считываются одним блоком. Например, при
// помощи функции read_i2c_block_data питоновского модуля smbus. Блок
// представляет собой последовательность 17 байтов, содержащих следующую
// информацию:
// 1-й байт - битовая маска наличия датчиков на шинах 1-Wire (1 - датчик
// есть, 0 - датчик отсутствует). Номерация датчиков
// соответствует нумерации пинов порта PD (от 0 до 7), к
// которым подключаются датчики.
// 2 и 3 байты - слово, содержащее код температуры 0-го датчика (пин 0 МК).
// Старший байт - первый. Температура в °C получается путем
// умножения этого кода на 0.0625
// 4 и 5 байты - код температуры следующего датчика (с номером 1) и так
// далее... Если датчик отстутсвует, то код равен 65535.
//-----------------------------------------------------------------------------
#include <avr/io.h>
#include <compat/twi.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#define ADDR 0x07 // Адрес сервера на шине I2C
#define READY 0 // Устройство доступно по I2C, данные готовы
#define BUSY 1 // Устройство занято - измеряет температуры
uint8_t mode = READY; // Режим работы устройства
uint8_t index = 0;
uint8_t buf[17];
uint8_t bufL = 17;
// На данный момент задействованы все пины порта PD. Если какие-нибудь пины
// необходимо использовать для других целей (например, PD0/RXD и PD1/TXD), то
// их необходимо замаскировать (например, для только что приведенного
// примера - mask1 = 0b11111100; mask0 = 0b00000011;
uint8_t mask1 = 0b11111111; // Все 8 пинов порта PD могут быть использованы в
// качестве шин 1-Wire.
uint8_t mask0 = 0b00000000; // mask0 = ~mask1
uint8_t bytes[8];
//-----------------------------------------------------------------------------
ISR(TWI_vect) { // Обработка прерываний TWI
unsigned char status = TWSR & 0b11111000; // Статус - в старших 5 битах
cli(); // Запрещаем прерывания
switch(status) { // Анализируем статус TWI
// case TW_SR_SLA_ACK: // 0x60
case TW_SR_DATA_ACK: // 0x80
// case TW_SR_STOP: // 0xA0
if(TWDR == 0x44) { // Команда запуска цикла преобразования датчиков
mode = BUSY;
TWCR &= ~(1<<TWEA);
}
break;
case TW_ST_SLA_ACK: // 0xA8
index = 0;
TWDR = buf[index];
break;
case TW_ST_DATA_ACK: // 0xB8
if(++index < bufL) {
TWDR = buf[index];
if(index == bufL - 1) // Последний байт
TWCR &= ~(1<<TWEA); // Не работает! Разобраться...
// PORTC |= 0b00000010;
} else TWDR = 255;
break;
case TW_ST_DATA_NACK: // 0xC0: данные отправлены, принят NACK
case TW_ST_LAST_DATA: // 0xC8: последний байт отправлен, ACK принят
TWCR |= (1<<TWEA);
// PORTC |= 0b00000100;
break;
default:
break;
}
TWCR |= (1<<TWINT); // Очищаем флаг TWINT
sei(); // Разрешаем прерывания
}
//-----------------------------------------------------------------------------------------------------------
uint8_t w1_reset() { // Сброс всех шин 1-Wire и обнаружение подключенных к ним датчиков
// cli(); // Запретим прерывания
DDRD |= mask1; // Пины шин - на вывод
PORTD &= mask0; // Установим низкий уровень на всех шинах 1-Wire
_delay_us(480);
DDRD &= mask0; // Пины шин - на ввод
_delay_us(60);
uint8_t devs = ~(PIND | mask0); // Там, где есть датчик - будет 1
_delay_us(420); //ждем оставшееся время до 480мкс
return devs;
// sei(); // Разрешаем прерывания
}
//-----------------------------------------------------------------------------------------------------------
void w1_write_byte(uint8_t b) { // Отсылаем один и тот же байт на все шины
uint8_t mask = 0b00000001;
for(uint8_t bit = 0; bit < 8; bit++) {
// cli(); // Запретим прерывания
DDRD |= mask1; // Пины шин - на вывод
PORTD &= mask0; // 0 на всех шинах 1-Wire
_delay_us(1);
if(b & mask) // Шлем единичку
DDRD &= mask0; // "Отпустим" шины - переводем порт в режим чтения
_delay_us(59);
DDRD &= mask0;
_delay_us(5);
mask <<= 1;
// sei(); // Разрешаем прерывания
}
}
//-----------------------------------------------------------------------------
void w1_read_bytes(uint8_t *b) { // Чтение байтов со всех шин 1-Wire
uint8_t wb[8]; // Вспомогательный массив, который заносятся читаемые биты.
// Каждый байт wb содержит соответствующие биты всех датчиков.
// Например, wb[3] содержит 3-и биты всех датчиков.
// cli(); // Запретим прерывания
for(uint8_t bit = 0; bit < 8; bit++) {
DDRD |= mask1; // Пины шин - на вывод
PORTD &= mask0; // 0 на всех шинах 1-Wire
_delay_us(1);
DDRD &= mask0; // Пины шин - на ввод
_delay_us(14);
wb[bit] = PIND & mask1; // Читаем все биты с номером bit
_delay_us(45);
}
_delay_us(5);
// Преобразуем полученную информацию (массив wb) к нормальной форме, чтобы
// каждый байт содержал все биты, полученные от одного датчика. По сути,
// нам нужно просто транспонировать матрицу 8x8, представленную массивом wb.
uint8_t bmask = 0b00000001;
for(uint8_t bit = 0; bit < 8; bit++) {
uint8_t nmask = 0b00000001;
for(uint8_t sn = 0; sn < 8; sn++) {
if(buf[0] & nmask) { // Датчик на этой шине есть
if(wb[bit] & nmask) b[sn] |= bmask; // Установим бит
else b[sn] &= ~bmask; // Сбросим бит
} else b[sn] |= bmask; // Если на шине нет датчика - будут все 1
nmask <<= 1;
}
bmask <<= 1;
}
// sei(); // Разрешаем прерывания
}
//-----------------------------------------------------------------------------
int main(void) {
// Порт C используется для индикации и отладки
DDRC |= 0b00000111;
PORTC &= 0b11111000;
DDRD |= mask1; // Пины шин - на вывод
PORTD |= mask1; // Отпускаем все шины
// Инициализация TWI
TWAR = ADDR<<1; // Установка адреса (старшие 7 бит TWAR)
TWCR = (1<<TWINT) | // Сбросим флаг TWINT
(1<<TWEA) | // Разрешаем подтверждение адреса
(1<<TWEN) | // Разрешаем работу TWI
(1<<TWIE);
sei(); // Разрешаем прерывания
while(1) {
if(mode == BUSY) { // Измеряем температуры
PORTC |= 0b00000001; // Индикация - "Занято"
uint8_t devs = w1_reset() & mask1;
if(devs) { // Датчики есть
buf[0] = devs; // Информация о наличии датчиков - в первом байте
w1_write_byte(0xcc); // Skip ROM
w1_write_byte(0x44); // Запускаем все датчики
_delay_ms(800); // Задержка не менее 750 мс
w1_reset();
w1_write_byte(0xcc); // Skip ROM
w1_write_byte(0xbe); // Будем читать температуры
w1_read_bytes(bytes);
for(uint8_t i = 0; i < 8; i++)
buf[2 + 2*i] = bytes;
w1_read_bytes(bytes);
for(uint8_t i = 0; i < 8; i++)
buf[1 + 2*i] = bytes;
w1_reset();
}
TWCR |= (1<<TWEA);
mode = READY;
} else {
PORTC &= 0b11111110; // Индикация - "Готово"
}
}
}
//-----------------------------------------------------------------------------