RGB ночник из керосиновой лампы. Сборка

Это вторая часть статьи про RGB ночник из «Летучей мыши». Здесь будет рассмотрен процесс сборки конструкции, дан небольшой обзор на управляющую электронику и программу микроконтроллера. Первая часть тут. Под катом много картинок, у кого трафик имейте ввиду!

 

Содержание

Раскрой и сборка

В качестве источника света используются отрезки светодиодной ленты WS2812B, в каждом из которых 10 светодиодов. Количество отрезков 8 штук. Ленту брал 144 светодиода/метр. Кусочки светодиодной ленты необходимо как-то закрепить внутри колбы лампы и обеспечить им достаточный теплоотвод. Решил сделать каркас из фольгированного стеклотекстолита. Размечаем, разрезаем, собираем! 😉

Прихватываем пайкой отрезки текстолита, формируя восьмиугольник:

Припаиваем нижний диск из текстолита:

Припаиваем верхнее кольцо из медной проволоки, стягивая всю конструкцию:

Отмываем от остатков флюса и получаем вот такой результат:

Лента, настало твое время!

Отрезаем от катухи 8 кусков по 10 светодиодов (на фотке показано 6):

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

Далее, пайка! 🙂 Провода питания к отрезкам ленты подпаиваем с верхней части конструкции, data-провод соединяет все отрезки ленты последовательно снизу вверх.

Делаем регулятор яркости. Первым делом разбираем узел подачи фитиля:

Задача состоит в впиливании каким-то образом переменного резистора на 50 килоом внутрь горелки керосиновой лампы, причем штатная ручка регулирования лампы должна быть соединена с движком резистора так, чтоб ничто не выдавало модификацию данного узла. После некоторых раздумий получаем следующее:

Крепление ручки оказалось не очень надежным, в дальнейшем я плотно обмотал место крепления ручки к резистору швейными нитками и пропитал нитки суперклеем. После этого конструкция стала монолитной, и сломать ее теперь намного сложнее.

Далее, прикручиваем светодиодную «горелку» к основанию керосиновой лампы. В дальнейшем был изготовлен светофильтр для рассеивания света из обычного листа А4, но лучше использовать какой-либо белый матовый пластик или пленку.

Примеряем регулятор яркости:

Как тут и был!

Так как в дальнейшем планируется устанавливать управляющую электронику внутрь емкости для керосина, приступаем к выпиливанию дна у лампы. Ну и заодно сверлим отверстие для крепления кнопки:

Отпиливаем от крышки бочка основание, теперь это у нас ручка настройки режима:

Монтируем кнопку и регулятор в корпус лампы:

Вклеиваем внутрь обрезка крышки часть от ручки переменного резистора:

И насаживаем самодельную ручку на резистор:

Фух, этот этап завершен! Далее идет не менее увлекательный процесс программирования, создания режимов, отладки прошивки и так далее. Описание программы будет в последней части статьи, а пока займемся схемой и пайкой. Вот фотка процесса отладки прошивки и схемы:

Схема

Принципиальная электрическая схема нашей лампы:

Большая часть схемы собрана из готовых модулей с Aliexpress. Питание +12 вольт подается от внешнего импульсного блока питания. Отладочная плата DD3 на микроконтроллере STM32F103C8T6 запитана 5-ю вольтами посредством стабилизатора 78L05 через разъем USB на плате, точнее провод припаян к ножке диода, которая электрически соединена с контактом питания USB разъема. Переменный резистор R1 подключен к выводу A4 платы и отвечает за изменение яркости свечения лампы, R2 подключен к A3 и управляет режимом работы. С помощью кнопки S1, которая подключена к выводу A6, можно выбрать один из 8-и режимов работы. На транзисторе Q1 выполнен преобразователь логического уровня управляющего сигнала, идущего от отладочной платы к светодиодной ленте. Лента через step-down преобразователь DD2 на микросхеме LM2596, питается напряжением 3,6 вольта. Преобразователь подвергнут нескольким переделкам, а именно добавлены керамические конденсаторы по 0,1 мкФ на входе и выходе преобразователя, а подстроечный резистор был заменен на 2 последовательно спаянных smd-резистора в сумме имеющие сопротивление 610 Ом.

