MCU
STM32F103(ST)
IDE
STM32CubeIDE
목표
I2C LCD(SSD1306)를 구동함

 

0. SSD1306

- SSD1306은 단일 칩 CMOS OLED/PLED 드라이버로, 유기/고분자 발광을 위한 컨트롤러

- OLED는 저전력 소비, 높은 대비, 선명한 이미지 품질로 알려져 있어 작은 전자 프로젝트, 웨어러블 기기 및 임베디드 시스템에 이상적

- 다이오드 도트 매트릭스 그래픽 디스플레이 시스템. 128개의 세그먼트와 64개의 공통으로 구성

- SSD 1306에는 256단계 밝기 제어, 디스플레이 RAM 및 오실레이터가 내장

- 일반적으로 다양한 크기로 제공되며, 0.96인치에서부터 더 큰 크기도 있음

- SSD1306 디스플레이를 프로젝트에서 사용하려면 I2C(Inter-Integrated Circuit) 사용하여 통신

 

- SSD1306 Datashet

 

1. STM32CubeIDE - CubeMX

- I2C2 Mode and Configuration : Disable → I2C

- Project - Generate Code


 

2. github에서 library 다운로드

https://github.com/afiskon/stm32-ssd1306

 

GitHub - afiskon/stm32-ssd1306: STM32 library for working with OLEDs based on SSD1306, SH1106, SH1107 and SSD1309, supports I2C

STM32 library for working with OLEDs based on SSD1306, SH1106, SH1107 and SSD1309, supports I2C and SPI - afiskon/stm32-ssd1306

github.com

 

- *.c는 프로젝트 폴더의 Core\Src 폴더 안에 넣음

- *.h는 프로젝트 폴더의 Core\Inc 폴더 안에 넣음

- Build Project 하면 자동으로 Compile 함

- ssd1306.c, ssd1306.h : ssd1306을 사용하기 위한 함수 모음

- ssd1306_fonts.c, ssd1306_fonts.h : 글자를 뿌리기 위한 폰트(크기별)

 

3. STM32CubeIDE - IDE

- main.c의 main() 수정

  /* USER CODE BEGIN 2 */
  ssd1306_Init();
  HAL_Delay(1000);

  ssd1306_SetCursor(0,0);
  ssd1306_WriteString("LINE1", Font_7x10, White);

  ssd1306_SetCursor(0,10);
  ssd1306_WriteString("LINE2", Font_7x10, White);

  ssd1306_SetCursor(0,20);
  ssd1306_WriteString("LINE3", Font_7x10, White);

  ssd1306_SetCursor(0,30);
  ssd1306_WriteString("LINE4", Font_7x10, White);

  ssd1306_SetCursor(0,40);
  ssd1306_WriteString("LINE5", Font_7x10, White);

  ssd1306_SetCursor(0,50);
  ssd1306_WriteString("LINE6", Font_7x10, White);

  ssd1306_UpdateScreen();
  /* USER CODE END 2 */
 

- 실행 결과

 

'ST > STM32F103C8' 카테고리의 다른 글

STM32F103C8 - RAM 48KB + bonus?  (0) 2025.08.08
STM32F103C8 - LL driver + ADC + DMA  (0) 2025.08.08
STM32F103C8 - 1cycle Time  (0) 2025.08.08
STM32F103C8 - I2C HAL driver 오류  (0) 2025.08.08
STM32F103C8 - ADC(DMA interrupt)  (1) 2025.08.08

 

STM32F103xx family의 Flash와 RAM 용량은 다음과 같다.

STM32F103VC datasheet 중

Flash 256KB, RAM 48KB인 STM32F103VCT6을 사용하는데 RAM이 48KB보다 큰것 같다???

 

이런건 참을수 없지! 바로 확인해 보자.

 

1. STM32F103VC_FLASH.ld 수정

_estack = 0x2000C000 → 0x2000CFFF

(C000 = 48KB를 넘어가는 임의의 값 CFFF로 설정)

 

_Min_Stack_Size = 0x400 → 0x9500

(stack 첫 주소가 48KB를 넘어가는 값으로 임의 설정. Descending Satck이라 스택 포인터가 맨 처음에 최상위 어드레스를 지정하고 낮은곳으로 자람)

