Zynq SoC Training – Data Logging using SD Cards

Acknowledgement (Korean)
아래의 글은 Ali Aljaani가 운영하는 블로그인 embedded centric의 “Zynq SoC Training”을 번역한 문서입니다. 한글 번역 및 공개를 허용한 Ali Aljaani에게 감사의 말을 전합니다.

Acknowledgement (English)
This post is a translated version of “Zynq SoC training” in embedded centric. Thank you Ali Aljaani for allowing me to translate nice posts. I’m Taekyung Heo, and I translated this post into Korean.

Contact information (Author)
Ali Aljaani’s blog: http://embeddedcentric.com/
Ali Aljaani’s mail: alialjaani@embeddedcentric.com
Ali Aljaani’s LinkedIn: https://www.linkedin.com/in/ali-aljaani-a7130099
Ali Aljaani’s github: https://github.com/ama142


이번 실습에서는 ZedBoard에서 사용할 수 있는 비휘발성 메모리에 대해 학습할 것이다. SD 메모리와 이를 위한 컨트롤러 (SD/SDIO), 그리고 드라이버에 대해 학습한다. 그리고 아래 방향의 푸쉬 버튼을 누를 때마다 “HH:MM:SS Event: Sensor X Triggered #”를 기록하는 어플리케이션을 개발할 것이다 (푸쉬 버튼은 실제 문제에서의 센서와 같은 역할을 한다). HH:MM:SS는 실제 시간을 의미한다. 어플리케이션은 FatFS (File Allocation table File System) API들을 사용해 SD 카드에 접근할 수 있다.

lab7-design-flow


Lab7 Design Flow

lab7-block-diagram


Lab7 Block Diagram

lab7-board-interface


Lab7 Board Interface

임베디드 시스템에서는 비휘발성 메모리에 행위를 기록함으로써 데이터 로깅을 할 수 있다. 로깅 대상이 되는 데이터는 다양하다: 온도를 측정하는 센서 값, 에너지 소모량 등이 있을 수 있다. ZedBoard에는 데이터 로깅을 위해 두 가지의 비휘발성 메모리를 사용할 수 있다. 첫 번째로 256Mbit 크기의 QSPI(quad-SPI) 시리얼 NOR 플래시가 있다. 두 번째로 ZedBoard 뒷면에 있는 SD 카드 커넥터에 SD 카드를 꽂아 사용할 수 있다 (커넥터에 이미 4GB 크기의 SD 카드가 꽂혀있다).

zedboard-block-diagram


Zedboard Block Diagram

두 가지 메모리 모두 초기화 및 데이터 저장을 위해 사용할 수 이다. 초기화를 통해 PS 서브시스템과 PL 서브시스템을 부팅 과정에서 설정할 수 있다 (Bitstream 사용). QSPI 메모리는 quad-SPI 플래시 컨트롤러를 사용해 접근할 수 있다. 이번 실습에서는 SD/SDIO 호스트 컨트롤러를 사용해 SD 카드에 접근하는 것에 대해 학습할 것이다. 이번 실습에서 푸쉬 버튼을 누를 때마다 “HH:MM:SS Event: Sensor X Triggered #”의 메시지를 SD 카드에 기록할 것이다. HH:MM:SS는 실제 시간을 의미한다. 이벤트의 로깅은 타임 스탬핑이라 불린다. 이는 기상청, 도로 주행량 확인, 야생 동물 연구 등에서 폭넓게 사용된다. 이 파일은 보드에 전력 공급이 끊겨도 유지된다. 이 파일은 또한 호스트 데스탑 또는 노트북으로 복사할 수 있다.
이번 실습의 하드웨어 설계는 다섯 번째 실습과 매우 유사하다 (Hardware Timers). 이번 실습에서는 SD0 컨트롤러가 PS에 추가되었다는 점이 다르다. PL-side에서 AXI 타이머는 실시간을 정확하게 측정하기 위해 사용되고, GPIO는 푸쉬 버튼과의 인터페이스를 제공하기 위해 사용된다. ZedBoardOLED는 실시간을 표시하기 위해 사용된다 (선택적임). PS-side에서는 SDO 컨트롤러를 사용해 SD 카드와 통신한다. UART는 터미널을 사용한 디버깅을 위해 쓰인다.

