Giao tiếp card MMC/SD

( 51 Votes )

Nội dung Các bài cần tham khảo trước
  1. Giới thiệu
  2. Sơ lượt về MMC/SD card
  3. Phương pháp giao tiếp MMC/SD card
  4. Giao tiếp AVR với MMC/SD card
  5. Sử dụng thư viện myMMC
Download ví dụ

 

I. Giới thiệu

     Bài viết này giới thiệu về cấu trúc và phương thức giao tiếp với các loại thẻ nhớ (card) MMC và SD. Trong các phương thức giao tiếp với MMC/SD, SPI sẽ được chọn để minh họa cách sử dụng AVR đọc và ghi card MMC/SD. Cụ thể bài này bao gồm:
    - Sơ lượt các loại card MMC/SD
    - Sơ đồ mạch điện giao tiếp với MMC/SD card
    - Định dạng lệnh và phương thúc giao tiếp với MMC/SD
    - Giao tiếp ghi/đọc MMC/SD bằng AVR thông qua chuẩn SPI

    Chú ý: khi nói về MMC/SD card người ta hay nói thêm về các định dạng ỗ đĩa FAT, FAT32 cho MMC/SD card. Tuy nhiên, đây là một vấn đề lớn khác mà bài học này không đề cập. Chúng ta chỉ học các giao tiếp với MMC/SD mà không cần quan tâm đến FAT hay FAT32.    

II. Sơ lượt về MMC/SD Card

      MMC là viết tắt của cụm từ tiếng anh Multi-Media Card và SD là Secure Digital Card. Nhìn chung MMC và SD giống nhau về mặt cấu trúc vật lý và phương thức giao tiếp. Điểm khác nhau lớn nhất của 2 loại card này là về tính năng bảo mật dữ liệu và tốc độ giao tiếp. SD card xuất hiện sau MMC card nên SD có nhiều tính năng và tốc độ cao hơn MMC. Tuy nhiên, đối với việc ghi-đọc MMC và SD ở tốc độ thấp bằng các vi điều khiển (như AVR) thì sự khác nhau của 2 loại card này là không đáng quan tâm. Vì vậy, từ bây giờ chúng ta sẽ dụng cụm từ chung MMC/SD để chỉ chung cho các loại card này.
Về phương thúc giao tiếp, MMC và SD card đều có thể được giao tiếp thông qua 2 chế độ (mode) cơ bản là SD/MMC mode và SPI mode. Giao tiếp với bằng mode SD/MMC có tốc độ cao nhưng đòi hỏi chip điều khiển cũng phải có tốc độ cao. Mode này không phù hợp với việc giao tiếp bằng vi điều khiển. Ngược lại, mode giao tiếp SPI tuy có tốc độ thấp hơn nhưng phù hợp với các chip điều khiển như AVR. Phần giới thiệu về MMC/SD card trong bài này vì thế chỉ tập trung vào mode SPI.
Về hình dáng bên ngoài, MMC và SD có cùng kích thước và cấu trúc chân gần như nhau, như trong hình 1.

card

Hình 1. Bố trí chân của MMC và SD card

      Như trình bày trong hình 1, MMC card (hình bên phải) có 7 chân trong khi SD card (hình bên trái) có 9 chân. Các chân thêm 8, 9 trên SD card là các chân dữ liệu của mode SD/MMC nên không quan trọng khi giao tiếp ở mode SPI. Ngoài ra 7 chân còn lại trên SD card hoàn toàn giống với MMC. Phần bên dưới mô tả chức năng các chân trong mode SPI.