Из куска пластика был вырезан круг диаметром 11 см, который является дном ламы и основой для крепления радиоэлектронных компонентов. Далее фотки процесса сборки электроники:

Нижнюю часть сделал на разъеме, чтоб все было по красоте 🙂 Припаиваем провода и ответную честь разъема к компонентам, интегрированным в корпус лампы, просверливаем крепежные отверстия для дна и вклеиваем туда гайки М3:

Торжественное соединение вершков и корешков!

Прикручиваем днище:

Ну и перед окончательной сборкой не забываем загрузить в микроконтроллер последнюю версию прошивки 😉

Софт

Управляющая программа написана на Си в среде IAR ARM 7.50.2, ссылки на исходники в конце статьи. Алгоритм работы следующий. При нажатии пользователем на кнопку выбора режима, на ленте должен загораться светящийся столбик, отображающий номер текущего режима. После прекращения нажатий на кнопку, столбик отображается еще 1 секунду, а потом начинается демонстрация выбранного режима.

Для реализации всего этого пришлось городить огород из программных таймеров и конечных автоматов, ибо так проще. В конце дам ссылку на ооочень хорошие статьи про конечные автоматы. Для людей, занимающихся разработкой под МК материал обязателен для прочтения.

Вся логика работы лампы сосредоточена в одном файле main.c, в остальных файлах реализованы драйверы периферии и протоколы обмена. Итак, поехали!

Вначале рассмотрим все вспомогательные функции, которые используются в программе.

Библиотека обмена с светодиодной лентой на базе WS2812B взята собственной разработки, описание тут: Драйвер светодиодной ленты на WS2812B для STM32F103C8.

Далее, в файлах color.c и color.h находится код конвертера HSV_to_RGB() из палитры HSV в RGB и TColorToRGB(), который преобразует цветовую температуру так же в RGB. К слову сказать, не нравится мне результат работы TColorToRGB(), тут или конвертер кривой, или сама светодиодная лента не может вытянуть нужные цвета, не знаю. Но все же оставил этот режим, хай будет. Приводить текст тут не буду, особо интересного там ни чего нет, кому надо — смотрите в исходниках.

Далее файл inputs.c. Тут находится все связанное с кнопкой и крутилками. Код кнопки:

//Инициализация
void ButtonInit(void)
{
  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; //Включаем тактирование порта GPIOA
  
  GPIOA->CRL &= ~(GPIO_CRL_CNF6 | GPIO_CRL_MODE6);
  GPIOA->CRL |= (GPIO_CRL_CNF6_1); 
  GPIOA->ODR |= (1<<6);
}

//Читаем значение
int ButtonIsPress(void)
{
  if(GPIOA->IDR & (1<<6))
    return 0;
  return 1;
}

Далее регуляторы. Тут уже веселее. Каждый резистор включен в режиме делителя напряжения. Выходы заведены на аналоговые входы микроконтроллера. Так как на соединительные провода резисторов оказывают влияние внешние помехи, прочитанное значение положения ручки регуляторов пропускается через простейший фильтр «скользящее окно». После фильтра отсутствуют сильные скачки считанных значений с аналоговых входов микроконтроллера.

Код фильтра:

#define WIN_SIZE 8 //размер окна фильтра

//массивы фильтров
uint16_t a_array[WIN_SIZE];
uint16_t b_array[WIN_SIZE];

uint8_t ab_index = 0;

//Инициализация фильтров
static void ma_init(void)
{
  ab_index = 0;
  
  for(int i=0; i<WIN_SIZE; i++)
  {
    a_array[i] = 0;
    b_array[i] = 0;
  }
}

//Добавить новое значение в фильтр
static void ma_add(uint16_t a_val, uint16_t b_val)
{
  a_array[ab_index] = a_val;
  b_array[ab_index] = b_val;
  
  ab_index++;
  if(ab_index >= WIN_SIZE)
    ab_index = 0;
}