배경 지식
A. SD 카드
Secure Digital (SD) 카드는 플래시 기반의 기억 장치로, 임베디드 시스템에서 비휘발성 저장 장치로 많이 쓰인다. SD 카드는 세 가지의 다른 종류로 나온다: SD (ZedBoard에서 사용되는 형태), miniSD, microSD (두 종류 모두 스마트폰이나 디지털 카메라에서 많이 쓰인다). 모든 SD 카드는 다음 그림과 같이 그 내부에 컨트롤러를 갖고 있다.

sd-card-block-diagram1


SD Card Block Diagram


이 컨트롤러는 실제 메모리 셀과 상호작용하는 것에 사용된다. 이 컨트롤러가 오류 수정 및 wear leveling을 수행한다. SD 카드의 컨트롤러를 사용하기 위해서는 PS-side에 있는 호스트 컨트롤러가 필요하다. 호스트 컨트롤러는 SD 카드와 상호작용할 수 있으며, SDIO Specification 2.0 프로토콜을 따라야 한다. SD 카드와 SD 카드 커넥터의 SDIO 인터페이스 신호는 다음과 같다.
sdio-interface-signals


SDIO Interface Signals

SDIO Specification 2.0에는 SD 카드의 일부는 아니지만 SD 카드 커넥터의 일부인 추가적인 두 개의 신호가 있다.

sd-connector-signals


SD Connector Signals

요약하자면, SDIO Specification 2.0은 다음의 신호들을 갖는다:

1. 데이터 전송을 위한 네 개의 신호(D0-D3).
2. 동기화 클럭을 위한 한 개의 신호(Clk).
3. 명령어와 응답을 위한 한 개의 신호(CMD).
4. 카드가 커넥터와 연결되었는지 확인하기 위한 한 개의 신호(CD).
5. 카드에 쓰기 보호를 위한 한 개의 신호(WP).
6. 한 개의 전력 신호.
7. 두 개의 접지 신호.

다음의 그림이 ZedBoard SD 카드 커넥터 (J12)의 회로 배치를 보여준다 (ZedBoard 사용자 매뉴얼에서 가져왔다). 데이터 신호(D0-D3), 제어 신호(CLK,CMD,CD,WP)들이 Zynq PS-side에 MIO40-MIO47(Multiplexed IO)에 차례대로 연결되어 있음을 확인할 수 있다. PS-side의 MIO40-MIO47은 LVCMOS 1.8 볼트로 구동된다(High가 1.8볼트). ZedBoard는 LVCMOS3.3 볼트로 구동되기 때문에, ZedBoard에서는 이 사이에 논리 레벨 변환 IC (IC8)을 넣어서 이를 해결했다.

mio40-mio47-ps-side-connections


MIO40-MIO47 PS-Side Connections

sd-connector-electrical-interface


SD Connector Electrical Interface

B. SD 호스트 컨트롤러 (SD0)
SD 카드는 최근 몇년간 임베디드 시스템에서 필수적인 요소가 되었다. SD 카드가 유연한 저장 장소를 가지면서도 임베디드 시스템에 적합한 크기를 제공하기 때문이다. 이것이 Zynq 칩이 한 개 이상의 SD 호스트 컨트롤러를 제공하는 이유이다. 이번 실습에서 사용하는 SD0를 다음 그림에서 빨간 원으로 표시했다:

zynq-architecture-sdo-circled-in-red


Zynq Architecture- SDO circled in red


SD0 호스트 컨트롤러는 SDIO 프로토콜을 사용해 SD 카드 내부의 컨트롤러와 통신한다. 두 개의 컨트롤러 사이에 전송되는 데이터는 512 바이트 크기로 전송된다. 이는 SD 카드에 저장된 파일들이 512 바이트 단위로 저장되어 있기 때문이다(기본 값이며, 변경 가능하다). 10 바이트를 저장한다고 하면 512 바이트 중에 나머지 502바이트는 낭비하게 된다. 1 킬로바이트 크기의 파일은 두 개의 블록이 필요하다. SD0는 그 내부에 DMA 엔진을 갖고 있다. 다음의 그림은 SD0 컨트롤러의 블록 다이어그램을 나타낸다.
sdsdio-controller-block-diagram


