Под новый год на торговых площадках и магазинах выбрасывается в продажу куча примитивных сувенирных светильников с питанием на литиевых батарейках-таблетках типа CR2032. Внутри находится светодиод, который либо имитирует мерцание свечи, либо хаотично меняет цвет. Такой светильник включается, кладется куда-нибудь под елку и благополучно забывается. Устав менять батарейки, я решил сделать для него режим энергосбережения и, заодно, поупражнять мозги, пощупав низко потребляющую серию микроконтроллеров STM8L10х.
Вообще, линейка STM8L почему-то незаслуженно забыта в плане всяких отладочных плат. Если вбить в поиске какую-нибудь «stm8s003f3 board» или «stm8s103f3 board», то выйдет куча вариантов. Ну да ладно, будем городить свою плату с диодами и датчиками.
В качестве «мозга» был выбран МК STM8L101F3P6. Благодаря низкому напряжению питания 1,65-3,6 вольт он отлично покрывает возможности литиевой батарейки и не требует дополнительных вмешательств по согласованию напряжений. А встроенной памяти в 8 кбайт хватит для реализации любого хитро выдуманного алгоритма. Так как в L серии полноценные загрузчики через последовательный интерфейс имеют только STM8L151xx и STM8L152xx, для прошивки потребуется программатор ST-Link с поддержкой SWIM. Я же воспользовался имеющейся у меня отладочной платой STM8L-Discovery.
Список компонентов
Микроконтроллер STM8L101F3P6 в корпусе TSSOP20 (10 шт./лот) ($7.14)
Переходник с TSSOP20 на DIP20 в качестве макетной платы (10 шт.) ($0.93)
3 мм светодиоды различных цветов (100 шт./лот) ($1.06)
Датчик вибрации HDX-2 SW-420 (10 шт./лот) ($0.49)
Еще опционально керамический конденсатор на 0,1 мкф параллельно питанию МК.
Схема и алгоритм работы
Светодиод подключается к выходу канала 1 таймера TIM2 для использования ШИМ-модуляции. Токоограничивающий резистор отсутствует, так как максимальное значение ШИМ подобрано таким образом, что бы ограничить ток 20 мА. Датчик вибрации вешается на любой удобный порт. На этом порту настраивается прерывание на положительную или отрицательную границу сигнала (на самом деле не имеет значения).
Микроконтроллер работает от внутреннего высокочастотного генератора с максимальным делителем (итоговая частота работы — 2 Мгц). При старте микроконтроллер начинает ждать сигналов с датчика вибрации. Если он их не дожидается, то через 10 секунд уходит в режим active halt, в котором по AN3147 потребляет всего 1 мкА. Стоит взять светильник в руку и начать его трясти, как яркость свечения светодиода начинает нарастать. Если его оставить в покое, то он плавно погаснет и опять заснет. После непрерывного пятисекундного «теребонькания» светильник перейдет в рабочий режим свечения, пытаясь выдать себя за свечку. Что бы погасить светильник, его также нужно непрерывно трясти 5 секунд, пока яркость светодиода не снизится до нуля или просто выключить штатной кнопкой. Если включенный светильник не трогать, то он сам погаснет через 5 часов и уйдет в сон.
Программа
Программа написана в среде IAR Embedded Workbench версии 1.40.1. Бесплатная версия ограничена размером прошивки в 8 кбайт, что вполне достаточно. Для удобства использовалась библиотека со стандартными периферийными функциями (STM8L10x standard peripheral library). Текст программы довольно подробно откомментирован, архив с проектом приложен в конце.
#include "stm8l10x.h"
#define LED_PORT GPIOC
#define LED_PIN GPIO_Pin_2 // вывод 20
#define LED_TOGGLE() (GPIO_ToggleBits(LED_PORT, LED_PIN))
//#define LED_ON() (GPIO_WriteBit(LED_PORT, LED_PIN, SET)) - это говно не работает!!!
//#define LED_OFF() (GPIO_WriteBit(LED_PORT, LED_PIN, RESET)) - - это говно не работает!!!
#define LED_ON() (LED_PORT->ODR |= GPIO_Pin_2)
#define LED_OFF() (LED_PORT->ODR &= ~GPIO_Pin_2)
#define BTN_GPIO_PORT GPIOB
#define BTN_GPIO_PIN GPIO_Pin_7 // порт PB7 (17) - вход кнопки
#define BTN_PRESSED() (!GPIO_ReadInputDataBit(BTN_GPIO_PORT, BTN_GPIO_PIN))
#define TIM2_PERIOD 1000 // период таймера 2
/* константы для режима свечи, все времена в миллисекундах */
#define MIN_DELAY_BEFORE_DOWN 1
#define MAX_DELAY_BEFORE_DOWN 1000
#define MIN_PERIOD_DOWN 200 // минимальная длительность угасания
#define MAX_PERIOD_DOWN 300 // максимальная длительность угасания
#define MIN_BRIGHTNESS_DOWN 100 // минимальное значение яркости, до которого будет угасать свеча (значение подбирается экспериментально)
#define MAX_BRIGHTNESS_DOWN 900 // максимальное значение яркости, до которого будет угасать свеча (значение подбирается экспериментально)
#define MIN_PERIOD_UP 200 // минимальная длительность разгорания
#define MAX_PERIOD_UP 500 // максимальная длительность разгорания
#define TIMER_INTERVAL 25 // периодичность срабатывания прерывания
#define MAX_BRIGHTNESS 900 // максимально возможное значение яркости (подбирается экспериментально во току светодиода 20мА)
#define set_brightness(br) TIM2_SetCompare2(br)
#define WORK_TIME_VALUE (720000UL) // время непрерывной работы в тиках 40 Гц таймера = 5ч * 60 м * 60 с * 40 Гц
//#define WORK_TIME_VALUE (24000UL) // ***debug***
////////////// прототипы ///////////////
static void TIM2_Config(void);
static void TIM4_Config(void);
void Delay(__IO uint16_t nCount);
uint16_t candleMode(void);
void EnterLowPowerMode();
void ExitLowPowerMode();
// uint16_t lighthouseMode(void);
/* немного рандомной магии */
#define RAND_MAX 32767
uint16_t rand(void);
uint16_t random(uint16_t minval, uint16_t maxval);
// функция возвращает псевдослучайное число из диапазона [0; RAND_MAX]
uint16_t rand()
{
static uint32_t next=1;
next=next*1103515245+12345;
return((uint16_t)(next/65536)%32768);
}
// функция возвращает псевдослучайное число из диапазона [minval; maxval]
uint16_t random(uint16_t minval, uint16_t maxval)
{
return ((uint16_t)(((maxval-minval)*1UL*rand())/RAND_MAX) + minval);
}
enum mode_T {OFF, ON, TRY_OFF} work_mode, next_mode;
volatile uint16_t g_pwm_value = 0;
volatile uint32_t g_off_counter = WORK_TIME_VALUE; // время, оставшееся до выключения
/* Что делать с неииспользуемыми выводами:
By default, the I/Os are configured as floating input.
It is important to change the configuration of all I/Os that are not connected to defined logic
signals so as to obtain one of the following configurations:
- input configuration with pull-up
- or, output configuration with a low (or high) logic level
Otherwise, an increased consumption is generated by noise, as the internal Schmitt triggers
detecting this noise are toggling.
Floating I/Os could generate additional consumption in the range of a few 10 µA.
There are also parts with some unbonded I/Os (typically different packages of the same
product). Those I/Os are connected to a defined level by factory option configuration, unless
otherwise specified in the datasheet.
*/
void main(void)
{
CLK_MasterPrescalerConfig(CLK_MasterPrescaler_HSIDiv8); // максимальный делитель = минимальная частота 16/8 = 2 МГц
TIM2_Config();
TIM4_Config();
//GPIO_Init(LED_PORT, LED_PIN, GPIO_Mode_Out_PP_Low_Fast); // вывод для светодиода для отладки
GPIO_Init(BTN_GPIO_PORT, BTN_GPIO_PIN, GPIO_Mode_In_PU_IT); // вход с подтяжкой с внешним прерыванием
EXTI_SetPinSensitivity(EXTI_Pin_7, EXTI_Trigger_Falling);
// неиспользуемые порты на выход и в 0
GPIO_Init(GPIOA, GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3, GPIO_Mode_Out_PP_Low_Slow); // выводы 3, 4, 5, 6
GPIO_Init(GPIOB, GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_5|GPIO_Pin_6, GPIO_Mode_Out_PP_Low_Slow); // выводы 10, 11, 12, 13, 15, 16
GPIO_Init(GPIOC, GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4, GPIO_Mode_Out_PP_Low_Slow); // выводы 18, 19, 20, 1, 2
GPIO_Init(GPIOD, GPIO_Pin_0, GPIO_Mode_Out_PP_Low_Slow); // выводы 9
work_mode = OFF;
set_brightness(0);
/* Enable interrupts*/
enableInterrupts();
while (1)
{
;
}
}
static void TIM2_Config(void)
{
/* Enable TIM2 clock */
CLK_PeripheralClockConfig(CLK_Peripheral_TIM2, ENABLE);
/* Config TIM2 channel 2 pins */
GPIO_Init(GPIOB, GPIO_Pin_2, GPIO_Mode_Out_PP_High_Fast); // вывод PB2 (12)
/* Time base configuration */
// 2 МГц/1000 = 2000 Гц
TIM2_TimeBaseInit(TIM2_Prescaler_1, TIM2_CounterMode_Up, TIM2_PERIOD);
/* PWM1 Mode configuration: Channel2 */
TIM2_OC2Init(TIM2_OCMode_PWM1, TIM2_OutputState_Enable, 0, TIM2_OCPolarity_High, TIM2_OCIdleState_Set); // вывод PB2/TIM2_CH2/(12)
TIM2_OC2PreloadConfig(ENABLE);
TIM2_ARRPreloadConfig(ENABLE);
TIM2_SetCompare2(0);
/* Enable TIM2 outputs */
TIM2_CtrlPWMOutputs(ENABLE);
/* TIM2 enable counter */
TIM2_Cmd(ENABLE);
}
static void TIM4_Config(void)
{
/* Enable TIM4 CLK */
CLK_PeripheralClockConfig(CLK_Peripheral_TIM4, ENABLE);
TIM4_DeInit();
/* Time base configuration */
TIM4_TimeBaseInit(TIM4_Prescaler_256, 195); // 2 МГц / 256 / 195 = 40 Гц
TIM4_ITConfig(TIM4_IT_Update, ENABLE);
TIM4_ARRPreloadConfig (ENABLE);
/* Enable TIM4 */
TIM4_Cmd(ENABLE);
}
// функция мерцания свечи
uint16_t candleMode(void)
{
/* переменные */
static uint8_t step = 0; // 0 - delay, 1 - down, 2 - up
static uint16_t needBrightness = MAX_BRIGHTNESS; // требуемое значение яркости
static uint16_t curBrightness = MAX_BRIGHTNESS; // текущее значение яркости
static uint16_t oneStep = 0; // шаг изменения яркости
static uint16_t curPeriod = 0; // длительность изменения свечения
static uint16_t delayBeforeDownUp = 0;
if(step == 0) // долгая задержка перед очередным угасанием
{
if (delayBeforeDownUp == 0) // генерация значений для следующего шага
{
step = 1;
needBrightness = random(MIN_BRIGHTNESS_DOWN, MAX_BRIGHTNESS_DOWN);
curPeriod = random(MIN_PERIOD_DOWN, MAX_PERIOD_DOWN);
oneStep = ((MAX_BRIGHTNESS - needBrightness) * TIMER_INTERVAL) / curPeriod; // шаг времени для каждого вызова функции
if (oneStep == 0) oneStep = 1; // если шаг получился нулевой, делаем его минимальным
}
else
{
if (delayBeforeDownUp > TIMER_INTERVAL) delayBeforeDownUp -= TIMER_INTERVAL;
else delayBeforeDownUp = 0;
}
}
else if(step == 1) // down
{
if (curBrightness == needBrightness) // генерация значений для следующего шага
{
step = 2;
needBrightness = MAX_BRIGHTNESS;
curPeriod = random(MIN_PERIOD_UP, MAX_PERIOD_UP);
oneStep = ((MAX_BRIGHTNESS - curBrightness) * TIMER_INTERVAL) / curPeriod;
if (oneStep == 0) oneStep = 1;
}
else
{
if ((curBrightness - needBrightness) > oneStep) curBrightness -= oneStep;
else curBrightness = needBrightness;
}
}
else
{
if (curBrightness == needBrightness) // генерация значений для следующего шага
{
step = 0;
delayBeforeDownUp = random(MIN_DELAY_BEFORE_DOWN, MAX_DELAY_BEFORE_DOWN);
}
else
{
if ((needBrightness - curBrightness) > oneStep) curBrightness += oneStep;
else curBrightness = needBrightness;
}
}
return curBrightness;
}
// бонусная функция имитации мерцания маяка (плавное нарастание -> плавный спад)
uint16_t lighthouseMode(void)
{
static uint8_t direction = 1; // верх
const uint16_t max_pwm = 900;
static uint16_t cur_pwm = 0;
static uint16_t pwm_step = 40; // шаг прибавления/убавления яркости
if(direction == 1)
{
if(cur_pwm + pwm_step < max_pwm) cur_pwm += pwm_step;
else
{
cur_pwm = max_pwm; direction = 0;
}
}
else // direction == 0
{
if(cur_pwm > pwm_step) cur_pwm -= pwm_step;
else
{
cur_pwm = 0; direction = 1;
}
}
return cur_pwm;
}
volatile uint16_t shake_counter = 0; // счетчик потрясываний
volatile bool halt_flag = FALSE; // флаг перехода в режим сна
// прерывание по совпадению таймера 4 (частота срабатывания 40 Гц)
INTERRUPT_HANDLER(TIM4_UPD_OVF_IRQHandler, 25)
{
const uint16_t pwm_step = MAX_BRIGHTNESS / 2 / 40; //
static uint16_t delay_counter = 0;
static const uint16_t delay_time = 16; // 400 мс
static const uint16_t delay_before_halt = 10 * 40; // время, которое МК пробудет в выключенном состоянии, перед тем как заснуть (в тиках таймера)
static uint16_t halt_counter = 10 * 40;
static uint32_t work_counter = WORK_TIME_VALUE;
switch(work_mode)
{
case OFF:
{
if(delay_counter > 0) // задержка включена для того, что бы после выключения светильник не начал опять включаться
{
if(shake_counter > 0)
{
shake_counter = 0;
}
else
{
delay_counter--;
}
}
else
{
if(shake_counter > 0) // если состояние изменилось
{
shake_counter = 0;
if(g_pwm_value < MAX_BRIGHTNESS - (pwm_step))
g_pwm_value += (pwm_step);
else
{
work_mode = ON;
//g_off_counter = g_off_value;
delay_counter = delay_time;
}
}
else
{
if(g_pwm_value > (pwm_step))
g_pwm_value -= (pwm_step);
else
g_pwm_value = 0;
}
if(g_pwm_value == 0) // если текущая яркость нулевая, пробуем заснуть, если никто не начнет опять трясти
{
if(halt_counter > 0)
{
halt_counter--;
}
else // дождались
{
halt_counter = delay_before_halt;
halt_flag = TRUE;
TIM4_ClearITPendingBit(TIM4_IT_Update); // сброс флага прерывания, что бы МК смог уснуть
__halt();
}
}
}
set_brightness(g_pwm_value);
}
break;
case ON:
{
set_brightness(candleMode());
if(delay_counter > 0) // если требуется задержка
{
if(shake_counter > 0)
{
shake_counter = 0;
}
else
{
delay_counter--; // счетчик уменьшаем только тогда, когда кнопка отпущена
}
}
else // отработка тряски
{
if(shake_counter > 0) // светильник трясут
{
shake_counter = 0;
g_pwm_value = MAX_BRIGHTNESS;
work_mode = TRY_OFF;
}
}
if(work_counter > 0)
{
work_counter--;
}
else
{
// кончилось время работы
g_pwm_value = 0;
set_brightness(g_pwm_value);
work_counter = WORK_TIME_VALUE;
work_mode = OFF;
}
} //end of case ON:
break;
case TRY_OFF:
{
if(shake_counter > 0) // есть тряска
{
shake_counter = 0;
if(g_pwm_value > pwm_step)
g_pwm_value -= pwm_step; // пока можно, уменьшаем яркость до 0
else // если дошли до 0
{
g_pwm_value = 0;
delay_counter = delay_time;
work_mode = OFF; // режим выключения
}
}
else // прекратили трясти
{
if(g_pwm_value < MAX_BRIGHTNESS - pwm_step)
g_pwm_value += pwm_step; // постепенно наращиваем яркость до максимума
else // дошли до максимума
{
work_mode = ON;
}
}
set_brightness(g_pwm_value);
} // end case TRY_OFF
break;
}
TIM4_ClearITPendingBit(TIM4_IT_Update);
}
// прерывание по входу на пине PB7 (17)
INTERRUPT_HANDLER(EXTI7_IRQHandler, 15)
{
//LED_TOGGLE(); // **** debug ****
if(shake_counter < U16_MAX) shake_counter++;
if(halt_flag) // если МК вышед из сна
{
halt_flag = FALSE;
}
EXTI_ClearITPendingBit(EXTI_IT_Pin7);
}
void Delay(__IO uint16_t nCount)
{
/* Decrement nCount value */
while (nCount != 0)
{
nCount--;
}
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval : None
*/
void assert_failed(uint8_t* file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %drn", file, line) */
/* Infinite loop */
while (1)
{
}
}
#endif
Модификация светильника
Со светильника снимается крышка и отпаивается штатный светодиод
На переходник-макетку напаивается микроконтроллер со всей обвязкой
Макетка припаивается к штатным контактам держателя батарейки и кнопке. 2 крепежных винта удалось задействовать для выводов NRST и SWIM, что бы в дальнейшем прошивать МК без потрошения светильника
Вот гифка, как это выглядит в темноте
Архив с программой на Яндекс.Диске
Всем добра!