Vi Điều Khiển

Chào mừng các bạn đến với thế giới của Vi điều khiển!

--welcome to the world of microcontrollers^^ --

Điện Tử Cơ Bản

nơi khởi đầu

Lập Trình

linh hồn của phần cứng

Thứ Hai, 30 tháng 7, 2012

Mũi điện tử giúp chẩn đoán sớm căn bệnh ung thư


Trung tâm nghiên cứu công nghệ nano thuộc Học viện công trình hóa học, Đại học công nghệ Haifa (Israel) vừa tuyên bố đã nghiên cứu thành công mũi điện tử bionic chẩn đoán sớm bệnh ưng thư.
Ảnh minh hoa. Nguồn: Internet
Thiết bị trên có thể chẩn đoán nhanh, chính xác các bệnh ung thư và vị trí ung thư như ung thư phổi, ung thư tuyến vú, ung thư tuyến tiền liệt và ung thư ruột kết.

Thứ Tư, 25 tháng 7, 2012

Thấu kính áp tròng tích hợp màn hình Led


Thấu kính áp tròng tích hợp màn hình Led, có thể hiển thị nhiều thông tin kiểu đồ họa, đang bước dần từ phim ảnh viễn tưởng ra cuộc sống thật nhờ công trình nghiên cứu của các nhà khoa học.

Nắm bắt xu thế ngày càng nhỏ gọn của các thiết bị hiển thị hình ảnh di động, nhà nghiên cứu Babak Parviz tại ĐH Washington, Seattle, Mỹ, đã đưa ra ý tưởng tích hợp những thiết bị này với một thấu kính áp tròng.

Để thực hiện điều này, Parviz đã tìm cách nhúng những vi mạch điện tử trong chất nền như giấy hoặc nhựa. Những mạch điện và đi-ôt phát quang đã được bọc trong vật liệu tương hợp sinh học và sau đó đặt vào những đường nứt khắc vào ống kính.

Ghép thành công vi mạch điện tử cho người mù


Vi mạch điện tử kích thước 3 x 3mm, được cấy vào nhãn cầu mắt của người mù
Vi mạch điện tử kích thước 3 x 3mm, được cấy vào nhãn cầu mắt của người mù.
TPO - “Lần đầu tiên tôi thấy ánh sáng sau 20 năm sống trong bóng tối”. Người đàn ông mù người Anh đã hồi phục được khả năng thị giác sau khi phẫu thuật thành công vi mạch điện tử vào nhãn cầu mắt.

Thứ Năm, 19 tháng 7, 2012

[Học lập trình 8051] Bài 7: Ngắt trong 8051


Ngắt trong 8051

Mục tiêu

Kết thúc bài học này, bạn có thể:

Ø  Phân biệt cơ chế ngắt với hỏi vòng
Ø  Nắm rõ các loại ngắt trong 8051
·        Ngắt timer/counter
·        Ngắt ngoài
·        Ngắt truyền thông nối tiếp
Ø  Lập trình các ngắt
·        Trình phục vụ ngắt là gì?
·        Cho phép ngắt và cấm ngắt
·        Thiết lập mức ưu tiên của các ngắt

Giới thiệu

            Ngắt (Interrupt) - như tên của nó, là một số sự kiện khẩn cấp bên trong hoặc bên ngoài bộ vi điều khiển xảy ra, buộc vi điều khiển tạm dừng thực hiện chương trình hiện tại, phục vụ ngay lập tức nhiệm vụ mà ngắt yêu cầu – nhiệm vụ này gọi là trình phục vụ ngắt (ISR: Interrupt Service Routine).
Trong bài này ta tìm hiểu khái niệm ngắt và lập trình các ngắt trong bộ vi điều khiển 8051.

1. Các ngắt của 8051

1.1  Phân biệt cơ chế ngắt với hỏi vòng

Lấy ví dụ: Bộ vi điều khiển đóng vai trò như một vị bác sĩ, các thiết bị kiểm soát bởi vi điều khiển được coi như các bệnh nhân cần được bác sĩ phục vụ.
Bình thường, vị bác sĩ sẽ hỏi thăm lần lượt từng bệnh nhân, đến lượt bệnh nhân nào được hỏi thăm nếu có bệnh thì sẽ được bác sĩ phục vụ, xong lại đến lượt bệnh nhân khác, và tiếp tục đến hết. Điều này tương đương với phương pháp thăm dò - hỏi vòng (Polling) trong vi điều khiển.
Cứ như thế, nếu chúng ta có 10 bệnh nhân, thì bệnh nhân thứ 10 dù muốn hay không cũng phải xếp hàng chờ đợi 09 bệnh nhân trước đó. Giả sử trường hợp bệnh nhân thứ 10 cần cấp cứu thì sao? Anh ta sẽ gặp nguy cấp trước khi đến lượt hỏi thăm của bác sĩ mất! L Nhưng, nếu anh ta sử dụng phương pháp “ngắt” thì mọi chuyện sẽ ổn ngay. Lúc đó vị bác sĩ sẽ ngừng mọi công việc hiện tại của mình, và tiến hành phục vụ trường hợp khẩn cấp này ngay lập tức, xong việc bác sĩ lại trở về tiếp tục công việc đang dở. Điều này tương đương với phương pháp ngắt (Interrupts) trong vi điều khiển.
            Trở lại với bộ vi điều khiển của chúng ta: 1 bộ vi điều khiển có thể phục vụ cho nhiều thiết bị, có 2 cách để thực hiện điều này đó là sử dụng các ngắt (Interrupts) và thăm dò (polling):

