Lap trinh ARM (phan 2)

3. Lập trình điều khiển MMC/SD card với board STM32-GEM3M trên nền TOPPERS/ASP

MMC là viết tắt của MultiMedia Card. Đây là loại thẻ nhớ sử dụng bộ nhớ NAND flash để lưu trữ dữ liệu được giới thiệu lần đầu vào năm 1997 bởi Siemens AG và SanDisk. Đối với các ứng dụng nhúng ở mức vi điều khiển, MMC/SD card là sự lựa chọn thích hợp cho các ứng dụng cần lưu trữ dữ liệu vì kết nối phần cứng với thẻ MMC/SD

đơn giản, hỗ trợ giao tiếp SPI, đồng thời dung lượng bộ nhớ lớn(có thể lên tới 32GBs). Bộ nhớ của thẻ MMC/SD được tổ chức dạng block, tương tự như ổ cứng(hardisk) trong máy tính. Do vậy cũng như ổ cứng, MMC/SD sử dụng tập lệnh ATA để giao tiếp với phần mềm điều khiển.

 Một số đặc điểm khi thẻ MMC/SD hoạt động ở chế độ SPI

 + Truyền dữ liệu đồng bộ trên 3 tín hiệu: CLK, DI, DO. 

+ Kích hoạt thẻ thông qua tín hiệu : Chip Select(CS).

+ Tần số hoạt động từ 0-20MHz.

+ Hỗ trợ truy cập Single Block và Multi Block.

+ Sơ đồ kết nối

Thứ tự chânChân kết nốiLoạiMô tả
1CSInputChip Select
2 DIInput/Push PullData Input
3VSS-
4VDD-
5BCLKInputClock Source
6VSS2-
7DOOutput/Push PullData Output
8-
9-

Khi hoạt động ở chế độ SPI, các lệnh điều khiển và dữ liệu được truyển chung trên 2 tín hiệu DI và DO

Khi đọc dữ liệu từ thẻ



Khi ghi dữ liệu xuống thẻ



 FATFS

FatFS là bộ mã nguồn miễn phí hỗ trợ định dạng FAT(File Allocation Table) được sử dụng rộng rãi trong hệ điều hành Windows. Tổ chức của FatFS được mô tả như sau


FatFS sẽ cung cấp giao diện các hàm cơ bản để thực thi các thao tác file trên thẻ MMC/SD tương tự như lập trình file trên máy tính. 

+ FatFS hỗ trợ các kiểu format FAT12, FAT16, FAT32.

+ Các hàm giao diện lập trình: f_mount, f_open, f_close, f_read, f_write,f_lseek, f_sync, f_opendir, f_readdir, f_getfree, f_stat, f_mkdir, f_untrnk, f_chmod, f_rename, f_mkfs.

FatFS gồm 2 phần chính là phần FAT bao gồm các hàm liên quan đến File Allocation Table, và phần ATA liên quan đến xử lý các lệnh ATA để giao tiếp với thẻ nhớ.

Tổ chức file chính của FatFs gồm:

+ file ff.c: gồm các hàm hỗ trợ FAT. 

+ file ata.c: gồm các hàm giao tiếp ATA command.


Để giao tiếp với phần điều khiển (driver) của thẻ MMC/SD, FatFS yêu cầu 5 hàm giao diện 

+ disk_initialize: hàm khởi tạo kết nối tới thẻ MMC/SD, đồng thời kích hoạt cho thẻ ở trạng thái sẵn sàng hoạt động.

+ disk_write: ghi dữ liệu vào một vùng sector nhất định.

+ disk_read: đọc dữ liệu từ một vùng sector cho trước.

+ disk_status: lấy trạng thái của thẻ.

+ disk_ioctl: các hàm xử lý các yêu cầu khác sẽ được bố trí xử lý ở đây.

Kiến trúc của FatFS đơn giản do đó rất tiện cho việc “port” sang một hệ nhúng khác. Người dùng không cần có kiến thức sâu về FAT vẫn có thể sử dụng FatFS vào hệ thống của mình.

 Giao tiếp với thẻ MMC/SD

Thẻ MMC/SD có 4 chế độ hoạt động là: InActive, Card Identification, Data Transfer, Interrupt. Thông thường thẻ sẽ hoạt động ở hai chế độ Card Identification và Data Transfer. 

