You are on page 1of 78

ĐẠI HỌC QUỐC GIA THÀNH PHỐ HỒ CHÍ MINH

TRƯỜNG ĐẠI HỌC BÁCH KHOA

KHOA ĐIỆN – ĐIỆN TỬ



BÁO CÁO THÍ NGHIỆM

MÔN ĐO LƯỜNG & ĐIỀU KHIỂN BẰNG MÁY TÍNH

GVHD: LÊ QUANG THUẦN

Thứ 2,Tiết 10-11-12

DANH SÁCH NHÓM

Nhóm – tổ STT Họ và tên MSSV


1 Nguyễn Duy Gơ 1610849
TNDD –
2 Nguyễn Văn Hà 1610863
Nhóm 02
3 Trần Thị Thúy Hà 1610868

Tp.HCM, ngày 28 tháng 5 năm 2019


PHỤ LỤC

1. Bài thí nghiệm 1: ........................................................................................... 02


2. Bài thí nghiệm 2: ........................................................................................... 18
3. Bài thí nghiệm 3: ........................................................................................... 33
4. Bài thí nghiệm 4: ........................................................................................... 44

1
Bài thí nghiệm số 1

LẬP TRÌNH PLC S7-

1200– CÁC ỨNG DỤNG LẬP TRÌNH CƠ BẢN

IV. THÍ NGHIỆM

1. Thí nghiệm 1: Khảo sát hoạt động của ngõ vào và ngõ ra
 Chuẩn bị thí nghiệm: Thực hiện nối dây như hình

- Đấu dây ngõ vào Zone 1 và Zone 5: BT1 – In 0.0, BT2 – In 0.1, BT2 – In 0.2; SW1
– In 0.3, SW2 – In 0.4, SW3 – In 0.5.
- Đấu dây ngõ ra Zone 1 và Zone 6: các ngõ Out 0.0 – 0.7 Zone 1 được nối lần lượt
với các DO 1-8 Zone 6
 Tiến hành thí nghiệm:
a. Thí nghiệm 1.1: Làm quen PLC với bài toán điều khiển START/STOP

Yêu cầu:
- START – nút nhấn BT1
- STOP – nút nhấn BT2.

2
- Đèn báo RUN– Đèn DO 1, chớp nháy chu kỳ 1s, sử dụng xung nhịp hệ thống.
Tiến hành lập trình:

Kết quả:
- Đèn DO1 nhấp nháy chu kì 1s

3
b. Thí nghiệm 1.2: Điều khiền hoạt động với các điều kiện ngõ vào khác
nhau:
Yêu cầu: Điều khiển các đèn ngõ ra theo điều kiện cùa ngõ vào như sau:
- Default: 8 LED ngõ ra nhấp nháy chu kỳ 2s
- Nhấn nút BT1, đèn LED sang theo thứ tự từ trái sang phải, mỗi đèn sang trong thời
gian 1s
- Nhấn nút BT2, đèn LED sang theo thứ tự từ phải sang trái, mỗi đèn sang trong thời
gian 0.5s

Tiến hành lập trình:

4
5
6
Kết quả:
- Mạch chạy đúng như yêu cầu.

7
2. Bài thí nghiệm 2: Giả lập hoạt động của máy trạng thái
Khảo sát, giả lập và điều khiển vận hành hệ thống phân loại sản phẩm:
 Mô tả: Hệ thống phân loại sản phẩm

- Có 3 loại sản phẩm được phân biệt theo chiều dài, bao gồm D – Sản phẩm dài; N:
Sản phẩm ngắn; TB: Sản phẩm có chiều dài trung bình.
- Chiều dài của sản phẩm được xác định bởi các cảm biến CB1, CB2 và CB3.
o Sản phẩm là N khi chỉ có 1 cảm biến phát hiện được sản phẩm.
o Sản phẩm là TB khi chỉ có 2 cảm biến phát hiện được sản phẩm.
o Sản phẩm là D khi chỉ có 3 cảm biến phát hiện được sản phẩm.
- Các ngõ ra báo chiều dài sản phẩm N, TB, D tương ứng với các ngõ ra Q0.0, Q0.1,
Q0.2. Các tín hiệu này báo trong thời gian 2s.
 Tiến hành thí nghiệm:
a. Thí nghiệm 2.1: Xây dựng mô hình giả lập hoạt động của hệ thống phân
loại sản phẩm
Chuẩn bị Thí nghiệm:
Thực hiện đấu dây

Xây dựng mô phỏng tín hiệu cảm biến cho các loại sản phẩm khác nhau:

8
- Ngõ vào: Nút nhấn BT1, BT2 và BT3 tương ứng là các tín hiệu báo giả lập SP N,
TB và D.
- Ngõ ra: tín hiệu Q0.3, Q0.4 và Q0.5 tương ứng là tín hiệu giả lập của CB1, CB2,
và CB3.
- Mô tả graph tín hiệu của các cảm biến CB1, CB2 và CB3 tương ứng với các sản
phẩm.

Yêu cầu:
- Cải tiến chương trình cho phép tăng tốc, giảm tốc băng tải với ngõ vào AI0 (thời
gian thay đổi)
- Tìm khuyết điểm của chương trình trên
- Nêu phương pháp, giải thuật khác giả lập hoạt động của hệ thống.

Tiến hành lập trình:

9
10
11
12
13
14
Kết quả:
- Mạch chạy đúng theo yêu cầu đề bài, với ngõ vào là AI0
- Chương trình còn khuyết điểm đó là chưa loại bỏ được việc nhấn nút mô phòng quá
sát nhau không đúng như thực tế. Ví dụ: khi đang mô phỏng sản phẩm trung bình
vật chưa đi hết cảm biến 1 nhưng đã nhấn nút mô phỏng sản phẩm dài dẫn đến sai
thời gian đi qua các cảm biến, đây không đúng với thực tế bởi hai sản phẩm không
thể chồng lên nhau mà nối tiếp nhau.
- Hướng khắc phục : Khi đang mô phỏng sản phẩm chưa hết thời gian ở cảm biến 1
thì không cho phép nhấn nút mô phỏng sản phẩm tiếp theo.

b. Thí nghiệm 2.2: Viết chương trình phân loại sản phẩm với tín hiệu giả lập
vừa xây dựng được
Gợi ý: Sinh viên lựa chọn 1 trong các phương pháp sau để giải quyết bài toán
- Phương pháp 1: Giải quyết bài toán theo phương pháp tuần tự với bộ tín hiệu ngõ
vào là CB1_CB2_CB3; ngõ ra là N_TB_D
- Phương pháp 2: Giải quyết bài toán bằng cách đếm số lượng sản phẩm giữa các cảm
biến kết hợp trạng thái của cảm biến.
- Phương pháp 3: Giải pháp khác

15
Yêu cầu:
- Lập trình hoạt động của hệ thống theo phương án đã chọn
- Kiểm chứng hoạt động trong các trường hợp khác nhau:
o Trường hợp từng sản phẩm đi qua hệ thống phân loại thời gian cách nhau.
o Trường hợp nhiều sản phẩm đi qua hệ thống liên tiếp nhau: Ngắn – dài -
trung bình.
o Kiểm chứng trường hợp cụ thể theo yêu cầu của GVHD.
Tiến hành lập trình:

16
Kết quả:
- Chương trình chạy đúng như yêu cầu

17
Bài thí nghiệm số 2

TRUYỀN THÔNG NỐI TIẾP GIỮA PC VỚI PLC


IV. THÍ NGHIỆM:

A. Bài thí nghiệm 1: Làm quen với PLC S7-200


