Подсветка для аквариума с радиоуправлением через Raspberry pi (Часть первая)

Месяц назад запустил аквариум. И сразу же возникла идея сделать автоматическое включение и выключение света. В качестве подсветки на крышку были приклеены полоски светодиодных лент. Влагозащищенные оставлены как есть, а обычные были залиты прозрачным силиконом для защиты от влаги. В итоге получилось относительно простое устройство с хорошим функционалом: в определённое время свет включается, выключается. Можно включать в произвольном порядке или комбинировать 3 блока светодиодных лент разным цветом и/или с разным количеством светодиодов, есть ручное управление через браузер любого устройства (телефон, компьютер, телевизор и т д)









Необходимые материалы:

  • raspberry pi с установленной raspbian – 1 штука
  • радиомодули NRF24L01+ – 2 штуки
  • Полевые транзисторы (я использовал p-канальные IRF9540) – 3 штуки
  • обычные транзисторы – 3 штуки
  • ATMega328p или аналогичный – 1 штука
  • Светодиодные ленты – столько сколько нужно для конкретного аквариума. На 80л аквариум пошло примерно 4м.
  • фольгированный текстолит – по желанию.
  • Блок питания +12v для светодиодных лент.
  • Блок питания +3.3v для микроконтроллера
  • а.. чуть не забыл кварц 16Мгц и  пара керамических конденсаторов.. Можно и без кварца, но частоту нужно будет менять, и чуток менять прошивку.

Вообще мне плату лень было делать, поэтому паял навесным, получилось ужасно..

Больше никогда так делать не буду

Но тут очень кстати пришла пачка макеток из китая, поэтому тут же всё перенес на макетку (Разъёмчик справа – для внутрисхемного программирования):

Пока без полевых транзисторов, но уже работает.

. В качестве блока питания был использован старый БП от компьютера, т.к. там и +3.3V, и +12v присутствуют. Блок питания 350W.. Его мощности хватит чтобы весь дом светодиодными лентами обмотать, нам же нужно только аквариум освещать, поэтому охлаждение БП оставили только пассивным, кулер не нужен.

управление

В схеме подразумевается управление четырьмя светодиодными лентами или другими устройствами, при этом ток не должен превышать 19A на каждый выход (по даташиту IRF9540). Управление организовано следующим образом: В принципе можно подключить подсветку на одну ногу МК и просто включать или выключать, но у меня для подсветки подключены ещё зелёная и синяя лента, поэтому решил включать-выключать каждую отдельно.

Схема приемника

На схеме не видно VCC и GND микроконтроллера. VCC я подключал к +3.3v, GND естественно корпус.

Программа на стороне клиента не сложная, основной код – это работа с радиомодулем и вывод в последовательный порт промежуточных данных для отладки:

// /* & & &
 * WirelessLed.c
 *
 * Created: 25.12.2015 16:51:18
 *  Author: rst10h
 */ 
#define F_CPU 16000000UL
#define BAUD 9600 
#include <avr/io.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <util/setbaud.h>
/*
#include "nrf24l01/nrf24l01.h"
#include "nrf24l01/nrf24l01registers.h"
#include "spi/spi.h"
*/
#include "nrf24l01p.h"

#define SPI_DDR DDRB
#define SPI_PORT PORTB
#define SPI_MISO PB4
#define SPI_MOSI PB3
#define SPI_SCK PB5
#define SPI_SS PB2

//CE and CSN port definitions
#define RADIO_PORT PORTB
#define RADIO_DDR DDRB
#define RADIO_PIN PINB

#define RADIO_CSN 1
#define RADIO_CE 0
//#define RADIO_IRQ 3
int bingo = 0;
int action = 0;
extern int uart_putchar(char c, FILE *stream); //Функция побайтового вывода
static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL,_FDEV_SETUP_WRITE);

uint8_t bufferout[32];
uint8_t bufferin[32];
// Инициализация интерфейса
void spi_init() {
	SPI_DDR |= (1 << SPI_MOSI) | (1 <<  SPI_SCK) | (1 << SPI_SS);
	SPCR = (1 << SPE) | (1 << MSTR); // режим 0, мастер, частота 1/4 от частоты ЦП
}

// Передаёт и принимает 1 байт по SPI, возвращает полученное значение
uint8_t spi_send_recv(uint8_t data) {
	SPDR = data;
	while (!(SPSR & (1 << SPIF)));
	return SPDR;
}