Để giao tiếp trao đổi dữ liệu với thẻ MMC/SD, vi điều khiển phải phát lệnh điều khiển xuống thẻ. Định dạng lệnh MMC được tổ chức gồm 48 bit như sau


Vị trí bit4746[45-40][39-8][7-1]0
Độ lớn1163271
Giá trị01xxx1
Mô tảStart bitTransmission bitCommand indexArgumentCRC7End bit

 Ở chế độ SPI, checksum luôn có giá trị là 0xFE ngoại trừ lúc khởi động vì lúc này tính năng tính checksum vẫn còn hoạt động. Khi nhận lệnh từ vi điều khiển, thẻ MMC/SD luôn trả lời lại bằng 1 byte. Nếu giá trị trả về là 0xFF có nghĩa là thẻ bận. 

Các lệnh MMC/SD thường gặp

Mã lệnhKý hiệuMô tả
CMD0GO_IDLE_STATEReset thẻ về trạng thái idle
CMD1SEND_OP_CODEYêu cầu thẻ gửi nội dung thông tin của Operating Condition Regiters
CMD8SEND_EXT_CSDYêu cầu thẻ gửi thông tin các thanh ghi CSD(Card Specific Data) dưới dạng block dữ liệu.
CMD9SEND_CSDYêu cầu thẻ gửi thông tin cụ thể của thanh ghi CSD.
CMD10SEND_CIDYêu cầu gửi các thông tin CID(Card Information Data).
CMD12STOP_TRANSMISSIONNgưng trao đổi dữ liệu
CMD16SET_BLOCKLENThiết lập độ lớn tính theo byte của một block dữ liệu, giá trị mặc này được lưu trong CSD
CMD17READ_SINGLE_BLOCKĐọc một block dữ liệu
CMD18READ_MULTIPLE_BLOCKĐọc nhiều block dữ liệu. Số lượng block được thiết lập bởi lệnh CMD23
CMD23SET_BLOCK_COUNTThiết lập số lượng block dữ liệu để ghi hoặc đọc.
CMD24WRITE_BLOCKGhi một block dữ liệu.
CMD25WRITE_MULTIPLE_BLOCKGhi nhiều block dữ liệu. Số lượng block được thiết lập bởi lệnh CMD23
CMD55APP_CMDThông báo cho thẻ nhớ lệnh tiếp theo là lệnh riêng của ứng dụng chứ không phải là lệnh chuẩn của MMC.

  Porting FatFS vào board STM32-GEM3M-2

Như phân tích ở trên, chúng ta chỉ cần chú ý vào 5 hàm giao diện với thẻ MMC/SD của FatFS.

Các hàm này sẽ trực tiếp gọi đến các hàm điều khiển phần cứng ta thường hay gọi là driver để trao đổi dữ liệu trực tiếp với thẻ.


 (*): disk_status không cần thiết phải cài đặt.

Như vậy chúng ta chỉ cần cài đặt các hàm ở khối “Driver” phù hợp với phần cứng của board STM32-GEM3M-2 là được.

+ hàm power_on(): nhiệm vụ hàm này là thiết lập các chân tín hiệu ra của STM32 cho phù hợp với kết nối SPI tới thẻ.

+ hàm power_off(): giải phóng các thiết lập trong hàm power_on().

+ hàm send_cmd(): gửi lệnh xuống thẻ theo đúng định dạng lệnh MMC/SD được mô tả trong bảng 1.

/* Send command packet */

xmit_spi(cmd); /* Start + Command index */

xmit_spi((BYTE)(arg >> 24));                             /* Argument[31..24] */

xmit_spi((BYTE)(arg >> 16));                             /* Argument[23..16] */

xmit_spi((BYTE)(arg >> 8));               /* Argument[15..8] */

xmit_spi((BYTE)arg);               /* Argument[7..0] */

n = 0x01; /* Dummy CRC + Stop */

if (cmd == CMD0) n = 0x95;               /* Valid CRC for CMD0(0) */

if (cmd == CMD8) n = 0x87;               /* Valid CRC for CMD8(0x1AA) */

xmit_spi(n);

+ hàm xmit_datablock(): gửi một khối dữ liệu xuống 

