На этой странице буду публиковать некоторые полезные алгоритмы и примеры для ваших проектов, которые накопились у меня за годы разработки собственных. Статья обновляется по мере моей ленивости, так что иногда заходите, проверяйте =)
НЕСКОЛЬКО ТРЮКОВ
- Автоформатирование – Arduino IDE умеет автоматически приводить ваш код в порядок (имеются в виду отступы, переносы строк и пробелы). Для автоматического форматирования используйте комбинацию CTRL+T на клавиатуре, либо Инструменты/АвтоФорматирование в окне IDE. Используйте чаще, чтобы сделать код красивым (каноничным, классическим) и более читаемым для других!
- Скрытие частей кода – сворачивайте длинные функции и прочие куски кода для экономии места и времени на скроллинг. Включается здесь: Файл/Настройки/Включить сворачивание кода
Не используйте мышку! Чем выше становится ваш навык в программировании, тем меньше вы будете использовать мышку (да-да, как в фильмах про хакеров). Используйте обе руки для написания кода и перемещения по нему, вот вам несколько полезных комбинаций и хаков, которыми я пользуюсь ПОСТОЯННО:
- Ctrl+← , Ctrl+→ – переместить курсор влево/вправо НА ОДНО СЛОВО
- Home , End – переместить курсор в начало/конец строки
- Shift+← , Shift+→ – выделить символ слева/справа от курсора
- Shift+Ctrl+← , Shift+Ctrl+→ – выделить слово слева/справа от курсора
- Shift+Home , Shift+End – выделить все символы от текущего положения курсора до начала/конца строки
- Ctrl+Z – отменить последнее действие
- Ctrl+Y – повторить отменённое действие
- Ctrl+C – копировать выделенный текст
- Ctrl+X – вырезать выделенный текст
- Ctrl+V – вставить текст из буфера обмена
Местные сочетания:
- Ctrl+U – загрузить прошивку в Arduino
- Ctrl+R – скомпилировать (проверить)
- Ctrl+Shift+M – открыть монитор порта
Также для отодвигания комментариев в правую часть кода используйте TAB, а не ПРОБЕЛ. Нажатие TAB перемещает курсор по некоторой таблице, из-за чего ваши комментарии будут установлены красиво на одном расстоянии за вдвое меньшее количество нажатий!
Питание от пинов – во время разработки прототипов без брэдборда всегда не хватает пинов для питания датчиков и модулей. Так вот, слабые (с потреблением тока менее 40 мА) 5 Вольтовые датчики можно питать от любых пинов! Достаточно сформировать пин как выход, и подать на него нужный сигнал (HIGH – 5 Вольт, LOW – GND).
Пример: подключаем трёхпиновый датчик звука, не используя пины 5V и GND
#define SENSOR_VCC 2 // пин VCC сенсора на D2 #define SENSOR_GND 3 // пин GND сенсора на D3 #define SENSOR_OUT 4 // сигнальный пин на D4 void setup() { // настройка пинов pinMode(SENSOR_VCC, OUTPUT); pinMode(SENSOR_GND, OUTPUT); // подаём сигналы digitalWrite(SENSOR_VCC, HIGH); digitalWrite(SENSOR_GND, LOW); } void loop() { // в качестве примера считываем сигнал boolean sound_signal = digitalRead(SENSOR_OUT); }
Питание от штекера для программатора. Вы наверняка задавались вопросом, а зачем на Arduino NANO на краю платы расположены 6 пинов? Это порт для подключения ISP программатора. Что он делает в списке лайфхаков? Вот вам фото распиновки, используйте!
- Использовать библиотеку энергосбережения GyverPower, есть подробный урок
- В паре с библиотекой сделать несколько модификаций: отключить светодиод питания и отрезать левую ногу регулятора напряжения. ВНИМАНИЕ! Резать ногу регулятору можно только в том случае, если плата питается от источника 3-5 Вольт в пины 5V и GND.
Arduino Pro Mini бывает двух типов: с кварцем на 16 МГц и 8 МГц. Китайцы обычно не подписывают плату, и есть риск перепутать разные платы, если у вас есть и те и те. На средних по цене Pro Mini стоит качественный полноразмерный кварц в овальном металлическом корпусе, на нём крупно написана цифра, обозначающая частоту в Мгц:
На недорогих платах стоит крошечный дешёвый кварц в SMD корпусе, вот он:
Берём лупу и смотрим: 16 МГц кварц маркируется примерно как “A1” or “A’N”, 8 МГц кварц маркируется “80’0” или что-то в этом стиле. Ну вот, теперь вы не перепутаете свои Pro Mini!
РАБОТА С ПЕРИФЕРИЕЙ (ДЛЯ ATMEGA328)
Или облегчённые и ускоренные куски ядра Arduino и не только
void pinModeFast(uint8_t pin, uint8_t mode) { switch (mode) { case INPUT: if (pin < 8) { bitClear(DDRD, pin); bitClear(PORTD, pin); } else if (pin < 14) { bitClear(DDRB, (pin - 8)); bitClear(PORTB, (pin - 8)); } else if (pin < 20) { bitClear(DDRC, (pin - 14)); // Mode: INPUT bitClear(PORTC, (pin - 14)); // State: LOW } return; case OUTPUT: if (pin < 8) { bitSet(DDRD, pin); bitClear(PORTD, pin); } else if (pin < 14) { bitSet(DDRB, (pin - 8)); bitClear(PORTB, (pin - 8)); } else if (pin < 20) { bitSet(DDRC, (pin - 14)); // Mode: OUTPUT bitClear(PORTC, (pin - 14)); // State: LOW } return; case INPUT_PULLUP: if (pin < 8) { bitClear(DDRD, pin); bitSet(PORTD, pin); } else if (pin < 14) { bitClear(DDRB, (pin - 8)); bitSet(PORTB, (pin - 8)); } else if (pin < 20) { bitClear(DDRC, (pin - 14)); // Mode: OUTPUT bitSet(PORTC, (pin - 14)); // State: HIGH } return; } }
void digitalWriteFast(uint8_t pin, bool x) { // раскомментируй, чтобы отключать таймер
/*switch (pin) { case 3: bitClear(TCCR2A, COM2B1); break; case 5: bitClear(TCCR0A, COM0B1); break; case 6: bitClear(TCCR0A, COM0A1); break; case 9: bitClear(TCCR1A, COM1A1); break; case 10: bitClear(TCCR1A, COM1B1); break; case 11: bitClear(TCCR2A, COM2A1); // PWM disable break; }*/ if (pin < 8) { bitWrite(PORTD, pin, x); } else if (pin < 14) { bitWrite(PORTB, (pin - 8), x); } else if (pin < 20) { bitWrite(PORTC, (pin - 14), x); // Set pin to HIGH / LOW } }
// быстро инвертирует состояние пина void digitalToggleFast(uint8_t pin) { if (pin < 8) { bitSet(PIND, pin); } else if (pin < 14) { bitSet(PINB, (pin - 8)); } else if (pin < 20) { bitSet(PINC, (pin - 14)); // Toggle pin state (for 'tone()') } }
bool digitalReadFast(uint8_t pin) { if (pin < 8) { return bitRead(PIND, pin); } else if (pin < 14) { return bitRead(PINB, pin - 8); } else if (pin < 20) { return bitRead(PINC, pin - 14); // Return pin state } }
void analogWriteFast(uint8_t pin, uint16_t duty) { if (!duty) { // If duty = 0 digitalWrite(pin, LOW); // Disable PWM and set pin to LOW return; // Skip next code } switch (pin) { case 5: bitSet(TCCR0A, COM0B1); // Enable hardware timer output OCR0B = duty; // Load duty to compare register return; case 6: bitSet(TCCR0A, COM0A1); OCR0A = duty; return; case 10: bitSet(TCCR1A, COM1B1); OCR1B = duty; return; case 9: bitSet(TCCR1A, COM1A1); OCR1A = duty; return; case 3: bitSet(TCCR2A, COM2B1); OCR2B = duty; return; case 11: bitSet(TCCR2A, COM2A1); OCR2A = duty; return; } }
// ВНИМАНИЕ! Нужное опорное установлено DEFAULT, можно изменить на своё uint16_t analogReadFast(uint8_t pin) { pin = ((pin < 8) ? pin : pin - 14); // analogRead(2) = analogRead(A2) ADMUX = (DEFAULT<< 6) | pin; // Set analog MUX & reference bitSet(ADCSRA, ADSC); // Start while (ADCSRA & (1 << ADSC)); // Wait return ADC; // Return result }
// установка делителя АЦП. Доступны 2,4,8,16,32,64,128 void analogPrescaler(uint8_t prescaler) { switch (prescaler) { case 2: ADCSRA = (ADCSRA & 0xF8) | 0x01; break; case 4: ADCSRA = (ADCSRA & 0xF8) | 0x02; break; case 8: ADCSRA = (ADCSRA & 0xF8) | 0x03; break; case 16: ADCSRA = (ADCSRA & 0xF8) | 0x04; break; case 32: ADCSRA = (ADCSRA & 0xF8) | 0x05; break; case 64: ADCSRA = (ADCSRA & 0xF8) | 0x06; break; case 128: ADCSRA = (ADCSRA & 0xF8) | 0x07; break; } }
// пример использования PCINT - прерывания на любом пине // прерывание вызывается при переключении состояния любого пина из группы // наши обработчики прерываний ISR(PCINT0_vect) { // пины 8-13 } ISR(PCINT1_vect) { // пины A0-A5 } ISR(PCINT2_vect) { // пины 0-7 } // функция для настройки PCINT uint8_t attachPCINT(uint8_t pin) { if (pin < 8) { // D0-D7 (PCINT2) PCICR |= (1 << PCIE2); PCMSK2 |= (1 << pin); return 2; } else if (pin > 13) { //A0-A5 (PCINT1) PCICR |= (1 << PCIE1); PCMSK1 |= (1 << pin - 14); return 1; } else { // D8-D13 (PCINT0) PCICR |= (1 << PCIE0); PCMSK0 |= (1 << pin - 8); return 0; } } // быстрый digitalRead для опроса внутри ISR // пригодится для проверки конкретного пина bool pinRead(uint8_t pin) { if (pin < 8) { return bitRead(PIND, pin); } else if (pin < 14) { return bitRead(PINB, pin - 8); } else if (pin < 20) { return bitRead(PINC, pin - 14); } }
// прицепляем аппаратные прерывания напрямую (пин, тип) void attachInterruptFast(uint8_t num, uint8_t type) { switch (num) { case 0: EICRA = (EICRA & 0x0C) | type; // Setup interrupt type bitSet(EIMSK, INT0); // Enable external interrupt return; case 1: EICRA = (EICRA & 0x03) | (type << 2); bitSet(EIMSK, INT1); return; } } void detachInterruptFast(uint8_t num) { bitClear(EIMSK, num); // Disable external interrupt } // векторы. В них будет прыгать прерывание ISR(INT0_vect) { } ISR(INT1_vect) { }
// пример работы с юартом // UART_begin(бод) - запустить // UART_write(byte) - отправить байт // UART_available() - проверка на входящий // UART_read() - прочитать байт // UART_end() - выключить void setup() { UART_begin(9600); UART_write(40); // отправить байт 40 UART_write(40); } void loop() { if (UART_available()) { // если есть что на приём byte data = UART_read(); // прочитать UART_write(data); // отправить обратно } } // собственно функции void UART_begin(uint32_t baudrate) { uint16_t speed = (F_CPU / (8L * baudrate)) - 1; UBRR0H = highByte(speed); UBRR0L = lowByte(speed); UCSR0A = (1 << U2X0); UCSR0B = (1 << TXEN0) | (1 << RXEN0); UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); } void UART_write(byte data) { while (!(UCSR0A & (1 << UDRE0))); UDR0 = data; } bool UART_available() { return (UCSR0A & (1 << RXC0)); } byte UART_read() { byte data = UDR0; return data; } void UART_end() { UCSR0B = 0; }
// используем минимальный набор для Serial // стандартный читаемый вывод делает встроенный в ядро Print.h // ==== класс ==== #include "Print.h" class Uart : public Print { public: void begin(uint32_t baudrate) { uint16_t speed = (F_CPU / (8L * baudrate)) - 1; UBRR0 = speed; UCSR0A = (1 << U2X0); UCSR0B = (1 << TXEN0) | (1 << RXEN0); UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); } void end() { UCSR0B = 0; } size_t write(uint8_t data) { while (!(UCSR0A & (1 << UDRE0))); UDR0 = data; } bool available() { return (UCSR0A & (1 << RXC0)); } char read() { byte data = UDR0; return data; } private: }; // ==== конец класса ==== Uart uart; void setup() { uart.begin(9600); uart.println("Hello "); uart.println(123456); // также есть // uart.end(); // uart.write(); // uart.available(); // uart.read(); } void loop() { }
// ВНИМАНИЕ! Это просто сон, без отключения АЦП и прочих жрущих блоков // для полноценного комфортного сна используйте GyverPower void setup() { pinMode(2, 2); // внешняя подтяжка лучше длоя энергосбережения! Serial.begin(9600); Serial.println("hello!"); delay(500); Serial.println("go to sleep"); delay(500); attachInterrupt(0, wakeUp, LOW); // вкл прерывание пробуждения goToSleep(); // отправка в сон delay(1000); // после сна Serial.println("im back in business"); // продолжили работу } void wakeUp() { detachInterrupt(0); // откл прерывание пробуждения SMCR &= ~ (1 << SE); // запретили сон Serial.println("five more minutes,pls"); // сказали что проснулись } void goToSleep() { SMCR |= (1 << SM1); // настроили сон как powerDown SMCR |= (1 << SE); // разрешили сон asm volatile ("sleep"); // инструкция сна } void loop() { }
// полные аналоги стандартным функциям времени // пригодится, если работать без Arduino.h // доступные функции // необходимо вызвать uptime0Init() при запуске, чтобы всё завелось void uptime0Init(); unsigned long millis0(); unsigned long micros0(); void delay0(unsigned long ms); void delayMicroseconds0(unsigned int us); // ==================== РЕАЛИЗАЦИЯ ================== #define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256)) #define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000) #define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3) #define FRACT_MAX (1000 >> 3) #define MICROS_MULT (64 / clockCyclesPerMicrosecond()) volatile unsigned long timer0_overflow_count = 0; volatile unsigned long timer0_millis = 0; static unsigned char timer0_fract = 0; void uptime0Init() { sei(); TCCR0A = (1 << WGM01) | (1 << WGM00); TCCR0B = (1 << CS01) | (1 << CS00); TIMSK0 |= (1 << TOIE0); } ISR(TIMER0_OVF_vect) { timer0_millis += MILLIS_INC; timer0_fract += FRACT_INC; if (timer0_fract >= FRACT_MAX) { timer0_fract -= FRACT_MAX; timer0_millis++; } timer0_overflow_count++; } unsigned long millis0() { uint8_t oldSREG = SREG; // запомнинаем были ли включены прерывания cli(); // выключаем прерывания unsigned long m = timer0_millis; // перехватить значение SREG = oldSREG; // если прерывания не были включены - не включаем и наоборот return m; // вернуть миллисекунды } unsigned long micros0() { uint8_t oldSREG = SREG; // запомнинаем были ли включены прерывания cli(); // выключаем прерывания unsigned long m = timer0_overflow_count; // счет переполнений uint8_t t = TCNT0; // считать содержимое счетного регистра if ((TIFR0 & _BV(TOV0)) && (t < 255)) // инкремент по переполнению m++; SREG = oldSREG; // если прерывания не были включены - не включаем и наоборот return (long)(((m << 8) + t) * MICROS_MULT); // вернуть микросекунды } void delay0(unsigned long ms) { uint32_t start = micros0(); while (ms > 0) { // ведем отсчет while ( ms > 0 && (micros0() - start) >= 1000) { ms--; start += 1000; } } } void delayMicroseconds0(unsigned int us) { #if F_CPU >= 24000000L us *= 6; // x6 us, = 7 cycles us -= 5; //=2 cycles #elif F_CPU >= 20000000L __asm__ __volatile__ ( "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop"); //just waiting 4 cycles if (us <= 1) return; // = 3 cycles, (4 when true) us = (us << 2) + us; // x5 us, = 7 cycles us -= 7; // 2 cycles #elif F_CPU >= 16000000L if (us <= 1) return; // = 3 cycles, (4 when true) us <<= 2; // x4 us, = 4 cycles us -= 5; // = 2 cycles, #elif F_CPU >= 12000000L if (us <= 1) return; // = 3 cycles, (4 when true) us = (us << 1) + us; // x3 us, = 5 cycles us -= 5; //2 cycles #elif F_CPU >= 8000000L if (us <= 2) return; // = 3 cycles, (4 when true) us <<= 1; //x2 us, = 2 cycles us -= 4; // = 2 cycles #else if (us <= 16) return; //= 3 cycles, (4 when true) if (us <= 25) return; //= 3 cycles, (4 when true), (must be at least 25 if we want to substract 22) us -= 22; // = 2 cycles us >>= 2; // us div 4, = 4 cycles #endif __asm__ __volatile__ ( "1: sbiw %0,1" "\n\t" // 2 cycles "brne 1b" : "=w" (us) : "0" (us) // 2 cycles ); }
// полные аналоги стандартным функциям времени // пригодится, если работать без Arduino.h // доступные функции // необходимо вызвать uptime2Init() при запуске, чтобы всё завелось void uptime2Init(); unsigned long millis2(); unsigned long micros2(); void delay2(unsigned long ms); void delayMicroseconds2(unsigned int us); // =================== РЕАЛИЗАЦИЯ ================== #define MICROSECONDS_PER_TIMER2_OVERFLOW (clockCyclesToMicroseconds(64 * 256)) #define MILLIS2_INC (MICROSECONDS_PER_TIMER2_OVERFLOW / 1000) #define FRACT2_INC ((MICROSECONDS_PER_TIMER2_OVERFLOW % 1000) >> 3) #define FRACT2_MAX (1000 >> 3) #define MICROS2_MULT (64 / clockCyclesPerMicrosecond()) volatile unsigned long timer2_overflow_count = 0; volatile unsigned long timer2_millis = 0; static unsigned char timer2_fract = 0; void uptime2Init() { sei(); TCCR2A = (1 << WGM20) | (1 << WGM21); TCCR2B = 1 << CS22; TIMSK2 = 1 << TOIE2; } ISR(TIMER2_OVF_vect) { timer2_millis += MILLIS2_INC; timer2_fract += FRACT2_INC; if (timer2_fract >= FRACT2_MAX) { timer2_fract -= FRACT2_MAX; timer2_millis++; } timer2_overflow_count++; } unsigned long millis2() { uint8_t oldSREG = SREG; // запомнинаем были ли включены прерывания cli(); // выключаем прерывания unsigned long m = timer2_millis; // перехватить значение SREG = oldSREG; // если прерывания не были включены - не включаем и наоборот return m; // вернуть миллисекунды } unsigned long micros2() { uint8_t oldSREG = SREG; // запомнинаем были ли включены прерывания cli(); // выключаем прерывания unsigned long m = timer2_overflow_count; // счет переполнений uint8_t t = TCNT2; // считать содержимое счетного регистра if ((TIFR2 & _BV(TOV2)) && (t < 255)) // инкремент по переполнению m++; SREG = oldSREG; // если прерывания не были включены - не включаем и наоборот return (long)(((m << 8) + t) * MICROS2_MULT); // вернуть микросекунды } void delay2(unsigned long ms) { uint32_t start = micros2(); while (ms > 0) { // ведем отсчет while ( ms > 0 && (micros2() - start) >= 1000) { ms--; start += 1000; } } } void delayMicroseconds2(unsigned int us) { #if F_CPU >= 24000000L us *= 6; // x6 us, = 7 cycles us -= 5; //=2 cycles #elif F_CPU >= 20000000L __asm__ __volatile__ ( "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop"); //just waiting 4 cycles if (us <= 1) return; // = 3 cycles, (4 when true) us = (us << 2) + us; // x5 us, = 7 cycles us -= 7; // 2 cycles #elif F_CPU >= 16000000L if (us <= 1) return; // = 3 cycles, (4 when true) us <<= 2; // x4 us, = 4 cycles us -= 5; // = 2 cycles, #elif F_CPU >= 12000000L if (us <= 1) return; // = 3 cycles, (4 when true) us = (us << 1) + us; // x3 us, = 5 cycles us -= 5; //2 cycles #elif F_CPU >= 8000000L if (us <= 2) return; // = 3 cycles, (4 when true) us <<= 1; //x2 us, = 2 cycles us -= 4; // = 2 cycles #else if (us <= 16) return; //= 3 cycles, (4 when true) if (us <= 25) return; //= 3 cycles, (4 when true), (must be at least 25 if we want to substract 22) us -= 22; // = 2 cycles us >>= 2; // us div 4, = 4 cycles #endif __asm__ __volatile__ ( "1: sbiw %0,1" "\n\t" // 2 cycles "brne 1b" : "=w" (us) : "0" (us) // 2 cycles ); }
// пример чтения установленных фьюз-байтов #define LOW_FUSE (0x0000) #define LOCK (0x0001) #define EXTENDED_FUSE (0x0002) #define HIGH_FUSE (0x0003) void setup() { Serial.begin(9600); Serial.print("Low: 0x"); Serial.println(fuse_get(LOW_FUSE_BYTE), HEX); Serial.print("High: 0x"); Serial.println(fuse_get(HIGH_FUSE_BYTE), HEX); Serial.print("Extended: 0x"); Serial.println(fuse_get(EXTENDED_FUSE_BYTE), HEX); Serial.print("Lock: 0x"); Serial.println(fuse_get(LOCK_BYTE), HEX); } void loop() { } uint8_t fuse_get(uint16_t address) { uint8_t data; asm volatile ( "sts %[spmreg], %[spmcfg] \n\t" "lpm %[data], Z \n\t" : [data] "=r" (data) : [spmreg]"i" (_SFR_MEM_ADDR(SPMCSR)), [spmcfg] "r" ((1 << SPMEN) |(1 << BLBSET)), "z" (address) ); return data; }
СТРУКТУРА ПРОГРАММЫ
/* пример "чистого" и удобного для работы цикла loop() работать так гораздо удобнее, и труднее запутаться Пример: 1: получение показаний с датчика, фильтрация 2: отработка нажатий кнопок 3: отрисовка на дисплей 4: отправка команд на управляющие устройства и так далее */ void setup() { } void loop() { task_1(); task_2(); task_3(); task_4(); // ... } void task_1() { // какие-то действия, ведущие к одной цели } void task_2() { // какие-то действия, ведущие к одной цели } void task_3() { // какие-то действия, ведущие к одной цели } void task_4() { // какие-то действия, ведущие к одной цели }
/* Данный код демонстрирует переключение режимов работы при помощи кнопки Для удобства используется библиотека отработки нажатий кнопки */ #define PIN 3 // кнопка подключена сюда (PIN --- КНОПКА --- GND) #define MODE_AM 5 // количество режимов (от 0 до указанного) #include "GyverButton.h" // моя библиотека для более удобной работы с кнопкой // скачать мождно здесь https://github.com/AlexGyver/GyverLibs GButton butt1(PIN); // создаём нашу "кнопку" byte mode = 0; // переменная режима void setup() { Serial.begin(9600); } void loop() { butt1.tick(); // обязательная функция отработки. Должна постоянно опрашиваться if (butt1.isPress()) { // правильная отработка нажатия с защитой от дребезга // увеличиваем переменную номера режима. Если вышла за количество режимов - обнуляем if (++mode >= MODE_AM) mode = 0; } // всё переключение в итоге сводится к оператору switch switch (mode) { case 0: task_0(); break; case 1: task_1(); break; case 2: task_2(); break; case 3: task_3(); break; case 4: task_4(); break; } } // наши задачи, внутри функций понятное дело может быть всё что угодно void task_0() { Serial.println("Task 0"); } void task_1() { Serial.println("Task 1"); } void task_2() { Serial.println("Task 2"); } void task_3() { Serial.println("Task 3"); } void task_4() { Serial.println("Task 4"); }
/* Данный код демонстрирует переключение режимов работы при помощи кнопки Для удобства используется библиотека отработки нажатий кнопки В этом варианте примера функции "режимов" вызываются только один раз */ #define PIN 3 // кнопка подключена сюда (PIN --- КНОПКА --- GND) #define MODE_AM 5 // количество режимов (от 0 до указанного) #include "GyverButton.h" // моя библиотека для более удобной работы с кнопкой // скачать мождно здесь https://github.com/AlexGyver/GyverLibs GButton butt1(PIN); // создаём нашу "кнопку" byte mode = 0; // переменная режима void setup() { Serial.begin(9600); } void loop() { butt1.tick(); // обязательная функция отработки. Должна постоянно опрашиваться if (butt1.isPress()) { // правильная отработка нажатия с защитой от дребезга // увеличиваем переменную номера режма. Если вышла за количество режимов - обнуляем if (++mode >= MODE_AM) mode = 0; // всё переключение в итоге сводится к оператору switch // переключение и вызов происходит только при нажатии!!! switch (mode) { case 0: task_0(); break; case 1: task_1(); break; case 2: task_2(); break; case 3: task_3(); break; case 4: task_4(); break; } } } // наши задачи, внутри функций понятное дело может быть всё что угодно void task_0() { Serial.println("Task 0"); } void task_1() { Serial.println("Task 1"); } void task_2() { Serial.println("Task 2"); } void task_3() { Serial.println("Task 3"); } void task_4() { Serial.println("Task 4"); }
#define MODE_AMOUNT 5 byte mode = 0; void nextMode() { mode++; // увеличиваем переменную номера режима if (mode >= MODE_AMOUNT) mode = 0; // закольцовываем } // Время выполнения 0.5 мксЕсть ещё парочка интересных вариантов. Результат не отличается, но сам механизм знать будет полезно:
// второй вариант. Время выполнения 0.5 мкс if (++mode >= MODE_AMOUNT) mode = 0; // тут инкремент внесён в условие, получаем более короткую запись // третий вариант. Время выполнения 5.5 мкс. НЕ ИСПОЛЬЗУЙТЕ ЕГО! mode = ++mode % MODE_AMOUNT; // очень интересный вариант, без использования условия! Работает остаток от деления
for (int i = 0; i < 30; i++) { // например, зажигаем i-ый светодиод delay(100); }Как переписать такой цикл, чтобы он не блокировал выполнение кода? Очень просто: нужно избавиться и от цикла, и от delay. Введём таймер на millis(), и будем работать по нему:
int counter = 0; // замена i uint32_t timer = 0; // переменная таймера #define T_PERIOD 100 // период переключения void loop() { if (millis() - timer >= T_PERIOD) { // таймер на millis() timer = millis(); // сброс // действие с counter - наш i-ый светодиод например counter++; // прибавляем счётчик if (counter > 30) counter = 0; // закольцовываем изменение } }Вот собственно и всё! Вместо переменной цикла i у нас теперь свой глобальный счётчик counter, который бегает от 0 до 30 (в этом примере) с периодом 100 мс.
ПОЛЕЗНЫЕ МАКРОСЫ
#define FOR_i(from, to) for(int i = (from); i < (to); i++)Макрос создаст цикл for от from до to и счётчиком i внутри. Пример использования:
// выведет числа от 0 до 9 FOR_i(0, 10) { Serial.println(i); }Нужны вложенные циклы? Можно сделать макрос с выбором имени переменной
#define FOR(x, from, to) for (int (x) = (from); (x) < (to); (x)++)И пример с ним:
// работа с двумерным массивом FOR(i, 0, 10) { FOR(j, 0, 3) { someArray[i][j] = someValue; } }
//=========================== #define EVERY_MS(x) \ static uint32_t tmr;\ bool flag = millis() - tmr >= (x);\ if (flag) tmr += (x);\ if (flag) //===========================Данный макрос заменяет «таймер на миллис» одной строчкой, без использования библиотек и создания классов! Пользоваться очень просто:
EVERY_MS(100) { // ... // данный код будет выполняться каждые 100 мс }Единственное ограничение: нельзя вызывать макрос больше одного раза в одном и том же блоке кода, это приведёт к ошибке =) То есть вот так нельзя:
void loop() { EVERY_MS(100) { // ваш код } EVERY_MS(500) { // ваш код } }Если очень нужна такая конструкция — помещаем каждый вызов в свой блок кода:
void loop() { { EVERY_MS(100) { // ваш код } } { EVERY_MS(500) { // ваш код } } }Либо используем блоки кода по условиям или как функцию:
void loop() { if (someCondition) { EVERY_MS(100) { // ваш код } } myAction(); } void myAction() { EVERY_MS(500) { // ваш код } }У — удобно!
#ifdef DEBUG_ENABLE #define DEBUG(x) Serial.println(x) #else #define DEBUG(x) #endifЕсли DEBUG_ENABLE задефайнен — все вызовы DEBUG() в коде будут заменены на вывод в порт. Если не задефайнен — они будут заменены НИЧЕМ, то есть просто «вырежутся» из кода! Также по DEBUG_ENABLE можно запустить сериал и получить полный контроль над отладкой: если она не нужна — убрали DEBUG_ENABLE и из кода убрался запуск порта и все выводы, что резко сокращает объём занимаемой памяти:
// раздефайнить или задефайнить для использования //#define DEBUG_ENABLE #ifdef DEBUG_ENABLE #define DEBUG(x) Serial.println(x) #else #define DEBUG(x) #endif void setup() { #ifdef DEBUG_ENABLE Serial.begin(9600); #endif } void loop() { DEBUG("kek"); delay(100); }
for(;;);Можно обернуть в более понятный макрос:
#define FOREVER for(;;)Теперь в коде на строке FOREVER; код «зависнет»:
// ...... if (criticalError) FOREVER; // ......
ВРЕМЯ, ТАЙМЕРЫ
// Данный код выполняет действия периодически за указанный период // Нам нужно задать период таймера В МИЛЛИСЕКУНДАХ // дней*(24 часов в сутках)*(60 минут в часе)*(60 секунд в минуте)*(1000 миллисекунд в секунде) // (long) обязательно для больших чисел, иначе не посчитает // можно посчитать на калькуляторе, но какбэ ардуино и есть калькулятор, пусть считает... unsigned long period_time = (long)5*24*60*60*1000; // переменная таймера, максимально большой целочисленный тип (он же uint32_t) unsigned long my_timer; void setup() { my_timer = millis(); // "сбросить" таймер } void loop() { if ((long)millis() - my_timer > period_time) { my_timer = millis(); // "сбросить" таймер // набор функций, который хотим выполнить один раз за период // бла бла бла // ... } }
// Данный код выполняет действие с периодом PERIOD на время WORK_TIME, эдакий свернизкочастотный ШИМ // Банально автополив // Нам нужно задать период таймера В МИЛЛИСЕКУНДАХ // дней*(24 часов в сутках)*(60 минут в часе)*(60 секунд в минуте)*(1000 миллисекунд в секунде) // (long) обязательно для больших чисел, иначе не посчитает // можно посчитать на калькуляторе, но какбэ ардуино и есть калькулятор, пусть считает... unsigned long period_time = (long)5*24*60*60*1000; unsigned long work_time = 10000; // время, на которое ну скажем включится лампочка #define TIMER_START 0 // 1 - отсчёт периода с момента ВЫКЛЮЧЕНИЯ лампочки, 0 - с ВКЛЮЧЕНИЯ // переменная таймера, максимально большой целочисленный тип (он же uint32_t) unsigned long period_timer, work_timer; boolean work_flag; void setup() { period_timer = millis(); // "сбросить" таймер } void loop() { if ((long)millis() - period_timer > period_time) { period_timer = millis(); // "сбросить" таймер периода work_timer = millis(); // сбросить таймер выполнения work_flag = true; // начали выполнение // включить лампу, помпу, реле, что угодно // банально digitalWrite(пин, HIGH) } if ( ((long)millis() - work_timer > work_time) && work_flag) { work_flag = false; // сброс флага на выполнение // можно сбросить таймер периода ПОСЛЕ выполнения задачи. Подумайте над этим! if (TIMER_START) period_timer = millis(); // выключить лампу, помпу, реле, что угодно // банально digitalWrite(пин, LOW) } if (work_flag) { // а вот этот блок кода выполняется всегда, пока мы находимся по времени "внутри" WORK_TIME } }
uint32_t tmr; bool flag; #define period1 10*1000L #define period2 60*60*1000L void setup() { } void loop() { if (millis() - tmr >= (flag ? period1 : period2)) { tmr = millis(); flag = !flag; // тут можно сделать digitalWrite(pin, flag); // для переключения реле } }
/* Делаем "параллельное" выполнение нескольких задач с разным периодом выполнения */ #define PERIOD_1 100 // период первой задачи #define PERIOD_2 2000 // период второй задачи #define PERIOD_3 666 // ... unsigned long timer_1, timer_2, timer_3; void setup() { } void loop() { if (millis() - timer_1 > PERIOD_1) { // условие таймера timer_1 = millis(); // сброс таймера // выполняем блок №1 каждые PERIOD_1 миллисекунд } if (millis() - timer_2 > PERIOD_2) { timer_2 = millis(); // выполняем блок №2 каждые PERIOD_2 миллисекунд } if (millis() - timer_3 > PERIOD_3) { timer_3 = millis(); // выполняем блок №3 каждые PERIOD_3 миллисекунд } }
/* Пример параллельного выполнения нескольких задач по таймеру. Библиотеку GyverTimer можно скачать здесь https://github.com/AlexGyver/GyverLibs */ #include "GyverTimer.h" // создать миллисекундный таймер, в скобках период в миллисекундах GTimer myTimer1(MS, 500); GTimer myTimer2(MS, 600); GTimer myTimer3(MS, 1000); void setup() { Serial.begin(9600); } void loop() { if (myTimer1.isReady()) Serial.println("Timer 1!"); if (myTimer2.isReady()) Serial.println("Timer 2!"); if (myTimer3.isReady()) Serial.println("Timer 3!"); }
//==== MILLISTIMER MACRO ==== #define EVERY_MS(x) \ static uint32_t tmr;\ bool flag = millis() - tmr >= (x);\ if (flag) tmr += (x);\ if (flag) //===========================Данный макрос заменяет «таймер на миллис» одной строчкой, без использования библиотек и создания классов! Пользоваться очень просто: добавьте указанный выше макрос в самое начало кода и вызывайте его как функцию
EVERY_MS(100) { // ... // данный код будет выполняться каждые 100 мс }Единственное ограничение: нельзя вызывать макрос больше одного раза в одном и том же блоке кода, это приведёт к ошибке =) То есть вот так нельзя:
void loop() { EVERY_MS(100) { // ваш код } EVERY_MS(500) { // ваш код } }Если очень нужна такая конструкция — помещаем каждый вызов в свой блок кода:
void loop() { { EVERY_MS(100) { // ваш код } } { EVERY_MS(500) { // ваш код } } }Либо используем блоки кода по условиям или как отдельную функцию, которая «оборачивает» макрос:
//=========================== #define EVERY_MS(x) \ static uint32_t tmr;\ bool flag = millis() - tmr >= (x);\ if (flag) tmr = millis();\ if (flag) //=========================== void setup() {} void loop() { myAction1(); myAction2(); } void myAction1() { EVERY_MS(1000) { // ваш код } } void myAction2() { EVERY_MS(500) { // ваш код } }
uint32_t now = millis(); while (millis () - now < 5000) { // тут в течение 5000 миллисекунд вертится код // удобно использовать для всяких калибровок }
#define PERIOD_1 2000 void loop() { if ( (millis() % PERIOD_1) == 0) { delay(1); // ваше действие } }Чем он хорош и чем плох? Хорош тем, что не нужна отдельная переменная типа uint32_t, а также данный таймер не сбивается и без проблем проходит через переполнение millis(). Минусы весьма существенные: операция % выполняется очень долго, также внутри таймера нужен delay(1), иначе таймер может сработать несколько раз в течение одной миллисекунды (пока миллис кратен периоду). Не используйте этот таймер, но знайте, что такой есть.
#define PERIOD 500 uint32_t timer = 0; void loop() { if (millis() - timer >= PERIOD) { // ваше действие do { timer += PERIOD; if (timer < PERIOD) break; // переполнение uint32_t } while (timer < millis() - PERIOD); // защита от пропуска шага } }Данный таймер имеет механику классического таймера с хранением переменной таймера, а его период всегда кратен PERIOD и не сбивается. Эту конструкцию можно упростить до
#define PERIOD 500 uint32_t timer = 0; void loop() { if (millis() - timer >= PERIOD) { // ваше действие timer += PERIOD; } }В этом случае алгоритм получается короче, кратность периодов сохраняется, но теряется защита от пропуска вызова и переполнения millis(). Мои библиотеки GyverTimer и timerMinim были обновлены до этого алгоритма, можете работать с ними.
// получаем из миллиса часы, минуты и секунды работы программы (часы не ограничены, т.е. аптайм) uint32_t sec = millis() / 1000ul; int timeHours = (sec / 3600ul); int timeMins = (sec % 3600ul) / 60ul; int timeSecs = (sec % 3600ul) % 60ul;
// получаем из миллиса часы, минуты и секунды работы программы. Часы ограничиваем до 23, т.е. режим часов uint32_t sec = millis() / 1000ul; int timeHours = (sec / 3600ul) % 24; int timeMins = (sec % 3600ul) / 60ul; int timeSecs = (sec % 3600ul) % 60ul;
// Полезные функции для получения номера дня по дате (день по счёту с 01.01.2000) // Можно использовать в качестве "миллиса" при подключенном RTC // Вторая функция получает дату из дня (день по счёту с 01.01.2000) void setup() { Serial.begin(9600); // смотрим каким днём (по счёту с 01.01.2000) будет 20 июня 2066 года Serial.println(daySince2000(20, 6, 2066)); // временные переменные для работы dayToDate() byte day; byte month; int year; // смотрим, в какую дату попадает день 24278 (по счёту с 01.01.2000) // данная функция запишет результат в указанные переменные! dayToDate(24278, day, month, year); Serial.print(day); Serial.print('.'); Serial.print(month); Serial.print('.'); Serial.println(year); } void loop() { } // ============== САМИ ФУНКЦИИ ============ // возвращает количество дней с 01.01.2000 (день 1-30/31, месяц 1-12, год 2000-...) int daySince2000(byte day, byte month, int year) { int days = day-1; // + день текущего месяца for (int i = 0; i < month - 1; i++) days += (i<7)?((i==1)?28:((i&1)?30:31)):((i&1)?31:30); if (month > 2 && (year & 3) == 0) days++; // + високосный days += (year - 2000) * 365; // + предыдущие года days += (year - 2000 + 3) / 4; // + предыдущие високосные года return days; } // записывает дату дня с номером day2000 в переменные по ссылкам void dayToDate(int day2000, byte &day, byte &month, int &year) { day2000++; int countDays = day2000; year = 0; while (countDays > 0) { year++; countDays -= 365; if ((year & 3) == 0) countDays--; } year--; day2000 -= year * 365; day2000 -= (year + 3) / 4; int days = 0; for (int i = 0; i < 12; i++) { int daysm = (i<7)?((i==1)?((year&3)==0?29:28):((i&1)?30:31)):((i&1)?31:30); if (day2000 <= days + daysm) { month = i + 1; day = day2000 - days; break; } days += daysm; } }
/* Простейший фильтр: запаздывающий, бегущее среднее, "цифровой фильтр", фильтр низких частот - это всё про него любимого Имеет две настройки: постоянную времени FILTER_STEP (миллисекунды), и коэффициент "плавности" FILTER_COEF Данный фильтр абсолютно универсален, подходит для сглаживания любого потока данных При маленьком значении FILTER_COEF фильтрованное значение будет меняться очень медленно вслед за реальным Чем больше FILTER_STEP, тем меньше частота опроса фильтра Сгладит любую "гребёнку", шум, ложные срабатывания, резкие вспышки и прочее говно. Пользуюсь им постоянно */ #define FILTER_STEP 5 #define FILTER_COEF 0.05 int val; float val_f = 0.0; unsigned long filter_timer; void setup() { Serial.begin(9600); } void loop() { if (millis() - filter_timer > FILTER_STEP) { filter_timer = millis(); // просто таймер // читаем значение (не обязательно с аналога, это может быть ЛЮБОЙ датчик) val = analogRead(0); // основной алгоритм фильтрации. Внимательно прокрутите его в голове, чтобы понять, как он работает val_f = val * FILTER_COEF + val_f * (1 - FILTER_COEF); // для примера выведем в порт Serial.println(val_f); } }
/* "Удобный" фильтр бегущее среднее (низких частот) Библиотеку GyverHacks можно скачать здесь https://github.com/AlexGyver/GyverLibs */ #include "GyverHacks.h" GFilterRA analog0; // фильтр назовём analog0 void setup() { Serial.begin(9600); // установка коэффициента фильтрации (0.0... 1.0). Чем меньше, тем плавнее фильтр analog0.setCoef(0.01); // установка шага фильтрации (мс). Чем меньше, тем резче фильтр analog0.setStep(10); } void loop() { Serial.println(analog0.filteredTime(analogRead(0))); }
// filtered_val - фильтрованное значение // val - новое значение (с датчика) // k - коэффициент фильтрации 0.. 1. Обычно около 0.01-0.1 (то бишь float) filtered_val = filtered_val * (1 - k) + val * k;Но если раскрыть скобки и «причесать» выражение, получится очень красивая короткая запись. Время выполнения одной операции фильтрации составляет 35 мкс.
filtered_val += (val - filtered_val) * k;
/* Элементарная реализация среднего арифметического. Сложили NUM_READINGS измерений, затем разделили сумму на NUM_READINGS и всё! Является "частным случаем" предыдущего фильтра Время выполнения примерно равно: 10 значений 50 мкс, 50 значений 92 мкс, 100 значений 146 мкс */ #define NUM_READINGS 500 int average; void setup() { Serial.begin(9600); } void loop() { long sum = 0; // локальная переменная sum for (int i = 0; i < NUM_READINGS; i++) { // согласно количеству усреднений sum += analogRead(0); // суммируем значения с любого датчика в переменную sum } average = sum / NUM_READINGS; // находим среднее арифметическое, разделив сумму на число измерений Serial.println(average); // для примера выводим в порт }
/* Готовая функция для вычисления среднего арифметического Принимает новые значения, суммирует их в своём массиве */ #define NUM_AVER 10 // выборка (из скольки усредняем) int middleArifm(int newVal) { // принимает новое значение static byte idx = 0; // индекс static int valArray[NUM_AVER]; // массив valArray[idx] = newVal; // пишем каждый раз в новую ячейку if (++idx >= NUM_AVER) idx = 0; // перезаписывая самое старое значение long average = 0; // обнуляем среднее for (int i = 0; i < NUM_AVER; i++) { average += valArray[i]; // суммируем } average /= NUM_AVER; // делим return average; // возвращаем }
/* Готовая функция для вычисления среднего арифметического Принимает новые значения, суммирует их в своём массиве */ // оптимизированный вариант без суммирования массива при каждом вызове // значения хранятся и отнимаются из переменной суммы #define NUM_AVER 10 // выборка (из скольки усредняем) int aver(int val) { static int t = 0; static int vals[NUM_AVER]; static int average = 0; if (++t >= NUM_AVER) t = 0; // перемотка t average -= vals[t]; // вычитаем старое average += val; // прибавляем новое vals[t] = val; // запоминаем в массив return (average / NUM_AVER); }
/* Медианный фильтр — довольно простая и интересная штука. Берёт значения и выбирает из них среднее. Не усредняет, а именно ВЫБИРАЕТ, отбрасывает все сильно отличющиеся. Время выполнения близко к нулю мкс Простой пример, чем отличается медианный фильтр от среднего арифметического: Возьмём числа 3, 4, 50. Среднее арифметическое даст нам 19. Целью медианного фильтра является фильтрация резких скачков, и после фильтрации он даст нам 4, как среднее между 3 и 50, а 50 будет отброшено как скачок. В данном скетче реализована фильтрация по трём значениям. Если интересен вариант с фильтрацией более трёх значений, то добро пожаловать в исходную статью. Осторожно, жесть. http://tqfp.org/programming/mediannyy-filtr-na-sluzhbe-razrabotchika.html */ int val[3]; int val_filter; byte index; void setup() { Serial.begin(9600); } void loop() { if (++index > 2) index = 0; // переключаем индекс с 0 до 2 (0, 1, 2, 0, 1, 2…) val[index] = analogRead(0); // записываем значение с датчика в массив // фильтровать медианным фильтром из 3ёх ПОСЛЕДНИХ измерений val_filter = middle_of_3(val[0], val[1], val[2]); Serial.println(val_filter); // для примера выводим в порт } // медианный фильтр из 3ёх значений float middle_of_3(float a, float b, float c) { int middle; if ((a <= b) && (a <= c)) { middle = (b <= c) ? b : c; } else { if ((b <= a) && (b <= c)) { middle = (a <= c) ? a : c; } else { middle = (a <= b) ? a : b; } } return middle; }
ЧИСЛА, МАТЕМАТИКА
void setup() { Serial.begin(9600); long data = 1234567; // число, которое нужно разбить int8_t bytes[10]; // буфер byte amount; // количество цифр в числе for (byte i = 0; i < 10; i++) { //> bytes[i] = data % 10; // записываем остаток в буфер data /= 10; // "сдвигаем" число if (data == 0) { // если число закончилось amount = i; // запомнили, сколько знаков break; } } // массив bytes хранит цифры числа data в обратном порядке! for (int8_t i = amount; i >= 0; i--) { //> Serial.println(bytes[i]); // выводим } } void loop() {}
void setup() { Serial.begin(9600); byte digits[] = {1, 2, 3, 4, 5, 6, 7}; long number = 0; for (byte i = 0; i < sizeof(digits) / sizeof(digits[0]); i++) { number += digits[i]; // пишем следующую цифру number *= 10; // "сдвигаем" число } number /= 10; // убираем лишнее умножение на 10 Serial.println(number); }
3/4==0
или 5/4==1
. Иногда бывает нужно получить округление вверх, для этого можно использовать функцию ceil()
, но она работает с float
и является очень громоздким решением. Целочисленная операция деления числа a
на число b
с округлением вверх делается вот так: (a + b - 1) / b
.int val = 1234, val2; byte b1, b2; // разбиваем val на байты b1 = val >> 8; // старший байт b2 = val & 0xFF; // младший байт val2 = b2 | (b1 << 8); // склеиваем обратно // тут val2 == 1234
Функция принимает HEX число в виде текста в формате ABC123
, 0xABC123
, #ABC123
с буквами в верхнем и нижнем регистре (abc123
, 0xabc123
, #abc123
) и преобразовывает в целочисленный uint32_t
. Версии для String и char*:
uint32_t StringHEX(String hex) { uint8_t s = 0; if (hex[0] == '#') s = 1; if (hex[1] == 'x') s = 2; uint8_t len = hex.length() - s; uint32_t val = 0; for (int i = 0; i < len; i++) { val <<= 4; uint8_t d = hex[i + s]; d -= (d <= '9') ? 48 : ((d <= 'F') ? 55 : 87); val |= d; } return val; }
uint32_t cstringHEX(char* hex) { uint8_t s = 0; if (hex[0] == '#') s = 1; if (hex[1] == 'x') s = 2; uint8_t len = strlen(hex) - s; uint32_t val = 0; for (int i = 0; i < len; i++) { val <<= 4; uint8_t d = hex[i + s]; d -= (d <= '9') ? 48 : ((d <= 'F') ? 55 : 87); val |= d; } return val; }
СТРОКИ
const char _qwerty_ru[] PROGMEM = "F<DULT:PBQRKVYJGHCNEA{WXIO}SM\">Zf,dult;pbqrkvyjghcnea[wxio]sm'.z~`"; String ru_to_qw(const String& ru) { String qw; uint8_t prev = 0; for (int i = 0; i < ru.length(); i++) { uint8_t cur = ru[i]; if (cur > 127) { uint8_t thiscur = cur; if (cur > 191) cur = 0; else if (prev == 209 && cur == 145) cur = 193; // ё else if (prev == 208 && cur == 129) cur = 192; // Ё prev = thiscur; } if (!cur) continue; if (cur <= 127) { qw += (char)cur; continue; } else if (cur <= 143) cur -= 80; else if (cur <= 191) cur -= 144; else cur -= 128; qw += (char)pgm_read_byte(&_qwerty_ru[cur]); } return qw; }
Использовать так:
Serial.println(ru_to_qw("123abcПривет")); // 123abcGhbdtn
const char _qwerty_ru[] PROGMEM = "F<DULT:PBQRKVYJGHCNEA{WXIO}SM\">Zf,dult;pbqrkvyjghcnea[wxio]sm'.z~`"; uint16_t ru_to_qw(const char* ru, char* qw) { uint16_t len = strlen(ru); uint16_t idx = 0; uint8_t prev = 0; for (int i = 0; i < len; i++) { uint8_t cur = ru[i]; if (cur > 127) { uint8_t thiscur = cur; if (cur > 191) cur = 0; else if (prev == 209 && cur == 145) cur = 193; // ё else if (prev == 208 && cur == 129) cur = 192; // Ё prev = thiscur; } if (!cur) continue; if (cur <= 127) { qw[idx++] = (char)cur; continue; } else if (cur <= 143) cur -= 80; else if (cur <= 191) cur -= 144; else cur -= 128; qw[idx++] = (char)pgm_read_byte(&_qwerty_ru[cur]); } qw[idx] = 0; return idx; }Использовать так:
char* ru = "Привет"; char qw[strlen(ru)]; ru_to_qw(ru, qw); Serial.println(qw); // Ghbdtn
int strlen_ru(const char* data) { int i = 0; int count = 0; while (data[i]) { if ((data[i] & 0xc0) != 0x80) count++; i++; } return count; }
String u_encode(uint32_t c) { char b1 = 0, b2 = 0, b3 = 0, b4 = 0; if (c < 0x80) { b1 = c & 0x7F | 0x00; } else if (c < 0x0800) { b1 = c >> 6 & 0x1F | 0xC0; b2 = c >> 0 & 0x3F | 0x80; } else if (c < 0x010000) { b1 = c >> 12 & 0x0F | 0xE0; b2 = c >> 6 & 0x3F | 0x80; b3 = c >> 0 & 0x3F | 0x80; } else if (c < 0x110000) { b1 = c >> 18 & 0x07 | 0xF0; b2 = c >> 12 & 0x3F | 0x80; b3 = c >> 6 & 0x3F | 0x80; b4 = c >> 0 & 0x3F | 0x80; } String s; s.reserve(4); s += b1; s += b2; s += b3; s += b4; return s; }Использовать так:
Serial.println(u_encode(0x2605)); // ★
wchar_t str[] = {'П', 'р', 'и', 'в', 'е', 'т'}; char* p = (char*)str; for (int i = 0; i < sizeof(str); i += 2) { Serial.print(p[i + 1]); Serial.print(p[i]); }
МАССИВЫ, БУФЕРЫ
void setup() { Serial.begin(9600); byte bytes[] = {0, 1, 2, 3, 4, 5, 6, 7}; // выводим в порт for (byte i = 0; i < 8; i++) { Serial.print(bytes[i]); Serial.print(' '); } Serial.println(); // копируем массив в буфер byte buf[8]; for (byte i = 0; i < 8; i++) { buf[i] = bytes[i]; } // переписываем наоборот for (byte i = 0; i < 8; i++) { bytes[i] = buf[7 - i]; } // выводим для проверки for (byte i = 0; i < 8; i++) { Serial.print(bytes[i]); Serial.print(' '); } }
// objAmount - размер массива for (int i = 0; i < objAmount-1; i++) { for (int j = i+1; j < objAmount; j++) { // сравниваем [i] и [j] } }
#define ARRAY_SIZE 5 byte bytes[ARRAY_SIZE]; void setup() { Serial.begin(9600); // 7 раз "задвинем" в массив случайное число // и выведем в порт for (byte i = 0; i < 7; i++) { updateArray(random(0, 100)); printArray(); } /* Вывод: 0 0 0 0 7 0 0 0 7 49 0 0 7 49 73 0 7 49 73 58 7 49 73 58 30 49 73 58 30 72 73 58 30 72 44 */ } void updateArray(int newVal) { for (byte i = 0; i < ARRAY_SIZE - 1; i++) { // сдвигаем члены влево bytes[i] = bytes[i + 1]; } // пишем новое значение в последний элемент bytes[ARRAY_SIZE - 1] = newVal; } void printArray() { // выводим в порт for (byte i = 0; i < ARRAY_SIZE; i++) { Serial.print(bytes[i]); Serial.print('\t'); } Serial.println(); } void loop() {}
#define ARRAY_SIZE 5 byte bytes[ARRAY_SIZE]; byte arrayCounter = 0; // номер ячейки void setup() { Serial.begin(9600); // 7 раз "задвинем" в массив случайное число // и выведем в порт for (byte i = 0; i < 7; i++) { //> updateArray(random(0, 100)); printArray(); } /* Вывод: 7 0 0 0 7 49 0 0 0 7 49 73 0 0 7 49 73 58 0 7 49 73 58 30 72 49 73 58 30 72 44 73 58 30 */ } void updateArray(int newVal) { // пишем новое значение в элемент номер arrayCounter bytes[arrayCounter] = newVal; arrayCounter++; // прибавляем // и зацикливаем if (arrayCounter > ARRAY_SIZE - 1) arrayCounter = 0; } void printArray() { // выводим в порт for (byte i = 0; i < ARRAY_SIZE; i++) { Serial.print(bytes[i]); Serial.print('\t'); } Serial.println(); } void loop() {}
// пример кольцевого буфера для хранения набора данных #define buffer_SIZE 32 // размер буфера int buffer[buffer_SIZE]; // сам буфер (массив) uint8_t buffer_head; // "голова" буфера uint8_t buffer_tail; // "хвост" буфера void setup() {} void loop() {} // запись в буфер void bufferWrite(int newVal) { // положение нового значения в буфере uint8_t i = (buffer_head + 1) % buffer_SIZE; // если есть местечко if (i != buffer_tail) { buffer[buffer_head] = newVal; // пишем в буфер buffer_head = i; // двигаем голову } } // чтение из буфера int bufferRead() { if (buffer_head == buffer_tail) return -1; // буфер пуст int thisVal = buffer[buffer_tail]; // берём с хвоста buffer_tail = (buffer_tail + 1) % buffer_SIZE; // хвост двигаем return thisVal; // возвращаем значение } // возвращает крайнее значение без удаления из буфера // если буфер пуст, вернёт -1 int bufferPeek() { return buffer_head != buffer_tail ? buffer[buffer_tail] : -1; } // вернёт количество непрочитанных элементов // если буфер пуст, вернёт -1 int bufferAmount() { return ((unsigned int)(buffer_SIZE + buffer_head - buffer_tail)) % buffer_SIZE; } // "очистка" буфера void bufferClear() { buffer_head = buffer_tail = 0; }
// пример кольцевого буфера для хранения набора данных #define buffer_SIZE 32 // размер буфера int buffer[buffer_SIZE]; // сам буфер (массив) uint8_t buffer_head; // "голова" буфера uint8_t buffer_tail; // "хвост" буфера void setup() {} void loop() {} // запись в буфер void bufferWrite(int newVal) { // положение нового значения в буфере uint8_t i = (buffer_head + 1 >= buffer_SIZE) ? 0 : buffer_head + 1; // если есть местечко if (i != buffer_tail) { buffer[buffer_head] = newVal; // пишем в буфер buffer_head = i; // двигаем голову } } // чтение из буфера int bufferRead() { if (buffer_head == buffer_tail) return -1; // буфер пуст int thisVal = buffer[buffer_tail]; // берём с хвоста if (++buffer_tail >= buffer_SIZE) buffer_tail = 0; // хвост двигаем return thisVal; // возвращаем значение } // возвращает крайнее значение без удаления из буфера // если буфер пуст, вернёт -1 int bufferPeek() { return (buffer_head != buffer_tail) ? buffer[buffer_tail] : -1; } // вернёт количество непрочитанных элементов // если буфер пуст, вернёт -1 int bufferAmount() { return ((unsigned int)(buffer_SIZE + buffer_head - buffer_tail)) % buffer_SIZE; } // "очистка" буфера void bufferClear() { buffer_head = buffer_tail = 0; }
void quickSort(int arr[], int low, int high) { if (low < high) { int pivot = arr[high]; int idx = low; for (int i = low; i <= high; i++) { if (arr[i] <= pivot) { int buf = arr[idx]; arr[idx] = arr[i]; arr[i] = buf; idx++; } } idx--; quickSort(arr, low, idx - 1); quickSort(arr, idx + 1, high); } }Первоначальный вызов:
int arr[] = {53, 2, 654, 32, 34, 6843, 84, 358}; quickSort(arr, 0, sizeof(arr)/sizeof(arr[0]) - 1); // quickSort(массив, 0, длина - 1)
ПЕРЕДАЧА ПАРАМЕТРОВ
int myArray[] = {100, 30, 890, 645, 251}; void setup() { getSecond(myArray); // результат 30 } int getSecond(int *intArray) { // возвращает второй элемент массива return intArray[1]; } void loop() { }
int myArray[] = {100, 30, 890, 645, 251}; void setup() { getSecond(myArray); // результат 30 } int getSecond(int intArray[]) { // возвращает второй элемент массива return intArray[1]; } void loop() { }
int myArray[] = {100, 30, 890, 645, 251}; void setup() { uart.begin(); uart.println(getSecond(&myArray)); // результат 30 } int getSecond(const void * intArray) { const int * Array = (const int *) intArray; // возвращает второй элемент массива return Array[1]; } void loop() { }
struct myStruct { byte lol = 210; int kek = 2019; float cheburek = 0.1; } testStruct; void setup() { Serial.begin(9600); Serial.println(getSecond(&testStruct)); // результат 2019 } int getSecond(const void * tempStruct) { const myStruct * thisStruct = (const myStruct *) tempStruct; // возвращает второй элемент структуры return thisStruct->kek; } void loop() { }
struct myStruct { byte lol = 210; int kek = 2019; float cheburek = 0.1; } testStruct; void setup() { Serial.begin(9600); Serial.println(getSecond(testStruct)); // результат 2019 } int getSecond(myStruct tempStruct) { // возвращает второй элемент структуры return tempStruct.kek; } void loop() { }
struct myStruct { byte lol = 210; int kek = 2019; float cheburek = 0.1; } testStruct; void setup() { Serial.begin(9600); Serial.println(getSecond(testStruct)); // результат 2019 } template‹class T› int getSecond(T& tempStruct) { // возвращает второй элемент структуры return tempStruct.kek; } void loop() { }
struct myStruct { byte lol = 210; int kek = 2019; float cheburek = 0.1; } testStruct; void setup() { Serial.begin(9600); Serial.println(getSecond(&testStruct)); // результат 2019 } template‹class T› int getSecond(T* tempStruct) { // возвращает второй элемент структуры return tempStruct->kek; } void loop() { }
int c; int myArray[] = {100, 30, 890, 645, 251, 645, 821, 325}; int newArray[8]; struct kekstruct { byte ass = 10; int lol = 15000; float tazz = 3.14; } kek; void setup() { // передаём сам массив и его размер в БАЙТАХ c = sumFunction(&myArray, sizeof(myArray)); rewriteFunction(&myArray, &newArray, sizeof(myArray)); uartBegin(); uartPrint(c); for (byte i = 0; i ‹ sizeof(newArray) / sizeof(int); i++) { uartPrintln(newArray[i]); } uartPrintln(); kekstruct kek2; //rewriteFunction(&kek, &kek2, sizeof(kek)); rewrite(kek, kek2); uartPrintln(kek2.ass); uartPrintln(kek2.lol); uartPrintln(kek2.tazz); } void loop() { } // суммирует массив int sumFunction(void* intArray, int arrSize) { uint8_t* current = reinterpret_cast‹uint8_t*›(intArray); // переменная для суммирования int sum = 0; // находим размер массива, разделив его вес // на вес одного элемента (тут у нас int) /*arrSize = arrSize / sizeof(int); */ for (byte i = 0; i ‹ arrSize; i++) { sum += *current; current++; } return sum; } // переписывает что угодно через reinterpret_cast void rewriteFunction(const void* curArray, void* tarArray, int arrSize) { uint8_t* target = reinterpret_cast‹uint8_t*›(tarArray); const uint8_t* current = reinterpret_cast‹const uint8_t*›(curArray); while (arrSize--) { *target++ = *current++; } }
ЦВЕТ, СВЕТОДИОДЫ
/* Данный код позволяет получить 1023 оттенка цвета с RGB светодиода одним потенциомтером алгоритм цвета 1: синий максимум, плавно прибавляется зелёный зелёный максимум, плавно убавляется синий зелёный максимум, плавно прибавляется красный красный максимум, плавно убавляется зелёный */ // пины подключения. Обратите внимание, это ШИМ пины #define R_PIN 3 #define G_PIN 5 #define B_PIN 6 byte bright = 100; // яркость, от 0 до 100 (можно повесить на второй потенциометр при желании) byte R, G, B; void setup() { // настраиваем как выходы pinMode(R_PIN, OUTPUT); pinMode(G_PIN, OUTPUT); pinMode(B_PIN, OUTPUT); } void loop() { int colorPot = analogRead(0); // получаем значение с потенциометра (0 - 1023) // разбиваем диапазон 0 - 1023 на 4 участка, и играемся с цветом согласно текущему значению if (colorPot <= 250) { //> byte k = map(colorPot, 0, 250, 0, 255); R = 0; G = k; B = 255; } else if (colorPot > 250 && colorPot <= 500) { //> byte k = map(colorPot, 250, 500, 0, 255); R = 0; G = 255; B = 255 - k; } else if (colorPot > 500 && colorPot <= 750) { //> byte k = map(colorPot, 500, 750, 0, 255); R = k; G = 255; B = 0; } else if (colorPot > 750 && colorPot <= 1023) { //> byte k = map(colorPot, 750, 1023, 0, 255); R = 255; G = 255 - k; B = 0; } // подаём ШИМ на светодиод, учитывая яркость analogWrite(R_PIN, (bright * R / 100)); analogWrite(G_PIN, (bright * G / 100)); analogWrite(B_PIN, (bright * B / 100)); }
/* Данный код позволяет получить 1023 оттенка цвета с RGB светодиода одним потенциомтером алгоритм цвета 2 синий убавляется, зелёный прибавляется зелёный убавляется, красный прибавляется */ // пины подключения. Обратите внимание, это ШИМ пины #define R_PIN 3 #define G_PIN 5 #define B_PIN 6 byte bright = 100; // яркость, от 0 до 100 (можно повесить на второй потенциометр при желании) byte R, G, B; void setup() { // настраиваем как выходы pinMode(R_PIN, OUTPUT); pinMode(G_PIN, OUTPUT); pinMode(B_PIN, OUTPUT); } void loop() { int colorPot = analogRead(0); // получаем значение с потенциометра (0 - 1023) // разбиваем диапазон 0 - 1023 на 2 участка, и играемся с цветом согласно текущему значению if (colorPot <= 500) { //> byte k = map(colorPot, 0, 500, 0, 255); R = 0; G = k; B = 255 - k; } else if (colorPot > 500) { //> byte k = map(colorPot, 500, 1000, 0, 255); R = k; G = 255 - k; B = 0; } // подаём ШИМ на светодиод, учитывая яркость analogWrite(R_PIN, (bright * R / 100)); analogWrite(G_PIN, (bright * G / 100)); analogWrite(B_PIN, (bright * B / 100)); }
/* Данный код позволяет получить 1023 оттенка цвета с RGB светодиода одним потенциомтером алгоритм цвета 3 - радужный красный в зелёный через жёлтый зелёный в синий через бирюзовый синий в краный через фиолетовый */ // пины подключения. Обратите внимание, это ШИМ пины #define R_PIN 3 #define G_PIN 5 #define B_PIN 6 byte bright = 100; // яркость, от 0 до 100 (можно повесить на второй потенциометр при желании) byte R, G, B; void setup() { // настраиваем как выходы pinMode(R_PIN, OUTPUT); pinMode(G_PIN, OUTPUT); pinMode(B_PIN, OUTPUT); } void loop() { int colorPot = analogRead(0); // получаем значение с потенциометра (0 - 1023) // разбиваем диапазон 0 - 1023 на 2 участка, и играемся с цветом согласно текущему значению if (colorPot <= 340) { //> byte k = map(colorPot, 0, 340, 0, 255); R = 255 - k; G = k; B = 0; } else if (colorPot > 340 && colorPot <= 680) { //> byte k = map(colorPot, 340, 680, 0, 255); R = 0; G = 255 - k; B = k; } else if (colorPot > 680) { //> byte k = map(colorPot, 680, 1023, 0, 255); R = k; G = 0; B = 255 - k; } // подаём ШИМ на светодиод, учитывая яркость analogWrite(R_PIN, (bright * R / 100)); analogWrite(G_PIN, (bright * G / 100)); analogWrite(B_PIN, (bright * B / 100)); }
// например цвет в HEX long val = 0x12ff34, val2; byte r, g, b; // разбиваем val на байты по цветам RRGGBB r = (val >> 16) & 0xFF; g = (val >> 8) & 0xFF; b = val & 0xFF; // склеиваем обратно в long val2 = ((long)r << 16) | ((long)g << 8) | b; // тут val2 == 0x12ff34
// функция переводит цвет из HSV в RGB (опционально сразу запускает ШИМ) void HSVtoRGB(uint8_t h, uint8_t s, uint8_t v) { float r, g, b; float H = (float)h / 255; float S = (float)s / 255; float V = (float)v / 255; int i = int(H * 6); float f = H * 6 - i; float p = V * (1 - S); float q = V * (1 - f * S); float t = V * (1 - (1 - f) * S); switch(i % 6){ case 0: r = V, g = t, b = p; break; case 1: r = q, g = V, b = p; break; case 2: r = p, g = V, b = t; break; case 3: r = p, g = q, b = V; break; case 4: r = t, g = p, b = V; break; case 5: r = V, g = p, b = q; break; } // финальные значения для R G B 8 бит byte R = r * 255; byte G = g * 255; byte B = b * 255; // тут можно отправлять на ШИМ analogWrite(3, R); analogWrite(5, G); analogWrite(6, B); }
// функция getBrightCRT принимает обычную и возвращает скорректированную // по CRT гамме яркость (8 бит) для управления яркостью светодиодов // CRT таблица const uint8_t CRTgammaPGM[256] PROGMEM = { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22, 22, 23, 23, 24, 25, 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 32, 33, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40, 41, 42, 43, 43, 44, 45, 46, 47, 48, 49, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 91, 93, 94, 95, 96, 98, 99, 100, 101, 103, 104, 105, 107, 108, 109, 110, 112, 113, 115, 116, 117, 119, 120, 121, 123, 124, 126, 127, 129, 130, 131, 133, 134, 136, 137, 139, 140, 142, 143, 145, 146, 148, 149, 151, 153, 154, 156, 157, 159, 161, 162, 164, 165, 167, 169, 170, 172, 174, 175, 177, 179, 180, 182, 184, 186, 187, 189, 191, 193, 194, 196, 198, 200, 202, 203, 205, 207, 209, 211, 213, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, }; byte getBrightCRT(byte val) { return pgm_read_byte(&(CRTgammaPGM[val])); }
// то же самое, что выше, но в виде полинома // выполняется медленнее таблицы, но занимает меньше Flash byte getBrightCRT(byte val) { return ((float)0.0044 * val * val - 0.1589 * val + 2.6983); }
((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | ((b & 0b11111000) >> 3)
// здесь color565 - int в кодировке цвета RGB565 byte r = (color565 & 0b1111100000000000) >> 8; byte g = (color565 & 0b0000011111100000) >> 3; byte b = (color565 & 0b0000000000011111) << 3; // если нужен uint32_t - можно склеить uint32_t color888 = ((long)r << 16) | ((long)g << 8) | b;
int HSV565(uint8_t h, uint8_t s, uint8_t v) { float r, g, b; float H = (float)h / 255; float S = (float)s / 255; float V = (float)v / 255; int i = int(H * 6); float f = H * 6 - i; float p = V * (1 - S); float q = V * (1 - f * S); float t = V * (1 - (1 - f) * S); switch (i % 6) { case 0: r = V, g = t, b = p; break; case 1: r = q, g = V, b = p; break; case 2: r = p, g = V, b = t; break; case 3: r = p, g = q, b = V; break; case 4: r = t, g = p, b = V; break; case 5: r = V, g = p, b = q; break; } // финальные значения для R G B 8 бит byte R = r * 255; byte G = g * 255; byte B = b * 255; return ((R & 0b11111000) << 8) | ((G & 0b11111100) << 3) | ((B & 0b11111000) >> 3); }
КНОПКИ
/* Пример использования библиотеки GyverButton, все возможности в одном скетче. - Опрос кнопки с программным антидребезгом контактов - Отработка нажатия, удерживания отпускания кнопки - Отработка одиночного, двойного и тройного нажатия (вынесено отдельно) - Отработка любого количества нажатий кнопки (функция возвращает число нажатий) - Отработка нажатия и удержания кнопки - Настраиваемый таймаут повторного нажатия/удержания - Функция изменения значения переменной с заданным шагом и заданным интервалом по времени */ #define PIN 3 // кнопка подключена сюда (PIN --- КНОПКА --- GND) #include "GyverButton.h" GButton butt1(PIN); int value = 0; void setup() { Serial.begin(9600); butt1.setDebounce(50); // настройка антидребезга (по умолчанию 80 мс) butt1.setTimeout(300); // настройка таймаута на удержание (по умолчанию 500 мс) butt1.setIncrStep(2); // настройка инкремента, может быть отрицательным (по умолчанию 1) butt1.setIncrTimeout(500); // настрйока интервала инкремента (по умолчанию 800 мс) } void loop() { butt1.tick(); // обязательная функция отработки. Должна постоянно опрашиваться if (butt1.isSingle()) Serial.println("Single"); // проверка на один клик if (butt1.isDouble()) Serial.println("Double"); // проверка на двойной клик if (butt1.isTriple()) Serial.println("Triple"); // проверка на тройной клик if (butt1.hasClicks()) // проверка на наличие нажатий Serial.println(butt1.getClicks()); // получить (и вывести) число нажатий if (butt1.isPress()) Serial.println("Press"); // нажатие на кнопку (+ дебаунс) if (butt1.isRelease()) Serial.println("Release"); // отпускание кнопки (+ дебаунс) if (butt1.isHolded()) Serial.println("Holded"); // проверка на удержание //if (butt1.isHold()) Serial.println("Hold"); // возвращает состояние кнопки if (butt1.isIncr()) { // если кнопка была удержана (это для инкремента) value = butt1.getIncr(value); // увеличивать/уменьшать переменную value с шагом и интервалом Serial.println(value); // для примера выведем в порт } }
#define BTN 3 // кнопка подключена сюда (PIN --- КНОПКА --- GND) boolean btnState, btnFlag; void setup() { Serial.begin(9600); pinMode(BTN, INPUT_PULLUP); } void loop() { btnState = !digitalRead(BTN); // читаем состояние кнопки с инверсией. 1 - нажата, 0 - нет if (btnState && !btnFlag) { // если нажата и была отпущена (btnFlag 0) btnFlag = true; // запомнили что нажата Serial.println("press"); } if (!btnState && btnFlag) { // если отпущена и была нажата (btnFlag 1) btnFlag = false; // запомнили что отпущена Serial.println("release"); } }
#define BTN 3 // кнопка подключена сюда (PIN --- КНОПКА --- GND) #define DEBOUNCE 100 // таймаут антидребезга, миллисекунды boolean btnState, btnFlag; unsigned long debounceTimer; void setup() { Serial.begin(9600); pinMode(BTN, INPUT_PULLUP); } void loop() { btnState = !digitalRead(BTN); // читаем состояние кнопки с инверсией. 1 - нажата, 0 - нет // если нажата и была отпущена (btnFlag 0) и прошло не менее DEBOUNCE времени if (btnState && !btnFlag && (millis() - debounceTimer > DEBOUNCE)) { btnFlag = true; // запомнили что нажата debounceTimer = millis(); // запомнили время нажатия Serial.println("press"); } if (!btnState && btnFlag) { // если отпущена и была нажата (btnFlag 1) btnFlag = false; // запомнили что отпущена debounceTimer = millis(); // запомнили время отпускания Serial.println("release"); } }
АНАЛОГОВЫЕ ПИНЫ, НАПРЯЖЕНИЕ
/* Скетч для калибровки точного вольтметра и его использование КАЛИБРОВКА: 0) Ставим vol_calibration 1, прошиваем 1) Запускаем, открываем монитор. Будет выведено реальное значение Vсс в милливольтах из расчёта по стандартной константе 1.1 2) Измеряем вольтметром напряжение на пинах 5V и GND, полученное значение отправляем в порт В МИЛЛИВОЛЬТАХ (то есть если у нас 4.54В то отправляем 4540). Новая константа будет рассчитана автоматически и запишется во внутреннюю память 3) Ставим vol_calibration 0, прошиваем 4) Наслаждайтесь точными измерениями! ИСПОЛЬЗОВАНИЕ: 0) Функция readVCC возвращает ТОЧНОЕ опорное напряжение В МИЛЛИВОЛЬТАХ. В расчётах используем не 5 Вольт, а readVсс! 1) При использовании analogRead() для перевода в вольты пишем: float voltage = analogRead(pin) * (readVсс() / 1023.0); - это точный вольтаж В МИЛЛИВОЛЬТАХ */ #define vol_calibration 1 // калибровка вольтметра (если работа от АКБ) 1 - включить, 0 - выключить float my_vcc_const = 1.1; // начальное значение константы должно быть 1.1 #include "EEPROMex.h" // библиотека для работы со внутренней памятью ардуино void setup() { Serial.begin(9600); if (vol_calibration) calibration(); // калибровка, если разрешена my_vcc_const = EEPROM.readFloat(1000); // считать константу из памяти } void loop() { /* // отображение заряда в процентах по ёмкости! Интерполировано вручную по графику разряда ЛИТИЕВОГО аккумулятора int volts = readVcc(); int capacity; if (volts > 3870) capacity = map(volts, 4200, 3870, 100, 77); else if ((volts <= 3870) && (volts > 3750) ) capacity = map(volts, 3870, 3750, 77, 54); else if ((volts <= 3750) && (volts > 3680) ) capacity = map(volts, 3750, 3680, 54, 31); else if ((volts <= 3680) && (volts > 3400) ) capacity = map(volts, 3680, 3400, 31, 8); else if (volts <= 3400) capacity = map(volts, 3400, 2600, 8, 0); Serial.println(capacity); */ } void calibration() { my_vcc_const = 1.1; // начальаня константа калибровки Serial.print("Real VCC is: "); Serial.println(readVcc()); // общаемся с пользователем Serial.println("Write your VCC (in millivolts)"); while (Serial.available() == 0); int Vcc = Serial.parseInt(); // напряжение от пользователя float real_const = (float)1.1 * Vcc / readVcc(); // расчёт константы Serial.print("New voltage constant: "); Serial.println(real_const, 3); Serial.println("Set vol_calibration 0, flash and enjoy!"); EEPROM.writeFloat(1000, real_const); // запись в EEPROM while (1); // уйти в бесконечный цикл } // функция чтения внутреннего опорного напряжения, универсальная (для всех ардуин) long readVcc() { #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) ADMUX = _BV(MUX5) | _BV(MUX0); #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) ADMUX = _BV(MUX3) | _BV(MUX2); #else ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #endif delay(2); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Start conversion while (bit_is_set(ADCSRA, ADSC)); // measuring uint8_t low = ADCL; // must read ADCL first - it then locks ADCH uint8_t high = ADCH; // unlocks both long result = (high << 8) | low; result = my_vcc_const * 1023 * 1000 / result; // расчёт реального VCC return result; // возвращает VCC }
// отображение заряда в процентах по ёмкости! Интерполировано // вручную по графику разряда ЛИТИЕВОГО аккумулятора int volts = analogRead(0) * 5 * (float)0.977; // несовсем корректно, так как 5 вольт ровно не бывает. Смотри предыдущий пример int capacity; if (volts > 3870) capacity = map(volts, 4200, 3870, 100, 77); else if ((volts <= 3870) && (volts > 3750) ) capacity = map(volts, 3870, 3750, 77, 54); else if ((volts <= 3750) && (volts > 3680) ) capacity = map(volts, 3750, 3680, 54, 31); else if ((volts <= 3680) && (volts > 3400) ) capacity = map(volts, 3680, 3400, 31, 8); else if (volts <= 3400) capacity = map(volts, 3400, 2600, 8, 0); Serial.println(capacity);
РАЗНОЕ
#include "Wire.h" void setup() { Serial.begin(9600); } void loop() { for (uint8_t i = 1; i < 128; i++) { Wire.beginTransmission(i); if (!Wire.endTransmission()) { Serial.print("0x"); Serial.println(i, HEX); } } delay(1000); }
// GND --- термистор --- A0 --- 10к --- 5V #define THERM A0 // к какому аналоговому пину мы подключены #define RESIST_10K 10000 // точное сопротивление 10к резистора (Ом) void setup() { Serial.begin(9600); //analogReference(EXTERNAL); } void loop() { Serial.print("Temperature "); Serial.print(getThermTemp(analogRead(THERM))); Serial.println(" *C"); delay(1000); } // цифры взяты из даташита #define RESIST_BASE 10000 // сопротивление при TEMP_BASE градусах по Цельсию (Ом) #define TEMP_BASE 25 // температура, при которой измерено RESIST_BASE (градусов Цельсия) #define B_COEF 3435 // бета коэффициент термистора (3000-4000) float getThermTemp(int resistance) { float thermistor; thermistor = RESIST_10K / ((float)1023 / resistance - 1); thermistor /= RESIST_BASE; // (R/Ro) thermistor = log(thermistor) / B_COEF; // 1/B * ln(R/Ro) thermistor += (float)1.0 / (TEMP_BASE + 273.15); // + (1/To) thermistor = (float)1.0 / thermistor - 273.15; // инвертируем и конвертируем в градусы по Цельсию return thermistor; }
asm volatile ( "cli \n\t" "jmp 0x0000 \n\t" );
// Тест скорости выполнения команд Arduino // Просто помести свой код внутри test() и загрузи прошивку! inline __attribute__((always_inline)) void test(void) { asm ("nop"); } // ========================================= volatile uint16_t cnt_ovf = 0; void setup() { Serial.begin(9600); TCCR1A = TCCR1B = TCNT1 = cnt_ovf = 0; // Сброс таймера TIFR1 = (1 << TOV1); TIMSK1 = (1 << TOIE0); // Прерывание переполнения TCCR1B = (1 << CS10); // Старт таймера test(); // тест TCCR1B = 0; // остановить таймер uint32_t count = TCNT1 - 2; // минус два такта на действия count += ((uint32_t)cnt_ovf * 0xFFFF); // с учетом переполнений Serial.print("ticks: "); Serial.println(count); Serial.print("time (us): "); Serial.println(count * (float)(1000000.0f / F_CPU), 4); } ISR(TIMER1_OVF_vect) { cnt_ovf++; } void loop() { }