Kỹ thuật bảng

0) Kiến thức cần biết Tập lệnh Bộ nhớ dữ liệu và bộ nhớ chương trình (Kiến trúc Harvard) Con trỏ chương trình Tổ chức bộ nhớ của PIC (các BANK) Khái niệm Stack

1) Giới thiệu về kỹ thuật bảng Để hiểu về kỹ thuật bảng, trước tiên chúng ta xem ví dụ sau: Giả sử chúng ta cần xuất dữ liệu ra màn hình LCD với dãy chữ : “PIC_Tutorial”. Ở đây, chúng ta không cần quan tâm đến hoạt động của LCD và hình thức xuất dữ liệu như thế nào. Chỉ cần các bạn nắm được rằng, để xuất dữ liệu như trên ra, chúng ta phải xuất tuần tự các ký tự “P, I, C, _, T, u, t, o, r, i, a, l” ra. Vậy làm thế nào để xuất được các chữ này ra? Ở đây có hai vấn đề, vấn đề thứ nhất là làm sao lưu trữ được các giá trị này, vấn đề thứ hai là làm sao sắp xếp thứ tự các chữ cái này để chúng ta gọi chúng ra một cách tuần tự. Trước tiên, chúng ta giải quyết vấn đề thứ hai trước, bởi vì nó rất đơn giản. Cách giải quyết là thay vì chúng ta phải tìm các chữ cái trong bảng chữ cái (cả chữ in lẫn chữ thường) để gọi ra vào đúng thời điểm cần xuất các chữ cái này ra, chúng ta sẽ đánh dấu các chữ cái này với các chỉ số, và khi gọi thì chúng ta chỉ gọi chỉ số đầu tiên là 0 (hoặc 1, tuỳ theo sự quy định), sau đó, chúng ta cứ tăng chỉ số này lên 1 đơn vị, và gọi tiếp chữ cái tiếp theo. Công việc này sẽ dừng lại khi nó đạt đến chỉ số cuối cùng. Tiếp theo, đề giải quyết vấn đề lưu trữ các chữ cái này ở đâu, các bạn cần biết và hiểu rõ khái niệm về bộ nhớ dữ liệu và bộ nhớ chương trình (hay bộ nhớ lệnh). Các bạn có thể tham khảo phần bộ nhớ dữ liệu và bộ nhớ chương trình trong phần kiến trúc Harvard và kiến trúc Von Neumann. Tuy nhiên, chúng ta sẽ nhắc lại rằng, đối với PIC dòng Mid Range, bộ nhớ chương trình có 14 bit cho mỗi lệnh, vì vậy, cho dù các bạn thực hiện lệnh nào đi nữa, thì lệnh đó luôn chiếm 14 bit trong bộ nhớ chương trình. Mặt khác, bộ nhớ dữ liệu thì lại bị giới hạn và một lần tương tác với bộ nhớ dữ liệu, các bạn phải thông qua thanh ghi W, như vậy, các bạn tốn thêm ít nhất 2 lệnh cho việc tương tác với thanh ghi W. Kết quả, cách thông minh nhất là lưu các dữ liệu đó vào trong bộ nhớ chương trình, thay vì lưu nó vào trong bộ nhớ dữ liệu. Lại nhắc lại về tập lệnh, nếu các bạn để ý kỹ, các bạn sẽ lại thấy rằng, có một số lệnh cho phép tương tác với giá trị k (8 bit), và giá trị k này không lưu trữ trong bộ nhớ dữ liệu, mà lưu trong bộ nhớ chương trình.

Tóm lại, kỹ thuật bảng là kỹ thuật lập trình để truy xuất dữ liệu một cách có thứ tự (thứ tự hiểu theo nghĩa rộng là một quy luật truy xuất nào đó), và khi các dữ liệu đó là hằng số, thì kỹ thuật này cho phép chúng ta lưu trữ các dữ liệu đó trong bộ nhớ chương trình, không làm tốn kém bộ nhớ dữ liệu, và việc truy xuất được thực hiện một cách nhanh nhất. 2) Cách xây dựng bảng Từ ý tưởng này, có thể có rất nhiều cách lập trình truy xuất dữ liệu bảng. Tuy nhiên, do sự giới hạn của tài liệu này, chúng tôi chỉ trình bày kỹ thuật bảng tiêu biểu nhất, và cũng tốt nhất, đồng thời sử dụng tài liệu application note AN556 của Microchip như một tài liệu tham khảo chính. Dưới đây là một đoạn code điển hình sử dụng kỹ thuật bảng trong AN556