xmit_spi(token); /* Xmit data token */

if (token != 0xFD) { /* Is data token */

wc = 0;

do { /* Xmit the 512 byte data block to MMC */

xmit_spi(*buff++);

xmit_spi(*buff++);

} while (--wc);

xmit_spi(0xFF); /* CRC (Dummy) */

xmit_spi(0xFF);

resp = rcvr_spi(); /* Reveive data response */

if ((resp & 0x1F) != 0x05) /* If not accepted, return with error */

return 0;

}

+ hàm rcvr_datablock():

do { /* Wait for data packet in timeout of 200ms */

token = rcvr_spi();

#ifdef SUPPORT_TIMEOUT

} while ((token == 0xFF) && Timer1);

#else

} while ((token == 0xFF));

#endif

if(token != 0xFE) 

              return 0; /* If not valid data token, retutn with error */

do { /* Receive the data block into buffer */

rcvr_spi_m(buff++);

rcvr_spi_m(buff++);

rcvr_spi_m(buff++);

rcvr_spi_m(buff++);

} while (btr -= 4);

rcvr_spi(); /* Discard CRC */

rcvr_spi();


Trước khi nhận dữ liệu, kiểm tra xem thẻ MMC/SD có đang bận hay không

+ hàm xmit_spi():

#define SPI_SD                   SPI1

#define xmit_spi(dat) sd_raw_rw_spi(dat)

BYTE sd_raw_rw_spi(BYTE b_data)

{

BYTE Data = 0;

 /* Wait until the transmit buffer is empty */

 //while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);

 /* Send the byte */

 SPI_I2S_SendData(SPI_SD, b_data);

 /* Wait until a data is received */

 while (SPI_I2S_GetFlagStatus(SPI_SD, SPI_I2S_FLAG_RXNE) == RESET);

 /* Get the received data */

 Data = SPI_I2S_ReceiveData(SPI_SD);

 /* Return the shifted data */

 return Data;

}

+ hàm rcvr_spi():






static

BYTE rcvr_spi (void)

{

return sd_raw_rw_spi(0xFF);

}



 Kết luận

FatFS với mã nguồn nhỏ gọn, rõ ràng đã cung cấp khả năng tuyệt vời truy cập file dưới định dạng FAT. Kiến trúc của FatFS rất phù hợp với các ứng dụng nhúng, tính khả chuyển(port) cho phép người phát triển sử dụng lại mã nguồn trên nhiều nền tảng phần cứng khác nhau. Người lập trình không cần phải hiểu rõ về định dạng FAT vẫn có thể sử dụng thành thạo bộ mã nguồn này trong các ứng dụng có điều khiển thẻ MMC/SD của mình. Đây chính là lợi ích lớn nhất của các middleware như FatFS

4. Lập trình I/O trên thẻ nhớ

Hiện nay, hệ thống file FAT (DOS) được lấy làm chuẩn định dạng cho thẻ nhớ flash. Bạn có thể dùng một con vi điều khiển để đọc và ghi file trên một thẻ nhớ SD hay MMC với hệ thống FAT. 

Sơ đồ mạch

Hình 01

Để đọc hay ghi file trên thẻ, đầu tiên chúng ta phải khởi tạo thẻ nhớ. Đoạn code sau đây thực hiện việc này.
boolean initializeMemCard(void)
{
    MEM_CS = HIGH;
    for(i= 0; i < 10; i++)
    {
        spi_put(0xFF);
    }
    MEM_CS = LOW;
    send_command(0x40,0,0,0,0x95);
    if(card_response(0x01))
    {
        i = 255;
        do
        {
            send_command(0x41,0,0,0,0,0xFF);
            i--;
        } while (!card_response(0x00) && i > 0);
        MEM_CS = HIGH;
        spi_put(0xFF);
        if(i == 0)
        {
            return false;
        }
        return true;
    }
    return false;
}

   Code 01

Chú ý: MEM_CS chính là tín hiệu chip select cho thẻ nhớ.