3. Thí nghiệm 1.2: Cải tiến chương trình của Thí nghiệm 1.1 hoạt động theo các
chế độ khác nhau:
- Khi SW1 tác động, đèn hoạt động theo chu kỳ XA = 30s, VA = 3s, DA = 43s; XB
= 40s, VB = 3s, DB = 33s.
- Khi SW2 tác động, đèn hoạt động theo chu kỳ XA = 60s, VA = 5s, DA = 63s; XB
= 60s, VB = 5s, DB = 63s.
- Khi SW3 tác động, đèn VA và VB nhấp nháy chu kỳ 1s.
Giải thuật:
- Giữ nguyên phần chương trình điều khiển đèn giao thông ở thí nghiệm 1.1, bổ sung
cho các SW2 và SW3.
- Thiết lập các khoảng thời gian cho các đèn giao thông khi ấn SW2 (I0.4) bằng lệnh
MOVE.
- Khi SW3 tác động (I0.5), 2 đèn VA và VB nhấp nháy chu kì 1s
=> mỗi đèn sáng 0.5s, tắt 0.5s
=> sử dụng khối chức năng so sánh (Network 12)

Chương trình điều khiển trên S7-200:

18
19
20
21
B. Thí nghiệm 2: Giao tiếp giữa PC và PLC sử dụng ngắt nhận ký tự, truyền thông
qua PORT 0, giao thức “9600, N,8,1”
4. Thí nghiệm 2.2: thực hiện cải tiến bài toán điều khiển đèn giao thông của thí
nghiệm 1.1 và quan sát trên giao diện.
Yêu cầu:
- Thay đổi thông số của các đèn XA, VA, XB, VB.
- Hiển thị trạng thái các đèn Xanh, Vàng, Đỏ.
- Hiển thị thời gian đếm ngược dạng decimal.
- Hiển thị thời gian đếm ngược dạng led 7 SEG.
Giải thuật:
- Thiết lập giao thức truyền từ giữa PC và PLC: ban đầu PC truyền xuống giá trị 0,
sau đó truyền lần lượt các giá trị thời gian theo thứ tự XA, VA, XB, VB; mỗi giá
trị cách nhau 10ms.
- Các giá trị thời gian XA, VA, XB, VB có đơn vị là giây, chứa trong 1 byte.
- Các timer T37, T38, T39, T30 có độ phân giải 100ms, do đó các giá trị thời gian
gởi xuống phải được nhân 10 trước khi nạp vào các timer.
- Sử dụng lệnh SEG để chuyển mã BCD sang mã LED 7 đoạn.
- Sử dụng lệnh XMT để truyền thông tin về PC.
- Format bảng tin truyền về như sau:

22
23
Chương trình C#:

24
25
26
Chương trình S7-200:
1) Main:
ORGANIZATION_BLOCK MAIN:OB1
TITLE=PROGRAM COMMENTS
BEGIN
Network 1
LD SM0.1
MOVB 16#09, SMB30
ATCH INT0, 8
ENI

Network 2
LD SM0.1
MOVB 1, VB120
MOVB 12, VB121
MOVB 1, VB122
MOVB 'B', VB123

Network 3

Network 4
LDN T40
LPS
TON T37, MW30
A T37
TON T38, VW132
LRD

27
A T38
TON T39, VW134
LPP
A T39
TON T40, VW136

Network 5
LDN T37
= Q0.0

Network 6
LD T37
AN T38
= Q0.1

Network 7
LD T38
= Q0.2

Network 8
LD T38
AN T39
= Q0.3

Network 9
LD T39
= Q0.4

Network 10

28
LDN T38
= Q0.5

Network 11
LD I0.5
MOVW 300, VW130
MOVW 30, VW132
MOVW 400, VW134
MOVW 30, VW136

Network 12

LDB= SMB2, 52
= Q1.0
Network 13
LDB> SMB2, 40
= Q1.1
Network 14
LDB> SMB2, 1
= Q1.2
Network 15
LDW= MW20, 4
= Q1.4
Network 16
LDW= MW10, 52
= Q1.5
Network 17
LDN T40

29
TON T40, 1
Network 18
LD T40
EU
+I 1, MW22
Network 19
LDW= MW30, 1
XMT VB120, 0
Network 20
LDW= MW30, 2
XMT VB122, 0
Network 21
LDW= MW30, 3
MOVW 0, MW22
2) Khối ngắt:
INTERRUPT_BLOCK INT_0:INT0
TITLE=INTERRUPT ROUTINE COMMENTS
BEGIN
Network 1
LD SM0.1
+I 1, MW8
Network 2
LDW= MW8, 1
BTI SMB2, MW10
AENO
MOVW MW10, VW130
-I +48, VW130
Network 3
LDW= MW8, 2

30
BTI SMB2, MW10
AENO
MOVW MW10, VW132
-I +48, VW132
Network 4
LDW= MW8, 3
BTI SMB2, MW10
AENO
MOVW MW10, MW20
-I +48, MW20
Network 5
LDW= MW8, 4
BTI SMB2, MW10
AENO
MOVW MW10, VW136
-I +48, VW136
Network 6
LDW= MW8, 5
MOVW 0, MW8
Network 7

LDB= SMB2, 52
MOVW 40, MW30
AENO
MOVW 40, VW132
Network 8
Network 9
Network 10

31
LDB= SMB1, 'D'
MOVB SMB2, VB100
MOVB SMB3, VB101
MOVB SMB4, VB102
MOVB SMB5, VB103

END_INTERRUPT_BLOCK

Đề xuất giải thuật khác cho thí yêu cầu bài toán ở thí nghiệm 2.2:

Ta có 4 trạng thái của led đơn, mỗi trạng thái sẽ sang trong 1 thời gian cụ thể. Do đó , ta
chỉ cần gửi 2 byte dữ liệu 1 byte chứa trạng thái và một byte đếm thời gian trạng thái đó
là có thể đồng bộ việc hiển thị giữa máy tính và PLC.

32
Bài thí nghiệm số 3

GIAO TIẾP TCP/IP


IV. THÍ NGHIỆM

B. Bài thí nghiệm 2: Phát triển Thí nghiệm 1 ứng dụng điều khiển nhiệt độ
5. Thí nghiệm 2.1: Điều khiển ON/OFF lò nhiệt, vùng trễ 3oC, nhiệt độ đặt 60oC.
Chu kỳ lấy mẫu 0.5s
- Sử dụng thanh ghi BUFF.data[0] điều khiển đóng ngắt lò nhiệt.
- Tín hiệu nhiệt độ IW64 được đọc về PC thông qua thanh ghi BUFF.data[6].
- Bài toán điều khiển được thực hiện từ PC.
- Vẽ đồ thị quan sát nhiệt độ đặt và nhiệt độ thực.
Giải thuật:
- Nhiệt độ lò nhiệt đọc về < (Giá trị đặt – 3oC) => Tín hiệu điều khiển bằng 1.
- Nhiệt độ lò nhiệt đọc về > (Giá trị đặt + 3oC) => Tín hiệu điều khiển bằng 0.
- (Giá trị đặt – 3oC) < Nhiệt độ lò nhiệt đọc về < (Giá trị đặt + 3oC) => Tín hiệu điều
khiển không thay đổi (bằng tín hiệu điều khiển trước đó).
- Gửi chế độ điều khiển thông qua thanh ghi BUFF.data[1] (BUFF.data[1] = 1: chế
độ ON/OFF, BUFF.data[1] = 2: chế độ PID)
- Gửi tín hiệu điều khiển xuống PLC thông qua thanh ghi BUFF.data[0].
- PLC xuất tín hiệu bật tắt lò nhiệt.
Chương trình điều khiển ON/OFF từ máy tính:
private void Plant(double uk)
{
yk = Convert.ToUInt16(TxtRdReg1.Text);
yk2 = yk1;
yk1 = yk;
uk2 = uk1;
uk1 = uk;
}

