Ai đó có thể giải thích mã tìm kiếm kỳ lạ này, được sử dụng để thiết lập bộ hẹn giờ?


10

Trong khi nhìn vào bản phác thảo mà người khác đã viết, tôi thỉnh thoảng bắt gặp đoạn mã trông giống như thế này:

TCCR1A = 0;
TCCR1B = 0;

TCNT1 = 34286;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);

Tất cả những gì tôi biết là có một cái gì đó với thời gian / bộ hẹn giờ (tôi nghĩ). Làm thế nào tôi có thể giải mã được bộ ba và tạo mã trên mạng như thế này? Là gì TCCR1A, TCCR1B, TCNT1, CS12, TIMSK1, và TOIE1?


Không biết đủ để trả lời nhưng: Electronics.stackexchange.com/questions/92350/ triệt , forum.arduino.cc/index.php?topic=134602.0stackoverflow.com/questions/9475482/ . Không biết bạn đã thấy những thứ này chưa.
Chim cánh cụt vô danh

1
Tải xuống bảng dữ liệu "Hoàn thành" cho thiết bị của bạn từ trang web Atmel và đọc các chương về bộ tính giờ. Theo tôi, bảng dữ liệu là tốt đáng ngạc nhiên để đọc.
jippie

Câu trả lời:


15

Đâ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ư TCCR1Bvv ... đượ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.


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:

nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây

Vì vậy, ví dụ, TIMSK1 |= (1 << TOIE1);đặt bit TOIE1trong TIMSK1. Điều này đạt được bằng cách dịch chuyển nhị phân 1 ( 0b00000001) sang trái theo TOIE1bit, 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 TOIE1và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 TIMSK1theo 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;
}

PORTClà thanh ghi điều khiển giá trị của các chân đầu ra bên trong PORTCATmega328P. PINClà 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 digitalWritehoặc các digitalReadchứ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.


Lưu ý rằng mã này cũng sẽ hoạt động với Atmega32u4 (được sử dụng trong Leonardo) vì nó chứa nhiều bộ định thời hơn ATmega328P.
jfpoilpret

1
@Ricardo - Có lẽ 90% + mã nhúng MCU nhỏ sử dụng thao tác đăng ký trực tiếp. Làm mọi thứ với các chức năng tiện ích gián tiếp không phải là chế độ phổ biến để thao tác IO / thiết bị ngoại vi. Có một số bộ công cụ để trừu tượng hóa việc kiểm soát phần cứng (chẳng hạn như Atmel ASF), nhưng thường được viết để biên dịch càng nhiều càng tốt để giảm chi phí thời gian chạy, và hầu như luôn luôn yêu cầu hiểu các thiết bị ngoại vi bằng cách đọc các bảng dữ liệu.
Sói Connor

1
Về cơ bản, công cụ arduino, bằng cách nói "đây là các hàm làm X", mà không thực sự bận tâm đến tài liệu thực tế hoặc cách phần cứng đang làm những việc nó làm, rất không bình thường. Tôi hiểu giá trị của nó như là một công cụ giới thiệu, nhưng ngoại trừ việc tạo mẫu nhanh, nó không thực sự được thực hiện trong môi trường chuyên nghiệp thực tế.
Sói Connor

1
Để rõ ràng, điều làm cho mã arduino trở nên khác thường đối với phần mềm MCU nhúng không phải là duy nhất đối với mã arduino, đó là một chức năng của cách tiếp cận tổng thể. Về cơ bản, một khi bạn đã hiểu rõ về MCU thực tế , việc thực hiện đúng cách (ví dụ: sử dụng trực tiếp các thanh ghi phần cứng) sẽ mất ít hoặc không mất thêm thời gian. Như vậy, nếu bạn muốn học dev MCU thực sự, tốt hơn hết là bạn chỉ cần ngồi xuống và hiểu MCU của bạn thực sự đang làm gì, thay vào đó dựa vào sự trừu tượng của người khác , có xu hướng bị rò rỉ.
Sói Connor

1
Lưu ý rằng tôi có thể hơi hoài nghi ở đây, nhưng rất nhiều hành vi tôi thấy trong cộng đồng arduino là lập trình chống mẫu. Tôi thấy rất nhiều chương trình "sao chép-dán", coi các thư viện là hộp đen và nói chung là các thực tiễn thiết kế kém trong cộng đồng nói chung. Tất nhiên, tôi khá tích cực trên EE.stackexchange, vì vậy tôi có thể có một cái nhìn hơi nghiêng, vì tôi có một số công cụ điều hành, và như vậy thấy rất nhiều câu hỏi đóng. Chắc chắn có một sự thiên vị trong các câu hỏi arduino mà tôi đã thấy ở đó đối với "hãy nói cho tôi biết C & P cần sửa gì", thay vào đó là "tại sao điều này không hoạt động".
Sói Connor

3

TCCR1A là bộ đếm thời gian / bộ đếm 1 thanh ghi điều khiển A

TCCR1B là bộ đếm thời gian / bộ đếm 1 thanh ghi B

TCNT1 là giá trị bộ đếm thời gian / bộ đếm 1

CS12 là đồng hồ thứ 3 chọn bit cho bộ đếm thời gian / bộ đếm 1

TIMSK1 là thanh ghi mặt nạ ngắt của bộ đếm thời gian / bộ đếm 1

TOIE1 là cho phép ngắt tràn bộ đếm thời gian / bộ đếm 1

Vì vậy, mã cho phép bộ đếm thời gian / bộ đếm 1 ở mức 62,5 kHz và đặt giá trị thành 34286. Sau đó, nó cho phép ngắt tràn để khi đạt tới 65535, nó sẽ kích hoạt chức năng ngắt, rất có thể được gắn nhãn là ISR(timer0_overflow_vect)


1

CS12 có giá trị là 2 vì nó đại diện cho bit 2 của thanh ghi TCCR1B.

(1 << CS12) lấy giá trị 1 (0b00000001) và dịch chuyển nó sang trái 2 lần để lấy (0b00000100). Thứ tự của các hoạt động ra lệnh rằng những điều trong () xảy ra đầu tiên, vì vậy điều này được thực hiện trước khi "| =" được ước tính.

(1 << CS10) nhận giá trị 1 (0b00000001) và dịch chuyển sang trái 0 lần để nhận (0b00000001). Thứ tự của các hoạt động ra lệnh rằng những điều trong () xảy ra đầu tiên, vì vậy điều này được thực hiện trước khi "| =" được ước tính.

Vì vậy, bây giờ chúng tôi nhận được TCCR1B | = 0b00000101, giống như TCCR1B = TCCR1B | 0b00000101.

Vì "|" là "HOẶC", tất cả các bit khác với CS12 trong TCCR1B đều không bị ảnh hưởng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.