Thursday, January 29, 2015

Tính toán cơ bản trong lập trình nhúng

Trong bài này mình sẽ giới thiệu hầu hết các thuật toán cơ bản nhất trong lập trình C (nhúng) mà mình biết,hầu hết là các thuật toán xử lý BIT,thanh ghi.Các thuật toán này hết sức cơ bản,nhiều bạn đã thành thạo rồi,tuy nhiên nó rất quan trọng và được sử dụng thường xuyên,nhưng lại thường bị mọi người bỏ qua.
Mình sẽ lấy ví dụ luôn với các thanh ghi trong module Digital I/O của MSP430 để các bạn tiện hình dung.

1.Các phép toán thông dụng

Mình chỉ nêu ra các phép toán rất hay dùng trong lập trình nhúng,các bạn tự tìm hiểu ý nghĩa vì nó rất cơ bản:
& , | , ^ , ! , ~ , << , >> , != , && , || , ++ , -- , (biểu thức điều kiện) a ? b : c , (viết tắt) ví dụ ^= ; |= 

2.Phép gán 1 từng bit : P1OUT |= (BIT1 + BIT2);

Phép gán này dùng khi bạn chỉ muốn set 1 bit trong thanh ghi lên 1,các bit còn lại của thanh ghi vẫn giữ nguyên giá trị.Đây là cách viết tắt,để tiết kiệm thời gian,cú pháp đầy đủ là : 

P1OUT |= BIT1;
<=> P1OUT = P1OUT | (0b000000010);

Như vậy dễ dàng nhận thấy BIT1 của P1OUT được đưa lên 1,các bit còn lại giữ nguyên giá trị.

Có thể gán 1 hoặc nhiều bit ở vị trí tùy ý.
P1OUT |= (BIT1 + BIT2);
<=> P1OUT = P1OUT | (0b000000110);

Như vậy dễ dàng nhận thấy BIT1 và BIT2 của P1OUT được đưa lên 1,các bit còn lại giữ nguyên giá trị.

3.Phép gán 0 từng bit : P1OUT &= ~(BIT1 + BIT2);

Tương tự như phép gán 1 từng bit.BIT 1 và BIT2 sẽ bị đưa về 0 , các bit còn lại trong thanh ghi P1OUT được giữ nguyên.

4.Phép đảo bit : P1OUT ^= (BIT1 + BIT2);

Phép toán này cũng tương tự như 2 phép toán trên , chỉ đổi mức logic với các bit có ở bên phải dấu bằng.Tuy nhiên có một mẹo nhỏ với phép toán này giúp giảm số dòng code,đó là khi bạn thực hiện các vòng lặp chỉ đảo dấu,ví dụ khi nháy led chẳng hạn.
Thay vì viết : 
while(1){ P1OUT |= BIT1; _delay_cycles(1000);P1OUT &= ~BIT1; _delay_cycles(1000);}

Thì chúng ta viết : { while(1) P1OUT ^= BIT1; _delay_cycles(1000); }

5.Đọc giá trị logic của 1 bit :  x = P1IN & (BIT1); 

Phép toán dễ nhận thấy nhất khi bạn đọc bàn phím.Giá trị x trả về chính là mức logic của BIT1,cho biết nút đã được bấm hay chưa.VD : if ( P1IN & BIT1 ) ...

6.Bắt xườn của 1 tín hiệu :

Giả sử bạn có một biến(hoặc tín hiệu) x,và một chương trình con A trong vòng while(1).Bạn chỉ muốn chạy chương trình A khi x từ 0 -> 1.Kịch bản cũng giống như bạn chạy chương trình trong ngắt xườn lên của 1 chân GPIO.
Trước hết phải chắc chắn rằng biến x được cập nhật giá trị liên tục.Tạo 1 biến x_past để lưu giá trị lần cuối cùng đọc x.Thuật toán ở đây là nếu (x==1) && (x_past==0) thì có nghĩa là có 1 xung sườn lên của x và cho phép thực hiện chương trình A.

Tạo biến toàn cục : unsigned int x,x_past;
macro: 
{
        if((x==1)&&(x_past==0))return 1;
        else return 0;
        x_past = x;
}

7.Tính toán với số thực
Tốt nhất là hạn chế tối đa việc tính toán với các giá trị kiểu float,vì CPU sẽ mất rất nhiều chu kỳ để có thể tính được 1 phép toán.Ngay cả việc tính toán với biến kiểu long cũng nên hạn chế nếu VĐK mà bạn sử dụng không hỗ trợ phép nhân 32 bit.Vì khi đó nó sẽ phải chia nhỏ phép nhân ra để tính từng phần một,rất mất thời gian và tốn tài nguyên.
Ví dụ nếu phải tính toán với số thực . 
int x;
 x  = x *2.5;

Thay vì viết : x = x*2.5;
Chúng ta viết :  x = x * 5/2;
Chỉ một thay đổi nhỏ thôi nhưng chương trình được tối ưu hóa lên rất nhiều.
Và một chú ý nữa là giới hạn của phép tinh:
Ví dụ: Nhân a= 100 với phân số 1245/1024 ,
Thay vì nhân trực tiếp : a = a*945/4096
Chúng ta chia nhỏ phép tính ra thành các phần tử nhỏ,vừa tránh phải nhân chia 32 bit,vừa tránh tràn khi nhân a với 1249.
Giải pháp có thể là : a = a*64/35*64/27

8.Thuật toán mặt nạ

Chúng ta sử dụng thuật toán mặt nạ khi muốn truyền giá trị vào một vài bit trong 1 thanh ghi,thường dùng nhất là khi quét led hoặc điều khiển port data của LCD16x2 ở chế độ 4 bit,khi chúng ta không muốn ảnh hường đến các chân khác trong 1 cổng.
Ví dụ chúng ta có biến unsigned char a  (0<a<16) ,cần gán giá trị của a vào cổng P1OUT từ BIT1 đến BIT4 . Giả sử a=3 ,P1OUT = 0x21 thì sau khi thực hiện P1OUT = 0x27.
Trước tiên chúng ta cần đọc giá trị các bit không cần tác động của P1OUT ,gán vào biến Temp.
Temp = 0xE1 & P1OUT;
P1OUT = (a<<1) & Temp ;

9.Thuật toán Random

Đôi khi trong lúc lập trình,các bạn cần tạo ra một số ngẫu nhiên,giả sử như trong khi test chương trình chằng hạn,hoặc giả lập nhiễu trên vđk,tạo chùm sáng ngẫu nhiên trong ma trận led trang trí ... thì chúng ta cần hàm ngẫu nhiêu.Thực chất thuật toán Random là tất định,tức là có thể đoán trước được.Trong một số trình biên dịch sẽ hỗ trợ sẵn hàm Rand() như AVRStudio chẳng hạn,tuy nhiêu CCS của MSP430 thì không được hỗ trợ.Thực ra viết hàm Rand() riêng cho vi điều khiển sẽ dễ dàng và tốn ít tài nguyên hơn hàm mặc định của C.Vì chúng ta có thể dễ dàng tìm được nguồn tạo số ngẫu nhiên trên VĐK,ví dụ giá trị ADC của một kênh để treo,tức là không nối gì :v hoặc thời gian chờ một nút nhấn nào đó.
Thuật toán Random là tối ưu khi nó có thể cho ra mọi giá trị trong khoảng cho trước với xác suất bằng nhau.
Còn tiếp !!!!

No comments:

Post a Comment

END COMMENT FACEBOOK-->