/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = 0x2000CFFF;    /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200;      /* required amount of heap  */
_Min_Stack_Size = 0x9500; /* required amount of stack */

/* Specify the memory areas */
MEMORY
{
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 48K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 256K
}
 

 

2. 컴파일 후에 map 파일을 열어서 stack이 48KB, 0x2000C000을 넘어갔는지 확인한다.

._user_heap_stack
                0x20002cf4     0x9700 load address 0x080114a0
                0x20002cf4                . = ALIGN (0x4)
                0x20002cf4                PROVIDE (end, .)
                [!provide]                PROVIDE (_end, .)
                0x20002ef4                . = (. + _Min_Heap_Size)
 *fill*         0x20002cf4      0x200 
                0x2000c3f4                . = (. + _Min_Stack_Size)
 *fill*         0x20002ef4     0x9500 
                0x2000c3f4                . = ALIGN (0x4)
 

실행 결과 아주 잘 돌아간다.

 

- 같은 계열은 전부 동일한 RAM size로 제작 했을것으로 생각된다. 넉넉한 인심!

(혹시 Flash도 크게 넣어 줬을까?)

(하지만 이런거 필요 없어요, shortage나 좀 해결해줘요)

- ST MCU shortage로 GigaDevice 복제품 테스트시 실행 되지 않는다. RAM 48KB

 

'ST > STM32F103C8' 카테고리의 다른 글

STM32F103C8 - I2C LCD(SSD1306)  (0) 2025.08.08
STM32F103C8 - LL driver + ADC + DMA  (0) 2025.08.08
STM32F103C8 - 1cycle Time  (0) 2025.08.08
STM32F103C8 - I2C HAL driver 오류  (0) 2025.08.08
STM32F103C8 - ADC(DMA interrupt)  (1) 2025.08.08

 

MCU
STM32F103(ST)
IDE
Atollic(TrueSTUDIO), STM32CubeMX
목표
LL driver를 사용하여 ADC를 DMA mode로 conversion 한다.

 

1. STM32CubeMX

- DMA, ADC LL driver 설정

 

- ADC Configuration 확인

ADC_Settings >

1) Scan Convsersion Mode : Disabled

여러 채널을 샘플링 하는 경우 scan 모드를 enable 하고 한개 채널만 샘플링 하는 경우 disable

2) Continous conversion mode : Disabled

3) Discontinous conversion mode : Disabled

 

ADC_Regular_Conversion Mode >

1) Enable Regular Conversions : Enable

2) Number of Conversion : 1

3) 각 Rank 별로 원하는 ADC 채널 선택

 

- DMA Settings 확인

1) DMA Request : ADC1

2) Data Width : Word(uint32_t)

3) Mode : Circular

- GENERATE CODE 클릭


2. ATOLLIC

- main.c 수정

uint32_t adc_val[20] = {1, };

LL_DMA_DisableChannel(DMA1,LL_DMA_CHANNEL_1);

LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_1,
					  LL_ADC_DMA_GetRegAddr(ADC1, LL_ADC_DMA_REG_REGULAR_DATA),
					  (uint32_t)&adc_val, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);

LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, 4);

LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1);
LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_1);

LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_1);

LL_ADC_REG_SetDMATransfer(ADC1,LL_ADC_REG_DMA_TRANSFER_UNLIMITED);
LL_ADC_Enable(ADC1);

HAL_GPIO_WritePin (LED_WDT_GPIO_Port, LED_WDT_Pin, GPIO_PIN_RESET);
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
	LL_ADC_REG_StartConversionSWStart(ADC1);
	printf("%d\n", adc_val[0]);
}
 