int uart_putchar(char c, FILE *stream){
	if (c == 'n')
	uart_putchar('r', stream);
	loop_until_bit_is_set(UCSR0A, UDRE0);
	UDR0 = c;
	return 0;
}
void uart_putcc(char c)
{
	while (!(UCSR0A & (1 << UDRE0) ) ) {
		continue;
	}
	UDR0 = c;
}

void uart_putss(const char *s )
{
	while (*s)
	uart_putcc(*s++);

}
//#define SPI_DDR DDRB

//#define SPI_SS 2
//#define SPI_MOSI 3
//#define SPI_SCK 5

// Выбирает активное состояние (высокий уровень) на линии CE
inline void radio_assert_ce() {
	RADIO_PORT |= (1 << RADIO_CE); // Установка высокого уровня на линии CE
}

// Выбирает неактивное состояние (низкий уровень) на линии CE
inline void radio_deassert_ce() {
	RADIO_PORT &= ~(1 << RADIO_CE); // Установка низкого уровня на линии CE
}

// Поскольку функции для работы с csn не предполагается использовать в иных файлах, их можно объявить static

// Выбирает активное состояние (низкий уровень) на линии CSN
inline static void csn_assert() {
	RADIO_PORT &= ~(1 << RADIO_CSN); // Установка низкого уровня на линии CSN
}

// Выбирает неактивное состояние (высокий уровень) на линии CSN
inline static void csn_deassert() {
	RADIO_PORT |= (1 << RADIO_CSN); // Установка высокого уровня на линии CSN
}

void radioinit() {
	RADIO_DDR |= (1 << RADIO_CSN) | (1 << RADIO_CE); // Ножки CSN и CE на выход
	  csn_deassert();
	  radio_deassert_ce();
	  spi_init();
}
// Выполняет команду cmd, и читает count байт ответа, помещая их в буфер buf, возвращает регистр статуса
uint8_t radio_read_buf(uint8_t cmd, uint8_t * buf, uint8_t count) {
	csn_assert();
	uint8_t status = spi_send_recv(cmd);
	while (count--) {
		*(buf++) = spi_send_recv(0xFF);
	}
	csn_deassert();
	return status;
}

// Выполняет команду cmd, и передаёт count байт параметров из буфера buf, возвращает регистр статуса
uint8_t radio_write_buf(uint8_t cmd, uint8_t * buf, uint8_t count) {
	csn_assert();
	uint8_t status = spi_send_recv(cmd);
	while (count--) {
		spi_send_recv(*(buf++));
	}
	csn_deassert();
	return status;
}

// Читает значение однобайтового регистра reg (от 0 до 31) и возвращает его
uint8_t radio_readreg(uint8_t reg) {
	csn_assert();
	spi_send_recv((reg & 31) | R_REGISTER);
	uint8_t answ = spi_send_recv(0xFF);
	csn_deassert();
	return answ;
}

// Записывает значение однобайтового регистра reg (от 0 до 31), возвращает регистр статуса
uint8_t radio_writereg(uint8_t reg, uint8_t val) {
	csn_assert();
	uint8_t status = spi_send_recv((reg & 31) | W_REGISTER);
	spi_send_recv(val);
	csn_deassert();
	return status;
}

// Читает count байт многобайтового регистра reg (от 0 до 31) и сохраняет его в буфер buf,
// возвращает регистр статуса
uint8_t radio_readreg_buf(uint8_t reg, uint8_t * buf, uint8_t count) {
	return radio_read_buf((reg & 31) | R_REGISTER, buf, count);
}

// Записывает count байт из буфера buf в многобайтовый регистр reg (от 0 до 31), возвращает регистр статуса
uint8_t radio_writereg_buf(uint8_t reg, uint8_t * buf, uint8_t count) {
	return radio_write_buf((reg & 31) | W_REGISTER, buf, count);
}

// Возвращает размер данных в начале FIFO очереди приёмника
uint8_t radio_read_rx_payload_width() {
	csn_assert();
	spi_send_recv(R_RX_PL_WID);
	uint8_t answ = spi_send_recv(0xFF);
	csn_deassert();
	return answ;
}

// Выполняет команду. Возвращает регистр статуса
uint8_t radio_cmd(uint8_t cmd) {
	csn_assert();
	uint8_t status = spi_send_recv(cmd);
	csn_deassert();
	return status;
}

