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
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ư
Chức năng | Mô tả |
---|---|
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)
I2C là 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
Sơ đồ khối 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 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 PA0
thì 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 GPIOAMSEL
và 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 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
// 0.Documentation Section
// main.c
// Runs on LM4F120 or TM4C123
// C6_InputOutput, Input from PF4, output to PF2 (blue LED)
// Authors: Daniel Valvano, Jonathan Valvano and Ramesh Yerraballi
// Date: July 8, 2013
// LaunchPad built-in hardware
// SW1 left switch is negative logic PF4 on the Launchpad
// SW2 right switch is negative logic PF0 on the Launchpad
// red LED connected to PF1 on the Launchpad
// blue LED connected to PF2 on the Launchpad
// green LED connected to PF3 on the Launchpad
// 1. Pre-processor Directives Section
// Constant declarations to access port registers using
// symbolic names instead of addresses
#define GPIO_PORTF_DATA_R (*((volatile unsigned long *)0x400253FC))
#define GPIO_PORTF_DIR_R (*((volatile unsigned long *)0x40025400))
#define GPIO_PORTF_AFSEL_R (*((volatile unsigned long *)0x40025420))
#define GPIO_PORTF_PUR_R (*((volatile unsigned long *)0x40025510))
#define GPIO_PORTF_DEN_R (*((volatile unsigned long *)0x4002551C))
#define GPIO_PORTF_LOCK_R (*((volatile unsigned long *)0x40025520))
#define GPIO_PORTF_CR_R (*((volatile unsigned long *)0x40025524))
#define GPIO_PORTF_AMSEL_R (*((volatile unsigned long *)0x40025528))
#define GPIO_PORTF_PCTL_R (*((volatile unsigned long *)0x4002552C))
#define SYSCTL_RCGC2_R (*((volatile unsigned long *)0x400FE108))
// 2. Declarations Section
// Global Variables
// Function Prototypes
void PortF_Init(void);
// 3. Subroutines Section
// MAIN: Mandatory for a C Program to be executable
int main(void){
while(1){
}
}
// Subroutine to initialize port F pins for input and output
// PF4 is input SW1 and PF2 is output Blue LED
// Inputs: None
// Outputs: None
// Notes: ...
void PortF_Init(void){ volatile unsigned long delay;
SYSCTL_RCGC2_R |= 0x00000020; // 1) F clock
delay = SYSCTL_RCGC2_R; // delay
GPIO_PORTF_LOCK_R = 0x4C4F434B; // 2) unlock PortF PF0
GPIO_PORTF_CR_R = 0x1F; // allow changes to PF4-0
GPIO_PORTF_AMSEL_R = 0x00; // 3) disable analog function
GPIO_PORTF_PCTL_R = 0x00000000; // 4) GPIO clear bit PCTL
GPIO_PORTF_DIR_R = 0x0E; // 5) PF4,PF0 input, PF3,PF2,PF1 output
GPIO_PORTF_AFSEL_R = 0x00; // 6) no alternate function
GPIO_PORTF_PUR_R = 0x11; // enable pullup resistors on PF4,PF0
GPIO_PORTF_DEN_R = 0x1F; // 7) enable digital pins PF4-PF0
}
// Color LED(s) PortF
// dark --- 0
// red R-- 0x02
// blue --B 0x04
// green -G- 0x08
// yellow RG- 0x0A
// sky blue -GB 0x0C
// white RGB 0x0E
// pink R-B 0x06
GPIO Register
Ok nhìn cũng chán rồi, có 1 số chỗ ví dụ GPIO_PORTF_DATA_R
, GPIO_PORTF_DIR_R
, GPIO_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_R
, GPIO_PORTF_DIR_R
, GPIO_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
và 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
Address | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Name |
---|---|---|---|---|---|---|---|---|---|
400F.E108 | -- | -- | GPIOF | GPIOE | GPIOD | GPIOC | GPIOB | GPIOA | SYSCTL_RCGC2_R |
4000.43FC | DATA | DATA | DATA | DATA | DATA | DATA | DATA | DATA | GPIO_PORTA_DATA_R |
4000.4400 | DIR | DIR | DIR | DIR | DIR | DIR | DIR | DIR | GPIO_PORTA_DIR_R |
4000.4420 | SEL | SEL | SEL | SEL | SEL | SEL | SEL | SEL | GPIO_PORTA_AFSEL_R |
4000.4510 | PUE | PUE | PUE | PUE | PUE | PUE | PUE | PUE | GPIO_PORTA_PUR_R |
4000.451C | DEN | DEN | DEN | DEN | DEN | DEN | DEN | DEN | GPIO_PORTA_DEN_R |
4000.4524 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | GPIO_PORTA_CR_R |
4000.4528 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | GPIO_PORTA_AMSEL_R |
4000.53FC | DATA | DATA | DATA | DATA | DATA | DATA | DATA | DATA | GPIO_PORTB_DATA_R |
4000.5400 | DIR | DIR | DIR | DIR | DIR | DIR | DIR | DIR | GPIO_PORTB_DIR_R |
4000.5420 | SEL | SEL | SEL | SEL | SEL | SEL | SEL | SEL | GPIO_PORTB_AFSEL_R |
4000.5510 | PUE | PUE | PUE | PUE | PUE | PUE | PUE | PUE | GPIO_PORTB_PUR_R |
4000.551C | DEN | DEN | DEN | DEN | DEN | DEN | DEN | DEN | GPIO_PORTB_DEN_R |
4000.5524 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | GPIO_PORTB_CR_R |
4000.5528 | 0 | 0 | AMSEL | AMSEL | 0 | 0 | 0 | 0 | GPIO_PORTB_AMSEL_R |
4000.63FC | DATA | DATA | DATA | DATA | JTAG | JTAG | JTAG | JTAG | GPIO_PORTC_DATA_R |
4000.6400 | DIR | DIR | DIR | DIR | JTAG | JTAG | JTAG | JTAG | GPIO_PORTC_DIR_R |
4000.6420 | SEL | SEL | SEL | SEL | JTAG | JTAG | JTAG | JTAG | GPIO_PORTC_AFSEL_R |
4000.6510 | PUE | PUE | PUE | PUE | JTAG | JTAG | JTAG | JTAG | GPIO_PORTC_PUR_R |
4000.651C | DEN | DEN | DEN | DEN | JTAG | JTAG | JTAG | JTAG | GPIO_PORTC_DEN_R |
4000.6524 | 1 | 1 | 1 | 1 | JTAG | JTAG | JTAG | JTAG | GPIO_PORTC_CR_R |
4000.6528 | AMSEL | AMSEL | AMSEL | AMSEL | JTAG | JTAG | JTAG | JTAG | GPIO_PORTC_AMSEL_R |
4000.73FC | DATA | DATA | DATA | DATA | DATA | DATA | DATA | DATA | GPIO_PORTD_DATA_R |
4000.7400 | DIR | DIR | DIR | DIR | DIR | DIR | DIR | DIR | GPIO_PORTD_DIR_R |
4000.7420 | SEL | SEL | SEL | SEL | SEL | SEL | SEL | SEL | GPIO_PORTD_AFSEL_R |
4000.7510 | PUE | PUE | PUE | PUE | PUE | PUE | PUE | PUE | GPIO_PORTD_PUR_R |
4000.751C | DEN | DEN | DEN | DEN | DEN | DEN | DEN | DEN | GPIO_PORTD_DEN_R |
4000.7524 | CR | 1 | 1 | 1 | 1 | 1 | 1 | 1 | GPIO_PORTD_CR_R |
4000.7528 | 0 | 0 | AMSEL | AMSEL | AMSEL | AMSEL | AMSEL | AMSEL | GPIO_PORTD_AMSEL_R |
4002.43FC | DATA | DATA | DATA | DATA | DATA | DATA | GPIO_PORTE_DATA_R | ||
4002.4400 | DIR | DIR | DIR | DIR | DIR | DIR | GPIO_PORTE_DIR_R | ||
4002.4420 | SEL | SEL | SEL | SEL | SEL | SEL | GPIO_PORTE_AFSEL_R | ||
4002.4510 | PUE | PUE | PUE | PUE | PUE | PUE | GPIO_PORTE_PUR_R | ||
4002.451C | DEN | DEN | DEN | DEN | DEN | DEN | GPIO_PORTE_DEN_R | ||
4002.4524 | 1 | 1 | 1 | 1 | 1 | 1 | GPIO_PORTE_CR_R | ||
4002.4528 | AMSEL | AMSEL | AMSEL | AMSEL | AMSEL | AMSEL | GPIO_PORTE_AMSEL_R | ||
4002.53FC | DATA | DATA | DATA | DATA | DATA | GPIO_PORTF_DATA_R | |||
4002.5400 | DIR | DIR | DIR | DIR | DIR | GPIO_PORTF_DIR_R | |||
4002.5420 | SEL | SEL | SEL | SEL | SEL | GPIO_PORTF_AFSEL_R | |||
4002.5510 | PUE | PUE | PUE | PUE | PUE | GPIO_PORTF_PUR_R | |||
4002.551C | DEN | DEN | DEN | DEN | DEN | GPIO_PORTF_DEN_R | |||
4002.5524 | 1 | 1 | 1 | 1 | CR | GPIO_PORTF_CR_R | |||
4002.5528 | 0 | 0 | 0 | 0 | 0 | GPIO_PORTF_AMSEL_R | |||
31-28 | 27-24 | 23-20 | 19-16 | 15-12 | '11-8 | '7-4 | '3-0 | ||
4000.452C | PMC7 | PMC6 | PMC5 | PMC4 | PMC3 | PMC2 | PMC1 | PMC0 | GPIO_PORTA_PCTL_R |
4000.552C | PMC7 | PMC6 | PMC5 | PMC4 | PMC3 | PMC2 | PMC1 | PMC0 | GPIO_PORTB_PCTL_R |
4000.652C | PMC7 | PMC6 | PMC5 | PMC4 | 0x1 | 0x1 | 0x1 | 0x1 | GPIO_PORTC_PCTL_R |
4000.752C | PMC7 | PMC6 | PMC5 | PMC4 | PMC3 | PMC2 | PMC1 | PMC0 | GPIO_PORTD_PCTL_R |
4002.452C | PMC5 | PMC4 | PMC3 | PMC2 | PMC1 | PMC0 | GPIO_PORTE_PCTL_R | ||
4002.552C | PMC4 | PMC3 | PMC2 | PMC1 | PMC0 | GPIO_PORTF_PCTL_R | |||
4000.6520 | LOCK | (write 0x4C4F434B để unlock) | GPIO_PORTC_LOCK_R | ||||||
4000.7520 | LOCK | (write 0x4C4F434B để unlock) | GPIO_PORTC_LOCK_R | ||||||
4000.5520 | LOCK | (write 0x4C4F434B để unlock) | GPIO_PORTC_LOCK_R |
Init port F
Thông thường để khởi tạo I/O port thì cần 7 bước
- Active clock cho port
- Unlock port, với TIVAC thì cần unlock cho PC3-0,PD7,PF0
- Disable analog function của pin vì chúng ở đây chúng ta dùng pin là digital I/O
- Clear bit PCTL để chọn regular digital function (chế độ digital thông thường)
- Set thanh ghi direction
- Clear bit trong thanh ghi alternate function
- 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
- Active clock trong thanh ghi
SYSCTL_RCGC2_R
để dùng port - Unlock port bằng cách ghi giá trị đặc biệt vào thanh ghi
LOCK
, tiếp theo là thiết lập các bit trong thanh ghiCR
, chỉ unlock PC3-0, PD7, và PF0 thôi nhé, mấy bit khác thì luôn unlock rồi - Disable analog functionality bằng cách clear bit trong thanh ghi
AMSEL
- Lựa chọn GPIO functionality bằng cách clear bit trong thanh ghi
PCTL
- Chỉnh pin là input hoặc output bằng cách xóa hoặc set các bit trong thanh ghi
DIR
- 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
- Enable các I/O pin tương ứng bằng cách write 1 lên thanh ghi
DEN
- Để 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)
#include "tm4c123ge6pm.h"
unsigned long In; // input from PF4
unsigned long Out; // output to PF2 (blue LED)
// Function Prototypes
void PortF_Init(void);
// 3. Subroutines Section
// MAIN: Mandatory for a C Program to be executable
int main(void){ // initialize PF0 and PF4 and make them inputs
PortF_Init(); // make PF3-1 out (PF3-1 built-in LEDs)
while(1){
In = GPIO_PORTF_DATA_R&0x10; // read PF4 into Sw1
In = In>>2; // shift into position PF2
Out = GPIO_PORTF_DATA_R;
Out = Out&0xFB;
Out = Out|In;
GPIO_PORTF_DATA_R = Out; // output
}
}
// Subroutine to initialize port F pins for input and output
// PF4 is input SW1 and PF2 is output Blue LED
// Inputs: None
// Outputs: None
// Notes: ...
void PortF_Init(void){ volatile unsigned long delay;
SYSCTL_RCGC2_R |= 0x00000020; // 1) activate clock for Port F
delay = SYSCTL_RCGC2_R; // allow time for clock to start
GPIO_PORTF_LOCK_R = 0x4C4F434B; // 2) unlock GPIO Port F
GPIO_PORTF_CR_R = 0x1F; // allow changes to PF4-0
// only PF0 needs to be unlocked, other bits can't be locked
GPIO_PORTF_AMSEL_R = 0x00; // 3) disable analog on PF
GPIO_PORTF_PCTL_R = 0x00000000; // 4) PCTL GPIO on PF4-0
GPIO_PORTF_DIR_R = 0x0E; // 5) PF4,PF0 in, PF3-1 out
GPIO_PORTF_AFSEL_R = 0x00; // 6) disable alt funct on PF7-0
GPIO_PORTF_PUR_R = 0x11; // enable pull-up on PF0 and PF4
GPIO_PORTF_DEN_R = 0x1F; // 7) enable digital I/O on PF4-0
}
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à
- Đọc giá trị của I/O register lưu vào một biến
- Mask hoặc clear bit mà chúng ta mong muốn clear về 0 trong biến
- Select hoặc set bit mà chúng ta muốn set lên 1 trong biến
- 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 PA
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()
và 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