- Chân 1: CS (Chip Select) là chân chọn chip dùng trong mode SPI, chân này nối với chân chọn chip của chip điều khiển (AVR).
- Chân 2:  DI (Data Input) hay là chân MOSI của chuẩn SPIm, chân này cần được nối với chân MOSI trên chip điều khiển (AVR).
- Chân 3, 6: là các chân GND.
- Chân 4: là chân nguồn.
- Chân 5: CLK là chân giữ nhịp trong mode SPI, chân này sẽ được nối với SLK trên chip điều khiển (AVR).
- Chân 7: DO (Data Output) hay chân MISO của chuẩn SPI, chân này được nối với chân MISO trên chip điều khiển (AVR).

     Nguồn nuôi MMC/SD card: đây là điểm cần lưu ý khi sử dụng các card MMC/SD, nguồn cho các card này phải nằm trong khoảng 2.7V đến 3.6V. Điều này thường gây khó khăn khi điều khiển MMC/SD card bằng các vi điều khiển vì các mạch điều khiển thường dùng mức điện áp 5V. Vì thế, không giống như các chip điện tử số thông thường, bạn không được phép nối MMC/SD card trực tiếp với các chip điều khiển có nguồn nuôi 5V. Theo đề nghị của tôi, chúng ta có thể dùng nguồn 3.3V cho MMC/SD card. Nguồn 3.3V có thể được tạo bằng các chip ổn áp như 1084K33, LM317 hoặc bằng các cầu chia áp dùng điện trở. Kết nối giữa mạch điều khiển (5V) và MMC có thể thực hiện gián tiếp thông qua các chip buffer, qua transitor, opto-transistor hay cầu chia áp điện trở,…Tôi đề nghị một mạch điện kết nối giữa chip điều khiển với card MMC/SD như trong hình 2.

connect mmc

