전원이 꺼지거나 리셋이 되도 지워지지 말아야할 정보를 사용할 경우, 

외부에 추가로 EEPROM 을 붙여서 SPI/I2C 로 읽고 쓸 수도 있지만,
STM32 MCU 에  Flash 메모리도 남고 아주 자주 쓰고 지우는 경우도 아니라서 Flash 메모리를 EEPROM 처럼 사용하는 방법을 알아보고 간단한 테스트를 해 보려고 합니다.

테스트하기 편하게 제가 갖고 있는 STM32F411 Nucleo 보드를 사용해서 STLINK 로 프로그램을 다운로드하고,
UART로 테스트 내용을 확인할 계획입니다. 추가로 외부에 연결할 부품도 필요 없기에 보드 사진은 생략합니다.

Nucleo 보드에서 사용하는 기능은 다음과 같습니다.
1. User SW 입력 (GPIO INPUT)
2. LED 출력 (GPIO OUT)
3. 디버그용 UART




내부 플레쉬 메모리를 읽고 쓰기 위해서 예전에 Custom DFU bootloader 를 만들 때 사용했던 함수들을 재활용해야겠습니다. 

메모리 엑세스 관련, 간단하게 함수 프로토 타입만 적어 놓겠고 자세한 내용은 예전 글을 참고 하시기 바랍니다.
1
2
3
4
5
6
7
8
uint16_t MEM_If_Init_FS(void);
uint16_t MEM_If_Erase_FS (uint32_t Add);
uint16_t MEM_If_Write_FS (uint8_t *src, uint8_t *dest, uint32_t Len);
uint8_t *MEM_If_Read_FS  (uint8_t *src, uint8_t *dest, uint32_t Len);
uint16_t MEM_If_DeInit_FS(void);
 
uint32_t GetSectorSize(uint32_t Sector);
uint32_t GetSector(uint32_t Address);
cs


1. MCU에서 사용할 메모리를 선택하기 전에 예상되는 코드 메모리를 계산.
현재 프로젝트를 진행하고 있는 프로그램에 적용할 생각으로 테스트를 하고 있는데, 프로그램 영역과 겹치지 않도록
남는 영역에 EEPROM Emulation 용 메모리를 선택해야 하므로, 프로그램 코드의 크기를 계산해 봐야 겠습니다.


[프로젝트로 진행중인 프로그램 크기 계산]




Total ROM Size = 37580(Code) + 1948(RO) + 344(RW) = 39872(0x9BC0)
Total RAM Size = 344(RW) + 71448(ZI) = 71792(0x11870)

64KByte = 64*1024 = 65536Byte(0x10000) 이므로, APP 프로그램(0x9BC0)는 0,1,2 Sector 범위에 들어 가고 약간 여분으로 프로그램을 더 추가 하더라도 3번 Sector를 넘어가진 않을 것 같습니다.

2. 위의 프로그램의 코드 계산의 토대로 메모리를 할당해 봤습니다.




APP 프로그램은 어드레스 0x0800 0000 부터 늘어나기 때문에, EEPROM Emulation 용 Flash 메모리는 뒤쪽에 안 쓰는 곳에 지정을 하는 게 맞고, 겨우 몇십 바이트 정도만 사용할 예정이라 가능한 작은 크기의 Sector를 할당하는 것이 좋겠습니다.

하지만, 지금 현재 개발 중인 APP 소스 크기가 39KB 에 달해서 Sector 0,1,2 를 사용하고 있습니다. 따라서 완료 시점까지 좀 여유있게 프로그램 메모리를 더 할당해야 해서 Sector 0~3 까지 64KB 를 할당하고, 아깝지만 EEPROM 영역을 Sector4(64KB) 로 할당을 해야 겠습니다. 뒤로 갈수록 섹터 크기가 커지니, 메모리가 어마어마하게 낭비되고 지우는 시간도 많이 걸릴 테지만, 128KB 를 쓰는 것은 더 아깝고 그다지 더 쓸 일도 없을 것 같으니 그만 안타까운 마음을 접기로 했습니다.

