Các Port của vi điều khiển

Trong bài này sẽ giới thiệu về các port song song của vi điều khiển, cách lập trình thiết lập và sử dụng memory-map I/O, các cách để truy xuất các thanh ghi I/O. Chúng ta sẽ thử từng bước trong mô phỏng, sau đó sẽ thực hiện trên hệ thống thật sử dụng logic analyzer.

Giới thiệu

Mục tiêu

  • Hiểu về parallel port (cổng song song)
  • Hiểu được cách để biến một pin thành input hoặc output thông qua direction register
  • Hiểu được các bước để init parallel port
  • Hiểu cách để truy xuất I/O register
  • Biết cách đọc dữ liệu từ input
  • Biết cách ghi dữ liệu ra output.
  • Biết cách sử dụng logic analyzer trong mô phỏng.

Trong bài này chúng ta sẽ tìm cách cấu hình I/O một chân nào đó của chip TM4C123 (MCU trong kit TIVA) chẳng hạn như cấu hình chân PA1 và PA0

Tổng quan I/O Port

Với các I/O port thông dụng nhất ta thường sử dụng là parallel port hay còn gọi là GPIO, cho phép chúng ta có thể trao đổi thông tin số với thế giới bên ngoài. Từ khi bắt đầu mỗi dự án cụ thể thì chúng ta đều phải xem xét hệ thống làm việc thế nào ? Làm sao để có thể test được hệ thống hoạt động. Mỗi công cụ gỡ rối(debug) hiệu quả sẽ được thiết kế để trở thành một phần của hệ thống thay vì gắn vào hệ thống sau khi được xây dựng, nghĩa là thế nào ? Khi bạn xây dựng một hệ thống thì bạn phải tính toán được cách để debug khi hệ thống của bạn có sự cố, chứ không thể chơi kiểu “mất bò mới lo làm chuồng” được 😀

Để sử dụng được I/O thì chúng ta cần phải cấu hình nó, tuy nhiên với mỗi chân của VDK sẽ có những chức năng khác nhau, ví dụ chân PA1 và PA0 vủa TM4C123 có thể vừa là chân I/O vừa là chân UART nên khi cấu hình với từng chức năng cũng sẽ là khác nhau.

JTAG(Joint Test Action Group) được chuẩn hóa với tiêu chuẩn IEE1149.1 là một tiêu chuẩn dùng để lập trình và debug vi điều khiển, với mỗi vi điều khiển ARM hầu như đều sẽ mất 5 chân cho anh JTAG này 🙁

Với I/O pin của TIVA TM4C thì có thể cấu hình được rất nhiều chức năng như

•  UART                           Universal asynchronous receiver/transmitter

•  SSI                                Synchronous serial interface

•  I2C                                Inter-integrated circuit

•  Timer                            Periodic interrupts, input capture, và output compare

•  PWM                            Pulse width modulation

•  ADC                              Chyển đổi tương tự số, đo lường các tín hiệu analog

•  Analog Comparator      Compare two analog signals

•  QEI                              Quadrature encoder interface

•  USB                              Universal serial bus

•  Ethernet                        High-speed network

•  CAN                             Controller area network

UART sẽ được sử dụng cho kết nối serial giữa 2 computer, nó là bất đồng bộ (asynchronous) và cho phép truyền thông liên tục theo cả 2 hướng

SSI là tên gọi khác của SPI, nó được dùng để giao tiếp với các thiết bị có I/O với tốc độ trung bình, trong các bài tiếp theo chúng ta sẽ dùng nó để giao tiếp với màn hình LCD. Ngoài ra có thể dùng SSI để giao tiếp với bộ chuyển đổi số tương tự(DAC) hoặc thẻ nhớ (SDC)

I2là bus I/O đơn giản đợc dùng để giao tiếp với các thiết bị ngoại vi tốc độ thấp

Input capture và output compare sẽ được sử dụng để tạo ta ngắt định kỳ và đo lường thời gian, độ rộng xung, pha, tần số

PWM output sẽ được dùng để áp dụng điều khiển tốc độ động cơ. Với phương pháp điều khiển động cơ thông thường thì sẽ dùng input capture để đo tốc độ vòng vay và PWM để điều khiển tốc độ. Với PWM ta còn có thể tạo ra DAC nữa

ADC thường được dùng để đo biên độ của các tín hiệu analog, nó rất quan trọng trong các hẹ thống thu thập dữ liệu