Hình 2. Sơ đồ kết nối giữa chip điều khiển và card MMC/SD

     Trong sơ đồ hình 2 các cầu chia áp 1.8K-3.3K được dùng để tạo mức điện áp khoảng 3.3V cho card MMC/SD. Do mạch điều khiển dùng điện áp nguồn 5V, tín hiệu xuất ra từ các đường SS, MOSI hay SCK có mức thấp 0V và mức cao 5V, nếu kết nối trực tiếp các đường này với card MMC/SD có thể làm hỏng card. Gọi điện áp được “chia” khi tới MMC/SD card là V, bạn có thể dễ dàng tính được điện áp từ mạch điều khiển như sau. Khi ngõ ra 0V điện áp V=0V, khi điện áp ngõ ra 5V thì V=5*3.3/(3.3+1.8)=3.2V, giá trị này phù hợp cho MMC/SD card. Trường hợp chân MISO (7), đây là chân dữ liệu truyền từ MMC/SD card về mạch điều khiển nên không cần giảm điện áp, tín hiệu 3.3V từ MMC/SD có thể được chip điều khiển chấp nhận. Do đó, chân MISO được nối trực tiếp giữa MMC/SD và chip điều khiển. Chú ý là card và chip điều khiển được nối GND chung.

 III. Phương thức Giao tiếp MMC/SD Card

     Như đã nói từ đầu, chúng ta chỉ khảo sát giao tiếp với MMC/SD ở chế SPI, vì thế các thuật ngữ giao tiếp được mặc định là SPI. Nếu xem lại bài học về SPI bạn sẽ thấy có 4 modes hoạt động của chuẩn này tùy thuộc vào cạnh của xung giữ nhịp SCK. MMC/SD card hoạt động tốt ở mode 0 của SPI tức là CPHA=0, CPOL=0.
      Nếu gọi dữ liệu giao tiếp giữa chip điều khiển chủ (hãy gọi là host) và MMC/SD card là một “thông điệp” (Message) thì thông điệp này được chia thành 3 loại: Lệnh (Command), Trả lời (Response) và Dữ liệu (Data token). Giao tiếp giữa host và card được bắt đầu khi host kéo chân chọn chip CS (chân 1) xuống mức 0. “Lệnh” được host truyền đến card thông qua đường MOSI (chân 2). “Trả lời” được card gởi lên host qua đường MISO và đường CLK là đường giữ nhịp của host với card. Giao tiếp giữa host và card thường được phân thành 2 giai đoạn: giai đoạn khởi động và giai đoạn truy cập dữ liệu. Trong giai đoạn khởi động, host sẽ gởi một số lệnh để khởi động, reset, cài đặt một số thông số giao tiếp như độ dài 1 khối dữ liệu (chúng ta gọi là sector)…Và trong giai đoạn truy cập dữ liệu, một hay nhiều khối dữ liệu sẽ được host ghi vào card hoăc host đọc từ card một cách liên tiếp.
      Trong các loại “thông điệp” giao tiếp giữa host và card, Lệnh là thông điệp quan trọng và phức tạp nhất. Để có thể thực hiện giao tiếp giữa host và card chúng ta cần biết cấu trúc và chức năng của Lệnh. Phần này chúng ta khảo sát cấu trúc của 1 lệnh và sau đó tìm hiểu một số lệnh cơ bản nhất giao tiếp với MMC/SD card.
      Lệnh từ host gởi đến card phải được đóng gói trong 1 định dạng gồm 48 bit. Ý nghĩa của các bit được giải thích như sau:
      command format
      Dòng đầu tiên trong bảng trên mô tả tên các bit (Description), dòng thứ 2 chỉ vị trí bit (Bit position), dòng 3 chỉ độ rộng của từng nhóm bit (Width) và dòng 4 chỉ các giá trị mà các bit có thể mang. 
      - Bit 47, gọi là start bit, là bit báo hiệu cho card rằng sắp có 1 lệnh được host gởi đến. Giá trị của start bit luôn bằng 0.
      - Bit 46, Tranmission bit, bit này nói rằng Lệnh sẽ được “gởi” theo chiều từ host đến card. Bit này luôn có giá trị 1 trong tất cả các lệnh.
      - Bit 45:40, các bit từ 40 đến 45 gọi là chỉ số lệnh (Command index). Thực ra đây là mã lệnh mà host gởi cho card. Có 1 danh sách các lệnh cơ bản, mỗi lệnh có 1 chức năng riêng và chỉ số lệnh là thứ tự của lệnh trong danh sách này. Chúng ta sẽ khảo sát chức năng của từng lệnh sau.   
      - Bit 39:8, các bit từ 8 đến 39 là các tham số của lệnh (argument). Ví dụ host muốn đọc 1 khối dữ liệu từ card, trước hết nó phải gởi mã lệnh (command index) sau đó phải gởi kèm địa chỉ của khối dữ liệu mà nó muốn đọc. Giá trị địa chỉ khối dữ liệu trong ví dụ này gọi là tham số của lệnh đọc dữ liệu.
      - Bit 7:1, đây là 7 bit mang giá trị kiểm tra. CRC là viết tắt của cụm từ Cyclic Redundancy Check, là một chuẩn kiểm tra tính hợp lý của dữ liệu được gởi. Nhìn chung việc tính CRC tương đối phức tạp, rất may trong các lệnh giao tiếp cơ bản với card chỉ có lệnh 0 (CMD0) là cần CRC, chúng ta sẽ khảo sát kỹ hơn về điều này trong lúc viết chương trình giao tiếp với card.
      - Bit 0, là Stop bit báo hiệu lệnh đã kết thúc, bit này có giá trị mặc định là 1.

      Như thế, để gởi 1 lệnh có nghĩa đến card, host cần phải sắp xếp các bit thành 1 định dạng 48 bit như trên, sau đó chia 48 bit này thành từng byte để gởi đến card thông qua SPI. Trong các phần tiếp theo chúng ta sẽ khảo sát các lệnh cơ bản giao tiếp với MMC/SD với host là vi điều khiển AVR.

 IV. Giao tiếp AVR với MMC/SD Card

     Để minh họa giao tiếp với MMC/SD card theo SPI, chúng ta sẽ dùng chip AVR Atmega32 làm chip chủ - host. Chúng ta sẽ tạo 1 thư viện mang tên myMMC trong WinAVR. Thư viện myMMC bao gồm 1 file header myMMC.h khai báo các hàm giao tiếp với MMC/SD card và  file myMMC.c chứa nội dung các hàm. Do việc giao tiếp với MMC/SD card được thực hiện thông qua module SPI của AVR, bạn phải nắm cách sử dụng module SPI của AVR trước khi đọc các phần tiếp theo của bài này.
     Các hàm giao tiếp với MMC/SD card có rất nhiều, chúng ta chỉ tìm hiểu và sử dụng các hàm cơ bản nhất. Để biết thêm thông tin về các hàm cho MMC/SD, bạn đọc có thể tham khảo phần 4.8 - Commands trong tài liệu “SanDisk SD Card Product Manual”.  Với các hàm được chọn để giới thiệu, tôi sẽ giải thích cụ thể trong lúc viết hàm. Để tạo thư viện myMMC, hãy tạo 1 file myMMC.h có nội dung như trong List 1. 