Tại hàm khởi tạo iniializeMemCard trong Code 1, dòng đầu tiên sẽ disable thẻ nhớ bằng cách đặt biến MEM_CS về giá trị HIGH. Tiếp theo, một vòng lặp sẽ đảm bảo rằng thẻ nhớ có đủ thời gian để hoàn thành quá trình khởi động. Lệnh spi_put (byte x) sẽ truyền tham số vào chân SDI của thẻ nhớ. Trong vòng lặp này, tham số x phải có giá trị 0xFF để thẻ nhớ khởi động thành công. Sau đó biến MEM_CS được thiết lập thành LOW và lệnh 0x40 (đưa thẻ nhớ về trạng thái rảnh rỗi) được gọi với các tham số lần lượt là 0, 0, 0, 0 và 0x95 (giá trị CRC). Đây là lúc duy nhất giá trị CRC quan trọng, vì sau khi card vào chế độ SPI, CRC sẽ không cần thiết nữa.
Lệnh send_command (byte cmd, byte arg1, byte arg2, byte arg3, byte arg4, byte CRC) sẽ truyền 6 tham số của nó bằng cách gọi lệnh spi_put (byte x) 6 lần.
Dòng code tiếp theo gọi hàm card_reponse (byte x) để đợi tín hiệu trả lời thích hợp từ thẻ nhớ. Hàm này sẽ gọi liên tục lệnh spi_get () và đợi token trả về. Nếu token = 0x01 thì hàm trả về true, nếu sau một số lần lặp lại token vẫn khác 0x01 thì hàm sẽ trả về false. Để đưa card ra khỏi trạng thái rảnh rỗi, lệnh 0x41 sẽ được gửi liên tục cho đến khi nhận được tín hiệu trả lời thích hợp. Nếu sau 255 lần lặp lại mà vẫn không thành công, hàm initializeMemCard sẽ trả về false.
Sau khi có tín hiệu trả lời thích hợp, biến MEM_CS lại được thiêt lập bằng HIGH và một byte sẽ được gửi đi, tạo ra thêm 8 chu kỳ đồng hồ để thẻ nhớ hoàn tất việc khởi động. Chú ý rằng các chu kỳ đồng hồ này cần thiết đối với tất cả các lệnh.
Đến đây, chúng ta đã tóm tắt được các bước của tiến trình khởi động của thẻ nhớ (các tiến trình khác cũng tương tự):
    1. Đặt biến MEM_CS=LOW.
    2. Gọi lệnh (1 byte lệnh, 4 byte thamsố, 1 byte CRC).
    3. Đợi một byte trả lời từ thẻ nhớ.
    4. Chờ nhận một data token (thao tác đọc) hay gửi data token (thao tác ghi).
    5. Gửi hay nhận một khối dữ liệu.
    6. Nhận 2 byte thông tin kiểm tra lỗi.
    7. Đặt biến MEM_CS=HIGH.
    8. Tạo thêm 8 chu kỳ đồng hồ để thẻ nhớ hoàn tất thao tác.
Bước 5 và 6 không xuất hiện trong tiến trình khởi động, nhưng là bước chính trong các thao tác đọc và ghi. Chú ý là thẻ nhớ nên tự động khởi tạo kích thước khối dữ liệu 512 byte như kích thước sector của FAT.

LệnhByte codeResponse byteData token nhậnData token chuyển
Khởi tạo và đưa thẻ vào trạng thái rảnh rỗi0x400x01----
Đưa thẻ ra khỏi trạng thái rảnh rỗi0x410x00----
Đọc dữ liệu0x510x000xFE--
Ghi dữ liệu0x580x00--8 chu kỳ đồng hồ, 0xFE
Đặt độ dài khối dữ liệu0x500x00----

Bảng 01

Bảng trên liệt kê tất cả các giá trị lệnh cần thiết để thao tác với hệ thống FAT. Mặc dù thẻ nhớ hỗ trợ chế độ đọc một khối một lần và cả nhiều khối một lần nhưng chúng ta hãy cùng tìm hiểu việc đọc một khối 512 byte (gửi lệnh đọc 0x51).
Phải đảm bảo rằng tham số địa chỉ được định dạng thích hợp cho thao tác đọc, ghi. Ví dụ, nếu ta muốn đọc sector thứ 2005, 4 byte địa chỉ tham số phải là 0x00, 0x0F, 0xA8, 0x00 vì sector được đánh số khởi đầu bằng 0. Sector 2004 có địa chỉ 0x000FA800, được xác định như sau:
    (2,005-1)x512= 1,024,048 = 0x000FA800. 