Ø  Trong phương pháp sử dụng ngắt: mỗi khi có một thiết bị bất kỳ cần được phục vụ thì nó báo cho bộ vi điều khiển bằng cách gửi một tín hiệu ngắt. Khi nhận được tín hiệu ngắt thì bộ vi điều khiển ngừng tất cả những gì nó đang thực hiện để chuyển sang phục vụ thiết bị gọi ngắt. Chương trình ngắt được gọi là trình phục vụ ngắt ISR (Interrupt Service Routine) hay còn gọi là trình quản lý ngắt (Interrupt handler). Sau khi phục vụ ngắt xong, bộ vi xử lý lại quay trở lại điểm bị ngắt trước đó và tiếp tục thực hiện công việc.
Ø  Trong phương pháp thăm dò: bộ vi điều khiển kiểm tra liên tục tình trạng của tất cả các thiết bị, nếu thiết bị nào có yêu cầu thì nó dừng lại phục vụ thiết bị đó. Sau đó nó tiếp tục kiểm tra tình trạng của thiết bị kế tiếp cho đến hết. Phương pháp thăm dò rất đơn giản, nhưng nó lại rất lãng phí thời gian để kiểm tra các thiết bị kể cả khi thiết bị đó không cần phục vụ. Trong trường hợp có quá nhiều thiết bị thì phương án thăm dò tỏ ra không hiệu quả, gây ra chậm trễ cho các thiết bị cần phục vụ.

Điểm mạnh của phương pháp ngắt là:

Ø  Bộ vi điều khiển có thể phục vụ được rất nhiều thiết bị (tất nhiên là không tại cùng một thời điểm). Mỗi thiết bị có thể nhận được sự chú ý của bộ vi điều khiển dựa trên mức ưu tiên được gán cho nó. Đối với phương pháp thăm dò thì không thể gán mức ưu tiên cho các thiết bị vì nó kiểm tra tất cả mọi thiết bị theo kiểu hỏi vòng.
Ø  Quan trọng hơn, trong phương pháp ngắt thì bộ vi điều khiển còn có thể che (làm lơ) một yêu cầu phục vụ của thiết bị. Điều này lại một lần nữa không thể thực hiện được trong phương pháp thăm dò.
Ø  Lý do quan trọng nhất mà phương pháp ngắt được ưu chuộng là vì nó không lãng phí thời gian cho các thiết bị không cần phục vụ. Còn phương pháp thăm dò làm lãng phí thời gian của bộ vi điều khiển bằng cách hỏi dò từng thiết bị kể cả khi chúng không cần phục vụ.

Ví dụ trong các bộ định thời được bàn đến ở các bài trước ta đã dùng một vòng lặp kiểm tra và đợi cho đến khi bộ định thời quay trở về 0. Trong ví dụ đó, nếu sử dụng ngắt thì ta không cần bận tâm đến việc kiểm tra cờ bộ định thời, do vậy không lãng phí thời gian để chờ đợi, trong khi đó ta có thể làm việc khác có ích hơn.

1.2 Sáu ngắt trong 8051

            Thực tế chỉ có 5 ngắt dành cho người dùng trong 8051 nhưng các nhà sản xuất nói rằng có 6 ngắt vì họ tính cả lệnh RESET. Sáu ngắt của 8051 được phân bố như sau:

1.      RESET: Khi chân RESET được kích hoạt từ 8051, bộ đếm chương trình nhảy về địa chỉ 0000H.  Đây là địa chỉ bật lại nguồn.
2.      2 ngắt dành cho các bộ định thời: 1 cho Timer0 và 1 cho Timer1. Địa chỉ tương ứng của các ngắt này là 000BH001BH.
3.      2 ngắt dành cho các ngắt phần cứng bên ngoài: chân 12 (P3.2) và 13 (P3.3) của cổng P3 là các ngắt phần cứng bên ngoài INT0INT1 tương ứng. Địa chỉ tương ứng của các ngắt ngoài này là 0003H0013H.
4.      Truyền thông nối tiếp: có 1 ngắt chung cho cả nhận và truyền dữ liệu nối tiếp. Địa chỉ của ngắt này trong bảng vector ngắt là 0023H.

1.3 Trình phục vụ ngắt

            Đối với mỗi ngắt thì phải có một trình phục vụ ngắt (ISR) hay trình quản lý ngắt để đưa ra nhiệm vụ cho bộ vi điều khiển khi được gọi ngắt. Khi một ngắt được gọi thì bộ vi điều khiển sẽ chạy trình phục vụ ngắt. Đối với mỗi ngắt thì có một vị trí cố định trong bộ nhớ để giữ địa chỉ ISR của nó. Nhóm vị trí bộ nhớ được dành riêng để lưu giữ địa chỉ của các ISR được gọi là bảng vector ngắt. Xem Hình 1.


Thứ Sáu, 13 tháng 7, 2012

Virtual Serial Port Driver - Tạo cổng nối tiếp ảo


Virtual Serial Port Driver là phần mềm hữu hiệu để tạo ra các cổng nối tiếp ảo và kết nối chúng theo cặp thông qua dây cáp null-modem ảo. Các ứng dụng trên cả hai đầu của cặp đó sẽ có thể trao đổi dữ liệu cho nhau. 
Khi đó, dữ liệu được ghi trên cổng đầu tiên sẽ xuất hiện ở cổng thứ hai và ngược lại.
Tất cả các cổng nối tiếp ảo đều hoạt động chính xác như những cổng thực, mô phỏng các thiết lập của chúng. Do đó, bạn có thể tạo ra bao nhiêu cặp cổng ảo theo ý muốn mà không cần phải sử dụng phần cứng bổ sung nào.
Thêm vào đó, công nghệ cổng nối ảo Eltima có thể được tích hợp toàn diện vào trong phần mềm của chính bạn.

[Học lập trình 8051] Bài 6: Truyền thông nối tiếp với 8051

Truyền thông nối tiếp với 8051

Mục tiêu

Kết thúc bài học này, bạn có thể hiểu:

Ø  Truyền dữ liệu nối tiếp đồng bộ, không đồng bộ
Ø  Đóng khung dữ liệu trong truyền thông không đồng bộ
Ø  Chuẩn giao diện RS232
Ø  Nối ghép 8051 với chuẩn RS232
Ø  Các bước lập trình truyền thông nối tiếp cho 8051
·        Cài đặt khung truyền
·        Cài đặt tốc độ baud

Giới thiệu

Các máy tính truyền dữ liệu theo hai cách: Song song và nối tiếp. Trong truyền dữ liệu song song thường cần rất nhiều đường dây dẫn chỉ để truyền dữ liệu đến một thiết bị chỉ cách xa vài bước. Ví dụ của truyền dữ liệu song song là các máy in hoặc các ổ cứng, mỗi thiết bị sử dụng một đường cáp với nhiều dây dẫn. Mặc dù trong các trường hợp như vậy thì nhiều dữ liệu được truyền đi trong một khoảng thời gian ngắn bằng cách dùng nhiều dây dẫn song song, nhưng khoảng cách thì không thể lớn được. Vì các đường cáp dài làm suy giảm thậm chí làm méo tín hiệu. Ngoài ra, các đường cáp dài có giá thành cao. Vì những lý do này, để truyền d liệu đi xa thì  phải sử dụng phương pháp truyền nối tiếp.