Trong ví dụ này, chúng ta sẽ phân tích và thấy rằng, từ nhãn [Table] con trỏ chương trình được cộng với giá trị nằm trong thanh ghi W và lưu lại vào con trỏ chương trình. Như vậy, vị trí con trỏ chương trình hiện tại đang nằm ở dòng lệnh addwf PCL, F

Sau khi thực hiện lệnh này PCL = W + PCL Tiếp theo đó, con trỏ chương trình được tăng thêm một đơn vị; vì mặc định, cứ mỗi lần thực hiện xong một lệnh, con trỏ chương trình sẽ tăng lên một đơn vị để thực hiện lệnh tiếp theo. Giả sử rằng, giá trị nằm trong thanh khi W trước khi nhảy đến nhãn [Table] đang là 1. Như vậy, con trỏ chương trình sẽ là PCL = 1 + PCL. Có nghĩa là con trỏ chương trình sẽ nhảy đến dòng lệnh retlw ‘A’ Tuy nhiên, sau đó, nó mặc định cộng thêm một đơn vị để thực hiện lệnh tiếp theo, và như vậy, lúc này con trỏ chương trình sẽ nhảy đến dòng lệnh retlw ‘B’

Như vậy, sau khi thực hiện lệnh addwf, lệnh tiếp theo được thực hiện sẽ là lệnh retlw ‘B’ mà không phải là lệnh retlw ‘A’. Nhắc lại lệnh RETLW rằng, giá trị k của lệnh sẽ được lưu vào thanh ghi W và sau đó con trỏ chương trình sẽ nhảy về TOS (top of stack). Như vậy, thanh ghi W sau khi nhảy về TOS sẽ mang giá trị ‘B’, và chỉ cần thay đổi giá trị của W trước khi nhảy đến nhãn [Table] chúng ta có thể truy xuất bất kỳ giá trị nào theo ý chúng ta muốn, vì giá trị của W sau khi trả về sẽ là giá trị ở chỉ số tương ứng với W ban đầu. Ở đây, chúng ta thấy, giá trị đầu tiên của bảng là ‘A’ sẽ ứng với chỉ số 0 của W khi nhảy đến bảng. Chúng ta sẽ gọi giá trị W ban đầu là chỉ số (offset / index) để phân biệt với giá trị W sau khi trả về. Việc quy định này có thể là hơi muộn trong chương này, nhưng từ nay về sau, trong các ứng dụng bảng, chúng ta sẽ chỉ quan tâm đến việc thay đổi chỉ số theo một quy luật nào đó, mà không cần quan tâm nhiều đến giá trị trong bảng nữa. Chúng ta xem như đã vượt qua giai đoạn đầu của việc xây dựng bảng. Ví dụ trên kia là ví dụ điển hình nhất cho việc sử dụng bảng. 3) Cách gọi bảng Bây giờ, chúng ta đã biết cách thiết lập một bảng. Chúng ta hãy tạm gác lại việc sẽ xử lý dữ liệu bảng ra sao, mà chỉ quan tâm đến việc, nếu biết chỉ số của bảng, làm thế nào để lấy được giá trị của bảng ra. Kiến thức về con trỏ chương trình rất cần thiết trong phần này. Do vậy, chúng tôi đề nghị các bạn xem lại chương về con trỏ chương trình. Ở đây, chúng ta chỉ nhắc lại rằng, con trỏ chương trình gồm 2 thành phần, PCL và PCH. Trong đó, PCL là 8 bit thấp, có thể ghi và đọc, còn PCH là 5 bit cao, không thể ghi và đọc, mà chỉ có thể tương tác một cách gián tiếp qua thanh ghi PCLATH. Do vậy, khi thay đổi, PCL chỉ có thể mang giá trị từ 0 đến 255 (0x00 đến 0xFF). Nếu bảng được lập ra mà độ dài bảng lớn hơn 255 thì chúng ta không thể truy xuất được giá trị cuối cùng trong bảng. Tương tự như vậy, nếu một bảng có độ dài ngắn hơn, nhưng vị trí bắt đầu của bảng nằm trong vùng từ 0 đến 255, còn vị trí cuối cùng của bảng lại nằm ngoài 255, thì điều này cũng không thực hiện được. Khi độ dài bảng, hoặc yêu cầu trình bày chương trình một cách rõ ràng, khiến các bạn không thể đặt bảng trong bộ nhớ chương trình từ 0 đến 255, các bạn bắt buộc phải xây dựng 2 bảng riêng biệt. Trước tiên, chúng ta thực hiện việc gọi bảng thông thường, khi giá trị của bảng nằm trong vùng từ 0 đến 255 trong bộ nhớ chương trình.

