prettyprint

2020年12月3日 星期四

STM32F103C8T6 Blue Pill and Black Pill開發板RTC模組精準度與校正探討

 本實驗主要探討STM32F103C8T6 RTC模組在使用不同Clock source精準度探討,並比較兩種常用開發版(Blue Pill 與 Black Pill)的差異。

參考資料AN2604 Application note: STM32F101xx and STM32F103xx RTC calibration

使用元件:

  1. STM32F103C8T6 Blue Pill x1
  2. STM32F103C8T6 Black Pill x1
測量模組:
  1. 使用STM32F103C8T6 TIMER Input Capture 偵測輸入方波的頻率與寬度(https://rfwumcu.blogspot.com/2020/11/stm32f103c8t6-timer-input-capture.html)
實作影片:




實驗步驟:
  • 測量兩種開發板不同RTC Clock Source(LSE, HSE, LSI)的頻率。
        RTC可使用三種時鐘來源:外部震盪器RTC_HSE(8M Hz/128=62.5K Hz)、LSE(32.768K Hz)與內部RC震盪器LSI(40K Hz)。



STM32CubeIDE RCC設定畫面要開啟HSE與LSE



RTC開啟RTC OUT,把PC13接到測量模組。此時測得的頻率為除64的值。

因為Blue Pill開發板,PC13接到板子內建的LED,因此當選用LSE clock source測量數值一直大幅飄動,當把板子上的LED移除掉,則可測到穩定值。

實驗數值影片
Blue Pill
HSE clock source



LSE clock source
未移除PC13 LED


移除PC13 LED


LSI  clock source




Black Pill
HSE clock source


LSE clock source





LSI  clock source



實驗結果
Blue Pill:
  • HSE clock source:平均為976.63525,HSE clock frequency = 976.63525x64=62504.656,推算每天約快6.4秒。
  • LSE clock source: 如影片平均值為511.9952,所以LSE clock 頻率為5111.9952x64=32767.6928,推算每天約慢0.81秒。
  • LSI clock source:平均為598.9092,LSI clock frequency=598.9092x64=38330.1888,推算每天約慢3606秒。

Black Pill:
  • HSE clock source:平均為976.62185,HSE clock frequency = 976.62185x64=62503.7984,推算每天約快5.2秒。
  • LSE clock source: 如影片平均值為512.0005,所以LSE clock 頻率為512.0005x64=32768.032,推算每天約慢0.08秒。
  • LSI clock source:平均為624.6267,LSI clock frequency=624.6267x64=40104.1088,推算每天約快224秒。
由以上結果RTC Clock Source以LSE最精確(獨立32.768KHz震盪器),其次HSE,再其次LSI。本次實驗用的開發板Black Pill也比Blue Pill較精確。分別以兩塊開發板在「使用STM32F103C8T6 RTC 實作時鐘、鬧鐘」文章中實作也驗證以上結果。但特別聲明並不代表所有black pill開發版RTC clock就比Blue pill 精確,只是針對本次實驗用的這兩塊板子的比較

時間校正
STM32F10xxx使用數位校正線路,在每2^20 clock cycles移除0~127cycles,以達到正確的clock cycles,因此只能調慢,像本實驗的Blue pill clock(32,767.6928)就比較慢(32,768),因此必須使用prescale將時間變快後,再移除一些clock cycles。

Blue Pill調整參數,選用LSE clock source,


prescale為32767,因此
hrtc.Init.AsynchPrediv = 32767-1;
計算每30天快幾秒:
((32767.6928/32767)-1)*86400*30=54.79
或假設需移除的cycles為C,每秒需移除32767.6928-32767=0.6928 cycles。
所以(C/2^20)*32767=0.6928,所以C=22.17


因此Calibration value為22,在RTC_Init()加入HAL_RTCEx_SetSmoothCalib(&hrtc, 0, 0, 22);指令。

Black Pill調整參數,選用LSE clock source,
hrtc.Init.AsynchPrediv =RTC_AUTO_1_SECOND
((32768.032/32768)-1)*86400*30=2.5,Calibration value為1,在RTC_Init()加入HAL_RTCEx_SetSmoothCalib(&hrtc, 0, 0, 1);指令。

簡易時間校正規則:
前述方法測量RTC OUT的頻率,可能因為所使用自製測量模組本身震盪器精確度或所使用的杜邦線線材品質可能會有誤差。簡易的方法以觀察一段時間記錄總時間誤差,再設定hrtc.Init.AsynchPrediv與查AN2604 Application note table 1設定Calibration value來做校正。
例如,30天38秒,查表最接近為37秒,Calibration value=15。
若30天60秒,推算LSE頻率f`, ((f/32768)-1)*86400*30=-60, f=32767.234,
設定hrtc.Init.AsynchPrediv=32767-1,則30天就會快((32767.234/32767)-1)*86400*30=18.50,查表Calibration value=7。

2020年11月29日 星期日

使用STM32F103C8T6 TIMER Input Capture 偵測輸入方波的頻率與寬度

本實驗目的使用STM32 MCU Timer Input Capture 功能來製作一簡易檢測輸入方波的頻率與佔空比。

參考文件:AN4776 Application note: General-purpose timer cookbook for STM32 microcontrollers 

使用軟體工具:STM32CubeIDE、HAL Library。

使用元件:

  1. STM32F103C8T6 BlackPill開發版 x1
  2. LCD 1602A x1(不含I2C或SPI介面)
  3. 10K電位計
  4. 小麵包版 17x10 兩塊
  5. 杜邦線

功能需求:

  1. 偵測輸入頻遇範圍:100Hz~1MHz
  2. 顯示當下頻率、波寬(Duty)
  3. 顯示偵測200ms時間內之平均頻率。
  4. 避免偵測高頻時由於中斷(Interrupt)太頻繁而無法讓慢速LCD顯示。
原理解說:
  1. 當偵測到輸入訊號(Rising or Falling Edge)時,暫存器CNT值會存入CCR。
  2. 使用TIMER2 Channel1與Channel2分別偵測輸入訊號之Rising Edge與Falling Edge。
  3. 輸入訊號接在TIM2 Channel1 Input。
  4. TIMER2 設定為Slave Reset Mode,Trigger Source選用TI1FP1,Channel1 設為Input Capture Direct Mode, Channel2 設為Input Capture indirect mode,當Channel1偵測到TI1FP1 Rising Edge 時register CNT值存入CCR1並reset CNT,Channel2偵測到TI1FP2 Falling Edge時 register CNT值存入CCR2。
  5. clock source 使用開發版的8M震盪器。Prescale為2,ARR(auto reload register)為65535,則最小偵測頻率為62Hz,8,000,000/2/(65535+1)=61.035。
  6. 輸入頻率為8M/(Prescale+1)/(CCR1+1),Duty為(CCR2+1)x100/(CCR1+1)
實作過程:
  • TIMER 3參數設定:
    避免因為偵測高頻時 input capture interrupt過於頻繁而使得慢速LCD被Block住而無法顯示,因此使用TIMER3 Base timer mode每200ms啟用TIMER2抓取輸入訊號200ms後顯示當時頻率,脈寬(Duty)與200ms內所有抓取訊號頻率的平均值。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) { if (!show_step) { HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); show_step=1; } else { HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_1); HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_2); if(cnt_avg > 3) { freq_avg = (double)((HAL_RCC_GetPCLK1Freq())/(htim2.Instance->PSC+1))/((val1_cnt_sum/(cnt_avg-3))+1); val1_cnt_sum=0; cnt_avg=0; } show_step=2; } }

  • TIME2參數設定。


void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef* htim) { if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); } if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) { val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); if (val1 !=0) { freq = (double)((HAL_RCC_GetPCLK1Freq())/(htim->Instance->PSC+1))/(val1+1); cnt_avg++; if (cnt_avg>3) //忽略前3次不計入平均 { val1_cnt_sum += val1; } duty=(val2+1)*100/(val1+1); } } }

  • Clock Configuration


線路圖
輸入訊號接PA0與GND



完成圖






實測影片
以另一片STM32_F4VE開發版產生PWM當輸入測試訊號,訊號序列為100Hz(5%)、500Hz(50%)、1kHz(50%)、5kHz(12%)、20kHz(50%)、40kHz(10%)、100kHz(75%)、500kHz(25%)、1MHz(50%)。







使用LCD library

完整程式碼 

/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * <h2><center>&copy; Copyright (c) 2020 STMicroelectronics. * All rights reserved.</center></h2> * * This software component is licensed by ST under BSD 3-Clause license, * the "License"; You may not use this file except in compliance with the * License. You may obtain a copy of the License at: * opensource.org/licenses/BSD-3-Clause * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "lcd.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ TIM_HandleTypeDef htim2; TIM_HandleTypeDef htim3; /* USER CODE BEGIN PV */ uint32_t val1, val2, cnt_avg=0; double val1_cnt_sum=0; uint16_t duty; double freq, freq_avg; uint8_t show_step=0,toggle=0; char buf[20]; Lcd_PortType ports[] = { GPIOA,GPIOA,GPIOA,GPIOB,GPIOB, GPIOB, GPIOB, GPIOB }; Lcd_PinType pins[] = {GPIO_PIN_11,GPIO_PIN_12,GPIO_PIN_15,GPIO_PIN_3,GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7}; Lcd_HandleTypeDef lcd; /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_TIM2_Init(void); static void MX_TIM3_Init(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef* htim) { if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); } if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) { val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); if (val1 !=0) { freq = (double)((HAL_RCC_GetPCLK1Freq())/(htim->Instance->PSC+1))/(val1+1); cnt_avg++; if (cnt_avg>3) //忽略前3次不計入平均 { val1_cnt_sum += val1; } duty=(val2+1)*100/(val1+1); } } } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) { if (!show_step) { HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); show_step=1; } else { HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_1); HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_2); if(cnt_avg > 3) { freq_avg = (double)((HAL_RCC_GetPCLK1Freq())/(htim2.Instance->PSC+1))/((val1_cnt_sum/(cnt_avg-3))+1); val1_cnt_sum=0; cnt_avg=0; } show_step=2; } } void lcd_display_data() { if (((++toggle)%10)==0) { sprintf(buf, "Duty:%d%s", duty,"%"); Lcd_cursor(&lcd, 0,0); Lcd_string(&lcd,buf); sprintf(buf, " "); Lcd_string(&lcd,buf); } else { sprintf(buf, "Favg:%.4f", freq_avg); Lcd_cursor(&lcd, 0,0); Lcd_string(&lcd,buf); sprintf(buf, " "); Lcd_string(&lcd,buf); } sprintf(buf,"Freq:%.4f", freq); Lcd_cursor(&lcd, 1,0); Lcd_string(&lcd,buf); sprintf(buf, " "); Lcd_string(&lcd,buf); if (!(toggle%2)) { Lcd_cursor(&lcd, 0, 15); Lcd_string(&lcd, "*"); } } /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM2_Init(); MX_TIM3_Init(); /* USER CODE BEGIN 2 */ lcd = Lcd_create(ports, pins, GPIOA, GPIO_PIN_9, GPIOA, GPIO_PIN_10, LCD_8_BIT_MODE); Lcd_clear(&lcd); Lcd_string(&lcd, "Starting"); show_step=0; HAL_TIM_Base_Start_IT(&htim3); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { if (show_step==2) { HAL_TIM_Base_Stop_IT(&htim3); lcd_display_data(); show_step=0; HAL_TIM_Base_Start_IT(&htim3); } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSE; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) { Error_Handler(); } } /** * @brief TIM2 Initialization Function * @param None * @retval None */ static void MX_TIM2_Init(void) { /* USER CODE BEGIN TIM2_Init 0 */ /* USER CODE END TIM2_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_SlaveConfigTypeDef sSlaveConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_IC_InitTypeDef sConfigIC = {0}; /* USER CODE BEGIN TIM2_Init 1 */ /* USER CODE END TIM2_Init 1 */ htim2.Instance = TIM2; htim2.Init.Prescaler = 1; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 65535; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } if (HAL_TIM_IC_Init(&htim2) != HAL_OK) { Error_Handler(); } sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET; sSlaveConfig.InputTrigger = TIM_TS_TI1FP1; sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING; sSlaveConfig.TriggerFilter = 0; if (HAL_TIM_SlaveConfigSynchro(&htim2, &sSlaveConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; sConfigIC.ICFilter = 0; if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1) != HAL_OK) { Error_Handler(); } sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI; if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_2) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM2_Init 2 */ /* USER CODE END TIM2_Init 2 */ } /** * @brief TIM3 Initialization Function * @param None * @retval None */ static void MX_TIM3_Init(void) { /* USER CODE BEGIN TIM3_Init 0 */ /* USER CODE END TIM3_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; /* USER CODE BEGIN TIM3_Init 1 */ /* USER CODE END TIM3_Init 1 */ htim3.Instance = TIM3; htim3.Init.Prescaler = 7999; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 999; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim3) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM3_Init 2 */ /* USER CODE END TIM3_Init 2 */ } /** * @brief GPIO Initialization Function * @param None * @retval None */ static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12 |GPIO_PIN_15, GPIO_PIN_RESET); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6 |GPIO_PIN_7, GPIO_PIN_RESET); /*Configure GPIO pins : PA9 PA10 PA11 PA12 PA15 */ GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12 |GPIO_PIN_15; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /*Configure GPIO pins : PB3 PB4 PB5 PB6 PB7 */ GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6 |GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/


2020年10月28日 星期三

使用STM32F103C8T6 RTC 實作時鐘、鬧鐘

本實驗使用STM32F103C8T6 MCU來實作時鐘(含鬧鐘功能),

使用元件:

  1. STM32F103C8T6 x1
  2. 按鈕 x3
  3. 1k電阻 x3
  4. OLED128x64   x1
  5. 蜂鳴器 x1
  6. 鈕扣電池 x1
功能需求:
  1. 可調整時間
  2. 設定鬧鐘
  3. 斷電VBAT供電
  4. 鬧鐘響起時按任何按鈕停止鬧鈴,響20秒未按按鈕,停止鬧鈴,5分鐘後再響鬧鈴,共三次。
  5. 2分鐘關螢幕進入STOP模式,按任一按鈕或鬧鈴響起喚醒。
在實作過程中遇到一些問題,例如無法進入STOP mode,在移除電源後,隔日復電日期未能自動更新等問題,可參閱另兩篇備忘文章
  1. https://rfwumcu.blogspot.com/2020/10/stm32-rtc.html
  2. https://rfwumcu.blogspot.com/2020/10/stm32-pwr-low-power-mode.html
實作過程:
一、線路圖


使用STM32CubeIDE開發環境與HAL library。

Timers分別使用RTC,TIM1,TIM2,TIM3。

RTC產稱每秒一次中斷(hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND)計時更新顯示。
TIM1: 設定為base timer,計時2分鐘關閉螢幕,進入STOP Mode以節省電源。
TIM2:設定為base timer,作為Software Debounce用,按鈕按下後啟動50ms後中斷。
TIM3:設定為1Hz PWM,驅動鬧鈴蜂鳴器,鬧鐘鬧鈴響起後,未按取消鬧鈴,20秒後關閉鬧鈴,5分鐘後再響鬧鈴共3次。

  • RCC Clock configuration 




    RCC啟用LSE作為RTC Clock,周邊clock為HSI,當系統進入STOP Mode使用的Clock。
  • RTC設定參數
在Parameter選取Automatic Predivider Calculation Enable 以產生每秒一次中斷(RTC_AUTO_1_SECOND)。

在NVIC Enable RTC global interrupt 以便每秒產生一次中斷並呼叫
void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef* hrtc) Callback function
在這function 中更新時間顯示。

啟用NVIC RTC alarm interrupt以便在呼叫HAL_RTC_SetAlarm_IT啟動Alarm Interrupt,並在設定Alarm時間呼叫
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef* hrtc) Callback function
 
  • TIM1參數設定
TIM1設定為base timer 時間為120秒關閉螢幕,進入stop mode,系統clock為8M:
8M/(39999+1)=200(Hz),
200/24000=0.0083(週期為1/0.0083=120秒)


NVIC啟用update interrupt

  • TIM2參數設定
TIM2仍設為Base timer 週期為50ms(Prescaler:7999, ARR:50) 
PB13~15設為EXTI,PB13為時間設定選擇鍵,PB15為鬧鐘設定選擇鍵,PB14為數值調整鍵。


Enable EXTI line[15:10] interrupts

當按鍵按下時產生中斷呼叫HAL_GPIO_EXTI_Callbak function,啟動TIM2
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if ((GPIO_Pin == GPIO_PIN_13 ||GPIO_Pin == GPIO_PIN_14 || GPIO_Pin == GPIO_PIN_15) && !buttonPress)
	{
		buttonPress=1;
		HAL_TIM_Base_Start_IT(&htim2);
	}


}
50ms後產生中斷呼叫HAL_TIM_PeriodElapsedCallback檢查是否仍是按下狀態,以達到software debounce功能。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
{
	if (htim->Instance==TIM2)
	{
		if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_13) == GPIO_PIN_RESET)
		{
			buttonPress=0;
			HAL_TIM_Base_Stop_IT(&htim2);
			EXTIButton13Press();
		}
.
.
.
}
  • TIM3參數設定:
        TIM3 Channel1(對應PB4) 設定為1Hz PWM, duty為70%,驅動鬧鈴蜂鳴器。


藉由呼叫HAL_RTC_SetAlarm_IT(&hrtc, &atime, RTC_FORMAT_BIN);設定Alarm,當時間到達時系統呼叫 callback function void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef* hrtc),並啟動TIM3 Channel 1 PWM(HAL_TIM_PWM_Start_IT(&htim3, TIM_CHANNEL_1);)讓蜂鳴器發出聲音。

HAL_RTC_SetAlarm_IT(&hrtc, &atime, RTC_FORMAT_BIN);
.
.
.
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef* hrtc)
{
.
.
.
            HAL_TIM_PWM_Start_IT(&htim3, TIM_CHANNEL_1);
.
.
.
}

在TIM3 PWM interrupt callback function 中檢查時間是否需要停止蜂鳴聲
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef* htim)
{
        if (alarm_count >= ALARM_BUZZ) //after ALARM_BUZZ Second turn off alarm
{
clearAlarm();
.
                .
                .
           };
}



實作展示:





使用OLED128x64 I2C介面200歐姆上拉電阻,使用程式庫
複製ssd1306.c ssd1306_fonts,c ssd1306.h ssd1306_fonts.h,ssd1306_conf.h

完整程式碼:
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * 

© Copyright (c) 2020 STMicroelectronics. * All rights reserved.

* * This software component is licensed by ST under BSD 3-Clause license, * the "License"; You may not use this file except in compliance with the * License. You may obtain a copy of the License at: * opensource.org/licenses/BSD-3-Clause * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "ssd1306.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ #define BLINK_DELAY 200 #define ALARM_BUZZ 20 #define ALARM_REPEAT 3 #define ALARM_RESUME_MIN 5 /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ I2C_HandleTypeDef hi2c1; RTC_HandleTypeDef hrtc; TIM_HandleTypeDef htim1; TIM_HandleTypeDef htim2; TIM_HandleTypeDef htim3; /* USER CODE BEGIN PV */ RTC_TimeTypeDef stime; RTC_DateTypeDef sdate; RTC_AlarmTypeDef atime; uint8_t year, month,day,hour, min,sec,ahour,amin; char sDateBuf[12], sTimeBuf[10], sWeekdayBuf[10],sAlarmBuf[10]; uint8_t alarmSet=0; uint8_t alarmEvent=0; uint8_t buttonPress=0; uint8_t startTimer1=0; uint8_t inStopMode=0,enterStopMode=0; uint8_t opMode=0; //0:normal, 1: in adjust time mode, 2: in set Alarm mode uint8_t adjustTimeItem = 0; //0:year,1:month, 2:day, 3;Hour, 4:min, 5:sec uint8_t adjustAlarmItem=0; // 0: enter alarm set mode, 1:on-off,2:hour,3:minute uint8_t strWeekday[7][10]={ "Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; uint8_t alarm_count=0; uint8_t alarm_resume=0; /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_RTC_Init(void); static void MX_I2C1_Init(void); static void MX_TIM1_Init(void); static void MX_TIM2_Init(void); static void MX_TIM3_Init(void); /* USER CODE BEGIN PFP */ void EXTIButton13Press(void); void EXTIButton14Press(void); void EXTIButton15Press(void); void wakeUpFromStopMode(void); /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ static uint8_t isLeapYear(uint16_t nYear) { if ((nYear % 4U) != 0U) { return 0U; } if ((nYear % 100U) != 0U) { return 1U; } if ((nYear % 400U) == 0U) { return 1U; } else { return 0U; } } void adjustAlarm(void) { char buf[4]; switch(adjustAlarmItem) { case 1: if(alarmSet) { sprintf(buf,"ON"); } else { sprintf(buf,"OFF"); } ssd1306_SetCursor(1, 1); ssd1306_WriteString(" ", Font_11x18, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY/4); ssd1306_SetCursor(1, 1); ssd1306_WriteString(buf, Font_11x18, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY); break; case 2: ssd1306_SetCursor(5, 22); ssd1306_WriteString(" ", Font_16x26, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY/4); sprintf(buf, "%02d", ahour); ssd1306_SetCursor(5, 22); ssd1306_WriteString(buf, Font_16x26, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY); break; case 3: ssd1306_SetCursor(53, 22); ssd1306_WriteString(" ", Font_16x26, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY/4); sprintf(buf, "%02d", amin); ssd1306_SetCursor(53, 22); ssd1306_WriteString(buf, Font_16x26, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY); break; } } void adjustTime(void) { char buf[4]; switch(adjustTimeItem) { case 0: ssd1306_SetCursor(1, 1); ssd1306_WriteString(" ", Font_11x18, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY/4); sprintf(buf, "%d", year); ssd1306_SetCursor(1, 1); ssd1306_WriteString(buf, Font_11x18, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY); break; case 1: ssd1306_SetCursor(34, 1); ssd1306_WriteString(" ", Font_11x18, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY/4); sprintf(buf, "%02d", month); ssd1306_SetCursor(34, 1); ssd1306_WriteString(buf, Font_11x18, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY); break; case 2: ssd1306_SetCursor(67, 1); ssd1306_WriteString(" ", Font_11x18, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY/4); sprintf(buf, "%02d", day); ssd1306_SetCursor(67, 1); ssd1306_WriteString(buf, Font_11x18, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY); break; case 3: ssd1306_SetCursor(1, 25); ssd1306_WriteString(" ", Font_16x26, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY/4); sprintf(buf, "%02d", hour); ssd1306_SetCursor(1, 25); ssd1306_WriteString(buf, Font_16x26, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY); break; case 4: ssd1306_SetCursor(49, 25); ssd1306_WriteString(" ", Font_16x26, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY/4); sprintf(buf, "%02d", min); ssd1306_SetCursor(49, 25); ssd1306_WriteString(buf, Font_16x26, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY); break; case 5: ssd1306_SetCursor(97, 31); ssd1306_WriteString(" ", Font_11x18, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY/4); sprintf(buf, "%02d", sec); ssd1306_SetCursor(97, 31); ssd1306_WriteString(buf, Font_11x18, White); ssd1306_UpdateScreen(); HAL_Delay(BLINK_DELAY); break; } } /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_RTC_Init(); MX_I2C1_Init(); MX_TIM1_Init(); MX_TIM2_Init(); MX_TIM3_Init(); /* USER CODE BEGIN 2 */ ssd1306_Init(); HAL_TIM_Base_Start_IT(&htim1); startTimer1 = 1; HAL_TIM_Base_Start_IT(&htim2); HAL_TIM_Base_Stop_IT(&htim2); atime.Alarm=RTC_ALARM_A; atime.AlarmTime.Hours=0; atime.AlarmTime.Minutes=0; atime.AlarmTime.Seconds=0; /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { if (enterStopMode) { enterStopMode=0; HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI); } switch(opMode) { case 1: //in adjust Time Mode adjustTime(); break; case 2: // in set Alarm Mode adjustAlarm(); break; } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_LSE; RCC_OscInitStruct.LSEState = RCC_LSE_ON; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) { Error_Handler(); } PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC; PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) { Error_Handler(); } } /** * @brief I2C1 Initialization Function * @param None * @retval None */ static void MX_I2C1_Init(void) { /* USER CODE BEGIN I2C1_Init 0 */ /* USER CODE END I2C1_Init 0 */ /* USER CODE BEGIN I2C1_Init 1 */ /* USER CODE END I2C1_Init 1 */ hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 400000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN I2C1_Init 2 */ /* USER CODE END I2C1_Init 2 */ } /** * @brief RTC Initialization Function * @param None * @retval None */ static void MX_RTC_Init(void) { /* USER CODE BEGIN RTC_Init 0 */ /* USER CODE END RTC_Init 0 */ RTC_TimeTypeDef sTime = {0}; RTC_DateTypeDef DateToUpdate = {0}; /* USER CODE BEGIN RTC_Init 1 */ /* USER CODE END RTC_Init 1 */ /** Initialize RTC Only */ hrtc.Instance = RTC; hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND; hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM; if (HAL_RTC_Init(&hrtc) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN Check_RTC_BKUP */ if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) != 0x6060) { /* USER CODE END Check_RTC_BKUP */ /** Initialize RTC and set the Time and Date */ sTime.Hours = 13; sTime.Minutes = 56; sTime.Seconds = 0; if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) { Error_Handler(); } DateToUpdate.WeekDay = RTC_WEEKDAY_SUNDAY; DateToUpdate.Month = RTC_MONTH_SEPTEMBER; DateToUpdate.Date = 26; DateToUpdate.Year = 20; if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN RTC_Init 2 */ uint32_t dateToStore; memcpy(&dateToStore,&(hrtc.DateToUpdate),sizeof(uint32_t)); BKP->DR2 = dateToStore >> 16; BKP->DR3 = dateToStore & 0xffff; HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x6060); } else { uint32_t dateMem; dateMem = BKP->DR2 << 16; dateMem |= BKP->DR3; memcpy(&DateToUpdate,&dateMem,sizeof(uint32_t)); HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN);           HAL_RTC_GetTime(&hrtc, &stime, RTC_FORMAT_BIN); //get time counter and call RTC_DateUpdate HAL_RTCEx_SetSecond_IT(&hrtc); } /* USER CODE END RTC_Init 2 */ } /** * @brief TIM1 Initialization Function * @param None * @retval None */ static void MX_TIM1_Init(void) { /* USER CODE BEGIN TIM1_Init 0 */ /* USER CODE END TIM1_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; /* USER CODE BEGIN TIM1_Init 1 */ /* USER CODE END TIM1_Init 1 */ htim1.Instance = TIM1; htim1.Init.Prescaler = 39999; htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 24000; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; if (HAL_TIM_Base_Init(&htim1) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM1_Init 2 */ /* USER CODE END TIM1_Init 2 */ } /** * @brief TIM2 Initialization Function * @param None * @retval None */ static void MX_TIM2_Init(void) { /* USER CODE BEGIN TIM2_Init 0 */ /* USER CODE END TIM2_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; /* USER CODE BEGIN TIM2_Init 1 */ /* USER CODE END TIM2_Init 1 */ htim2.Instance = TIM2; htim2.Init.Prescaler = 7999; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 50; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM2_Init 2 */ /* USER CODE END TIM2_Init 2 */ } /** * @brief TIM3 Initialization Function * @param None * @retval None */ static void MX_TIM3_Init(void) { /* USER CODE BEGIN TIM3_Init 0 */ /* USER CODE END TIM3_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfigOC = {0}; /* USER CODE BEGIN TIM3_Init 1 */ /* USER CODE END TIM3_Init 1 */ htim3.Instance = TIM3; htim3.Init.Prescaler = 7999; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 999; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim3) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } if (HAL_TIM_PWM_Init(&htim3) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK) { Error_Handler(); } sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 700; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM3_Init 2 */ /* USER CODE END TIM3_Init 2 */ HAL_TIM_MspPostInit(&htim3); } /** * @brief GPIO Initialization Function * @param None * @retval None */ static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /*Configure GPIO pins : PB13 PB14 PB15 */ GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); /* EXTI interrupt init*/ HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); } /* USER CODE BEGIN 4 */ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if ((GPIO_Pin == GPIO_PIN_13 ||GPIO_Pin == GPIO_PIN_14 || GPIO_Pin == GPIO_PIN_15) && !buttonPress) { buttonPress=1; HAL_TIM_Base_Start_IT(&htim2); } } void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef* hrtc) { if (opMode==0) { HAL_RTC_GetTime(hrtc, &stime, RTC_FORMAT_BIN); HAL_RTC_GetDate(hrtc, &sdate, RTC_FORMAT_BIN); ssd1306_Fill(Black); sprintf(sTimeBuf, "%02d", stime.Seconds); ssd1306_SetCursor(97, 31); ssd1306_WriteString(sTimeBuf, Font_11x18, White); sprintf(sTimeBuf, "%02d:%02d:", stime.Hours,stime.Minutes); ssd1306_SetCursor(1,25); ssd1306_WriteString(sTimeBuf, Font_16x26, White); sprintf(sDateBuf, "%02d/%02d/%02d", sdate.Year,sdate.Month,sdate.Date); ssd1306_SetCursor(1,1); ssd1306_WriteString(sDateBuf, Font_11x18, White); sprintf(sWeekdayBuf, "%s", strWeekday[sdate.WeekDay]); ssd1306_SetCursor((int)(128-strlen(sWeekdayBuf)*7),7); ssd1306_WriteString(sWeekdayBuf, Font_7x10, White); if (alarmSet) { sprintf(sAlarmBuf, "%02d:%02d", atime.AlarmTime.Hours,atime.AlarmTime.Minutes); ssd1306_SetCursor(52, 52); ssd1306_WriteString(sAlarmBuf, Font_7x10, White); ssd1306_SetCursor(1, 52); ssd1306_WriteString("Alarm: ", Font_7x10, White); } ssd1306_UpdateScreen(); } } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) { if (htim->Instance==TIM2) { if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_13) == GPIO_PIN_RESET) { buttonPress=0; HAL_TIM_Base_Stop_IT(&htim2); EXTIButton13Press(); } if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14) == GPIO_PIN_RESET) { buttonPress=0; HAL_TIM_Base_Stop_IT(&htim2); EXTIButton14Press(); } if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_15) == GPIO_PIN_RESET) { buttonPress=0; HAL_TIM_Base_Stop_IT(&htim2); EXTIButton15Press(); } } if (htim->Instance==TIM1) { if (startTimer1 && !alarmEvent) { ssd1306_SetDisplayOn(0); startTimer1=0; HAL_TIM_Base_Stop_IT(htim); inStopMode=1; enterStopMode=1; } } } void clearAlarm(void) { HAL_TIM_PWM_Stop_IT(&htim3, TIM_CHANNEL_1); alarmEvent=0; alarmSet=0; alarm_count=0; } void EXTIButton13Press(void) { if(alarmEvent && alarmSet) { clearAlarm(); return; } if (inStopMode) { wakeUpFromStopMode(); } else { if (startTimer1) { startTimer1=0; HAL_TIM_Base_Stop_IT(&htim1); } switch(opMode) { case 0: opMode++; hour=stime.Hours; min=stime.Minutes; sec=stime.Seconds; year=sdate.Year; month=sdate.Month; day=sdate.Date; break; case 1: adjustTimeItem++; if (adjustTimeItem==6) { opMode=0; adjustTimeItem=0; stime.Hours=hour; stime.Minutes=min; stime.Seconds=sec; sdate.Year=year; sdate.Month=month; sdate.Date=day; atime.AlarmTime.Hours=ahour; atime.AlarmTime.Minutes=amin; if (HAL_RTC_SetTime(&hrtc, &stime, RTC_FORMAT_BIN) != HAL_OK) { Error_Handler(); } if (HAL_RTC_SetDate(&hrtc, &sdate, RTC_FORMAT_BIN) != HAL_OK) { Error_Handler(); } if (alarmSet) { HAL_RTC_SetAlarm_IT(&hrtc, &atime, RTC_FORMAT_BIN); } uint32_t dateToStore; memcpy(&dateToStore,&(hrtc.DateToUpdate),sizeof(uint32_t)); BKP->DR2 = dateToStore >> 16; BKP->DR3 = dateToStore & 0xffff; startTimer1=1; HAL_TIM_Base_Start_IT(&htim1); } } } } void EXTIButton14Press(void) { if(alarmEvent && alarmSet) { clearAlarm(); } if (inStopMode) { wakeUpFromStopMode(); } else { if (opMode==1) { int mDays=31; switch(adjustTimeItem) { case 0: year++; if (year > 99) year=20; break; case 1: month++; if (month > 12) month=1; case 2: if (month == 2) { if (isLeapYear(year)) { mDays=29; } else { mDays=28; } } if (month == 4 || month == 6 || month == 9 || month == 11) { mDays=30; } day++; if (day > mDays) day=1; break; case 3: hour=(hour+1)%24; break; case 4: min=(min+1)%60; break; case 5: sec=(sec+1)%60; break; } } if (opMode==2) { switch(adjustAlarmItem) { case 1: alarmSet = (alarmSet+1)%2; break; case 2: ahour = (ahour+1)%24; break; case 3: amin = (amin+1)%60; break; } } } } void EXTIButton15Press(void) { char buf[6]; if(alarmEvent && alarmSet) { clearAlarm(); return; } if (inStopMode) { wakeUpFromStopMode(); } else { opMode=2; if (startTimer1) { startTimer1=0; HAL_TIM_Base_Stop_IT(&htim1); } if (adjustAlarmItem == 0) { ssd1306_Fill(Black); if (alarmSet) { sprintf(buf,"ON "); } else { sprintf(buf, "OFF"); } ssd1306_SetCursor(1, 1); ssd1306_WriteString(buf, Font_11x18, White); sprintf(buf, "%02d:%02d", ahour, amin); ssd1306_SetCursor(5, 22); ssd1306_WriteString(buf, Font_16x26, White); ssd1306_UpdateScreen(); } adjustAlarmItem++; if (adjustAlarmItem == 4) { opMode=0; adjustAlarmItem=0; atime.AlarmTime.Hours=ahour; atime.AlarmTime.Minutes=amin; if (alarmSet) { alarmEvent=0; alarm_resume=0; HAL_RTC_SetAlarm_IT(&hrtc, &atime, RTC_FORMAT_BIN); } startTimer1=1; HAL_TIM_Base_Start_IT(&htim1); } } } void wakeUpFromStopMode(void) { inStopMode=0; ssd1306_SetDisplayOn(1); startTimer1=1; HAL_TIM_Base_Start_IT(&htim1); } void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef* hrtc) { if(alarmSet) { if (inStopMode) { wakeUpFromStopMode(); } alarmEvent=1; HAL_TIM_PWM_Start_IT(&htim3, TIM_CHANNEL_1); } } void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef* htim) { alarm_count++; if (alarm_count >= ALARM_BUZZ) //after ALARM_BUZZ Second turn off alarm { clearAlarm(); alarm_resume++; if (alarm_resume < ALARM_REPEAT) { amin += ALARM_RESUME_MIN; // resume alarm ALARM_RESUME_MIN minutes later if (amin >=60) { ahour++; amin = amin % 60; if (ahour >=24) ahour % 24; } atime.AlarmTime.Hours=ahour; atime.AlarmTime.Minutes=amin; alarmSet = 1; HAL_RTC_SetAlarm_IT(&hrtc, &atime, RTC_FORMAT_BIN); } else { alarm_resume=0; } } } /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/


2020年10月25日 星期日

STM32 RTC實驗備忘錄

  1. STM32F1有ONE_SECOND interrupt,STM32F4沒有,只能用RTC Wakeup代替。
  2. STM32F4 注意要在HAL_RTC_GetTime之後呼叫HAL_RTC_GetDate才能取得取得數值(浪費一天得到)。在HAL_RTC_GetTime有說明:note:You must call HAL_RTC_GetDate() after HAL_RTC_GetTime() to unlock the values  in the higher-order calendar shadow registers to ensure consistency between the time and date values. Reading RTC current time locks the values in calendar shadow registers until current date is read.
  3. STM32F4 有ALARM A AND B,STM32F1 只有ALARM A
  4. STM32F1接ST-Link LSE可能電源不過,因此外接電源即可正常。
  5. STM32F1 RTC 在電池模式下(Power off, Standby, low power mode)日期不會更新問題,網路上搜尋解決方法較好為smt32fxx_hal_rtc.c 的RTC_DateUpdate function 下最後加入

uint32_t dateToStore;

   memcpy(&dateToStore,&hrtc->DateToUpdate,sizeof(uint32_t));

   BKP->DR2 = dateToStore >> 16;

   BKP->DR3 = dateToStore & 0xffff;

然後

在main.c的MX_RTC_Init 

判斷若backup register 是否儲存,在restore回日期

/* USER CODE BEGIN Check_RTC_BKUP */

  if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) != 0x30)

  {

  /* USER CODE END Check_RTC_BKUP */

  /** Initialize RTC and set the Time and Date

  */

.

.

.

/* USER CODE BEGIN RTC_Init 2 */

        uint32_t dateToStore;

       memcpy(&dateToStore,&hrtc->DateToUpdate,sizeof(uint32_t));

       BKP->DR2 = dateToStore >> 16;

       BKP->DR3 = dateToStore & 0xffff;

  HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x30);

}

else

  {

  uint32_t dateMem;

  dateMem = BKP->DR2 << 16;

  dateMem |= BKP->DR3;

  memcpy(&DateToUpdate,&dateMem,sizeof(uint32_t));

  HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN);

  }

/* USER CODE END RTC_Init 2 */

2020年10月2日 星期五

STM32F103C8T6 進入 Low Power Mode與WakeUp 實驗備忘

實驗環境: 使用STM32CubeIDE與HAL functions

Q1.為何在Low Power mode下,無法透過GPIO EXTI wake-up?
A1:
  1. HAL_PWR_EnterSTOPMode,HAL_PWR_EnterSLEEPMode不可在callback function中呼叫,否則無法透過EXTI wake-up
  2. 在callback function 中設定flag於 main while(1) loop中check flag並啟動 low power mode,則可順利由low power mode中順利喚醒。
 int main(void)
{
    .
    .
    .
    /* USER CODE BEGIN 2 */
    enterSTOPMode=0;
  /* USER CODE END 2 */

    .
    .
    .
  while (1)
  {
	  if(enterSTOPMode)
{ enterSTOPMode=0;
HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI); } }   .     .     . }
Q2.為何HAL_PWR_EnterSTANDBYMode使用PA0 pin喚醒後無法再一次進入STANDBY mode?
A2:
        呼叫HAL_PWR_EnterSTANDBYMode前必須先CLEAR FLAG。以下順序呼叫
 int main(void)
{
    .
    .
    .
    /* USER CODE BEGIN 2 */
    enterStandbyMode=0;
  /* USER CODE END 2 */

    .
    .
    .
  while (1)
  {
	  if(enterStandbyMode)
{ enterStandbyMode=0;
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1); HAL_PWR_EnterSTANDBYMode(); } }   .     .     . }

2020年7月17日 星期五

搶答遊戲按鈕設計--使用STM32F103C8T6微控制器

本篇以STM32F103C8T6微控制器來實現搶答遊戲時誰先取得答題權,因為STM32F103C8T6使用Cortex-M3處理器,因此所有的GPIO Port都可以用來作為外部中斷使用。Arduino UNO/NANO/Pro mini只有兩個數位外部中斷(D2、D3),即使Mega也只有5個(D2、D3、D19、D20與D21)。
但根據Cortex-M3硬體結構當使用PA0當外部中斷時,PB0、PC0等即無法設為外部中斷。本實驗使用PA0、PA1當搶答外部中斷,PA2為復位外部中斷。PB0與PB1連接LED顯示誰先搶到答題權。
連接按鈕部分使用硬體RC debounce線路,整個線路連接如下圖所示
完成展示影片如下,最右邊的按鈕為復位按鈕,關掉LED,回到可搶答狀態。


軟體主要使用API
attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);
來設定外部中斷ISR(Interrupt Service Routine),因為按鈕設計平時為高電位,偵測按下時的Mode為FALLING。

完整程式如下:
uint16_t ip1 = PA0;
uint16_t led1 = PB0;
uint16_t ip2 = PA1;
uint16_t led2 = PB1;
uint16_t ip_init = PA2;
volatile bool i1_pressed, i2_pressed;
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  pinMode(ip1, INPUT);
  pinMode(ip2, INPUT);
  pinMode(ip_init, INPUT);
  led_init();
  attachInterrupt(digitalPinToInterrupt(ip1), ip1_isr, FALLING);
  attachInterrupt(digitalPinToInterrupt(ip2), ip2_isr, FALLING);
  attachInterrupt(digitalPinToInterrupt(ip_init), led_init, FALLING);
}
void led_init(void) {
  i1_pressed=false;
  i2_pressed=false;
  
  digitalWrite(led1, LOW);
  digitalWrite(led2, LOW);
  Serial.println("ip_init");
}
void ip1_isr(void) {
  if (i2_pressed) return;
  i1_pressed=true;
  digitalWrite(led1, HIGH);
  Serial.println("ip1");
}
void ip2_isr(void) {
  if (i1_pressed) return;
  i2_pressed=true;
  digitalWrite(led2, HIGH);
  Serial.println("ip2");
}
void loop() {
  // put your main code here, to run repeatedly:
  

}

2020年6月26日 星期五

STM32F103C8T6 使用Arduino IDE 開發環境常用方法

STM32F103C8T6 MCU,功能不錯,價格也很親民。最近測試了STM32F103C8T6  BluePill與BlackPill,為了與Arduino, ESP8266等MCU使用相同的Arduino IDE做為開發工具,上網查了一些方法並測試一下,把它彙整整理,以便有興趣使用STM32板子的人提供整合的經驗。

  • 第一種方法使用FTDI(USB-to-UART)
需加一塊FTDI的板子。因為手邊剛好有一塊ESP8266 NodeMCU就拿它來當FTDI用,
void setup() {
  // put your setup code here, to run once:
  pinMode(0, INPUT);
  pinMode(1, INPUT);

}

void loop() {
  // put your main code here, to run repeatedly:

}
燒錄到ESP8266就可當FTDI用了。
接線方式
ESP8266        STM32F103C8
3V3                V3
G                    G
RX                A10
TX                A9


STM32F103C8
boot0 接到+

上傳blink測試程式,upload method選用Serial


  • 第二種方法在STM32F103C8 Flash Bootloader

可直接使用板子的USB上傳程式碼。
常用兩種Bootloader為

另外兩種開發板分別為

    可參考下列網址即可完成,

採用第二種開發版程式需加裝STM32CubeProgrammer,下載網址https://www.st.com/en/development-tools/stm32cubeprog.html


使用ST的Arduino_Core_STM32




但如果使用上述generic_boot20_p13.bin bootloader則燒錄一次後com port即消失無法使用第二次
因此改用STM32_HID_Bootloader,但第一次上傳時若COM無法選取,直接上傳一次後就會出現正常的COM port了。

建議採用Arduino_Core_STM32,因為可使用完整ST提供完整的開發環境。

USB Support與upload method如上圖所示,若選錯可能破壞bootloader。



2020年5月17日 星期日

遙控微控制器小車篇(三) Joystick 遙控

本實驗使用ESP8266與Arduino pro mini微控制器,透過WiFi遙控小車。

使用元件
  1. TT馬達+65mm 車輪 X 4
  2.  L298N 直流馬達驅動板
  3. ESP8266 mini X 2
  4. Arduino pro mini X 1
  5. 搖桿
微控制器小車:使用ESP8266控制小車移動與速度,WiFi設定成SoftAP模式,開起WebServer接受搖桿端送來的移動參數。

搖控端使用一顆ESP8266設成Station模式,發送搖桿參數給受控小車。
LED指示燈亮時表示WiFi連線為異常狀態,系統會嘗試重連,當WiFi Ready時LED熄滅。
因ESP8266只有1個analog port。因此再使用一顆Arduino pro mini A0與A1 port讀取搖桿參數。ESP8266與Arduino間以I2C通訊。
實驗結果只有ESP8266設成master, Arduino設成slaver才能成功。反之則失敗。

線路圖





完成影片:



程式碼
1.ESP8266微控制器小車
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
int speed = 512;
const char* ssid = "esp8266car";
const char* pwd = "your-password";

ESP8266WebServer server(8080);

const int PIN1A=D2;
const int PIN1B=D1;
const int PIN2A=D5;
const int PIN2B=D6;


void handleRoot() {
  String action = "";
  int x=500, y=500;
  int mspeed=0, tspeed=0;
  int direct=0;
  int turn=0;
  int a1=0, a2=0, b1=0, b2=0;
  for (uint8_t i = 0; i < server.args(); i++) {
    if (server.argName(i) == "x") x=server.arg(i).toInt();
    if (server.argName(i) == "y") y=server.arg(i).toInt();
  }
  if (x < 400) {
    turn=1;
    tspeed = 1024-x;
  } else {
    if (x > 600) {
      turn=-1;
      tspeed = x;
    } else {
      turn=0;
      tspeed = 0;
    }
  }

  if (y < 400) {
    direct=1;
    mspeed = 1024-y;
  } else {
    if (y > 600) {
      direct=-1;
      mspeed = y;
    } else {
      direct=0;
      mspeed = 0;
    }
  }
  
  if (direct != 0 && turn == 0) {
      if (direct > 0) {
        a1 = mspeed;
        a2 = mspeed;
      } 
      if (direct < 0) {
        b1 = mspeed;
        b2 = mspeed;
      }
  }
  if (direct == 0 && turn != 0) {
      if (turn > 0) a2 = tspeed;
      if (turn < 0) a1 = tspeed;
  }
  if (direct != 0 && turn != 0) {
    if (direct > 0){
      a1 = mspeed;
      a2 = mspeed;
      if (turn > 0) a1 = 1024-tspeed;
      if (turn < 0) a2 = 1024-tspeed;
    }
    if (direct < 0) {
      b1 = mspeed;
      b2 = mspeed;
      if (turn > 0) b1 = 1024-tspeed;
      if (turn < 0) b2 = 1024-tspeed;
    }
  }
  analogWrite(PIN1A, a1);
  analogWrite(PIN1B, b1);
  analogWrite(PIN2A, a2);
  analogWrite(PIN2B, b2);

  server.send(200, "text/html", "");
}

void setup() {
  // put your setup code here, to run once:
  delay(1000);
  
  WiFi.mode(WIFI_AP_STA);
  WiFi.softAP(ssid, pwd);
  server.on("/move", handleRoot);
  
  server.begin();
  Serial.begin(115200);
  Serial.println(WiFi.softAPIP());

  pinMode(PIN1A, OUTPUT);
  pinMode(PIN1B, OUTPUT);
  pinMode(PIN2A, OUTPUT);
  pinMode(PIN2B, OUTPUT);


}

void loop() {
  // put your main code here, to run repeatedly:
  server.handleClient();
  
 
}

2.ESP8266 搖桿WiFi發射
#include <Wire.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>

#define pinSDA D1
#define pinSCL D2
#define pinIndicator D3

String motion;
char ich;
bool isNewMotion;
int tx, ty;
String sx, sy;
const char* ssid     = "esp8266car";
const char* password = "your-password";
char url[100];

WiFiClient client;

void carMotion(int x, int y) {
  HTTPClient http;
  
      
  sprintf(url, "http://192.168.4.1:8080/move?x=%d&y=%d",x,y);
 
  http.begin(client, url);
    
  
  int httpCode = http.GET();

      // httpCode will be negative on error
      if (httpCode > 0) {
        digitalWrite(pinIndicator,LOW);
        
        // file found at server
        if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
          String payload = http.getString();
          Serial.println(payload);
        }
      } else {
        // send error
        digitalWrite(pinIndicator,HIGH);
      
      }
    http.end();
}

void setup() {
 pinMode(pinIndicator, OUTPUT);
 
 Wire.begin(pinSDA, pinSCL); /* join i2c bus with SDA=D1 and SCL=D2 of NodeMCU */
 WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
// LED light
  digitalWrite(pinIndicator,HIGH);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  // LED off : connect OK
    digitalWrite(pinIndicator,LOW);
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

}

void loop() {
 Wire.requestFrom(8, 14); /* request & read data of size 14 from slave */
 motion = "";
 isNewMotion=false;
 while(Wire.available()){
    char c = Wire.read();
    motion += c;
    isNewMotion=true;
 }
 if (isNewMotion) {
    //Serial.println(motion);
    tx = motion.indexOf(",");
    ty = motion.indexOf("E",tx);
    
    sx=motion.substring(0,tx);
    sy=motion.substring(tx+1,ty);
    tx=sx.substring(sx.indexOf("=")+1).toInt();
    ty =sy.substring(sy.indexOf("=")+1).toInt();
    carMotion(tx,ty);
 }
}
3.Arduino pro mini讀取搖桿
#include <Wire.h>
char buf[20];
String recv;

void setup() {
 Wire.begin(8);                /* join i2c bus with address 8 */
 Wire.onReceive(receiveEvent); /* register receive event */
 Wire.onRequest(requestEvent); /* register request event */
 
}

void loop() {
 delay(100);
}

// function that executes whenever data is received from master
void receiveEvent(int howMany) {
  recv = "";
 while (0 <Wire.available()) {
    char c = Wire.read();      /* receive byte as a character */
    recv +=c;           /* print the character */
  }
 
}

// function that executes whenever data is requested from master
void requestEvent() {
  sprintf(buf, "x=%d,y=%dE", analogRead(A0), analogRead(A1));
  Wire.write(buf);
  delay(10);

 
}