//плучить сглаженное значение
static void ma_get(uint16_t *a, uint16_t *b)
{
  uint32_t a_tmp = 0;
  uint32_t b_tmp = 0;
  for(int i = 0; i<WIN_SIZE; i++)
  {
    a_tmp += a_array[i];
    b_tmp += b_array[i];
  }
  
  (*a) = a_tmp / WIN_SIZE;
  (*b) = b_tmp / WIN_SIZE;
}

Ну и далее, само чтение данных с аналоговых входов:

//Инициализация АЦП
void ADC_init(void)
{
  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; //Включаем тактирование порта GPIOA
  RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; //Включаем тактирование порта ADC1
  
  //PA3 и PA4 в аналоговом режиме
  GPIOA->CRL &= ~(GPIO_CRL_CNF3 | GPIO_CRL_MODE3 | GPIO_CRL_CNF4 | GPIO_CRL_MODE4);
  
  RCC->CFGR |= RCC_CFGR_ADCPRE_1 | RCC_CFGR_ADCPRE_0; //Предделитель АЦП
  
  ADC1->CR1 = ADC_CR1_SCAN;
  ADC1->CR2 = ADC_CR2_JEXTTRIG |
    ADC_CR2_JEXTSEL_0 | ADC_CR2_JEXTSEL_1 | ADC_CR2_JEXTSEL_2;
  
  ADC1->SMPR2 |= ADC_SMPR2_SMP3_0 | ADC_SMPR2_SMP3_1 | ADC_SMPR2_SMP3_2 
    | ADC_SMPR2_SMP4_0 | ADC_SMPR2_SMP4_1 | ADC_SMPR2_SMP4_2;
  
  ADC1->JSQR = ADC_JSQR_JSQ4_0 | ADC_JSQR_JSQ4_1 //channel 3
    | ADC_JSQR_JSQ3_2 //channel 4
    | ADC_JSQR_JL_0; //nom of channels: 2
  
  ADC1->CR2 |= ADC_CR2_ADON;
  
  ma_init(); //Инициализация фильтров
}

//Читаем значения положений ручек
void ADC_AnalogRead(uint16_t *a, uint16_t *b)
{
  ADC1->SR = 0;
  ADC1->CR2 |= ADC_CR2_JSWSTART;
  
  while(!(ADC1->SR & ADC_SR_JEOC))
    ;
  
  uint16_t _a, _b;
  _a = ADC1->JDR1;
  _b = ADC1->JDR2;
  
  ma_add(_a, _b);
  ma_get(a, b);
}

Для реализации всей функциональности нам понадобятся 3 таймера для измерения 3-х разных промежутков времени: один для обновления светодиодной ленты с частотой 50 Гц, другой для задания промежутка времени отображения номера режима (светящийся столбик), третий для реализации системы антидребезга контактов кнопки выбора режима. Но тратить 3 аппаратных таймера микроконтроллера на такого рода вещи крайне нерационально, к тому же в STM32F103C8 их всего три штуки, и один уже задействован в реализации протокола обмена данными с светодиодной лентой. На помощь приходят программные таймеры, для работы которых нужен всего один аппаратный.

Функция инициализации таймера:

#define TIMER_FPS               0 //Таймер обновления ленты
#define TIMER_DISP              1 //Таймер отображения режима
#define TIMER_ANTIBOUNCE        2 //Таймер для антидребезга

#define NUM_SOFT_TIMERS         3 //Всего таймеров: 3 штуки
uint32_t soft_timer[NUM_SOFT_TIMERS]; //Тут хранятся текущие значения таймеров

