From 2329a7cf733da2d3eb9574a0162c8c5adf043eeb Mon Sep 17 00:00:00 2001 From: XiongYu Date: Wed, 8 Aug 2018 11:12:07 +0800 Subject: [PATCH] refactor(hw_timer): Refactor hw_timer driver for esp8266 idf --- components/esp8266/driver/hw_timer.c | 245 ++++++++++++++++++ components/esp8266/include/driver/hw_timer.h | 198 ++++++++++++++ .../esp8266/include/esp8266/timer_struct.h | 70 +++++ components/esp8266/ld/esp8266.peripherals.ld | 4 +- examples/peripherals/hw_timer/Makefile | 9 + examples/peripherals/hw_timer/README.md | 56 ++++ .../peripherals/hw_timer/main/component.mk | 3 + .../hw_timer/main/hw_timer_example_main.c | 91 +++++++ examples/peripherals/hw_timer/wave.png | Bin 0 -> 895 bytes 9 files changed, 675 insertions(+), 1 deletion(-) create mode 100644 components/esp8266/driver/hw_timer.c create mode 100644 components/esp8266/include/driver/hw_timer.h create mode 100644 components/esp8266/include/esp8266/timer_struct.h create mode 100644 examples/peripherals/hw_timer/Makefile create mode 100644 examples/peripherals/hw_timer/README.md create mode 100644 examples/peripherals/hw_timer/main/component.mk create mode 100644 examples/peripherals/hw_timer/main/hw_timer_example_main.c create mode 100644 examples/peripherals/hw_timer/wave.png diff --git a/components/esp8266/driver/hw_timer.c b/components/esp8266/driver/hw_timer.c new file mode 100644 index 00000000..2416e0b4 --- /dev/null +++ b/components/esp8266/driver/hw_timer.c @@ -0,0 +1,245 @@ +// Copyright 2018-2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +#include +#include +#include + +#include "FreeRTOS.h" + +#include "rom/ets_sys.h" +#include "esp8266/eagle_soc.h" +#include "esp8266/timer_struct.h" + +#include "esp_heap_caps.h" +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_log.h" + +#include "driver/hw_timer.h" + +#define ENTER_CRITICAL() portENTER_CRITICAL() +#define EXIT_CRITICAL() portEXIT_CRITICAL() + +static const char *TAG = "hw_timer"; + +#define HW_TIMER_CHECK(a, str, ret_val) \ + if (!(a)) { \ + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret_val); \ + } + +#define hw_timer_intr_enable() _xt_isr_unmask(1 << ETS_FRC_TIMER1_INUM) + +#define hw_timer_intr_disable() _xt_isr_mask(1 << ETS_FRC_TIMER1_INUM) + +#define hw_timer_intr_register(a, b) _xt_isr_attach(ETS_FRC_TIMER1_INUM, (a), (b)) + + +typedef struct { + hw_timer_callback_t cb; +} hw_timer_obj_t; + +hw_timer_obj_t *hw_timer_obj = NULL; + +esp_err_t hw_timer_set_clkdiv(hw_timer_clkdiv_t clkdiv) +{ + HW_TIMER_CHECK(hw_timer_obj, "hw_timer has not been initialized yet", ESP_FAIL); + HW_TIMER_CHECK(clkdiv >= TIMER_CLKDIV_1 && clkdiv <= TIMER_CLKDIV_256, "clkdiv is out of range.", ESP_ERR_INVALID_ARG); + + ENTER_CRITICAL(); + frc1.ctrl.div = clkdiv; + EXIT_CRITICAL(); + + return ESP_OK; +} + +uint32_t hw_timer_get_clkdiv() +{ + return frc1.ctrl.div; +} + +esp_err_t hw_timer_set_intr_type(hw_timer_intr_type_t intr_type) +{ + HW_TIMER_CHECK(hw_timer_obj, "hw_timer has not been initialized yet", ESP_FAIL); + HW_TIMER_CHECK(intr_type >= TIMER_EDGE_INT && intr_type <= TIMER_LEVEL_INT, "intr_type is out of range.", ESP_ERR_INVALID_ARG); + + ENTER_CRITICAL(); + frc1.ctrl.intr_type = intr_type; + EXIT_CRITICAL(); + + return ESP_OK; +} + +uint32_t hw_timer_get_intr_type() +{ + return frc1.ctrl.intr_type; +} + +esp_err_t hw_timer_set_reload(bool reload) +{ + HW_TIMER_CHECK(hw_timer_obj, "hw_timer has not been initialized yet", ESP_FAIL); + + ENTER_CRITICAL(); + if (true == reload) { + frc1.ctrl.reload = 0x01; + } else { + frc1.ctrl.reload = 0x00; + } + EXIT_CRITICAL(); + + return ESP_OK; + +} + +bool hw_timer_get_reload() +{ + if (frc1.ctrl.reload) { + return true; + } else { + return false; + } +} + +esp_err_t hw_timer_enable(bool en) +{ + HW_TIMER_CHECK(hw_timer_obj, "hw_timer has not been initialized yet", ESP_FAIL); + + ENTER_CRITICAL(); + if (true == en) { + frc1.ctrl.en = 0x01; + } else { + frc1.ctrl.en = 0x00; + } + EXIT_CRITICAL(); + + return ESP_OK; +} + +bool hw_timer_get_enable() +{ + if (frc1.ctrl.en) { + return true; + } else { + return false; + } +} + +esp_err_t hw_timer_set_load_data(uint32_t load_data) +{ + HW_TIMER_CHECK(hw_timer_obj, "hw_timer has not been initialized yet", ESP_FAIL); + HW_TIMER_CHECK(load_data < 0x1000000, "load_data is out of range.", ESP_ERR_INVALID_ARG); + + ENTER_CRITICAL(); + frc1.load.data = load_data; + EXIT_CRITICAL(); + + return ESP_OK; +} + +uint32_t hw_timer_get_load_data() +{ + return frc1.load.data; +} + +uint32_t hw_timer_get_count_data() +{ + return frc1.count.data; +} + +static void hw_timer_isr_cb(void* arg) +{ + if (!frc1.ctrl.reload) { + frc1.ctrl.en = 0; + } + if (hw_timer_obj->cb != NULL) { + hw_timer_obj->cb(arg); + } +} + +esp_err_t hw_timer_disarm(void) +{ + HW_TIMER_CHECK(hw_timer_obj, "hw_timer has not been initialized yet", ESP_FAIL); + + frc1.ctrl.val = 0; + + return ESP_OK; +} + +esp_err_t hw_timer_alarm_us(uint32_t value, bool reload) +{ + HW_TIMER_CHECK(hw_timer_obj, "hw_timer has not been initialized yet", ESP_FAIL); + HW_TIMER_CHECK( (reload ? ((value > 50) ? 1 : 0) : ((value > 10) ? 1 : 0)) && (value <= 0x199999), "value is out of range.", ESP_ERR_INVALID_ARG); + + hw_timer_set_reload(reload); + hw_timer_set_clkdiv(TIMER_CLKDIV_16); + hw_timer_set_intr_type(TIMER_EDGE_INT); + hw_timer_set_load_data(((TIMER_BASE_CLK >> hw_timer_get_clkdiv()) / 1000000) * value); // Calculate the number of timer clocks required for timing, ticks = (80MHz / div) * t + hw_timer_enable(true); + + return ESP_OK; +} + +static void hw_timer_obj_delete(void) +{ + if (NULL == hw_timer_obj) { + return; + } + hw_timer_obj->cb = NULL; + heap_caps_free(hw_timer_obj); + hw_timer_obj = NULL; +} + +static esp_err_t hw_timer_obj_create(void) +{ + hw_timer_obj = (hw_timer_obj_t *)heap_caps_malloc(sizeof(hw_timer_obj_t), MALLOC_CAP_8BIT); + + if (NULL == hw_timer_obj) { + hw_timer_obj_delete(); + return ESP_FAIL; + } + hw_timer_obj->cb = NULL; + + return ESP_OK; +} + +esp_err_t hw_timer_deinit(void) +{ + HW_TIMER_CHECK(hw_timer_obj, "hw_timer has not been initialized yet", ESP_FAIL); + + hw_timer_disarm(); + hw_timer_intr_disable(); + TM1_EDGE_INT_DISABLE(); + hw_timer_intr_register(NULL, NULL); + hw_timer_obj_delete(); + + return ESP_OK; +} + +esp_err_t hw_timer_init(hw_timer_callback_t callback, void *arg) +{ + HW_TIMER_CHECK(hw_timer_obj == NULL, "hw_timer has been initialized", ESP_FAIL); + HW_TIMER_CHECK(callback != NULL, "callback pointer NULL", ESP_ERR_INVALID_ARG); + + if (ESP_FAIL == hw_timer_obj_create()) { + return ESP_FAIL; + } + hw_timer_obj->cb = callback; + hw_timer_intr_register(hw_timer_isr_cb, arg); + TM1_EDGE_INT_ENABLE(); + hw_timer_intr_enable(); + + return ESP_OK; +} \ No newline at end of file diff --git a/components/esp8266/include/driver/hw_timer.h b/components/esp8266/include/driver/hw_timer.h new file mode 100644 index 00000000..a6e328e5 --- /dev/null +++ b/components/esp8266/include/driver/hw_timer.h @@ -0,0 +1,198 @@ +// Copyright 2018-2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define TIMER_BASE_CLK APB_CLK_FREQ + +typedef void (*hw_timer_callback_t)(void *arg); + +typedef enum { + TIMER_CLKDIV_1 = 0, + TIMER_CLKDIV_16 = 4, + TIMER_CLKDIV_256 = 8 +} hw_timer_clkdiv_t; + +typedef enum { + TIMER_EDGE_INT = 0, // edge interrupt + TIMER_LEVEL_INT = 1 // level interrupt +} hw_timer_intr_type_t; + +/** + * @brief Set the frequency division coefficient of hardware timer + * + * @param clkdiv frequency division coefficient + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL hardware timer has been initialized + */ +esp_err_t hw_timer_set_clkdiv(hw_timer_clkdiv_t clkdiv); + +/** + * @brief Get the frequency division coefficient of hardware timer + * + * @return + * - 0 TIMER_CLKDIV_1 + * - 4 TIMER_CLKDIV_16 + * - 8 TIMER_CLKDIV_256 + */ +uint32_t hw_timer_get_clkdiv(); + +/** + * @brief Set the interrupt type of hardware timer + * + * @param intr_type interrupt type + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL hardware timer has been initialized + */ +esp_err_t hw_timer_set_intr_type(hw_timer_intr_type_t intr_type); + +/** + * @brief Get the interrupt type of hardware timer + * + * @return + * - 0 TIMER_EDGE_INT + * - 1 TIMER_LEVEL_INT + */ +uint32_t hw_timer_get_intr_type(); + +/** + * @brief Enable hardware timer reload + * + * @param reload false, one-shot mode; true, reload mode + * + * @return + * - ESP_OK Success + * - ESP_FAIL hardware timer has been initialized + */ +esp_err_t hw_timer_set_reload(bool reload); + +/** + * @brief Get the hardware timer reload status + * + * @return + * - true reload mode + * - false one-shot mode + */ +bool hw_timer_get_reload(); + +/** + * @brief Enable hardware timer + * + * @param en false, hardware timer disable; true, hardware timer enable + * + * @return + * - ESP_OK Success + * - ESP_FAIL hardware timer has been initialized + */ +esp_err_t hw_timer_enable(bool en); + +/** + * @brief Get the hardware timer enable status + * + * @return + * - true hardware timer has been enabled + * - false hardware timer is not yet enabled + */ +bool hw_timer_get_enable(); + +/** + * @brief Set the hardware timer load value + * + * @param load_data hardware timer load value + * - FRC1 hardware timer, range : less than 0x1000000 + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL hardware timer has been initialized + */ +esp_err_t hw_timer_set_load_data(uint32_t load_data); + +/** + * @brief Get the hardware timer load value + * + * @return load value + */ +uint32_t hw_timer_get_load_data(); + +/** + * @brief Get the hardware timer count value + * + * @return count value + */ +uint32_t hw_timer_get_count_data(); + +/** + * @brief deinit the hardware timer + * + * @return + * - ESP_OK Success + * - ESP_FAIL hardware timer has not been initialized yet + */ +esp_err_t hw_timer_deinit(void); + +/** + * @brief Initialize the hardware timer + * + * @param callback user hardware timer callback function + * @param arg parameter for ISR handler + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL hardware timer has been initialized + */ +esp_err_t hw_timer_init(hw_timer_callback_t callback, void *arg); + +/** + * @brief Set a trigger timer us delay to enable this timer + * + * @param value + * - If reload is true, range : 50 ~ 0x199999 + * - If reload is false, range : 10 ~ 0x199999 + * @param reload false, one-shot mode; true, reload mode. + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL hardware timer has not been initialized yet + */ + +esp_err_t hw_timer_alarm_us(uint32_t value, bool reload); + +/** + * @brief disable this timer + * + * @return + * - ESP_OK Success + * - ESP_FAIL hardware timer has not been initialized yet + */ +esp_err_t hw_timer_disarm(void); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/components/esp8266/include/esp8266/timer_struct.h b/components/esp8266/include/esp8266/timer_struct.h new file mode 100644 index 00000000..518c5b0a --- /dev/null +++ b/components/esp8266/include/esp8266/timer_struct.h @@ -0,0 +1,70 @@ +// Copyright 2018-2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include "esp8266/eagle_soc.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/* ESP8266 FRC Register Definitions */ + +// FRC1 is a 24-bit countdown timer, triggers interrupt when reaches zero. +typedef struct { + union { + struct { + uint32_t data: 23; + uint32_t reserved23: 9; + }; + uint32_t val; + } load; + + union { + struct { + uint32_t data: 23; + uint32_t reserved23: 9; + }; + uint32_t val; + } count; + + union { + struct { + uint32_t div: 6; + uint32_t reload: 1; + uint32_t en: 1; + uint32_t intr_type: 1; + uint32_t reserved24: 23; + }; + uint32_t val; + } ctrl; + + union { + struct { + uint32_t clr: 1; + uint32_t reserved1: 31; + }; + uint32_t val; + } intr; +} frc1_struct_t; + +extern volatile frc1_struct_t frc1; + +#ifdef __cplusplus +} +#endif /* end of __cplusplus */ + diff --git a/components/esp8266/ld/esp8266.peripherals.ld b/components/esp8266/ld/esp8266.peripherals.ld index 57329602..b466b3ac 100644 --- a/components/esp8266/ld/esp8266.peripherals.ld +++ b/components/esp8266/ld/esp8266.peripherals.ld @@ -1,4 +1,6 @@ PROVIDE ( GPIO = 0x60000300); PROVIDE ( uart0 = 0x60000000 ); -PROVIDE ( uart1 = 0x60000f00 ); \ No newline at end of file +PROVIDE ( uart1 = 0x60000f00 ); + +PROVIDE ( frc1 = 0x60000600 ); \ No newline at end of file diff --git a/examples/peripherals/hw_timer/Makefile b/examples/peripherals/hw_timer/Makefile new file mode 100644 index 00000000..200c2d39 --- /dev/null +++ b/examples/peripherals/hw_timer/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := hw_timer + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/peripherals/hw_timer/README.md b/examples/peripherals/hw_timer/README.md new file mode 100644 index 00000000..441ffd4c --- /dev/null +++ b/examples/peripherals/hw_timer/README.md @@ -0,0 +1,56 @@ +# _HW_TIMER Example_ + +* This example will show you how to use hw_timer by led or logic analyzer: + * Using hw_timer to generate waveforms of different frequencies + * Observe the waveform with led or logic analyzer. + +## How to use example + +### Hardware Required + + * Connect GPIO12 with led1 + * Connect GPIO15 with led2 + +### Configure the project + +``` +make menuconfig +``` + +* Set serial port under Serial Flasher Options. + + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +make -j4 flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +## Example Output + +* LOG: + +``` +I (228) hw_timer_example: Config gpio +I (220) gpio: GPIO[12]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (224) gpio: GPIO[15]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (248) hw_timer_example: Initialize hw_timer for callback1 +I (258) hw_timer_example: Set hw_timer timing time 100us with reload +I (1268) hw_timer_example: Deinitialize hw_timer for callback1 +I (1261) hw_timer_example: Initialize hw_timer for callback2 +I (1264) hw_timer_example: Set hw_timer timing time 1ms with reload +I (2278) hw_timer_example: Set hw_timer timing time 10ms with reload +I (4278) hw_timer_example: Set hw_timer timing time 100ms with reload +I (7278) hw_timer_example: Cancel timing +I (7270) hw_timer_example: Initialize hw_timer for callback3 +I (7272) hw_timer_example: Set hw_timer timing time 1ms with one-shot + +``` + +* WAVE FORM: + + ![wave](wave.png) \ No newline at end of file diff --git a/examples/peripherals/hw_timer/main/component.mk b/examples/peripherals/hw_timer/main/component.mk new file mode 100644 index 00000000..44bd2b52 --- /dev/null +++ b/examples/peripherals/hw_timer/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/peripherals/hw_timer/main/hw_timer_example_main.c b/examples/peripherals/hw_timer/main/hw_timer_example_main.c new file mode 100644 index 00000000..826fbd04 --- /dev/null +++ b/examples/peripherals/hw_timer/main/hw_timer_example_main.c @@ -0,0 +1,91 @@ +/* hw_timer example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" + +#include "esp_log.h" + +#include "driver/gpio.h" +#include "driver/hw_timer.h" + +static const char *TAG = "hw_timer_example"; + +#define TEST_ONE_SHOT false // testing will be done without auto reload (one-shot) +#define TEST_RELOAD true // testing will be done with auto reload + +#define GPIO_OUTPUT_IO_0 12 +#define GPIO_OUTPUT_IO_1 15 +#define GPIO_OUTPUT_PIN_SEL ((1ULL << GPIO_OUTPUT_IO_0) | (1ULL << GPIO_OUTPUT_IO_1)) + +void hw_timer_callback1(void *arg) +{ + static int state = 0; + + gpio_set_level(GPIO_OUTPUT_IO_0, (state ++) % 2); +} + +void hw_timer_callback2(void *arg) +{ + static int state = 0; + + gpio_set_level(GPIO_OUTPUT_IO_1, (state ++) % 2); +} + +void hw_timer_callback3(void *arg) +{ + gpio_set_level(GPIO_OUTPUT_IO_0, 0); + gpio_set_level(GPIO_OUTPUT_IO_1, 1); +} + +void app_main(void) +{ + ESP_LOGI(TAG, "Config gpio"); + gpio_config_t io_conf; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL; + io_conf.pull_down_en = 0; + io_conf.pull_up_en = 0; + gpio_config(&io_conf); + + ESP_LOGI(TAG, "Initialize hw_timer for callback1"); + hw_timer_init(hw_timer_callback1, NULL); + ESP_LOGI(TAG, "Set hw_timer timing time 100us with reload"); + hw_timer_alarm_us(100, TEST_RELOAD); + vTaskDelay(1000 / portTICK_RATE_MS); + ESP_LOGI(TAG, "Deinitialize hw_timer for callback1"); + hw_timer_deinit(); + + ESP_LOGI(TAG, "Initialize hw_timer for callback2"); + hw_timer_init(hw_timer_callback2, NULL); + ESP_LOGI(TAG, "Set hw_timer timing time 1ms with reload"); + hw_timer_alarm_us(1000, TEST_RELOAD); + vTaskDelay(1000 / portTICK_RATE_MS); + ESP_LOGI(TAG, "Set hw_timer timing time 10ms with reload"); + hw_timer_alarm_us(10000, TEST_RELOAD); + vTaskDelay(2000 / portTICK_RATE_MS); + ESP_LOGI(TAG, "Set hw_timer timing time 100ms with reload"); + hw_timer_alarm_us(100000, TEST_RELOAD); + vTaskDelay(3000 / portTICK_RATE_MS); + ESP_LOGI(TAG, "Cancel timing"); + hw_timer_disarm(); + hw_timer_deinit(); + + ESP_LOGI(TAG, "Initialize hw_timer for callback3"); + hw_timer_init(hw_timer_callback3, NULL); + + ESP_LOGI(TAG, "Set hw_timer timing time 1ms with one-shot"); + hw_timer_alarm_us(1000, TEST_ONE_SHOT); +} + + diff --git a/examples/peripherals/hw_timer/wave.png b/examples/peripherals/hw_timer/wave.png new file mode 100644 index 0000000000000000000000000000000000000000..ea148f99b96c71d25e2d8c39cf2675ef03dbf83f GIT binary patch literal 895 zcmeAS@N?(olHy`uVBq!ia0y~yU^N1=y*QYFBzv_D2asYecJd72;NZCZ(EkCDXYT3Z z7*a9k?X5t++W`Ws0n1Mbov@k{depk>NzO#QMe@GNELXVvZSEhmd!Ehk`Yg}Jg7vQs zSIV#hO;UImUInBWZs>260a8gvihyK?;Auvnl$#DvMZ=88xApZdPnp#ml=pd#98f$# z{7=3mP}Px+r$ACbc^WeVgF{aQACP01gkR!L^3RuFCe3ZmlH1#wus0bR&Y_NqYc_!k}^YeR5c637=4xV%Sw ziN2SND_$G#Q()>D`^yK#KrEjV#Zr-2Dzv#rn_*WJ+v9}*DUU=QA zcg^JssaxHyWxm)HwfIzQSoW5Ur7zrd*#BiqUtIC};q4Q#(iV5vFP6?_lm0z@MXA)T zHP=p9@dDL_Y(AaUYqpIqwz5+e=v6FnhtqR2s&(w=#))+Kb62E4yjSt;pJw&hrmV%^ zqMy&qUK8_t7VCXGx7*E&Pc)ygy7p7EwNy*|T40IpyN!Roe%FVckENw zM+W3xse5fx+&lGk%;pETk7raDh*VC9JZ@#bbc5g1LsnhuTBqMYBqp2=LXohHTc&Bt z9B}LL`XjgP#n0D&e(Y~vRuO%CUHkOEu|E~MWbR$txczF?+RG=brbxZ0f(c2nixD?{an^LB{Ts5s5UrV literal 0 HcmV?d00001