Tại sao mã AVR sử dụng dịch chuyển bit [đã đóng]


7

Trong lập trình AVR, các bit đăng ký luôn được đặt bằng cách dịch chuyển trái sang 1vị trí bit thích hợp - và chúng bị xóa bởi phần bổ sung của cùng một vị trí.

Ví dụ: đối với ATtiny85, tôi có thể đặt PORTB, b 4 như thế này:

PORTB |= (1<<PB4);

hoặc xóa nó như thế này:

PORTB &= ~(1<<PB4);

Câu hỏi của tôi là: Tại sao nó được thực hiện theo cách này? Mã đơn giản nhất kết thúc là một mớ hỗn độn của bit-shift. Tại sao các bit được định nghĩa là vị trí bit thay vì mặt nạ.

Chẳng hạn, tiêu đề IO cho ATtiny85 bao gồm:

#define PORTB   _SFR_IO8(0x18)
#define PB5     5
#define PB4     4
#define PB3     3
#define PB2     2
#define PB1     1
#define PB0     0

Đối với tôi, sẽ hợp lý hơn nhiều khi định nghĩa các bit là mặt nạ thay thế (như thế này):

#define PORTB   _SFR_IO8(0x18)
#define PB5     0x20
#define PB4     0x10
#define PB3     0x08
#define PB2     0x04
#define PB1     0x02
#define PB0     0x01

Vì vậy, chúng tôi có thể làm một cái gì đó như thế này:

// as bitmasks
PORTB |=  PB5 |  PB3 |  PB0;
PORTB &= ~PB5 & ~PB3 & ~PB0;

để bật và tắt các bit b 5 , b 3 và b 0 tương ứng. Như trái ngược với:

// as bit-fields
PORTB |=  (1<<PB5) |  (1<<PB3) |  (1<<PB0);
PORTB &= ~(1<<PB5) & ~(1<<PB3) & ~(1<<PB0);

Mã bitmask đọc rõ ràng hơn: set bit PB5, PB3PB0. Hơn nữa, nó dường như sẽ lưu các hoạt động vì các bit không còn cần phải thay đổi.

Tôi nghĩ có lẽ nó đã được thực hiện theo cách này để bảo toàn tính tổng quát để cho phép chuyển mã từ một n -bit AVR sang m -bit (ví dụ 8 bit đến 32 bit). Nhưng điều này dường như không xảy ra, vì nó #include <avr/io.h>giải quyết các tệp định nghĩa cụ thể cho vi điều khiển đích. Ngay cả việc thay đổi mục tiêu từ ATtiny 8 bit sang Atmega 8 bit ( ví dụ, trong đó định nghĩa bit thay đổi cú pháp từ PBxsang PORTBx, chẳng hạn), yêu cầu thay đổi mã.


3
Tôi thứ hai này. Ngay cả việc sử dụng phổ biến _BV(b)thay vì (1<<b)làm cho mọi thứ trở nên lộn xộn không cần thiết. Tôi thường định nghĩa bit mnemonics với _BV(), ví dụ #define ACK _BV(1).
canxi3000

2
Một khi nó nhận ra rằng trình biên dịch sẽ diễn giải những điều này là cùng một hằng số, việc sử dụng trong mã nguồn thực sự là một vấn đề ưu tiên. Trong mã của riêng bạn làm bất cứ điều gì bạn nghĩ là khôn ngoan nhất; trong việc sửa đổi các dự án hiện có, tuân theo truyền thống của họ.
Chris Stratton

3
"Vì cách tiếp cận bitmask rõ ràng sẽ mang lại mã người dùng cuối dễ đọc hơn" - ý kiến ​​cá nhân của bạn. Tôi thấy việc dịch chuyển 1 và 0 sang đúng vị trí rõ ràng hơn nhiều so với việc phải đoán xem một vài số được thêm vào có phải là bitmasks hay không.
Tom Carpenter

3
@TomCarpenter Thú vị. Chà, có lẽ tôi đã vô tình hỏi một câu hỏi dựa trên ý kiến. Dù bằng cách nào, đã có một số phản hồi tốt. Đến từ nhiều nền tảng DSP (TI) (trong đó bitmask là chuẩn mực), có vẻ như cú pháp kỳ quặc đến mức tôi nghĩ rằng có một số lý do cụ thể cho nó.
Blair Fonville

1
@BlairFonville Có thể bạn đã biết điều này, nhưng ARM hoạt động chính xác như bạn mô tả (với bitmasks).
Chi

Câu trả lời:


7

Mã đơn giản nhất kết thúc là một mớ hỗn độn của bit-shift. Tại sao các bit được định nghĩa là vị trí bit thay vì mặt nạ.

Không hoàn toàn không. Các ca làm việc chỉ trong mã nguồn C, không phải trong mã máy đã biên dịch. Tất cả các ví dụ bạn đã trình bày có thể và sẽ được trình biên dịch giải quyết tại thời điểm biên dịch vì chúng là các biểu thức hằng đơn giản.