static void MX_ADC1_Init(void)
{

  /* USER CODE BEGIN ADC1_Init 0 */

  /* USER CODE END ADC1_Init 0 */

  LL_ADC_InitTypeDef ADC_InitStruct = {0};
  LL_ADC_CommonInitTypeDef ADC_CommonInitStruct = {0};
  LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};

  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* Peripheral clock enable */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC1);

  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOC);
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOB);
  /**ADC1 GPIO Configuration
  PA2   ------> ADC1_IN2
  PC5   ------> ADC1_IN15
  PB0   ------> ADC1_IN8
  PB1   ------> ADC1_IN9
  */
  GPIO_InitStruct.Pin = LL_GPIO_PIN_2;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = LL_GPIO_PIN_5;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
  LL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = LL_GPIO_PIN_0|LL_GPIO_PIN_1;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
  LL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /* ADC1 DMA Init */

  /* ADC1 Init */
  LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);

  LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_LOW);

  LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);

  LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT);

  LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);

  LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_WORD);

  LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_WORD);

  /* USER CODE BEGIN ADC1_Init 1 */

  /* USER CODE END ADC1_Init 1 */
  /** Common config
  */
  ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT;
  ADC_InitStruct.SequencersScanMode = LL_ADC_SEQ_SCAN_DISABLE;
  LL_ADC_Init(ADC1, &ADC_InitStruct);
  ADC_CommonInitStruct.Multimode = LL_ADC_MULTI_INDEPENDENT;
  LL_ADC_CommonInit(__LL_ADC_COMMON_INSTANCE(ADC1), &ADC_CommonInitStruct);
  ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
  ADC_REG_InitStruct.SequencerLength = 1;
  ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
  ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE;
  ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED;
  LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);
  /** Configure Regular Channel
  */
  LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, LL_ADC_CHANNEL_15);
  LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_15, LL_ADC_SAMPLINGTIME_55CYCLES_5);
  /* USER CODE BEGIN ADC1_Init 2 */

  /* USER CODE END ADC1_Init 2 */

}
 

 

- stm32f1xx_it.c : DMA1_Channel1_IRQHandler(void) 수정

void DMA1_Channel1_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel1_IRQn 0 */
	 if( LL_DMA_IsActiveFlag_TC1(DMA1))
	 {
		  LL_DMA_ClearFlag_TC1(DMA1);
	 }
	 else if(LL_DMA_IsActiveFlag_TE1(DMA1))
	 {
	 	 LL_DMA_ClearFlag_TE1(DMA1);
	 }
  /* USER CODE END DMA1_Channel1_IRQn 0 */

  /* USER CODE BEGIN DMA1_Channel1_IRQn 1 */

  /* USER CODE END DMA1_Channel1_IRQn 1 */
}
 

 

- 실행 결과

 

'ST > STM32F103C8' 카테고리의 다른 글

STM32F103C8 - I2C LCD(SSD1306)  (0) 2025.08.08
STM32F103C8 - RAM 48KB + bonus?  (0) 2025.08.08
STM32F103C8 - 1cycle Time  (0) 2025.08.08
STM32F103C8 - I2C HAL driver 오류  (0) 2025.08.08
STM32F103C8 - ADC(DMA interrupt)  (1) 2025.08.08

 

STM32의 1cycle은 과연 1/주파수(frequency) = 주기(period)가 맞을까?

어떻게 측정을 할까? 실제로 1cycle이 몇초인지 측정해 보았다.(STM32F103C8으로 테스트)

 

1. BIT Toggle 시간 측정

   while (1)
   {
	  GPIOB->ODR |= GPIO_PIN_12;
	  GPIOB->ODR &= ~GPIO_PIN_12;
   }
 

- duty가 50%가 안나온다. while()로 돌렸지만, for문과 같다고 봐야 한다.

(PORT를 low로 만들고 while문 나와서 다시 들어가는데 시간이 걸린다는 뜻)

- high일때의 시간은 124.7332ns

 

2. BIT Toggle + 1cycle 명령어(SHIFT) 추가 하여 시간 측정

   while (1)
   {
	  GPIOB->ODR |= GPIO_PIN_12;
	  delay_count << 1;
	  GPIOB->ODR &= ~GPIO_PIN_12;
	  delay_count << 1;
   }
 

- high일때의 시간은 138.4076ns

- 위와 같이 했는데 1번과 시간이 같다면 컴파일러가 최적화 시키면서 지운것이므로 delay_count에 volatile 붙일 것

- asm 코드를 봐야 하지만 디버거를 붙이지 않아서 확인 하지 못함

2번 high 시간 - 1번 high 시간 = 1cycle time = 1/주파수 = 주기
138.4076 - 124.7332 = 13.6744ns ≒ 1 / 72MHz = 13.89ns

 