Chú ý rằng định dạng địa chỉ của SD/MMC là kiểu big endian. Đoạn code trong Code 2 sẽ đọc sector 2004 trong thẻ nhớ. Đoạn này gần tương tự với đoạn khởi tạo thẻ. Khác biệt đáng chú ý là việc gọi thêm hàm card_response (byte x) lần thứ 2 với tham số có giá trị 0xFE chính là data token. Tất cả dữ liệu được trao đổi qua card đều bắt đầu với data token này. 512 lần gọi hàm spi_get() trong vòng lặp sẽ điền đầy giá trị của sector 2004 vào buffer. Hai lần gọi thêm hàm spi_get() sẽ đọc các byte CRC không được dùng tới được đính kèm vào các khối dữ liệu gửi bởi thẻ nhớ.

unsigned char buf[512];
boolean read_sector(void)
{
    unsigned short i;
    boolean retval = false;
    MEM_CS = LOW;
    send_comman(0x51,0,0x0F,xA8,0,0xFF);
    if(card_response(0x00))
    {
        if(card_response(0xFE))
        {
            for(i = 0; i < 512; i++)
            {
                buf[i] = spi_get();
            }
            spi_get();
            spi_get();
            retval = true;
        }
        MEM_CS = HIGH;
        spi_put(0xFF);
    }
    return retval;
}

    Code 02

Việc ghi dữ liệu cũng tương tự như việc đọc, điểm khác nhau duy nhất chỉ là hướng đi của dữ liệu. Trong thao tác đọc, thẻ nhớ cung cấp data token và dữ liệu còn trong thao tác ghi, bộ vi điều khiển chịu trách nhiệm này. Để ghi một khối dữ liệu, bạn cần gửi lệnh ghi (0x58) cùng với địa chỉ đúng định dạng, chờ byte trả lời, tạo ra 8 chu kỳ đồng hồ, gửi data token, và bắt đầu gửi dữ liệu. Chú ý đừng quên 2 byte CRC cuối cùng đính vào khối dữ liệu. Không giồng thao tác đọc, ở thao tác ghi bạn phải đảm bảo thẻ nhớ đã hoàn tất việc ghi sau khi bạn đã gửi dữ liệu. Để thực hiện việc này, ta cần kiểm tra byte trả lời xuất ra từ thẻ nhớ. Hàm checkWriteState() gọi lặp lại nhiều lần hàm spi_get() trong khi chờ data token 0x05. Khi đã có data token này rồi, byte khác 0 đầu tiên có được từ hàm spi_get() sẽ báo hiệu hoàn tất.


VÍ DỤ VỀ FAT

Sơ đồ FAT

Hình 02

Hãy xem xét 2 ví dụ về đọc và ghi file trên một thẻ nhớ. Giả sử chúng ta đang làm việc với thẻ được định dạng FAT 16. Trên một đĩa được định dạng theo FAT 16 sẽ có khoảng 65,000 vị trí nhớ. Một thẻ nhớ được định dạng như một đĩa cứng với một phân vùng (phân vùng DOS chính). Như hình 2, sector đầu tiên (512 byte) của thẻ nhớ chứa MBR (master boot record). Phân vùng FAT (phân vùng DOS chính) tiếp theo ngay phía sau. Phân vùng này bắt đầu bằng một vài sector để dành với sector đầu tiên là boot record (không được nhầm với MBR), 1 hoặc 2 bảng FAT và một bảng thư mục gốc (root directory table). Vùng dữ liệu thật sự nằm ngay phía sau bảng thư mục gốc, được bố trí thành từng nhóm các sector gọi là cluster. Không gian lưu trữ được gán cho các file theo từng cluster. MBR chứa một trình nạp nhỏ (boot strap loader) và bảng thư mục. Trong một đĩa cứng thật sự, trình nạp sẽ tìm và chạy trình nạp thứ 2 trong boot record của phân vùng chính (primary partition). Trong một thẻ nhớ, MBR vẫn chứa chương trình mồi chính, nhưng máy tính chỉ dùng thông tin từ bảng thư mục.

ĐỌC FILE