// Возвращает 1, если на линии IRQ активный (низкий) уровень.
uint8_t radio_is_interrupt() {
	//return (RADIO_PIN & RADIO_IRQ) ? 0 : 1;
	return (radio_cmd(NOP) & ((1 << RX_DR) | (1 << TX_DS) | (1 << MAX_RT))) ? 1 : 0;
}
// Функция производит первоначальную настройку устройства. Возвращает 1, в случае успеха, 0 в случае ошибки
uint8_t radio_start() {
uint8_t self_addr[] = {0xE1, 0xF0, 0xF0, 0xF0, 0xF0}; // Собственный адрес
  uint8_t remote_addr[] = {0xD2, 0xF0, 0xF0, 0xF0, 0xF0}; // Адрес удалённой стороны
  uint8_t chan = 76; // Номер радио-канала (в диапазоне 0 - 125)

  radio_deassert_ce();
  for(uint8_t cnt = 100;;) {
    radio_writereg(CONFIG, (1 << EN_CRC) | (1 << CRCO) | (1 << PRIM_RX)); // Выключение питания
    if (radio_readreg(CONFIG) == ((1 << EN_CRC) | (1 << CRCO) | (1 << PRIM_RX))) 
      break;
    // Если прочитано не то что записано, то значит либо радио-чип ещё инициализируется, либо не работает.
    if (!cnt--)
      return 0; // Если после 100 попыток не удалось записать что нужно, то выходим с ошибкой
    _delay_ms(1);
  }

  radio_writereg(EN_AA, 0x3F); // включение автоподтверждения только по каналам
  radio_writereg(EN_RXADDR, (1 << ERX_P0) | (1 << ERX_P1)); // включение каналов 0 и 1
  radio_writereg(SETUP_AW, SETUP_AW_5BYTES_ADDRESS); // выбор длины адреса 5 байт
  radio_writereg(SETUP_RETR, SETUP_RETR_DELAY_4000MKS | SETUP_RETR_UP_TO_15_RETRANSMIT); 
  radio_writereg(RF_CH, chan); // Выбор частотного канала
  radio_writereg(RF_SETUP,7); // выбор скорости 1 Мбит/с и мощности 0dBm
  
  radio_writereg_buf(RX_ADDR_P0, &remote_addr[0], 5); // Подтверждения приходят на канал 0 
  radio_writereg_buf(TX_ADDR, &remote_addr[0], 5);

  radio_writereg_buf(RX_ADDR_P1, &self_addr[0], 5);
  
  radio_writereg(RX_PW_P0, 32);
  radio_writereg(RX_PW_P1, 32); 
  radio_writereg(DYNPD, (0 << DPL_P0) | (0 << DPL_P1)); // выключение произвольной длины для каналов 0 и 1
  radio_writereg(FEATURE, 0x00); // Запрещение произвольной длины пакета данных

  radio_writereg(CONFIG, (1 << EN_CRC) | (1 << CRCO) | (1 << PWR_UP) | (1 << PRIM_RX)); // Включение питания
  return (radio_readreg(CONFIG) == ((1 << EN_CRC) | (1 << CRCO) | (1 << PWR_UP) | (1 << PRIM_RX))) ? 1 : 0;
}
// Вызывается, когда превышено число попыток отправки, а подтверждение так и не было получено.
void on_send_error() {
	// TODO здесь можно описать обработчик неудачной отправки
}

// Вызывается при получении нового пакета по каналу 1 от удалённой стороны.
// buf - буфер с данными, size - длина данных (от 1 до 32)