PORT Toggle에 생각보다 많은 시간이 걸린다.

작업하던 project에서 테스트 코드만 넣어서 측정했더니 위와 같이 시간이 나온다.

 

3. BIT 제어 레지스터 + 1cycle 명령어(SHIFT) 추가 하여 시간 측정

   while (1)
   {
	  GPIOB->BSRR = GPIO_PIN_12;
	  delay_count << 1;
	  GPIOB->BRR = GPIO_PIN_12;
	  delay_count << 1;
   }
 

- high일때의 시간은 82.8843ns

 

 

'ST > STM32F103C8' 카테고리의 다른 글

STM32F103C8 - RAM 48KB + bonus?  (0) 2025.08.08
STM32F103C8 - LL driver + ADC + DMA  (0) 2025.08.08
STM32F103C8 - I2C HAL driver 오류  (0) 2025.08.08
STM32F103C8 - ADC(DMA interrupt)  (1) 2025.08.08
STM32F103C8 - I2C(EEPROM)  (1) 2025.08.08

 

ATOLLIC에서 I2C HAL driver를 사용하여 I2C 방식 EEPROM 을 읽고 쓰는 글을 올렸었다.

테스트 과정에서 EEPROM 읽기 쓰기가 동작 했다 안했다 하여 한참 고생한 적이 있다.

 

정확한 원인은 찾지 못했으나,

- 오실로스코프로 측정 하여 MX_I2C1_Init() 과정에서 이상 파형이 출력되는 것을 확인

- 기준 clock이 enable 되는 시점이 문제라고 판단하여 그부분을 수정하였고 정상 동작됨을 확인 하였다.

(아래 파형 사진은 Time division이 모두 같음)

 

1. 현상

: I2C 초기화 중 SDA, SCL에 pulse 파형이 출력(MX_I2C1_Init()만 호출해도 결과는 같음)

 

2. 조치

: MX_I2C1_Init()의 I2C clock enable 시점을 변경하여 문제 해결

(회로 변경 X)

 

재미있는건, ST MCU shortage로 GigaDevice 복제품을 테스트 중이라 2번 조치 없이 I2C를 테스트 해보았는데, 파형은 아래와 같으며 정상 동작 한다.

 

정확한 원인을 아시거나, 같은 문제로 고민하시는 분은 댓글 달아주시면 많은 도움이 될것입니다.

 

'ST > STM32F103C8' 카테고리의 다른 글

STM32F103C8 - LL driver + ADC + DMA  (0) 2025.08.08
STM32F103C8 - 1cycle Time  (0) 2025.08.08
STM32F103C8 - ADC(DMA interrupt)  (1) 2025.08.08
STM32F103C8 - I2C(EEPROM)  (1) 2025.08.08
STM32F103C8 - RTC(Systick handler)  (0) 2025.08.08

 

MCU
STM32F103(ST)
IDE
Atollic(TrueSTUDIO), STM32CubeMX
목표
HAL driver를 사용하여 ADC를 여러개를 DMA interrupt mode로 주기적으로 multi conversion 한다.

 

1. STM32CubeMX : ADC-DMA 설정

- 'Pinout & Configuration' Tab으로 이동

1) Analog 선택

2) ADC1 항목 선택

 

- ADC Configuration 확인

ADC_Settings >

1) Scan Convsersion Mode : Enabled

여러 채널을 샘플링 하는 경우 scan 모드를 enable 하고 한개 채널만 샘플링 하는 경우 disable

2) Continous conversion mode : Enabled

3) Discontinous conversion mode : Disabled

 

ADC_Regular_Conversion Mode >

1) Enable Regular Conversions : Enable

2) Number of Conversion : 3

3) 각 Rank 별로 원하는 ADC 채널 선택

 

- DMA Settings 확인

1) DMA Request : ADC1

2) ADC1 and ADC2 global interrupts : Enabled에 체크

3)Data Width : Word(uint32_t), Half Word(uint16_t)

4)Mode : Normal(HAL_ADC_Start_DMA() 함수를 계속 호출해야 함),