3. 버퍼 메모리(Read/Write 변수) 할당
EEPROM 이 아니라 지정된 메모리에 프로그램을 쓰려면, 전체 Sector를 지우고 변경할 내용을 한번에 써야하기에
메모리에 쓸 영역만큼의 SRAM 크기를 지정해야 합니다.
저는 아무리 사용해 봐야 512 개 정도면 충분할 것 같아서 512 바이트만큼 배열 변수로 할당했습니다.


4. EEPROM Emulator 테스트 프로그램 구상
- 메모리를 초기화 하기 위한 Flash 메모리 영역에 인식 ID 를 지정해서, 처음으로 메모리를 사용할 경우 디폴트 값으로 초기화를 하는 용도로 사용.
- 10-Byte의  Flash Memory 에, Nucleo 보드의 User-SW 를 1번 누를 때 마다 각각의 Byte 의 값이 1씩 증가되면서,
Write 한다. (현재 변화된 10-Byte의 Flash Memory 값을 읽어서 UART에 표시)
- 전원 Reset 시 10-Byte의 Flash Memory 값을 읽어서 UART에 표시하여 Flash 에 값이 남아있는지 확인. 


5. APP 프로그램의 코드영역이 EEPROM FLASH 영역을 침범하지 않도록 ROM Size 변경
ROM 크기를 기본 256KB 에서 64KB 로 변경해서, 만일 코드 사이즈가 커져서
EEPROM 영역에 데이터가 써지는 것을 방지한다.





6-0. eeprom emulation Test 프로그램의 #define .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 512KB
#define ADDR_FLASH_SECTOR_0     ((uint32_t)0x08000000/* Base @ of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1     ((uint32_t)0x08004000/* Base @ of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2     ((uint32_t)0x08008000/* Base @ of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3     ((uint32_t)0x0800C000/* Base @ of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4     ((uint32_t)0x08010000/* Base @ of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5     ((uint32_t)0x08020000/* Base @ of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6     ((uint32_t)0x08040000/* Base @ of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7     ((uint32_t)0x08060000/* Base @ of Sector 7, 128 Kbytes */
 
#define FLASH_USER_START_ADDR   ADDR_FLASH_SECTOR_4   /* Start @ of user Flash area */
#define FLASH_USER_END_ADDR     FLASH_USER_START_ADDR  +  GetSectorSize(FLASH_SECTOR_4) -1 /* End @ of user Flash area : sector start address + sector size -1 */
 
#define USR_MEM_CNT            512
#define USR_ASIGN_ID        0xA5
#define USR_ASIGN_Add        0
#define USR_DATA0_Add        1
 
cs

6-1. eeprom emulation Test 프로그램의 main() 함수 프로그램 소스코드.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
int main(void)
{
 
  /* USER CODE BEGIN 1 */
    uint8_t flash_buffer[512];
    __IO uint32_t data32 = 0;
    
  /* USER CODE END 1 */
 
  /* MCU Configuration----------------------------------------------------------*/
 
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
 
  /* USER CODE BEGIN 2 */
    printf("UART Test..!!\n\r");
    
    // Flash data memory Test..
    init_FLASH(flash_buffer);
    
    MEM_If_Read_FS ((uint8_t *)FLASH_USER_START_ADDR, flash_buffer, 512);
    dump_data(flash_buffer,0,16);
    
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
    uint8_t sw_stat,old_sw_stat;
    old_sw_stat = sw_stat = HAL_GPIO_ReadPin(USER_SW_GPIO_Port,USER_SW_Pin);
    
  while (1)
  {
    old_sw_stat = sw_stat;
    sw_stat = HAL_GPIO_ReadPin(USER_SW_GPIO_Port,USER_SW_Pin);
        
    if (old_sw_stat != sw_stat)
    {
        if (sw_stat == 0)
        {
            proc_sw_push(flash_buffer);
            MEM_If_Read_FS ((uint8_t *)FLASH_USER_START_ADDR, flash_buffer, 512);
            dump_data(flash_buffer,0,16);
        }
    }
        HAL_Delay(50);
  /* USER CODE END WHILE */
 
  /* USER CODE BEGIN 3 */
 
  }
  /* USER CODE END 3 */
 
}
cs