Các bạn vừa làm gì? Các bạn vừa đưa một giá trị được xác định trước từ biến “offset” vào thanh ghi W. Sau đó các bạn gọi bảng [Table]. Điều đó có nghĩa là các bạn đưa chỉ số vào trong bảng và gọi bảng. Chính xác, việc gọi bảng chỉ đơn giản như vậy thôi. Hoạt động này xảy ra như thế nào? Nhắc lại rằng, khi chuyển một giá trị vào thanh ghi W, khi gọi bảng [Table], giá trị trong W sẽ là chỉ số của bảng (xem phần trên), và lệnh địa chỉ của lệnh Call + 1 sẽ được lưu vào TOS. Như vậy, sau khi thoát khỏi [Table], chương trình sẽ tiếp tục thực hiện sau lệnh Call. Chúng ta đã biết, sau khi thoát khỏi [Table] giá trị của W sẽ không phải là chỉ số offset mà chúng ta đưa vào, mà là giá trị của bảng ở vị trí chỉ số offset. Điều đó có nghĩa là, chúng ta đã lấy được giá trị của bảng ở vị trí có chỉ số chúng ta đưa vào. Vậy khi có hai bảng, một bảng nằm trong vùng nhớ 0 đến 255 và một bảng nằm ở địa chỉ khác?

Với bảng nằm ngoài vùng nhớ từ 0 đến 255, chúng ta phải chuyển con trỏ chương trình sang PAGE khác bằng cách thay đổi giá trị trong PCH. Tuy nhiên, PCH chỉ có thể được tương tác một cách gián tiếp qua PCLATH. Do vậy, thay vì tác động vào PCH, chúng ta tác động vào PCLATH trước khi gọi bảng. Còn một ví dụ khác để có thể truy cập bảng từ bất kỳ địa chỉ nào, tuy nhiên, chúng tôi không khuyến khích các bạn mới học sử dụng cách truy xuất này. Hơn nữa, theo kinh nghiệm thực tế, rất ít khi chúng ta phải lập những bảng quá dài, đến nỗi phải cắt thành 2 bảng hoặc phải đưa các giá trị của bảng sang PAGE khác. 4) Những vấn đề cần lưu ý a) Xây dựng bảng Các bạn thấy rằng, khi xây dựng bảng, điều cần thiết nhất là biết được bảng mình xây dựng ra nằm ở vị trí nào trong bộ nhớ chương trình, để đến khi gọi và viết chương trình được thuận tiện hơn. Do vậy, điều cần thiết là các bạn phải đánh dấu vị trí bắt đầu của bảng bằng cách dùng directive ORG 0xAAAA, như ví dụ sau: ORG 0x80 Table ADDWF RETLW RETLW RETLW . . PCL, F ‘A’ ‘B’ ‘C’