33
private void ONOFF(double sp, double dl)
{
ek = sp - yk;
if (((ek - ek1) >= 0) && (ek >= dl))
uk = 1;
else if (((ek - ek1) < 0) && (ek <= -dl))
uk = 0;
ek1 = ek;
}

6. Thí nghiệm 2.2: Điều khiển PID lò nhiệt, thời gian lấy mẫu 0.2s; các thông số
Kp, Ki, Kd sinh viên tự chọn. Công suất ngõ ra thay đổi từ 0-100%.
- Sử dụng thanh ghi BUFF.data[0] là thông số % công suất cung cấp cho lò nhiệt.
- Sử dụng phương pháp điều rộng xung PWM với thông số độ rộng xung từ
BUFF.data[0], chu kỳ xung PWM là 1s.
- Tín hiệu nhiệt độ IW64 được đọc về PC thông qua thanh ghi BUFF.data[6].
- Bài toán điều khiển được thực hiện từ PC.
- Vẽ đồ thị quan sát nhiệt độ đặt và nhiệt đô thực.
Giải thuật:
- Tính sai số: 𝑒𝑘 = 𝑠𝑝 – 𝑦𝑘 ;

Với: 𝑒𝑘 : sai số
𝑠𝑝: nhiệt độ đặt
𝑦𝑘 : nhiệt độ lò nhiệt trả về

- Tính udk theo công thức sau:

34
𝐾𝐼 .𝑇
 𝑢(𝑘 ) = 𝑢(𝑘 − 1) + 𝐾𝑃 (𝑒(𝑘 ) − 𝑒(𝑘 − 1)) + (𝑒(𝑘 ) + 𝑒(𝑘 − 1)) +
2
𝐾𝐷
(𝑒(𝑘 ) − 2𝑒(𝑘 − 1) + 𝑒(𝑘 − 2))
𝑇

- Scale lại tín hiệu udk (từ 0% đến 100% công suất).
- Gửi chế độ điều khiển thông qua thanh ghi BUFF.data[1] (BUFF.data[1] = 1: chế
độ ON/OFF, BUFF.data[1] = 2: chế độ PID).
- Gửi udk xuống PLC thông qua thanh ghi BUFF.data[0].
- PLC thực hiện phát xung PWM để điều khiển lò nhiệt.

Chương trình điều khiển PID từ máy tính:


private void PID(double Kp, double Ki, double Kd, double sp, double T)
{
ek = sp - yk;
uk = uk1 + Kp * (ek - ek1) + Ki * (T / 2) * (ek + ek1) + (Kd / T) * (ek - 2 * ek1 +
ek2);
if (uk < 0)
uk = 0;
else if (uk > 100)
uk = 100;
ek2 = ek1;
ek1 = ek;
}
Thiết lập PLC hoạt động phát xung PWM:
- Dựa vào thanh ghi BUFF.data[1] để xác định chế độ điều khiển (BUFF.data[1] = 1:
chế độ ON/OFF, BUFF.data[1] = 2: chế độ PID)
- Chế độ ON/OFF: nếu udk (nhận được từ máy tính thông qua thanh ghi BUFF.data[0])
bằng 1 thì bật lò nhiệt, bằng 0 thì tắt lò nhiệt.
- Chế độ PID: Tạo xung PWM với chu kỳ T = 1s bằng Timer. Khi thời gian của Timer
nhỏ hơn 𝑢𝑑𝑘 % ∗ 𝑇 thì bật lò nhiệt, thời gian của Timer lớn hơn 𝑢𝑑𝑘 % ∗ 𝑇 thì tắt lò
nhiệt.
35
Chương trình Tia Portal:

Gọi hàm Modbus Server

Đọc tín hiệu Analog và đổi sang tín hiệu số

Chế độ ON/OFF, udk = 1 thì bật lò nhiệt, udk = 0 thì tắt lò nhiệt

36
Chế độ PID, tính 𝑢𝑑𝑘 % ∗ 𝑇 lưu vào MW14

Chế độ PID, bật Timer 1s lặp lại liên tục

Chế độ PID, đưa giá trị đếm tức thời của Timer vào MW200

Chế độ PID, so sánh giá trị Timer với 𝑢𝑑𝑘 % ∗ 𝑇, nếu nhỏ hơn thì bật lò nhiệt…

37
…nếu lớn hơn thì tắt lò nhiệt
Kết quả thí nghiệm:
- Điều khiển ON/OFF:

Hình 1. Đáp ứng hệ thống dùng bộ điều khiển ON/OFF

- Điều khiển PID

38
Hình 2. Đáp ứng hệ thống dùng bộ điều khiển PID

Giải pháp khác có thể thực hiện cho 2 bài toán ở thí nghiệm 2.1 và 2.2:

Như vậy ở bài thí ngiệm số 3, chúng ta đã nắm bắt được cấu trúc phần cứng của hệ thống
điều khiển nhiệt độ và lập trình, kiểm chứng chất lượng điều khiển của 2 bộ ON/OFF và
PID. Tuy nhiên 2 bộ điều khiển ở 2 thí nghiệm này dù đơn giản nhưng đều mắc phải một
số nhược điểm nhất định trong khi đó, việc điều khiển chính xác nhiệt độ với độ vọt lố
nhỏ và sai số xác lập nhỏ là rất cần thiết và quyết định đến chất lượng sản phẩm cần gia
nhiệt. Thứ nhất là bộ ON/OFF: chính cái tên đã cho ta biết được bản chất của bộ điều
khiển này là chỉ có 2 trạng thái điều khiển là ON hoặc là OFF mà thôi, với các giá trị đặt
khác nhau nhiệt độ của lò được điều chỉnh để có thể dao động trong phạm vi sai số nhất
định xung quanh giá trị đặt, tuy nhiên nếu xét trong ứng dụng thực tế thì bộ điều khiển
này rõ ràng không tốt vì nhiệt độ của lò luôn dao động và không ổn định được sẽ ảnh
hưởng đến chất lượng sản phẩm, lấy ví dụ trong các lò nung gạch, nung gốm,… Thứ hai
là bộ điều khiển PID: đây là bộ điều khiển phổ biến được áp dụng rộng rãi trong công

39
nghiệp. Tuy nhiên, theo phương pháp sử dụng ở thí nghiệm 2.2 chúng ta phải tự chỉnh
định hay nois cách khác là “mò” các thông số Kp, Ki, Kd để có được đáp ứng tốt nhất.
Việc này có ý nghĩa về mặt nghiên cứu đối với sinh viên vì giúp các bạn củng cố kiến
thức, cách thức chỉnh định một bộ điều khiển PID, nhưng lại làm mất thời gian khá nhiều
vì mỗi lần lặp lại như vậy ta phải đợi cho lò nguội bớt đi để có thể chỉnh tiếp. Vì vậy
nhóm em xin được đưa ra phương pháp điều khiển cho cả 2 thí nghiệm đó là dùng “BỘ
ĐIỀU KHIỂN THÍCH NGHI PID AUTO – TUNING”. Cấu trúc của bộ điều khiển
này được thể hiện qua hình sau:

Hình 3. Sơ đồ cấu trúc bộ điều khiển PID auto_tuning. Hệ thống sử dụng bộ điều khiển
ON/OFF ở chế độ chỉnh định (D) và bộ PID ở chế độ điều khiển (C)

