Gặp lỗi không rõ nguyên nhân khi sử dụng hàm thư viện stc8a8k64d4

, ,

Tôi đã đặt hàm khởi tạo chân IO và hàm khởi tạo ADC vào cùng một hàm, nhưng dù xếp chân IO đứng trước hay ADC đứng trước thì hàm phía sau cũng đều báo lỗi, Keil không thể nhận diện được (như hình). Tuy nhiên, khi tách hai cái này ra thành hai hàm riêng biệt thì lại hoạt động bình thường. Đây là lỗi gì vậy, mong được chỉ giáo.

Hãy sao chép toàn bộ mã và gửi lên, chụp màn hình không thể nhìn rõ được có phải là vấn đề về ký hiệu hay không (ký hiệu tiếng Trung và tiếng Anh có sự khác biệt).

Đây là một vấn đề chuẩn C điển hình và dễ gây bực bội, đặc biệt thường gặp khi sử dụng Keil (đặc biệt là C51 hoặc các phiên bản trình biên dịch cũ hơn).

Nguyên nhân gốc rễ: Hạn chế của tiêu chuẩn C89

Lý do gây lỗi nằm ở chỗ: Theo tiêu chuẩn C89 (tiêu chuẩn mà nhiều trình biên dịch nhúng mặc định tuân theo), tất cả các khai báo biến phải được đặt ở đầu thân hàm, tức là trước bất kỳ câu lệnh thực thi nào.

Hãy xem xét đoạn mã của bạn (như trong hình dưới):

  1. Bạn khai báo GPIO_InitStructure trước (đây là khai báo, không vấn đề gì).
  2. Ngay sau đó, bạn thực hiện GPIO_InitStructure.Mode = ... và gọi hàm GPIO_Inilize(...) (đây là các câu lệnh thực thi).
  3. Sau đó, ở dòng 11, bạn lại tiếp tục khai báo ADC_InitTypeDef ADC_InitStructure;.

Vì khai báo ở dòng 11 xuất hiện sau lời gọi hàm ở dòng 10, trình biên dịch sẽ không nhận diện được nó, dẫn đến lỗi syntax error (lỗi cú pháp) và các lỗi undefined identifier (định danh chưa được định nghĩa) tiếp theo.


Cách khắc phục

Phương pháp 1: Di chuyển toàn bộ khai báo biến lên đầu hàm (khuyên dùng)

Đây là cách sửa an toàn và ổn định nhất — đưa tất cả các khai báo cấu trúc InitTypeDef lên vị trí đầu tiên của hàm.

void ADC_Init()
{
    // 1. Hoàn thành việc khai báo tất cả các biến trước
    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_InitTypeDef  ADC_InitStructure;

    // 2. Sau đó mới bắt đầu viết logic và mã khởi tạo
    GPIO_InitStructure.Mode = GPIO_PullUp;
    // ... các thiết lập GPIO khác
    GPIO_Inilize(GPIO_P0, &GPIO_InitStructure);

    ADC_InitStructure.ADC_SMPduty = 15;
    // ... các thiết lập ADC khác
    ADC_Inilize(&ADC_InitStructure);
    
    ADC_PowerControl(ENABLE);
}

Phương pháp 2: Bật chế độ C99

Nếu bạn muốn được tự do khai báo biến tại nơi sử dụng như trong C++ hiện đại hay Java, bạn có thể bật chế độ C99 trong cài đặt Keil:

  1. Nhấp vào biểu tượng “Magic Wand” (Options for Target).
  2. Chọn tab C/C++.
  3. Tích chọn C99 Mode.

Lưu ý: Một số trình biên dịch 8051 (ví dụ như C51 phiên bản cũ) có thể không hỗ trợ C99. Nếu sau khi tích chọn xuất hiện thêm nhiều lỗi hơn, hãy quay lại dùng phương pháp 1.


Vì sao tách thành hai hàm thì lại được?

Khi bạn tách chúng thành hai hàm riêng biệt, mỗi hàm đều bắt đầu bằng dòng khai báo biến — điều này phù hợp với quy tắc “khai báo phải đứng trước câu lệnh thực thi”, do đó trình biên dịch có thể thông dịch thành công.

Một lời khuyên nhỏ:
Trong đoạn mã, tên hàm GPIO_Inilize bị sai chính tả (thừa chữ “i”) — đây là một “truyền thống” đặc trưng của thư viện STC. Dù trông có vẻ khó chịu, nhưng hãy đảm bảo rằng tên hàm trong mã nguồn khớp với tên trong file header.

1 Lượt thích