//Инициализация таймера TIM3
void TickTimerInit(void)
{
  RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; //Включаем тактирование TIM3
  
  TIM3->PSC = 71; //Предделитель 71+1=72
  TIM3->ARR = 10000; //Период 10000мкс, частота 100Гц
  TIM3->DIER = TIM_DIER_UIE; //Прерывание по обновлению
  
  //Очищаем массив программных таймеров
  for(int i=0; i<NUM_SOFT_TIMERS; i++)
  {
    soft_timer[i] = 0;
  }
  
  TIM3->SR &= ~(TIM_SR_UIF); //Очищаем флаг прерывания
  NVIC_EnableIRQ(TIM3_IRQn); //Разрешаем прерывание от таймера
  TIM3->CR1 |= TIM_CR1_CEN; //Включаем таймер
}

А в прерывании увеличиваем на единицу каждый программный таймер:

//Прерывание от TIM3
//Вызывается с частотой 100 Гц
void TIM3_IRQHandler(void)
{
  static int i = 0;
  TIM3->SR &= ~(TIM_SR_UIF); //Очищаем флаг прерывания
  
  //Увеличиваем на единицу
  //каждый программный таймер
  for(i=0; i<NUM_SOFT_TIMERS; i++)
    soft_timer[i]++;
}

Так, основное готово. Продолжаем:

//Получаем текущее значение n-го таймера
uint32_t GetTimer(int n)
{
  uint32_t tmp;
  
  if(n>=NUM_SOFT_TIMERS)
    return 0;
  
  //Так как к массиву soft_timer[]
  //доступ осуществляется асинхронно из двух мест,
  //(из прерывания TIM3_IRQHandler() и функций 
  //получения/очистки значения),
  //то для атомарности обращения к soft_timer[]
  //необходимо сначала запретить прерывания от TIM3, 
  //получить значение таймера, и снова разрешить
  //прерывания от TIM3.

  NVIC_DisableIRQ(TIM3_IRQn);
  tmp = soft_timer[n];
  NVIC_EnableIRQ(TIM3_IRQn);
  
  return tmp;
}

//Сбрасываем n-й таймер в ноль
void ResetTimer(int n)
{
  if(n>=NUM_SOFT_TIMERS)
    return;

  NVIC_DisableIRQ(TIM3_IRQn);
  soft_timer[n] = 0;
  NVIC_EnableIRQ(TIM3_IRQn);
}

//Присваиваем n-му таймеру значение val
//(в программе эта функция не используется)
void SetTimer(uint32_t val, int n)
{
  if(n>=NUM_SOFT_TIMERS)
    return;
  
  NVIC_DisableIRQ(TIM3_IRQn);
  soft_timer[n] = val;
  NVIC_EnableIRQ(TIM3_IRQn);
}

Ну и простой пример, как этим всем пользоваться. Пусть нам надо с частотой 50 Гц (период 0,02 сек.) выполнять какое-то действие Do():

void main(void)
{
  for(;;)  //бесконечный цикл
  {
    //Если прошло 2 или больше тиков программного таймера
    //(1 тик программного таймера равен 0,01 сек.)
    if(GetTimer(0) >= 2) 
    {
      ResetTimer(0); //сбрасываем таймер
      Do(); //делаем какую-то полезную вещь
    }
  }
}

Тут следует отметить одну важную вещь. Перепишем код вот так:

void main(void)
{
  for(;;)  //бесконечный цикл
  {
    //Если прошло 2 или больше тиков программного таймера
    //(1 тик программного таймера равен 0,01 сек.)
    if(GetTimer(0) >= 2) 
    {
      Do(); //делаем какую-то полезную вещь
      ResetTimer(0); //сбрасываем таймер
    }
  }
}

Тут мы сначала что-то делаем, а потом сбрасываем таймер. В этом случае функция Do() будет выполняться каждые 0,02+время_выполнения_Do. С этим, думаю, все ясно. Поехали дальше.

Давайте теперь посмотрим в main():

