Đây không phải là tìm kiếm kỳ lạ. Đó là mã MCU bình thường thực sự trông như thế nào.
Những gì bạn có ở đây là một ví dụ về khái niệm các thiết bị ngoại vi được ánh xạ bộ nhớ . Về cơ bản, phần cứng MCU có các vị trí đặc biệt trong không gian địa chỉ SRAM của MCU được gán cho nó. Nếu bạn ghi vào các địa chỉ này, các bit của byte được ghi vào địa chỉ n sẽ điều khiển hành vi của thiết bị ngoại vi m .
Về cơ bản, một số ngân hàng bộ nhớ theo nghĩa đen có rất ít dây chạy từ tế bào SRAM đến phần cứng. Nếu bạn viết "1" cho bit này trong byte đó, nó sẽ đặt ô SRAM này ở mức cao logic, sau đó bật một phần của phần cứng.
Nếu bạn nhìn vào các tiêu đề cho MCU, có rất nhiều bảng từ khóa <-> ánh xạ địa chỉ. Đây là cách những thứ như TCCR1B
vv ... được giải quyết tại thời điểm biên dịch.
Cơ chế ánh xạ bộ nhớ này được sử dụng rất rộng rãi trong MCU. ATUega MCU trong arduino sử dụng nó, cũng như các dòng MCU PIC, ARM, MSP430, STM32 và STM8, cũng như rất nhiều MCU mà tôi không quen thuộc ngay lập tức.
Mã Arduino là thứ kỳ lạ, với các chức năng truy cập vào các thanh ghi điều khiển MCU một cách gián tiếp. Mặc dù điều này trông có vẻ "đẹp" hơn, nhưng nó cũng chậm hơn nhiều và sử dụng nhiều không gian chương trình hơn.
Các hằng số bí ẩn đều được mô tả rất chi tiết trong bảng dữ liệu ATmega328P , mà bạn thực sự nên đọc nếu bạn thích làm bất cứ điều gì hơn sau đó thỉnh thoảng bật các chân trên arduino.
Chọn đoạn trích từ biểu dữ liệu được liên kết ở trên:
Vì vậy, ví dụ, TIMSK1 |= (1 << TOIE1);
đặt bit TOIE1
trong TIMSK1
. Điều này đạt được bằng cách dịch chuyển nhị phân 1 ( 0b00000001
) sang trái theo TOIE1
bit, với TOIE1
định nghĩa trong tệp tiêu đề là 0. Điều này sau đó được bit ORed vào giá trị hiện tại của TIMSK1
, điều này có hiệu quả đặt mức cao một bit này.
Nhìn vào tài liệu cho bit 0 của TIMSK1
, chúng ta có thể thấy nó được mô tả là
Khi bit này được ghi vào một và cờ I trong Thanh ghi trạng thái được đặt (ngắt được bật trên toàn cầu), ngắt tràn Timer / Counter1 được bật. Vectơ ngắt tương ứng (Xem phần ngắt gián đoạn trên trang 57) được thực thi khi Cờ TOV1, nằm trong TIFR1, được đặt.
Tất cả các dòng khác nên được giải thích theo cùng một cách.
Một số lưu ý:
Bạn cũng có thể thấy những điều như TIMSK1 |= _BV(TOIE1);
. _BV()
là một macro thường được sử dụng ban đầu từ việc thực hiện libc AVR . _BV(TOIE1)
là chức năng giống hệt nhau (1 << TOIE1)
, với lợi ích của khả năng đọc tốt hơn.
Ngoài ra, bạn cũng có thể thấy các dòng như: TIMSK1 &= ~(1 << TOIE1);
hoặc TIMSK1 &= ~_BV(TOIE1);
. Điều này có chức năng ngược lại TIMSK1 |= _BV(TOIE1);
, trong đó nó bỏ đặt bit TOIE1
vào TIMSK1
. Điều này đạt được bằng cách lấy mặt nạ bit được tạo bởi _BV(TOIE1)
, thực hiện thao tác KHÔNG theo bit trên nó ( ~
) và sau đó ANDing TIMSK1
theo giá trị KHÔNG CÓ này (là 0b11111110).
Lưu ý rằng trong tất cả các trường hợp này, giá trị của những thứ như (1 << TOIE1)
hoặc _BV(TOIE1)
được giải quyết hoàn toàn tại thời gian biên dịch , do đó chúng có chức năng giảm xuống một hằng số đơn giản và do đó không mất thời gian thực hiện để tính toán khi chạy.
Mã được viết đúng sẽ thường có các bình luận nội tuyến với mã mô tả chi tiết những gì các thanh ghi được gán để làm. Đây là một thói quen SPI mềm khá đơn giản mà tôi đã viết gần đây:
uint8_t transactByteADC(uint8_t outByte)
{
// Transfers one byte to the ADC, and receives one byte at the same time
// does nothing with the chip-select
// MSB first, data clocked on the rising edge
uint8_t loopCnt;
uint8_t retDat = 0;
for (loopCnt = 0; loopCnt < 8; loopCnt++)
{
if (outByte & 0x80) // if current bit is high
PORTC |= _BV(ADC_MOSI); // set data line
else
PORTC &= ~(_BV(ADC_MOSI)); // else unset it
outByte <<= 1; // and shift the output data over for the next iteration
retDat <<= 1; // shift over the data read back
PORTC |= _BV(ADC_SCK); // Set the clock high
if (PINC & _BV(ADC_MISO)) // sample the input line
retDat |= 0x01; // and set the bit in the retval if the input is high
PORTC &= ~(_BV(ADC_SCK)); // set clock low
}
return retDat;
}
PORTC
là thanh ghi điều khiển giá trị của các chân đầu ra bên trong PORTC
ATmega328P. PINC
là thanh ghi trong đó các giá trị đầu vàoPORTC
có sẵn. Về cơ bản, những thứ như thế này là những gì xảy ra trong nội bộ khi bạn sử dụng digitalWrite
hoặc các digitalRead
chức năng. Tuy nhiên, có một hoạt động tra cứu chuyển đổi "số pin" của arduino thành số pin phần cứng thực tế, sẽ mất một nơi nào đó trong vương quốc của 50 chu kỳ đồng hồ. Như bạn có thể đoán, nếu bạn đang cố gắng đi nhanh, việc lãng phí 50 chu kỳ đồng hồ cho một thao tác chỉ cần 1 là hơi vô lý.
Hàm trên có thể mất một nơi nào đó trong vương quốc của 100-200 chu kỳ xung nhịp để truyền 8 bit. Điều này đòi hỏi 24 pin-write và 8 lần đọc. Đây là nhiều, nhanh hơn nhiều lần sau đó sử dụng các digital{stuff}
chức năng.