prettyprint

2021年1月13日 星期三

STM32微控制器(STM32F407VET6) SD-4bits、SD-SPI,FLASH等儲存設備管理

    本實驗開發版使用STM32F4VE,軟體使用STM32CubeIDE與HAL library為開發環境,透過FatFS API同時管理三個SD-4bits,SD-SPI,SPI-FLASH等Devices,SD使用SDIO 4bits與SPI介面兩種方式,另外使用SPI介面存取開發版上的W25Q16JV Flash。

參考資料:

  1. http://elm-chan.org/fsw/ff/00index_e.html
  2. https://github.com/nimaltd/w25qxx
  3. https://controllerstech.com/sd-card-using-spi-in-stm32/
  4. SDIO匯流排(一) - IT閱讀
 

硬體環境: 

開發版上的MicroSD接口使用SDIO SD 4 bits Wide Bus模式,另外使用SPI 2連接外接TFT卡的SD卡,使用SPI1連接W25Q16JV Flash。

實驗內容:

    同時連接三個設備,開發版內接MicroSD為Volume 0,TFT Module上的SD為Volume 1,開發版上的Flash為Volume 2。
    同時mount三個Volumes分別建立檔案,寫入資料,再從Volume0複製image檔到Volume1。




上圖說明Application 透過FatFs Module做檔案操作,FatFs Module透過FatFs MAI(Media Access Interface)與Storage Device Driver對實體設備存取。本實驗著重在MAI實作。SPI SD與Flash Device driver使用上述參考網址2與3。

實驗步驟:

步驟一

    設定SDIO, SPI1, SPI2 Connectivity
  • Volume 0:SDIO使用SD 4 bits Wide bus connectivity,開啟相關DMA、NVIC。
使用4bits Wide bus
開啟DMA
NVIC enable

  • Volume 1: SPI2(連接TFT卡上的SD,使用SPI mode)
DMA enable


GPIO Pin
PC3為SD_CS
  • Volume2: SPI1(連接開發版上W25Q17JV Flash)
GPIO pins
PB0為FLASH_CS
未啟用DMA


步驟二

設定FATFS

  • SD Card連接SDIO 4 bits Widw Bus為Volume 0
  • User-defined連接SPI 2 SD為Voulme 1
  • SPI1 Flash透過手動更改FATF/APP/fatfs.c加入volume2
  • Locale and Namespace Parameter:
    • USE_LFN:2(使用長檔名)
    • LFN_UNICODE:ANSI/OEM(暫時不用中文檔名)
    • STRF_ENCODE:UTF-8(可存中文內容)
  • Physical Driver Parameter:
    • VOLUMES:3(手動改為3,因為要實際連接3個Volumes)
    • MAX_SS:4096
修改FATFS/Target/ffconf.h:
#define _FS_LOCK    3  //改為3,因為要同時開啟3個檔案

加入程式碼:


程式堆疊為Application->Fatfs Module->FatFs MAI->Storage Device Controller(Hardware Driver)
  • Hardware Driver:
    • SD 4 bits Wide bus: bsp_driver_sd.c,bsp_driver_sd.h,STM32CubeIDE已自動產生。
    • SD SPI bus: fatfs_sd.c, fatfs_sd.h (參考網址3)
    • Flash SPI bus: w25qxx.c w25qxx.h, w25qxxConf.h((參考網址2)
  • FatFs MAI:
    • 要實作initialize, status, read, write, ioctl
    • 透過上面五個functions 呼叫 Hardware Driver實際讀寫資料到device。
    • 實作get_fattime取得檔案時間。
    • SD 4 bits Wide bus: sd_diskio.c, sd_diskio.h,STM32CubeIDE已自動產生並實作上述functions。
    • SD SPI bus:  user_diskio.c, user_diskio.h ,STM32CubeIDE已自動產生,但並未連結。呼叫Hardware Driver參閱下列程式碼一。
    • Flash SPI bus: flash_diskio.c, flash_diskio.h參閱下列程式碼二、三。
    • 定義每個device的Diskio_drvTypeDef以便呼叫各自的initialize, status, read, write, ioctl functions。SD_Driver(SD 4bits Wide Bus)與USER_Driver(SD SPI)已自動產生,FLASH_Driver參閱程式碼三。
    • FATFS/APP/fatfs.c: void MX_FATFS_Init(void)將volume path與 hardware driver連結,修改fatfs.c部分請參閱程式碼四。
  • 實驗應用程式main.c請參閱程式碼五。
程式碼一(user_diskio.c)
DSTATUS USER_initialize ( BYTE pdrv /* Physical drive nmuber to identify the drive */ ) { /* USER CODE BEGIN INIT */ Stat = STA_NOINIT; return SD_disk_initialize(pdrv); /* USER CODE END INIT */ } /** * @brief Gets Disk Status * @param pdrv: Physical drive number (0..) * @retval DSTATUS: Operation status */ DSTATUS USER_status ( BYTE pdrv /* Physical drive number to identify the drive */ ) { /* USER CODE BEGIN STATUS */ Stat = STA_NOINIT; return SD_disk_status(pdrv); /* USER CODE END STATUS */ } /** * @brief Reads Sector(s) * @param pdrv: Physical drive number (0..) * @param *buff: Data buffer to store read data * @param sector: Sector address (LBA) * @param count: Number of sectors to read (1..128) * @retval DRESULT: Operation result */ DRESULT USER_read ( BYTE pdrv, /* Physical drive nmuber to identify the drive */ BYTE *buff, /* Data buffer to store read data */ DWORD sector, /* Sector address in LBA */ UINT count /* Number of sectors to read */ ) { /* USER CODE BEGIN READ */ return SD_disk_read(pdrv, buff, sector, count); /* USER CODE END READ */ } /** * @brief Writes Sector(s) * @param pdrv: Physical drive number (0..) * @param *buff: Data to be written * @param sector: Sector address (LBA) * @param count: Number of sectors to write (1..128) * @retval DRESULT: Operation result */ #if _USE_WRITE == 1 DRESULT USER_write ( BYTE pdrv, /* Physical drive nmuber to identify the drive */ const BYTE *buff, /* Data to be written */ DWORD sector, /* Sector address in LBA */ UINT count /* Number of sectors to write */ ) { /* USER CODE BEGIN WRITE */ /* USER CODE HERE */ return SD_disk_write(pdrv, buff, sector, count); /* USER CODE END WRITE */ } #endif /* _USE_WRITE == 1 */ /** * @brief I/O control operation * @param pdrv: Physical drive number (0..) * @param cmd: Control code * @param *buff: Buffer to send/receive control data * @retval DRESULT: Operation result */ #if _USE_IOCTL == 1 DRESULT USER_ioctl ( BYTE pdrv, /* Physical drive nmuber (0..) */ BYTE cmd, /* Control code */ void *buff /* Buffer to send/receive control data */ ) { /* USER CODE BEGIN IOCTL */ DRESULT res = RES_ERROR; return SD_disk_ioctl(pdrv, cmd, buff); /* USER CODE END IOCTL */ } #endif /* _USE_IOCTL == 1 */

程式碼二:  flash_diskio.h
#ifndef __FLASH_DISKIO_H
#define __FLASH_DISKIO_H

#ifdef __cplusplus
 extern "C" {
#endif

extern Diskio_drvTypeDef  FLASH_Driver;



#ifdef __cplusplus
}
#endif

#endif
程式碼三: flash_diskio.c
#include <string.h>
#include "ff_gen_drv.h"
#include "w25qxx.h"

static volatile DSTATUS Stat = STA_NOINIT;

DSTATUS FLASH_initialize (BYTE pdrv);
DSTATUS FLASH_status (BYTE pdrv);
DRESULT FLASH_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1
  DRESULT FLASH_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
  DRESULT FLASH_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */

Diskio_drvTypeDef  FLASH_Driver =
{
		FLASH_initialize,
		FLASH_status,
		FLASH_read,
#if  _USE_WRITE
		FLASH_write,
#endif  /* _USE_WRITE == 1 */
#if  _USE_IOCTL == 1
		FLASH_ioctl,
#endif /* _USE_IOCTL == 1 */
};

/* Private functions ---------------------------------------------------------*/

/**
  * @brief  Initializes a Drive
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS FLASH_initialize (
	BYTE pdrv           /* Physical drive nmuber to identify the drive */
)
{
  /* USER CODE BEGIN INIT */
    Stat = STA_NOINIT;
    if (W25qxx_Init()) Stat = RES_OK;
    return Stat;
  /* USER CODE END INIT */
}