void main(void)
{
  ADC_init(); //Инициализация АЦП крутилок яркости и режима
  ButtonInit(); //Инициализация кнопки выбора режима
  TickTimerInit(); //Инициализация программных таймеров
  
  //Эта функция нужна для инициализации 
  //одного из режимов работы
  DropsClear();
  
  //Инициализация интерфейса светодиодной ленты
  ws2812b_init();
  while(!ws2812b_is_ready())
    ;
  
  //Бесконечный цикл 
  for(;;)
  {
    sfm_main(); //Главный конечный автомат
    fsm_disp(); //Конечный автомат, отвечающий за отображение эффектов
  }
}

Из интересного тут только бесконечный цикл, в котором вызываются 2 функции: sfm_main() и fsm_disp(). Это и есть те самые конечные автоматы, реализующие всю логику работы лампы.

Код конечного автомата sfm_main():

void IncrMode(void)
{
  NumMode++;
  if(NumMode > 8)
    NumMode = 1;
}


void sfm_main(void)
{
  static uint8_t b_state = 0;
  
  switch(b_state)
  {
  case 0:
    //если зарегистрировали нажатие на кнопку
    if(ButtonIsPress())
    {
      ResetTimer(TIMER_ANTIBOUNCE);
      b_state = 1; //переходим в состояние 1
    }
    break;
    
  case 1:
    //тут логика такая: если через 5 тиков таймера 
    //кнопка все еще находится в нажатом состоянии,
    //это означает, то нажате действительно было
    //и это не случайный дребезг контактов, например,
    //при отпускании кнопки
    if(GetTimer(TIMER_ANTIBOUNCE) >= 5)
    {
      if(ButtonIsPress())
      {
        //Если надатие было, то
        IncrMode(); //увеличиваем номер режима на единицу
        flag_dispmode = 1; //устанавливаем глобальный флаг
        //flag_dispmode в единицу, который говорит другому конечному автомату, 
        //что надо показать столбик, демонстрирующий номер режима
        
        b_state = 2; //Переходим в состояние отпускания кнопки
        ResetTimer(TIMER_ANTIBOUNCE);
      }
      else
      {
        //если же через 5 тиков кнопка не нажата,
        //то это был какой-то дребезг 
        //и на него не надо обращать внимание.
        //Возвращаемся в исходное состояние
        b_state = 0;
      }
    }
    break;
    
  case 2:
    if(GetTimer(TIMER_ANTIBOUNCE) >= 10)
    {
      //делаем задержку на 10 тиков
      //после этого проверяем, была ли отпущена кнопка.
      //Если отпустили, то переходим в тсходное состояние.
      //Скорее всего задержка в 10 тиков 
      //тут особо и не нужна, осталось как рудимент 
      //процесса разработки
      if(!ButtonIsPress())
      {
        b_state = 0;
      }
    }
    break;
  }
}

Он отвечает за обработку событий нажатия на кнопку, изменение номера режима, и дает команду конечному автомату fsm_disp() об отображении номера режима.

Код конечного автомата fsm_disp():

void fsm_disp(void)
{
  static uint8_t state = 0;
  
  switch(state)
  {
  case 0:
    //Обновляем светодиодную ленту с частотой 50 Гц
    if(GetTimer(TIMER_FPS) >= 2) 
    {
      ResetTimer(TIMER_FPS);
      ADC_AnalogRead(&dimmer_val, &mode_val); //читаем состояние крутилок
      //dimmer_val и mode_val - глобальные переменные, хранящие 
      //положение ручек яркости и настройки режима
      
      Display(); //обновляем ленту
    }
    
    //Если от конечного автомата sfm_main()
    //пришла команда об отображении номера режима,
    //то переходим в соответствующее состояние
    if(flag_dispmode)
    {
      ResetTimer(TIMER_DISP);
      flag_dispmode = 0;
      state = 1;
    }
    break;
    
  case 1:
    //обновляем "столбик" номера режима с частотой 50 Гц
    if(GetTimer(TIMER_FPS) >= 2)
    {
      ResetTimer(TIMER_FPS);
      ADC_AnalogRead(&dimmer_val, &mode_val);
      disp_mode();
    }
    
    //Если пришло еще одно событие об отображении номера режима
    if(flag_dispmode)
    {
      //сбрасываем тайсер времени отображения режима
      ResetTimer(TIMER_DISP);
      flag_dispmode = 0;
    }
    
    //Если прошла секунда с момента последнего
    //нажатия на кнопку, то 
    //возвращаем все как было
    //и переходим в состояние отображения заданного эффекта
    if(GetTimer(TIMER_DISP) >= 100) //прошло 1000мс
    {
      while(!ws2812b_is_ready())
        ;
      ws2812b_buff_claer();
      ws2812b_send();
      state = 0;
    }
    break;
  }
}