myMMC.h
     File header myMMC.h gồm 2 phần. Phần đầu chứa các định nghĩa về chân, PORT của module SPI trên AVR, phần này có thể được thay đổi tùy theo cấu hình cụ thể của từng loại AVR. Dòng 12 chúng ta định nghĩa 1 biến tên Block_len = 512, biến này chỉ độ dài của 1 khối dữ liệu. Khác với các chip nhớ (RAM, EEPROM,…) các MMC/SD card không cho phép truy cập (đọc ghi) trên từng byte riêng lẻ mà phải truy cập 1 hoặc nhiều khối dữ liệu. Một khối dữ liệu có thể bao gồm 512 bytes, 1024 bytes,…tùy theo cách khai báo khi khởi động card. Tuy nhiên, trong bài này chúng ta chỉ dùng khối dữ liệu có độ dài 512 bytes. Các dòng từ 23 đến 27 định nghĩa một số chỉ số lệnh (mã lệnh) cơ bản giao tiếp với MMC/SD card. Cụ thể chúng ta sẽ sử dụng các mã lệnh sau:
     Lệnh CMD0 (0x00): Go to Idel state, reset card và đưa card vào trạng thái nghỉ, chờ đợi.
     Lệnh CMD1 (0x01): Send operation condition, yêu cầu card trả lời về trạng thái hoạt động hiện tại của card.
     Lệnh CMD16 (0x10): Set blocklen, cài đặt giá trị độ dài của khối dữ liệu.
     Lệnh CMD17 (0x11): Read single block, đọc 1 khối dữ liệu từ card.
     Lệnh CMD24 (0x18): Write single block, ghi 1 khối dữ liệu vào card.

     Biến mmc_status là một biến toàn cục chứa trạng thái lỗi khi giao tiếp với MMC/SD card, nếu việc giao tiếp diễn ra thuận lợi giá trị của mmc_status bằng 0, ngược lại giá trị của mmc_status sẽ báo cho chúng ta biết lỗi xảy ra ở vị trí nào, cụ thể:
     mmc_status = 0: ok
     mmc_status = 1: lỗi khi reset, timeout xảy ra trong quá trình reset
     mmc_status = 2: lỗi xảy ra khi gọi lệnh CMD1
     mmc_status = 3: lỗi xảy ra khi set độ dài khối dữ liệu
     mmc_status = 4: lỗi khi gọi lệnh writeblock CMD24, timeout xảy ra
     mmc_status = 5: timeout xảy ra trong quá trình ghi dữ liệu
     mmc_status = 6: timeout xảy ra do card đang bận
     mmc_status = 7: lỗi khi gọi lệnh readblock CMD17
     mmc_status = 8: lỗi khi kiểm tra token (dấu hiệu) của lệnh đọc dữ liệu

     Các dòng từ 44 đến 46 khai báo các hàm khởi động, truyền và nhận thông qua SPI của AVR. Các dòng từ 50 đến 54 là các hàm giao tiếp với MMC/SD card. Cụ thể nội dung các hàm này sẽ được khảo sát trong file myMMC.c, xem List 2, List 3 và List 4.
