diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..ac13e2a --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,24 @@ +on: + workflow_dispatch: + push: + pull_request: + +permissions: + contents: read + pull-requests: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: install avr tool chain + run: | + apt update && apt install -y \ + avrdude \ + gcc-avr \ + gdb-avr \ + avr-libc \ + binutils-avr + - name: make + run: make all diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c15080 --- /dev/null +++ b/.gitignore @@ -0,0 +1,55 @@ +build/ +.vscode/ + +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..733ec55 --- /dev/null +++ b/Makefile @@ -0,0 +1,70 @@ +# AVR project Makefile + +# Microcontroller +MCU = atmega328p + +# F_CPU (in Hz) +F_CPU = 16000000UL + +# Programmer +PROGRAMMER = -c arduino -P /dev/ttyACM0 -b 115200 + +# Compiler +CC = avr-gcc +OBJCOPY = avr-objcopy +OBJDUMP = avr-objdump +SIZE = avr-size +AVRDUDE = avrdude + +# Compiler flags +CFLAGS = -g -Wall -Os -mmcu=$(MCU) -DF_CPU=$(F_CPU) -fexceptions + +# Source directory +SRC_DIR = src + +# Build directory +BUILD_DIR = build + +# Source files +SRCS = main.c $(wildcard $(SRC_DIR)/libs/**/*.c) + +# Object files +OBJS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRCS)) + +# Output files +TARGET = main +ELF = $(TARGET).elf +HEX = $(TARGET).hex +MAP = $(TARGET).map +EEP = $(TARGET).eep + +# Rules +all: $(HEX) + +$(HEX): $(ELF) + $(OBJCOPY) -O ihex -R .eeprom $(ELF) $(HEX) + +$(ELF): $(OBJS) + $(CC) $(CFLAGS) $(OBJS) -o $(ELF) + $(OBJDUMP) -h -S $(ELF) > $(MAP) + $(SIZE) --format=avr --mcu=$(MCU) $(ELF) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR) $(dir $(OBJS)) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +$(dir $(OBJS)): + mkdir -p $@ + +flash: $(HEX) + $(AVRDUDE) $(PROGRAMMER) -p $(MCU) -U flash:w:$(HEX):i + +eeprom: $(EEP) + $(AVRDUDE) $(PROGRAMMER) -p $(MCU) -U eeprom:w:$(EEP):i + +clean: + rm -rf $(BUILD_DIR) $(ELF) $(HEX) $(MAP) $(EEP) + +.PHONY: all flash eeprom clean diff --git a/main.c b/main.c new file mode 100644 index 0000000..35b40bc --- /dev/null +++ b/main.c @@ -0,0 +1,113 @@ +#define F_CPU 16000000UL +#define BAUDRATE 115200 + +#define VOL_UP 25 +#define VOL_DOWN 22 +#define CH_UP 21 +#define CH_DOWN 7 +#define RESET 69 + +#include +#include +#include +#include "./src/libs/uart.h" +#include "./src/libs/IRremote.h" +#include "./src/libs/softwarePWM.h" + +uint8_t brightness = 255; +uint8_t level = 0; + +void LED_brightness() { + for (uint8_t i = 0; i < 7; i++) { + softwarePWM_Set(i, brightness); + } +} + +void LED_turn_on(uint8_t led) { + softwarePWM_Set(led, brightness); +} + +void LED_turn_off(uint8_t led) { + softwarePWM_Set(led, 0); +} + +void RGB_toggle() { + uint8_t m = (level - 1) % 3; + softwarePWM_Set(4, brightness * (m == 0)); + softwarePWM_Set(5, brightness * (m == 1)); + softwarePWM_Set(6, brightness * (m == 2)); +} + +void vol_up() { + if (level >= 7) { + RGB_toggle(); + level = (level % 3) ? level + 1 : 7; + return; + } + LED_turn_on(level++); +} + +void vol_down() { + if (level >= 7) { + RGB_toggle(); + --level; + return; + } + if (level == 5) { + LED_turn_on(4); + LED_turn_on(5); + LED_turn_on(6); + --level; + return; + } + if (level) LED_turn_off(--level); +} + +void ch_up() { + brightness += (bool)level * 15; + if (brightness >= 255) brightness = 255; + LED_brightness(); +} + +void ch_down() { + brightness -= (bool)level * 15; + if (brightness <= 0) brightness = 0; + LED_brightness(); +} + +void LED_control(uint16_t command) { + switch (command) { + case VOL_UP: vol_up(); break; + case VOL_DOWN: vol_down(); break; + case CH_UP: ch_up(); break; + case CH_DOWN: ch_down(); break; + default: break; + } +} + + +int main() +{ + uint16_t address = 0; + uint16_t command = 0; + + IR_init(); + uart_init(); + softwarePWM_Init(); + + uart_puts("Well I'm working here!\r\n"); + while (1) { + if (IR_codeAvailable()) { + if (!IR_isRepeatCode()) { + IR_getCode(&address, &command); + uart_putU16(address); + uart_puts(", "); + uart_putU16(command); + uart_puts(", "); + uart_putU8(level); + uart_puts("\r\n"); + LED_control(command); + } + } + } +} \ No newline at end of file diff --git a/src/libs/IRremote.h b/src/libs/IRremote.h new file mode 100644 index 0000000..14c06d3 --- /dev/null +++ b/src/libs/IRremote.h @@ -0,0 +1,619 @@ +/*___________________________________________________________________________________________________ + +Title: + IRremote.h v1.0 + +Description: + Library for sending and receiving remote controller codes on AVR devices + + For complete details visit: + https://www.programming-electronics-diy.xyz/2022/08/ir-remote-control-library-for-avr.html + +Author: + Liviu Istrate + istrateliviu24@yahoo.com + www.programming-electronics-diy.xyz + +Donate: + Software development takes time and effort so if you find this useful consider a small donation at: + paypal.me/alientransducer +_____________________________________________________________________________________________________*/ + + +/* ----------------------------- LICENSE - GNU GPL v3 ----------------------------------------------- + +* This license must be included in any redistribution. + +* Copyright (c) 2022 Liviu Istrate, www.programming-electronics-diy.xyz (istrateliviu24@yahoo.com) + +* Project URL: https://www.programming-electronics-diy.xyz/2022/08/ir-remote-control-library-for-avr.html + +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . + +--------------------------------- END OF LICENSE --------------------------------------------------*/ + +#ifndef IRREMOTE_H_ +#define IRREMOTE_H_ + +//----------------------------------------------------------------------------- +// Includes +//----------------------------------------------------------------------------- +#include +#include +#include +#include + +//----------------------------------------------------------------------------- +// Macros User SETUP +//----------------------------------------------------------------------------- +// Input Capture Pin (must be an ICP) +#define ICP_DDR DDRB +#define ICP_PORT PORTB +#define ICP_PIN PB0 + +// OCRA pin used for transmitting +#define IR_CARRIER_DDR DDRB +#define IR_CARRIER_PORT PORTB +#define IR_CARRIER_PIN PB1 + +// Size of the receiver buffer that holds the pulse length in us +//#define IR_RX_BUFFER_SIZE 68 // increase this if the main loop is slow + +//----------------------------------------------------------------------------- +// Macros +//----------------------------------------------------------------------------- +// See the Timer calculator spreadsheet for results based on prescaler selection +#if F_CPU >= 16000000 + #define IR_TIMER_PRESCALER 64 +#elif F_CPU >= 8000000 + #define IR_TIMER_PRESCALER 8 +#else + #define IR_TIMER_PRESCALER 1 +#endif + +// One timer value in microseconds +#define IR_TICK_TIME (1.0 / (F_CPU / (float)IR_TIMER_PRESCALER)) * 1000000 // convert to microseconds +#define IR_TIMER_OVERFLOW IR_TICK_TIME * 65536 // us +#define IR_OCR_38KHZ (F_CPU / (2 * 1 * 38000)) +#define IR_OCR_DUTYCYCLE IR_OCR_38KHZ / 4 // 0.25% duty cycle + +// Minimum and maximum time in microseconds +// NEC +#define IR_NEC_AGC_MIN_TIME 9000 +#define IR_NEC_AGC_MAX_TIME 9300 +#define IR_NEC_LONG_PAUSE_MIN_TIME 4300 +#define IR_NEC_LONG_PAUSE_MAX_TIME 4600 +#define IR_NEC_SHORT_PAUSE_MIN_TIME 2000 +#define IR_NEC_SHORT_PAUSE_MAX_TIME 2300 + +// RC-5 +#define IR_RC5_MIN_TIME 880 +#define IR_RC5_MAX_TIME 980 +#define IR_RC5_JOINED_BITS_TIME 1500 + +// IR protocols +#define IR_PROTOCOL_NONE 0 +#define IR_PROTOCOL_NEC 1 +#define IR_PROTOCOL_NEC_EXTENDED 2 +#define IR_PROTOCOL_RC5 3 +#define IR_PROTOCOL_RC5_EXTENDED 4 + +typedef uint8_t bool; +#define true 1 +#define false 0 + +// Timer 1 +#define TCCRnA TCCR1A +#define TCCRnB TCCR1B +#define COMnA1 COM1A1 +#define WGMn3 WGM13 +#define ICNCn ICNC1 +#define CSn1 CS11 +#define CSn0 CS10 +#define TIMSKn TIMSK1 +#define ICIEn ICIE1 +#define TOIEn TOIE1 +#define ICRn ICR1 +#define OCRnA OCR1A +#define ICESn ICES1 +#define TIMERn_CAPT_vect TIMER1_CAPT_vect +#define TIMERn_OVF_vect TIMER1_OVF_vect + + +// By default the output of IR receiver is high. +// When the carrier frequency is detected, the output will go low. +// So to make the IR receiver output low, the carrier must be on. +#define IR_OUTPUT_LOW(){\ + TCCRnA |= (1 << COMnA1);\ + TCNT1 = 0;\ +} + +#define IR_OUTPUT_HIGH(){\ + TCCRnA &= ~(1 << COMnA1);\ + TCNT1 = 0;\ +} + +//----------------------------------------------------------------------------- +// Global VARIABLES +//----------------------------------------------------------------------------- +typedef enum { + IR_STAGE_PULSE_1, + IR_STAGE_PULSE_2, + IR_STAGE_PULSE_3, + IR_STAGE_PULSE_4, + IR_STAGE_PULSE_5, + IR_STAGE_PULSE_6, + IR_STAGE_ADDRESS, + IR_STAGE_DATA_BITS, + IR_STAGE_STOP_BIT, + IR_STAGE_END +} IR_Stage; + +// Receiver +static uint32_t IR_dataRX; +static uint16_t IR_address; +static uint16_t IR_command; +static uint8_t IR_protocolType; +static uint8_t IR_protocolTypeExt; +static uint8_t IR_RXexpectedBits; +static uint8_t IR_stageRX; +static uint8_t IR_ToggleBit; +static uint8_t IR_waitNextPulse; +static bool IR_RepeatCode; +static bool IR_frameStart; +volatile static uint16_t IR_Timestamp; +volatile static uint8_t IR_TimerOverflows; + +//----------------------------------------------------------------------------- +// Function PROTOTYPES +//----------------------------------------------------------------------------- +void IR_init(void); +bool IR_codeAvailable(void); +bool IR_isRepeatCode(void); +uint8_t IR_getToggleBit(void); +uint8_t IR_getProtocol(void); +void IR_getCode(uint16_t* address, uint16_t* command); +void IR_sendCode(uint16_t address, uint16_t command, uint8_t toggle, uint8_t protocol); +void IR_disable(void); + +static void IR_resetRX(void); + + +//----------------------------------------------------------------------------- +// Functions +//----------------------------------------------------------------------------- + +/*----------------------------------------------------------------------------- + +------------------------------------------------------------------------------*/ +void IR_init(void){ + // Set Input Capture pin as input + ICP_DDR &= ~(1 << ICP_PIN); + + // Pin used to generate the 38kHz carrier signal + IR_CARRIER_PORT &= ~(1 << IR_CARRIER_PIN); + IR_CARRIER_DDR |= (1 << IR_CARRIER_PIN); + + /* Set Input Capture Pin */ + // Input Capture Noise Canceler + // Input Capture Edge Select Falling Edge + TCCRnB = (1 << ICNCn); + + // Set prescaler + #if IR_TIMER_PRESCALER == 64 + TCCRnB |= (1 << CSn1) | (1 << CSn0); + #elif IR_TIMER_PRESCALER == 8 + TCCRnB |= (1 << CSn1); + #elif IR_TIMER_PRESCALER == 1 + TCCRnB |= (1 << CSn0); + #endif + + // Timer/Counter 1, Input Capture Interrupt Enable + // Timer/Counter 1, Overflow Interrupt Enable + TIMSKn = (1 << ICIEn) | (1 << TOIEn); + + sei(); +} + + +/*----------------------------------------------------------------------------- + Returns true when new code is available. The decoding is also done here. +------------------------------------------------------------------------------*/ +bool IR_codeAvailable(void){ + bool code_received = false; + uint8_t command_inv; + IR_protocolTypeExt = IR_protocolType; + + if(IR_stageRX == IR_STAGE_END){ + if(IR_protocolType == IR_PROTOCOL_NEC){ + if(IR_dataRX == 0){ + // Repeat code received + code_received = true; + goto EXIT; + } + + // Save lower 2 bytes - the address + IR_address = IR_dataRX; + // Save the command + IR_command = IR_dataRX >> 16; + IR_command &= 0x00FF; + command_inv = IR_dataRX >> 24; + command_inv = ~command_inv; + + // Check if extended protocol + if((IR_address & 0x00FF) == ~(IR_address & 0xFF00)){ + IR_address &= 0x00FF; + }else{ + IR_protocolTypeExt = IR_PROTOCOL_NEC_EXTENDED; + } + + // Validate the command + if(IR_command == command_inv){ + code_received = true; + } + + }else if(IR_protocolType == IR_PROTOCOL_RC5){ + code_received = true; + + IR_address = (IR_dataRX >> 6) & 0x1F; + IR_command = IR_dataRX & 0x3F; + IR_ToggleBit = (IR_dataRX >> 11) & 0x01; + + // Second Start bit + if(((IR_dataRX >> 12) & 0x01) == 0){ + IR_protocolTypeExt = IR_PROTOCOL_RC5_EXTENDED; + IR_command |= (1 << 6); + } + } + + EXIT: + IR_stageRX = IR_STAGE_PULSE_1; + IR_resetRX(); + } + + if(code_received) return true; + return false; +} + + +/*----------------------------------------------------------------------------- + When IR_codeAvailable() returns true, this function can be used to + obtain the decoded address and command. + + address, command pointer to variables where to store decoded data +------------------------------------------------------------------------------*/ +void IR_getCode(uint16_t* address, uint16_t* command){ + *address = IR_address; + *command = IR_command; + IR_address = IR_command = 0; +} + + +/*----------------------------------------------------------------------------- + RC-5 protocol only. + Returns the toggle bit: 0 or 1. +------------------------------------------------------------------------------*/ +uint8_t IR_getToggleBit(void){ + return IR_ToggleBit; +} + + +/*----------------------------------------------------------------------------- + Returns the protocol type defined by the following macros: + + IR_PROTOCOL_NONE 0 + IR_PROTOCOL_NEC 1 + IR_PROTOCOL_NEC_EXTENDED 2 + IR_PROTOCOL_RC5 3 + IR_PROTOCOL_RC5_EXTENDED 4 +------------------------------------------------------------------------------*/ +uint8_t IR_getProtocol(void){ + return IR_protocolTypeExt; +} + + +/*----------------------------------------------------------------------------- + NEC protocol only. + Returns true if a repeat code is received. +------------------------------------------------------------------------------*/ +bool IR_isRepeatCode(void){ + bool buff = IR_RepeatCode; + IR_RepeatCode = false; + return buff; +} + + +/*----------------------------------------------------------------------------- + Encodes and sends data using delays. The input capture pin interrupt + is disabled during this time and the timer is used to generate the carrier + on a particular pin. The modulation is done by disabling the timer. + + toggle The toggle bit, 0 or 1 (for RC-5 only). + For NEC a 1 means a repeat code. + protocol One of the defined protocols +------------------------------------------------------------------------------*/ +void IR_sendCode(uint16_t address, uint16_t command, uint8_t toggle, uint8_t protocol){ + uint32_t frame = 0; + + // Block ICP ISR + IR_stageRX = IR_STAGE_END; + + // Generate 38kHz carrier + // PWM, Phase and Frequency Correct, Mode 8 + // Clear OC1A/OC1B on Compare Match when up-counting. + // Set OC1A/OC1B on Compare Match when down-counting. + // Prescaler 1 + TCCRnA = (1 << COMnA1); + TCCRnB = (1 << WGMn3) | (1 << CSn0); + TIMSKn = 0; + ICRn = IR_OCR_38KHZ; // 38kHz + OCRnA = IR_OCR_DUTYCYCLE; + + if((protocol == IR_PROTOCOL_NEC) || (protocol == IR_PROTOCOL_NEC_EXTENDED)){ + frame = (uint32_t)command << 16; + frame |= (uint32_t)(~command) << 24; + frame |= address; // extended 16-bits address + + if(protocol == IR_PROTOCOL_NEC) frame |= (~address) << 8; + + // Send leader code + // AGC pulse - low for x ms + IR_OUTPUT_LOW(); + _delay_ms(9); + + // Long or short pause - high x ms + IR_OUTPUT_HIGH(); + + if(toggle){ + _delay_us(2250); + goto END_CODE; + }else{ + _delay_us(4500); + } + + // Send code - LSB first + for(uint8_t i = 0; i < 32; i++){ + if(((uint32_t)1 << i) & frame){ + // Send 1 + IR_OUTPUT_LOW(); + _delay_us(562); // low pulse + IR_OUTPUT_HIGH(); + _delay_us(1687); // high pulse + }else{ + // Send 0 + IR_OUTPUT_LOW(); + _delay_us(562); // low pulse + IR_OUTPUT_HIGH(); + _delay_us(562); // high pulse + } + } + + END_CODE: + + // Stop bit + IR_OUTPUT_LOW(); + _delay_us(562); // low pulse + IR_OUTPUT_HIGH(); // idle high + + }else if((protocol == IR_PROTOCOL_RC5) || (protocol == IR_PROTOCOL_RC5_EXTENDED)){ + // Set Start bit 1 to 1 + frame = (1 << 13); + + // Set Start bit 2 + frame |= (1 << 12); // set bit to 1 + + if(protocol == IR_PROTOCOL_RC5_EXTENDED){ + // Set bit to 0 that represents a 1 but inverted + if(command & (1 << 6)) frame &= ~(1 << 12); + } + + // Set toggle bit + if(toggle) frame |= (1 << 11); + + // Address + frame |= address << 6; + + // Command + frame |= command; + + // Send code - MSB first + for(uint8_t i = 0; i < 14; i++){ + if(((uint16_t)1 << (13 - i)) & frame){ + // Send 1 + IR_OUTPUT_HIGH(); + _delay_us(889); // low pulse + IR_OUTPUT_LOW(); + _delay_us(889); // high pulse + }else{ + // Send 0 + IR_OUTPUT_LOW(); + _delay_us(889); // low pulse + IR_OUTPUT_HIGH(); + _delay_us(889); // high pulse + } + } + } + + // Restore Timer for Receive mode + TCCRnA = 0; + IR_init(); + IR_stageRX = IR_STAGE_PULSE_1; +} + + +/*----------------------------------------------------------------------------- + Disable the Timer and associated interrupts. +------------------------------------------------------------------------------*/ +void IR_disable(void){ + TIMSKn = 0; + TCCRnB = 0; +} + + +static void IR_resetRX(void){ + IR_dataRX = 0; + IR_RXexpectedBits = 0; + IR_waitNextPulse = 0; + IR_stageRX = IR_STAGE_PULSE_1; +} + + +//----------------------------------------------------------------------------- +// ISR Handlers +//----------------------------------------------------------------------------- +// Timer/Counter Capture Event +// ~35us @ 16MHz + +/*----------------------------------------------------------------------------- + The protocol type is defined here but not the extended ones. + If the protocol is extended is decided in the decoder function. + This ISR triggers on every pulse then the pulses are converted to bits + that are stored in a 32-bit variable used by the decoder function. +------------------------------------------------------------------------------*/ +ISR(TIMERn_CAPT_vect){ + uint16_t pulse_length; + uint16_t IR_TimestampPrev; + uint8_t pulse_level; + + if(IR_stageRX == IR_STAGE_END) return; + + // Save previous timestamp + IR_TimestampPrev = IR_Timestamp; + + // Read TCNT1 timestamp + IR_Timestamp = ICRn; + + // Select the opposite edge to trigger + TCCRnB ^= (1 << ICESn); + pulse_level = TCCRnB & (1< 15000){ + // This prevents collecting data in the middle of the frame + IR_frameStart = true; + IR_resetRX(); + return; + } + + + // Detect protocol type by checking leader code + switch(IR_stageRX){ + case IR_STAGE_PULSE_1: + IR_frameStart = false; + IR_protocolType = 0; + IR_stageRX = IR_STAGE_PULSE_2; + + if(pulse_length < IR_NEC_AGC_MAX_TIME && pulse_length > IR_NEC_AGC_MIN_TIME){ + // NEC protocol, AGC pulse + IR_protocolType = IR_PROTOCOL_NEC; + }else if(pulse_level == 0 && pulse_length > IR_RC5_MIN_TIME && pulse_length < IR_RC5_MAX_TIME){ + // RC-5 protocol + // Second half of the Start Bit 1 + IR_protocolType = IR_PROTOCOL_RC5; + IR_stageRX = IR_STAGE_DATA_BITS; + IR_RXexpectedBits = 13; + + if(pulse_length > IR_RC5_JOINED_BITS_TIME){ + IR_waitNextPulse++; + } + } + break; + + case IR_STAGE_PULSE_2: + if(IR_protocolType == IR_PROTOCOL_NEC){ + if(pulse_length < IR_NEC_LONG_PAUSE_MAX_TIME && pulse_length > IR_NEC_LONG_PAUSE_MIN_TIME){ + // Check for long pause + IR_stageRX = IR_STAGE_DATA_BITS; + + }else if(pulse_length < IR_NEC_SHORT_PAUSE_MAX_TIME && pulse_length > IR_NEC_SHORT_PAUSE_MIN_TIME){ + // Check for short pause + IR_RepeatCode = true; + IR_stageRX = IR_STAGE_END; + } + + } + break; + + case IR_STAGE_DATA_BITS: + // Collect NEC data bits - LSB is sent first + if(IR_protocolType == IR_PROTOCOL_NEC){ + // Wait for two pulses to form a bit + if(pulse_level){ + if(pulse_level && pulse_length > 1500){ + IR_dataRX |= ((uint32_t)1 << IR_RXexpectedBits); + } + + IR_RXexpectedBits++; + + // All bits received. Wait for STOP bit. + if(IR_RXexpectedBits > 31){ + IR_stageRX = IR_STAGE_STOP_BIT; + } + } + + // Collect RC-5 data bits - MSB is sent first + }else if(IR_protocolType == IR_PROTOCOL_RC5){ + IR_waitNextPulse++; + + // Wait for two pulses to form a bit + if(IR_waitNextPulse > 1){ + IR_waitNextPulse = 0; + IR_RXexpectedBits--; + + if(pulse_level == 0){ + IR_dataRX |= ((uint32_t)1 << IR_RXexpectedBits); + } + } + + // Received half of the next bit + if(pulse_length > IR_RC5_JOINED_BITS_TIME){ + IR_waitNextPulse++; + } + + // All bits received + if(IR_RXexpectedBits == 1 && IR_waitNextPulse == 1 && pulse_level == 0){ + // The case when bit 0 is at the end + IR_stageRX = IR_STAGE_END; + } + + if(IR_RXexpectedBits == 0){ + // The case when bit 1 is at the end + IR_stageRX = IR_STAGE_END; + } + } + break; + + case IR_STAGE_STOP_BIT: + IR_stageRX = IR_STAGE_END; + } +} + + +// Timer/Counter Overflow +ISR(TIMERn_OVF_vect){ + IR_TimerOverflows++; +} + +#endif /* IRREMOTE_H_ */ \ No newline at end of file diff --git a/src/libs/softwarePWM.h b/src/libs/softwarePWM.h new file mode 100644 index 0000000..bd74749 --- /dev/null +++ b/src/libs/softwarePWM.h @@ -0,0 +1,499 @@ +/*___________________________________________________________________________________________________ + +Title: + softwarePWM.h v1.0 + +Description: + Multichannel software PWM based on "AVR136: Low-Jitter Multi-Channel Software PWM" + application note. + + For complete details visit: + https://www.programming-electronics-diy.xyz/2021/02/multi-channel-software-pwm-library-for.html + +Author: + Liviu Istrate + istrateliviu24@yahoo.com + www.programming-electronics-diy.xyz + +Donate: + Software development takes time and effort so if you find this useful consider a small donation at: + paypal.me/alientransducer +_____________________________________________________________________________________________________*/ + + +/* ----------------------------- LICENSE - GNU GPL v3 ----------------------------------------------- + +* This license must be included in any redistribution. + +* Copyright (C) 2021 Liviu Istrate, www.programming-electronics-diy.xyz (istrateliviu24@yahoo.com) + +* Project URL: https://www.programming-electronics-diy.xyz/2021/02/multi-channel-software-pwm-library-for.html + +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . + +--------------------------------- END OF LICENSE --------------------------------------------------*/ + +#ifndef SOFTWARE_PWM_H +#define SOFTWARE_PWM_H + +/************************************************************* + INCLUDES +**************************************************************/ +#include +#include +#include +#include + +/************************************************************* + SYSTEM SETTINGS +**************************************************************/ +#ifndef PORTE + #define PINE _SFR_IO8(0x0C) + #define DDRE _SFR_IO8(0x0D) + #define PORTE _SFR_IO8(0x0E) +#endif + +#define TRUE 1 +#define FALSE 0 + +#define SOFTPWM_TIMER0 0 // Use timer0 +#define SOFTPWM_TIMER1 1 // Use timer1 +#define SOFTPWM_TIMER2 2 // Use timer2 + +#define SOFTPWM_PWMDEFAULT 0x00 // default PWM value at start up for all channels + + +/************************************************************* + USER SETUP SECTION +**************************************************************/ +#define SOFTPWM_TIMER SOFTPWM_TIMER0 // Which timer to use +#define SOFTPWM_TIMER1_OCR 255 +#define SOFTPWM_CHMAX 7 // maximum number of PWM channels +#define USE_LOGARITHMIC_ARRAY FALSE + +// Mapping channels to pin and ports +#define SOFTPWM_CHANNEL0_DDR DDRD +#define SOFTPWM_CHANNEL0_PORT PORTD +#define SOFTPWM_CHANNEL0_PIN 4 +#define SOFTPWM_CHANNEL0_PIN_STATES pinlevelD + +#define SOFTPWM_CHANNEL1_DDR DDRD +#define SOFTPWM_CHANNEL1_PORT PORTD +#define SOFTPWM_CHANNEL1_PIN 5 +#define SOFTPWM_CHANNEL1_PIN_STATES pinlevelD + +#define SOFTPWM_CHANNEL2_DDR DDRD +#define SOFTPWM_CHANNEL2_PORT PORTD +#define SOFTPWM_CHANNEL2_PIN 6 +#define SOFTPWM_CHANNEL2_PIN_STATES pinlevelD + +#define SOFTPWM_CHANNEL3_DDR DDRD +#define SOFTPWM_CHANNEL3_PORT PORTD +#define SOFTPWM_CHANNEL3_PIN 7 +#define SOFTPWM_CHANNEL3_PIN_STATES pinlevelD + +#define SOFTPWM_CHANNEL4_DDR DDRB +#define SOFTPWM_CHANNEL4_PORT PORTB +#define SOFTPWM_CHANNEL4_PIN 2 +#define SOFTPWM_CHANNEL4_PIN_STATES pinlevelB + +#define SOFTPWM_CHANNEL5_DDR DDRB +#define SOFTPWM_CHANNEL5_PORT PORTB +#define SOFTPWM_CHANNEL5_PIN 3 +#define SOFTPWM_CHANNEL5_PIN_STATES pinlevelB + +#define SOFTPWM_CHANNEL6_DDR DDRB +#define SOFTPWM_CHANNEL6_PORT PORTB +#define SOFTPWM_CHANNEL6_PIN 4 +#define SOFTPWM_CHANNEL6_PIN_STATES pinlevelB + +#define SOFTPWM_CHANNEL7_DDR 0 +#define SOFTPWM_CHANNEL7_PORT 0 +#define SOFTPWM_CHANNEL7_PIN 0 +#define SOFTPWM_CHANNEL7_PIN_STATES 0 + +#define SOFTPWM_CHANNEL8_DDR 0 +#define SOFTPWM_CHANNEL8_PORT 0 +#define SOFTPWM_CHANNEL8_PIN 0 +#define SOFTPWM_CHANNEL8_PIN_STATES 0 + +#define SOFTPWM_CHANNEL9_DDR 0 +#define SOFTPWM_CHANNEL9_PORT 0 +#define SOFTPWM_CHANNEL9_PIN 0 +#define SOFTPWM_CHANNEL9_PIN_STATES 0 + +// Which ports are used +#define SOFTPWM_ON_PORT_B TRUE +#define SOFTPWM_ON_PORT_C FALSE +#define SOFTPWM_ON_PORT_D TRUE +#define SOFTPWM_ON_PORT_E FALSE + +// What pins are on this ports +#define SOFTPWM_PINS_PORTB _BV(2) | _BV(3) | _BV(4) +#define SOFTPWM_PINS_PORTC FALSE +#define SOFTPWM_PINS_PORTD _BV(4) | _BV(5) | _BV(6) | _BV(7) +#define SOFTPWM_PINS_PORTE FALSE + + +/************************************************************* + SYSTEM SETTINGS +**************************************************************/ + +#if SOFTPWM_CHMAX > 0 + #define CH0_CLEAR (SOFTPWM_CHANNEL0_PIN_STATES &= ~(1 << SOFTPWM_CHANNEL0_PIN)) +#endif + +#if SOFTPWM_CHMAX > 1 + #define CH1_CLEAR (SOFTPWM_CHANNEL1_PIN_STATES &= ~(1 << SOFTPWM_CHANNEL1_PIN)) +#endif + +#if SOFTPWM_CHMAX > 2 + #define CH2_CLEAR (SOFTPWM_CHANNEL2_PIN_STATES &= ~(1 << SOFTPWM_CHANNEL2_PIN)) +#endif + +#if SOFTPWM_CHMAX > 3 + #define CH3_CLEAR (SOFTPWM_CHANNEL3_PIN_STATES &= ~(1 << SOFTPWM_CHANNEL3_PIN)) +#endif + +#if SOFTPWM_CHMAX > 4 + #define CH4_CLEAR (SOFTPWM_CHANNEL4_PIN_STATES &= ~(1 << SOFTPWM_CHANNEL4_PIN)) +#endif + +#if SOFTPWM_CHMAX > 5 + #define CH5_CLEAR (SOFTPWM_CHANNEL5_PIN_STATES &= ~(1 << SOFTPWM_CHANNEL5_PIN)) +#endif + +#if SOFTPWM_CHMAX > 6 + #define CH6_CLEAR (SOFTPWM_CHANNEL6_PIN_STATES &= ~(1 << SOFTPWM_CHANNEL6_PIN)) +#endif + +#if SOFTPWM_CHMAX > 7 + #define CH7_CLEAR (SOFTPWM_CHANNEL7_PIN_STATES &= ~(1 << SOFTPWM_CHANNEL7_PIN)) +#endif + +#if SOFTPWM_CHMAX > 8 + #define CH8_CLEAR (SOFTPWM_CHANNEL8_PIN_STATES &= ~(1 << SOFTPWM_CHANNEL8_PIN)) +#endif + +#if SOFTPWM_CHMAX > 9 + #define CH9_CLEAR (SOFTPWM_CHANNEL9_PIN_STATES &= ~(1 << SOFTPWM_CHANNEL9_PIN)) +#endif + + +/************************************************************* + GLOBAL VARIABLES +**************************************************************/ +static unsigned char compare[SOFTPWM_CHMAX]; +static volatile unsigned char compbuff[SOFTPWM_CHMAX]; + +#if USE_LOGARITHMIC_ARRAY == TRUE +static const uint8_t LogBrightness[] PROGMEM = { + 8,8,8,8,9,9,9,9,9,10,10,10,10,11,11,11,12,12,12,12,13,13,13,14,14,14, + 15,15,15,16,16,17,17,17,18,18,19,19,19,20,20,21,21,22,22, + 23,23,24,25,25,26,26,27,28,28,29,30,30,31,32,32,33,34,35, + 35,36,37,38,39,40,41,42,42,43,44,45,46,48,49,50,51,52,53, + 54,56,57,58,59,61,62,64,65,66,68,69,71,73,74,76,78,79,81, + 83,85,87,89,91,93,95,97,99,101,103,106,108,110,113,115,118, + 121,123,126,129,132,135,138,141,144,147,150,154,157,161,164, + 168,171,175,179,183,187,191,196,200,204,209,213,218,223,228, + 233,238,243,249,255 +}; +#endif + + +/************************************************************* + FUNCTION PROTOTYPES +**************************************************************/ +void softwarePWM_Init(void); +void softwarePWM_Set(unsigned char channel, unsigned char value); +void softwarePWM_Resume(void); +void softwarePWM_Pause(void); + +#if USE_LOGARITHMIC_ARRAY == TRUE +uint8_t softwarePWM_LineartoLog(uint8_t dutyCycle); +#endif + + +/************************************************************* + FUNCTIONS +**************************************************************/ +void softwarePWM_Init(void){ + unsigned char i; + unsigned char pwm = SOFTPWM_PWMDEFAULT; + + // Set port pins to output + #if SOFTPWM_ON_PORT_B == TRUE + DDRB |= SOFTPWM_PINS_PORTB; + #endif + + #if SOFTPWM_ON_PORT_C == TRUE + DDRC |= SOFTPWM_PINS_PORTC; + #endif + + #if SOFTPWM_ON_PORT_D == TRUE + DDRD |= SOFTPWM_PINS_PORTD; + #endif + + #if SOFTPWM_ON_PORT_E == TRUE + DDRE |= SOFTPWM_PINS_PORTE; + #endif + + // Initialise all channels + for(i=0; i < SOFTPWM_CHMAX; i++){ + compare[i] = pwm; + compbuff[i] = pwm; + } + + #if SOFTPWM_TIMER == SOFTPWM_TIMER0 + TIFR0 = (1 << TOV0); // clear interrupt flag + TIMSK0 = (1 << TOIE0); // enable overflow interrupt + TCCR0B = (1 << CS00); // start timer, no prescale + #define SOFTPWM_ISR_VECT TIMER0_OVF_vect + + #elif SOFTPWM_TIMER == SOFTPWM_TIMER1 + TIFR1 = (1 << OCF1A); // clear interrupt flag + TIMSK1 = (1 << OCIE1A); // enable Output Compare A Match interrupt + TCCR1B = (1 << CS10 | 1 << WGM12); // start timer, no prescale + OCR1A = SOFTPWM_TIMER1_OCR; + #define SOFTPWM_ISR_VECT TIMER1_COMPA_vect + + #elif SOFTPWM_TIMER == SOFTPWM_TIMER2 + TIFR2 = (1 << TOV2); // clear interrupt flag + TIMSK2 = (1 << TOIE2); // enable overflow interrupt + TCCR2B = (1 << CS20); // start timer, no prescale + #define SOFTPWM_ISR_VECT TIMER2_OVF_vect + #endif + + sei(); // enable global interrupts +} + + + +void softwarePWM_Set(unsigned char channel, unsigned char value){ + if(channel < SOFTPWM_CHMAX) compbuff[channel] = value; +} + + + +void softwarePWM_Resume(void){ + #if SOFTPWM_TIMER == SOFTPWM_TIMER0 + power_timer0_enable(); + TIMSK0 = (1 << TOIE0); // enable overflow interrupt + TCCR0B = (1 << CS00); // start timer, no prescale + + #elif SOFTPWM_TIMER == SOFTPWM_TIMER1 + power_timer1_enable(); + TIFR1 = (1 << OCF1A); // clear interrupt flag + TIMSK1 = (1 << OCIE1A); // enable Output Compare A Match interrupt + TCCR1B = (1 << CS10 | 1 << WGM12); // start timer, no prescale + OCR1A = SOFTPWM_TIMER1_OCR; + + #elif SOFTPWM_TIMER == SOFTPWM_TIMER2 + power_timer2_enable(); + TIMSK2 = (1 << TOIE2); // enable overflow interrupt + TCCR2B = (1 << CS20); // start timer, no prescale + #endif +} + + + +void softwarePWM_Pause(void){ + #if SOFTPWM_TIMER == SOFTPWM_TIMER0 + TIMSK0 &= ~(1 << TOIE0); // disable overflow interrupt + TCCR0B = 0; // No clock source (Timer/Counter stopped) + power_timer0_disable(); + + #elif SOFTPWM_TIMER == SOFTPWM_TIMER1 + TIMSK1 = (1 << OCIE1A); // disable Output Compare A Match interrupt + TCCR1B = 0; // No clock source (Timer/Counter stopped) + power_timer1_disable(); + + #elif SOFTPWM_TIMER == SOFTPWM_TIMER2 + TIMSK2 &= ~(1 << TOIE2); // disable overflow interrupt + TCCR2B = 0; // No clock source (Timer/Counter stopped) + power_timer2_disable(); + #endif +} + + +#if USE_LOGARITHMIC_ARRAY == TRUE +uint8_t softwarePWM_LineartoLog(uint8_t dutyCycle){ + + if(dutyCycle < 32){ + return 0; + + }else if(dutyCycle < 51){ + return 1; + + }else if(dutyCycle < 64){ + return 2; + + }else if(dutyCycle < 75){ + return 3; + + }else if(dutyCycle < 83){ + return 4; + + }else if(dutyCycle < 90){ + return 5; + + }else if(dutyCycle < 96){ + return 6; + + }else if(dutyCycle < 102){ + return 7; + + } + + if(dutyCycle > 255) dutyCycle = 0; + dutyCycle -= 102; + + return pgm_read_byte(&LogBrightness[dutyCycle]); + +} +#endif + + +/************************************************************* + ISR Handlers +**************************************************************/ + +ISR(SOFTPWM_ISR_VECT){ + static unsigned char softcount = 0xFF; + + #if SOFTPWM_ON_PORT_B == TRUE + static unsigned char pinlevelB = SOFTPWM_PINS_PORTB; + // Update outputs + PORTB = (PORTB & (~(SOFTPWM_PINS_PORTB))) | pinlevelB; + #endif + + #if SOFTPWM_ON_PORT_C == TRUE + static unsigned char pinlevelC = SOFTPWM_PINS_PORTC; + PORTC = (PORTC & (~(SOFTPWM_PINS_PORTC))) | pinlevelC; + #endif + + #if SOFTPWM_ON_PORT_D == TRUE + static unsigned char pinlevelD = SOFTPWM_PINS_PORTD; + PORTD = (PORTD & (~(SOFTPWM_PINS_PORTD))) | pinlevelD; + #endif + + #if SOFTPWM_ON_PORT_E == TRUE + static unsigned char pinlevelE = SOFTPWM_PINS_PORTE; + PORTE = (PORTE & (~(SOFTPWM_PINS_PORTE))) | pinlevelE; + #endif + + if(++softcount == 0){ + #if SOFTPWM_CHMAX > 0 + compare[0] = compbuff[0]; + #endif + + #if SOFTPWM_CHMAX > 1 + compare[1] = compbuff[1]; + #endif + + #if SOFTPWM_CHMAX > 2 + compare[2] = compbuff[2]; + #endif + + #if SOFTPWM_CHMAX > 3 + compare[3] = compbuff[3]; + #endif + + #if SOFTPWM_CHMAX > 4 + compare[4] = compbuff[4]; + #endif + + #if SOFTPWM_CHMAX > 5 + compare[5] = compbuff[5]; + #endif + + #if SOFTPWM_CHMAX > 6 + compare[6] = compbuff[6]; + #endif + + #if SOFTPWM_CHMAX > 7 + compare[7] = compbuff[7]; + #endif + + #if SOFTPWM_CHMAX > 8 + compare[8] = compbuff[8]; + #endif + + #if SOFTPWM_CHMAX > 9 + compare[9] = compbuff[9]; + #endif + + + // Set all port pins high + #if SOFTPWM_ON_PORT_B == TRUE + pinlevelB = SOFTPWM_PINS_PORTB; + #endif + + #if SOFTPWM_ON_PORT_C == TRUE + pinlevelC = SOFTPWM_PINS_PORTC; + #endif + + #if SOFTPWM_ON_PORT_D == TRUE + pinlevelD = SOFTPWM_PINS_PORTD; + #endif + + #if SOFTPWM_ON_PORT_E == TRUE + pinlevelE = SOFTPWM_PINS_PORTE; + #endif + } + + // clear port pin on compare match (executed on next interrupt) + #if SOFTPWM_CHMAX > 0 + if(compare[0] == softcount) CH0_CLEAR; + #endif + + #if SOFTPWM_CHMAX > 1 + if(compare[1] == softcount) CH1_CLEAR; + #endif + + #if SOFTPWM_CHMAX > 2 + if(compare[2] == softcount) CH2_CLEAR; + #endif + + #if SOFTPWM_CHMAX > 3 + if(compare[3] == softcount) CH3_CLEAR; + #endif + + #if SOFTPWM_CHMAX > 4 + if(compare[4] == softcount) CH4_CLEAR; + #endif + + #if SOFTPWM_CHMAX > 5 + if(compare[5] == softcount) CH5_CLEAR; + #endif + + #if SOFTPWM_CHMAX > 6 + if(compare[6] == softcount) CH6_CLEAR; + #endif + + #if SOFTPWM_CHMAX > 7 + if(compare[7] == softcount) CH7_CLEAR; + #endif + + #if SOFTPWM_CHMAX > 8 + if(compare[8] == softcount) CH8_CLEAR; + #endif + + #if SOFTPWM_CHMAX > 9 + if(compare[9] == softcount) CH9_CLEAR; + #endif + +} + +#endif \ No newline at end of file diff --git a/src/libs/uart.h b/src/libs/uart.h new file mode 100644 index 0000000..ab4b60c --- /dev/null +++ b/src/libs/uart.h @@ -0,0 +1,169 @@ +// Reference: http://www.rjhcoding.com/avrc-uart.php + +#ifndef F_CPU +#define F_CPU 16000000UL +#endif + +#ifndef BAUDRATE +#define BAUDRATE 9600 +#endif + +#define BAUD_PRESCALLER (((F_CPU / (BAUDRATE * 16UL))) - 1) + +#include +#include + +void uart_init(); +void uart_putc(unsigned char data); +void uart_puts(char* s); +void uart_putU8(uint8_t val); +void uart_putU16(uint16_t val); +void uart_puthex8(uint8_t val); +void uart_puthex16(uint16_t val); + +void uart_init() +{ + UBRR0H = (uint8_t)(BAUD_PRESCALLER >> 8); + UBRR0L = (uint8_t)(BAUD_PRESCALLER); + + UCSR0B = (1 << RXEN0) | (1 << TXEN0); + UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); +} + +void uart_putc(unsigned char data) +{ + // wait for transmit buffer to be empty + while (!(UCSR0A & (1 << UDRE0))) + ; + + // load data into transmit register + UDR0 = data; +} + +void uart_puts(char *s) +{ + // transmit character until NULL is reached + while (*s > 0) + uart_putc(*s++); +} + +void uart_putU8(uint8_t val) +{ + uint8_t dig1 = '0', dig2 = '0'; + + // count value in 100s place + while (val >= 100) + { + val -= 100; + dig1++; + } + + // count value in 10s place + while (val >= 10) + { + val -= 10; + dig2++; + } + + // print first digit (or ignore leading zeros) + if (dig1 != '0') + uart_putc(dig1); + + // print second digit (or ignore leading zeros) + if ((dig1 != '0') || (dig2 != '0')) + uart_putc(dig2); + + // print final digit + uart_putc(val + '0'); +} + +void uart_putU16(uint16_t val) +{ + uint8_t dig1 = '0', dig2 = '0', dig3 = '0', dig4 = '0'; + + // count value in 10000s place + while (val >= 10000) + { + val -= 10000; + dig1++; + } + + // count value in 1000s place + while (val >= 1000) + { + val -= 1000; + dig2++; + } + + // count value in 100s place + while (val >= 100) + { + val -= 100; + dig3++; + } + + // count value in 10s place + while (val >= 10) + { + val -= 10; + dig4++; + } + + // was previous value printed? + uint8_t prevPrinted = 0; + + // print first digit (or ignore leading zeros) + if (dig1 != '0') + { + uart_putc(dig1); + prevPrinted = 1; + } + + // print second digit (or ignore leading zeros) + if (prevPrinted || (dig2 != '0')) + { + uart_putc(dig2); + prevPrinted = 1; + } + + // print third digit (or ignore leading zeros) + if (prevPrinted || (dig3 != '0')) + { + uart_putc(dig3); + prevPrinted = 1; + } + + // print third digit (or ignore leading zeros) + if (prevPrinted || (dig4 != '0')) + { + uart_putc(dig4); + prevPrinted = 1; + } + + // print final digit + uart_putc(val + '0'); +} + +void uart_puthex8(uint8_t val) +{ + // extract upper and lower nibbles from input value + uint8_t upperNibble = (val & 0xF0) >> 4; + uint8_t lowerNibble = val & 0x0F; + + // convert nibble to its ASCII hex equivalent + upperNibble += upperNibble > 9 ? 'A' - 10 : '0'; + lowerNibble += lowerNibble > 9 ? 'A' - 10 : '0'; + + // print the characters + uart_putc(upperNibble); + uart_putc(lowerNibble); +} + +void uart_puthex16(uint16_t val) +{ + // transmit upper 8 bits + uart_puthex8((uint8_t)(val >> 8)); + + // transmit lower 8 bits + uart_puthex8((uint8_t)(val & 0x00FF)); +} \ No newline at end of file