Chao ôi, mình trước đây cũng từng gặp phải cái bẫy giống hệt như vậy rồi!:face_with_steam_from_nose:

Trình biên dịch Keil C51 này là một phần mềm cổ lỗ sĩ, mặc định dùng chuẩn C89, do đó các biến bắt buộc phải khai báo ngay đầu hàm, không được chèn thêm khai báo biến ở giữa đoạn mã, nếu không nó sẽ báo lỗi cú pháp ngay lập tức. Hồi đó mình mất gần nửa tiếng mới phát hiện ra vấn đề.

Bạn nhìn trong đoạn code của mình đi, đã khai báo cấu trúc cho GPIO xong, sau đó gọi hàm GPIO_Inilize, rồi mới tiếp tục khai báo cấu trúc cho ADC. Trình biên dịch gặp vậy là hoảng loạn, tưởng bạn đang viết mã loạn xạ. Cách khắc phục cực kỳ đơn giản: hãy dời toàn bộ phần khai báo ADC_InitTypeDefGPIO_InitTypeDef lên ngay đầu hàm, hoàn tất việc khai báo tất cả các biến trước, sau đó mới viết các câu lệnh thực thi, mọi chuyện sẽ ổn ngay.

1 Lượt thích

Có vẻ như bạn đã gặp phải một rắc rối kinh điển mang tên “C89”! Trong khi các ngôn ngữ lập trình hiện đại (và các chuẩn C mới hơn như C99 hay C11) cho phép khai báo biến ở bất kỳ đâu, thì trình biên dịch Keil C51 (dùng cho vi điều khiển STC) thường được cấu hình để tuân theo chuẩn cũ hơn là ANSI C (C89).

Nguyên nhân: “Khai báo trước câu lệnh”

Trong chuẩn C89, tất cả các khai báo biến phải xuất hiện ngay từ đầu khối mã (ví dụ như trong một hàm), trước mọi câu lệnh thực thi (gán giá trị, gọi hàm, v.v.).

Xem đoạn mã của bạn:

  1. Bạn khai báo GPIO_InitStructure (khai báo - OK).
  2. Bạn gán GPIO_InitStructure.Mode = ... (câu lệnh - OK).
  3. Sau đó bạn lại cố gắng khai báo ADC_InitTypeDef ADC_InitStructure; (khai báo sau câu lệnh - Lỗi!).

Trình biên dịch bị nhầm lẫn vì nó mong đợi các câu lệnh logic/toán học sau dòng 10, chứ không phải một định nghĩa biến hoàn toàn mới.


Cách sửa: Đưa tất cả khai báo lên đầu

Để khắc phục, bạn chỉ cần dồn tất cả các khai báo biến lên đầu hàm ADC_Init().

void ADC_Init()
{
    // 1. Tất cả khai báo đều phải ở đây
    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_InitTypeDef  ADC_InitStructure; 

    // 2. Bây giờ mới thực hiện các lệnh khởi tạo
    GPIO_InitStructure.Mode = GPIO_PullUp;
    GPIO_InitStructure.Pin = GPIO_Pin_0 | GPIO_Pin_1 | ...;
    GPIO_Inilize(GPIO_P0, &GPIO_InitStructure);

    ADC_InitStructure.ADC_SMPduty = 15;
    ADC_InitStructure.ADC_Speed = ADC_SPEED_2X16T;
    // ... và tiếp tục
    ADC_Inilize(&ADC_InitStructure);
    ADC_PowerControl(ENABLE);
}

Tại sao tách riêng thành từng hàm lại hoạt động?

Khi bạn đặt chúng vào các hàm riêng biệt, mỗi biến đều nằm ở đầu khối hàm của chính nó. Vì không có câu lệnh thực thi nào đứng trước chúng trong từng hàm đó, nên trình biên dịch hoàn toàn hài lòng.


Mẹo chuyên nghiệp: Thay đổi cài đặt Keil

Nếu bạn muốn tránh vấn đề này trong tương lai và viết mã C trông “hiện đại” hơn, bạn có thể thử bật chế độ C99 trong Keil:

  1. Nhấp chuột phải vào thư mục Target hoặc Project.
  2. Chọn Options for Target.
  3. Chuyển đến tab C51 (hoặc C/C++).
  4. Tìm ô chọn có nhãn C99 Mode và tích vào đó.

Lưu ý: Không phải tất cả các phiên bản trình biên dịch C51 đều hỗ trợ đầy đủ C99, do đó cách “đưa khai báo lên đầu” vẫn là phương pháp đáng tin cậy nhất để đảm bảo mã nguồn của bạn có thể di chuyển dễ dàng giữa các chip dựa trên nền tảng 8051.