Byte thứ0 – 78 – 1011 – 2526 – 2728 – 31
File 1Tên filePhần mở rộngThuộc tính, ngàyCluster đầuKích thước
File 2Tên filePhần mở rộngThuộc tính, ngàyCluster đầuKích thước
File nTên filePhần mở rộngThuộc tính, ngàyCluster đầuKích thước

Bảng 02

Chúng ta sẽ xem xét việc đọc một file hello.txt chứa trong thư mục gốc và đưa nội dung của nó (Hello World) vào một mảng ký tự. Bạn có thể thử nghiệm ví dụ này bằng cách tạo file có nội dung “Hello World”, lưu file vào thư mục gốc của thẻ nhớ. Để đọc file, bạn phải xác định mục nhập của file trong bảng thư mục gốc ngay phía sau 2 bảng FAT (xem hình 2). Bảng thư mục gốc có cấu trúc dạng bảng (xem bảng 2), mỗi một dòng 32 byte tương ứng với một mục nhập file. Một vài dòng sẽ trống hay trỏ về các file đã bị xóa. Các cột trong bảng sẽ cung cấp các thông tin vê file. Cột đầu tiên là tên file, cột thứ 2 chỉ phần mở rộng, không bao gồm dấu chấm (FAT 16 có tên file dạng 8.3). Cột kế cuối chỉ ra cluster đầu tiên chứa dữ liệu file và cột cuối cùng xác định kích thước file theo byte (định dạng little endian). Các cột khác (byte 11 - 25) chứa các thông tin khác như ngày giờ tạo, thay đổi file.
Để xác định file hello.txt, tìm tên file và phần mở rộng trong bảng thư mục gốc. Vì bảng thư mục gốc cũng chỉ là dữ liệu chứa trong bộ nhớ, bạn có thể đọc thông tin từ bảng thư mục theo từng sector dùng hàm read_sector(). Sector đầu tiên trong phân vùng FAT là boot record, chứa các tham số cho ta biết vị tri của sector đầu tiên của bảng thư mục gốc. Ngoài ra ta cũng biết chiều dài của mỗi dòng trong bảng thư mục, như vậy sẽ dễ dàng tìm ra file hello.txt. Hàm này sẽ trả về dòng chứa mục nhập của file. Nếu không tìm ra, có nghĩa là file không tồn tại trong thẻ nhớ. Trong bảng thư mục gốc, ký tự đầu tiên trong tên một file bị xóa được thay thế bằng một ký tự đặc biệt. Dòng đầu tiên bắt đầu bằng byte có giá trị 0x00 chỉ ra rằng sẽ không có dòng tiếp theo nào được dùng. Vì thế, trừ khi thư mục gốc chứa tối đa số file nó có thể, công việc tìm kiếm có thể dừng lại mà không cần phải đọc đến dòng cuối cùng của bảng. Giả sử chúng ta đã biết được dòng tương ứng với mục nhập file hello.txt. Bạn có thể đọc 2 byte từ cột chứa số cluster đầu tiên theo định dạng litle endian để xác định xem dữ liệu bắt đầu từ đâu. Bạn cũng có thể đọc cột kích thước file để biết kích thước file. Để bắt đầu đọc nội dung file, bạn phải xác định được sector đầu tiên của cluster đầu tiên. Boot record có chứa một tham số cho chúng ta biết có bao nhiêu sector trong một cluster. Đây là thông tin được xác định khi đĩa được dịnh dạng. Cluster càng lớn thì bảng FAT càng nhỏ nhưng sẽ dẫn đến việc lãng phí vùng nhớ đối với các file không dùng hết dữ không gian nhớ ở cluster cuối cùng của file. Vùng dữ liệu nằm ngay sau bảng thư mục gốc, vì thế bạn có thể xác định sector đầu tiên của cluster đầu tiên theo cách sau: 
Nếu cột cluster đầu tiên trong bảng thư mục của file hello.txt chứa số 107 thì cluster 107 sẽ là nơi bắt đầu chứa dữ liệu cho file (Cluster đầu tiên của vùng dữ liệu là cluster 2 vì mục nhập số 0 và 1 đã dành cho thông tin bảng FAT như hình 2). Giả sử rằng mỗi cluster có 4 sector. Có nghĩa là file bắt đầu tại sector số:
(Sector đầu của vùng dữ liệu) + [(cluster đầu - 2) x (số sector trên một cluster)]
Trong đó, sector đầu của vùng dữ liệu được tính toán với tham số trong MBR để xác định vị trí bắt đầu của phân vùng FAT, sau đó dùng tham số của boot record để xác định vị trí bắt đầu của vùng dữ liệu.
Trở lại với file hello.txt, giả sử rằng file dài 12 byte. Có nghĩa là toàn bộ dữ liệu của file chứa đủ trong một cluster đầu tiên (cụ thể là trong sector đầu tiên). Bây giờ hàm read_sector() có thể đọc dữ liệu của file vào bộ vi điều khiển. Tất cả các byte sau byte thứ 12 được bỏ qua. 
Nếu bạn thay đổi file hello.txt để nó chứa 1000 dòng “Hello World!”. Nội dung của file lúc này sẽ không chứa đủ trong 1 cluster. Làm thế nào bạn xác định được phần còn lại của file nằm ở đâu? Câu trả lời nằm trong bảng FAT. Sau khi đọc dữ liệu từ cluster 107, bạn sẽ vào mục nhập 107 trong bảng FAT để đọc xem cluster tiếp theo là số bao nhiêu. Có thể có 2 bảng FAT giống hệt như nhau trên thẻ nhớ (xem hình 2). Boot record chứa các tham số cho bạn biết có bao nhiêu bảng FAT, độ dài mỗi bảng FAT và vị trí bắt đầu của bảng FAT đầu tiên so với boot record. Mỗi mục nhập trong FAT 16 là một số 16 bit (định dạng little endian), nên mục nhập 107 sẽ nằm tại byte 214 và 215 của sector đầu tiên của mỗi bảng FAT. Nhiều bảng FAT cùng hiện diện trên thẻ nhớ để có thể phục hồi nếu hệ thống file gặp sự cố. Cấu trúc bảng FAT đơn giản, nhưng bù lại không tốt lắm trong việc chịu lỗi. Nếu máy tính gặp sự cố mất điện khi bạn đang cập nhật bảng FAT thì bạn có thể sẽ mất vị trí dữ liệu tiếp theo của một số lớn file. Mỗi bảng FAT được cập nhật riêng rẽ, vì thế chỉ có một bảng FAT bị hỏng khi gặp sự cố. Các dịch vụ CHKDSK hay SCANDSK của hệ điều hành cố gắng tối thiểu hóa chuyện mất mát dữ liệu bằng cách phục hồi tối đa thông tin từ bảng FAT còn lại không bị hư. Để tìm cluster tiếp theo của file, đọc sector tương ứng (thường từ bảng FAT đầu tiên) bằng hàm read_sector() và lấy dữ liệu cần thiết. Giả sử mục nhập 107 trong bảng FAT chứa giá trị 489. Có nghĩa là khối dữ liệu tiếp theo của file nằm tại cluster 489. Cứ như vậy ta sẽ tìm ra dần dần các khối dữ liệu tiếp theo của file. Ta sẽ biết khi nào đã hết dữ liệu của file vì ta đã có thông tin về độ dài của file, hơn nữa khi một mục nhập của file trong bảng FAT chứa giá trị từ 0xFFF8 đến 0xFFFF thì có nghĩa là file không còn cluster nào theo sau nữa.