Analog comparator là bộ so sánh tương tự có 2 đầu vào là analog và tạo ra đầu ra số dựa vào đầu vào tương tự lớn hơn

QEI có thể được sử dụng để giao tiếp với các động cơ brushless

USB là kết nối serial tốc độ cao

Ethernet port có thể giúp vi điều khiển có thể kết nối tới internet hoặc mạng local

CAN có thể tạo ra kết nối tốc độ cao giữa các vi điều khiển, đặc biệt nó được sử dụng nhiều trong lĩnh vực automotive và các hệ thống điều khiển phân tán.

I/O của TIVA LaunchPad

Sơ đồ khối của TM4C123

Hình dưới đây là cấu trúc và các chức năng có thể có của các pin của LM4F120H5QR vàTM4C123GH6PM

Các pin của VDK họ LM4F/TM4C đều được chỉ định tới 8 chức năng I/O khác nhau. Các chân này có thể được cấu hình với I/O dạng số, dạng input analog, timer I/O hoặc serial I/O.

Ví dụ: PA0 có thể là I/O dạng số hoặc serial input, ở đây có 2 bus được dùng cho I/O, Digital I/O port sẽ được kết nối với cả advanced peripheral bus và advanced high performance bus. Vì là nhiều bus nên VDK có thể thực hiện các chu kỳ bus I/O liên tục với instruction lấy từ flash ROM.

LM4F120H5QR có 8 công UART,4 SSI, 4 I2C, 2 x 12 bit ADC, 12 timer, 1 CAN, 1 USB, trong khi TM4C123GH6PM  sẽ có thêm 16 PWM output,43 đường line I/O và 12 ADC input, mỗi ADC có thể chuyển đổi 1M mẫu trên giây.

Tổng quan về port

Với input port thì bit direction trong thanh ghi sẽ được set là 0, nghĩa là có thể đọc được giá trị từ các chân input

Còn output port thì ngược lại, bit direction trong thanh ghi sẽ được set là 1

Hình minh họa dưới đây sẽ giúp bạn hiểu hơn khi ta cấu hình thanh ghi direction

Chân của TM4C123

Hình trên là các chức năng có thể có của mỗi chân của Launchpad, ví dụ chân PA0thì ta có thể cấu hình được nó là chân UART(U0Rx) hoặc cấu hình nó là chân CAN(CAN1Rx)

Vậy để cấu hình được những chân này thì chúng ta cần phải quan tâm tới bit configuration trong thanh ghi GPIOAMSELvà 4 bit trong thanh ghi GPIOPCTL

Ví dụ: Nếu chúng ta muốn sử dụng UART7 trên chân PE0 và PE1 thì chúng ta phải thực hiện

  • Set bit 1,0 trong thanh ghi GPIO_PORTE_DEN_R (enable digital)
  • Clear bit 1,0 trong thanh ghi GPIO_PORTE_AMSEL_R (disable analog)
  • Set các bit PMCx trong thanh ghi GPIO_PORTE_PCTL_R lên 0001 (enable UART functionality)
  • Set bit 1,0 trong thanh ghi GPIO_PORTE_AFSEL_R (enable alternate function)

Tạm thời chúng ta sẽ biết cách sơ bộ để có thể cấu hình UART7 là như thế, cụ thể trong code thế nào mình sẽ nói ở phần dưới

Với hình trên có một số điểm chúng ta cần phải lưu ý:

  • Chỉ có 43 chân
  • Tất cả các pin đều có thể làm GPIO (general purpose input/output)
  • Các pin đều có thể là GPIO hoặc là một chân có chức năng nào đó (cái này tùy vào nhà sx chip)
  • Một số chân có chức năng quen thuộc ví dụ như SSI,UART,ADC, chúng ta sẽ có thể nghiên cứu được, tuy nhiên lại có một số chức năng khác như CAN, IDX thì sẽ ít được nói đến (muốn biết thì chỉ có xem datasheet để hiểu thêm)
  • Chân PC3-PC0 không có trong bảng bởi vì 4 chân này được dành cho JTAG debugger

Như hình trên ta có thể thấy được cấu tạo cơ bản của kit launchpad TIVAC, trên board sẽ có tích hợp luôn phần debug là ICDI(Integrated In-Circuit Debug Interface) nên tha hồ mà debug khi gặp sự cố nha các bạn

