diff --git a/Core/Inc/Sound.h b/Core/Inc/Sound.h new file mode 100644 index 0000000..45a067f --- /dev/null +++ b/Core/Inc/Sound.h @@ -0,0 +1,58 @@ +/* + * Sound.h + * + * Created on: Oct 1, 2024 + * Author: DashyFox + */ + +#ifndef INC_SOUND_H_ +#define INC_SOUND_H_ + +#include + +// Define default timer and channels +#define SOUND_TIMER TIM4 +#define SOUND_TIMER_RCC RCC_APB1ENR_TIM4EN +#define SOUND_CHANNEL_1 3 // TIM4 Channel 3 +#define SOUND_CHANNEL_2 4 // TIM4 Channel 4 + +// Define the Note structure +typedef struct { + uint32_t frequency; // Frequency in Hz + uint32_t duration; // Duration in milliseconds, 0 means infinite + uint8_t duty_cycle; // PWM duty cycle in percentage (0-100) +} Note_t; + +// Enum for musical notes +typedef enum { + NOTE_C4 = 261, + NOTE_D4 = 293, + NOTE_E4 = 329, + NOTE_F4 = 349, + NOTE_G4 = 392, + NOTE_A4 = 440, + NOTE_B4 = 493, + NOTE_C5 = 523, + // Add more notes as needed + NOTE_REST = 0 +} MusicalNote_t; + +// Enum for note durations (in fractions of a whole note) +typedef enum { + DURATION_WHOLE = 1, + DURATION_HALF = 2, + DURATION_QUARTER = 4, + DURATION_EIGHTH = 8, + DURATION_SIXTEENTH = 16 + // Add more durations as needed +} NoteDuration_t; + +// Function declarations +void sound_init(void); +void sound_set_bpm(uint32_t bpm); +void sound_play_note(Note_t note, uint8_t channel); +void sound_tick(void); +Note_t sound_create_note(MusicalNote_t note, NoteDuration_t duration, uint8_t duty_cycle); + + +#endif /* INC_SOUND_H_ */ diff --git a/Core/Src/RobotFunctions.c b/Core/Src/RobotFunctions.c index 8fa4076..72412af 100644 --- a/Core/Src/RobotFunctions.c +++ b/Core/Src/RobotFunctions.c @@ -12,6 +12,7 @@ #include "Print.h" #include "SimpleTimer.h" #include "Indicator.h" +#include "Sound.h" #define ballReact_value 10 // время реакции на вылет мяча @@ -86,6 +87,7 @@ void Robot_INIT() { EEPROM_INIT(); setPosDefault(); UART3_START(); + sound_init(); for (int i = 0; i < 10; ++i) { // if ((i&1U)!=1 || i > 4) for test @@ -115,6 +117,7 @@ void BallEXT() { void RobotTick() { BallEXT_Handler(); led_tick(); + sound_tick(); // No Ball Handler if (currentInfo.state == RUN && millis() - noBallTimer > noBallTimeout) { diff --git a/Core/Src/Sound.c b/Core/Src/Sound.c new file mode 100644 index 0000000..375a629 --- /dev/null +++ b/Core/Src/Sound.c @@ -0,0 +1,173 @@ +/* + * Sound.c + * + * Created on: Oct 1, 2024 + * Author: DashyFox + */ + +#include "Sound.h" +#include "stm32f103xb.h" +#include "SimpleTimer.h" + +static uint32_t bpm = 145; // Default BPM +static uint32_t note_end_time_ch3 = 0; // End time for channel 3 +static uint32_t note_end_time_ch4 = 0; // End time for channel 4 +static Note_t current_note_ch3; +static Note_t current_note_ch4; + +// Initialize the sound system +void sound_init(void) { + // Enable timer clock + RCC->APB1ENR |= SOUND_TIMER_RCC; + + // Configure GPIO pins for timer output (assuming GPIOB pins for TIM4 CH3 and CH4) + RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; + + if (SOUND_CHANNEL_1 == 1 || SOUND_CHANNEL_1 == 2) { + GPIOB->CRL &= ~(0xF << ((SOUND_CHANNEL_1 - 1) * 4)); // Clear + GPIOB->CRL |= (0xB << ((SOUND_CHANNEL_1 - 1) * 4)); // Alternate function push-pull + } else if (SOUND_CHANNEL_1 == 3 || SOUND_CHANNEL_1 == 4) { + GPIOB->CRH &= ~(0xF << ((SOUND_CHANNEL_1 - 8) * 4)); // Clear + GPIOB->CRH |= (0xB << ((SOUND_CHANNEL_1 - 8) * 4)); // Alternate function push-pull + } + + if (SOUND_CHANNEL_2 == 1 || SOUND_CHANNEL_2 == 2) { + GPIOB->CRL &= ~(0xF << ((SOUND_CHANNEL_2 - 1) * 4)); // Clear + GPIOB->CRL |= (0xB << ((SOUND_CHANNEL_2 - 1) * 4)); // Alternate function push-pull + } else if (SOUND_CHANNEL_2 == 3 || SOUND_CHANNEL_2 == 4) { + GPIOB->CRH &= ~(0xF << ((SOUND_CHANNEL_2 - 8) * 4)); // Clear + GPIOB->CRH |= (0xB << ((SOUND_CHANNEL_2 - 8) * 4)); // Alternate function push-pull + } + + // Initialize the timer for PWM output + SOUND_TIMER->PSC = 0; // Will be set in sound_play_note() + SOUND_TIMER->ARR = 0xFFFF; // Will be set in sound_play_note() + + // Configure PWM mode on both channels + if (SOUND_CHANNEL_1 == 3) { + SOUND_TIMER->CCMR2 &= ~TIM_CCMR2_OC3M; + SOUND_TIMER->CCMR2 |= (6 << TIM_CCMR2_OC3M_Pos); // PWM mode 1 for channel 3 + SOUND_TIMER->CCER |= TIM_CCER_CC3E; // Enable output on channel 3 + } else if (SOUND_CHANNEL_1 == 4) { + SOUND_TIMER->CCMR2 &= ~TIM_CCMR2_OC4M; + SOUND_TIMER->CCMR2 |= (6 << TIM_CCMR2_OC4M_Pos); // PWM mode 1 for channel 4 + SOUND_TIMER->CCER |= TIM_CCER_CC4E; // Enable output on channel 4 + } + + if (SOUND_CHANNEL_2 == 3) { + SOUND_TIMER->CCMR2 &= ~TIM_CCMR2_OC3M; + SOUND_TIMER->CCMR2 |= (6 << TIM_CCMR2_OC3M_Pos); // PWM mode 1 for channel 3 + SOUND_TIMER->CCER |= TIM_CCER_CC3E; // Enable output on channel 3 + } else if (SOUND_CHANNEL_2 == 4) { + SOUND_TIMER->CCMR2 &= ~TIM_CCMR2_OC4M; + SOUND_TIMER->CCMR2 |= (6 << TIM_CCMR2_OC4M_Pos); // PWM mode 1 for channel 4 + SOUND_TIMER->CCER |= TIM_CCER_CC4E; // Enable output on channel 4 + } + + // Generate an update event to reload the prescaler and ARR values + SOUND_TIMER->EGR = TIM_EGR_UG; + + // Enable the timer + SOUND_TIMER->CR1 |= TIM_CR1_CEN; +} + +// Set BPM +void sound_set_bpm(uint32_t new_bpm) { + bpm = new_bpm; +} + +// Play a note on a specific channel +void sound_play_note(Note_t note, uint8_t channel) { + if (channel == SOUND_CHANNEL_1) { + current_note_ch3 = note; + if (note.frequency == 0) { + // Pause on channel 3 + SOUND_TIMER->CCER &= ~TIM_CCER_CC3E; // Disable output on channel 3 + note_end_time_ch3 = millis() + note.duration; + } else { + // Configure frequency and duty cycle for channel 3 + uint32_t timer_clock = SystemCoreClock / 2; // Assuming APB1 prescaler is 2 + uint32_t prescaler = (timer_clock / (note.frequency * 65536)) + 1; + uint32_t arr = (timer_clock / (prescaler * note.frequency)) - 1; + uint32_t ccr = ((arr + 1) * note.duty_cycle) / 100 - 1; + + SOUND_TIMER->PSC = prescaler - 1; + SOUND_TIMER->ARR = arr; + SOUND_TIMER->CCR3 = ccr; + + // Configure PWM mode for channel 3 + SOUND_TIMER->CCMR2 &= ~(TIM_CCMR2_OC3M | TIM_CCMR2_OC3PE); + SOUND_TIMER->CCMR2 |= (TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3PE); // PWM mode 1 + + SOUND_TIMER->CCER |= TIM_CCER_CC3E; // Enable output on channel 3 + SOUND_TIMER->EGR = TIM_EGR_UG; // Update registers + SOUND_TIMER->CR1 |= TIM_CR1_CEN; // Enable timer + + if (note.duration == 0) { + note_end_time_ch3 = 0; // Infinite duration + } else { + note_end_time_ch3 = millis() + note.duration; + } + } + } else if (channel == SOUND_CHANNEL_2) { + current_note_ch4 = note; + if (note.frequency == 0) { + // Pause on channel 4 + SOUND_TIMER->CCER &= ~TIM_CCER_CC4E; // Disable output on channel 4 + note_end_time_ch4 = millis() + note.duration; + } else { + // Configure frequency and duty cycle for channel 4 + uint32_t timer_clock = SystemCoreClock / 2; // Assuming APB1 prescaler is 2 + uint32_t prescaler = (timer_clock / (note.frequency * 65536)) + 1; + uint32_t arr = (timer_clock / (prescaler * note.frequency)) - 1; + uint32_t ccr = ((arr + 1) * note.duty_cycle) / 100 - 1; + + SOUND_TIMER->PSC = prescaler - 1; + SOUND_TIMER->ARR = arr; + SOUND_TIMER->CCR4 = ccr; + + // Configure PWM mode for channel 4 + SOUND_TIMER->CCMR2 &= ~(TIM_CCMR2_OC4M | TIM_CCMR2_OC4PE); + SOUND_TIMER->CCMR2 |= (TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4PE); // PWM mode 1 + + SOUND_TIMER->CCER |= TIM_CCER_CC4E; // Enable output on channel 4 + SOUND_TIMER->EGR = TIM_EGR_UG; // Update registers + SOUND_TIMER->CR1 |= TIM_CR1_CEN; // Enable timer + + if (note.duration == 0) { + note_end_time_ch4 = 0; // Infinite duration + } else { + note_end_time_ch4 = millis() + note.duration; + } + } + } +} + +// Function to be called periodically to handle note duration +void sound_tick(void) { + if (note_end_time_ch3 != 0 && millis() >= note_end_time_ch3) { + // Stop the note on channel 3 + SOUND_TIMER->CCER &= ~TIM_CCER_CC3E; // Disable output on channel 3 + note_end_time_ch3 = 0; + } + if (note_end_time_ch4 != 0 && millis() >= note_end_time_ch4) { + // Stop the note on channel 4 + SOUND_TIMER->CCER &= ~TIM_CCER_CC4E; // Disable output on channel 4 + note_end_time_ch4 = 0; + } +} + +// Create a Note_t from musical note and duration enums +Note_t sound_create_note(MusicalNote_t note_enum, NoteDuration_t duration_enum, uint8_t duty_cycle) { + Note_t result_note; + result_note.frequency = note_enum; + if (duration_enum == 0) { + result_note.duration = 0; // Infinite duration + } else { + // Calculate duration in milliseconds + uint32_t whole_note_duration = (60000 / bpm) * 4; // Duration of whole note in ms + result_note.duration = whole_note_duration / duration_enum; + } + result_note.duty_cycle = duty_cycle; + return result_note; +} diff --git a/Core/Src/main.c b/Core/Src/main.c index 3f88bf5..f7fadb7 100644 --- a/Core/Src/main.c +++ b/Core/Src/main.c @@ -33,6 +33,7 @@ #include "RobotFunctions.h" #include "ShiftReg.h" #include "UART3_Handler.h" +#include "Sound.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ @@ -58,6 +59,7 @@ IWDG_HandleTypeDef hiwdg; TIM_HandleTypeDef htim1; TIM_HandleTypeDef htim2; TIM_HandleTypeDef htim3; +TIM_HandleTypeDef htim4; UART_HandleTypeDef huart3; DMA_HandleTypeDef hdma_usart3_rx; @@ -96,6 +98,7 @@ static void MX_TIM2_Init(void); static void MX_TIM3_Init(void); static void MX_USART3_UART_Init(void); static void MX_IWDG_Init(void); +static void MX_TIM4_Init(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ @@ -148,6 +151,7 @@ int main(void) MX_TIM3_Init(); MX_USART3_UART_Init(); MX_IWDG_Init(); + MX_TIM4_Init(); /* USER CODE BEGIN 2 */ __HAL_RCC_USART3_CLK_ENABLE(); @@ -210,7 +214,7 @@ int main(void) UART3_Handler(); RobotTick(); - forTimer(blinkTimer, 500) { + forTimer(blinkTimer, 1500) { resetForTimer(blinkTimer); GPIOC->ODR ^= GPIO_PIN_13; // unsigned char text[] = "Hello\n"; @@ -235,6 +239,11 @@ int main(void) getStateString(currentInfo.state)); CDC_Transmit_FS((uint8_t*)buffer, strlen(buffer)); + + Note_t note = sound_create_note(NOTE_C4, DURATION_EIGHTH, 50); + sound_play_note(note, SOUND_CHANNEL_1); + + } /* USER CODE END WHILE */ @@ -344,10 +353,10 @@ static void MX_IWDG_Init(void) hiwdg.Instance = IWDG; hiwdg.Init.Prescaler = IWDG_PRESCALER_64; hiwdg.Init.Reload = 625*3; -// if (HAL_IWDG_Init(&hiwdg) != HAL_OK) -// { -// Error_Handler(); -// } + if (HAL_IWDG_Init(&hiwdg) != HAL_OK) + { + Error_Handler(); + } /* USER CODE BEGIN IWDG_Init 2 */ /* USER CODE END IWDG_Init 2 */ @@ -566,6 +575,59 @@ static void MX_TIM3_Init(void) } +/** + * @brief TIM4 Initialization Function + * @param None + * @retval None + */ +static void MX_TIM4_Init(void) +{ + + /* USER CODE BEGIN TIM4_Init 0 */ + + /* USER CODE END TIM4_Init 0 */ + + TIM_MasterConfigTypeDef sMasterConfig = {0}; + TIM_OC_InitTypeDef sConfigOC = {0}; + + /* USER CODE BEGIN TIM4_Init 1 */ + + /* USER CODE END TIM4_Init 1 */ + htim4.Instance = TIM4; + htim4.Init.Prescaler = 0; + htim4.Init.CounterMode = TIM_COUNTERMODE_UP; + htim4.Init.Period = 65535; + htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; + htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; + if (HAL_TIM_PWM_Init(&htim4) != HAL_OK) + { + Error_Handler(); + } + sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; + sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; + if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK) + { + Error_Handler(); + } + sConfigOC.OCMode = TIM_OCMODE_PWM1; + sConfigOC.Pulse = 0; + sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; + sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; + if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_3) != HAL_OK) + { + Error_Handler(); + } + if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_4) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN TIM4_Init 2 */ + + /* USER CODE END TIM4_Init 2 */ + HAL_TIM_MspPostInit(&htim4); + +} + /** * @brief USART3 Initialization Function * @param None diff --git a/Core/Src/stm32f1xx_hal_msp.c b/Core/Src/stm32f1xx_hal_msp.c index cfcbe37..da8c22b 100644 --- a/Core/Src/stm32f1xx_hal_msp.c +++ b/Core/Src/stm32f1xx_hal_msp.c @@ -61,7 +61,7 @@ extern DMA_HandleTypeDef hdma_usart3_rx; /* USER CODE END 0 */ void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim); - /** + /** * Initializes the Global MSP. */ void HAL_MspInit(void) @@ -218,6 +218,29 @@ void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base) } +/** +* @brief TIM_PWM MSP Initialization +* This function configures the hardware resources used in this example +* @param htim_pwm: TIM_PWM handle pointer +* @retval None +*/ +void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim_pwm) +{ + if(htim_pwm->Instance==TIM4) + { + /* USER CODE BEGIN TIM4_MspInit 0 */ + + /* USER CODE END TIM4_MspInit 0 */ + /* Peripheral clock enable */ + __HAL_RCC_TIM4_CLK_ENABLE(); + /* USER CODE BEGIN TIM4_MspInit 1 */ + + /* USER CODE END TIM4_MspInit 1 */ + + } + +} + void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim) { GPIO_InitTypeDef GPIO_InitStruct = {0}; @@ -262,6 +285,26 @@ void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim) /* USER CODE END TIM2_MspPostInit 1 */ } + else if(htim->Instance==TIM4) + { + /* USER CODE BEGIN TIM4_MspPostInit 0 */ + + /* USER CODE END TIM4_MspPostInit 0 */ + + __HAL_RCC_GPIOB_CLK_ENABLE(); + /**TIM4 GPIO Configuration + PB8 ------> TIM4_CH3 + PB9 ------> TIM4_CH4 + */ + GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + + /* USER CODE BEGIN TIM4_MspPostInit 1 */ + + /* USER CODE END TIM4_MspPostInit 1 */ + } } /** @@ -317,6 +360,28 @@ void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* htim_base) } +/** +* @brief TIM_PWM MSP De-Initialization +* This function freeze the hardware resources used in this example +* @param htim_pwm: TIM_PWM handle pointer +* @retval None +*/ +void HAL_TIM_PWM_MspDeInit(TIM_HandleTypeDef* htim_pwm) +{ + if(htim_pwm->Instance==TIM4) + { + /* USER CODE BEGIN TIM4_MspDeInit 0 */ + + /* USER CODE END TIM4_MspDeInit 0 */ + /* Peripheral clock disable */ + __HAL_RCC_TIM4_CLK_DISABLE(); + /* USER CODE BEGIN TIM4_MspDeInit 1 */ + + /* USER CODE END TIM4_MspDeInit 1 */ + } + +} + /** * @brief UART MSP Initialization * This function configures the hardware resources used in this example diff --git a/StackSport.ioc b/StackSport.ioc index 6e67818..0dd575e 100644 --- a/StackSport.ioc +++ b/StackSport.ioc @@ -23,8 +23,9 @@ Mcu.CPN=STM32F103C8T6 Mcu.Family=STM32F1 Mcu.IP0=DMA Mcu.IP1=I2C1 -Mcu.IP10=USB -Mcu.IP11=USB_DEVICE +Mcu.IP10=USART3 +Mcu.IP11=USB +Mcu.IP12=USB_DEVICE Mcu.IP2=IWDG Mcu.IP3=NVIC Mcu.IP4=RCC @@ -32,8 +33,8 @@ Mcu.IP5=SYS Mcu.IP6=TIM1 Mcu.IP7=TIM2 Mcu.IP8=TIM3 -Mcu.IP9=USART3 -Mcu.IPNb=12 +Mcu.IP9=TIM4 +Mcu.IPNb=13 Mcu.Name=STM32F103C(8-B)Tx Mcu.Package=LQFP48 Mcu.Pin0=PC13-TAMPER-RTC @@ -52,20 +53,22 @@ Mcu.Pin2=PD1-OSC_OUT Mcu.Pin20=PA14 Mcu.Pin21=PB6 Mcu.Pin22=PB7 -Mcu.Pin23=VP_IWDG_VS_IWDG -Mcu.Pin24=VP_SYS_VS_Systick -Mcu.Pin25=VP_TIM1_VS_ClockSourceINT -Mcu.Pin26=VP_TIM2_VS_ClockSourceINT -Mcu.Pin27=VP_TIM3_VS_ClockSourceINT -Mcu.Pin28=VP_USB_DEVICE_VS_USB_DEVICE_CDC_FS +Mcu.Pin23=PB8 +Mcu.Pin24=PB9 +Mcu.Pin25=VP_IWDG_VS_IWDG +Mcu.Pin26=VP_SYS_VS_Systick +Mcu.Pin27=VP_TIM1_VS_ClockSourceINT +Mcu.Pin28=VP_TIM2_VS_ClockSourceINT +Mcu.Pin29=VP_TIM3_VS_ClockSourceINT Mcu.Pin3=PA0-WKUP +Mcu.Pin30=VP_USB_DEVICE_VS_USB_DEVICE_CDC_FS Mcu.Pin4=PA1 Mcu.Pin5=PA2 Mcu.Pin6=PA3 Mcu.Pin7=PA4 Mcu.Pin8=PA5 Mcu.Pin9=PA6 -Mcu.PinsNb=29 +Mcu.PinsNb=31 Mcu.ThirdPartyNb=0 Mcu.UserConstants= Mcu.UserName=STM32F103C8Tx @@ -167,6 +170,8 @@ PB7.GPIOParameters=GPIO_Label PB7.GPIO_Label=SDA PB7.Mode=I2C PB7.Signal=I2C1_SDA +PB8.Signal=S_TIM4_CH3 +PB9.Signal=S_TIM4_CH4 PC13-TAMPER-RTC.GPIOParameters=GPIO_Speed,PinState PC13-TAMPER-RTC.GPIO_Speed=GPIO_SPEED_FREQ_LOW PC13-TAMPER-RTC.Locked=true @@ -248,6 +253,10 @@ SH.S_TIM3_CH1.0=TIM3_CH1,Input_Capture1_from_TI1 SH.S_TIM3_CH1.ConfNb=1 SH.S_TIM3_CH2.0=TIM3_CH2,Input_Capture2_from_TI2 SH.S_TIM3_CH2.ConfNb=1 +SH.S_TIM4_CH3.0=TIM4_CH3,PWM Generation3 CH3 +SH.S_TIM4_CH3.ConfNb=1 +SH.S_TIM4_CH4.0=TIM4_CH4,PWM Generation4 CH4 +SH.S_TIM4_CH4.ConfNb=1 TIM1.Channel-PWM\ Generation1\ CH1=TIM_CHANNEL_1 TIM1.Channel-PWM\ Generation2\ CH2=TIM_CHANNEL_2 TIM1.IPParameters=Channel-PWM Generation1 CH1,Channel-PWM Generation2 CH2,Period @@ -263,6 +272,9 @@ TIM3.Channel-Input_Capture2_from_TI2=TIM_CHANNEL_2 TIM3.IPParameters=Channel-Input_Capture1_from_TI1,Channel-Input_Capture2_from_TI2,Prescaler,Period TIM3.Period=65000 TIM3.Prescaler=47 +TIM4.Channel-PWM\ Generation3\ CH3=TIM_CHANNEL_3 +TIM4.Channel-PWM\ Generation4\ CH4=TIM_CHANNEL_4 +TIM4.IPParameters=Channel-PWM Generation3 CH3,Channel-PWM Generation4 CH4 USART3.BaudRate=9600 USART3.IPParameters=VirtualMode,BaudRate USART3.VirtualMode=VM_ASYNC