Như vậy, chúng ta biết được vị trí đầu của bảng, và vị trí cuối của bảng một cách rõ ràng. Lời khuyên: Không nên xây dựng bảng nằm ngoài PAGE 0, vì phải mất vài lệnh khác để điều khiển PCH rồi mới gọi bảng. Trong trường hợp bất khả kháng, nên đặt bảng trong cùng một PAGE. b) Gọi bảng Có chủ yếu ba hình thức gọi bảng. Chúng ta có thể gọi bảng trong PAGE hiện hành (thường là PAGE 0). Các bạn phải phân biết khái niệm BANK và khái niệm PAGE thật rõ ràng. Phần này được đề cập trong chương tổ chức bộ nhớ của PIC. Ngoài ra, chúng ta có thể gọi bảng trong một PAGE khác (ví dụ 2). Trường hợp thứ ba là gọi một bảng được đặt ở 2 PAGE. Tuy nhiên, trường hợp này chúng tôi không khuyến khích các bạn sử dụng, và không đề cập trong tài liệu này. Khi gọi bảng ở một PAGE khác, các bạn phải tương tác với thanh ghi PCLATH để thay đổi giá trị của PCH.

c) Ghi chú thích cho bảng Ghi chú cho bảng rất quan trọng. Những ghi chú cần thiết là những ghi chú ban đầu cho bảng, ghi chú thứ hai là ghi chú cho nội dung của các giá trị trong bảng, ghi chú thứ ba là ghi chú cho vị trí đầu và vị trí cuối của bảng trong bộ nhớ. Sau đây là một ví dụ về ghi chú cho bảng . . ;---------------------------------------------------------; Bảng trả về các giá trị mã ASCII của ; bảng chữ cái ; Lưu ý rằng chỉ số đầu tiên để truy cập bảng là 0 ; Bảng được đặt trong PAGE 0 ORG 0x80 Table ;---------------------------------------------------------ADDWF PCL, F ; bắt đầu bảng lại 0x80 RETLW ‘A’ ; ma ASCII cua ‘A’ chỉ số ‘0’ RETLW ‘B’ ; ‘B’ chỉ số ‘1’ RETLW ‘C’ ; ‘C’ chỉ số ‘2’ . ; ket thuc bảng tại 0x83 ;---------------------------------------------------------; Kết thúc bảng chữ cái ;---------------------------------------------------------Chúng tôi đề nghị các bạn sử dụng cách ghi chú thích này để thống nhất cho tất cả các chương trình của các bạn. Khi nhìn vào bảng, các bạn chỉ cần quan tâm đến các giá trị của bảng, mà không cần quan tâm đến các nhãn hoặc vị trí của bảng, nhưng khi cần tìm thông tin, các bạn biết tìm nó ở đâu. Nhãn và ORG nên được viết giữa hai dòng ;----- để phân tách khỏi bảng. Chúng ta nên đặt một chú thích ngắn để xác định vị trí kết thúc bảng. Chúng ta sẽ sử dụng ký hiệu ;----------------------------------------------------------;----------------------------------------------------------Để bắt đầu và kết thúc một chương trình con, và dùng ký hiệu : ;================================== ;================================== Để bắt đầu và kết thúc một đoạn chương trình lớn.

5) Tổng kết chương Trong chương này, chúng ta đã học và hiểu được bản chất thuật toán xây dựng bảng bằng MPASM, đó chính là vấn đề lưu các giá trị trong bảng ở đâu, và làm sao xếp thứ tự cho các giá trị đó. Chúng ta đã biết cách xây dựng bảng và gọi bảng từ bất kỳ vị trí nào trong bộ nhớ chương trình. Điều chúng ta quan tâm hơn, đó là làm sao xây dựng được bảng nhiều chiều và bảng động, dựa vào những ý tưởng của việc xây dựng mảng (array) trong ngôn ngữ lập trình cấp cao (chúng ta nên dựa vào C++). Phần thuật toán chi tiết sẽ được đề cập trong tập 2 của cuốn sách. Cuối cùng, các bạn học được cách ghi chú thích cho một bảng, và cách sắp xếp vị trí bảng một cách hợp lý trong chương trình.