Hệ thống trên bao gồm 2 khối: khối điều khiển ON/OFF và khối điều khiển PID. Ban đầu
bộ điều khiển ON/OFF sẽ hoạt động để dò tìm độ lợi tới hạn và chu kì tới hạn của hệ
thống. Sau khi xác định được 2 thông số này, hệ thống sẽ tính toán các thông số Kp, Ki,
Kd và chuyển sang chế độ điều khiển PID.

Nguyên lý hoạt động của bộ điều khiển PID auto-tuning như mô tả ở Hình 3.

Ban đầu, hệ thống sử dụng bộ điều khiển ON-OFF để dò tìm độ lợi tới hạn Kc và chu kỳ
tới hạn Tc của hệ thống. Trong giai đoạn từ thời điểm A tới thời điểm B ở Hình 4, các giá
trị Kc và Tc phải được xác định. Sau thời điểm B, các thông số Kp, Ki, Kd được tính toán
theo công thức (3) – (5) và hệ thống sẽ chuyển sang bộ điều khiển PID.

1
PID( s )  K p (1   Td s ) (2)
Ti s
K P  KC cos  M (3)
Ti  Td (4)

40
TC  4 
Td   tan  M   tan 2  M  (5)
4   

Hình 4. Đáp ứng nhiệt độ ngõ ra ở chế độ Hình 5. Giá trị đặt r(t)=700 ở chế độ
điều khiển ON/OFF ON/OFF và r(t)=1000 ở chế độ PID

Yếu tố quyết định đến chất lượng điều khiển của bộ PID auto-tuning là hệ thống phải
chuyển sang bộ điều khiển PID nhanh nhất có thể để thời gian quá độ nhỏ và độ vọt lố
thấp. Hơn nữa, để bộ điều khiển ON-OFF không gây ra vọt lố lớn thì ở thời điểm ban đầu
ta cài đặt r(t) bằng khoản 1/2 giá trị đặt mong muốn. Sau khi hệ thống chuyển sang bộ
điều khiển PID ta mới cài đặt r(t) bằng giá trị đặt mong muốn. Hình 5 minh họa giá trị r(t)
= 70° khi ở chế độ điều khiển ON-OFF và r(t) = 100° khi ở chế độ điều khiển PID
(100°C là giá trị đặt mong muốn). Kết quả, hệ thống không bị vọt lố trên 100° khi ở chế
độ điều khiển ON-OFF và thời gian xác định các giá trị Kc và Tc cũng nhanh hơn.

Khối tính toán thông số PID có thể được lập trình như sau:

Code MATLAB:

function [select,PID, params, cnt] = fcn(e, relay,


params_, cnt_)
d = 0.5;
params = params_;
cnt = cnt_ + 1;
PID = zeros(3,1);
select = 0;

41
T1 = params(1); T2 = params(2); idx = params(3);
emin = params(4); emax = params(5);
if ((relay(1)~=0) && (relay(2)==0)),
idx = idx + 1;
if idx==1,
T1 = cnt;
elseif idx==2,
T2 = cnt;
else
idx = 2;
end
else
if idx==1,
if e<emin,
emin = e;
elseif e>emax
emax = e;
end
elseif idx==2,
Tc = T2 - T1;
M = abs(emax) + abs(emin);
Kc = 2*d/(pi*M/2);
% PID controller
PHIm = pi/4;
alfa = 60;
Td = ( tan(PHIm) + sqrt(4/alfa + tan(PHIm)^2) ) * Tc /
(4*pi);
Ti = alfa*Td;
Kp = Kc*cos(PHIm);

42
Ki = Kp/Ti;
Kd = Kp*Td;
PID(1) = Kc*cos(PHIm);
PID(2) = Kp/Ti;
PID(3) = Kp*Td;
select = 1;
end
end
params(1) = T1; params(2) = T2; params(3) = idx;
params(4) = emin; params(5) = emax;

Giải thích code:


 cnt là biến đếm dùng để xác định thời gian tại điểm A, B, giá trị ban đầu bằng 0,
sau mỗi chu kì lấy mẫu sẽ được cộng thêm 1 tương ứng với thời gian lấy mẫu T =
1s.
 Khi relay đổi từ trạng thái OFF sang ON lần thứ nhất, biến đếm idx = 1, giá trị của
cnt được gán cho T1, tương ứng với thời điểm A.
 Khi relay đổi từ trạng thái OFF sang ON lần thứ hai, biến đếm idx = 2, giá trị của
cnt được gán cho T2, tương ứng với thời điểm B.
 Chu kỳ tới hạn được tính bằng: TC = T2 – T1
 Trong khoảng thời gian từ khi relay đổi trạng thái OFF  ON lần thứ nhất đến lần
thứ hai (khi idx = 1), xác định điểm cao nhất và thấp nhất của đáp ứng (emax và
emin, với e là sai số của đáp ứng so với giá trị đặt), sau đó tính M bằng: M =
(abs(emax) + abs(emin))/2

Bài thí nghiệm số 4

LẬP TRÌNH CARD USB GIAO TIẾP VỚI MÁY TÍNH


43
Thí nghiệm 1: Lập trình vi điều khiển sử dụng Keil-C.
#include <math.h>
#include <stdio.h>
#include <stdint.h>
#include "stm32f3xx_hal.h"
#include "main.h"
#include "driver.h"
#include "usb_device.h"
#include "usbd_custom_hid_if.h"

Tiến hành add các thư viện cần thiết để lập trình.
static uint8_t DI_value;
static float AO_value[2];
static int AI_value[3];
static uint32_t C0_value;
static uint32_t DO_pwm_frequency[3];
static uint16_t Ts_ms =100;//ms
static uint8_t usb_rx_buffer[64];
static uint8_t usb_tx_buffer[17];
static volatile uint8_t usb_tx_flag = 0;
static volatile uint8_t usb_rx_flag = 0;

Khai báo các biến toàn cục.


void USB_RX_Interrupt(void);
void Sample_Timer_Interrupt(void);

Khai báo hai hàm ngắt.