myMMC.c-1
     List 2 là phần đầu của file myMMC.c chứa nội dung các hàm khởi động và truyền/nhận thông qua SPI. Hàm SPI_MasterInit(void) khởi động module SPI của AVR như 1 Master SPI, cho phép ngắt nhận dữ liệu SPI và mode hoạt động là mode 0 (CPHA=0, CPOL=0). Hàm SPI_tByte(uint8_t data) truyền 1 byte dữ liệu từ AVR đến card và hàm SPI_rByte(void) đọc 1 byte dữ liệu từ card về AVR.
myMMC.c-2

     List 3 chứa các hàm cơ bản cho MMC/SD card bao gồm hàm nhận Response từ card, hàm gởi Lệnh đến card và hàm khởi động card. 
     Hàm mmc_rResponse(uint8_t Response): sau khi gởi bất cứ thứ gì đến card, card đều trả lời lại bằng 1 giá trị gọi là Response, tùy vào trường hợp cụ thể mà Response có giá trị khác nhau (thường là 0). Hàm mmc_rResponse(uint8_t Response) cho phép kiểm tra response có đúng với yêu cầu hay không, nếu đúng thì trả về 0, không thì tiếp tục yêu cầu card trả về response (bằng cách gởi dummy byte (byte vô nghĩa) là 0xFF qua SPI). Việc yêu cầu response sẽ được lặp đi lặp lại (dùng vòng lặp while) cho đến khi nào card trả về đúng response mong muốn hoặc số lần lặp vượt quá 1 con số nào đó gọi là Timeout. Timeout (hết giờ) là khái niệm chỉ một sự kiện đã được chờ đợi, yêu cầu rất nhiều lần nhưng vẫn không  đạt được.
     Trước khi giải thích ý nghĩa của hàm mmc_tCommand, cần nhắc lại định dạng 1 thông điệp Lệnh (48 bit) cho MMC/SD card như sau: 
      command format
     Hàm mmc_tCommand(uint8_t Cmd, uint32_t arg): là hàm gởi 1 thông điệp Lệnh (48 bits) đến MMC/SD card. Các tham số kèm theo hàm bao gồm: Cmd là mã lệnh, arg tham số của lệnh (ví dụ địa chỉ khối dữ liệu cần ghi/đọc).  Dòng  13 chúng ta kéo chân SS xuống để kích hoạt MMC/SD card như là 1 Slave của SPI. Tiếp theo gởi 1 byte vô nghĩa 0xFF đến card (dòng 14) bằng hàm SPI_tByte, chú ý là bất kỳ lệnh nào đều có start bit bằng 0, do đó khi bắt đầu chúng ta gởi byte 0xFF (có bit đầu bằng 1) thì card không nhận ra byte như 1 lệnh và bỏ qua byte này. Xem lại định dạng của một thông điệp Lệnh ở phần III chúng ta thấy rằng 8 bit đầu tiên trong 48 bit của thông điệp lệnh bao gồm “Start bit + transmission bit + 6 bit mã lệnh”. Dòng  15  gởi 8 bit đầu tiên đến card, SPI_tByte(Cmd | 0x40). Bạn biết rằng 0x40 = 01000000 (nhị phân) nên Cmd | 0x40 chính là ghép giá trị của mã lệnh Cmd và đầu 01 (0: start bit, 1: transmission bit) tạo thành byte đầu tiên và sau đó gởi đến card. Các dòng từ 16 đến 19 lần lượt gởi 4 byte của tham số arg (32 bit) đến card. Dòng 20 gởi 7 bit CRC7 và stop bit. Bạn thấy rằng chúng ta gởi giá trị 0x95 thay cho CRC va Stop bit, điều này được giải thích như sau. Thật ra, giá trị CRC7 phải được tính toán tùy thuộc vào giá trị các bit đã gởi trước đó (mã lệnh, arg,…), tuy nhiên việc tính toán này là rất khó khăn và tốn nhiều thời gian. Trong khi đó đối với MMC/SD card chỉ có lệnh CMD0 (reset và đưa card vào trạng thái nghỉ) là cần tính  CRC7 chính xác. Tham số của mã lệnh CMD0 là 0, giá trị CRC7 cho mã lệnh này đã được tính sẵn là 74 tức 0x4A, hay 1001010 (nhị phân). Vì vậy “CRC7+stop bit” cho lệnh CMD0 sẽ là 10010101 (stop bit) = 0x95. Cần lưu ý là hàm mmc_tCommand dùng để gởi bất kỳ thông điệp lệnh nào đến card chứ không riêng gì lệnh CMD0, nhưng chúng  để để mặc định “CRC7+stop bit”=0x95 vẫn có thể chấp nhận được vì đối các lệnh khác, MMC/SD card không yêu cầu tính chính xác CRC7. Dòng SPI_tByte(0xFF) cuối cùng chỉ là dòng vô nghĩa vì trước đó Stop bit đã được gởi đến MMC/SD card. 
     Hàm mmc_init(void): khởi động MMC/SD card. Việc khởi động card phải được tiến hành theo trình tự như trong hình 3.