GHI FILE

Ở ví dụ thứ 2 chúng ta sẽ tạo file goodbye.txt trong thư mục gốc với nội dung “Goodbye World!”. Đầu tiên, bạn cần tìm một dòng còn dùng được trong bảng thư mục gốc để tạo mục nhập mới. Bạn có thể làm điều này bằng cách tìm một mục nhập có tên file bắt đầu với ký tự 0xE5 hay 0x00, tương ứng với file đã bị xóa hoặc là mục nhập chưa dùng. Sau khi tìm được mục nhập ta sẽ ghi vào mục nhập tên file, phần mở rộng, thời gian tạo file và các thuộc tính của file. Trường thuộc tính tại byte thứ 11 chứa một số cờ bit, trong đó có một cờ chỉ ra rằng đây là file hay là một thư mục con. Khi tạo một file bình thường, bạn phải đặt giá trị cho trường thuộc tính là 0x20. Một điều rắc rối là bạn cần phải ghi vào bảng thư mục này vị trí cluster đầu tiên của file. Có 2 trường hợp phải xem xét. Trường hợp đầu tiên là ta đang dùng lại mục nhập của một file bị xóa. Vị trí cluster đầu tiên và toàn bộ chuỗi giá trị trong FAT (của file đã bị xóa) vẫn không bị thay đổi. Bạn có thể ghi đè dữ liệu lên các cluster này. Nếu file mới tạo dài hơn file cũ bị xóa thì ta thêm cluster vào. Nói thêm về trường hợp file bị xóa: Để giảm thiểu các tác vụ I/O khi một file nào đó bị xóa, chỉ một ký tự đầu tiên của tên file đó bị chuyển thành 0xE5. Phần thông tin còn lại trong bảng thư mục không thay đổi. Đây là lý do tại sao file có thể được phục hồi. Trường hợp thứ 2 là ta dùng một mục nhập mới trong bảng thư mục gốc, như vậy ta cần phải tìm một cluster trống. Để tìm một cluster trống, ta chỉ cần duyệt FAT bằng cách đọc từng sector một và tìm một mục nhập có giá trị 0x0000. Sau khi tìm được, đổi giá trị của nó thành 0xFFFF để báo hiệu đây là nơi kết thúc file, và ghi lại vị trí này vào trường vị trí cluster đầu tiên trong bảng thư mục gốc. Với vị trí của cluster trống cho file goodbye.txt, bạn có thể bắt đầu ghi nội dung của file (“Goodbye World!”). Bạn ít nhất phải ghi vào sector đầu tiên của cluster. Đặt chuỗi cần ghi vào một mảng ký tự 512 byte rồi dùng hàm write_sector() để ghi vào thẻ nhớ. Việc mảng ký tự chứa gì sau ký tự cuối cùng cần ghi không quan trọng. Chúng ta biết rằng nội dung file goodbye.txt có thể được chứa hoàn toàn trong sector đầu tiên của một cluster nên ta không cần viết vào các sector tiếp theo nữa.

