Решил восстановить УФ лампу для засветки фоторезиста. Прикинул, что ставить ардуинку, слишком жирно. Обойдется и Attiny13. Нашел проект с индикатором на TM1637, которого нет. Зато TM1650 как у Матроскина гуталина. Проект кухонного таймера: https://github.com/SergeyVN94/timer-attiny13a?ysclid=ldkar0a44x759169601. Переписал вывод на TM1650, и буквально 3 строки для изменения алгоритма работы.
Небольшое видео:
https://youtu.be/Y6N882hlXzU
Схема:
Скетч:
#include <avr/io.h>
#define F_CPU 1200000UL
#include <util/delay.h>
#include <avr/interrupt.h>
//сделано по мотивам:
//https://arduino.ru/forum/programmirovanie/attiny13a-101-primenenie?page=19#comment-225250 I2C
//https://github.com/SergeyVN94/timer-attiny13a?ysclid=ldkar0a44x759169601 таймер
//https://arduino.ru/forum/programmirovanie/kontroller-led-i-klaviatury-fd650v-kak-im-upravlyat#comment-232263 TM1650
/*
DDRx Настройка разрядов порта x на вход или выход.
PORTx Управление состоянием выходов порта x (если соответствующий разряд настроен как выход), или подключением внутреннего pull-up резистора (если соответствующий разряд настроен как вход).
PINx Чтение логических уровней разрядов порта x.
*/
typedef uint8_t byte;
// настройки пинов
#define OUTPUT true
#define INPUT false
#define HIGH true
#define LOW false
#define MODE true // пин настраивается на вход или выход
#define STATE false // установка высокого или низкого уровня пина
// пины
#define _NOP() asm volatile("nop")
//массив знакогенератора 7 сегментного индикатора
byte simv[11] = {
B00111111, //0
B00000110, //1
B01011011, //2 cоответствие разрядов и сегментов
B01001111, //3
B01100110, //4 _1_
B01101101, //5 6 | | 2
B01111101, //6 |_7_|
B00000111, //7 5 | | 3
B01111111, //8 |_4_|
B01101111, //9
B00000000, //пустота
};
// delay() для тиньки и свой нормальный, а delayMicroseconds() дурной
//а вот глобального такого дефайна может и не быть, как ни странно, так для страховки завел
//#define F_CPU 4800000UL
#define LOOP_CYCLES 8
#define us(num) (num/(LOOP_CYCLES*(1/(F_CPU/1000000.0))))
inline __attribute__((gnu_inline)) void asm_delay(uint16_t delay) {
uint16_t u = us(delay);
do _NOP(); while (delay--);
}
#define wdelay_us(x) asm_delay(us(x))
#define wdelay(x) delay(x)
/*****************************************************/
//дальше мелкий вариант i2c на любых ногах
#define IICR 1
#define IICW 0
#define I2CDelay() wdelay_us(4) // на 100КГц примерно по 4 мкс нужно держать уровни SCL
#define PIN_SIGNAL PB1 // pin 6
#define PIN_BUTTONS PB2 // pin 7
#define PIN_SDA PB3 // pin 2
#define PIN_CLK PB4 // pin 3
#define SDA_In() (PINB & 0B00001000)
#define SCL_In() (PINB & 0B00010000)
// кнопки
#define BTN_SET 0
#define BTN_PLUS 1
#define BTN_MINUS 2
#define BTN_NOT_SELECTED 3
// состояния программы
#define SET_MINUTES 0
#define SET_SECONDS 1
#define WORK 2
#define PAUSE 3
#define WORK_END 4
// другие настройки
#define MAX_COUNTER 75
#define SIGNAL_TIME 0 // in seconds
const byte selectedBtn = 0;
/*
Настройка пина.
Если 'mode' равен STATE, устанавливается логический уровень пина.
Если 'mode' равен MODE, пин настраивается на вход или выход.
*/
void changePin(byte pin, bool state, bool mode = STATE) {
if (mode == STATE) (state ? (PORTB |= (1 << pin)) : (PORTB &= ~(1 << pin)));
else (state ? (DDRB |= (1 << pin)) : (DDRB &= ~(1 << pin)));
}
/******************************************************/
bool isTimerTick = false;
bool pointsOn = true;
byte PROGRAM_STATE = SET_MINUTES;
byte timerCounter = 0;
uint16_t secondsCounter = 0;
uint16_t getButtonsAnalogValue() {
// включить ацп, тактирование в 128 раз меньше, начать измерение
ADCSRA = (1 << ADEN) | (1) | (1 << ADSC);
// ждать конца измерения
while (ADCSRA & (1 << ADSC));
return (ADCL | (ADCH << 8));
}
byte checkButtons() {
uint16_t analogValue = getButtonsAnalogValue();
if (analogValue >= 950 && analogValue <= 1023) return BTN_SET;
else if (analogValue >= 470 && analogValue <= 530) return BTN_PLUS;
else if (analogValue >= 300 && analogValue <= 400) return BTN_MINUS;
return BTN_NOT_SELECTED;
}
void setStartState() {
PROGRAM_STATE = SET_MINUTES;
secondsCounter = 0;
pointsOn = true;
printTime();
}
ISR(TIM0_COMPA_vect) {
if (timerCounter < MAX_COUNTER) {
timerCounter += 1;
} else {
timerCounter = 0;
isTimerTick = true;
}
}
inline void SDA_Hi() {
PORTB |= 0B00001000;
DDRB &= 0B11110111;
}
inline void SDA_Lo() {
PORTB &= 0B11110111;
DDRB |= 0B00001000;
}
inline void SCL_Hi() {
PORTB |= 0B00010000;
DDRB &= 0B11101111;
}
inline void SCL_Lo() {
PORTB &= 0B11101111;
DDRB |= 0B00010000;
}
void I2CInit (void)
{
SDA_Hi();
SCL_Hi();
}
void I2CStart (void)
{
SCL_Hi();
while (!SCL_In());
I2CDelay();
SDA_Lo();
SCL_Lo();
}
void I2CStop (void)
{
I2CDelay();
SCL_Hi();
I2CDelay();
SDA_Hi();
I2CDelay();
}
boolean I2CWrite(byte b)
{
byte i = 1 << 7;
boolean ack = 0;
while (i)
{
I2CDelay();
if (b & i) SDA_Hi(); else SDA_Lo();
I2CDelay();
SCL_Hi();
I2CDelay();
SCL_Lo();
i >>= 1;
}
SDA_Hi();
I2CDelay();
SCL_Hi();
if ( SDA_In() == 0 ) ack = 1;
I2CDelay();
SCL_Lo();
SDA_Lo();
return ack;
}
byte I2CRead(boolean ack)
{
byte i = 8;
byte b = 0;
while (i--)
{
I2CDelay();
SCL_Hi();
if (SDA_In()) b |= 1;
b <<= 1;
I2CDelay();
SCL_Lo();
}
if (ack) SDA_Lo();
else SDA_Hi();
I2CDelay();
SCL_Hi();
I2CDelay();
SCL_Lo();
SDA_Lo();
return b;
}
/*************************************/
int main(void) {
changePin(PIN_SIGNAL, OUTPUT, MODE);
changePin(PIN_BUTTONS, INPUT, MODE);
// Инициализация дисплея TM1650,Подробнее https://arduino.ru/forum/programmirovanie/kontroller-led-i-klaviatury-fd650v-kak-im-upravlyat#comment-232263
I2CStart();
I2CWrite(0x27 << 1);
I2CWrite(B00010001);
I2CStop();
// timer
/* нужно:
1) включить срабатывание по сравнению с регистром (режим СТС)
2) утановить предделитель
3) указать регистр, с которым сравнивается
4) записать число с которым сравнивать
5) включить прерывания
TCCR0B, TCCR0A - регистры управления таймером/счетчиком
TIMSK0 - регистр настрек
TCNT0 - таймер/счетчик
OCR0A и OCR0B - регистры сравнения
*/
TCCR0A |= (1 << WGM01); // режим СТС (сравнение с регистром)
TCCR0B |= (1 << CS01) | (1 << CS00); // предделитель частоты на 64
TIMSK0 |= (1 << OCIE0A); // сравнение с регистрам А включение прерывания
OCR0A = 250;
TCNT0 = 0; // сброс таймера
sei(); // включить прерывания
// АЦП
ADMUX = 1; // сравнение с питанием, ацп на пине PB2
// инициализация
byte signalCounter = SIGNAL_TIME;
printTime();
while (1) {
const byte selectedBtn = checkButtons();
if (PROGRAM_STATE == WORK) changePin (PIN_SIGNAL,1);
else changePin (PIN_SIGNAL,0);
switch (PROGRAM_STATE) {
case SET_MINUTES: {
if (selectedBtn == BTN_PLUS) secondsCounter = (secondsCounter < 5940) ? (secondsCounter + 60) : 0;
if (selectedBtn == BTN_MINUS) secondsCounter = (secondsCounter >= 60) ? (secondsCounter - 60) : 5940;
if (selectedBtn == BTN_SET) PROGRAM_STATE = SET_SECONDS;
if (selectedBtn != BTN_NOT_SELECTED) printTime();
break;
}
case SET_SECONDS: {
if (selectedBtn == BTN_PLUS) secondsCounter = (secondsCounter < 5990) ? secondsCounter + 10 : 0;
if (selectedBtn == BTN_MINUS) secondsCounter = (secondsCounter > 0) ? (secondsCounter - 10) : 5990;
if (selectedBtn == BTN_SET) PROGRAM_STATE = WORK;
if (selectedBtn != BTN_NOT_SELECTED) printTime();
break;
}
case WORK: {
if (selectedBtn == BTN_SET || selectedBtn == BTN_MINUS) setStartState();
if (selectedBtn == BTN_PLUS) PROGRAM_STATE = PAUSE;
break;
}
case PAUSE: {
pointsOn = true;
if (selectedBtn == BTN_PLUS) PROGRAM_STATE = WORK;
if (selectedBtn == BTN_SET || selectedBtn == BTN_MINUS) setStartState();
break;
}
case WORK_END: {
if (isTimerTick && signalCounter > 0) signalCounter -= 1;
byte isSignalDisable = signalCounter == 0 || selectedBtn != BTN_NOT_SELECTED;
if (isSignalDisable) {
setStartState();
signalCounter = SIGNAL_TIME;
}
changePin(PIN_SIGNAL, isSignalDisable ? LOW : HIGH);
break;
}
default: {
setStartState();
break;
}
}
if (isTimerTick) {
if (PROGRAM_STATE == WORK) {
if (secondsCounter == 0) PROGRAM_STATE = WORK_END;
else secondsCounter -= 1;
pointsOn = !pointsOn;
printTime();
}
isTimerTick = false;
}
_delay_ms(150);
}
}
void printTime()
{
byte dm = secondsCounter / 60 / 10; // данные
byte em = (secondsCounter / 60) % 10;
byte ds = (secondsCounter % 60) / 10;
byte es = (secondsCounter % 60) % 10;
//************ поразрядно выводим данные на индикатор *******************
I2CStart();
I2CWrite(0x34 << 1); //выставляем номер разряда индикатора
I2CWrite(simv[dm]); //пишем в него символ
I2CStop();
I2CStart();
I2CWrite(0x35 << 1);
I2CWrite(simv[em]+pointsOn*128);
I2CStop();
I2CStart();
I2CWrite(0x36 << 1);
I2CWrite(simv[ds]);
I2CStop();
I2CStart();
I2CWrite(0x37 << 1);
I2CWrite(simv[es]);
I2CStop ();
}
/*
Режимы работы
Режим ввода минут - режим по умолчанию В этом режиме кнопки plus и minus задают число.
После нажатия кнопки set таймер переключается на выбор секунд.
Режим ввода секунд - секунды выбираются так же как и минуты. Если нажать на set, таймер запускается.
Режим работы - таймер отсчитывает время. В этом режиме кнопки set и минус сбрасывают таймер в режим
ввода минут. Кнопка plus включает паузу.
Режим паузы. Нажатие на plus продолжит работу таймера, set и минус сбрасывают таймер.
Режим окончания работы. На пине PB4 включается спикер (можно заменить на светодиод) на 5 секунд.
Нажатие любой из кнопок отключает спикер и таймер возвращается в режим ввода минут.
Использование
Кнопками plus и minus задайте минуты. Нажмите на set и введите секунды. Еще раз нажмите на set чтобы
запустить таймер.
*/