Circular(HAL_ADC_Start_DMA() 함수를 최초에 한번만 호출하면 됨)

- GENERATE CODE 클릭


2. ATOLLIC : main.c 수정

- 변수 선언 ; CubeMX에서 Half Word(uint16_t)로 설정

/* USER CODE BEGIN 0 */
uint16_t adc_read[3] = {1, };
 

- HAL_ADC_Start_DMA 호출 ; CubeMX에서 DMA 설정을 Circular로 했기 때문에 1번만 호출해 주면됨(매번 호출할 필요 없음)

- DMA와 설정은 같으나, interrupt handler에서 주기적으로 읽어오기 위해 아래와 같은 방법을 사용합니다.

/* USER CODE BEGIN 2 */
HAL_ADC_Start_DMA(&hadc1,(uint16_t*)&(adc_read[0]),3);

/* USER CODE END 2 */
 

- ATOLLIC : stm32f1xx_it.c 수정

아래와 같이 작성하였는데 동일 값이 두번씩 중복되어 들어옴

void DMA1_Channel1_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel1_IRQn 0 */

  /* USER CODE END DMA1_Channel1_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_adc1);
  /* USER CODE BEGIN DMA1_Channel1_IRQn 1 */
	if(adc_flag == 1)
	{
		adc_read1[adc_index] = adc_read[0];
		adc_read2[adc_index] = adc_read[1];
		adc_read3[adc_index++] = adc_read[2];
		if(adc_index > 999) {
			adc_flag = 0;
		}
	}
  /* USER CODE END DMA1_Channel1_IRQn 1 */
}
 

- ATOLLIC : stm32f1xx_hal_dma.c 수정

- HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma) 함수 안으로 들어가 보면 Half Transfer인지 Full Trasfer인지 flag 확인하는 부분이 있는데 Full Trasfer안에 코드 작성

/* Transfer Complete Interrupt management ***********************************/
else if (((flag_it & (DMA_FLAG_TC1 << hdma->ChannelIndex)) != RESET) && ((source_it & DMA_IT_TC) != RESET))
{
	if(adc_flag == 1)
	{
		adc_read1[adc_index] = adc_read[0];
		adc_read2[adc_index] = adc_read[1];
		adc_read3[adc_index++] = adc_read[2];
		if(adc_index > 999) {
			adc_flag = 0;
		}
	}
 

- ADC를 DMA로 읽어오면서 굳이 interrupt안에서 짤 필요가 있나 생각할 수 있는데 필요에 의해 위와 같이 작성. 일반적인 상황에서는 DMA로 지정한 변수(adc_read)만 사용해도 충분함

 

 

'ST > STM32F103C8' 카테고리의 다른 글

STM32F103C8 - 1cycle Time  (0) 2025.08.08
STM32F103C8 - I2C HAL driver 오류  (0) 2025.08.08
STM32F103C8 - I2C(EEPROM)  (1) 2025.08.08
STM32F103C8 - RTC(Systick handler)  (0) 2025.08.08
STM32F103C8 - PWM  (0) 2025.08.08

 

MCU
STM32F103(ST)
IDE
Atollic(TrueSTUDIO), STM32CubeMX
목표
HAL driver를 사용하여 I2C EEPROM 쓰고 읽기

 

1. STM32F103C8 - I2C EEPROM 회로도

- SDA, SCL pull-up 저항 값은 아래와 같이 설정되므로 적당히 아무거나(10k) 붙이면 통신이 안될수도 있음(NXP, UM10204 참고)

 

2. STM32CubeMX : I2C 설정

- 'Pinout & Configuration' Tab으로 이동

1) I2C1 선택

2) I2C : I2C 선택

3) I2C Clock Speed는 기본 100kHz로 설정된다. 사용하려는 device가 이 속도를 지원하는지 확인 할것

- PB7(I2C1_SDA), PB6(I2C1_SCL) 자동 할당(녹색 표시)

- GENERATE CODE 클릭


3. ATOLLIC : main.c 수정

- main() : 변수 선언