init

Hình 3. Trình tự khởi động MMC/SD card

     Trước khi khởi động card chúng ta cần khởi động module SPI của AVR như 1 Master SPI (dòng 25). Việc khởi động card được bắt đầu bằng cách tạo ra liên tiếp 80 xung (hoăc hơn) trên đường CLK của SPI trong lúc chân chon chip SS còn ở mức cao (Card chưa được kích hoạt). Dòng 26 kéo chân  SS lên cao và dòng 27 cho SPI gởi 10 byte liên tiếp, do đó sẽ có ít nhất 80 xung được tạo ra trên CLK. Sau khi phát 80 xung trên CLK đã đến lúc kích hoạt card như một SPI Slave bằng cách kéo chân SS xuống mức 0 như trong dòng 28. Các dòng từ 29 đến 34 thực hiện gởi mã lệnh CMD0 đến card, trong lúc gởi nếu có lỗi hay timeout xảy ra thì hàm khởi động sẽ bị dừng lại và giá trị lỗi sẽ được chứa trong biến mmc_status. Các dòng từ 36 đến 45 thực hiện gởi và kiểm tra lỗi đối với mã lệnh CMD1. Chú ý là các tham số kèm theo 2 mã lệnh CMD0 và CMD1 đề là 0.  Dòng 46 gởi lệnh CMD16 quy định độ dài 1 khối dữ liệu, tham số kèm theo là arg=Block_len trong đó Block_len là biến được định nghĩa trước trong file myMMC.h, Block_len=512. Cuối cùng, nếu quá trình khởi động card xảy ra thuận lợi, giá trị 0 sẽ được trả về.
myMMC.c-3
     List 4 chứa 2 hàm ghi và đọc MMC/SD card. Hai hàm này chỉ cho phép ghi và đọc1 khối dữ liệu đơn thông qua 2 mã lệnh CMD24 và CMD17. 
     Hàm mmc_writeblock(uint32_t LBAddress, uint8_t *buff): ghi 1 khối dữ liệu (512 bytes) vào MMC/SD card. Khối dữ liệu cần ghi được chứa trong mảng buff và địa chỉ card nơi khối dữ liệu được ghi vào chính là tham số LBAddress. Việc ghi vào card phải được thực hiện theo tuần tự: gởi lệnh “write single block” CMD24 (dòng 6, 7) -> kiểm tra Response từ card (dòng 8 đến 12) ->  gởi dấu hiệu dữ liệu (data token, 0xFE) (dòng 13)-> ghi liên tiếp 1 khối dữ liệu 512 bytes (dòng 14) -> ghi 2 byte dummy 0xFF (dòng 15, 16) -> đọc Response từ card và so sánh với 0x05 (dòng 19 đến 25) -> chờ cho mmc rãnh (dòng 27 đến 33). Hàm mmc_writeblock thực hiện đúng theo trình tự này nên bạn đọc có thể tự theo dõi. Tôi chỉ giải thích một số điểm quan trọng và mới trong hàm trên. Thứ nhất là về “dấu hiệu dữ liệu” (data token). Bạn quan sát sau khi gởi mã lệnh ghi dữ liệu CMD24 ở dòng 7 và chờ Response từ card,  ở dòng 13 chúng ta không gởi ngay dữ liệu mà gởi 1 byte có giá trị 0xFE ( SPI_tByte(0xFE) ). Giá trị 0xFE này gọi là data token, báo cho card rằng mọi thứ đã sẵn sàng, dữ liệu sẽ được gởi đến card ngay sau đây. Thứ hai, về cách tính địa chỉ LBAddress cho lệnh CMD24. LBAddress là viết tắt của cụm từ “Logical Block Address” nghĩa là địa chỉ logic (hay địa chỉ danh nghĩa) của khối dữ liệu (512 bytes). Như thế khoảng cách giữa 2 LBAddress liên tiếp là 512 bytes. Trong khi mã lệnh CMD24 yêu cầu tham số là địa chỉ byte đầu tiên của 1 khối dữ liệu (tính theo địa chỉ byte), chúng ta không thể gán trực tiếp LBAddress cho CMD24 mà phải qua 1 bước chuyển đổi. Hình 4 minh họa mối quan hệ giữa địa chỉ khối LBAddress và địa chỉ byte.

