Nhúng C - Cách thanh lịch nhất để chèn độ trễ


8

Tôi đang làm việc trong một dự án liên quan đến một ccuex-m4 mcu (LPC4370). Và tôi cần chèn một độ trễ trong khi bật tối ưu hóa của trình biên dịch. Cho đến nay, cách giải quyết của tôi là di chuyển lên và xuống một đầu ra kỹ thuật số bên trong một vòng lặp for:

for (int i = 0; i < 50000; i++)
{
     LPC_GPIO_PORT->B[DEBUGPIN_PORT][DEBUG_PIN1] = TRUE;
     LPC_GPIO_PORT->B[DEBUGPIN_PORT][DEBUG_PIN1] = FALSE;
}

Nhưng tôi tự hỏi liệu có cách nào tốt hơn để đánh lừa GCC không.


8
Đây không phải là một cách tốt để làm chậm trễ.
Marko Buršič

3
Bạn muốn sự chậm trễ là bao lâu? Làm thế nào chính xác nó phải được?
Elliot Alderson

4
Bạn có thể đặt hẹn giờ để ngắt dòng chảy tràn / tràn cho độ trễ mong muốn của bạn và chỉ cần vào chế độ ngủ. Bộ xử lý sẽ thức dậy tại ngắt mà chỉ đơn giản là có một câu lệnh return.
Peter Smith

5
Tôi đang bỏ phiếu để đóng câu hỏi này ngoài chủ đề vì đây là vấn đề XY: Sử dụng độ trễ trong mã giao tiếp về cơ bản luôn không chính xác. Bạn cần hiểu và giải quyết vấn đề thực tế của bạn . Trong các trường hợp không liên lạc trong đó độ trễ là phù hợp, bạn sẽ thấy rằng hầu hết các thiết lập phần mềm MCU đều có cơ chế trì hoãn chờ bận.
Chris Stratton

3
@ChrisStratton Tôi không biết rằng các bài đăng kiểu XY là một lý do gần gũi hợp lệ ở mọi nơi trên mạng.
Marc.2377

Câu trả lời:


22

Bối cảnh của sự chậm trễ không phụ thuộc nội tuyến này bị thiếu ở đây. Nhưng tôi giả sử bạn cần một độ trễ ngắn trong quá trình khởi tạo hoặc phần khác của mã nơi mã được phép chặn.

Câu hỏi của bạn không nên là làm thế nào để đánh lừa GCC. Bạn nên nói với GCC những gì bạn muốn.

#pragma GCC push_options
#pragma GCC optimize ("O0")   

for(uint i=0; i<T; i++){__NOP()}

#pragma GCC pop_options

Từ đỉnh đầu của tôi, vòng lặp này sẽ có khoảng 5 * T đồng hồ.

( nguồn )


Nhận xét công bằng của Colin về một câu trả lời khác . Một NOP không được đảm bảo để thực hiện các chu kỳ trên M4. Nếu bạn muốn làm mọi thứ chậm lại, có lẽ ISB (đường ống xả) là một lựa chọn tốt hơn. Xem Hướng dẫn sử dụng chung .


Ok, đây là lần đầu tiên tôi thấy #pragma. Nếu tôi hiểu chính xác thì loại cài đặt này chỉ áp dụng cho phần mã nhỏ này. Bạn có thể tư vấn điều này qua việc thực hiện sử dụng bộ đếm thời gian không?
a_bet

2
Bạn cũng có thể sử dụng một noplệnh không thay thế sẽ không bị xóa khỏi đường ống.
Harry Beadle

4
@ Jeroen3: -O0không một cắn thấp hơn 5 * T , một cái gì đó giống như 8 hướng dẫn với một loạt các chi phí. Sẽ tốt hơn nếu tạo một vòng lặp tối ưu hóa ngắn (hoặc ít nhất là một vòng biên dịch theo cùng một cách mà không sử dụng các pragma) và sử dụng __asm__ __volatile__("");để ngăn GCC tối ưu hóa vòng lặp đi, tức là như thế này .
Groo