Lưu ý: thường mới mua thì chỗ power selection sẽ được gạt sang PWR, để có thể nạp chương trình thì bạn phải gạt về debug

Khái niệm cơ bản về Input và Output Port

Input và Output Port

Chúng ta sẽ quay lại một chút ở phần input port, nó cho phép phần mềm có thể đọc từ các tín hiệu số bên ngoài, tất nhiên là chỉ đọc. Điều này có nghĩa là chu kỳ đọc từ các địa chỉ của port  sẽ trả về giá trị trên input vào thời điểm đó. Thông thường với tristate driver (hình tam giác) sẽ truyền tín hiệu đầu vào lên bus dữ liệu trong suốt một chu kỳ đọc từ địa chỉ của port. Một chu kỳ ghi vào input port thường sẽ không gây ra ảnh hưởng nhiều. Gía trị số tồn tại trên input pin được copy vào vi điều khiển khi phần mềm thực hiện đọc từ địa chỉ của port.

Với dòng LM4F/TM4C thì sẽ có digital input là 5V-tolerant, nghĩa là điện áp ở mức high là từ 2.0 tới 5.0V, với một số dòng khác như STM32F thì một số digital input là 5V tolerant, còn một số thì không

Khác với input port thì output port có thể tham gia vào chu trình đọc và ghi giống như với bộ nhớ thông thường. Hình bên dưới sẽ là readable output port (port output có thể đọc được). Một chu kỳ tới port address sẽ ảnh hưởng tới giá trị của output pin. Đặc biệt vi điều khiển sẽ đưa thông tin lên data bus và thông tin này sẽ có xung nhịp trong D fliplop. Vì nó là readable output nên chu kỳ đọc có thể truy cập vào port address và trả về giá trị hiện tại tồn tại trên port pin. Không có chỉ output trên dòng LM4F/TM4C

Bidirectional I/O Pin

Để cho các vi điều khiển có thể dễ bán hơn thì các nhà SX đã hô biến cho port có thể có 2 chức năng là input hoặc output. Với việc sử dụng thanh ghi direction xác định pin đó là input (bit direction là 0) hay là output(bit direction là 1) như hình bên dưới.

Chúng ta có thể định nghĩa một nghi thức khởi tạo như một khi chương trình được thực thi trong quá trình khởi tạo phần cứng và phần mềm. Nếu nghi thức này đưa bit direction về 0 nghĩa là port sẽ đóng vai trò là input, và ngược lại thì nó sẽ là output port

Lưu ý: rất nhiều chương trình có thể bị lỗi mà nguyên nhân bắt nguồn từ việc cấu hình các cổng I/O, ví dụ như bạn không thể write vào input port, cũng như đôi lúc không thể read được từ output port

Lập trình IO và thanh ghi

Trước khi bắt đầu chúng ta xem thử qua chương trình dưới đây, với một số bạn quen rồi thì chắc không vấn đề, chứ nếu bạn là người mới thì nhìn vào cái này đúng là như mớ bòng bong, thôi không sao cả, cứ xem qua coi bạn có thể hiểu được tới đâu

Chương trình cấu hình với TM4C

GPIO Register

Ok nhìn cũng chán rồi, có 1 số chỗ ví dụ GPIO_PORTF_DATA_RGPIO_PORTF_DIR_RGPIO_PORTF_DEN_R, chỗ này nó nằm ở đâu trong datasheet ? À datasheet là gì mà đi đâu cũng thấy các cao thủ nói rằng, chỉ cần coi datasheet là có thể làm được mọi thứ, sơ bộ nó như là 1 quyển sách mô tả về tất cả các chức năng và thành phần có trong vi điều khiển đó các bạn, để mình ví dụ minh họa cho các bạn 1 ví dụ đơn giản về cách tìm kiếm trong datasheet

Giả sử giờ với GPIO_PORTF_DATA_RGPIO_PORTF_DIR_RGPIO_PORTF_DEN_R, mình sẽ đi tìm trong datasheet với các từ khóa GPIO, DATA_R, DIR_R, DEN_R tương ứng với register là DATA, DIR, DEN.

Bước 1: Tải datasheet của TM4C về tại đây

Bước 2: Vào mục 10.General-Purpose Input/Outputs (GPIOs), 10.5 Register Description, ta sẽ thấy Register 1, Register 2 và Register 18 tương ứng với DATA_R, DIR_R, DEN_R