address

Hình 4. Quan hệ giữa địa chỉ khối LBAddress và địa chỉ byte

     Trong hình 4, giá trị bên trong các ô nhớ là địa chỉ tính theo byte, giá trị bên ngoài là địa chỉ logic của khối dữ liệu. Khối trên có địa chỉ LBAddress = 0, khối dưới có LBAddress = 1,…Giả sử bây giờ bạn muốn điền 512 bytes dữ liệu vào khối dưới (LBAddress = 1) bằng mã lệnh CMD24, bạn không được phép gán tham số 1 cho lệnh CMD24 mà phải gán địa chỉ đầu tiên của khối dưới, tức giá trị 512. Từ đó, chúng ta dễ dàng nhận thấy địa chỉ byte của byte đầu tiên trong khối thứ LBAddress là:  a = 512 * LBAddress. Vì thế trong dòng 4 chúng ta thực hiện phép gán tempA=512*LBAddress với tempA là giá trị địa chỉ tính theo byte, tempA sẽ được dùng làm tham số địa chỉ khi gởi mã lệnh đọc dữ liệu CMD24 ở dòng 7. 
     Hàm mmc_readblock(uint32_t LBAddress, uint8_t *buff): đọc 1 khối dữ liệu (512 bytes) từ MMC/SD card vào mảng buff. Khối dữ liệu cần đọc được chứa trong card tại địa chỉ LBAddress. Nội dung hàm này cũng tương tự hàm ghi vào card, chỉ khác là tại dòng 45 mã lệnh CMD17 được sử dụng để đọc dữ liệu từ card. Quá trình đọc 1 khối dữ liệu từ MMC/SD card được thực hiện theo trình tự sau: gởi lệnh “read single block” CMD17 (dòng 44, 45) -> kiểm tra Response từ card (dòng 46 đến 50) -> kiểm tra cho đến khi nào nhận được dấu hiệu dữ liệu (data token, 0xFE) (dòng 52 đến 56) -> đọc liên tiếp 1 khối dữ liệu 512 bytes (dòng 58) -> ghi 2 byte dummy 0xFF (dòng 60, 61).  

 V. Sử dụng thư viện myMMC

    Phần này chúng ta khảo sát ví dụ minh họa cách sử dụng thư viện myMMC để lưu trữ và đọc card MMC/SD. Phần mềm mô phỏng mạch điện Proteus sẽ được sử dùng cho mục đích minh họa vì phần mềm này có thể mô phỏng đối tượng card mmc. Mạch điện mô phỏng cho ví dụ được trình bày trong hình 5.

mophong

Hình 5. Mô phỏng ví dụ giao tiếp giữa AVR và MMC/SD

     Trong mạch điện hình 5 một chip AVR Atmega32 được dùng điều khiển 1 card mmc, 2 Led 7 đoạn hiển thị các bước và lỗi khi chạy chương trình (Led trên hiển thị các bước, Led dưới hiển thị lỗi ghi/đọc MMC/SD card). Giá trị đọc về từ card sẽ được xuất ra uart và hiển thị trên Terminal. List 5 trình bày chương trình ví dụ, file myMMC_test.c