int main(void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
GPIO_Init();

44
AI_Init();
DO_Init();
AO_Init();
Counter_Init();
MX_USB_DEVICE_Init();
Sample_Timer_Init();

Trong hàm “main” đầu tiên ta sẽ khởi tạo các ngoại vi và cấu hình xung clock.
while (1)
{
if(usb_tx_flag)
{
usb_tx_flag =0;
DI_value = DI_Read_All();
C0_value = Counter_Read();
AI_Read_All(AI_value);
/* DIN */
usb_tx_buffer[0] = DI_value;
/* counter */
usb_tx_buffer[1] = (uint8_t)(C0_value >> 24);
usb_tx_buffer[2] = (uint8_t)(C0_value >> 16);
usb_tx_buffer[3] = (uint8_t)(C0_value >> 8);
usb_tx_buffer[4] = (uint8_t)(C0_value);

/* AIN */
usb_tx_buffer[5] = (uint8_t)(AI_value[0] >> 24);
usb_tx_buffer[6] = (uint8_t)(AI_value[0] >> 16);
usb_tx_buffer[7] = (uint8_t)(AI_value[0] >> 8);
usb_tx_buffer[8] = (uint8_t)(AI_value[0]);
usb_tx_buffer[9] = (uint8_t)(AI_value[1] >> 24);
usb_tx_buffer[10] = (uint8_t)(AI_value[1] >> 16);
usb_tx_buffer[11] = (uint8_t)(AI_value[1] >> 8);
usb_tx_buffer[12] = (uint8_t)(AI_value[1]);
usb_tx_buffer[13] = (uint8_t)(AI_value[2] >> 24);

45
usb_tx_buffer[14] = (uint8_t)(AI_value[2] >> 16);
usb_tx_buffer[15] = (uint8_t)(AI_value[2] >> 8);
usb_tx_buffer[16] = (uint8_t)(AI_value[2]);
USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,usb_tx_buffer,17);
}

Sau đó chương trình sẽ vào hàm “while(1)”, Kiểm tra cờ “usb_tx_flag”, nếu bằng 1 thì thì reset lại cờ
về 0 sau đó thiết lập các hàm để truyền đi các giá trị DI, AI, và giá trị của counter.
if (usb_rx_flag)
{
int i;
usb_rx_flag = 0;
switch (usb_rx_buffer[0])/* cmd id */
{
case 'N':/* DO, AO */
{
//DO
DO_Write_All(&usb_rx_buffer[1]);
//AO
AO_value[0] = ((float)usb_rx_buffer[9]*256+(float)usb_rx_buffer[10])/1000;
AO_value[1] = ((float)usb_rx_buffer[11]*256+(float)usb_rx_buffer[12])/1000;
AO_Write_All(AO_value);

Kiểm tra cờ “usb_rx_flag”, nếu bằng 1 thì reset lại cờ về 0 sau đó thiết lập các hàm để nhận.
//reset Counter
if (usb_rx_buffer[13] == 'R')
{
Counter_Reset();
}
break;
}
case 'F':/* pwm frequency */
{
for(i=0;i<3;i++)
{

46
DO_pwm_frequency[i]= (uint32_t)(usb_rx_buffer[i+1]);
}
DO_pwm_set_frequency(DO_pwm_frequency);
break;
}
case 'G':/* ADC 18 bit gain */
{
AI18_Set_Gain(usb_rx_buffer[1]);
break;
}
case 'T':/* sample time */
{
Ts_ms = ((int)usb_rx_buffer[1]<<8) + (int)usb_rx_buffer[2];
Sample_Timer_Set_Period(Ts_ms);
}}}}}

Nếu “buffer[13]” nhận về là kí tự ‘R’ thì reset lại giá trị của counter.
Nếu nhận được kí tự ‘F’ thì thiết lập tần số PWM.
Nếu nhận được kí tự ‘G’ thì thiết lập độ lợi cho ADC 18 bit.
Nếu nhận được kí tự ‘T’ thì thiết lập thời gian lấy mẫu.
void USB_RX_Interrupt(void)
{
int i;
USBD_CUSTOM_HID_HandleTypeDef *myusb=(USBD_CUSTOM_HID_HandleTypeDef
*)hUsbDeviceFS.pClassData;
//myusb->Report_buf[0]= numbers of byte data
for (i=0;i<myusb->Report_buf[0];i++)
{
usb_rx_buffer[i]=myusb->Report_buf[i+1];
}
usb_rx_flag = 1;
}

Hàm ngắt nhận dữ liệu.


void Sample_Timer_Interrupt(void)

47
{
usb_tx_flag =1;
}

Hàm ngắt truyền dữ liệu.


void _Error_Handler(char *file, int line)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
while(1)
{
}
/* USER CODE END Error_Handler_Debug */
}

#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif

48
Thí nghiệm 2: Tạo window form app C#.

Giao diện lập trình trên C#.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using LibUsbDotNet;
using LibUsbDotNet.Info;
using LibUsbDotNet.Main;

Tiến hành add các thư viện cần thiết.


namespace HIF__