Bước 3: Vào xem register 1 và 2 chút, sau đó nhảy xuống register 18, bạn sẽ thấy bit/field 0:7 với Name là DEN, chúng ta có thể dùng nó để enable hoặc disable các chân tương ứng. Điều này nghĩa là gì ? Nếu bạn muốn dùng cái gì đó thì bạn phải khai báo, tiếp đến là enable nó lên, nếu không dùng nữa thì mình disable đi 

Vậy là xong được bước ban đầu, bạn có thể tìm hiểu được 1 ít về code thông qua kí hiệu và datasheet, mình sẽ nói kỹ hơn ở các phần sau.

Với hầu hết các vi điều khiển thi hầu hết I/O port đều là memory mapped, nghĩa là với chương trình viết ra ta có thể truy xuất được tới các I/O port một cách đơn giản bằng việc đọc hoặc ghi vào các địa chỉ thích hợp. Tuy nhiên điều quan trọng mà ta phải ghi nhớ là việc thao tác vào I/O nhìn thì giống như việc đọc và ghi vào memory nhưng các I/O port thường sẽ không hoạt động như memory. Ví dụ: một số bit thì là chỉ đọc (read-only), 1 số thì là chỉ ghi(write-only), 1 số thì chỉ xóa, 1 số lại chỉ set, ngoài ra thì một số bit còn không thể điều chỉnh.

Để phần mềm có thể dễ đọc hơn thì người ta thường dùng các định nghĩa tượng trưng cho I/O port, ví dụ như việc thiết lập thanh ghi direction (GPIO_PORTF_DIR_R) để xác định chân nào là input và chân nào là output. Với mỗi chân thì có thể là GPIO hoặc chân chức năng khác, thì họ sẽ set bit trong thanh ghi alternate function (GPIO_PORTF_AFSEL_R) khi muốn dùng chức năng khác của chân (tham khảo bảng bên dưới). Sau khi thực hiện 2 thao tác với DIR_R AFSEL_R thì coi như đã cấu hình thành công, việc cấu hình này thường chỉ thực hiện 1 lần lúc khởi tạo, sau đó để đưa chân lên mức 1 hoặc về 1 mức không thì ta cần ghi dữ liệu vào thanh ghi Data (GPIO_PORTF_DATA_R) , ngược lại với giai đoạn cấu hình chỉ 1 lần thì chúng ta sẽ phải đọc và ghi vào thanh ghi data nhiều lần tương ứng với đầu vào và đầu ra trong chương trình.