SD/SDIO Controller Block Diagram

C. SD 호스트 컨트롤러 드라이버 (XSdPs 드라이버)
SD 호스트 컨트롤러 드라이버 (XSdPs driver)은 다음 함수들을 제공하며, 이를 사용해 SD 카드의 초기화 및 읽기/쓰기가 가능하다:

xsdps-driver-low-level-functions


XSdPs driver low-level functions


이러한 함수들만으로도 이번 실습을 수행하기에는 충분하지만, 여러 개의 큰 파일을 이 함수들을 사용해 다루는 것은 매우 불편하다. 모든 파일들의 블록의 시작 주소를 유지해야 하기 때문이다. 이 문제는 다음의 예제에서 확인할 수 있다:

세 개의 파일이 있다: FILE1.TXT는 1536 바이트, FILE2.TXT는 1536 바이트, FILE3.TXT는 512바이트이다. 이같은 세 개의 파일을 다루기 위해서는 일곱 개의 주소를 유지해야 한다 (아래의 그림을 보라). 그렇다면 100 메가바이트 크기의 10개 파일을 다루려면 몇 개의 주소를 유지해야 하는가? NOTE: 파일의 블록들은 연속되어 있지 않을 수 있다(FILE2.TXT와 같이). 따라서 이러한 문제를 해결하기 위해서는 파일 시스템의 도입이 필요하다.

keeping-track-of-files-blocks-addresses-for-three-files


Keeping track of files block’s addresses (three files only)

D. FatFs – File Allocation Table File System 라이브러리 (Elm-Chan 라이브러리)
파일 시스템 라이브러리는 고수준의 사용자 친화적인 함수들을 제공한다 (API, Application Programming Interface). API들은 저수준에서 파일 블록들을 다루지 않아도 되게끔 해준다. 다음은 FatFs의 API 목록이다:

fatfs-apis


FatFs APIs


기본적으로, FatFs 라이브러리는 드라이버 함수를 다루는 것에서 발생하는 복잡성을 제거해준다. 하지만, 내부적으로는 FatFs는 SD 호스트 컨트롤러의 드라이버 함수를 호출한다 (디스크 입출력과 같은 함수). FatFs 라이브러리는 standalone 시스템 또는 운영체제 일부에서 사용된다. 이번 실습에서는 FatFs를 standalone 시스템에 적용하며, 운영체제의 지원은 없다. 다음의 그림이 이번 실습에서 사용하는 소프트웨어 스택을 보여준다:
lab7-software-stack1


Lab7 Software Stack


위의 그림은 또한 어플리케이션, 라이브러리, 드라이버, 그리고 하드웨어 상호작용을 보여주고 있다. FatFs를 사용함으로써 하드웨어와 드라이버를 직접 다루는 어려움이 없어짐을 확인할 수 있다. FatFs 라이브러리는 이러한 API들을 XSdPs 드라이버 함수들로 바꾸어 호출해준다. 최종적으로는 SD 호스트 컨트롤러의 메모리 영역으로 쓰겠지만, 일반적으로는 Xil_out32(), Xil_in32() 함수로 구현된다.
Xilinx SDK에서 지원하는 FatFs는 세 종류의 FAT을 사용할 수 있다: FAT12, FAT16, FAT32. 이 세 가지의 FAT는 디스크 사용량, 지원 가능한 파티션의 최대 크기, 파일 이름의 최대 길이 등에서 다른 특성을 갖는다. FAT32는 FAT16의 확장된 버전이며, FAT12(FAT)은 1981년에 DOS 시스템에서 처음 도입되었다. 이번 실습에서 사용할 FatFs는 업그레이드된 버전인 FAT32이다.

E. FatFs API
FatFs 라이브러리를 사용하기 위해서는 파일 시스템 (FATFS)의 인스턴스를 생성하고, 이를 SD 카드와 연관지어야 한다.

fatfs-instance2

같은 방식으로, 어플리케이션에서 사용하는 파일을 위한 파일 인스턴스를 생성해야 한다.

fil-instance

이에 추가적으로, FatFs API의 반환값을 저장하기 위한 변수를 선언해야 한다. 이 변수들은 FRESULT의 타입을 갖는다. 이 변수의 값을 확인함으로써 API 호출이 성공했는지 실패했는지 확인할 수 있다. 그리고 잘못되었다면 그 이유는 무엇인지 확인할 수 있다.