/**
  * @brief  Gets Disk Status
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS FLASH_status (
	BYTE pdrv       /* Physical drive number to identify the drive */
)
{
  /* USER CODE BEGIN STATUS */

    return Stat;

  /* USER CODE END STATUS */
}

/**
  * @brief  Reads Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data buffer to store read data
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to read (1..128)
  * @retval DRESULT: Operation result
  */
DRESULT FLASH_read (
	BYTE pdrv,      /* Physical drive nmuber to identify the drive */
	BYTE *buff,     /* Data buffer to store read data */
	DWORD sector,   /* Sector address in LBA */
	UINT count      /* Number of sectors to read */
)
{
/* pdrv should be 0 */
	if (pdrv || !count) return RES_PARERR;

	/* no disk */
	if (Stat & STA_NOINIT) return RES_NOTRDY;

	while(count>0)
	{
		W25qxx_ReadSector(buff, sector, 0, 0);
		buff+=w25qxx.SectorSize;
		sector++;
		count--;
	}

	return count ? RES_ERROR : RES_OK;
}

/**
  * @brief  Writes Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data to be written
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to write (1..128)
  * @retval DRESULT: Operation result
  */
#if _USE_WRITE == 1
DRESULT FLASH_write (
	BYTE pdrv,          /* Physical drive nmuber to identify the drive */
	const BYTE *buff,   /* Data to be written */
	DWORD sector,       /* Sector address in LBA */
	UINT count          /* Number of sectors to write */
)
{
  /* USER CODE BEGIN WRITE */
  /* USER CODE HERE */

	while(count > 1)
	{
		W25qxx_EraseSector(sector);
		W25qxx_WriteSector(buff, sector, 0, 0);
		count--;
		buff += w25qxx.SectorSize;
		sector++;
	}
	if (count == 1)
	{
		W25qxx_EraseSector(sector);
		W25qxx_WriteSector(buff, sector, 0, 0);
		count--;
	}

	return count ? RES_ERROR : RES_OK;
  /* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */

/**
  * @brief  I/O control operation
  * @param  pdrv: Physical drive number (0..)
  * @param  cmd: Control code
  * @param  *buff: Buffer to send/receive control data
  * @retval DRESULT: Operation result
  */
#if _USE_IOCTL == 1
DRESULT FLASH_ioctl (
	BYTE pdrv,      /* Physical drive nmuber (0..) */
	BYTE cmd,       /* Control code */
	void *buff      /* Buffer to send/receive control data */
)
{
  /* USER CODE BEGIN IOCTL */
    DRESULT res = RES_ERROR;

    switch(cmd) {
    case CTRL_SYNC:
    	res = RES_OK;
    	break;
    case GET_SECTOR_SIZE:
    	*(DWORD*)buff = w25qxx.SectorSize;
    	res = RES_OK;
    	break;
    case GET_BLOCK_SIZE:
    	*(DWORD*)buff = w25qxx.BlockSize;
    	res = RES_OK;
    	break;
    case GET_SECTOR_COUNT:
    	*(DWORD*)buff = w25qxx.SectorCount;
    	res = RES_OK;
    	break;
    default:
    	res = RES_PARERR;
    	break;
    }

    return res;

  /* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */


程式碼四:fatfs.c
/**
  ******************************************************************************
  * @file   fatfs.c
  * @brief  Code for fatfs applications
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */

#include "fatfs.h"

uint8_t retSD;    /* Return value for SD */
char SDPath[4];   /* SD logical drive path */
FATFS SDFatFS;    /* File system object for SD logical drive */
FIL SDFile;       /* File object for SD */
uint8_t retUSER;    /* Return value for USER */
char USERPath[4];   /* USER logical drive path */
FATFS USERFatFS;    /* File system object for USER logical drive */
FIL USERFile;       /* File object for USER */

/* USER CODE BEGIN Variables */
extern RTC_HandleTypeDef hrtc;
RTC_DateTypeDef sDate;
RTC_TimeTypeDef sTime;

uint8_t retFLASH;    /* Return value for USER */
char FLASHPath[4];   /* USER logical drive path */
FATFS FLASHFatFS;    /* File system object for USER logical drive */
FIL FLASHFile;       /* File object for USER */
/* USER CODE END Variables */

void MX_FATFS_Init(void)
{
  /*## FatFS: Link the SD driver ###########################*/
  retSD = FATFS_LinkDriver(&SD_Driver, SDPath); //SDPath: 0:/
  /*## FatFS: Link the USER driver ###########################*/
  retUSER = FATFS_LinkDriver(&USER_Driver, USERPath);//USERPath: 1:/
/* USER CODE BEGIN Init */ /*## FatFS: Link the FLASH driver ###########################*/ retFLASH = FATFS_LinkDriver(&FLASH_Driver, FLASHPath);//FLASHPath: 2:/
/* additional user code for init */ /* USER CODE END Init */ } /** * @brief Gets Time from RTC * @param None * @retval Time in DWORD */ DWORD get_fattime(void) { /* USER CODE BEGIN get_fattime */         HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN); DWORD tm = (sDate.Year+20)<<25|sDate.Month<<21|sDate.Date<<16|sTime.Hours<<11|sTime.Minutes<<5|sTime.Seconds;//year from 1980, second/2 //return 0; return tm; /* USER CODE END get_fattime */ } /* USER CODE BEGIN Application */ /* USER CODE END Application */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

程式碼五:main.c
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 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"
#include "fatfs.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "fatfs_sd.h"
#include "ff.h"
#include "w25qxx.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 ---------------------------------------------------------*/
RTC_HandleTypeDef hrtc;
SD_HandleTypeDef hsd;
DMA_HandleTypeDef hdma_sdio_rx;
DMA_HandleTypeDef hdma_sdio_tx;

SPI_HandleTypeDef hspi1;
SPI_HandleTypeDef hspi2;
DMA_HandleTypeDef hdma_spi2_rx;
DMA_HandleTypeDef hdma_spi2_tx;

/* USER CODE BEGIN PV */
FATFS fs_sd, fs_spi_sd, fs_spi_flash;
FIL file_sd, file_spi_sd, file_spi_flash;
FRESULT res;
char fname[256];
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_SDIO_SD_Init(void);
static void MX_SPI2_Init(void);
static void MX_SPI1_Init(void);
static void MX_RTC_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* 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_DMA_Init();
  MX_SDIO_SD_Init();
  MX_FATFS_Init();
  MX_SPI2_Init();
  MX_SPI1_Init();
  MX_RTC_Init();
  /* USER CODE BEGIN 2 */



  UINT bw,br;
  BYTE work[_MAX_SS];
  //對FLASH產生檔案系統
  //W25qxx_EraseChip();
  //res = f_mkfs(FLASHPath, FM_ANY, 0, work, _MAX_SS);
  char buff[256];
 // mount 3 volumes
  res=f_mount(&fs_sd, SDPath, 0);
  res=f_mount(&fs_spi_sd, USERPath, 0);
  res=f_mount(&fs_spi_flash, FLASHPath, 0);
  //create a file in volume0
  memset(fname,0,256);
  strcpy(fname,SDPath);
  strcat(fname, "sd_file.txt");
  res = f_open(&file_sd, fname, FA_CREATE_NEW|FA_WRITE|FA_READ);
  //create a file in volume1
  memset(fname,0,256);
  strcpy(fname,USERPath);
  strcat(fname, "sd_spi_file.txt");
  res = f_open(&file_spi_sd, fname, FA_CREATE_NEW|FA_WRITE|FA_READ);
  //create a file in volume2
  memset(fname,0,256);
  strcpy(fname,FLASHPath);
  strcat(fname, "flash_spi_file.txt");
  res = f_open(&file_spi_flash, fname, FA_CREATE_NEW|FA_WRITE|FA_READ);
  sprintf(buff, "write test to sd 寫入SD\n");
  res=f_write(&file_sd, buff, strlen(buff), &bw);
  sprintf(buff, "write test to spi sd 寫入SPI SD\n");
  res=f_write(&file_spi_sd, buff, strlen(buff), &bw);
  sprintf(buff, "write test to flash 寫入FLASH\n");
  res=f_write(&file_spi_flash, buff, strlen(buff), &bw);
  res=f_close(&file_sd);
  res=f_close(&file_spi_sd);
  res=f_close(&file_spi_flash);
//copy a image file from volume0 to volume1
  memset(fname,0,256);
  strcpy(fname,SDPath);
  strcat(fname, "test16.bmp");
  res = f_open(&file_sd, fname, FA_READ);
  memset(fname,0,256);
  strcpy(fname,USERPath);
  strcat(fname, "test16.bmp");
  res = f_open(&file_spi_sd, fname, FA_CREATE_NEW|FA_WRITE|FA_READ);
  do
  {
	  memset(buff, 0, 256);
	  res = f_read(&file_sd, buff, 256, &br);
	  if (res == FR_OK)
	  {
		  res= f_write(&file_spi_sd, buff, br, &bw);
	  }

  }while (res==FR_OK && br > 0);
  res=f_close(&file_sd);
  res=f_close(&file_spi_sd);

  res=f_mount(&fs_sd, "", 0);
  res=f_mount(&fs_spi_sd, "", 0);
  res=f_mount(&fs_spi_flash, "", 0);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* 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 PeriphClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 7;
  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_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
  PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @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 sDate = {0};

  /* USER CODE BEGIN RTC_Init 1 */

  /* USER CODE END RTC_Init 1 */
  /** Initialize RTC Only
  */
  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = 127;
  hrtc.Init.SynchPrediv = 255;
  hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
  hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
  hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }

  /* USER CODE BEGIN Check_RTC_BKUP */

  /* USER CODE END Check_RTC_BKUP */

  /** Initialize RTC and set the Time and Date
  */
  sTime.Hours = 0;
  sTime.Minutes = 0;
  sTime.Seconds = 0;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  sDate.WeekDay = RTC_WEEKDAY_MONDAY;
  sDate.Month = RTC_MONTH_JANUARY;
  sDate.Date = 1;
  sDate.Year = 21;

  if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN RTC_Init 2 */

  /* USER CODE END RTC_Init 2 */

}

