From 40f352bcc74db6a8caffef9415d094e11589a92e Mon Sep 17 00:00:00 2001 From: Alexey Zholtikov Date: Sun, 14 Sep 2025 08:13:38 +0300 Subject: [PATCH] feat: initial --- .gitignore | 1 + README.md | 64 ++++++++- include/zh_avr_ac_dimmer.h | 84 ++++++++++++ version.txt | 1 + zh_avr_ac_dimmer.c | 263 +++++++++++++++++++++++++++++++++++++ 5 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 include/zh_avr_ac_dimmer.h create mode 100644 version.txt create mode 100644 zh_avr_ac_dimmer.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..496ee2c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index 5181aa0..6ee5b19 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,64 @@ -# zh_avr_ac_dimmer +# AVR library for AC dimmer +## Features + +1. Support of 50, 60 and 400 Hz power frequency. + +## Dependencies + +1. [zh_avr_common](http://git.zh.com.ru/avr_libraries/zh_avr_common) + +## Using + +In an existing project, run the following command to install the components: + +```text +cd ../your_project/lib +git clone http://git.zh.com.ru/avr_libraries/zh_avr_common +git clone http://git.zh.com.ru/avr_libraries/zh_avr_ac_dimmer +``` + +In the application, add the component: + +```c +#include "zh_avr_ac_dimmer.h" +``` + +## Examples + +```c +#include "zh_avr_ac_dimmer.h" + +int main(void) +{ + zh_avr_ac_dimmer_init_config_t ac_dimmer_init_config = ZH_AVR_AC_DIMMER_INIT_CONFIG_DEFAULT(); + ac_dimmer_init_config.ac_dimmer_frequency = ZH_50HZ; + ac_dimmer_init_config.zero_cross_port = AVR_PORTD; + ac_dimmer_init_config.zero_cross_gpio = PORTD2; + ac_dimmer_init_config.triac_port = AVR_PORTD; + ac_dimmer_init_config.triac_gpio = PORTD3; + zh_avr_ac_dimmer_init(&ac_dimmer_init_config); + zh_avr_ac_dimmer_start(); + for (;;) + { + for (uint8_t i = 1; i <= 100; ++i) + { + zh_avr_ac_dimmer_set(i); + _delay_ms(50); + } + for (uint8_t i = 100; i >= 1; --i) + { + zh_avr_ac_dimmer_set(i); + _delay_ms(50); + } + } + return 0; +} + +// ISR(PCINT0_vect) // For AVR_PORTB. +// ISR(PCINT1_vect) // For AVR_PORTC. +ISR(PCINT2_vect) // For AVR_PORTD. +{ + zh_avr_ac_dimmer_isr_handler(); +} +``` diff --git a/include/zh_avr_ac_dimmer.h b/include/zh_avr_ac_dimmer.h new file mode 100644 index 0000000..4fd7ac4 --- /dev/null +++ b/include/zh_avr_ac_dimmer.h @@ -0,0 +1,84 @@ +#pragma once + +#include "stdio.h" +#include "avr_err.h" +#include "avr_port.h" +#include "stdbool.h" +#include "avr/interrupt.h" +#include "util/delay.h" + +#define ZH_TRIAC_TIME 2 // Triac turn-on time (in microseconds). Depends on the type of triac. Tested on BTA16-600. + +#define ZH_AVR_AC_DIMMER_INIT_CONFIG_DEFAULT() \ + { \ + .ac_dimmer_frequency = ZH_50HZ, \ + .zero_cross_gpio = 0xFF, \ + .zero_cross_port = 0, \ + .triac_gpio = 0xFF, \ + .triac_port = 0} + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef enum + { + ZH_50HZ = 1, + ZH_60HZ = 2, + ZH_400HZ = 3 + } zh_avr_ac_dimmer_frequency_t; + + typedef struct // Structure for initial initialization of AC dimmer. + { + uint8_t ac_dimmer_frequency; // AC frequency. + uint8_t zero_cross_gpio; // Zero cross GPIO. + uint8_t zero_cross_port; // Zero cross port. + uint8_t triac_gpio; // Triac GPIO. + uint8_t triac_port; // Triac port. + } zh_avr_ac_dimmer_init_config_t; + + /** + * @brief Initialize AC dimmer. + * + * @param[in] config Pointer to AC dimmer initialized configuration structure. Can point to a temporary variable. + * + * @note Before initialize the AC dimmer recommend initialize zh_avr_ac_dimmer_init_config_t structure with default values. + * + * @code zh_avr_ac_dimmer_init_config_t config = ZH_AVR_AC_DIMMER_INIT_CONFIG_DEFAULT @endcode + * + * @return AVR_OK if success or an error code otherwise. + */ + avr_err_t zh_avr_ac_dimmer_init(const zh_avr_ac_dimmer_init_config_t *config); + + /** + * @brief Start AC dimmer. + * + * @return AVR_OK if success or an error code otherwise. + */ + avr_err_t zh_avr_ac_dimmer_start(void); + + /** + * @brief Stop AC dimmer. + * + * @return AVR_OK if success or an error code otherwise. + */ + avr_err_t zh_avr_ac_dimmer_stop(void); + + /** + * @brief Set AC dimmer dimming value. + * + * @param[in] value Dimming value (0 to 100). + * + * @return AVR_OK if success or an error code otherwise. + */ + avr_err_t zh_avr_ac_dimmer_set(uint8_t value); + + /** + * @brief AC dimmer ISR handler. + */ + void zh_avr_ac_dimmer_isr_handler(void); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..afaf360 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/zh_avr_ac_dimmer.c b/zh_avr_ac_dimmer.c new file mode 100644 index 0000000..0256eef --- /dev/null +++ b/zh_avr_ac_dimmer.c @@ -0,0 +1,263 @@ +#include "zh_avr_ac_dimmer.h" + +static zh_avr_ac_dimmer_init_config_t _init_config = {0}; +volatile static bool _is_dimmer_work = false; +volatile static uint8_t _dimmer_value = 0; +volatile static uint8_t _dimmer_count = 0; +static bool _is_initialized = false; + +static avr_err_t _zh_avr_ac_dimmer_validate_config(const zh_avr_ac_dimmer_init_config_t *config); +static void _zh_avr_ac_dimmer_gpio_init(void); +static avr_err_t _zh_avr_ac_dimmer_timer_init(void); + +avr_err_t zh_avr_ac_dimmer_init(const zh_avr_ac_dimmer_init_config_t *config) +{ + ZH_ERROR_CHECK(_is_initialized == false, AVR_ERR_INVALID_STATE); + avr_err_t err = _zh_avr_ac_dimmer_validate_config(config); + ZH_ERROR_CHECK(err == AVR_OK, err); + _init_config = *config; + cli(); + _zh_avr_ac_dimmer_gpio_init(); + err = _zh_avr_ac_dimmer_timer_init(); + ZH_ERROR_CHECK(err == AVR_OK, err); + sei(); + _is_initialized = true; + return AVR_OK; +} + +avr_err_t zh_avr_ac_dimmer_start(void) +{ + ZH_ERROR_CHECK(_is_initialized == true, AVR_ERR_NOT_FOUND); + _is_dimmer_work = true; + return AVR_OK; +} + +avr_err_t zh_avr_ac_dimmer_stop(void) +{ + ZH_ERROR_CHECK(_is_initialized == true, AVR_ERR_NOT_FOUND); + _is_dimmer_work = false; + return AVR_OK; +} + +avr_err_t zh_avr_ac_dimmer_set(uint8_t value) +{ + ZH_ERROR_CHECK(_dimmer_value <= 100, AVR_ERR_INVALID_ARG); + _dimmer_value = value; + return AVR_OK; +} + +void zh_avr_ac_dimmer_isr_handler(void) +{ + uint8_t flag = false; + switch (_init_config.zero_cross_port) + { + case AVR_PORTB: + if ((PINB & (1 << _init_config.zero_cross_gpio)) == 0) + { + flag = true; + } + break; + case AVR_PORTC: + if ((PINC & (1 << _init_config.zero_cross_gpio)) == 0) + { + flag = true; + } + break; + case AVR_PORTD: + if ((PIND & (1 << _init_config.zero_cross_gpio)) == 0) + { + flag = true; + } + break; + default: + break; + } + if (_is_dimmer_work == true && flag == true) + { + if (_dimmer_value != 0) + { + if (_dimmer_value >= 100) + { + TIMSK0 &= ~(1 << OCIE0A); + switch (_init_config.triac_port) + { + case AVR_PORTB: + PORTB |= (1 << _init_config.triac_gpio); + _delay_us(ZH_TRIAC_TIME); + PORTB &= ~(1 << _init_config.triac_gpio); + break; + case AVR_PORTC: + PORTC |= (1 << _init_config.triac_gpio); + _delay_us(ZH_TRIAC_TIME); + PORTC &= ~(1 << _init_config.triac_gpio); + case AVR_PORTD: + PORTD |= (1 << _init_config.triac_gpio); + _delay_us(ZH_TRIAC_TIME); + PORTD &= ~(1 << _init_config.triac_gpio); + break; + default: + break; + } + } + else + { + TCNT0 = 0; + TIFR0 = (1 << OCF0A); + TIMSK0 |= (1 << OCIE0A); + } + } + } +} + +static avr_err_t _zh_avr_ac_dimmer_validate_config(const zh_avr_ac_dimmer_init_config_t *config) +{ + ZH_ERROR_CHECK(config != NULL, AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK((config->zero_cross_gpio >= 0 && config->zero_cross_gpio <= 7), AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK((config->triac_gpio >= 0 && config->triac_gpio <= 7), AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK(config->zero_cross_port >= AVR_PORTB && config->zero_cross_port <= AVR_PORTD, AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK(config->triac_port >= AVR_PORTB && config->triac_port <= AVR_PORTD, AVR_ERR_INVALID_ARG); + ZH_ERROR_CHECK(config->ac_dimmer_frequency >= ZH_50HZ && config->ac_dimmer_frequency <= ZH_400HZ, AVR_ERR_INVALID_ARG); + return AVR_OK; +} + +static void _zh_avr_ac_dimmer_gpio_init(void) +{ + switch (_init_config.zero_cross_port) + { + case AVR_PORTB: + DDRB &= ~(1 << _init_config.zero_cross_gpio); + PORTB |= (1 << _init_config.zero_cross_gpio); + PCICR |= (1 << PCIE0); + PCMSK0 |= (1 << _init_config.zero_cross_gpio); + break; + case AVR_PORTC: + DDRC &= ~(1 << _init_config.zero_cross_gpio); + PORTC |= (1 << _init_config.zero_cross_gpio); + PCICR |= (1 << PCIE1); + PCMSK1 |= (1 << _init_config.zero_cross_gpio); + break; + case AVR_PORTD: + DDRD &= ~(1 << _init_config.zero_cross_gpio); + PORTD |= (1 << _init_config.zero_cross_gpio); + PCICR |= (1 << PCIE2); + PCMSK2 |= (1 << _init_config.zero_cross_gpio); + break; + default: + break; + } + switch (_init_config.triac_port) + { + case AVR_PORTB: + DDRB |= (1 << _init_config.triac_gpio); + PORTB &= ~(1 << _init_config.triac_gpio); + break; + case AVR_PORTC: + DDRC |= (1 << _init_config.triac_gpio); + PORTC &= ~(1 << _init_config.triac_gpio); + break; + case AVR_PORTD: + DDRD |= (1 << _init_config.triac_gpio); + PORTD &= ~(1 << _init_config.triac_gpio); + break; + default: + break; + } +} + +static avr_err_t _zh_avr_ac_dimmer_timer_init(void) +{ + TCCR0A |= (1 << WGM01); + switch (F_CPU) + { + case 8000000: + switch (_init_config.ac_dimmer_frequency) + { + case ZH_50HZ: + OCR0A = 99; + TCCR0B |= (1 << CS01); + break; + case ZH_60HZ: + OCR0A = 82; + TCCR0B |= (1 << CS01); + break; + case ZH_400HZ: + OCR0A = 99; + TCCR0B |= (1 << CS00); + break; + default: + break; + } + break; + case 16000000: + switch (_init_config.ac_dimmer_frequency) + { + case ZH_50HZ: + OCR0A = 24; + TCCR0B |= (1 << CS01) | (1 << CS00); + break; + case ZH_60HZ: + OCR0A = 165; + TCCR0B |= (1 << CS01); + break; + case ZH_400HZ: + OCR0A = 24; + TCCR0B |= (1 << CS01); + break; + default: + break; + } + break; + case 20000000: + switch (_init_config.ac_dimmer_frequency) + { + case ZH_50HZ: + OCR0A = 249; + TCCR0B |= (1 << CS01); + break; + case ZH_60HZ: + OCR0A = 207; + TCCR0B |= (1 << CS01); + break; + case ZH_400HZ: + OCR0A = 249; + TCCR0B |= (1 << CS00); + break; + default: + break; + } + break; + default: + return AVR_ERR_INVALID_ARG; + break; + } + return AVR_OK; +} + +ISR(TIMER0_COMPA_vect) +{ + if (_dimmer_count >= (100 - _dimmer_value)) + { + _dimmer_count = 0; + TIMSK0 &= ~(1 << OCIE0A); + switch (_init_config.triac_port) + { + case AVR_PORTB: + PORTB |= (1 << _init_config.triac_gpio); + _delay_us(ZH_TRIAC_TIME); + PORTB &= ~(1 << _init_config.triac_gpio); + break; + case AVR_PORTC: + PORTC |= (1 << _init_config.triac_gpio); + _delay_us(ZH_TRIAC_TIME); + PORTC &= ~(1 << _init_config.triac_gpio); + case AVR_PORTD: + PORTD |= (1 << _init_config.triac_gpio); + _delay_us(ZH_TRIAC_TIME); + PORTD &= ~(1 << _init_config.triac_gpio); + break; + default: + break; + } + } + ++_dimmer_count; +} \ No newline at end of file