Bảng thanh ghi
Address76543210Name
400F.E108----GPIOFGPIOEGPIODGPIOCGPIOBGPIOASYSCTL_RCGC2_R
4000.43FCDATADATADATADATADATADATADATADATAGPIO_PORTA_DATA_R
4000.4400DIRDIRDIRDIRDIRDIRDIRDIRGPIO_PORTA_DIR_R
4000.4420SELSELSELSELSELSELSELSELGPIO_PORTA_AFSEL_R
4000.4510PUEPUEPUEPUEPUEPUEPUEPUEGPIO_PORTA_PUR_R
4000.451CDENDENDENDENDENDENDENDENGPIO_PORTA_DEN_R
4000.452411111111GPIO_PORTA_CR_R
4000.452800000000GPIO_PORTA_AMSEL_R
4000.53FCDATADATADATADATADATADATADATADATAGPIO_PORTB_DATA_R
4000.5400DIRDIRDIRDIRDIRDIRDIRDIRGPIO_PORTB_DIR_R
4000.5420SELSELSELSELSELSELSELSELGPIO_PORTB_AFSEL_R
4000.5510PUEPUEPUEPUEPUEPUEPUEPUEGPIO_PORTB_PUR_R
4000.551CDENDENDENDENDENDENDENDENGPIO_PORTB_DEN_R
4000.552411111111GPIO_PORTB_CR_R
4000.552800AMSELAMSEL0000GPIO_PORTB_AMSEL_R
4000.63FCDATADATADATADATAJTAGJTAGJTAGJTAGGPIO_PORTC_DATA_R
4000.6400DIRDIRDIRDIRJTAGJTAGJTAGJTAGGPIO_PORTC_DIR_R
4000.6420SELSELSELSELJTAGJTAGJTAGJTAGGPIO_PORTC_AFSEL_R
4000.6510PUEPUEPUEPUEJTAGJTAGJTAGJTAGGPIO_PORTC_PUR_R
4000.651CDENDENDENDENJTAGJTAGJTAGJTAGGPIO_PORTC_DEN_R
4000.65241111JTAGJTAGJTAGJTAGGPIO_PORTC_CR_R
4000.6528AMSELAMSELAMSELAMSELJTAGJTAGJTAGJTAGGPIO_PORTC_AMSEL_R
4000.73FCDATADATADATADATADATADATADATADATAGPIO_PORTD_DATA_R
4000.7400DIRDIRDIRDIRDIRDIRDIRDIRGPIO_PORTD_DIR_R
4000.7420SELSELSELSELSELSELSELSELGPIO_PORTD_AFSEL_R
4000.7510PUEPUEPUEPUEPUEPUEPUEPUEGPIO_PORTD_PUR_R
4000.751CDENDENDENDENDENDENDENDENGPIO_PORTD_DEN_R
4000.7524CR1111111GPIO_PORTD_CR_R
4000.752800AMSELAMSELAMSELAMSELAMSELAMSELGPIO_PORTD_AMSEL_R
4002.43FCDATADATADATADATADATADATAGPIO_PORTE_DATA_R
4002.4400DIRDIRDIRDIRDIRDIRGPIO_PORTE_DIR_R
4002.4420SELSELSELSELSELSELGPIO_PORTE_AFSEL_R
4002.4510PUEPUEPUEPUEPUEPUEGPIO_PORTE_PUR_R
4002.451CDENDENDENDENDENDENGPIO_PORTE_DEN_R
4002.4524111111GPIO_PORTE_CR_R
4002.4528AMSELAMSELAMSELAMSELAMSELAMSELGPIO_PORTE_AMSEL_R
4002.53FCDATADATADATADATADATAGPIO_PORTF_DATA_R
4002.5400DIRDIRDIRDIRDIRGPIO_PORTF_DIR_R
4002.5420SELSELSELSELSELGPIO_PORTF_AFSEL_R
4002.5510PUEPUEPUEPUEPUEGPIO_PORTF_PUR_R
4002.551CDENDENDENDENDENGPIO_PORTF_DEN_R
4002.55241111CRGPIO_PORTF_CR_R
4002.552800000GPIO_PORTF_AMSEL_R
31-2827-2423-2019-1615-12'11-8'7-4'3-0
4000.452CPMC7PMC6PMC5PMC4PMC3PMC2PMC1PMC0GPIO_PORTA_PCTL_R
4000.552CPMC7PMC6PMC5PMC4PMC3PMC2PMC1PMC0GPIO_PORTB_PCTL_R
4000.652CPMC7PMC6PMC5PMC40x10x10x10x1GPIO_PORTC_PCTL_R
4000.752CPMC7PMC6PMC5PMC4PMC3PMC2PMC1PMC0GPIO_PORTD_PCTL_R
4002.452CPMC5PMC4PMC3PMC2PMC1PMC0GPIO_PORTE_PCTL_R
4002.552CPMC4PMC3PMC2PMC1PMC0GPIO_PORTF_PCTL_R
4000.6520LOCK (write 0x4C4F434B để unlock)GPIO_PORTC_LOCK_R
4000.7520LOCK (write 0x4C4F434B để unlock)GPIO_PORTC_LOCK_R
4000.5520LOCK(write 0x4C4F434B để unlock)GPIO_PORTC_LOCK_R

Chương trình khởi tạo port F

Thông thường để khởi tạo I/O port thì cần 7 bước

  1. Active clock cho port
  2. Unlock port, với TIVAC thì cần unlock cho PC3-0,PD7,PF0
  3. Disable analog function của pin vì chúng ở đây chúng ta dùng pin là digital I/O
  4. Clear bit PCTL để chọn regular digital function (chế độ digital thông thường)
  5. Set thanh ghi direction
  6. Clear bit trong thanh ghi alternate function
  7. Enable digital port
Lưu ý: Lỗi thường gặp nhất là bus fault nếu ta truy xuất vào port mà không enable clock