49
{
public partial class Form1 : Form
{
#region variable
byte DI_value;
byte[] DO_value = new byte[8];
byte[] DO_text = new byte[8];
double[] PWM_text = new double[8];
double[] F_text = new double[3];
byte[] F_value = new byte[3];
double gain1;
byte G_value;
Int16 Ts_text;
byte[] Ts_value = new byte[2];
//
double AO_0_text;
double AO_1_text;

Int16 AO_0_value;
Int16 AO_1_value;

byte[] AO_0 = new byte[2];


byte[] AO_1 = new byte[2];
//
double AI_0_VALUE;
double AI_1_VALUE;
double AI_2_VALUE;

int ai_0_value;
int ai_1_value;
int ai_2_value;

byte[] AI_0_value = new byte[4];


byte[] AI_1_value = new byte[4];

50
byte[] AI_2_value = new byte[4];
//
byte[] c_value = new byte[4];
int C_value;
//
byte reC0;
//
double pidSetpoint = 0, pidOutput = 0, pidPreError = 0;
double currentPos = 0;
double pidError = 0;
double Kp = 0.071, Ki = 0.017, Kd = 0.000041;
double pPart = 0, iPart = 0, dPart = 0;

bool enablePID = false;

#endregion variable

Khai báo các biến sử dụng truyền và nhận dữ liệu, các biến tính toán điều khiển PID.
public Form1()
{
InitializeComponent();
}

#region Declaring Global Variable


public static UsbDevice myUsbDevice, myUsbDevice_temp;
UsbEndpointReader reader;
UsbEndpointWriter writer;
IAsyncResult ketthuc;

#endregion Declaring Global Variable

Khai báo biến toàn cục cho kết nối USB.


private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (MessageBox.Show("Are you want to exit ?", "Confirmation",
MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{

51
}
else
{
e.Cancel = true;
}
}

private void Form1_FormClosed(object sender, FormClosedEventArgs e)


{
try
{
Usb_exit();
Application.Exit();
}
catch
{

Đoạn chương trình đóng ứng dụng.


private void Form1_Load(object sender, EventArgs e)
{
timer1.Interval = 200;
timer1.Enabled = false;
G_value = 1;
gain1 = 1;
Ts_value[0] = 0;
Ts_value[1] = 10;
Ts.Text = "10";
Ts_text = 10;
F_value[0] = 2;
F0.Text = "2";

52
F_value[1] = 2;
F1.Text = "2";
F_value[2] = 2;
F2.Text = "2";
cbEnablePID.Checked = false;
}

Config Timer và giá trị ban đầu của các Textbox.


private void btnconnect_Click(object sender, EventArgs e)
{
if (btnconnect.Text == "Connect")
{
if (myUsbDevice == null)
{
UsbRegDeviceList allDevices = UsbDevice.AllDevices;
foreach (UsbRegistry usbRegistry in allDevices)
{
if (usbRegistry.Open(out myUsbDevice))
{
txtproductname.Text = myUsbDevice.Info.ProductString;
txtvendorid.Text =
myUsbDevice.Info.Descriptor.VendorID.ToString();
txtproductid.Text =
myUsbDevice.Info.Descriptor.ProductID.ToString();
txtmanufacturer.Text = myUsbDevice.Info.ManufacturerString;
USB_DATA_RECEIVER_INIT();
btnconnection.Text = "Disconnect";
timer1.Enabled = true;
}
}
}
if (myUsbDevice == null)
{
MessageBox.Show("Device Not Found !!!", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}

53
}
else
{
Usb_exit();
btnconnection.Text = "Connect";
txtproductname.Text = "";
txtvendorid.Text = "";
txtproductid.Text = "";
txtmanufacturer.Text = "";
textBox1.Text = "";
textBox2.Text = "";

textBox4.Text = "";

textBox7.Text = "";
textBox8.Text = "";
txt_AI_0.Text = "";
txt_AI_1.Text = "";
txt_Counter_0.Text = "";

Chương trình cho nút nhấn “Connect”.


Nếu text đang là chữ connect thì sau khi nhấn sẽ kết nối đến cổng usb. Nếu không tìm
thấy thiết bị sẽ thông báo “Device Not Found!!!”. Sau khi nhấn connect thì text sẽ
chuyển sang chữ disconnect.
Ngược lại khi nhấn nút mà text đang là chữ disconnect thì sẽ ngắt kết nối với thiết bị và
text chuyển lại sang chữ connect.
#region USB_DATA_RECEIVER_INIT
void USB_DATA_RECEIVER_INIT()
{
IUsbDevice wholeUsbDevice = myUsbDevice as IUsbDevice;

54
if (!ReferenceEquals(wholeUsbDevice, null))
{
wholeUsbDevice.SetConfiguration(1);
wholeUsbDevice.ClaimInterface(0);
}
//Open usb end point reader and writer
reader = myUsbDevice.OpenEndpointReader(ReadEndpointID.Ep01);
writer = myUsbDevice.OpenEndpointWriter(WriteEndpointID.Ep01);

//Set Interrupt service rountie for reader complete event


reader.DataReceived += (OnRxEndPointData);
reader.DataReceivedEnabled = true;
}
#endregion USB_DATA_RECEIVER_INIT

Khởi tạo nhận dữ liệu qua cổng USB.


#region USB EXIT
private void Usb_exit()
{
reader.DataReceivedEnabled = false;
reader.DataReceived -= (OnRxEndPointData);
//this.EndInvoke(ketthuc);
reader.Dispose();
writer.Dispose();
if (myUsbDevice != null)
{
if (myUsbDevice.IsOpen)
{
IUsbDevice wholeUsbDevice = myUsbDevice as IUsbDevice;
if (!ReferenceEquals(wholeUsbDevice, null))
{
wholeUsbDevice.ReleaseInterface(0);
}
myUsbDevice.Close();

55
}
myUsbDevice = null;
UsbDevice.Exit();
}
}
#endregion USB EXIT

Thiết lập kết nối đóng mở.


#region USB DATA RECEIVER INTERRUPT SERVICE ROUNTIE
Action<byte[]> UsbReceiverAction;
private void OnRxEndPointData(object sender, EndpointDataEventArgs e)
{

UsbReceiverAction = UsbReceiverActionFunction;
if ((myUsbDevice.IsOpen) && (reader != null))
{
ketthuc = this.BeginInvoke(UsbReceiverAction, e.Buffer);
}
}

Hàm ngắt nhận.


#region PWMtext
private void PWM0_CheckedChanged(object sender, EventArgs e)
{
if (textBox1.Text == "")
{
DO_text[0] = 0;
}
}
private void PWM1_CheckedChanged(object sender, EventArgs e)
{
if (textBox2.Text == "")
{
DO_text[1] = 0;
}

56
}

private void PWM3_CheckedChanged(object sender, EventArgs e)


{
if (textBox4.Text == "")
{
DO_text[3] = 0;
}
}

private void PWM6_CheckedChanged(object sender, EventArgs e)


{
if (textBox7.Text == "")
{
DO_text[6] = 0;
}
}

private void PWM7_CheckedChanged(object sender, EventArgs e)


{
if (textBox8.Text == "")
{
DO_text[7] = 0;
}
}

Hàm viết cho việc check vào ô các checkbox PWM.


private void textBox1_TextChanged(object sender, EventArgs e)
{
if (textBox1.Text == "")
{
DO_text[0] = 0;
}
else

57
PWM_text[0] = (Int16)double.Parse(textBox1.Text);
if (PWM_text[0] < 0 || PWM_text[0] > 100)
{
MessageBox.Show("Ngoài tầm, hãy nhập giá trị từ 0 đến 100",
"Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
DO_text[0] = (byte)(PWM_text[0]);
}
}
}

private void textBox2_TextChanged(object sender, EventArgs e)


{
if (textBox2.Text == "")
{
DO_text[1] = 0;
}
else
{
PWM_text[1] = (Int16)double.Parse(textBox2.Text);
if (PWM_text[1] < 0 || PWM_text[1] > 100)
{
MessageBox.Show("Ngoài tầm, hãy nhập giá trị từ 0 đến 100",
"Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
DO_text[1] = (byte)(PWM_text[1]);
}
}
}

58
private void textBox4_TextChanged(object sender, EventArgs e)
{
if (textBox4.Text == "")
{
DO_text[3] = 0;
}
else
{
PWM_text[3] = (Int16)double.Parse(textBox4.Text);
if (PWM_text[3] < 0 || PWM_text[3] > 100)
{
MessageBox.Show("Ngoài tầm, hãy nhập giá trị từ 0 đến 100",
"Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
DO_text[3] = (byte)(PWM_text[3]);
}
}
}

private void textBox7_TextChanged(object sender, EventArgs e)


{
if (textBox7.Text == "")
{
DO_text[6] = 0;
}
else
{
PWM_text[6] = (Int16)double.Parse(textBox7.Text);
if (PWM_text[6] < 0 || PWM_text[6] > 100)
{
MessageBox.Show("Ngoài tầm, hãy nhập giá trị từ 0 đến 100",
"Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}

59
else
{
DO_text[6] = (byte)(PWM_text[6]);
}
}
}

private void textBox8_TextChanged(object sender, EventArgs e)


{
if (textBox8.Text == "")
{
DO_text[7] = 0;
}
else
{
PWM_text[7] = (Int16)double.Parse(textBox8.Text);
if (PWM_text[7] < 0 || PWM_text[7] > 100)
{
MessageBox.Show("Ngoài tầm, hãy nhập giá trị từ 0 đến 100",
"Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
DO_text[7] = (byte)(PWM_text[7]);
}
}
}

#endregion PWMtext

Hàm thiết lập duty cho các kênh PWM. Giá trị nhập vào từ 0 đến 100 tương ứng duty
cycle từ 0 đến 100. Nếu nhập số khác ngoài vùng từ 0 đến 100 sẽ báo lỗi và yêu cầu
nhập lại.
Check vào ô checkbox của PWM tương ứng và thay đổi thông số duty sẽ làm thay đổi
độ sáng đèn trên USBcard.
#region Frequency

60
private void F0_TextChanged(object sender, EventArgs e)
{
if (F0.Text == "")
{
MessageBox.Show("Hãy nhập giá trị từ 1 đến 255", "Warning",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
F_text[0] = (Int16)double.Parse(F0.Text);
if (F_text[0] < 1 || F_text[0] > 240)
{
MessageBox.Show("Ngoài tầm, hãy nhập giá trị từ 1 đến 255", "Warning",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
F_value[0] = (byte)(F_text[0]);
}
}
}
private void F1_TextChanged(object sender, EventArgs e)
{
if (F1.Text == "")
{
MessageBox.Show("Hãy nhập giá trị từ 1 đến 255", "Warning",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
F_text[1] = (Int16)double.Parse(F1.Text);
if (F_text[1] < 1 || F_text[1] > 240)
{
MessageBox.Show("Ngoài tầm, hãy nhập giá trị từ 1 đến 255",
"Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}

61
else
{
F_value[1] = (byte)(F_text[1]);
}
}
}
private void F2_TextChanged(object sender, EventArgs e)
{
if (F2.Text == "")
{
MessageBox.Show("Hãy nhập giá trị từ 1 đến 255", "Warning",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
F_text[2] = (Int16)double.Parse(F2.Text);
if (F_text[2] < 1 || F_text[2] > 240)
{
MessageBox.Show("Ngoài tầm, hãy nhập giá trị từ 1 đến 255",
"Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
F_value[2] = (byte)(F_text[2]);
}
}

}
#endregion Frequence

Hàm thiết lập giá trị tần số. Giá trị nhập vào là một số 8 bit như đã khai báo nên chỉ
được nhập từ 1 đến 255.
private void Gain1_SelectedIndexChanged(object sender, EventArgs e)
{
switch (Gain1.Text)

62
case "x1":
gain1 = 1;
G_value = 0x9c;
break;
case "x2":
gain1 = 2;
G_value = 0x9d;
break;
case "x4":
gain1 = 4;
G_value = 0x9e;
break;
}
byte[] data_send = { 2, 71, G_value }; //G
try
{
int bytesWritten;
writer.Write(data_send, 1000, out bytesWritten);

}
catch (Exception err)
{
MessageBox.Show("Can Not Send Data To USB Device\nDetails: " + err,
"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

Hàm sự kiện thiết lập độ lợi cho AI.


private void btnTs_Click(object sender, EventArgs e)
{
byte[] data_send = { 3, 84, Ts_value[0], Ts_value[1] };
try
{
int bytesWritten;
writer.Write(data_send, 1000, out bytesWritten);

63
}
catch (Exception err)
{
MessageBox.Show("Can Not Send Data To USB Device\nDetails: " + err,
"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}

Hàm ngắt cho sự kiện xử lí nút nhấn “Config Ts”.


private void done_Click(object sender, EventArgs e)
{
byte[] data_send = { 4, 70, F_value[0], F_value[1], F_value[2] }; //F
try
{
int bytesWritten;
writer.Write(data_send, 1000, out bytesWritten);

}
catch (Exception err)
{
MessageBox.Show("Can Not Send Data To USB Device\nDetails: " + err,
"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

Hàm ngắt cho sự kiện xử lí nút nhấn “Config Frequency”.


private void timer1_Tick(object sender, EventArgs e)
{
#region Case Check

if (checkBox1.Checked & checkBox1.Enabled)


{
if (PWM0.Checked)
DO_value[0] = DO_text[0];
else

64
DO_value[0] = 100;
}
else if (checkBox1.Enabled)
DO_value[0] = 0;

if (checkBox2.Checked & checkBox2.Enabled)


{
if (PWM1.Checked)
DO_value[1] = DO_text[1];
else
DO_value[1] = 100;
}
else if (checkBox2.Enabled)
DO_value[1] = 0;

if (checkBox3.Checked & checkBox3.Enabled)


{
DO_value[2] = 1;
}
else if (checkBox3.Enabled)
DO_value[2] = 0;

if (checkBox4.Checked & checkBox4.Enabled)


{
if (PWM3.Checked)
DO_value[3] = DO_text[3];
else
DO_value[3] = 100;
}
else if (checkBox4.Enabled)
DO_value[3] = 0;

if (checkBox5.Checked & checkBox5.Enabled)


{

65
DO_value[4] = 1;
}
else if (checkBox5.Enabled)
DO_value[4] = 0;

if (checkBox6.Checked & checkBox6.Enabled)


{

DO_value[5] = 1;
}
else if (checkBox6.Enabled)
DO_value[5] = 0;

if (checkBox7.Checked & checkBox7.Enabled)


{
if (PWM6.Checked)
DO_value[6] = DO_text[6];
else
DO_value[6] = 100;
}
else if (checkBox7.Enabled)
DO_value[6] = 0;

if (checkBox8.Checked & checkBox8.Enabled)


{
if (PWM7.Checked)
DO_value[7] = DO_text[7];
else
DO_value[7] = 100;
}
else if (checkBox8.Enabled)
DO_value[7] = 0;

66
#endregion Case Check

Trong hàm ngắt của timer ta viết chương trình để cập nhật giá trị các DO (DO0 – DO7).
Ứng với checkbox được tick mà lệnh if được thỏa mãn và cập nhật giá trị cho DO tương
ứng.
Thí nghiệm 3: Điều khiển vị trí động cơ bằng bộ điều khiển PID.

Tiếp tục window form app C# ở thí nghiệm 2. Thêm các chương trình cần thiết để điều
khiển vị trí động cơ bằng bộ điều khiển PID.
private void resetC0_Click(object sender, EventArgs e)
{
reC0 = 82;//R
currentPos = 0;
}

private void btnSetpoint_Click(object sender, EventArgs e)


{
pidSetpoint = Convert.ToDouble(txtPIDSetpoint.Text);
Kp = double.Parse(textBox10.Text);
Ki = double.Parse(textBox11.Text);
Kd = double.Parse(textBox12.Text);
}

Hàm ngắt cho sự kiện nút nhấn “reset” và “set”. Khi nhấn nút “reset” thì giá trị của ô
“txt_Counter_0” sẽ về 0 tương ứng vị trí ban đầu khi chưa đếm được xung nào. Khi
nhập giá trị vào ô “txtPIDSetpoint” và nhấn nút “set” thì vị trí cài đặt sẽ được cập nhật.
private void UsbReceiverActionFunction(byte[] input)
{
//di_value = String.Concat((char)input[0], (char)input[1], (char)input[2]);
DI_value = input[0];
c_value[3] = input[1];
c_value[2] = input[2];
c_value[1] = input[3];
c_value[0] = input[4];
C_value = BitConverter.ToInt32(c_value, 0);

if (C_value > 32767)

67
C_value = C_value - 65536;
currentPos = C_value;
textBox9.Text = currentPos.ToString() + ',' + pidOutput.ToString();
// calculate pid
if (enablePID)
{
pidError = pidSetpoint - currentPos;
pPart = Kp * pidError;
iPart += 0.5*Ki * (pidError+ pidPreError) * Ts_text / 1000;
dPart = Kd * (pidError - pidPreError)/((double)Ts_text / 1000);
pidPreError = pidError;
if (iPart > 90)
iPart = 90;
else if (iPart < -90)
iPart = -90;
pidOutput = pPart + iPart + dPart;
if (pidOutput > 90)
pidOutput = 90;
else if (pidOutput < -90)
pidOutput = -90;
// send cmd
byte[] data_send = { 14, 78, 0, 0, DO_value[2], DO_value[3], DO_value[4],
DO_value[5], DO_value[6], DO_value[7], AO_0[0], AO_0[1], AO_1[0], AO_1[1], reC0 };
try
{
int bytesWritten;
if (pidOutput > 0)
{
data_send[3] = (byte)pidOutput;
}
else
{
pidOutput = -pidOutput;
data_send[2] = (byte)pidOutput;

68
writer.Write(data_send, 1000, out bytesWritten);

}
catch (Exception err)
{
MessageBox.Show("Can Not Send Data To USB Device\nDetails: " + err,
"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
reC0 = 83; //S
}

//
AI_0_value[3] = input[5];
AI_0_value[2] = input[6];
AI_0_value[1] = input[7];
AI_0_value[0] = input[8];
AI_1_value[3] = input[9];
AI_1_value[2] = input[10];
AI_1_value[1] = input[11];
AI_1_value[0] = input[12];
AI_2_value[3] = input[13];
AI_2_value[2] = input[14];
AI_2_value[1] = input[15];
AI_2_value[0] = input[16];
ai_0_value = BitConverter.ToInt32(AI_0_value, 0);
ai_1_value = BitConverter.ToInt32(AI_1_value, 0);
ai_2_value = BitConverter.ToInt32(AI_2_value, 0);

AI_0_VALUE = (Convert.ToDouble(ai_0_value) / 4095)*3.3;


AI_1_VALUE = (Convert.ToDouble(ai_1_value) / 4095)*3.3;
AI_2_VALUE = (Convert.ToDouble(ai_2_value) / (gain1 * 131072)) * 2.048;
/****************************************/
#region DI
if (Convert.ToBoolean(DI_value & 0x01))

69
oval_DI_1.FillColor = Color.Red;
}
else
{
oval_DI_1.FillColor = Color.White;
}
//
if (Convert.ToBoolean(DI_value >> 1 & 0x01))
{
oval_DI_2.FillColor = Color.Red;
}
else
{
oval_DI_2.FillColor = Color.White;
}
//
if (Convert.ToBoolean(DI_value >> 2 & 0x01))
{
oval_DI_3.FillColor = Color.Red;
}
else
{
oval_DI_3.FillColor = Color.White;
}
//
if (Convert.ToBoolean(DI_value >> 3 & 0x01))
{
oval_DI_4.FillColor = Color.Red;
}
else
{
oval_DI_4.FillColor = Color.White;
}
//

70
if (Convert.ToBoolean(DI_value >> 4 & 0x01))
{
oval_DI_5.FillColor = Color.Red;
}
else
{
oval_DI_5.FillColor = Color.White;
}
//
if (Convert.ToBoolean(DI_value >> 5 & 0x01))
{
oval_DI_6.FillColor = Color.Red;
}
else
{
oval_DI_6.FillColor = Color.White;
}
//
if (Convert.ToBoolean(DI_value >> 6 & 0x01))
{
oval_DI_7.FillColor = Color.Red;
}
else
{
oval_DI_7.FillColor = Color.White;
}
//
if (Convert.ToBoolean(DI_value >> 7 & 0x01))
{
oval_DI_8.FillColor = Color.Red;
}
else
{
oval_DI_8.FillColor = Color.White;

71
}
#endregion DI

txt_AI_0.Text = AI_0_VALUE.ToString("0.000");
txt_AI_1.Text = AI_1_VALUE.ToString("0.000");
txt_AI_2.Text = AI_2_VALUE.ToString("0.000");

txt_Counter_0.Text = C_value.ToString();
}

#endregion USB DATA RECEIVER INTERRUPT SERVICE ROUNTIE


}
}

Đoạn chương trình tính PID, màu sắc cho các ovalshape, hiện thị value trên textbox
AI0 - AI2.
KẾT QUẢ THÍ NGHIỆM:

 Kết nối thiết bị:

72
 Thay đổi giá trị AI0 (bằng cách chỉnh biến trở VR1), cho quay động cơ với
PWM = 30%

 Kiểm tra hoạt động DO

73
 Thay đổi thời gian lấy mẫu lần lượt là 1000ms, 10ms

Nhận xét: Thời gian lấy mẫu tăng tốc độ thay đổi AIN chậm dần.

 Tính toán PID:


Sai số vị trí được tính bằng cách lấy giá trị “setpoint” cài đặt trừ cho giá trị hiện tại
“currentpoint”.
Gọi u là điện áp mà cấp cho động cơ để quay đến vị trí yêu cầu, với yêu cầu là nhanh, chính
xác và ổn định. Gọi e là sai số vị trí giữa giá trị đặt và giá trị hiện tại, trên thực tế nếu sai
số càng lớn thì chúng ta phải cần một điện áp u lớn để động cơ nhanh chóng quay đến vị
trí đặt. Và ta có công thức:
u  KP  e
Trong đó Kp là một hằng số dương P (Propotional gain), e là sai số, mục đích là đưa vị trí
động cơ đến điểm đặt càng nhanh càng tốt, thực tế nếu ta tăng Kp càng lớn thì u càng lớn
hay động cơ quay càng nhanh. Tuy nhiên thực tế nếu lực u quá lớn sẽ làm cho gia tốc của
trục động cơ lớn, do đó khi động cơ quay đến vị trí đặt vì quán tính lớn mặc dù u=0 thì
động cơ vẫn dịch chuyển ra khỏi vị trí đặt một khoảng nào đó, giá trị sai số lúc này gọi là
overshot. Giá trị overshot xuất hiện đồng nghĩa lại xuất hiện sai số e nhưng lúc này nó có
giá trị âm, tạo ra u ngược chiều với u ban đầu kéo kéo động cơ lại vị trí đặt. Nhưng lại một
lần nữa, giá trị Kp lớn nên lực u cũng lớn và lại đẩy vị trí động cơ lệch khỏi vị trí mong
muốn. Quá trình cứ tiếp diển xe cứ dao động mãi quanh điểm đặt.

74
Mặt khác, tốc độ thay đổi của e có thể tính bằng đạo hàm của biến này theo thời gian. Như
vậy, khi động cơ quay từ vị trí hiện tại đến vị trí đặt, đạo hàm sai số e tăng giá trị nhưng
ngược chiều với u. Nếu sử dụng đạo hàm làm thành phần thắng thì có thể giảm overshot.
Vậy ta sẽ có bộ điều khiển như sau:
de
u  KP e  KD
dt
Trong đó, (de/dt) là vận tốc thay đổi của sai số e là Kd là hằng số không âm gọi là hệ số D
(Derivative gain). D giúp làm giảm dao dộng cảu đáp ứng quanh vị trí đặt.
Giả sử, thành phần D lớn hơn rất nhiều so với thành phần P hoặc bản thân thành phần P rất
nhỏ, khi đó động cơ chưa thật sự đến vị trí đặt thì đã dừng hẳn, thành phần D bằng 0. Động
cơ sẽ dừng hẳn vì thành phần P và sai số e lúc này quá nhỏ không thể thắng được ma sát
tĩnh, sai số e lúc này được gọi là sai số tĩnh steady state error. Để tránh sai số tĩnh người ta
thêm vào bộ điều khiển một thành phần có chức năng cộng dồn sai số. Khi trạng thái tĩnh
xảy ra, hai thành phần P và D mất tác dụng, thành phần cộng dần sai số sẽ làm tăng u theo
thời gian, đến một lúc nào đó u đủ mạnh để thắng ma sát tĩnh thì động cơ sẽ tiếp tục quay
dần đến vị trí đặt. Thành phần cộng dồn sai số chính là thành phần I trong bộ điều khiển
PID. Nó được tính bằng tích phân của e theo thời gian. Đến đây ta có bộ điều khiển PID
đầy đủ như sau:
de
u  K P  e  K I   e  dt  KD
dt
Thực tế ta rời rạc hóa công thức PID và dùng PID rời rạc để lập trình.
pidError = pidSetpoint - currentPos;
pPart = Kp * pidError;
iPart += 0.5*Ki * (pidError+ pidPreError) * Ts_text / 1000;
dPart = Kd * (pidError - pidPreError)/((double)Ts_text / 1000);
pidPreError = pidError;

pidOutput = pPart + iPart + dPart;

Cách 2:
double pidError_2, pidError_1;
double pidOutput_1;

pidError_2 = pidError_1;
pidError_1 = pidError;
pidError = pidSetpoint - currentPos;
pidOutput_1 = pidOutput;

75
pidOutput = pidOutput_1 + Kp * (pidError - pidError_1) +
Ki * Ts_text / 2000 * (pidError + pidError_1) + Kd / Ts_text / 1000 * (pidError - 2 *
pidError_1 + pidError_2);

 Dò thông số PID:
 Ban đầu cho Ki và Kd bằng 0. Chỉnh Kp tăng dần đến khi đáp ứng dao động
tuần hoàn quanh giá trị mong muốn.
 Đặt Ki bằng với chu kỳ dao động.
 Điều chỉnh Kp lại cho phù hợp.
 Nếu có vọt lố thì điều chỉnh giá trị Kd.

 Kết quả bộ điều khiển PID với các Setpoint khác nhau:

Ngõ ra và udk tại thời


điểm chụp màn hình
(giá trị ngõ ra có thể
tiến tới xác lập với sai
số bằng 0 nhưng cần
một khoảng thời gian)

76
 Bổ sung thiết kế giao diện:

Phần nhập thông số


PID để thuận tiện
cho việc chỉnh định
(nếu không nhập thì
chạy mặc định với
các giá trị khởi tạo:

double Kp = 0.071, Ki
= 0.017, Kd =
Quan sát tín hiệu
0.000041;)
ngõ ra và tín hiệu
điều khiển.

77

You might also like