uint8_t send_data(uint8_t * buf, uint8_t size) {
	radio_deassert_ce(); // Если в режиме приёма, то выключаем его
	uint8_t conf = radio_readreg(CONFIG);
	if (!(conf & (1 << PWR_UP))) // Если питание по какой-то причине отключено, возвращаемся с ошибкой
	return 0;
	uint8_t status = radio_writereg(CONFIG, conf & ~(1 << PRIM_RX)); // Сбрасываем бит PRIM_RX
	if (status & (1 << TX_FULL_STATUS))  // Если очередь передатчика заполнена, возвращаемся с ошибкой
	return 0;
	radio_write_buf(W_TX_PAYLOAD, buf, size); // Запись данных на отправку
	radio_assert_ce(); // Импульс на линии CE приведёт к началу передачи
	_delay_us(15); // Нужно минимум 10мкс, возьмём с запасом
	radio_deassert_ce();
	return 1;
}
void on_packet(uint8_t * buf, uint8_t size) {
	// TODO здесь нужно написать обработчик принятого пакета
        // При получении пакета моргнём диодом и отправим ответ.
	if (buf[0]=='p') {
		bufferout[0] = '^';
	}
	if (buf[0]=='s') {
		//char b = buf[1]0b00001111;
		PORTC &= 0b11110000;
		PORTC |= 0b00001111 & buf[1];
		bufferout[0] = '!';
		
	}
	bufferout[1] = PINC & 0b00001111;
	PORTC |= 0b00100000;
	_delay_ms(300);
	PORTC &= 0b11011111;
	//printf("отпправляем.. rn");
	send_data(bufferout,32);

	// Если предполагается немедленная отправка ответа, то необходимо обеспечить задержку ,
	// во время которой чип отправит подтверждение о приёме
	// чтобы с момента приёма пакета до перевода в режим PTX прошло:
	// 130мкс + ((длина_адреса + длина_CRC + длина_данных_подтверждения) * 8 + 17) / скорость_обмена
	// При типичных условиях и частоте МК 8 мГц достаточно дополнительной задержки 100мкс
}
void check_radio() {
	if (!radio_is_interrupt()) // Если прерывания нет, то не задерживаемся
	return;
	uint8_t status = radio_cmd(NOP);
	radio_writereg(STATUS, status); // Просто запишем регистр обратно, тем самым сбросив биты прерываний
	
	if (status & ((1 << TX_DS) | (1 << MAX_RT))) { // Завершена передача успехом, или нет,
	if (status & (1 << MAX_RT)) { // Если достигнуто максимальное число попыток
	radio_cmd(FLUSH_TX); // Удалим последний пакет из очереди
	on_send_error(); // Вызовем обработчик
}
if (!(radio_readreg(TX_EMPTY) & (1 << TX_EMPTY))) { // Если в очереди передатчика есть что передавать
radio_assert_ce(); // Импульс на линии CE приведёт к началу передачи
_delay_us(15); // Нужно минимум 10мкс, возьмём с запасом
radio_deassert_ce();
    } else {
      uint8_t conf = radio_readreg(CONFIG);
      radio_writereg(CONFIG, conf | (1 << PRIM_RX)); // Устанавливаем бит PRIM_RX: приём
      radio_assert_ce(); // Высокий уровень на линии CE переводит радио-чип в режим приёма
    }
  }
  uint8_t protect = 4; // В очереди FIFO не должно быть более 3 пакетов. Если больше, значит что-то не так
  while (((status & (7 << RX_P_NO)) != (7 << RX_P_NO)) && protect--) { // Пока в очереди есть принятый пакет
    uint8_t l = radio_read_rx_payload_width(); // Узнаём длину пакета
    if (l > 32) { // Ошибка. Такой пакет нужно сбросить
      radio_cmd(FLUSH_RX); 
    } else { 
	  //printf("Есть данные..rn");
      uint8_t buf[32]; // буфер для принятого пакета
      radio_read_buf(R_RX_PAYLOAD, &buf[0], l); // начитывается пакет
      if ((status & (7 << RX_P_NO)) == (1 << RX_P_NO)) { // если datapipe 1 
        on_packet(&buf[0], l); // вызываем обработчик полученного пакета
      }
    }         
    status = radio_cmd(NOP);
  }
}
/*
void print_observe_tx(uint8_t value)
{
	printf("OBSERVE_TX=%02x: POLS_CNT=%x ARC_CNT=%xrn",
	value,
	(value >> NRF24L01_REG_PLOS_CNT) & 0b1111,
	(value >> NRF24L01_REG_ARC_CNT) & 0b1111
	);
}*/
void print_byte_register(const char* name, uint8_t reg, uint8_t qty)
{
	printf("%st =", name);
	while (qty--)
	printf(" 0x%02x",radio_readreg(reg++));
	printf("n");
}
void print_address_register(const char* name, uint8_t reg, uint8_t qty)
{

	printf("%st =",name);
	while (qty--)
	{
		uint8_t buffer[5];
		radio_readreg_buf(reg++,buffer,sizeof buffer);
		printf(" 0x");
		uint8_t* bufptr = buffer + sizeof buffer;
		while( --bufptr >= buffer )
		printf("%02x",*bufptr);
	}

	printf("n");
}
	void printinfo() {
		//char buff[100];
		
		printf("inforn");
		//Вывод отладочной информации по радиомодулю
		print_byte_register("STATUS",STATUS,1);
		print_byte_register("CONFIG",CONFIG,1);
		print_address_register("RX_ADDR_P0-1",RX_ADDR_P0,2);
		print_byte_register("RX_ADDR_P2-5",RX_ADDR_P2,4);
		print_address_register("TX_ADDRt",TX_ADDR,1);
		print_byte_register("RX_PW_P0-6",RX_PW_P0,6);
		print_byte_register("EN_AAt",EN_AA,1);
		print_byte_register("EN_RXADDR",EN_RXADDR,1);
		print_byte_register("RF_CHt",RF_CH,1);
		print_byte_register("RF_SETUP",RF_SETUP,1);
		print_byte_register("CONFIGt",CONFIG,1);
		print_byte_register("DYNPD/FEATURE",DYNPD,2);
		
	}
