Component
3.3.3 Xây dựng chơng trình
Chúng ta coi đã biết lập trình ứng dụng với Visual Basic, vì vậy ở đây sẽ không chủ định giới thiệu các bớc để tiến hành xây dựng một chơng trình ứng dụng nh thế nào, những vấn đề này có thể tham khảo trong tài liệu tiếng Việt [1] hoặc bất kể một tài liệu hớng dẫn lập trình nào với Visual Basic. Trong phần này chủ yếu trình bày việc sử dụng hoặc xây dựng các hàm hoặc thủ tục phục vụ thu thập số liệu từ AX5210H, việc áp dụng các kỹ thuật để sử dụng các thành phần COM có sẵn. Giao diện chính của chơng trình đợc xây dựng trên Form có tên là frmMain, khi hoàn tất giao diện nó có dạng nh hình 4.1 trong phụ lục.
Trong mô hình chơng trình đã đợc mô tả trong hình 3.2, máy tính giao tiếp với AX5210H thông qua các thành phần COM. Các COM này có thể của nhà sản xuất AXIOM và cũng có thể là COM tự xây dựng. Dới đây sẽ tìm hiểu hai hớng áp dụng
các kỹ thuật phần mềm với COM. Hớng thứ nhất sử dụng các thủ tục, hàm trong COM có sẵn của hãng sản xuất và hớng khác sẽ thực hiện xây dựng thành phần COM dới dạng th viện liên kết động DLL, sau đó sử dụng nó trong chơng trình.
Bộ đệm dữ liệu của các kênh tín hiệu điện não là một mảng kiểu số thực đợc khai báo trong Module1.bas nh sau:
Public MyDataChanels() As Single
Khi khởi tạo bộ đệm, mảng trên sẽ đợc khởi tạo thành mảng hai chiều bởi câu lệnh Redim : gồm 16 hàng từ 0ữ15 lu giữ giá trị điện áp tơng ứng cho 16 kênh tín hiệụ Hàng 0 dùng cho kênh đánh dấu M1, 1ữ14 dùng cho 14 kênh đạo trình tín hiệu điện não, hàng 15 dùng cho kênh đánh dấu M2. Sau đây chúng ta sẽ đi tìm hiểu hớng thứ nhất.
a).Sử dụng COM của AXIOM để viết chơng trình thu thập số liệu
Để áp dụng cách này yêu cầu:
- Chơng trình điều khiển và quản lý thiết bị AXIOM phải đợc cài đặt.
- Cài đặt cấu hình thiết bị bằng cách chạy chơng trình quản lý thiết bị trong menu Start: Start\ Programs\ Axiom Driver\ Devmrg32. Chọn card AX5210H, thiết lập các tham số cần thiết cho card sau đó lu toàn bộ vào tệp "C:\Windows\ axdrv.ini".
Sau khi hoàn tất các thủ tục này có thể bắt tay vào viết chơng trình. Những biến, hằng, các hàm và thủ tục cần thiết cho thao tác thu thập số liệu trong chơng trình đ- ợc khai báo và thực hiện nh dới đây trong mô đun Module1.bas:
Cấu trúc dữ liệu AIPARM chứa các tham số cần thiết để thiết lập cấu hình và thao tác vào ra với AX5210H:
Type AIPARM
wGainCode As Integer ' Hệ số khuếch đại kênh
wExpChan As Integer ' Số hiệu kênh thuộc card mở rộng
wExpGainCode As Integer ' Hệ số khuyếch đại kênh trên card mở rộng fData As Single ' Giá trị số liệu thực của kênh đọc đợc hWnd As Long ' Thẻ Windows
End Type
'Một số biến sử dụng trong quá trình thu thập số liệu:
Public wErr As Integer 'Chứa giá trị lỗi trả về của một hàm
Public lParm As AIPARM 'Chứa các tham số sử dụng khi thu thập dữ liệu Public wDev As Integer 'Nhận dạng thiết bị
Public strErrMsg As String * 80 ' Đệm thông báo lỗi
Public strIniFile As String ' Chứa tên tệp cấu hình thiết bị "axdrv.ini" Public Count As Long
Public ChanelNum As Integer ' Số kênh tín hiệu điện não Public Size As Long 'Kích thớc bộ đệm của một kênh
Các hàm và thủ tục phục vụ thu thập số liệu trong th viện "AXDRV32.DLL" đợc khai báo bắt đầu bằng từ khoá Declare theo cách nh đã đợc tìm hiểu ở phần trên. Một số hàm cần thiết đợc khai báo sau:
Public Declare Function axGetErrMsg Lib "axDrv32" (ByVal wErr As Integer,_ ByVal strErrMsg As String) As Integer
Public Declare Function axInitialize Lib "axDrv32"(ByVal strIniFile As String)_ As Integer
Public Declare Function axAISetting Lib "axDrv32" (wDev As Integer, lParm_ As AIPARM) As Integer
Public Declare Function axADInput Lib "axDrv32"(ByVal wDev As Integer,_ lParm As AIPARM) As Integer
Nội dung thủ tục Init() khởi tạo card với tham số cấu hình đợc lu trong tệp "axdrv.ini":
Public Sub Init() strIniFile = "axdrv.ini"
wErr = axInitialize(strIniFile) If wErr <> 0 Then
wErr = axGetErrMsg(wErr, strErrMsg) MsgBox (strErrMsg)
End If End Sub
Nội dung thủ tục thiết lập lại cấu hình cho AX5210H nh sau: Public Sub Setting()
lParm.hWnd = hWnd
wErr = axAISetting(wDev, lParm) If wErr <> 0 Then
wErr = axGetErrMsg(wErr, strErrMsg) MsgBox (strErrMsg)
End If End Sub
Khi thực hiện thủ tục này, một hộp thoại sẽ xuất hiện cho phép chúng ta thay đổi các tham số phục vụ thu thập số liệu từ các kênh tín hiệu đầu vàọ Các tham số này
sẽ đợc lu lại trong biến toàn cục lParm và đợc dùng lại trong hàm thu thập số liệu kênh đơn axADInput vận hành ở chế độ kích hoạt biến đổi A/D từ chơng trình (Trigger By Software). Nội dung hàm lấy số liệu từ một kênh xác định nh sau:
Public Function GetsDatăChanel As Integer) as Single lParm. wChan=Chanel
lParm. wGainCode=1
wErr = axADInput(wDev, lParm) If wErr <> 0 Then
wErr = axGetErrMsg(wErr, strErrMsg) MsgBox (strErrMsg)
Exit Function End If
GetsData= lParm.fData End Function
Thực hiện xong đoạn lệnh trong hàm này, nếu không có lỗi, dữ liệu thực của kênh sẽ đợc lu trong trờng fData của biến lParm tức là lParm.fDtata và trả về giá trị đó cho hàm.
Sau khi hoàn tất toàn bộ quá trình thu thập số liệu, sử dụng hàm sau để đ a card trở về trạng thái an toàn:
wErr = axTerminate()
* Vòng lặp thu thập số liệu 16 kênh tín hiệu điện não đợc đặt trong thủ tục chính Main(). Thủ tục này đợc thực hiện ngay khi chơng trình bắt đầu chạỵ Nội dung của Main() nh sau:
Public Sub Main() Dim i As Integer FrmMain.Show
Count = 0 Gain = 0
StartOrStop=False ChanelNum=16 Size=499999
Redim MyDatăChanelNum-1,Size)' Khởi tạo bộ đệm Do While DoEvents If StartOrStop Then Count = Count + 1 For i = 0 To 15 MyDatăi,Count - 1) =GetsDatăi) Call Display(i) Next i End If Loop End Sub
Trong Main(), đầu tiên FrmMain đợc kích hoạt đa ra cho ngời sử dụng một giao diện chính của chơng trình có các nút, các điều khiển chơng trình, lựa chọn các chế độ, ..vv, giao diện này đợc chỉ ra trên hình 4.1 trong phần phụ lục. Thủ tục Main() là hạt nhân chính của chơng trình. Việc thu thập, xử lý, hiển thị số liệu đợc đặt trong một vòng lặp Do While DoEvents...Loop. Vòng lặp quét các kênh tín hiệu điện não nằm trong vòng lặp này chỉ đợc thực hiện khi điều kiện StartOrStop là True và điều kiện này đợc điều khiển bởi núm ấn trong giao diện chơng trình. Vòng lặp với điều kiện DoEvents cho phép chơng trình tạm dừng vòng lặp, lu lại toàn bộ trạng thái của vòng lặp vào Stack mỗi khi có sự kiện nào đó đợc yêu cầu thực hiện, chẳng hạn mỗi khi khích chuột vào một núm nào đó trên giao diện chơng trình. Sau khi kết thúc sự
kiện đó chơng trình lại quay trở về thực hiện tiếp những việc trong vòng lặp. Vòng lặp này đợc thực hiện ở chế độ không tải của chơng trình (Idle). Vòng lặp con
For...Next thực hiện quét lần lợt các kênh tín hiệu điện não từ 0ữ15 đồng thời nó gọi đến thủ tục Display(i) sau khi lấy đợc số liệu của một kênh để hiển thị dữ liệu của kênh đó lên màn hình dới dạng đồ thị ghi lại giá trị tín hiệu tức thời đọc đợc của kênh ị Thủ tục này đã đợc xây dựng trong Module2.bas, trong mô đun này chứa các biến, các thủ tục và hàm phục vụ hiển thị các thông tin, tín hiệu của các kênh điện não lên màn hình. Trong lĩnh vực đo ghi các tín hiệu y-sinh, phần trình diễn đồ họa cũng có những điểm đặc thù của y-sinh: nó có đặc điểm của một thiết bị tự ghi nhiều kênh tín hiệu cùng hiển thị đồng thời mô tả quá trình thay đổi của các tín hiệu điện sinh. Tuy nhiên ở đây không đi tìm hiểu về vấn đề này vì mục đích chính là áp dụng các kỹ thuật gọi hàm API của Windows để sử dụng chức năng của các thành phần COM của nhà sản xuất hoặc của thành phần COM tự xây dựng.
Sau khi chơng trình đợc hoàn thiện theo phơng án này, kết quả chạy chơng trình cho thấy: hàm thu thập số liệu axADInput trong th viện "AXDRV32.DLL" của hãng AXIOM thực hiện đọc giá trị tín hiệu với trễ rất lớn, không đảm bảo tốc độ để tiến hành thu thập đồng thời số liệu của 16 kênh tín hiệu điện nãọ Vì vậy sau đây chúng ta tiếp tục xem xét hớng thứ hai để giải quyết vấn đề nàỵ
b).Xây dựng COM truyền thông với AX5210H
Dựa trên cấu trúc, khuôn dạng, chức năng điều khiển vào/ra của các thanh ghi trên AX5210H nh đã đợc trình bày trong mục 3.1.3, ta có thể làm chủ đợc toàn bộ quá trình truyền thông với card ở mức vào/ra bít, điều khiển card thực hiện thu thập số liệu bằng cách đọc và ghi trực tiếp lên các thanh ghi chức năng của nó. Nhng thật không may vì Visual Basic là một ngôn ngữ bậc cao chuyên dùng để lập trình cho mục đích quản trị cơ sở dữ liệu và lôgíc kinh doanh nên không hỗ trợ nhiều thủ tục và hàm thao tác với phần cứng ở mức thấp. Một câu hỏi đặt ra là: bằng cách nào
có thể tiếp cận đợc với các cổng vào/ra hoặc các thanh ghi trên card trong môi trờng Visual Basic? COM chính là chìa khoá để giải quyết vấn đề nàỵ Các thủ tục hoặc hàm cần thiết hỗ trợ vào ra cho AX5210H sẽ đợc viết trên ngôn ngữ lập trình ở mức thấp gần với ngôn ngữ máy hoặc phần cứng của máy tính, sau đó chúng đợc biên dịch thành tệp có khuôn dạng sử dụng lại COM hay các th viện liên kết động DLL. Visual Basic sẽ sử dụng các thủ tục/ hàm đó thông qua các lời gọi dạng API của Windows truyền thông với AX5210H.
Nh vậy thông qua COM chúng ta vừa tận dụng đợc thế mạnh của các ngôn ngữ lập trình bậc thấp khác nhau chẳng hạn nh Assembler hoặc C/ C++
, Visual C++
và vừa tận dụng đợc u điểm của ngôn ngữ lập trình mức cao Visual Basic là cho phép dễ dàng nhanh chóng tạo ra các giao diện đẹp mắt, quản trị cơ sở dữ liệu thuận tiện, xắp xếp dữ liệu ..vv. Và khi đó không phải phụ thuộc vào các tệp trình điều khiển đồ sộ của hãng nữa, chơng trình của chúng ta có thể chạy độc lập mà chỉ cần tới một th viện tự tạo nhỏ, gọn để truy cập vào ra với AX5210H.
Trong chơng trình đã sử dụng Visual C++ để viết các hàm và thủ tục truy cập lên AX5210H ở chế độ kích hoạt biến đổi A/D từ chơng trình và dịch thành th viện liên kết động MyDLL.dll. Th viện này đợc Visual Basic sử dụng để truy cập lên AX5210H. Th viện MyDLL.dll đợc tạo ra với dự án Win32 Dynamic Linking Library trên nền Win32 trong Microsoft Visual C++ 6.0 của bộ Microsoft Visual Studio 6.0. Không đi sâu tìm hiểu cách viết một DLL nh thế nào trong Microsoft Visual C++ 6.0 mà ở đây chỉ trình bày nội dung các hàm đã đợc viết để thao tác với AX5210H. Phần khai báo các th viện chuẩn, các hằng và các biến nh sau:
#include "stdafx.h" #include "coniọh" #include "stdlib.h" #include "MyDLL.h" #include "math.h"
/*Các hằng sử dụng trong th viện*/
#define RESOLUTION 4096 /* Độ phân giải A/D 12-bit */
#define MAXAIVOLTAGE 5 /* Điện áp tơng tự đầu vào cực đại */ #define MINAIVOLTAGE -5 /* Điện áp tơng tự đầu vào cực tiểu */ /*Các biến sử dụng trong th viện*/
static float GainArray[5]={1,2,4,8,16}; /*Mảng chứa hệ số khuyếch đại kênh*/ unsigned short CBaseAđress; /*Biến chứa địa chỉ cơ bản của AX5210H*/ Các thủ tục và hàm trong th viện MyDLL.dll sử dụng chủ yếu hai hàm truy cập trực tiếp đến các cổng vào ra (I/O Port) của máy tính, đó là hàm: _inp dùng để đọc dữ liệu từ một cổng có địa chỉ xác định port, dữ liệu đọc đợc có giá trị nằm trong khoảng từ 0ữ255 và hàm _outp dùng để ghi một byte dữ liệu databyte tới một cổng có địa chỉ xác định port. Địa chỉ cổng port là một số nguyên không dấu kiểu
unsigned short có giá trị nằm trong khoảng từ 0ữ65535, hai hàm trên đợc mô tả nh sau:
int _inp(unsigned short port )
int _outp( unsigned short port, int databyte )
Trong th viện sử dụng biến CBaseAđress giữ địa chỉ cơ bản của AX5210H, địa chỉ cơ bản phải trùng với địa chỉ đã thiết lập bởi chuyển mạch DIP trên card. Địa chỉ mặc định do nhà máy thiết lặp sẵn trên card là 0x300 ( 300 hex).
*.Hàm khởi tạo AX5210H: chỉ cần sử dụng một lần duy nhất trớc khi gọi các hàm khác trong th viện, nhiệm vụ của hàm này là: thực hiện thiết lập địa chỉ cơ bản
CBaseAđress cho AX5210H, đặt hệ số khuyếch đại bằng 1 cho kênh đầu vào số 0 bằng cách ghi giá trị 0 vào thanh ghi có địa chỉ CBaseAđress+1 và che các bít TRGE và INTP bởi việc ghi 0 tới thanh ghi có địa chỉ CBaseAđress+2. Nội dung của hàm nh sau:
int CInitial(int base_ađr) {
CBaseAđress=base_ađr; /*Thiết lập địa chỉ cơ bản cho AX5210H*/ _outp(CBaseAđress + 1, 0); /* Thiết lập hệ số khuyếch đại = 1 */
_outp(CBaseAđress + 2, 0); /* Không cho phép TRGE và INTE */ return 0;
}
*.Thủ tục kết thúc hoặc đặt card AX5210H vào trạng thái an toàn: void CTerminate()
{
headDOData=FreeDODatăCBaseAđress, headDOData); }
*.Hàm đọc giá trị điện áp từ một kênh ở chế độ kích hoạt biến đổi A/D từ chơng trình, số liệu đọc từ card sẽ đợc chuyển đổi và lu giữ dới dạng thập phân chỉ giá trị thực điện áp của kênh (đơn vị V). Để tiện theo dõi quá trình truyền thông vào/ra với AX5210H, có thể xem trên lu đồ thực hiện của hàm chỉ ra trên hình 3.6 trong đó để hiểu đợc ý nghĩa của bít EOC và thuật toán có thể xem lại mục 3.2.2.
NOYES YES
NOYES YES
Input
Chan, gain, *fpdata
Chọn kênh, thiết lập hệ số khuyếch đại
Tạo trễ >=5às
Kích hoạt biến đổi A/D, i=0
EOC=0?
Đọc dữ liệu, biến đổi thành giá trị điện áp thực của kênh Đọc bít trạng thái EOC i=999 ? i=i+1 Output Return 5000 Return 0, *fpdata
Hình 3.6-Lu đồ quá trình đọc giá trị điện áp từ một kênh xác định của hàm
CReadDataAI
ở đây chan : số hiệu kênh đợc chọn; GainCode: chỉ số mảng hệ số khuyếch đại (0,1,2,3,4) ; *pfdata: giá trị thực của điện áp đọc đợc từ kênh chan. Nội dung của hàm nh sau:
int CReadDataAI(int chan,int GainCode, float* pfdata) {
int i;
float GainVal; unsigned int data;
/*Thiết lập hệ số khuyếch đại và số hiệu kênh vào*/ _outp(CBaseAđress+1, (GainCode<<4)|chan);
/*Có thể tạo trễ để khẳng định card đã chuyển đến kênh đợc chọn: For (i=0;i<1000;i++){
}
_outp(CBaseAđress, 0);
/* Kiểm tra trạng thái của bít EOC */ for (i = 0; i < 1000; i++){
/* Đọc bít EOC mô tả trạng thái biến đổi A/D: EOC=0 A/D đã kết thúc if ((_inp(CBaseAđress + 2) & 0x08) == 0)
break; if (i == 999) {
return 5000; /*Có nghĩa là bị lỗi */ }
}
/* Đọc dữ liệu từ các thanh ghi dữ liệu */
data = (inp(CBaseAđress) >> 4) + (inp(CBaseAđress + 1) << 4); /* Biến đổi thành dữ liệu thực tế điện áp vào (V) */
GainVal= GainArray[GainCode]; *pfdata=(float)data/RESOLUTION*(MAXAIVOLTAGE- MINAIVOLTAGE) + MINAIVOLTAGE; *pfdata /= GainVal; return (0); }
Quá trình hoạt động của hàm diễn ra nh sau: trớc tiên là chọn kênh và thiết lập hệ số khuyếch đại cho kênh, sau đó tạo ra một kích hoạt bộ biến đổi A/D bởi thao tác ghi giá trị 0 lên thanh ghi có địa chỉ CBaseAđress. Lúc này card bắt đầu thực hiện quá trình biến đổi tín hiệu tơng tự từ đầu vào đã đợc chọn thành tín hiệu số- biến đổi A/D. Trạng thái quá trình biến đổi đợc kiểm tra bởi bít EOC trong thanh ghi trạng thái ở địa chỉ CBaseAđress+2. Nếu EOC=1 có nghĩa quá trình biến đổi A/D cha kết thúc, còn nếu EOC=0 có nghĩa quá trình biến đổi A/D đã kết thúc và lúc này dữ liệu xuất hiện trên các thanh ghi dữ liệu ở địa chỉ CBaseAđress và