fsm_disp() отвечает за вызов функции «отрисовки» эффекта Display() с частотой 50 Гц и отображение номера текущего режима disp_mode().

Функция disp_mode():

void disp_mode(void)
{
  int i;
  
  while(!ws2812b_is_ready())
    ;
  ws2812b_buff_claer();
  
  
  for(i=0; i<NumMode; i++)
  {
    HSV_to_RGB(i*(300/6), 255, (dimmer_val/16), &rc, &gc, &bc);
    ws2812b_set(i, rc, gc, bc);
  }
  
  ws2812b_send();
  
}

Тут все просто: рисуем столбик из NumMode светодиодов, соответствующий номеру режима, разным цветом.

Так, приближаемся к «сердцу» всей системы. Функция Display():

void Display(void)
{
  switch(NumMode)
  {
  case 1:
    SingleColor(dimmer_val, mode_val);
    break;
    
  case 2:
    VGrad(dimmer_val, mode_val);
    break;
    
  case 3:
    While(dimmer_val, mode_val);
    break;
    
  case 4:
    DynamicSingleColor(dimmer_val, mode_val);
    break;
    
  case 5:
    DynamicHRainbow(dimmer_val, mode_val);
    break;
    
  case 6:
    DynamicVRainbow(dimmer_val, mode_val);
    break;
    
  case 7:
    DynamicHelixRainbow(dimmer_val, mode_val);
    break;
    
  case 8:
    DynamicRain(dimmer_val, mode_val);
    break;
    
  default:
    NumMode = 1;
    break;
  }
}

Тут у нас огроменный switch, который вызывает функцию нужного нам режима NumMode. Еще раз обращаю внимание на тот факт, что Display() выполняется 50 раз в секунду. Это важно для динамических эффектов, так как они внутри себя имеют счетчик номера вызова, ориентируясь на который производятся вычисления промежутков времени в анимациях.

Количество режимов 8 штук. Все рассматривать не будем, давайте только взглянем на самый простой и самый сложный режим. Режим «Одиночный цвет:»

void SingleColor(uint16_t dv, uint16_t mv) 
{
  HSV_to_RGB(((mv * 8) / 91), 255, dv/16, &rc, &gc, &bc);
  
  while(!ws2812b_is_ready())
    ;
  for(int i=0; i<WS2812B_NUM_LEDS; i++)
  {
    ws2812b_set(i, rc, gc, bc);
  }
  ws2812b_send();
}

Ориентируясь на положения ручек яркости и настройки режима, функцией HSV_to_RGB() получаем компоненты RGB палитры HSV и потом в цикле заполняем все светодиоды выбранным цветом.

Ну и самый сложный, самый мой любимый режим: DynamicRain() — капли дождя:

#define TRAIL_LEN       4
typedef struct 
{
  int8_t coord;            //текущая координата капли
  int8_t vel;              //длительность стекания капли
  int8_t counter;          //счетчик, используется алгоритмом
  uint16_t color;          //цвет капли
  uint8_t flag_inprogress; //1-в процессе, 0-завершено
  uint16_t post_delay;     //задержка перед генерацией новой капли
} drop;

static drop drops[NUM_COLUM];

static void DropsClear(void)
{
  for(int i=0; i<NUM_COLUM; i++)
  {
    drops[i].coord = -1;
    drops[i].vel = 0;
    drops[i].color = 0;
    drops[i].flag_inprogress = 0;
    drops[i].post_delay = 1;
  }
}