int main(void)
{
  /* USER CODE BEGIN 1 */
	HAL_StatusTypeDef status = HAL_ERROR;

	int i = 0;

	uint16_t FM24V02_addr = 0xA0;

	uint8_t tx_buffer[65] = {};
	uint8_t rx_buffer[65] = {};

  /* USER CODE END 1 */
 

- Address는 A2, A1, A0를 GND('0')에 연결하여 '0b1010000'이다.

- HAL_I2C_Mem_Write(), Read() 사용하려면 7bit address를 <<1 shift 하라고 되어있으므로 최종 주소는 '0xA0' (HAL_I2C_Mem_Write() 설명은 ATOLLIC에서 함수 이름에 마우스 커서를 올려놓거나, 키보드 F3을 누르거나 또는 HAL lib manual에서 확인 가능)

 

- main() : 실행 코드 작성

  /* USER CODE BEGIN 2 */
	tx_buffer[0] = 0x12;

	// Byte Write
	status = HAL_I2C_Mem_Write(&hi2c1, FM24V02_addr, 0x0001, 2, tx_buffer, 1, 1000);
	if(status != HAL_OK)
	{
		printf("I2C Byte Write error\n");
	}

	// Byte Read
	status = HAL_I2C_Mem_Read(&hi2c1, FM24V02_addr, 0x0001, 2, rx_buffer, 1, 1000);
	if(status == HAL_OK)
	{
		printf("Byte Read : %d\n", rx_buffer[0]);
	}
	else
	{
		printf("I2C Byte Read error\n");
	}

	// Page Write
	for (i = 0; i < 64; i++)
	{
		tx_buffer[i] = i;
	}
	status = HAL_I2C_Mem_Write(&hi2c1, FM24V02_addr, 0x0100, 2, tx_buffer, 64, 1000);
	if(status != HAL_OK)
	{
		printf("I2C Page Write error\n");
	}

	// Page Read
	status = HAL_I2C_Mem_Read(&hi2c1, FM24V02_addr, 0x0100, 2, rx_buffer, 64, 1000);
	if(status == HAL_OK)
	{
		for (i = 0; i < 64; i++)
		{
			printf("Page Read(%d) : %d\n", i, rx_buffer[i]);
		}
	}
	else
	{
		printf("I2C Page Read error\n");
	}

  /* USER CODE END 2 */
 

- 실행 결과

- 실행 결과 이하 생략

 

- 참고 : 일부러 Address를 틀리게 썼을때 실행 결과


4. 8bit 데이터는 위와 같이 하면 문제 없이 동작하나, 16bit 데이터(256이상)를 쓰고 읽어오려면 약간 다른 방법으로 해야 한다.

- write() : 16bit 데이터를 8bit로 쪼개서 쓰기

- read() : 8bit씩 읽어서 16bit로 합침

uint16_t FM24V02_addr = 0xA0;
uint8_t reg_addr = 0x0001;

uint8_t data_8bit = 0;
uint16_t data_16bit[65] = {};

void eeprom_i2c_write(uint16_t size)
{
	HAL_StatusTypeDef status = HAL_ERROR;
	reg_addr = 0x0001;

	for(int i = 0; i< size; i++)
    {
		data_8bit = (uint8_t)(data_16bit[i] >> 8);
		status = HAL_I2C_Mem_Write(&hi2c1, FM24V02_addr, reg_addr+(i*2), 2, &data_8bit, 1, 1000);
		HAL_Delay(10);

		data_8bit = (uint8_t)(data_16bit[i] & 0xff);
		status = HAL_I2C_Mem_Write(&hi2c1, FM24V02_addr, reg_addr+(i*2)+1, 2, &data_8bit, 1, 1000);

		if(status != HAL_OK)
		{
		    printf("EEPROM Byte Write error : %d\n", status);
		}
    }
}

void eeprom_i2c_read(uint16_t size)
{
	HAL_StatusTypeDef status = HAL_ERROR;
	reg_addr = 0x0001;

    for(int i = 0; i< size; i++)
    {
    	status = HAL_I2C_Mem_Read(&hi2c1, FM24V02_addr, reg_addr+(i*2), 2, &data_8bit , 1, 1000);
    	data_16bit[i] = data_8bit << 8;
        HAL_Delay(10);

        status = HAL_I2C_Mem_Read(&hi2c1, FM24V02_addr, reg_addr+(i*2)+1, 2, &data_8bit , 1, 1000);
        data_16bit[i] += data_8bit;

		if(status == HAL_OK)
		{
			printf("EEPROM Page Read(%d) : %d\n", i, data_16bit[i]);
		}
		else
		{
			printf("EEPROM Byte Read error\n");
		}
    }
}
 

'ST > STM32F103C8' 카테고리의 다른 글

STM32F103C8 - I2C HAL driver 오류  (0) 2025.08.08
STM32F103C8 - ADC(DMA interrupt)  (1) 2025.08.08
STM32F103C8 - RTC(Systick handler)  (0) 2025.08.08
STM32F103C8 - PWM  (0) 2025.08.08
STM32F103C8 - TIMER  (1) 2025.08.08

 

MCU
STM32F103(ST)
IDE
Atollic(TrueSTUDIO), STM32CubeMX
목표
HAL driver를 사용하여 RTC tick을 이용해 LED를 ON-OFF 한다.

 

1. ATOLLIC 실행

RTC 설정은 STM32F103C8 - Project 생성 및 기본 설정 을 참고(이 과정이 선행 되어야함)

RTC tick을 이용해 LED를 ON-OFF 하는데 구현 방법은 2가지로 나눌 수 있다.

 

1. void SysTick_Handler()에 바로 작성

extern uint32_t uwTick;

void SysTick_Handler(void)
{
	/* USER CODE BEGIN SysTick_IRQn 0 */

	/* USER CODE END SysTick_IRQn 0 */
	HAL_IncTick();
	/* USER CODE BEGIN SysTick_IRQn 1 */
	if ( uwTick % 500 == 0 )
	{
		HAL_GPIO_TogglePin (LED_WDT_GPIO_Port, LED_WDT_Pin);

	}
	/* USER CODE END SysTick_IRQn 1 */
}
 

 

2. void SysTick_Handler()에서 HAL_SYSTICK_IRQHandler();를 호출하게 수정

void SysTick_Handler(void)
{
	/* USER CODE BEGIN SysTick_IRQn 0 */

	/* USER CODE END SysTick_IRQn 0 */
	HAL_IncTick();
	/* USER CODE BEGIN SysTick_IRQn 1 */
	HAL_SYSTICK_IRQHandler();
	/* USER CODE END SysTick_IRQn 1 */
}
 

HAL_SYSTICK_IRQHandler();를 따라가 보면 HAL_SYSTICK_Callback();를 호출하게 되어있다.

void HAL_SYSTICK_IRQHandler(void)
{
	HAL_SYSTICK_Callback();
}
 

바로 아래 HAL_SYSTICK_Callback(); 함수가 있는데 비어있다.

__weak void HAL_SYSTICK_Callback(void)
{
  /* NOTE : This function Should not be modified, when the callback is needed,
            the HAL_SYSTICK_Callback could be implemented in the user file
   */
}
 

Callback 함수를 작성해 준다.

extern uint32_t uwTick;

void HAL_SYSTICK_Callback(void)
{
	if ( uwTick % 500 == 0) {
		HAL_GPIO_TogglePin (LED_WDT_GPIO_Port, LED_WDT_Pin);
	}
}
 

'ST > STM32F103C8' 카테고리의 다른 글

STM32F103C8 - ADC(DMA interrupt)  (1) 2025.08.08
STM32F103C8 - I2C(EEPROM)  (1) 2025.08.08
STM32F103C8 - PWM  (0) 2025.08.08
STM32F103C8 - TIMER  (1) 2025.08.08
STM32F103C8 - ADC(DMA)  (0) 2025.08.08

 

MCU
STM32F103(ST)
IDE
Atollic(TrueSTUDIO), STM32CubeMX
목표
HAL driver를 사용하여 PWM으로 20KHz에 duty 50의 파형을 만든다.

 

1. STM32CubeMX : PWM 설정

- 'Pinout & Configuration' Tab으로 이동

1) TIM2 선택

2) Channel 1 : PWM Generation CH1 선택

3) Prescaler : 32-1

- 64MHz / 32 = 2,000,000 = 2MHz

4) Counter Period : 100-1

- 2,000,000 / 100 = 20,000 = 20KHz

5) PWM Generation Channel1 > Pulse : 50

- 20KHz에 duty 50인 파형 발생

- GENERATE CODE 클릭


2. ATOLLIC : main.c 수정

- HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_RTC_Init();
MX_USART1_UART_Init();
MX_TIM6_Init();
MX_TIM7_Init();
/* USER CODE BEGIN 2 */
HAL_ADC_Start_DMA(&hadc1,(uint16_t*)&(adc_val[0]),3);
HAL_TIM_Base_Start_IT (&htim6);
HAL_TIM_Base_Start_IT (&htim7);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
/* USER CODE END 2 */
 

 

'ST > STM32F103C8' 카테고리의 다른 글

STM32F103C8 - I2C(EEPROM)  (1) 2025.08.08
STM32F103C8 - RTC(Systick handler)  (0) 2025.08.08
STM32F103C8 - TIMER  (1) 2025.08.08
STM32F103C8 - ADC(DMA)  (0) 2025.08.08
STM32F103C8 - ADC(Interrupt)  (0) 2025.08.08

 