Ví dụ cụ thể luôn cho các bạn dễ hình dung, chúng ta sẽ cấu hình PF4 và PF0 là input và PF1 – PF3 là output, theo các bước ở trên thì ta phải

  1. Active clock trong thanh ghi SYSCTL_RCGC2_R để dùng port
  2. Unlock port bằng cách ghi giá trị đặc biệt vào thanh ghi LOCKtiếp theo là thiết lập các bit trong thanh ghi CRchỉ unlock PC3-0, PD7, và PF0 thôi nhé, mấy bit khác thì luôn unlock  rồi
  3. Disable analog functionality bằng cách clear bit trong thanh ghi AMSEL
  4. Lựa chọn GPIO functionality bằng cách clear bit trong thanh ghi PCTL
  5. Chỉnh pin là input hoặc output bằng cách xóa hoặc set các bit trong thanh ghi DIR
  6. Vì chúng ta dùng pin là digital I/O thông thường nên cần phải clear các bit trong thanh ghi AFSEL
  7. Enable các I/O pin tương ứng bằng cách write 1 lên thanh ghi DEN
  8. Để chạy được ví dụ này trên kit TIVA thì chúng ta cần phải set thêm bit trong thanh ghi PURđể dùng 2 nút nhấn có sẵn với internal pull-up resistor(điện trở nội kéo lên)
Chương trình

Khi phần mềm đọc từ địa chỉ 0x400253FC thì 8 bit cuối sẽ trả về giá trị hiện tại của PortF, 24 bit đầu sẽ trả về giá trị là 0. Như hình trên mục Bidirectional I/O Pin thì khi chúng ta đọc I/O port thì input pin sẽ show ra các trạng thái hiện tại của pin, và output pin sẽ show ra giá trị cuối cùng được ghi vào port. Hàm PortF_Input sẽ đọc giá trị của 5 chân input và trả về giá trị hiện tại của các input, trong khi đó hàm PortF_Output sẽ ghi giá trị mới vào 3 output pin.

#include sẽ định nghĩa symbolic name cho tất cả I/O port của vi điều khiển trong header file là  tm4c123ge6pm.h

Friendly Code

Từ này có vẻ hơi lạ với chúng ta, nhưng nếu bạn sử dụng và thao tác trên thanh ghi nhiều thì cần phải viết code một cách thân thiện, dễ hiểu, ngoài việc chỉ thay đổi giá trị bit bạn  cần thay đổi ra thì không được làm ảnh hưởng tới các bit không liên quan. Cách điển hình để làm việc này là

  1. Đọc giá trị của I/O register lưu vào một biến
  2. Mask hoặc clear bit mà chúng ta mong muốn clear về 0 trong biến
  3. Select hoặc set bit mà chúng ta muốn set lên 1 trong biến
  4. Ghi giá trị này vào thanh ghi I/O

Ví dụ: Với portB là output, mình cần set bit 5 và clear bit 3, giải pháp tốt(gọi là friendly code) là không làm thay đổi những bit không liên quan như 7,6,4,2,1,0 thì ta phải làm như sau

GPIO_PORTB_DATA_R = (GPIO_PORTB_DATA_R&(~0x08))|0x20;

Một cách khác tương tự

in = GPIO_PORTB_DATA_R; // read value
in &= ~0x08;            // clear bit 3
in |= 0x20;             // set bit 5
GPIO_PORTB_DATA_R = in; // update port

Trong TIVAC thì portB với bit từ 23-8 sẽ không được thêm, nếu có thì với cách làm trên chúng ta cũng sẽ không làm ảnh hưởng gì tới nó cả

Địa chỉ bit đặc biệt

Một cách khác để có thể viết một chương trình gọi là friendly code là sử dụng bit-specific addressing, nghĩa là dùng địa chỉ trực tiếp của từng bit, ở trên TM4C cho phép chúng ta có thể làm điều này, tuy nhiên chỉ giới hạn ở một số thanh ghi data thôi.

Với cách trên thì ta có thể truy xuất tới bit 1 – 8 trong data port.Chúng ta có thể định nghĩa 8 địa chỉ offset như sau

Ví dụ: Chúng ta muốn truy cập vào bit 1 2 3 của port A. Với base address của Port A là 0x4000.4000 và constants là 0x0008 (bit 1),0x0010(bit 2), và 0x0020(bit 3). Tính tổng 0x4000.4000+0x0008+0x0010 +0x0020 thì địa chỉ sẽ là 0x4000.4038. Nếu chúng ta đọc từ địa chỉ này thì chỉ có giá trị của bit 1,2,3 được trả về, và tương tự nếu chúng ta write xuống địa chỉ trên thì chỉ có 3 bit được thay đổi.