void DynamicRain(uint16_t dv, uint16_t mv)
{
  uint32_t rnd;
  
  while(!ws2812b_is_ready())
    ;
  
  for(int i=0; i<NUM_COLUM; i++)
  {
    rnd = (uint32_t)rand();
    
    if(drops[i].flag_inprogress == 0) //Если тут нет капли
    {
      //Генерация капли
      drops[i].coord = NUM_ROW-1; //Начинаем сверху
      drops[i].color = rnd%360; //задаем цвет
      drops[i].vel = rnd%10; //задаем период стекания
      drops[i].counter = 0; //сбрасываем счетсик
      
      //задержка перед генерацией следующей капли
      int16_t rnd_param = (3800-mv)/4;
      if(rnd_param < 0)
        drops[i].post_delay = 0;
      else
        drops[i].post_delay = rnd % rnd_param; 
      
      
      drops[i].flag_inprogress = 1;
      //Тут очищаем колонку
      for(int j=0; j<NUM_ROW; j++)
      {
        ws2812b_set(i*NUM_ROW+j, 0, 0, 0);
      }
      
    }
    else //иначе выводим на индикатор
    {
      if(drops[i].coord >= -(TRAIL_LEN+1))
      {
        if(drops[i].counter < drops[i].vel)
        {
          drops[i].counter++;
        }
        else
        {
          drops[i].counter = 0;        
          
          if(drops[i].coord >= 0) //Если капля не вышла за пределы экрана
          {
            int j;
            for(j=0; j<drops[i].coord; j++)
            {
              ws2812b_set(i*NUM_ROW+j, 0, 0, 0);
            }
          
            int16_t L = (dv/16);
            
            for(; j<NUM_ROW; j++)
            {
              HSV_to_RGB(drops[i].color, 255, L, &rc, &gc, &bc);
              ws2812b_set(i*NUM_ROW+j, rc, gc, bc);
              L-=((dv/16)/(TRAIL_LEN+1));
              if(L<0)
                L=0;
            }
          }
          else //тут рисуем только хвост
          {
            int j;
            uint8_t trail_residue = TRAIL_LEN+drops[i].coord+1;
            
            for(j=0; j<NUM_ROW-trail_residue; j++)
            {
              ws2812b_set(i*NUM_ROW+j, 0, 0, 0);
            }
            
            int16_t L = (dv/16);
            L -= ((TRAIL_LEN-trail_residue+1)*((dv/16)/(TRAIL_LEN+1)));
            
            for(j=0; j<trail_residue; j++)
            {
              HSV_to_RGB(drops[i].color, 255, L, &rc, &gc, &bc);
              ws2812b_set(i*NUM_ROW+j, rc, gc, bc);
              L-=((dv/16)/(TRAIL_LEN+1));
              if(L<0)
                L=0;
            }
          }
          
          drops[i].coord--;
        }
      }
      else
      {
        if(drops[i].post_delay == 0)
          drops[i].flag_inprogress = 0;
        else
          drops[i].post_delay--;

      }
    }
  }
  
  ws2812b_send();
}

Тут я даже пытаться не буду объяснять, как это работает 🙂

Чтобы добавить новый режим, нужно сделать 3 вещи. Первое: написать функцию, реализующую определенный режим, затем в Display() добавить еще одну конструкцию case с номером на единицу больше номера последнего режима (в данном случае 9). И напоследок, в функции IncrMode() в условии if(NumMode > 8) увеличить константу сравнения на единицу (тут тоже на 9). Отлично, новый режим интегрирован в систему!

На этом, в принципе, все! Спасибо за внимание!!! 😉

Ссылки

  1. Часть 1: RGB ночник из керосиновой лампы
  2. Программа для МК: https://github.com/DiMoonElec/KeroseneRGBLamp
  3. Применение Switch-технологии при разработке прикладного программного обеспечения для микроконтроллеров. Часть 1
Метки: , , , , , . Закладка Постоянная ссылка.

Добавить комментарий