1.      Các cơ sở của truyền thông nối tiếp

Trong truyền thông nối tiếp dữ liệu được gửi đi từng bit một, so với truyền song song thì là một hoặc nhiều byte được truyền đi cùng một lúc. Hình 1 so sánh giữa việc truyền dữ liệu nối tiếp và song song.

            Hình 1: Sơ đồ truyền dữ liệu nối tiếp so với sơ đồ truyền song song.

Thứ Tư, 11 tháng 7, 2012

Khái niệm cơ bản trong kỹ thuật Vi xử lý



Khái niệm cơ bản trong kỹ thuật Vi xử lý

I. CẤU TRÚC PHẦN CỨNG CỦA MỘT VI XỬ LÝ


Intel 4004, vi xử lý 4 bit thương mại đầu tiên năm 1971

Bộ vi xử lý Intel 80486DX2

        Những kiến thức được diễn đạt trong tài liệu này là những ý kiến mang tính chủ quan mà người viết muốn san sẻ với các bạn đọc có cùng mối quan tâm và chỉ liên quan đến những vấn đề cơ bản của kỹ thuật vi xử lý nói chung, không phải là kiến thức áp dụng cho một loại vi xử lý cụ thể.

Thứ Ba, 10 tháng 7, 2012

[Tự học Lập trình C] Bài 16: Hàm [Thực Hành]


Bài 16: Hàm [Thực Hành]

Mục tiêu:

Kết thúc bài học này, bạn có thể:

Ø  Định nghĩa và gọi hàm
Ø  Sử dụng các tham số trong hàm

Phần I – Trong thời gian 1 giờ 30 phút đầu:

16.1  Hàm

Như chúng ta đã biết, một hàm là một khối các lệnh thực hiện một tác vụ xác định. Trong bài này, chúng ta tập trung vào cách tạo và sử dụng hàm.

16.1.1  Định nghĩa hàm

Một hàm được định nghĩa với một tên hàm, theo sau bởi cặp dấu ngoặc nhọn {} bên trong chứa một hay nhiều câu lệnh.
Ví dụ:

argentina()
{
            statement 1;
            statement 2;
            statement 3;
}

16.1.2  Gọi một hàm

Một hàm có thể được gọi từ chương trình chính bằng cách đưa ra tên của hàm theo sau bởi cặp dấu ngoặc () và một dấu chấm phẩy ;.
Ví dụ:

argentina();

Bây giờ, xem chương trình hoàn thiện:

1.    Gọi trình soạn thảo chương trình C.
2.    Tạo tập tin mới.
3.    Đưa vào đoạn mã lệnh sau:

#include<stdio.h>
#include<conio.h>
void Vietnam();
void Italy();
void Brazil();
void Argentina();
main() 
{
            printf("\nI am in main");
            Vietnam();
            Italy();
            Brazil();
            Argentina();
            getch();
}
void Vietnam()
{
            printf("\nI am in Vietnam");
}
void Italy()
{
            printf("\nI am in Italy");
}
void Brazil()
{
            printf("\nI am in Brazil");         
}
void Argentina()
{
            printf("\nI am in Argentina");
}

4.    Biên dịch và thực thi chương trình.

Kết quả của chương trình:

Thứ Hai, 9 tháng 7, 2012

[Học lập trình 8051] Bài 5: Bộ đếm-Bộ định thời trong 8051


Bộ đếm/ bộ định thời trong 8051

Mục tiêu

Kết thúc bài học này, bạn sẽ nắm được:

Ø  Bộ đếm, bộ định thời là gì?
Ø  Các thanh ghi liên quan
Ø  Cách thức hoạt động của bộ đếm/bộ định thời
Ø  Các bước lập trình bộ đếm/bộ định thời

Giới thiệu

Bộ đếm/Bộ định thời: Đây là các ngoại vi được thiết kế để thực hiện một nhiệm vụ đơn giản: đếm các xung nhịp. Mỗi khi có thêm một xung nhịp tại đầu vào đếm thì giá trị của bộ đếm sẽ được tăng lên 01 đơn vị (trong chế độ đếm tiến/đếm lên) hay giảm đi 01 đơn vị (trong chế độ đếm lùi/đếm xuống).
Xung nhịp đưa vào đếm có thể là một trong hai loại:

Ø  Xung nhịp bên trong IC: Đó là xung nhịp được tạo ra nhờ kết hợp mạch dao động bên trong IC và các linh kiện phụ bên ngoài nối với IC. Trong trường hợp sử dụng xung nhịp loại này, người ta gọi là các bộ định thời (timers). Do xung nhịp bên loại này thường đều đặn nên ta có thể dùng để đếm thời gian một cách khá chính xác.
Ø  Xung nhịp bên ngoài IC: Đó là các tín hiệu logic thay đổi liên tục giữa 02 mức 0-1 và không nhất thiết phải là đều đặn. Trong trường hợp này người ta gọi là các bộ đếm (counters). Ứng dụng phổ biến của các bộ đếm là đếm các sự kiện bên ngoài như đếm các sản phầm chạy trên băng chuyền, đếm xe ra/vào kho bãi…

Một khái niệm quan trọng cần phải nói đến là sự kiện “tràn” (overflow). Nó được hiểu là sự kiện bộ đếm đếm vượt quá giá trị tối đa mà nó có thể biểu diễn và quay trở về giá trị 0. Với bộ đếm 8 bit, giá trị tối đa là 255 (tương đương với FF trong hệ Hexa) và là 65535 (FFFFH) với bộ đếm 16 bit.

            8051 có 02 bộ đếm/bộ định thời. Chúng có thể được dùng như các bộ định thời để tạo một bộ trễ thời gian hoặc như các bộ đếm để đếm các sự kiện xảy ra bên ngoài bộ VĐK. Trong bài này chúng ta sẽ tìm hiểu về cách lập trình cho chúng và sử dụng chúng như thế nào. Phần 1 là Lập trình bộ định thời, và phần 2 là Lập trình cho bộ đếm.

1. Các bộ định thời của 8051

            8051 có hai bộ định thời là Timer 0Timer 1, ở phần này chúng ta bàn về các thanh ghi của chúng và sau đó trình bày cách lập trình chúng như thế nào để tạo ra các độ trễ thời gian.

1.1 Các thanh ghi cơ sở của bộ định thời

            Cả hai bộ định thời Timer 0Timer 1 đều có độ dài 16 bit được truy cập như hai thanh ghi tách biệt byte thấpbyte cao. Chúng ta sẽ bàn riêng về từng thanh ghi.

1.1.1 Các thanh ghi của bộ Timer 0

            Thanh ghi 16 bit của bộ Timer 0 được truy cập như byte thấp và byte cao:

Ø  Thanh ghi byte thấp được gọi là TL0 (Timer0 Low byte).
Ø  Thanh ghi byte cao được gọi là TH0 (Timer0 High byte).

Các thanh ghi này có thể được truy cập, hoặc được đọc như mọi thanh ghi khác chẳng hạn như A, B, R0, R1, R2 v.v...


Hình 1: Các thanh ghi của bộ Timer 0

Chủ Nhật, 8 tháng 7, 2012

[Học lập trình 8051] Bài 4: Các chân, cổng vào/ra

Các chân, cổng vào/ra

Mục tiêu:

Kết thúc bài học này, bạn có thể:

Ø  Nắm được cấu trúc các chân của 8051
Ø  Biết rõ tác dụng của chúng, cách sử dụng

1. Mô tả các chân của 8051
           
Mặc dù các thành viên của họ 8051 (ví dụ 8751, 89C51, DS5000) đều có các kiểu đóng vỏ khác nhau, chẳng hạn như hai hàng chân DIP (Dual In-Line Pakage) dạng vỏ dẹt vuông QFP (Quad Flat Pakage) và dạng chíp không có chân đỡ LLC (Leadless Chip Carrier) thì chúng đều có 40 chân cho các chức năng khác nhau như vào/ra I/0, đọc RD, ghi WR, địa chỉ, dữ liệu và ngắt. Cần phải lưu ý rằng một số hãng cung cấp một phiên bản 8051 có 20 chân với số cổng vào-ra ít hơn cho các ứng dụng yêu cầu thấp hơn. Tuy nhiên, vì hầu hết các nhà phát triển chính sử dụng chíp đóng vỏ 40 chân với hai hàng chân DIP nên ta chỉ tập trung mô tả phiên bản này.

Hình 1: Sơ đồ bố trí chân của 8051.

Thứ Hai, 2 tháng 7, 2012

Quy ước khi viết Mã nguồn C/C++



QUY ƯỚC KHI VIẾT MÃ NGUỒN C/C++ 

      Giới thiệu
Khi viết mã nguồn, việc sửa lỗi, hay dùng lại mã nguỗn là điều rất cần thiết. Để sử dụng mã nguồn một cách hiệu quả và làm cho người đọc dễ hiểu thì việc trình bày mã nguồn là điều rất quan trọng. Nếu không có một quy tắc nào trong viết mã nguồn, thì chính người viết ra nó cũng khó hiểu được mã nguồn đó sau một thời gian dài. Do đó chúng ta cần có các Quy tắc, hay còn gọi là Phong cách viết mã nguồn. Bài viết này liệt kê một số Quy tắc cơ bản cần thiết.

[Tự học Lập trình C] Bài 15: Hàm [Lý Thuyết]


Bài 15: Hàm [Lý Thuyết]

Mục tiêu:

Kết thúc bài học này, bạn có thể:

Ø  Tìm hiểu về cách sử dụng các hàm
Ø  Tìm hiều về cấu trúc của một hàm
Ø  Khai báo hàm và các nguyên mẫu hàm
Ø  Thảo luận các kiểu khác nhau của biến
Ø  Tìm hiểu cách gọi các hàm:
·                    Gọi bằng giá trị
·                    Gọi bằng tham chiếu

Ø  Tìm hiểu về các qui tắc về phạm vi của hàm
Ø  Tìm hiểu các hàm trong các chương trình có nhiều tập tin
Ø  Tìm hiểu về các lớp lưu trữ
Ø  Tìm hiểu về con trỏ hàm.

Giới thiệu

Một hàm là một đoạn chương trình thực hiện một tác vụ được định nghĩa cụ thể. Chúng thực chất là những đoạn chương trình nhỏ giúp giải quyết một vấn đề lớn.

15.1  Sử dụng các hàm

Nói chung, các hàm được sử dụng trong C để thực thi một chuỗi các lệnh liên tiếp. Tuy nhiên, cách sử dụng các hàm thì không giống với các vòng lặp. Các vòng lặp có thể lặp lại một chuỗi các chỉ thị với các lần lặp liên tiếp nhau. Nhưng việc gọi một hàm sẽ sinh ra một chuỗi các chỉ thị được thực thi tại vị trí bất kỳ trong chương trình. Các hàm có thể được gọi nhiều lần khi có yêu cầu. Giả sử một phần của mã lệnh trong một chương trình dùng để tính tỉ lệ phần trăm cho một vài con số. Nếu sau đó, trong cùng chương trình, việc tính toán như vậy cần phải thực hiện trên những con số khác, thay vì phải viết lại các chỉ thị giống như trên, một hàm có thể được viết ra để tính tỉ lệ phần trăm của bất kỳ các con số. Sau đó chương trình có thể nhảy đến hàm đó, để thực hiện việc tính toán (trong hàm) và trở về nơi nó đã được gọi. Điều này sẽ được giải thích rõ ràng hơn khi thảo luận về cách hoạt động của các hàm.

Một điểm quan trọng khác là các hàm thì dễ viết và dễ hiểu. Các hàm đơn giản có thể được viết để thực hiện các tác vụ xác định. Việc gỡ rối chương trình cũng dễ dàng hơn khi cấu trúc chương trình  dễ đọc, nhờ vào sự đơn giản hóa hình thức của nó. Mỗi hàm có thể được kiểm tra một cách độc lập với các dữ liệu đầu vào, với dữ liệu hợp lệ cũng như không hợp lệ. Các chương trình chứa các hàm cũng dễ bảo trì hơn, bởi vì những sửa đổi, nếu yêu cầu, có thể được giới hạn trong các hàm của chương trình. Một hàm không chỉ được gọi từ các vị trí bên trong chương trình, mà các hàm còn có thể đặt vào một thư viện và được sử dụng bởi nhiều chương trình khác, vì vậy tiết kiệm được thời gian viết chương trình.

15.2  Cấu trúc hàm

Cú pháp tổng quát của một hàm trong C là:

            type_specifier function_name (arguments)
            {
                        //body of the function
                        //return statement
            }

type_specifier xác định kiểu dữ liệu của giá trị sẽ được trả về bởi hàm. Nếu không có kiểu được đưa ra, hàm cho rằng trả về một kết quả số nguyên. Các đối số được phân cách bởi dấu phẩy. Một cặp dấu ngoặc rỗng () vẫn phải xuất hiện sau tên hàm ngay cả khi nếu hàm không chứa bất kỳ đối số nào. Các tham số xuất hiện trong cặp dấu ngoặc () được gọi là tham số hình thức hoặc đối số hình thức. Phần thân của hàm có thể chứa một hoặc nhiều câu lệnh. Một hàm nên trả về một giá trị và vì vậy ít nhất một lệnh return phải có trong hàm.

15.2.1 Các đối số của một hàm

Trước khi thảo luận chi tiết về các đối số, xem ví dụ sau:

            #include <stdio.h>
            main()
            {
                        int i;
                        for(i =1; i <=10; i++)
                        printf(“\nSquare of %d is %d “, i,squarer (i));
            }
   
            squarer(int x)
            /* int x; */
            {
                        int j;
                        j = x * x;
                        return(j);
            }

Chương trình trên tính tính bình phương các số từ 1 đến 10. Điều này được thực hiện bằng việc gọi hàm squarer. Dữ liệu được truyền từ thủ tục gọi (trong trường hợp trên là hàm main()) đến hàm được gọi squarer thông qua các đối số. Trong thủ tục gọi, các đối số được biết như là các đối số thực và trong định nghĩa của hàm được gọi (squarer()) các đối số được gọi là các đối số hình thức. 
Kiểu dữ liệu của các đối số thực phải cùng kiểu với các đối số hình thức. Hơn nữa, số lượng và thứ tự của các tham số thực phải giống như của các tham số hình thức.

Khi một hàm được gọi, quyền điều khiển sẽ được chuyển đến cho nó, ở đó các đối số hình thức được thay thế bởi các đối số thực. Sau đó hàm được thực thi và khi bắt gặp câu lệnh return, nó sẽ chuyển quyền điều khiển cho chương trình gọi nó.

Hàm squarer() được gọi bằng cách truyền số cần được tính bình phương. Đối số x có thể được khai báo theo một trong các cách sau khi định nghĩa hàm:

Phương pháp 1:

squarer(int x)
//x được định nghĩa cùng với kiểu dữ liệu trong cặp dấu ngoặc ().

Phương pháp 2:

squarer(x)
int x;
//x được đặt trong cặp dấu ngoặc (), và kiểu của nó được khai báo ngay sau tên hàm.

Chú ý:
·        Trong trường hợp 1: Khi các đối số được khai báo trong cặp dấu ngoặc (), mỗi đối số phải được định nghĩa riêng lẻ, cho dù chúng có cùng kiểu dữ liệu. Ví dụ, nếu xy là hai đối số của một hàm abc(), thì abc(char x, char y) là một khai báo đúng và abc(char x, y) là sai.
·        Trong trường hợp 2: x phải được định nghĩa ngay sau tên hàm, trước khối lệnh. Điều này thật tiện lợi khi có nhiều tham số có cùng kiểu dữ liệu được truyền. Trong trường hợp như vậy, chỉ phải chỉ rõ kiểu đề một lần duy nhất tại điểm bắt đầu.

15.2.2  Sự trả về từ hàm

Lệnh return có hai mục đích:

Ø  Ngay lập tức trả điều khiển từ hàm về chương trình gọi.
Ø  Bất kỳ cái gì bên trong cặp dấu ngoặc () theo sau return được trả về như là một giá trị cho chương trình gọi.

Trong hàm squarer(), một biến j kiểu int được định nghĩa để lưu giá trị bình phương của đối số truyền vào. Giá trị của biến này được trả về cho hàm gọi thông qua lệnh return. Một hàm có thể thực hiện một tác vụ xác định và trả quyền điều khiển về cho thủ tục gọi nó mà không cần trả về bất kỳ giá trị nào. Trong trường hợp như vậy, lệnh return có thể được viết dạng return(0) hoặc return. Chú ý rằng, nếu một hàm cung cấp một giá trị trả về và nó không làm điều đó thì nó sẽ trả về giá trị không thích hợp.

Trong chương trình tính bình phương của các số, chương trình truyền dữ liệu tới hàm squarer thông qua các đối số. Có thể có các hàm được gọi mà không cần bất kỳ đối số nào. Ở đây, hàm thực hiện một chuỗi các lệnh và trả về giá trị, nếu được yêu cầu.

Chú ý rằng, hàm squarer() cũng có thể được viết như sau

squarer(int x)
{
return(x*x);
}

Ở đây một biểu thức hợp lệ được xem như một đối số trong câu lệnh return. Trong thực tế, lệnh return có thể được sử dụng theo một trong các cách sau đây:

return;
return(hằng);
return(biến);
return(biểu thức);
return(câu lệnh đánh giá); ví dụ: return(a>b?a:b);

Tuy nhiên, giới hạn của lệnh return là nó chỉ có thể trả về một giá trị duy nhất.

15.2.3  Kiểu của một hàm

type-specifier được sử dụng để xác định kiểu dữ liệu trả về của một hàm. Trong ví dụ trên, type-specifier không được viết bên cạnh hàm squarer(), vì squarer() trả về một giá trị kiểu int. type-specifier là không bắt buộc nếu một giá trị kiểu số nguyên được trả về hoặc nếu không có giá trị nào được trả về. . Tuy nhiên, tốt hơn nên chỉ ra kiểu dữ liệu trả về là int nếu một giá trị số nguyên được trả về và tương tự dùng void nếu hàm không trả về giá trị nào.

 15.3 Gọi hàm

Có thể gọi một hàm từ chương trình chính bằng cách sử dụng tên của hàm, theo sau là cặp dấu ngoặc (). Cặp dấu ngoặc là cần thiết để nói với trình biên dịch là đây là một lời gọi hàm. Khi một tên hàm được sử dụng trong chương trình gọi, tên hàm có thể là một phần của một một lệnh hoặc chính nó là một câu lệnh. Mà ta đã biết một câu lệnh luôn kết thúc với một dấu chấm phẩy (;). Tuy nhiên, khi định nghĩa hàm, không được dùng dấu chấm phầy ở cuối phần định nghĩa. Sự vắng mặt của dấu chấm phẩy nói với trình biên dịch đây là phần định nghĩa của hàm và không được gọi hàm.

Một số điểm cần nhớ:

Ø  Một dấu chấm phẩy được dùng ở cuối câu lệnh khi một hàm được gọi, nhưng nó không được dùng sau một sự định nghĩa hàm.
Ø  Cặp dấu ngoặc () là bắt buộc theo sau tên hàm, cho dù hàm có đối số hay không.
Ø  Hàm gọi đến một hàm khác được gọi là hàm gọi hay thủ tục gọi. Và hàm được gọi đến còn được gọi là hàm được gọi hay thủ tục được gọi.
Ø  Các hàm không trả về một giá trị số nguyên cần phải xác định kiểu của giá trị được trả về.
Ø  Chỉ một giá trị có thể được trả về bởi một hàm.
Ø  Một chương trình có thể có một hoặc nhiều hàm.

15.4  Khai báo hàm

Một hàm nên được khai báo trong hàm main() trước khi nó được định nghĩa hoặc sử dụng. Điều này phải được thực hiện trong trường hợp hàm được gọi trước khi nó được định nghĩa.

Xem ví dụ:

            #include <stdio.h>
            main()
            {
            address(…);
            }


            address(…)
            {
            }

Hàm main() gọi hàm address() và hàm address() được gọi trước khi nó được định nghĩa. Mặc dù, nó không được khai báo trong hàm main() thì điều này có thể thực hiện được trong một số trình biên dịch C, hàm address() được gọi mà không cần khai báo gì thêm cả. Đây là sự khai báo không tường minh của một hàm.

Trong trình biên dịch Dev-C++, ta cần phải khai báo nguyên mẫu hàm trước hàm main() nếu như muốn định nghĩa hàm đó sau hàm main(), ví dụ:

            #include <stdio.h>
address(…);
            main()
            {
            address();
            }


            address(…)
            {
            }


15.5  Các nguyên mẫu hàm

Một nguyên mẫu hàm là một khai báo hàm trong đó xác định rõ kiểu dữ liệu của các đối số và giá trị trả về. Thông thường, các hàm được khai báo bằng cách xác định kiểu của giá trị được trả về bởi hàm, và tên hàm. Tuy nhiên, chuẩn ANSI C cho phép số lượng và kiểu dữ liệu của các đối số hàm được khai báo.  Một hàm abc() có hai đối số kiểu intxy, và trả về một giá trị kiểu char, có thể được khai báo như sau:

            char abc();
hoặc
            char abc(int x, nt y);

Cách định nghĩa sau được gọi là nguyên mẫu hàm. Khi các nguyên mẫu được sử dụng, C có thể tìm và thông báo bất kỳ kiểu dữ liệu không hợp lệ khi chuyển đổi giữa các đối số được dùng để gọi một hàm với sự định nghĩa kiểu của các tham số. Một lỗi sẽ được thông báo ngay khi có sự khác nhau giữa số lượng các đối số được sử dụng để gọi hàm và số lượng các tham số khi định nghĩa hàm.

Cú pháp tổng quát của một nguyên mẫu hàm:

    type function_name(type parm_namel,type parm_name2,..type
                        parm_nameN);

Khi hàm được khai báo không có các thông tin nguyên mẫu, trình biên dịch cho rằng không có thông tin về các tham số được đưa ra. Một hàm không có đối số có thể gây ra lỗi khi khai báo không có thông tin nguyên mẫu. Để tránh điều này, khi một hàm không có tham số, nguyên mẫu của nó sử dụng void trong cặp dấu ngoặc (). Như đã nói ở trên, void cũng được sử dụng để khai báo tường minh một hàm không có giá trị trả về.

Ví dụ, nếu một hàm noparam() trả về kiểu dữ liệu char và không có các tham số được gọi, có thể được khai báo như sau:

            char noparam(void);

Khai báo trên chỉ ra rằng hàm không có tham số, và bất kỳ lời gọi có truyền tham số đến hàm đó là không đúng.

Khi một hàm không nguyên mẫu được gọi, tất cả các kiểu char được đổi thành kiểu int và tất cả kiểu float được đổi thành kiểu double. Tuy nhiên, nếu một hàm là nguyên mẫu, thì các kiểu đã đưa ra trong nguyên mẫu được giữ nguyên và không có sự tăng cấp kiểu xảy ra.

15.6  Các biến

Như đã thảo luận, các biến là những vị trí được đặt tên trong bộ nhớ, được sử dụng để chứa giá trị có thể hoặc không thể được sửa đổi bởi một chương trình hoặc một hàm. Có ba loại biến cơ bản: biến cục bộ, tham số hình thức, và biến toàn cục.

1-Biến cục bộ: là những biến được khai báo bên trong một hàm.
2-Tham số hình thức: được khai báo trong một định nghĩa hàm như là các tham số.
3-Biến toàn cục: được khai báo bên ngoài các hàm.

15.6.1  Biến cục bộ

Biến cục bộ còn được gọi là biến động, từ khoá auto được sử dụng để khai báo chúng. Chúng chỉ được tham chiếu đến bởi các lệnh bên trong của khối lệnh mà biến được khai báo. Để rõ hơn, một biến cục bộ được tạo ra trong lúc vào một khối và bị huỷ trong lúc đi ra khỏi khối đó. Khối lệnh thông thường nhất mà trong đó một biến cục bộ được khai báo chính là hàm.