Để có thể sử dụng và read/write xuống từng bit của port A thì trong chương trình ta có thể thêm một số define như sau

#define PA7   (*((volatile unsigned long *)0x40004200))
#define PA6   (*((volatile unsigned long *)0x40004100))
#define PA5   (*((volatile unsigned long *)0x40004080))
#define PA4   (*((volatile unsigned long *)0x40004040))
#define PA3   (*((volatile unsigned long *)0x40004020))
#define PA2   (*((volatile unsigned long *)0x40004010))
#define PA1   (*((volatile unsigned long *)0x40004008))
#define PA0   (*((volatile unsigned long *)0x40004004))

Giờ thì có thể đơn giản hơn để set/clear PA5 mà không ảnh hưởng tới các pin khác bằng cách

PA5 = 0x20;       // make PA5 high
PA5 = 0x00;       // make PA5 low

Nếu bạn đọc giá trị từ PA5 sẽ trả về 0x20 hoặc 0x00 tùy theo giá trị pin hiện tại là high hay low. Nếu PA5 là output thì có thể dùng đoạn code sau để thay đổi trạng thái của PA5

PA5 = PA5^0x20;   // toggle PA5

Nếu bạn muốn thay đổi nhiều hơn 1 bit thì sao nhỉ ? Ví dụ luôn là thay đổi bit 6 và 7 của port A thì ta có thể define constant như sau

#define PA76   (*((volatile unsigned long *)0x40004300))
PA76 = 0x80;       // make PA7 high and PA6 low

Vậy với Port A có base address là 0x40004000 vậy thì các port khác sẽ là gì nhỉ ? Các bạn tham khảo hình bên dưới nhé

Monitor Debug với LED

Một trong những nhiệm vụ quan trọng của việc debug một hệ thống là quan sát được vị trí và thời gian mà phần mềm được thực thi. Một tool debug ngon lành là tool có thể giúp ta gỡ rối được các hệ thống realtime, trong hệ thống thời gian thực thì ta cần thời gian thực thi của tool debug là càng nhỏ càng tốt so với thời gian thực thi của chương trình. Có một khái niệm và Intrusiveness được định nghĩa là mức độ mà nó làm thay đổi hiệu suất của hệ thống đang được thử nghiệm. Ví dụ như lệnh print..

Với việc sử dụng LED thì đây cũng là một cách để debug đơn giản, người ta thường gọi là BOOLEAN monitor. Bạn có thể nối LED với 1 chân output không dùng tới, và phần mềm sẽ toggle LED để chỉ cho bạn có thể biết được là khi nào thì chương trình chạy.

#define PF2 (*((volatile unsigned long *)0x40025010))
void HeartBeat(void){
  PF2 ^= 0x04;  // toggle LED
}
#define HeartBeat2() {PF2 ^= 0x04;}

Ví dụ với chương trình trên thì hàm HeartBeat() sẽ toggle PF2, Macro HeartBeat2() cũng sẽ toggle PF2 nhưng sẽ chạy nhanh hơn bởi vì nó không có gọi hàm và return

Nếu chúng ta thêm 2 hàm HeartBeat()HeartBeat2() vao hệ thống thì sẽ mất 13 chu kỳ bus để thực thi HeartBeat và 7 chu kỳ bus để thực thi HeartBeat2. Tai bus clock là 80Mhz thì 7 chu kỳ bus sẽ là 87.5ns.

Debug với logic analyzer

Logic Analyzer và oscilloscope là 2 công cụ khá quan trọng để debug được hardware và software.

Với logic analyzer thì có thể quan sát được tín hiệu số tại nhiều thời điểm khác nhau

Với oscilloscope thì có thể quan sát được điện áp với thời gian dữ liệu

2 cách này để tới ví dụ cụ thể mình sẽ hướng dẫn các bạn luôn cho dễ hiểu

Tạm Kết

Vậy là trong bài viết này chúng ta đã có thể hiểu về parallel port (cổng song song) và cách init nó, cách để biến một pin thành input hoặc output thông qua direction register, cách để truy xuất I/O register, đọc dữ liệu từ input và ghi ra output, hiểu được cơ bản debug và cách để debug rồi, đường còn dài, cố gắng đi tiếp các bài tiếp theo nha các bạn

Leave a Reply

avatar

Website này sử dụng Akismet để hạn chế spam. Tìm hiểu bình luận của bạn được duyệt như thế nào.

  Subscribe  
Notify of