(1<<PB4) chỉ là một cách để nói "bit PB4".

  • Vì vậy, nó không chỉ hoạt động, nó không tạo ra nhiều kích thước mã.
  • Điều này cũng hợp lý khi lập trình viên con người đặt tên cho các bit theo chỉ số của họ (ví dụ 5) chứ không phải bằng mặt nạ bit của họ (ví dụ 32) bởi vì cách này có thể sử dụng các số liên tiếp 0..7 để xác định các bit thay vì sức mạnh vụng về hai (1, 2, 4, 8, .. 128).

  • Và có một lý do khác (có thể là lý do chính):
    Các tệp tiêu đề C không chỉ được sử dụng cho mã C mà còn cho mã nguồn của trình biên dịch mã (hoặc mã trình biên dịch được mã hóa trong mã nguồn C). Trong mã trình biên dịch mã AVR, bạn chắc chắn không chỉ muốn sử dụng mặt nạ bit (có thể được tạo từ các chỉ mục bằng cách dịch chuyển bit). Đối với một số hướng dẫn trình biên dịch mã thao tác bit AVR (ví dụ SBI, CBI, BST, BLD), bạn phải sử dụng các chỉ số bit làm toán tử ngay lập tức trong mã op lệnh của chúng.
    Chỉ khi bạn xác định bit của SFR bằng chỉ số(không phải bằng mặt nạ bit), bạn có thể sử dụng trực tiếp các mã định danh như toán tử hướng dẫn trình biên dịch chương trình. Mặt khác, bạn phải có hai định nghĩa cho mỗi bit SFR: một định nghĩa chỉ số bit của nó (có thể được sử dụng, ví dụ như toán hạng trong các hướng dẫn trình biên dịch thao tác bit đã nói ở trên) và một định nghĩa mặt nạ bit của nó (chỉ có thể được sử dụng cho các lệnh trong đó toàn bộ byte bị thao túng).


1
Tôi hiểu điều đó. Tôi không hỏi liệu nó có hoạt động hay không. Tôi biết nó làm. Tôi đang hỏi tại sao các định nghĩa được viết như chúng là. Đối với tôi nó sẽ cải thiện đáng kể khả năng đọc mã nếu chúng được định nghĩa là mặt nạ thay vì vị trí bit.
Blair Fonville

5
Tôi nghĩ rằng câu trả lời này bỏ lỡ điểm. Ông không bao giờ nói về hiệu quả mã hoặc trình biên dịch. Đó là tất cả về sự lộn xộn của mã nguồn .
đường ống

4
@Blair Fonville: không có cách nào dễ dàng để xác định một macro như vậy. Nó cần để tính toán logarit đến cơ sở 2. Không có chức năng tiền xử lý tính toán logarit. Tức là nó chỉ có thể được thực hiện bằng cách sử dụng một bảng và điều đó, tôi nghĩ, sẽ là một ý tưởng rất tồi.
Sữa đông

2
@pipe: Tôi không nói về nó bởi vì tôi chỉ không coi đó là "ô nhiễm mã" hoặc "lộn xộn mã nguồn" (hoặc bất cứ điều gì bạn muốn gọi nó). Ngược lại, tôi nghĩ rằng thậm chí hữu ích khi nhắc nhở lập trình viên / người đọc rằng hằng số anh ta đang sử dụng là sức mạnh của hai (và điều đó được thực hiện bằng cách sử dụng biểu thức thay đổi).
Sữa đông

1
@RR, gỡ lỗi) cực kỳ nội bộ.
Sữa đông

4

Có lẽ dịch chuyển bit không phải là trường hợp sử dụng duy nhất cho các PB*định nghĩa. Có lẽ có một trường hợp sử dụng khác trong đó các PB*định nghĩa được sử dụng trực tiếp thay vì số lượng ca. Nếu vậy thì tôi tin rằng nguyên tắc DRY sẽ dẫn bạn thực hiện một bộ định nghĩa có thể được sử dụng cho cả hai trường hợp sử dụng (như các PB*định nghĩa này ) thay vì hai bộ định nghĩa khác nhau có thông tin lặp đi lặp lại.

Ví dụ, tôi đã viết một ứng dụng có thể thực hiện các phép đo từ tối đa 8 kênh ADC. Nó có một giao diện để bắt đầu một phép đo mới, trong đó bạn có thể chỉ định nhiều kênh thông qua trường 8 bit, một bit cho mỗi kênh. (Các phép đo được thực hiện song song khi nhiều kênh được chỉ định.) Sau đó, nó có một giao diện khác trả về kết quả đo cho một kênh riêng lẻ. Vì vậy, một giao diện sử dụng số kênh dưới dạng dịch chuyển sang trường bit và giao diện khác sử dụng trực tiếp số kênh. Tôi đã xác định một bảng liệt kê duy nhất để bao gồm cả hai trường hợp sử dụng.

typedef enum
{
    CHANNEL_XL_X = 0,
    CHANNEL_XL_Y = 1,
    CHANNEL_XL_Z = 2,
    CHANNEL_G_X = 3,
    CHANNEL_G_Y = 4,
    CHANNEL_G_Z = 5,
    CHANNEL_AUX1 = 6,
    CHANNEL_AUX2 = 7
} ChannelNum;

struct MeasurementResult;

void StartMeasurement(uint8_t channel_mask);
MeasurementResult ReadMeasurementResult(ChannelNum channel_num);

main
{
    ...

    StartMeasurement( (1 << CHANNEL_XL_X) | (1 << CHANNEL_XL_Y) | (1 << CHANNEL_XL_Z) );

    meas_result_x = ReadMeasurementResult(CHANNEL_XL_X);
    meas_result_y = ReadMeasurementResult(CHANNEL_XL_Y);
    meas_result_z = ReadMeasurementResult(CHANNEL_XL_Z);
}
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.