fresult-variable

FRESULT가 다음의 19개 값 중 하나를 가질 수 있으며, 0이 아닌 값은 실패를 의미한다. 각 값의 해석은 다음과 같다.

fatfs-return-valuesfresult


FatFs Return Values(FRESULT)

세 개의 변수를 정의한 다음에 API를 사용할 수 있다. 첫 번째로 사용할 API는 f_mount() 함수이다.

f_mount1


FatFs f_mount() format

f_mount() 함수는 SD 카드의 초기화를 담당하는데, SD 카드와 생성된 FATFS 인스턴스를 연관짓는다. f_mount() 함수는 세 개의 인자값을 받는다: FATFS 인스턴스를 가리키는 포인터. 논리 드라이브 번호 (한 개의 SD 카드만 있으면 0:/이다.), 그리고 타임아웃 시간이다.

Note: 이 함수는 또한 SD 카드 언마운트에도 사용될 수 있다. 첫 번째 인자값에 NULL을 주어 언마운트할 수 있다.

이 함수는 FatFs API와 유사한 FRESULT 값을 반환한다.

f_mount-with-checked-returned-status1


f_mount() with checked returned status

위 예제에서의 Path는 논리 드라이브 번호에 대한 문자열 포인터이다.
logical-drive-number

API에 의해 반환된 값을 확인하고, 문제가 발생했다면 이를 호출 함수에게 알리는 것이 좋다:
1. 호출 함수에 반환한다.
2. 사용자에게 어떤 에러가 발생했는지 알린다.

f_mount()를 성공적으로 호출한 다음에, 카드는 초기화되며 사용할 수 있는 상태가 된다. 노트북과 데스크탑 컴퓨터에서도 유사한 방식으로 파일을 다루고 있다. 이번 실습에서도 이같은 방식으로 파일을 다룰 것이다. 읽기 또는 쓰기를 위해서 파일을 열고, 수정하고, 닫을 것이다.

f_open() 함수는 파일을 생성하고 열기 위한 목적으로 사용된다. 이 함수는 세 개의 인자값을 받는다: 파일 인스턴스에 대한 포인터, 경로를 포함한 파일의 이름, 그리고 접근 모드가 있다.

f_open


FatFs f_open() format

단순함을 위해, 파일은 SD 카드의 루트에 직접 저장된다. 하지만, 디렉토리의 생성과 구조는 지원된다. 접근 모드는 파일을 새롭게 만들 것인지, 존재하는 파일을 열 것인지 결정한다. 더 구체적으로는 다음과 같이 덮어쓰기를 결정할 수 있다:

fatfs-access-mode


FatFs Access Mode

Example 1: “Record.txt” 파일을 SD 카드의 루트 디렉토리에 만들고, 파일이 존재하면 제거되거나 덮어쓴다.

exampl1


Example 1 solution

Example 2: SD 카드의 루트 디렉토리에 위치한 “temperature.txt” 파일을 연다.

example-2


Example 2 solution

이로써 FatFs에서 파일을 어떻게 열 수 있는지 확인했다. 이제 파일을 읽고 쓰는 방법만 알면 된다.

파일에서 데이터를 읽기 위해서는, f_open() 함수를 FA_READ 모드로 열어야 한다. 이 때 파일을 읽기 위한 버퍼가 생성된다. 이 버퍼의 크기는 파일의 크기와 무관하며, 어플리케이션에 유관하다. 항상 메모리 워드 정렬에 유의해야 한다.

Example 3: 파일이 긴 문자열을 갖고 있다고 하자. 한 번에 9개의 문자를 읽기 위해서는 다음과 같이 버퍼를 정의해야 한다.

char-buffer1


Example 3 solution

Note: 버퍼 크기보다 1보다 크게 변수를 선언했다. 배열의 마지막 요소에 NULL을 저장하기 위함이다.

또한 파일에서 몇 바이트를 읽었는지 확인하기 위한 정수형 카운터를 선언해야 한다. 이는 파일의 끝(EOF)에 도달했는지 확인하는 것과 가은 경우에 유용하다.