MCU
STM32F103(ST)
IDE
Atollic(TrueSTUDIO), STM32CubeMX
목표
HAL driver를 사용하여 TIMER로 LED를 1Hz(1초에 한번 켜졌다 꺼졌다)로 ON-OFF 한다.

 

TIMER6은 외부 출력이 없는 내부 타이머. 사용에 앞서 TIM6의 기본 clock이 얼마인지 확인해야 한다. TIMER6은 APB1에 연결 되어 있음을 확인 할 수 있다.

APB1 Timer clock = 64MHz

 

1. STM32CubeMX : TIMER 설정

- 'Pinout & Configuration' Tab으로 이동

1) TIM6 선택

2) Activated에 체크

3) Prescaler : 3200-1

- 64MHz / 3,200 = 20,000 = 20KHz

4) Counter Period : 10000-1

- 1/20,000 * 10,000 = 0.5s 마다 인터럽트 발생

5) TIM6 global interrupt : Enbled

- GENERATE CODE 클릭


2. ATOLLIC : main.c 수정

- HAL_TIM_Base_Start_IT (&htim6); 인터럽트 시작을 위해 추가

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_RTC_Init();
MX_USART1_UART_Init();
MX_TIM6_Init();
MX_TIM7_Init();
/* USER CODE BEGIN 2 */
HAL_ADC_Start_DMA(&hadc1,(uint16_t*)&(adc_val[0]),3);
HAL_TIM_Base_Start_IT (&htim6);
HAL_TIM_Base_Start_IT (&htim7);
/* USER CODE END 2 */
 

- ATOLLIC : stm32f1xx_it.c 수정

- void TIM6_IRQHandler(void) 수정

- CubeMX 설정에서 0.5초마다 인터럽트가 걸리게 설정하였고 인터럽트 핸들러에서 매 인터럽트 마다 포트를 토글 하므로 1초마다 LED가 깜박임.

void TIM6_IRQHandler(void)
{
  /* USER CODE BEGIN TIM6_IRQn 0 */

  /* USER CODE END TIM6_IRQn 0 */
  HAL_TIM_IRQHandler(&htim6);
  /* USER CODE BEGIN TIM6_IRQn 1 */
  //TIM6->ARR = 10000;
  HAL_GPIO_TogglePin(LED_WDT_GPIO_Port, LED_WDT_Pin);
  /* USER CODE END TIM6_IRQn 1 */
}
 

직접 인터럽트 핸들러를 수정하는 방법도 있고 callback 함수를 추가하는 방법도 있으나, 여기서는 TIM6가 내부 인터럽트만 존재하므로(한가지) 인터럽트 핸들러를 수정하였음.

 

'ST > STM32F103C8' 카테고리의 다른 글

STM32F103C8 - RTC(Systick handler)  (0) 2025.08.08
STM32F103C8 - PWM  (0) 2025.08.08
STM32F103C8 - ADC(DMA)  (0) 2025.08.08
STM32F103C8 - ADC(Interrupt)  (0) 2025.08.08
STM32F103C8 - ADC(Polling)  (1) 2025.08.08

+ Recent posts