/**
  * @brief SDIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_SDIO_SD_Init(void)
{

  /* USER CODE BEGIN SDIO_Init 0 */

  /* USER CODE END SDIO_Init 0 */

  /* USER CODE BEGIN SDIO_Init 1 */

  /* USER CODE END SDIO_Init 1 */
  hsd.Instance = SDIO;
  hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
  hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
  hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
  hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
  hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
  hsd.Init.ClockDiv = 0;
  /* USER CODE BEGIN SDIO_Init 2 */

  /* USER CODE END SDIO_Init 2 */

}

/**
  * @brief SPI1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_SPI1_Init(void)
{

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  /* USER CODE BEGIN SPI1_Init 1 */

  /* USER CODE END SPI1_Init 1 */
  /* SPI1 parameter configuration*/
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */

  /* USER CODE END SPI1_Init 2 */

}

/**
  * @brief SPI2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_SPI2_Init(void)
{

  /* USER CODE BEGIN SPI2_Init 0 */

  /* USER CODE END SPI2_Init 0 */

  /* USER CODE BEGIN SPI2_Init 1 */

  /* USER CODE END SPI2_Init 1 */
  /* SPI2 parameter configuration*/
  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_MASTER;
  hspi2.Init.Direction = SPI_DIRECTION_2LINES;
  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi2.Init.NSS = SPI_NSS_SOFT;
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi2.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI2_Init 2 */

  /* USER CODE END SPI2_Init 2 */

}

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Stream3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream3_IRQn);
  /* DMA1_Stream4_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn);
  /* DMA2_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
  /* DMA2_Stream6_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream6_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream6_IRQn);

}

/**
  * @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_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(SD_CS_GPIO_Port, SD_CS_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : PC13 */
  GPIO_InitStruct.Pin = GPIO_PIN_13;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  /*Configure GPIO pin : SD_CS_Pin */
  GPIO_InitStruct.Pin = SD_CS_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(SD_CS_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : PA6 */
  GPIO_InitStruct.Pin = GPIO_PIN_6;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pin : FLASH_CS_Pin */
  GPIO_InitStruct.Pin = FLASH_CS_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(FLASH_CS_GPIO_Port, &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 */
  __disable_irq();
  while (1)
  {
  }
  /* 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,
     ex: 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年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:
  

}