counter-varibale-to-keep-track-of-the-data-read-from-a-file

이제 f_read() 함수를 사용해 파일을 읽는 것에 대해 알아보자. 이 함수는 네 개의 인자를 받는다: f_open()을 사용해 연 파일 인스턴스의 포인터, 읽은 데이터를 저장하기 위한 버퍼, 읽어낼 바이트의 수, 그리고 카운터 변수가 있다.

fatfs-f_read-format


FatFs f_read() format

다음의 예제에서 “buff” 버퍼에 네 개의 바이트를 읽어 저장할 것이다. 성공적으로 실행되면 count는 4의 값을 갖게 된다.

f_read-reading-4-bytes-from-a-file


f_read() reading 4 bytes from a file

만약 반대로, 파일에 쓰기 위해서는 f_write() 함수를 호출한다.

f_write-format


FatFs f_write() format

f_write() 함수는 f_read() 함수와 비슷한 변수를 받는다. 하지만 이번에 버퍼는 데이터를 쓰기 위해 사용된다.

Example 4: buff에 저장된 9 바이트 크기의 데이터를 file3 파일 인스턴스에 쓴다. 성공적으로 쓰기 연산이 수행되면, count는 9를 갖게 된다.

example-4


Example 4 solution

sprintf()과 같은 표준 C 함수를 사용해 쓰기 버퍼에 있는 데이터의 형식을 자유롭게 조작할 수 있다.

자원을 더이상 사용하지 않는 파일이라면 닫는 것이 좋다. f_close() 함수를 사용해 파일을 닫을 수 있다.

f_close-format


FatFs f_close() format

Example 5: file1을 닫는다 (더이상 필요하지 않으므로)

example-5


Example 5 solution

목표
1. ZedBoard에서 사용 가능한 비휘발성 메모리 소개
2. SD 카드의 일반적인 구조에 대해 학습하고, ZedBoard의 칩에 어떻게 SD 카드가 연결되는지 확인
3. SD 카드에 효율적으로 접근하기 위한 하드웨어 및 소프트웨어 컴포넌트 이해
4. 하드웨어를 더 효율적으로 사용하기 위해 라이브러리를 사용하는 것의 중요성 이해
5. FatFs Application Programming Interface (API) 연습
6. 간단한 로깅 어플리케이션 개발

실습 단계
A. lab5의 파일들을 새로운 디렉토리로 복사하기
여섯 번째 실습의 실습 단계 A를 수행한다. 첫 번째 단계에서 새로운 폴더의 이름을 “lab7″으로 한다.
B. PS의 SD0 호스트 컨트롤러를 활성화하기
1. zynq_interrupt_system_i 블록 다이어그램 파일을 더블 클릭하여 블록 다이어그램을연다.

block-diagram-file


Block Diagram File


2. 다이어그램 창에서 Zynq Processing System을 더블 클릭하여 Re-customize IP 창을 연다.
3. MIO Configuration-> I/O Peripherals를 클릭하고 SD0를 활성화한다. bank 1 I/O VoltageLVCMOS 1.8V로 변경한다. 그리고 다음과 같이 되도록 변경한다.
mio-configration-for-sd0


MIO Configration for SD0


MIO 40-45에 대해 Pullup 저항을 비활성화 하고, 속도를 fast로 지정한다. JP6 점퍼를 사용하지 않을 것이라면, 카드 감지 신호 “CD”와 쓰기 보호 신호 “WP”를 활성화해야 한다.

Note: 앞서 수정한 것들은 SD 카드 커넥터의 전기적인 인터페이스를 맞추기 위한 것들이다 (ZedBoard 사용자 매뉴얼에서 확인). 인터페이스는 다음과 같고, MIO 신호들은 SD 커넥터에 Ti TXS02612 SDIO 레벨 변환기로 연결되어 있다.

sd-card-interface


SD Card Interface

OK를 클릭해 Re-customize IP 창을 닫는다.

C. 변경을 반영하기 위해 Bitstream 재생성하기
Flow Navigator 창에서 Program and Debug 섹션의 Generate Bitstream을 클릭한다. 수정을 반영하겠냐는 대화 상자가 뜨면 Save를 클릭한다.

generating-bitstream1


Generating Bitstream