1
@Groo Tôi không thể tin rằng chúng ta đang thảo luận về tính hiệu quả của mã chậm trễ theo cách bẩn nhất mà con người biết đến. Nhưng có, một dây chuyền lắp ráp nội tuyến dễ bay hơi sẽ hoạt động tốt. Tôi tin rằng pragma thể hiện ý định tốt hơn cho bất kỳ độc giả mới nào.
Jeroen3

2
asm volility là cách chính xác để làm điều này nếu bạn không có chức năng trì hoãn / macro do nhà cung cấp cung cấp. Không vô hiệu hóa tối ưu hóa, ngay cả đối với 1 dòng, nó gây rối với vòng lặp for.
Navin

12

Sử dụng một bộ đếm thời gian nếu bạn có sẵn. SysTick rất đơn giản để định cấu hình, với tài liệu trong Hướng dẫn sử dụng Cortex M4 (hoặc M0 nếu bạn đang ở phần M0). Tăng một số trong ngắt của nó và trong hàm trì hoãn của bạn, bạn có thể chặn cho đến khi số đó tăng lên một số bước nhất định.

Phần của bạn chứa nhiều bộ định thời nếu hệ thống đã được sử dụng và nguyên tắc vẫn giữ nguyên. Nếu sử dụng một bộ đếm thời gian khác, bạn có thể định cấu hình nó như một bộ đếm và chỉ cần nhìn vào thanh ghi đếm của nó để tránh bị gián đoạn.

Nếu bạn thực sự muốn làm điều đó trong phần mềm, thì bạn có thể đặt asm("nop");bên trong vòng lặp của mình. nopkhông phải mất thời gian, bộ xử lý có thể loại bỏ chúng khỏi đường ống của nó mà không thực hiện nó, nhưng trình biên dịch vẫn sẽ tạo ra vòng lặp.


Systick rất đơn giản để cấu hình nhưng tôi khuyên bạn nên sử dụng một bộ đếm thời gian khác ngay khi bạn có thể vì Systick có những hạn chế liên quan đến kích thước bộ đếm và ngắt khi sử dụng cho độ trễ.
DKNguyen

Bạn thậm chí không cần sử dụng ngắt, chỉ cần thăm dò đăng ký đếm. Điều này nên được định nghĩa là một biến động, vì vậy trình biên dịch sẽ không tối ưu hóa nó ra. IMO, SysTick là một lựa chọn tốt, vì nó thường được cấu hình để cung cấp 'bộ đếm thời gian O / S', ví dụ như bộ đếm thời gian micro giây. Sau đó, bạn sẽ có những wait_microseconds(100);thứ đơn giản trong mã.
Evil Dog Pie

@EvilDogPie Không " chỉ thăm dò số lượng đăng ký " gần như tồi tệ như chỉ có một vòng lặp chặt chẽ? (mặc dù có lẽ dễ dàng hơn để ngăn chặn GCC tối ưu hóa nó đi).
TripeHound

@TripeHound Vâng, chính xác là nó có một vòng lặp chặt chẽ. Đó là những gì o / p đang yêu cầu: một vòng lặp chặt chẽ cho một độ trễ ngắn không bị loại bỏ bởi tối ưu hóa trình biên dịch. Có những nơi mà một vòng lặp chặt chẽ không phải là một cách tồi để thực hiện một độ trễ ngắn, đặc biệt là trong một hệ thống nhúng không đa nhiệm.
Evil Dog Pie

10