6-2. eeprom emulation Test 프로그램의 메모리 초기화 함수 프로그램 소스코드.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void init_FLASH(uint8_t *mem_buf)
{
    memset(mem_buf,0,USR_MEM_CNT);
    MEM_If_Read_FS ((uint8_t *)FLASH_USER_START_ADDR, mem_buf, USR_MEM_CNT);
    
    if (mem_buf[USR_ASIGN_Add] != USR_ASIGN_ID)
    {
        memset(mem_buf,0,USR_MEM_CNT);
        mem_buf[USR_ASIGN_Add] = USR_ASIGN_ID;
        mem_buf[USR_DATA0_Add+0= 0;
        mem_buf[USR_DATA0_Add+1= 1;
        mem_buf[USR_DATA0_Add+2= 2;
        mem_buf[USR_DATA0_Add+3= 3;
        mem_buf[USR_DATA0_Add+4= 4;
        mem_buf[USR_DATA0_Add+5= 5;
        mem_buf[USR_DATA0_Add+6= 6;
        mem_buf[USR_DATA0_Add+7= 7;
        mem_buf[USR_DATA0_Add+8= 8;
        mem_buf[USR_DATA0_Add+9= 9;
        
        MEM_If_Erase_FS(FLASH_USER_START_ADDR);
        MEM_If_Write_FS(mem_buf, (uint8_t *)FLASH_USER_START_ADDR,USR_MEM_CNT);
    }
}
cs

6-3. eeprom emulation Test 프로그램의 dump 함수 프로그램 소스코드.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
uint8_t dump_data(uint8_t *mem_buf,uint32_t start_mem,uint32_t cnt_mem)
{
    uint32_t i,cnt_i;
    
    if (start_mem < USR_MEM_CNT)
    {
        if ((start_mem+cnt_mem) > USR_MEM_CNT)
        {
            return 1;
        }
        else
        {
            for (cnt_i=0;cnt_i < cnt_mem;)
            {
                printf("Addr [%05X ] ",start_mem+cnt_i);
                if ((cnt_mem - cnt_i) < 16)
                {
                    uint16_t t_i;
                    t_i = (cnt_mem - cnt_i);
                    
                    for (i=0;i<t_i;i++,cnt_i++)
                    {
                        printf("%02X ",mem_buf[start_mem+cnt_i]);
                    }
                    printf("\n\r");
                }
                else
                {
                    for (i=0;i<16;i++,cnt_i++)
                    {
                        printf("%02X ",mem_buf[start_mem+cnt_i]);
                    }
                    printf("\n\r");
                }
            }
            return 0;
        }
    }
    else
    {
        return 2;
    }
}
cs

6-4. eeprom emulation Test 프로그램의 스위치처리 함수 프로그램 소스코드.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void proc_sw_push(uint8_t *mem_buf)
{
    static uint8_t cnt_push=0;
    
    cnt_push++;
    printf("cnt Push switch = [%3d]\n\r",cnt_push);
    mem_buf[USR_DATA0_Add+0]++;
    mem_buf[USR_DATA0_Add+1]++;
    mem_buf[USR_DATA0_Add+2]++;
    mem_buf[USR_DATA0_Add+3]++;
    mem_buf[USR_DATA0_Add+4]++;
    mem_buf[USR_DATA0_Add+5]++;
    mem_buf[USR_DATA0_Add+6]++;
    mem_buf[USR_DATA0_Add+7]++;
    mem_buf[USR_DATA0_Add+8]++;
    mem_buf[USR_DATA0_Add+9]++;
    
    MEM_If_Erase_FS(FLASH_USER_START_ADDR);
    MEM_If_Write_FS(mem_buf, (uint8_t *)FLASH_USER_START_ADDR,USR_MEM_CNT);
}
cs

7. 최종 UART 터미날로 본 결과 테스트




버튼을 누를 때 마다, 10 바이트의 값이 1씩 증가하고.. Power On-Reset 을 했더니 메모리 값이 변하지 않고,
남아 있는 것을 확인 할 수 있었습니다.


어디서 저장이 된 것인지 잘 모르겠지만,

SSID 와 Password가 저장되어서 지우고 싶을 때가 있다.

그럴 땐, 이렇게.. (ARDUINO IDE 에서 사용하는 함수이다.)

WiFi.disconnect();


PSOC5 는 EEPROM 테스트 관련 글이 없어서 약간 시간을 내서 글을 올려 봅니다.


물론 예제에서도 참고할 수 있는데, 예제가 너무 복잡해 보이네요.
또한 제가 사용할 때는, EEPROM 을 따로 Erase를 할 필요가 없었는데.. 
예제에서는 항상 Erase를 하고 Write를 하는게 좀 이상했습니다.

이번에 테스트한 보드는, Cypress에서 많이 파는 CY8CKIT-059 로 진행했습니다.
사용한 콤포넌트는 Port 입력 1개(TACT Switch)와 UART 만 사용했습니다.

byte(unsigned char) 타입 데이터 2개와 uint(unsigned 16 bit) 타입 데이터 2개를 스위치를 1번 누를 때마다,
1씩 증가하도록 하고 EEPROM 에 저장을 했습니다.
그리고 리셋 버튼을 누르면 EEPROM에 저장된 값을 UART 로 출력해서 EEPROM의 동작을 확인했습니다.

보드의 외형과 사용하는 포트들은 다음과 같습니다.




다음은 PSOC Creeator 의 컴포넌트 설계도입니다. (TopDesign.cysch)



다음은 실제 PSOC5 IC의 사용하는 포트입니다.






다음은 프로그램 테스트 과정입니다.

1. 처음 프로그램을 다운로드하고 UART 로 출력되는 내용 (EEPROM에 저장되는 값들을 초기화하여 Write 한다)




2. 스위치를 3번 눌러서 값을 3증가시켜서 저장하고 UART로 출력. 리셋 버튼을 눌러 EEPROM 값을 읽어 확인.




3. 전원을 OFF --> ON 해서 정말로 데이터가 남아 있는지도 확인.





psoc 소스 코드 첨부합니다.

eeprom_test.Bundle01.zip


ESP8266 에서 EEPROM 은 실제로는 없고 외부에 있는 SPIFF 플래쉬 메모리의 일부를 사용하는 것이다.


EEPROM 을 다루는 함수들은 Library 에 있는 EEPROM.cpp 에 정의되어 있다.
라이브러리 소스의 위치는 다음과 같다.
C:\Users\trion\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries\EEPROM

함수들의 종류는 다음과 같다.
1. void EEPROMClass::begin(size_t size)
   : 예를 들어 아두이노 스케치 코드에서 begin.EEPROM(512); 라고 코딩을 하면, EEPROM 을 512바이트 할당하고,
     EEPROM 관련 항목들을 초기화한다.
2. uint8_t EEPROMClass::read(int address)
   : 위에서 예를 든 begin 함수와 같이 설명하자면, 어드레스 0~511 까지 512개의 바이트 데이터를 해당 주소에서 읽어 올 수 있다.
3. void EEPROMClass::write(int address, uint8_t value)
   : 해당 주소(address)에 값을 쓴다. 이 때, 실제로 EEPROM에 물리적으로 기록되는 것이 아니고,
     램으로 된 버퍼값을 바꾸는 것이다.
4. bool EEPROMClass::commit()
   : 실제로 물리적으로 EEPROM(외부 플래쉬 메모리)에 값을 기록해서 리셋이나 전원이 꺼져도 값을 유지할 수 있다.

다음은 아두이노 IDE 툴에서 EEPROM 을 테스트한 코드 입니다.
: NODE MCU V3.0 보드의 Flash 버튼을 누르면 EEPROM 의 0번 어드레스의 값의 0번 비트가 토글 되면서,
  값을 기록하고 소프트웨어 리셋을 이용하여 리셋을 한 후,
  EEPROM의 0번 어드레스의 값이 바뀌어 있는지 EEPROM을 읽어서 시리얼 포트에 출력해 본 테스트입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <EEPROM.h>
 
const int buttonPin = 0;     // the number of the pushbutton pin
int buttonState = 0,buttonState_old=0;         // variable for reading the pushbutton status
byte value;
 
void setup() {
  // put your setup code here, to run once:
  delay(1000);
  Serial.begin(115200);
  EEPROM.begin(512);
  pinMode(buttonPin, INPUT);
  value = EEPROM.read(0);
  Serial.println("");
  Serial.print("EEPROM value = ");
  Serial.println(value,HEX);
 
  buttonState_old = buttonState = digitalRead(buttonPin);
}
 
void loop() {
  // put your main code here, to run repeatedly:
  buttonState_old = buttonState;
  buttonState = digitalRead(buttonPin);
  if (buttonState_old != buttonState)
  {
    if (buttonState == LOW) 
    {
      value = EEPROM.read(0);
      value ^= 1;
      EEPROM.write(0, value);
      EEPROM.commit(); 
      Serial.print("Button Pressed !, EEPROM value = ");
      Serial.println(value,HEX);
      Serial.println("ESP8266 Software Reset");
      ESP.reset();
    }
  }
 
}
cs


다음은 위의 ESP8266 아두이노 IDE 스케치 코드에의해 디버그용으로 시리얼에 표시한 내용 입니다.





이제 I2C 통신 테스트를 해 보려고 합니다.


먼저 Cube 툴에서 I2C1 을 선택했더니, PB6(I2C1_SDA) , PB7(I2C_SCL) 로 핀아웃이 배정되는군요.



Nucleo 보드의 핀위치는 다음과 같습니다.



현재 제가 갖고 있는 I2C 테스트하기 가장 좋은 놈이 하나 있군요. 예전에 PSOC4로 I2C를 테스트하려고 산 EEPROM 모듈(AT24C04)가 있어서 Nucleo 보드와 위의 핀에 연결해 봤습니다.



I2C 테스트 코드를 IRQ 용으로 작성을 했는데, 아직은 IRQ 처럼 사용하지는 않았습니다.
그저 Write 테스트를 해 보려했습니다.

I2C Write 방식은 
'시작 + 7비트 DEVICE ADDRESS + R/W + ACK(EEPROM 에서 응답) + 끝' 이 기본이고,

여기서 추가로 데이터를 몇개 더 보낼 때는,
'시작 + 7비트 DEVICE ADDRESS + R/W + ACK(EEPROM 에서 응답) + 8비트 데이터#1 + ACK(EEPROM 에서 응답)
 + 8비트 데이터#2 + ACK(EEPROM 에서 응답) + ....+ 8비트 데이터#n + ACK(EEPROM 에서 응답) + 끝' 과 같이 하면 됩니다.

I2C 디바이스는 각각 고유의 DEVICE ADDRESS가 있는데, 위의 AT24C04 모듈의 DEV ADDR 은 0x50 입니다.
따라서 main.c 에 어드레스를 다음과 같이 정의했습니다.
#define I2C_ADDRESS        0x50

그리고 main() 함수에 다음을 추가해서 오실로 스코프로 찍어볼 준비를 했구요.
(I2C 설정은 Cube 툴에서 해주니 설정에 관한 설명은 생략합니다)

   i2c_tx_buf[0] = 0x01;
   i2c_tx_buf[1] = 0x02;
   i2c_tx_buf[2] = 0x03;
    if(HAL_I2C_Master_Transmit_IT(&hi2c1, (uint16_t)I2C_ADDRESS, i2c_tx_buf, 3)!= HAL_OK)
    {
      /* Error_Handler() function is called in case of error. */
      Error_Handler();
    }

그런데 스코프로 찍어 보니, 7비트 어드레스만 우측으로 1비트 밀려서 나가고 더이상 데이터가 나오지 않아서
자세히 보니, 어드레스를 좌측으로 1비트 밀어야 ACK가 응답으로 오면서 후속 데이터가 제대로 나가겠구나 .. 라는 생각이 드네요.

그래서 DEVICE Address 를 다음과 같이 수정했고,
#define I2C_ADDRESS        (0x50 << 1)

오실로 스코프의 파형도 정상으로 나왔습니다.



소스 파일 첨부합니다.

I2C_test.zip


+ Recent posts