Sau khi thêm nội dung vào file, cập nhật trường kích thước file trong bảng thư mục. Đồng thời ta cũng nên cập nhật thời gian truy cập/thay đổi file. Nếu một file tăng kích thước và cần thêm cluster để chứa, một cluster trống mới cần được xác định trong bảng FAT. Vị tri của cluster trống này sẽ được ghi vào mục nhập của cluster trước đó của file trong bảng FAT, và giá trị của mục nhập cho cluster cuối cùng của file sẽ là 0xFFFF để xác định đây là cluster kết thúc của file. Vì thế, một chuỗi mục nhập trong FAT có thể kéo dài mãi cho đến khi còn cluster trống.

int main(void)
{
    signed int stringSize;
    signed char handle;
    char stringBuf[100];
    cpu_init(); //Initialize the CPU clocks, etc.
    eint(); //Enable global interrupts (if required).
    fat_initialize(); //Initialize the FAT library.
    handle = fat_openRead("hello.txt");
    if (handle >= 0)
    {
        stringSize = fat_read(handle, stringBuf, 99);
        stringBuf[stringSize] = ‘’;
        lcd_print(stringBuf);
        fat_close(handle);
    }
    handle = fat_openWrite("goodbye.txt");
    if (handle >= 0)
    {
        strcpy(stringBuf, "Goodbye World!");
        stringSize = strlen(stringBuf);
        fat_write(handle,stringBuf, stringSize);
        fat_flush(); //Optional.
        fat_close(handle);
    }
    while (1)
    {
        //Stay here.
    }
}



Code 03

Ví dụ:
Đoạn code trên sẽ đọc 12 ký tự đầu tiên từ file hello.txt trong thư mục gốc. Sau đó chương trình in các ký tự đó ra màn hình LCD (dòng chữ Hello World!). Tiếp đến chương trình sẽ tạo file goodbye.txt và ghi “Goodbye Worlds!” vào file. Khi chương trình kết thúc, bạn có thể gắn thẻ nhớ của bạn vào bất kỳ đầu đọc thẻ nhớ chuẩn nào và bạn sẽ thấy file goodbye.txt trong thẻ

Post a Comment

Mới hơn Cũ hơn