Không làm mất đi các câu trả lời khác ở đây, nhưng chính xác thì bạn cần độ trễ nào? Một số datasheets đề cập đến nano giây; những người khác micro giây; và vẫn còn những phần nghìn giây khác.

  • Độ trễ Nanosecond thường được phục vụ tốt nhất bằng cách thêm các hướng dẫn "lãng phí thời gian". Thật vậy, đôi khi tốc độ rất cao của vi điều khiển có nghĩa là độ trễ đã được thỏa mãn giữa các hướng dẫn "đặt chân cao, sau đó đặt pin ở mức thấp" mà bạn hiển thị. Mặt khác, một hoặc nhiều NOP, JMPhướng dẫn tiếp theo hoặc hướng dẫn lãng phí thời gian khác là đủ.
  • Độ trễ micrô giây có thể được thực hiện bằng một forvòng lặp (tùy thuộc vào tốc độ CPU), nhưng độ trễ dài hơn có thể đảm bảo chờ đợi trên một bộ đếm thời gian thực tế;
  • Độ trễ Millisecond thường được phục vụ tốt nhất bằng cách thực hiện hoàn toàn việc khác trong khi chờ quá trình hoàn tất, sau đó quay lại để đảm bảo rằng nó đã thực sự được hoàn thành trước khi tiếp tục.

Tóm lại, tất cả phụ thuộc vào ngoại vi.


3

Cách tốt nhất là sử dụng bộ định thời trên chip. Systick, RTC hoặc bộ định thời ngoại vi. Chúng có ưu điểm là thời gian chính xác, xác định và có thể dễ dàng điều chỉnh nếu thay đổi tốc độ xung nhịp của CPU. Tùy chọn, bạn thậm chí có thể để CPU ngủ và sử dụng ngắt đánh thức.

Mặt khác, các vòng lặp "làm chậm trễ", hiếm khi chính xác và đi kèm với các vấn đề khác nhau như "khớp nối chặt chẽ" với một bộ lệnh và xung nhịp CPU cụ thể.

Một số điều cần lưu ý:

  • Việc lặp đi lặp lại một chân GPIO là một ý tưởng tồi vì điều này sẽ rút ra hiện tại một cách không cần thiết và có khả năng cũng gây ra các vấn đề EMC nếu pin được kết nối với dấu vết.
  • Sử dụng hướng dẫn NOP có thể không hoạt động. Nhiều kiến ​​trúc (như Cortex M, iirc) có thể bỏ qua NOP ở cấp độ CPU và thực sự không thực hiện chúng.

Nếu bạn muốn nhấn mạnh vào việc tạo ra một vòng lặp bận rộn bẩn thỉu, thì chỉ cần volatileđủ điều kiện để lặp vòng lặp. Ví dụ:

void dirty_delay (void)
{
  for(volatile uint32_t i=0; i<50000u; i++)
    ;
}

Điều này được đảm bảo để tạo mã crap khác nhau. Ví dụ gcc ARM -O3 -ffreestandingđưa ra:

dirty_delay:
        mov     r3, #0
        sub     sp, sp, #8
        str     r3, [sp, #4]
        ldr     r3, [sp, #4]
        ldr     r2, .L7
        cmp     r3, r2
        bhi     .L1
.L3:
        ldr     r3, [sp, #4]
        add     r3, r3, #1
        str     r3, [sp, #4]
        ldr     r3, [sp, #4]
        cmp     r3, r2
        bls     .L3
.L1:
        add     sp, sp, #8
        bx      lr
.L7:
        .word   49999

Từ đó, về lý thuyết, bạn có thể tính toán số lượng tích tắc của mỗi hướng dẫn và thay đổi số ma thuật 50000 tương ứng. Đường ống, dự đoán nhánh, vv sẽ có nghĩa là mã có thể thực thi nhanh hơn chỉ là tổng số chu kỳ đồng hồ. Vì trình biên dịch quyết định liên quan đến ngăn xếp, bộ nhớ đệm dữ liệu cũng có thể đóng một phần.

Toàn bộ quan điểm của tôi ở đây là việc tính toán chính xác thời gian mà mã này sẽ thực sự mất rất nhiều thời gian. Thử nghiệm & điểm chuẩn lỗi với một phạm vi có lẽ là một ý tưởng hợp lý hơn so với việc thử tính toán lý thuyết.

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.