//Инициализация USART
void SerilalIni(){

	// USART initialization
	// Communication Parameters: 8 Data, 1 Stop, No Parity
	// USART Receiver: On
	// USART Transmitter: On
	// USART Mode: Asynchronous
	// USART Baud rate: 9600

	UCSR0A=0x00;
	UCSR0B=(1<<RXEN0) | (1<<TXEN0);
	UCSR0C=0x06;

	UBRR0H = UBRRH_VALUE;
	UBRR0L = UBRRL_VALUE+1;

	stdout = &mystdout;//Направляем поток вывода на наш буфер
}

//Запуск программы начинается с этого места
int main(void) {
        //4 первых бита и ещё один на выход
	DDRC = 0b00101111;
	uint8_t pipe = 0;
	uint8_t i;
	SerilalIni();//Инициализируем вывод
	_delay_ms(1000);
	printf("Запуск!!!...n");
        //два раза мигнём светодиодом, чтобы все видели что мы работаем
	_delay_ms(1000);
	PORTC = 1<<PORTC5;
	_delay_ms(1000);
	PORTC = 0<<PORTC5;	
	_delay_ms(1000);
	PORTC = 1<<PORTC5;
	_delay_ms(1000);
        //А теперь включим всю подсветку, особенно полезно на случай, если сервер ничего не отправил и надо включать вручную.
	PORTC = 0<<PORTC5 | 0b00001111;
	_delay_ms(1000);
	//init interrupt
	//sei();
	//setup buffer
        //почистим буферы приема и отправки
	for(i=0; i<sizeof(bufferout); i++)
	bufferout[i] = ' ';
	bufferout[31] = 0;
	for(i=0; i<sizeof(bufferin); i++)
	bufferin[i] = 0;
	//nrf24l01_printinfo();
	printf("Инициализация....n");
        //запускаем радиомодуль
	radioinit();
	  while (!radio_start()) {
		  _delay_ms(1000);
	  }
	  printinfo();
	  printf("всё ок, запускаю основной цикл....n");
	  // Перед включением питания чипа и сигналом CE должно пройти время достаточное для начала работы осциллятора
	  // Для типичных резонаторов с эквивалентной индуктивностью не более 30мГн достаточно 1.5 мс
	  _delay_ms(2);
	  
	  radio_assert_ce();
          //собственно главный бесконечный цикл
	  for(;;) {
		  check_radio();
		  _delay_ms(2);
		  // TODO здесь основной код программы		  
	  }
}

Архив с проектом для Atmel Studio можно скачать здесь: Архив с исходниками

Прошивка там же, в папке Release

Клиент ждет пакета по адресу F0F0F0F0E1, длина пакета 32 байта, но используется только два. Если первый байт “p”, ничего не делает, отправляет в качестве ответа байт “^”, вторым байтом идет состояние порта С. Если же первый байт будет “s”, то содержимое второго байта запишется в порт С (вернее  первые 4 бита), затем пойдет ответ в виде: Байт(1) – символ “!”, второй байт – состояние порта С. Ответ уходит на адрес:F0F0F0F0D2. Конфигурация у приёмника и у передатчика должна быть одинаковая:

конфигурация
Конфигурация радиомодуля на Raspberry pi

Такую же информацию выдает atmega на пин ТХ с битрейтом 9600 (я использовал для отладки, но решил не удалять, т.к. на работу не влияет).

Про настройку raspberry pi для управления подсветкой расскажу в следующей части.

окончательный вариант нуждается только в коробочке

Коробочка найдена, а вот и она:

Ещё пара фотографий процесса:

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>