Bitstream 생성에는 컴퓨터 성능에 따라 다소 시간이 걸릴 수 있다. Bitstream 생성이 완료되면 대화 상자에서 View Reports을 선택하고 OK를 클릭한다.

D. 하드웨어 설계를 SDK에 내보내기
1. File > Export > Export Hardware를 클릭하고, Include bitstream을 선택한다.

export-hardware-to-sdk


Export Hardware to SDK


2. File>Launch SDK를 클릭한다. Launch SDK 대화 상자가 뜰 것이다. 기본 옵션을 그대로 두고, OK를 선택한다. 설계와 관련된 모든 하드웨어 파일이 SDK에 내보내졌고, 이제 소프트웨어를 개발할 수 있다.

시스템의 하드웨어 설정은 완료되었다. Vivado를 닫아도 된다.

E. SDK에서 작업하기
1. SDK에서 File > New > Application Project를 클릭한다.
2. Application Project 창에서 인자값을 다음과 같이 입력한다.

new-standalone-c-project-e28093-data_logger

New Standalone C Project – data_logger


다음 창에서, Empty Application을 입력하고 Finish를 클릭한다. 이제 BSP와 관련 드라이버가 컴파일될 것이다.

3. data_logger 프로젝트 디렉토리를 확장하고, src 디렉토리를 우클릭해 New->Source File을 선택한다.

new-c-source-file-e28093-lab7


New C Source File – lab7.c


4. 다음 창에서 source file에 “lab7.c”를 입력하고 Finish를 클릭한다. 이는 src 디렉토리에 빈 C 파일을 생성할 것이다.
lab7-c-empty-source-file-created-in-sdk


lab7.c empty source file created in SDK


5. data_logger_bsp을 우클릭하고, Board Support Package Settings를 클릭한다.
6. Board Support Package Settings에서 xiffs를 클릭해 FatFs 파일 시스템 라이브러리를 BSP에 추가한다. OK를 클릭한다.
board-support-package-settings-include-fatfs-file-system-library


Board Support Package Settings- Include FatFs File System Library


7. lab7.c의 내용을 편집창에서 lab7.c 파일에 붙여넣는다 (내 GitHub 페이지에서 받을 수 있다). 그리고 Save를 클릭하거나 Ctrl+S를 눌러 저장한다. 이제 lab7 어플리케이션과 BSP가 컴파일되어 실행 파일이 생성될 것이다. 이는 ARM 프로세서의 PS-side에서 실행된다.

lab7.c는 lab5.c의 확장된 버저니다. lab5.c 위에 어떤 부분이 추가되었는지 주석이 붙어 있다. 수정된 부분은 main() 함수와 푸쉬 버튼 처리를 위한 BTN_Intr_Handler()이다.

main() 함수는 SD 카드를 초기화 및 마운트, RECORDS.txt 파일 생성 루틴을 갖고 있다 (287-296번째 줄).

modified-main-lab72


modified main() – Lab7


파일에 쓰기를 수행하는 코드는 BTN_Intr_Handler()에 있다 (130-169번째 줄).
writing-to-file-inside-pushbutton-isr-everytime-btnd-is-pressed1


Writing to file inside pushbutton ISR everytime BTND is pressed


8. Xilinx Tools-> Program FPGA를 선택하여 Bitstream을 다운로드한다 (수 초가 걸린다).
program-fpga


Downloading Bitstream to the PL


9. data_logger 프로젝트 디렉토리 -> Run As-> Launch on Hardware (GDB) 를 선택해 data_logger 어플리케이션을 ARM 프로세서에서 실행한다. 다운로드가 완료되면 아래쪽 푸쉬 버튼을 누를 때마다 문자열이 RECORDS.txt에 저장된다. 이 내용은 card reader를 사용해 확인할 수 있다. 그 내용은 다음과 같다:
records-txt-created-by-the-board-and-stored-on-the-sd-card1


RECORDS.txt created by the board and stored on its SD card


이로써 일곱 번째 실습인 Data Logging using SD Cards를 마친다. 이번 실습에 대한 해답은 여기에 있다.


Advertisements
Tagged with: , , , , , , , , , , ,
Posted in FPGA

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

누적 방문자 수
  • 93,229 hits
%d bloggers like this: