I. Giới thiệu
Bài viết này sẽ nói về cách giao tiếp giữa AVR và máy tính cá nhân (PC) theo một cách đơn giản nhưng khá toàn diện. Nó đơn giản vì tôi sẽ dùng một giao diện khá “cổ điển” để giao tiếp giữa AVR và PC, giao diện RS232 thông qua các cổng COM. Toàn diện vì tôi sẽ hướng dẫn các bạn từ cách mắc mạch chuyển giữa AVR và PC, cách viết chương trình giao tiếp theo chuẩn RS232 trên máy tính và trên AVR. Cụ thể bài này bao gồm:
- Sơ lượt sơ đồ và chức năng các chân cổng COM trên máy tính.
- Mạch chuyển kết nối AVR với PC qua cổng COM.
- Tạo cổng COM ảo trên PC cho mục đích mô phỏng.
- Sử dụng các hàm trong thư viện xuất nhập chuẩn của C như printf, scanf…trong WinAVR.
- Viết chương trình giao tiếp RS232 cho AVR.
- Sử dụng Hyper Terminal của Windows trong giao tiếp RS232.
- Viết chương trình truy xuất cổng COM trên PC (Visual C++, Visual Basic)
Cổng COM hay cổng nối tiếp (COM Port, Serial Port) là cổng giao tiếp thuộc vào dạng “lão làng” trên PC, cả máy tính để bàn và Laptop. Ngày nay với sự xuất hiện và “bành trướng” của chuẩn USB thì cổng COM (và cả cổng LPT hay cổng song song) đang dần biến mất. Giao tiếp thông qua cổng COM là giao tiếp theo chuẩn nối tiếp RS232. Chuẩn này có tốc độ khá chậm nếu đem so sánh với USB. Tuy nhiên, với dân robotics hay control thì COM-RS232 lại rất được ưa chuộng vì tính đơn giản và cũng vì…sự chậm chạp này. Các cổng COM trên các máy tính hiện tại (nếu có) đa số là dạng cổng “đực” 9 chân (male 9 pins). Tuy nhiên, đâu đó vẫn còn tồn tại loại cổng COM 25 chân, loại này về hình dáng khá giống cổng LPT nhưng là loại male trong khi cổng LPT là female. Hình 1 thể hiện 2 dạng của cổng COM và bảng 1 tóm tắt chức năng các chân của cổng này.
Hình 1. Cổng COM 9 chân và 25 chân.
Bảng 1: Các chân trên cổng COM
Đáng chú ý nhất trong các chân của cổng COM là 3 chân 0V SG (signal ground), chân phát dữ liệu TxD và chân nhận dữ liệu RxD. Đây là 3 chân cơ bản phục vụ truyền thông theo chuẩn RS232 và tương thích với UART trên AVR. Các chân còn lại cũng có thể được sử dụng nếu người dùng có 1 ít kiến thức về tổ chức thanh ghi của PC. Tuy nhiên, trong đa số trường hợp giao tiếp qua cổng COM thì chỉ 3 chân trên được sử dụng.
Như đã trình bày trong bài AVR5-UART, chuẩn RS232 và UART nhìn chung là như nhau về mặt khung truyền, tốc độ baud…nhưng khác nhau về mức điện áp và cực. Xem lại ví dụ so sánh trong hình 2.
Hình 2. So sánh UART và RS232.
Trong chuẩn UART (trên AVR), mức 1 tương ứng điện áp cao (5V, TTL) trong khi đối với RS232 thì mức 1 tương ứng với điện áp thấp (điện áp âm, có thể -12V). Như thế rõ ràng cần một “cầu chuyển” (converter) kết nối giữa 2 chuẩn này. May mắn là chúng ta không cần phải tự thiết kế cầu chuyển này vì đã có các IC chuyên dụng. MAX232 là một trong các IC chuyển UART-RS232 được sử dụng nhiều nhất. Tất nhiên, bạn hoàn toàn có thể tự tạo một mạch chuyển đơn giản chỉ với một vài linh kiện như tụ điện, điện trở, diode và transisotor nhưng tính ổn định thì tôi không đảm bảo. Hình 3 mô tả cách dùng IC Max232 để kết nối giữa UART trên AVR và cổng COM của PC.
Hình 3. Kết nối AVR với PC thông qua Max232.
Mạch điện trên chỉ có tác dụng thay đổi mức điện áp cho phù hợp giữa RS232 và UART, nó hoàn toàn không làm thay đổi phương thức giao tiếp của các chuẩn này và vì thế việc lập trình trên PC và AVR đều không có gì thay đổi. Thật ra Max232 có đến 2 cầu chuyển, trong hình 3 chúng ta chỉ sử dụng cầu chuyển 1. Chân phát TxD (chân 3) của cổng COM được nối với chân R1IN (Receive 1 Input) của Max232 thì tương ứng chân R1OUT (Receive 1 Output) phải nối với chân nhận RX của AVR. Tương tự cho trường hợp T1IN và T1OUT. Giá trị các tụ điện 10uF là tương đối chuẩn, tuy nhiên khi bạn thay bằng tụ 1uF thì mạch vẫn hoạt động nhưng khoảng cách truyền (cab nối) sẽ ngắn hơn (nếu dài quá sẽ phát sinh lỗi truyền thông). Các điện trở trong hình 3 chỉ có tác dụng làm bảo vệ cổng COM và các IC, bạn có thể không cần dùng các điện trở này vẫn không ảnh hưởng hoạt động của mạch. VCC và GND là nguồn của mạch AVR.
Chú ý: nếu muốn thực hiện giao tiếp giữa 2 máy tính với nhau thông qua cổng COM, bạn cần dùng1 cab chéo (chân TxD của PC1 nối với RxD của PC2 và ngược lại) để nối 2 cổng COM lại với nhau.
III. Tạo cổng COM ảo cho mô phỏng
Muốn thực hiện giao tiếp giữa AVR và PC thông qua cổng COM thì hiển nhiên bạn cần có cái cổng COM, ngoài ra bạn cần tự làm một mạch AVR và cầu chuyển Max232. Thật không may là không phải máy tính nào cũng có cổng này, nếu bạn chỉ muốn học cách giao tiếp AVR-PC hoặc chỉ muốn kiểm tra một giải thuật nào đó thì có lẽ mô phỏng là giải pháp được ưa thích hơn. Cho mục đích mô phỏng giao tiếp RS232, Proteus lại một lần nữa hữu ích khi cho phép mô phỏng truyền nhận dữ liệu với cổng COM. Như thế vấn đề còn lại là làm sao tạo các cổng COM ảo trên máy tính và kết nối chúng với nhau để thực hiện mô phỏng giao tiếp. Do tính chất của các cổng COM là chỉ được “mở” (open) 1 lần duy nhất, nghĩa là 2 phần mềm không thể cùng mở 1 cổng. Ý tưởng của chúng ta là tạo ra 2 cổng COM ảo được “nối chéo” sẵn với nhau (ví dụ COM2 và COM3). Trong phần mềm Proteus ngõ ra của UART được nối với COM2. Trong phần mềm trên PC (ví dụ Hyper Terminal) chúng ta kết nối với COM3. Bằng cách này chúng ta đã có thể thực hiện giao tiếp giữa AVR (mô hình Proteus) với PC (phần mềm Hyper Terminal).
Có một vài phần mềm tốt có khả năng tạo cổng COM ảo và kết nối ảo giữa chúng đúng như yêu cầu của chúng ta. Trong phần này tôi sẽ giới thiệu 2 phần mềm như thế, trong đó có 1 phần mềm miễn phí (Virtual Serial Port Emulator) và 1 phần mềm thu phí (Eltima Virtual Serial Port Driver).
Virtual Serial Port Emulator (VSPE): là một phần mềm tạo cổng COM và kết nối ảo tốt của Eterlogic. Điều đặc biệt là phiên bản dành cho Windows 32 bits hoàn toàn miễn phí, vì vậy đây là phần mêm đầu tiên bạn phải để ý khi muốn tạo dùng cho mục đích học tập.
Trước tiên bạn hãy download phần mềm VSPE bản mới nhất tại website chính thức của Eterlogic:http://www.eterlogic.com/Products.VSPE.html (nhấn vào nút Download ở phía cuối trang web). Giải nén file zip và chạy file SetupVSPE.exe để cài đặt. Sau khi cài đặt hãy tìm và chạy chương trình VSPE. Giao diện của VSPE như trong hình 4.
Hình 4. Giao diện phần mềm VSPE.
Sử dụng VSPE khá đơn giản, bạn chỉ việc nhấn vào nút “Create New Device” được tô đỏ trong hình 4, hoặc vào menu “Device” và chọn “Create”, để tạo 1 cổng COM ảo. Trong hộp thoại “Specify device type” bạn chọn như bên dưới và nhấn Next. Sau đó bạn có thể chọn tên cho cổng COM mình muốn tạo trong (ví dụ COM2).
Hình 5. Tạo cổng COM2 ảo bằng VSPE.
Bạn có thể tiến hành tạo bao nhiêu cổng COM ảo tùy thích. Ví dụ bạn tạo 2 cổng COM2 và COM3, bước tiếp theo chúng ta sẽ “đấu chéo” 2 cổng này với nhau để mô phỏng việc truyền dữ liệu qua RS232. Từ giao diện chính của VSPE bạn nhấn tiếp vào nút “Create new Device…”. Lần này, trong hộp thoại “Specify device type” bạn không chọn Connector nữa mà chọn “Serial Redirector” như trong hình 6. Sau đó nhấn next, chọn 2 cổng COM ảo đã tạo lúc trước và nhấn vào nút Finish.
Hình 6. Tạo kết nối giữa 2 cổng COM.
Sau khi hoàn tất bạn sẽ thấy các cổng COM ảo và kết nối giữa chúng được thể hiện trong giao diện VSPE như ở hình 7. Bạn có thể “minimize” giao diện VSPE để ẩn nó vào taskbar. Chú ý là nếu bạn đóng chương trình VSPE lại (tắt) thì các cổng COM ảo cũng biến mất.
Hình 7. Các cổng COM ảo và kết nối tạo bằng VSPE.
Virtual Serial Port Driver (VSPD): là một phần mềm tạo cổng COM và kết nối ảo tốt của Eltima Software. Đây là phần mềm có thu phí, bạn có thể download bản dùng thử 14 ngày tại website chính thức của Eltima Software:http://www.eltima.com/products/vspdxp/
So với VSPE thì VSPD dễ sử dụng và ổn định hơn (vì là phần mềm thương mại). Sau khi download bản trial và tiến hành cài đặt, bạn hãy tìm và chạy file “Configure Virtual Serial Port Driver”. Giao diện của VSPD như trong hình 8.
Hình 8. Giao diện phần mềm VSPD.
Trong tab “Manager ports” phần mềm tự động đề nghị 1 cặp cổng COM ảo có thể được tạo ra, bạn có thể chọn lại tùy thích và nhấn “Add pair” để tạo 2 cổng COM này. Khác với VSPE, cổng COM ảo do VSPD tạo ra sẽ xuất hiện trong “Device list” của Windows và không bị mất đi khi người dùng tắt phần mềm VSPD. Hãy chạy trình “Device manager” của Windows, trong mục Ports (COM & LPT) bạn sẽ thấy các cổng COM ảo được tạo thành (xem ví dụ trong hình 9).
Hình 9. Các cổng COM ảo và kết nối giữa chúng được tạo bởi VSPD.
IV. Sử dụng thư viện xuất nhập chuẩn stdio.h trong WinAVR
Những ai đã từng học ngôn ngữ lập trình C chắc sẽ không quên chương trình “hello world” đầu tiên của mình:
Chương trình này chỉ làm 1 việc đơn giản là in dòng chữ “hello, world” lên màn hình. Việc in dòng chữ được thực hiện bởi lệnh “printf” trong dòng 3. Lệnh printf nằm trong thư viện stdio gọi là thư viện xuất nhập chuẩn (standard input/output). Lệnh printf trong stdio không chỉ được dùng để in lên màn hình mà có thể in lên bất kỳ thiết bị xuất nào (output device), ngay cả in ra 1 file trên ổ cứng máy tính…Cho AVR, nếu bạn sử dụng trình dịch CodevisionAVR của HPinfotech, khi bạn gọi lệnh printf thì chuỗi dữ liệu sẽ được in ra (xuất ra) module UART (tất nhiên bạn phải cài đặt các thanh ghi của UART để kích hoạt UART trước). Như thế CodevisionAVR tự hiểu UART là thiết bị xuất/nhập mặc định cho các lệnh trong thư viện stdio (printf, scanf…). Tuy nhiên, với WinAVR (avr-gcc) mọi chuyện lại khác, để sử dụng các lệnh xuất nhập chuẩn chúng ta cần khai báo một thiết bị xuất nhập và hàm xuất nhập “cơ bản”. Hàm xuất nhập cơ bản là hàm do người dùng định nghĩa, nhiệm vụ của nó là xuất (hoặc nhập) một ký tự ra một thiết bị xuất nhập nào đó. Ví dụ trong bài AVR5 - giao tiếp UART chúng ta định nghĩa một hàm “uart_char_tx” xuất ký tự ra UART như sau:
Hoặc trong bài TextLCD chúng ta khảo sát hàm “putChar_LCD” xuất một ký tự ra LCD như bên dưới:
Cả 2 hàm “uart_char_tx” và “putChar_LCD” như ví dụ trên đều có thể được dùng làm hàm xuất nhập “cơ bản” cho các hàm như printf...trong thư viện xuất nhập chuẩn sdtio. Nếu giả sử hàm “uart_char_tx” được dùng thì khi gọi hàm hàm printf, chuỗi dữ liệu sẽ được xuất ra UART. Ngược lại, trường hợp hàm “putChar_LCD” được sử dụng như hàm cơ bản thì hàm printf của stdio sẽ xuất chuỗi dữ liệu lên LCD. Bằng phương thức này, trình dịch avr-gcc cho phép chúng ta tiếp cận thư viện stdio một cách mềm dẻo hơn, bạn có thể dùng các hàm của stdio để xuất/nhập dữ liệu vào bất kỳ thiết bị nào như UART terminal, TextLCD, Graphic LCD hay thậm chí SD, MMC card…một khi bạn định nghĩa được hàm xuất nhập “cơ bản”.
Để minh họa cho cách sử dụng các hàm trong thư viện stdio, tôi sẽ trình bày một ví dụ xuất dữ liệu ra TextLCD và uart bằng các hàm printf…của stdio. Mạch điện mô phỏng cho ví dụ được này thể hiện trong hình 10 bên dưới.
Hình 10. Mô phỏng ví dụ xuất dữ liệu với thư viện stdio.
Tất cả các dữ liệu hiển thị trên LCD và uart terminal trong hình 10 đều được thực hiện thông qua các hàm printf và fprintf. Ngoài ra trong ví dụ này, người dùng có thể nhập 1 ký tự từ bàn phím và mã ASCII của phím đó sẽ được in ra trên Terminal. Đoạn code trình bày trong List1.
List 1. Xuất dữ liệu ra LCD và UART bằng thư viện xuất nhập chuẩn stdio
Để sử dụng các hàm trong thư viện xuất nhập chuẩn, chúng ta cần include file header của thư viện như trong dòng code 4 “#include <stdio.h>”. Chú ý khi sử dụng avr-gcc, các hàm liên quan đến avr (avr-libc) nằm trong thư mục con “/avr/” của thư mục include nên khi kính kèm phải chỉ rõ thư mục con này. Ví dụ header io.h hoặc interrupt.h chứa các hàm chuyên biệt cho avr, khi đính kèm các file này chúng ta ghi cụ thể như: “#include <avr/io.h>”…Tuy nhiên, các file header của ngôn ngữ C chuẩn (như stdio.h, math.h, …) thì nằm trực tiếp ở thư mục include, khi đính kèm các file này phải ghi trực tiếp như trong dòng code 4. Ngoài ra do ví dụ này có sử dụng LCD, bạn cần copy và include thư viện myLCD.h như trong dòng 5 (xem lại bài TextLCD).
Như đã trình bày ở trên, để sử dụng các hàm trong stdio chúng ta cần có các hàm xuất/nhập cơ bản. Các dòng code từ 7 đến 11 là hàm xuất dữ liệu ra uart có tên “uart_char_tx”, hàm này sẽ được dùng làm hàm cơ bản cho các hàm xuất của stdio sau này. Thực chất hàm “uart_char_tx” đã được trình bày trong bài học về UART, ở đây có một thay đổi nhỏ là dòng code 8 “if (chr==’\n’) uart_char_tx(‘\r’)”, dòng này có nghĩa là khi gặp người dùng muốn xuất ra “ký tự” ‘\n’ thì hàm “uart_char_tx” sẽ xuất ra thêm ký tự ‘\r’ . Như thế, nếu sau này bắt gặp một dấu hiệu xuống dòng ‘\n’ (có mã ASCII là 10, gọi là Line Feed – LF) ở cuối câu thì một tổ hợp mã '\r'+'\n' (mã '\r' = 13 gọi là Carriage Return – CR) sẽ được gởi để thực hiện xuống dòng. Để nắm rõ hơn vấn đề này bạn tìm hiểu thêm về CRLF (Carriage Return Line Feed) trong Windows.
Hai dòng code 13 và 14 rất quan trọng khi muốn sử dụng thư viện stdio. Ý nghĩa của 2 dòng này là tạo 2 “FILE” ảo (hay còn gọi là stream) dành cho việc xuất dữ liệu. Chúng ta khảo sát dòng 14: tạo stream cho UART.
static FILE uartstd= FDEV_SETUP_STREAM(uart_char_tx, NULL,_FDEV_SETUP_WRITE);
Chúng ta tạo 1 biến tên uartstd (người dùng tự đặt tên tùy ý) có kiểu là FILE (một dạng thiết bị ảo), sau đó dùng macro “FDEV_SETUP_STREAM” để khởi tạo và cài đặt các thông số cho uartstd. Macro này có chức năng mở 1 thiết bị xuất nhập (fdevopen) và gán các “công cụ” cho việc xuất nhập ra thiết bị.
#define FDEV_SETUP_STREAM(put, get, rwflag)
Các thông số kèm theo “FDEV_SETUP_STREAM” bao gồm 1 hàm cơ bản gọi là “put”, một hàm cơ bản gọi là “get” và một cờ chỉ chức năng xuất hoặc nhập của thiết bị được mở. Cụ thể, trong dòng code 13, biến uartstd là một “thiết bị ảo” được dùng cho việc xuất dữ liệu (do thông số _FDEV_SETUP_WRITE). Công cụ để xuất ra uartstd là hàm “uart_char_tx” mà chúng ta đã tạo phía trên. Không có hàm nhận dữ liệu về từ uartstd (thông số get = NULL). Bạn có thể hình dung thế này: biến uartstd là một tờ giấy, hàm “uart_char_tx” là một “con dấu” (stamp) cho phép in một ký tự lên tờ giấy uartstd. Chúng ta gán “uart_char_tx” cho usrtstd thì sau này tất cả việc in ấn lên tờ giấy uartstd sẽ do “con dấu” “uart_char_tx” thực hiện. Hàm “uart_char_tx” vì thế gọi là hàm xuất cơ bản.
Tương tự như thế, trong dòng code 13 chúng ta tạo 1 “tờ giấy” khác tên lcdstd và hàm cơ bản cho nó lá hàm “putChar_LCD”, hàm này đã được định nghĩa sẵn trong thư viện myLCD.h.
Các dòng code trong chương trình chính từ dòng 17 đến 25 dùng khởi động UART và TextLCD, bạn có thể xem lại các bài liên quan để hiểu thêm. Sau khi khởi động, UART và LCD đã sẵn sàng cho việc xuất dữ liệu. Bây giờ chúng ta có thể dùng các hàm trong thư viện stdio như printf hay sprint…để xuất dữ liệu. Bạn hay quan sát hình 10 vì tôi sẽ dùng nó để so sánh đối chiếu với các dòng code sau. Dòng 27 “printf("In lan 1")”, mục đích là in chuỗi “In lan 1” lên LCD bằng hàm printf. Tuy nhiên, xem trên hình 10 bạn không nhìn thấy chuỗi này xuất hiện. Xem tiếp dòng code 28 “fprintf(&lcdstd," www.hocavr.com ")” và xem lại hình 10, lần này bạn đã thấy chuỗi ký tự “www.hocavr.com” xuất hiện trên LCD, nghĩa là việc in đã thành công với hàm fprintf. Hàm fprintf là hàm xuất dữ liệu ra một thiết bị ảo, trong đó tham số thứ nhất của hàm trỏ đến thiết bị và tham số thứ hai là chuỗi dữ liệu cần in. Trong trường hợp này chúng ta đã dùng fprintf để xuất chuỗi “www.hocavr.com” ra thiết bị ảo lcdstd và đã thành công. Vậy với hàm printf ở dòng 27 thì sao? Hãy khảo sát tiếp các dòng từ 30 đến 32. Dòng 30 chúng ta lại một lần nữa dùng hàm printf “printf("In lan 3")” để in dòng “In lan 3” lên LCD nhưng vẫn không thành công (xem LCD trong hình 10). Ở dòng 31 chúng ta gán “stdout=&lcdstd” trong đó stdout là một biến (thật ra là 1 stream hay một thiết bị ảo) có sẵn của ngôn ngữ C, biến này qui định thiết bị mặc định dùng cho việc xuất nhập dữ liệu, khi gán stdout trỏ đến lcdstd như dòng 31 nghĩa là chúng ta khai báo LCD là thiết bị xuất nhập mặc định. Vì vậy, trong dòng 32 chúng ta gọi hàm printf “printf("In lan 4: %i", x)” chúng ta đã thành công. Lần này, quan sát trên LCD bạn sẽ thấy dòng “In lan 4: 8205” xuất hiện. Ở đây 8205 là giá trị của biến x trong câu lệnh ở dòng 32. Tóm lại, hàm fprintf cho phép in trực tiếp ra một thiết bị ảo được chỉ định trong khi đó muốn dùng hàm printf chúng ta cần gán thiết bị xuất nhập mặc định trước cho biến stdout. Hãy quan sát đoạn code từ dòng 34 đến 37 và ba dòng đầu trong Terminal ở hình 10, chắc chắn bạn đã tự lý giải được các dòng code này.
Cuối cùng là trình phục vụ ngắt nhận dữ liệu của UART trong các dòng code từ 41 đến 44. Trong trình này, chúng ta chỉ thực hiện việc đơn giản là in dòng “Ma ASCII:” kèm theo đó là giá trị nhận về từ UART chứa trong thanh ghi UDR: “fprintf(&uartstd,"Ma ASCII: %i\n", UDR)”.
Để tìm hiểu đầy đủ về thư viện stdio trong WinAVR bạn cần đọc tài liệu “avr-libc Manual”, phần Standard IO facilities.
IV. RS232 Terminal
RS232 Terminal là thuật ngữ dùng chỉ các phần mềm máy tính có khả năng nhận và phát dữ liệu ra cổng COM (như một thiết bị đầu cuối). Các RS232 Terminal rất hữa dụng để kiểm tra các chương trình truyền nhận dữ liệu qua cổng COM. Hệ điều hành Windows có sẵn một RS232 Terminal gọi là “Hyper Terminal”. Công cụ này khá tốt cho mục đích giao tiếp thông thường. Để sử dụng Hyper Terminal bạn hãy vào “All Programs/ Accessories/Communications/Hyper Terminal” hoặc đơn giản là vào Run và gõ lệnh “hypertrm”. Một hộp thoại có tên “Connection Description” xuất hiện, hãy điền một tên bất kỳ cho cuộc gọi và nhấn OK. Trong hộp thoại tiếp theo, Connect to, hãy chọn cổng COM mà bạn muốn giao tiếp, và nhấn OK. Cuối cùng là hộp thoại COM Properties cho phép bạn thiết lập các thông số giao tiếp như Baudrate, Parity bit, Stop bit như trong hình 11, chú ý hãy chọn Flow control là "none"…và nhấn OK.
Hình 11. Thiết lập cuộc gọi.
Giả sử bạn chạy chương trình ví dụ trong phần demo của stdio, bạn thu được giao diện HyperTerminal như trong hình 12.
Hình 12. Giao diện Hyper Terminal.
Trong bài học này, tôi giới thiệu một chương trình Terminal có tên Hercules của HW group (http://www.hw-group.com/products/hercules/index_en.html). Đây là một Terminal miễn phí rất tốt, dễ sử dụng và ổn định. Ngoài chức năng RS232 Terminal, Hercules còn được dùng cho các giao diện khác như TCP, UDP…Bạn chỉ cần download chương trình về và chạy file Hercules.exe. Bạn thu được giao diện Hercules như sau:
Hình 13. Giao diện phần mềm Hercules.
Hãy chọn tab Serial để giao tiếp với cổng COM, thiết lập các thông số như tên cổng, Baudrate, Data frame…rồi nhấn nút Open, bạn đã sẵn sàng để sử dụng Hercules.
Giả sử bạn có 3 cổng COM ảo tên là COM2, COM3 nối với nhau. Hãy sử dụng ví dụ trong phần stdio, trong mạch điện mô phỏng Proteus của ví dụ hãy xóa thiết bị ảo Terminal. Hãy thêm vào một thiết bị tên là COMPIM bằng cách search với keyword là COMPIM (hoặc chạy file AVR_STD_Terminal.DSN trong thư mục AVR_STD của ví dụ trên). Kết nối như trong hình 14. Sau đó right click vào COMPIM để vào hộp thoại “Edit component”, đổi thông số Physical port thành CÒM, đổi Virtual Baud Rate thành 38400. Chạy lại mô phỏng bạn sẽ thấy kết quả hiển thị trên Hercules như trong hình 14. Type 1 phím bất kỳ để thấy mã ASCII.
Đây là ví dụ cho phép bạn giao tiếp giữa chương trình AVR mô phỏng trong Proteus và ứng dụng chạy trên Windows thông qua các cổng COM ảo. Nó thực chất là một dạng giao tiếp máy tính bằng cổng COM, dành cho trường hợp bạn chưa có mạch AVR thật. Mấu chốt nằm ở thiết bị COMPIM trong Proteus. COMPIM thực chất là mô hình cổng COM tồn tại trên máy tính của bạn. Trong trường hợp này chúng ta dùng Eltima VSPE (hoặc VSPD) để tạo 2 cổng COM ảo trên máy tính là COM2 và COM3, chúng được đấu chéo với nhau. Chúng ta set COMPIM trong Proteus là COM2 trong khi cổng trên Hercules là COM3. Khi chạy mô phỏng, AVR sẽ gởi dữ liệu ra COMPIM (tức COM2), COM2 truyền đến COM3 và hiển thị trên Hercules. Chúng ta có thể tự viết các chương trình trên Windows để nhận và gởi giá trị qua COM thay cho Hercules. Trong phần tiếp theo tôi sẽ hướng dẫn bạn tạo chương trình như thế.
Hình 14. Kết hợp mô phỏng và Hercules.
V. Lập trình giao tiếp với cổng COM bằng Visual Basic và Visual C++
Các chương trình Terminal đề cập ở trên là một dạng ứng dụng giao tiếp giữa máy tính và vi điều khiển ở mức độ đơn giản. Trong nhiều trường hợp, yêu cầu giao tiếp đòi hỏi mức độ phức tạp cao hơn, ví dụ lưu trữ dữ liệu hay vẽ đồ thị biến thiên, thì người dùng cần phải tự viết các chương trình trên máy tính của riêng mình. Phần này tôi hướng dẫn bạn các viết chương trình trên máy tính để truyền và nhận dữ liệu từ cổng COM bằng 2 ngôn ngữ lập trình Visual Basic và Visual C++ (6.0) trên nền Windows. Chú ý, mục đích bài viết này là về AVR nên phần viết ứng dụng trên Windows tôi chỉ trình bày một cách đơn giản cốt cho bạn nắm được nguyên lý. Để phát triển các ứng dụng phức tạp hơn người đọc cần tự trang bị cho mình kiến thức về lập trình trên Windows. Trong tất cả các hướng dẫn bên dưới tôi giả sử là người đọc ít nhất biết được cách tạo Project trong Visual Basic hoặc/và Visual C++.
1. Viết chương trình giao tiếp cổng COM bằng Visual Basic 6.0
Kể từ các phiên bản Windows 2000 về sau, việc giao tiếp với các cổng máy tính truyền thống, như cổng LPT, trong Windows tương đối khó khăn. Tuy nhiên, với cổng COM thì có điều may mắn là Microsoft có cung cấp một công cụ (thật ra là một control – điều khiển) có tên gọi là “Microsoft Communication Control” hay viết tắt là MSComm. MSComm xuất hiện trong các phần mềm lập trình nổi tiếng của MS như Visual Basic hay Visual C++ dưới dạng một “điều khiển”. Vì là một “điều khiển” được thiết kế sẵn cho cổng COM nên MSComm chứa tất cả các công cụ cần thiết để giao tiếp với cổng này, công việc của người viết chương trình chỉ đơn giản là khai báo và sử dụng. Để minh họa cách sử dụng MSComm trong Visual Basic, hãy làm theo hướng dẫn bên dưới.
Chạy Visual Basic 6, vào menu “File/New Project” và tạo một “Standard EXE”. Bạn sẽ thấy một Project có tên là “Project1” kèm một hộp thoại nền (form chính) có tên Form1 xuất hiện. Bạn có thể đặt tên bất kỳ cho Project và form chính. Hãy quan sát ví dụ trong hình 15. Từ thanh công cụ Toolbox hãy click vào control “textbox” và vẽ lên “form” chính 2 textbox tên là txtOuput và txtInput (xem hình 15) (đổi tên các textbox trong cửa sổ Properties nằm ở gốc thấp, bên phải). với txtOutput, hãy set thông số Multiple thành True và ScrollBars thành “3 – Both”
Hình 15. Giao diện Visual Basic 6.
Tiếp theo hãy đưa control MSComm vào form chính. Theo mặc định, control MSComm không có sẵn trong Toolbox của Visual Basic, chúng ta cần thêm vào Toolbox trước khi sử dụng. Để thêm MSComm vào Toolbox, chọn Menu “Project/Components” bạn sẽ thấy một hộp thoại tên Components xuất hiện như trong hình 16. Tìm và click chọn vào ô “Microsoft Comm Control 6.0” như trong hình và nhấn OK. Lúc này, quan trong Toolbox của VB bạn sẽ thấy icon của MSComm xuất hiện. Click vào icon này và vẽ 1 đối tượng MSComm lên form chính (xem lại hình 15). Giữ tên mặc định của đối tượng này là MSComm1.
Hình 16. Thêm công cụ MSComm vào Project.
Viết code:
Mục đích của ví dụ này như sau: dữ liệu nhận về từ cổng COM sẽ hiển thị trên textbox txtOutput, và khi người dùng type 1 ký tự vào txtInput ký tự sẽ được truyền đi qua cổng COM.
Trước hết, hãy doubleclick vào form chính, viết đoạn code sau vào sự kiện Form_Load():
Mục đích của đoạn code này là cài đặt các thông số cho MSComm1.
- Thông số CommPort = 3 nghĩa là chúng ta muốn kết nối với cổng COM3. Thông số này do người dùng thay đổi tùy theo cổng COM chúng ta muốn giao tiếp.
- Thông số Setting = “38400, N, 8,1” nghĩa là tốc độ Baud=38400, không sử dụng bit Parity, độ dài khung truyền bằng 8 và có 1 bit Stop.
- RThreshold = 1 nghĩa là khi có 1 ký tự đến cổng COM, ngắt nhận dữ liệu sẽ xảy ra.
- InputLen = 1 nghĩa là khi đọc dữ liệu từ bộ đệm nhận, chúng ta sẽ đọc lần lượt 1 ký tự (1 byte).
- PortOpen = True tức cho phép “mở” cổng COM để sẵn sàng giao tiếp.
Tiếp theo, doubleclick vào biểu tượng của MSComm1 trên form chính để viết code vào sự kiệnMSComm1_onComm():
Sự kiện onComm() thực chất là trình phục vụ ngắt nhận dữ liệu của MSComm. Khi có 1 byte dữ liệu gởi đến bộ đệm của cổng COM (số lượng byte do RThreshold quy định) thì sự kiện onComm sẽ xảy ra (ngắt xảy ra), trong sự kiện này chúng ta sẽ viết code để nhận và xử lý dữ liệu. Dòng 2 chúng ta khai báo 1 biến tạm thời tên là InputText với kiểu dữ liệu string. Chú ý là sự kiện onComm có thể xảy ra do nhiều nguyên nhân, ở đây chúng ta chỉ quan tâm đến trường hợp dữ liệu truyền đến, dòng 3 là một dạng “lọc” sự kiện, chúng ta chỉ thực hiện các dòng code bên trong khi mà sự kiện comEvReceive xảy ra (dữ liệu được nhận về): If Me.MSComm1.CommEvent = comEvReceive Then. Việc quan trọng duy nhất để đọc dữ liệu được gởi đến COM là đọc bộ đệm Input của MSComm như trong dòng code 4: InputText = MSComm1.Input. Sau dòng lệnh này dữ liệu sẽ được chứa trong biến tạm InputText. Tiếp theo chúng ta chỉ cần cộng dồn các ký tự nhận về vào nội dung của Textbox txtOutput để hiển thị lên màn hình (dòng 5) : txtOutput.Text = txtOutput.Text + InputText. Dòng code 6 làm nhiệm vụ đưa con trỏ đến cuối nội dung của txtOutput để tiện cho việc quan sát dữ liệu.
Cuối cùng, doubleclick vào Textbox txtInput và tìm sự kiện KeyPress để viết các dòng code sau:
Sự kiện txtInput_KeyPress xảy ra khi người dùng nhấn 1 phím nào đó vào txtInput. Dòng codeMe.MSComm1.Output = Chr(KeyAscii) thực hiện việc gởi giá trị của KeyAscii ra cổng COM, trong đó KeyAscii là mã Ascii của phím được nhấn.
Bạn đã hoàn tất viết chương trình truyền nhận dữ liệu qua cổng COM bằng Visual Basic. Để kiểm tra chương trình của bạn, hãy thực hiện mô phỏng theo các bước sau:
- Dùng 1 trong 2 phần mềm VSPD hoặc VSPE để tạo 2 cổng COM ảo là COM2 và COM3, đấu chéo chúng với nhau (xem lại phần cổng COM ảo).
- Tìm trong thư mục chứa ví dụ AVR_STD và chạy file mô phỏng bằng phần mềm Proteus AVR_STD_Terminal.DSN.
- Quay lại Visual Basic, nhấn nút Run hoặc F5 để chạy Project vừa mới viết.
- Nhấn Run trong Proteus để mô phỏng mạch điện AVR_STD_Terminal.DSN. Bạn sẽ thấy kết một số text xuất hiện trông txtOutput như trong hình 17. Click vào txtInput và type bất kỳ một phím nào đó để xem kết quả. So sánh với mô phỏng trong hình 14 bạn thấy nét tương đồng. Như thế bạn đã thành công khi tự viết cho mình 1 ứng dụng giap tiếp với cổng COM bằng Visual Basic.
2. Viết chương trình giao tiếp cổng COM bằng Visual C++ 6.0
Phần này chúng ta sẽ thực hiện một ví dụ truyền nhận qua cổng COM tương tự như ví dụ ở phần trên nhưng sử dụng Visual C++ (VC++) của Microsoft. Mục đích chính là hướng dẫn cách sử dụng MSComm trong VC++, vì thế tôi sẽ trình bày rất sơ sài những phần như tạo Project trong VC++. Bạn đọc cần tự trang bị thêm kiến thức về lập trình VC++. Một trong những tài liệu rất hay cho người mới học lập trình VC là “Teach Yourself Visual C++ 6 in 21 Days” của “Sams Teach Yourself”, bạn có thể tìm đọc nếu thấy cần thiết.
Từ VC++ hãy vào menu “File/New” để tạo 1 Project mới. Chọn loại Project là “MFC AppWizard (exe)”, trong Ô Project Name đặt tên cho Project là AVR_PC, nhấn OK. Trong hộp thoại thứ 2 hãy chọn “Dialog based” cho loại Project, và nhấn Finish để tạo Project (các bước khác để mặc định).
Hình 17. Tạo Project MFC trong VC++6.
Khi Project mới được tạo sẽ có 1 hộp thoại chính (Dialog) xuất hiện với 2 button “OK” và “Cancel” trên đó. Dùng công cụ “Edit” để thêm vào 2 “Edit box” và sắp xếp lại giao diện như hình 19. Right click vào các Edit box và chọn Proterties từ các Popup_menu, lần lượt đổi ID của 2 Edit box thành IDC_OUTPUT và IDC_INPUT.
Cũng giống như trong VB, Control MSComm không xuất hiện mặc định trong Toolbox của VC++, chúng ta cần thêm vào khi muốn sử dụng control này. Hãy vào menu “Project/Add to Project/ Components and Controls…”. Khi hộp thoại “Components and Control Gallery” xuất hiện bạn chọn vào thư mục “Registered ActiveX Controls” và tìm đến file “Microsoft Communications Control, Version 6.0” rồi nhấn nút insert, nhấn OK khi được hỏi bất kỳ câu hỏi gì, sau đó nhấn nút Close để đóng hộp thoại lại. Lúc này icon của MSComm sẽ xuất hiện trong Toolbox của VC++ như trong hình 19. Click chọn icon của MSComm và vẽ 1 control vào Dialog chính của Project. Theo mặc định Control này có tên IDC_MSCOMM1.
Hình 18. Thêm Control MSComm vào Toolbox trong VC++.
Hình 19. Giao diện chương trình trong Visual C++.
Việc lập trình trong VC++ tương đối khó hơn VB (cho người mới tìm hiểu). Các thuộc tính của các Control như Edit box không được truy cập trực tiếp như Textbox trong VB. Ví dụ để gán và hiển thị một chuỗi hay số lên Edit box chúng ta phải thực hiện gán và cập nhật dữ liệu qua các biến trung gian. Ở bước này chúng ta đi tạo 2 biến cho 2 Edit box. Nhấn vào menu “View/ClassWizard” hoặc tổ hợp phím “Ctrl+W” , trong hộp thoại “MFC ClasWizard” hãy chọn tab “Member Variables”. Click vào dòng IDC_OUTPUT (chính là edit phía trên), nhấn vào nút “Add vatiable…” và điền tên biến là “m_txtOutput” với kiểu biến là CString như trong hình 20. Lặp lại các bước trên để tạo 1 biến tên “m_txtInput” cho “IDC_INPUT”. Cuối cùng là tạo 1 biến có tên “m_comm” cho IDC MSCOMM1. Nhấn OK để đóng hộp thoại MFC ClassWizard. Từ bây giờ, chúng ta chỉ cần nhớ 3 biến “m_txtOutput”, “m_txtInput”, “m_comm” khi muốn truy cập các Edit boxes và MSComm trong lúc viết code.
Hình 20. Tạo biến txtOutput cho Edit box IDC_OUTPUT.
Viết Code:
Nhấn Ctrl+W để mở lại ClassWizard, lần này chọn tab “Message Maps", trong ô “Class name” đảm bảo rằng “CAVR_PCDlg” được chọn. Trong ô “Object IDS” hãy chọn "CAVR_PCDlg”, ô “Messages” tìm và chọn "WM_INITDIALOGS” sau đó nhấn vào nút “Edit Code” (xem hình 21).
Hình 21. Bắt đầu viết code.
Bây giờ bạn có thể viết code cho sự kiện “OnInitDilaog()”, đây là sự kiện xảy ra khi bạn chạy chương trình và Dialog chính được khởi động. Vì thế chúng ta sẽ cài đặt các thông số cho m_comm vào đây (m_comm là tên biến đại diện cho control IDC_MSCOMM1 mà chúng ta đã tạo ở các bước trên). Hãy thêm các dòng sau vào sau dòng “// TODO: Add extra initialization here”:
Năm dòng code trên tương ứng với 5 dòng trong phần Form_Load() khi viết Project bằng VB mà chúng ta đã khảo sát ở trên, vì thế tôi không cần giải thích thêm cho các dòng code này.
Tiếp theo chúng ta sẽ viết code cho sự kiện onComm (ngắt nhận) của control MSComm, trước khi viết code hãy nhấnCtrl+W để hiện hộp thoại ClassWizard và thực hiện 6 bước như trong hình 22 để thêm sự kiện onComm vào Project.
Hình 22. Thêm sự kiện onComm để nhận dữ liệu từ cổng COM.
Viết đoạn code sa vào sự kiện onComm:
Như đã trình bày ở trên, m_comm là biến đại diện cho MSComm việc thao tác với cổng COM bây giờ thực hiên thông qua biến m_comm. Trong dòng 4 chúng ta khai báo 1 biến phụ strInput có kiểu CString dùng chứa giá trị nhận về sau này. Cũng giống như trong VB, sự kiện onComm có thể xảy ra do nhiều nguyên nhân, chúng ta chỉ quan tâm đến trường hợp có dữ liệu đến bộ đệm, dòng 5 cho phép ‘lọc” ra sự kiện cần thiết: if (m_comm.GetCommEvent()==2 ). Dòng 6 chúng ta khai báo 1 biến phụ tên in_dat với kiểu COleVariant. COleVariant là lớp (class) của MFC, tên gọi của nó là sự kết hợp của C + OLE +VARIANT trong đó OLE là “Object Linking Embedded” là một kiểu đối tượng không có sẵn mà được “nhúng” vào, MSComm là một loại OLE. VARIANT là một kiểu biến chưa xác định. Khi bạn có môt biến x, đôi khi bạn muốn gán giá trị số cho x nhưng cũng có khi bạn lại muốn gán chuỗi ký tự cho x. Khi đó hãy khai bao x là VARIANT. Trong trường hợp MSComm, dữ liệu vào và ra của đối tượng này thuộc dạng “chưa xác định” hay VARIANT. Trong dòng 7 chúng ta chỉ đơn giản nhận giá trị từ m_comm về biến in_dat: in_dat = m_comm.GetInput(). Dòng tiếp theo chúng ta “trích” thành phần chuỗi ký tự từ biến in_dat và gán cho biến strInput: strInput=in_dat.bstrVal(một cách tương đối có thể hiểu đổi in_dat thành CString và gán cho strInput). Chúng ta phải trích CString vì các Edit box chỉ hiển thị được CString. Để hiển thị dữ liệu nhận về lên Edit box (IDC_OUTPUT) chúng ta cộng dồn biếnm_txtOutput (biến đại diện của Edit box IDC_OUTPUT) bằng dòng lệnh 9: m_txtOutput+=strInput. Cuối cùng, để cho giá trị của biến m_txtOutput cập nhật lên Edit box chúng ta phải gọi hàm UpdateData với tham số FALSE như dòng 10: UpdateData(FALSE) (đây là cách làm việc của Visual C++).
Các dòng code từ 12 đến 14 được dùng với mục đích đưa con trỏ về cuối dòng của Edi box sau khi kết thúc quá trình nhận dữ liệu. Bạn có thể bỏ qua nếu thấy không cần thiết.
Viêc cuối cùng là viết code cho Edit box bên dưới (IDC_INPUT) để khi chúng ta gõ (type) vào đấy, ký tự sẽ được gởi đến cổng COM. Nhấn Ctrl+W và thực hiện các bước bên dưới để thêm vào sự kiện onChange.
Hình 23. Thêm sự kiện onChange cho IDC_INPUT.
Hãy viết đoạn code sau vào sự kiện onChange của Edit box Input:
Khi người dùng type 1 ký tự nào đó vào Edit box, sự kiện onChange xảy ra, khi đó chúng ta sẽ trích ký tự cuối cùng trong nội dung của Edit box bên dưới mà đại diện là biến m_txtInput bằng dòng lệnh 11: tmpStr=m_txtInput.Right(1).Trong đó tmpStr là một biến tạm khai báo ở dòng 9. Chú ý rất quan trong khi muốn đọc nội dung của Edit box chúng ta cần gọi hàm UpdateData với tham số TRUE trước đó như trong dòng 10. Sau cùng, gọi phương phương thức SetOutput của đối tượng MSComm để gởi giá trị ra cổng COM: m_comm.SetOutput(COleVariant(tmpStr)). Để gởi một ký tự (hay chuỗi ký tự) ra cổng COM trước hết chúng ta cần “ép kiểu” ký tự đó về COleVariant vì như đã trình bày, MSComm chỉ làm việc với COleVaraint. Đoạn COleVariant(tmpStr) thực hiện việc “ép kiểu” này.
Sau khi viết xong đoạn code cho sự kiện onChange bạn có thể nhấn tổ hợp phím Ctrl+F5 để chạy chương trình. Dùng mạch điện AVR_STD_Terminal.DSN và chạy mô phỏng như trong phần lập trình với VB. Kết quả thu được sẽ như trong hình 24.
Hình 24. Giao tiếp giữa AVR và Visual C++.
Mời bạn tham khảo thêm phần mềm gCOM, một công cụ giao tiếp, lưu trữ dữ liệu và vẽ đồ thị cổng COM
Nguồn: hocavr