myMMC_test.c 

      Chương trình ví dụ này có sử dụng các hàm xuất nhập chuẩn để xuất dữ liệu từ card ra uart, bạn đọc có thể tham khảo thêm bài Giao tiếp AVR với máy tính (1) để nắm rõ hơn. Ngoài ra chương trình cũng dùng bộ nhớ flash (biến hocavr[] ở dòng 32) để lưu trữ dữ liệu tạm trước khi ghi vào card nên ở phần đầu chương trình chúng ta cần đính kem 1 headers “sdtio.h” và “avr/prmspace.h”. Cần phần liên quan đến uart sẽ không được giải thích thêm, tôi chỉ giải thích phần ghi và đọc dữ liệu từ mmc card. Tại dòng 31 chúng ta khai báo 1 mảng 512 phần tử có tên là buff, mảng này sẽ dùng để chứa dữ liệu trước khi ghi vào card hoặc dữ liệu đọc về từ card.
     Phần chương trình chính được chia thành 3 phần. Phần đầu bao gồm các khai báo và khởi động card nhu dòng 45 gọi hàm khởi động mmc card mmc_init(). Trong phần thứ hai, 4 khối dữ liệu (ở đây tôi gọi là sector) được ghi vào card mmc ở địa chỉ từ 1 đến 4. Và phần cuối cùng, toàn bộ 4 khối dữ liệu này lần lượt được đọc và xuất ra uart, hiển thị trên Terminal.
     Để bắt đầu 1 quá trình ghi vào mmc người dùng cần chuẩn bị trước dữ liệu, chứa dữ liệu trong 1 mảng. Dòng 51 khai báo 1 con trỏ kiểu char (1 byte) trỏ đến 1 chuỗi ký tự sẽ được ghi  vào sector 1 (khối dữ liệu có địa chỉ 1). Chuỗi ký tự này được ghi vào card bằng hàm mmc_writeblock(1, teststring) ở dòng 53. Tương tự, con trỏ teststring được gán cho 1 chuỗi khác và ghi vào sector 2 ở dòng 63, mmc_writeblock(2, teststring). Hàm mmc_writeblock không có khả năng truy cập trực tiếp dữ liệu chứa trong bộ nhớ chương trình (flash). Để ghi khối dữ liệu hocavr[] được chứa trong flash trước đó chúng ta cần đọc nội dung từ flash ra và gán cho mảng buff trước, bằng hàm pgm_read_byte. Các dòng từ 66 đến 74 thực hiện điều này. Sau khi đọc dữ liệu từ flash vào buff, chúng ta sẽ tiến hành ghi vào card như dòng 75. Do dữ liệu của biến hocavr[] khá lớn, cần đến 2 sector để chứa, các dòng từ 79 deđến 86 tiếp tục ghi phần còn lại của khối hocavr[] vào sector 4.
     Đoạn code từ dòng 92 đến 108 đọc và xuất từng khối dữ trong card ra terminal. Ngoài hàm đọc card mmc_readblock, các dòng code ở phần này khá quen thuộc, bạn đọc hãy tự giải thích.
     Trước khi tiến hành mô phỏng, bạn hãy vào thư mục chứa Project của ví dụ này, tạo 1 thư mục tên card, trong thư mục card tạo 1 file trống tên mycard.mmc. Mở cửa sổ Properties của mô hình mmc card trong phần mêm Proteus, tại phần “Card Image File”, dùng nút công cụ bên phải để tìm đến file mycard.mmc đã tạo. Nội dung ô Card Image File bây giờ sẽ là “card\mycard.mmc”. Tiến hành mô phỏng, kết quả mô phỏng hiển thị trên Terminal như trong hình 6.

result-terminal

Hình 6. Kết quả ghi – đọc dữ liệu từ mmc card.