Xem đoạn mã lệnh sau:

            void blkl(void)  /* void denotes no value returned*/
            {
                        char ch;
                        ch = ‘a’;
            }
            void blk2(void)
            {
                        char ch;
                        ch = ‘b’;
            }

Biến ch được khai báo hai lần, trong blk1() và blk2()ch trong blk1() không có liên quan đến ch trong blk2() bởi vì mỗi ch chỉ được biết đến trong khối lệnh mà nó được khai báo.

Vì các biến cục bộ được tạo ra và huỷ đi trong một khối mà chúng được khai báo, nên nội dung của chúng bị mất bên ngoài phạm vi của khối. Điều này có nghĩa là chúng không thể duy trì giá trị của chúng giữa các lần gọi hàm.

Từ khóa auto có thể được dùng để khai báo các biến cục bộ, nhưng thường nó không được dùng vì mặc nhiên các biến không toàn cục được xem như là biến cục bộ.

Các biến cục bộ được sử dụng bởi các hàm thường được khai báo ngay sau dấu ngoặc mở ‘{‘ của hàm và trước tất cả các câu lệnh. Tuy nhiên, các khai báo có thể ở bên trong một khối của một hàm. Ví dụ:

            void blk1(void)
            {
                        int t;
                        t = 1;
                        if(t > 5)
                        {
                                    char ch;
                        }
}

Trong ví dụ trên biến ch được tạo ra và chỉ hợp lệ bên trong khối mã lệnh if. Nó không thể được tham chiếu đến trong một phần khác của hàm blk1().

Một trong những thuận lợi của sự khai báo một biến theo cách này đó là bộ nhớ sẽ chỉ được cấp phát cho nó khi nếu điều kiện để đi vào khối lệnh if được thoả. Điều này là bởi vì các biến cục bộ chỉ được khai báo khi đi vào khối lệnh mà các biến được định nghĩa trong đó.

Chú ý: Điều quan trọng cần nhớ là tất cả các biến cục bộ phải được khai báo tại điểm bắt đầu của khối mà trong đó chúng được định nghĩa, và trước tất cả các câu lệnh thực thi.

15.6.2  Tham số hình thức

Một hàm sử dụng các đối số phải khai báo các biến để nhận các giá trị của các đối số. Các biến này được gọi là tham số hình thức của hàm và hoạt động giống như bất kỳ một biến cục bộ bên trong hàm.

Các biến này được khai báo bên trong cặp dấu ngoặc () theo sau tên hàm. Xem ví dụ sau:

            blk1(char ch, int i)
            {
                        if(i > 5)
                                    ch = ‘a’;
                        else
                                    i = i +1;
                        return;
            }

Hàm blk1() có hai tham số: ch và i.

Các tham số hình thức phải được khai báo cùng với kiểu của chúng. Như trong ví dụ trên, ch có kiều char và i có kiểu int. Các biến này có thể được sử dụng bên trong hàm như các biến cục bộ bình thường. Chúng bị huỷ đi khi ra khỏi hàm. Cần chú ý là các tham số hình thức đã khai báo có cùng kiểu dữ liệu với các đối số được sử dụng khi gọi hàm. Trong trường hợp có sai, C có thể không hiển thị lỗi nhưng có thể đưa ra một kết quả không mong muốn. Điều này là vì, C vẫn đưa ra một vài kết quả trong các tình huống khác thường. Người lập trình phải đảm bảo rằng không có các lỗi về sai kiểu.

Cũng giống như với các biến cục bộ, các phép gán cũng có thể được thực hiện với tham số hình thức của hàm và chúng cũng có thể được sử dụng bất kỳ biểu thức nào mà C cho phép.

15.6.3  Biến toàn cục

Các biến toàn cục là biến được thấy bởi toàn bộ chương trình, và có thể được sử dụng bởi một mã lệnh bất kỳ. Chúng được khai báo bên ngoài các hàm của chương trình và lưu giá trị của chúng trong suốt sự thực thi của chương trình. Các biến này có thể được khai báo bên ngoài main() hoặc khai báo bất kỳ nơi đâu trước lần sử dụng đầu tiên. Tuy nhiên, nơi tốt nhất để khai báo các biến toàn cục là tại đầu chương trình, nghĩa là trước hàm main().

            int ctr;               /* ctr is global */
            void blk1(void);
            void blk2(void);
            void main(void)
            {
                        ctr = 10;
                        blk1 ();
            }
            void blk1(void)
            {
                        int rtc;
                        if (ctr > 8)
                        {
                                    rtc = rtc + 1;
                                    blk2();
                        }
            }
            void blk2(void)
            {
                        int ctr;
                        ctr = 0;
            }

Trong đoạn mã lệnh trên, ctr là một biến toàn cục và được khai báo bên ngoài hàm main() và blk1(), nó có thể được tham chiếu đến trong các hàm. Biến ctr trong blk2(), là một biến cục bộ và không có liên quan với biến toàn cục ctr. Nếu một biến toàn cục và cục bộ có cùng tên: tất cả các tham chiếu đến tên đó bên trong khối chứa định nghĩa biến cục bộ sẽ được kết hợp với biến cục bộ mà không phải là biến toàn cục.

Các biến toàn cục được lưu trữ trong các vùng cố định của bộ nhớ. Các biến toàn cục hữu dụng khi nhiều hàm trong chương trình sử dụng cùng dữ liệu. Tuy nhiên, nên tránh sử dụng biến toàn cục nếu  không cần thiết, vì chúng giữ bộ nhớ trong suốt thời gian thực hiện chương trình. Vì vậy việc sử dụng một biến toàn cục ở nơi mà một biến cục bộ có khả năng đáp ứng cho hàm sử dụng là không hiệu quả. Ví dụ sau sẽ giúp làm rõ hơn điều này:

            void addgen(int i, int j)
            {
                        return(i + j);
            }

            int i, j;
            void addspe(void)
            {
                        return(i + j);
            }

Cả hai hàm addgen() và addspe() đều trả về tổng của các biến i và j. Tuy nhiên, hàm addgen()  được sử dụng để trả về tổng của hai số bất kỳ; trong khi hàm addspe() chỉ trả về tổng của các biến toàn cục i và j.

15.7  Lớp lưu trữ (Storage Class)

Mỗi biến trong C có một đặc trưng được gọi là lớp lưu trữ. Lớp lưu trữ xác định hai khía cạnh của biến: thời gian sống của biến và phạm vi của biến. Thời gian sống của một biến là thời gian mà giá trị của biến tồn tại. Phạm vi của một biến xác định các phần của một chương trình sẽ có thể nhận ra biến. Một biến có thể xuất hiện trong một khối, một hàm, một tập tin, một nhóm các tập tin, hoặc toàn bộ chương trình

Theo cách nhìn của trình biên dịch C, một tên biến xác định một vài vị trí vật lý bên trong máy tính, ở đó một chuỗi các bit biểu diễn giá trị được lưu trữ của biến. Có hai loại vị trí trong máy tính mà ở đó giá trị của biến có thể được lưu trữ: bộ nhớ hoặc thanh ghi CPU. Lớp lưu trữ của biến xác định vị trí biến được lưu trữ là trong bộ nhớ hay trong một thanh ghi. C có bốn lớp lưu trữ. Đó là:

Ø  auto
Ø  external
Ø  static
Ø  register

Đó là các từ khoá. Cú pháp tổng quát cho khai báo biến như sau:

            storage_specifier type var_name;

15.7.1  Biến tự động (auto)

Biến tự động thật ra là biến cục bộ mà chúng ta đã nói ở trên. Phạm vi của một biến tự động có thể nhỏ hơn hàm, nếu nó được khai báo bên trong một câu lệnh ghép: phạm vi của nó bị giới hạn trong câu lệnh ghép đó. Chúng có thể được khai báo bằng từ khóa auto, nhưng sự khai báo này là không cần thiết. Bất kỳ một biến được khai báo bên trong một hàm hoặc một khối lệnh thì mặc nhiên là thuộc lớp auto và hệ thống cung cấp vùng bộ nhớ được yêu cầu cho biến đó.

15.7.2 Biến ngoại (extern)

Trong C, một chương trình lớn có thể được chia thành các module nhỏ hơn, các module này có thể được biên dịch riêng lẻ và được liên kết lại với nhau. Điều này được thực hiện nhằm tăng tốc độ quá trình biên dịch các chương trình lớn.
Tuy nhiên, khi các module được liên kết, các tập tin phải được chương trình thông báo cho biết về các biến toàn cục được yêu cầu. Một biến toàn cục chỉ có thể được khai báo một lần. Nếu hai biến toàn cục có cùng tên được khai báo trong cùng một tập tin, một thông điệp lỗi ‘duplicate variable name’ (tên biến trùng) có thể được hiển thị hoặc đơn giản trình biên dịch C chọn một biến khác. Một lỗi tương tự xảy ra nếu tất cả các biến toàn cục được yêu cầu bởi chương trình chứa trong mỗi tập tin.
Mặc dù trình biên dịch không đưa ra bất kỳ một thông báo lỗi nào trong khi biên dịch, nhưng sự thật các bản sao của cùng một biến đang được tạo ra. Tại thời điểm liên kết các tập tin, bộ liên kết sẽ hiển thị một thông báo lỗi như sau ‘duplicate label’ (nhãn trùng nhau) vì nó không biết sử dụng biến nào.
Lớp extern được dùng trong trường hợp này. Tất cả các biến toàn cục được khai báo trong một tập tin và các biến giống nhau được khai báo là biến ngoại, trong tất cả các tập tin.

Xem đoạn mã lệnh sau:

            File1                                       File2
        
            int i,j;                                      extern int i,j;
            char a;                                    extern char a;
            main()                                    xyz()
            {                                              {
                        …                                            i = j * 5
…                                            …
            }                                              }
            abc()                                       pqr()
            {                                              {
                        i = 123;                                  j = 50;
…                                            …
            }                                              }

File2 có các biến toàn cục giống như File1, ngoại trừ một điểm là các biến này có từ khóa extern được thêm vào sự khai báo của chúng. Từ khóa này nói với trình biên dịch là tên và kiểu của biến toàn cục được sử dụng mà không cần phải tạo lại sự lưu trữ cho chúng. Khi hai module được liên kết, các tham chiếu đến các biến ngoại được giải quyết.

Nếu một biến không được khai báo trong một hàm, trình biên dịch sẽ kiểm tra nó có so khớp với bất kỳ biến toàn cục nào không. Nếu khớp với một biến toàn cục, thì trình biên dịch sẽ xem như một biến toàn cục đang được tham chiếu đến.


15.7.3  Biến tĩnh (static)

Các biến tĩnh là các biến cố định bên trong các hàm và các tập tin. Không giống như các biến toàn cục, chúng không được biết đến bên ngoài hàm hoặc tập tin của chúng, nhưng chúng giữ được giá trị của chúng giữa các lần gọi. Điều này có nghĩa là, nếu một hàm kết thúc và sau đó được gọi lại, các biến tĩnh đã định nghĩa trong hàm đó vẫn giữ được giá trị của chúng. Sự khai báo biến tĩnh được bắt đầu với từ khóa static.

Có thể định nghĩa các biến tĩnh có cùng tên như hướng dẫn với các biến ngoại. Các biến cục bộ (biến tĩnh cũng như biến động) có độ ưu tiên cao hơn các biến ngoại và giá trị của các biến ngoại sẽ không ảnh hưởng bởi bất kỳ sự thay đổi nào các biến cục bộ. Các biến ngoại có cùng tên với các biến nội trong một hàm không thể được truy xuất trực tiếp bên trong hàm đó.

Các giá trị khởi tạo có thể được gán cho các biến trong sự khai báo các biến tĩnh, nhưng các giá trị này phải là các hằng hoặc các biểu thức. Trình biên dịch tự động gán một giá trị mặc nhiên 0 đến các biến tĩnh không được khởi tạo. Sự khởi tạo thực hiện ở đầu chương trình.

Xem hai chương trình sau. Sự khác nhau giữa 2 biến cục bộtự độngtĩnh sẽ được làm rõ.

Ví dụ về biến tự động:

            #include <stdio.h>
            #include <conio.h>
            void incre();
            main()
            {
                        incre();
                        incre();
                        incre();
                        getch();
            }

            void incre()
            {
                        char var = 65;      /* var is automatic      variable*/
                        printf("\nThe character stored in var is %c", var++);
            }
    
Kết quả của chương trình trên sẽ là: