Saya sedang mengerjakan proyek yang melibatkan STM32 MCU (pada papan STM32303C-EVAL tepatnya) yang harus menanggapi gangguan eksternal. Saya ingin reaksi terhadap interupsi eksternal secepat mungkin. Saya telah memodifikasi contoh pustaka periferal standar dari halaman web ST dan program saat ini hanya menyalakan LED di setiap sisi kenaikan berturut-turut pada PE6:
#include "stm32f30x.h"
#include "stm32303c_eval.h"
EXTI_InitTypeDef EXTI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
static void EXTI9_5_Config(void);
int main(void)
{
/* Initialize LEDs mounted on STM32303C-EVAL board */
STM_EVAL_LEDInit(LED1);
/* Configure PE6 in interrupt mode */
EXTI9_5_Config();
/* Infinite loop */
while (1)
{
}
}
// Configure PE6 and PD5 in interrupt mode
static void EXTI9_5_Config(void)
{
/* Enable clocks */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOD | RCC_AHBPeriph_GPIOE, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
/* Configure input */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOD, &GPIO_InitStructure);
/* Connect EXTI6 Line to PE6 pin */
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource6);
/* Configure Button EXTI line */
EXTI_InitStructure.EXTI_Line = EXTI_Line6;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
/* Enable and set interrupt to the highest priority */
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
Penangan interrupt terlihat seperti ini:
void EXTI9_5_IRQHandler(void)
{
if((EXTI_GetITStatus(EXTI_Line6) != RESET))
{
/* Toggle LD1 */
STM_EVAL_LEDToggle(LED1);
/* Clear the EXTI line 6 pending bit */
EXTI_ClearITPendingBit(EXTI_Line6);
}
}
Dalam kasus khusus ini, interupsi dibuat oleh generator fungsi eksternal yang dapat diprogram berjalan pada 100 Hz. Setelah memeriksa respons MCU pada osiloskop, saya agak terkejut bahwa dibutuhkan hampir 1,32 kita untuk MCU untuk mulai memproses interupsi:
Dengan MCU berjalan pada 72 MHz (saya telah memeriksa output SYSCLK pada pin MCO sebelumnya) ini berjumlah hampir 89 siklus clock. Bukankah seharusnya respons MCU terhadap interupsi jauh lebih cepat?
PS Kode ini dikompilasi dengan IAR Embedded Workbench dan dioptimalkan untuk kecepatan tertinggi.
if{}
pernyataan diperlukan karena rutin interupsi tidak tahu apa sumber interupsi itu.Jawaban:
Masalah
Nah Anda harus melihat fungsi yang Anda gunakan, Anda tidak bisa hanya membuat asumsi pada kecepatan kode yang belum Anda lihat:
Ini adalah fungsi EXTI_GetITStatus:
Seperti yang Anda lihat, ini bukan hal sederhana yang hanya membutuhkan satu atau dua siklus.
Berikutnya adalah fungsi sakelar LED Anda:
Jadi di sini Anda memiliki beberapa pengindeksan array dan baca modifikasi tulis untuk beralih LED.
HAL sering kali menghasilkan jumlah overhead yang baik karena mereka harus mengurus pengaturan yang salah dan penggunaan fungsi yang salah. Memeriksa parameter yang diperlukan dan juga terjemahan dari parameter sederhana ke bit dalam register dapat mengambil jumlah komputasi yang serius (setidaknya untuk waktu kritis setidaknya mengganggu).
Jadi dalam kasus Anda, Anda harus menerapkan bare metal interrupt Anda secara langsung pada register dan tidak bergantung pada HAL.
Contoh solusi
Misalnya sesuatu seperti:
Catatan: ini tidak akan mengubah LED tetapi hanya mengaturnya. Tidak ada tombol atom yang tersedia di GPIO STM. Saya juga tidak suka
if
konstruksi yang saya gunakan, tetapi menghasilkan perakitan lebih cepat daripada pilihan sayaif (EXTI_PR_PR6 == (EXTI->PR & EXTI_PR_PR6))
.Varian toggle bisa berupa sesuatu di sepanjang baris ini:
Menggunakan variabel yang berada dalam RAM daripada menggunakan
ODR
register harus lebih cepat, terutama ketika Anda menggunakan 72 MHz, karena akses ke periferal dapat lebih lambat karena sinkronisasi antara domain jam yang berbeda dan jam periferal hanya berjalan pada frekuensi yang lebih rendah. Tentu saja, Anda tidak boleh mengubah status LED di luar interupsi agar toggle berfungsi dengan benar. Atau variabel harus bersifat global (maka Anda harus menggunakanvolatile
kata kunci saat mendeklarasikannya) dan Anda harus mengubahnya di mana saja.Perhatikan juga, bahwa saya menggunakan C ++, karenanya
bool
dan bukanuint8_t
tipe atau serupa untuk mengimplementasikan flag. Meskipun jika kecepatan adalah perhatian utama Anda, Anda mungkin harus memilih untukuint32_t
bendera karena ini akan selalu disejajarkan dengan benar dan tidak menghasilkan kode tambahan saat mengakses.Penyederhanaan dimungkinkan karena Anda mudah-mudahan tahu apa yang Anda lakukan dan selalu tetap seperti itu. Jika Anda benar-benar hanya memiliki satu interupsi yang diaktifkan untuk penangan EXTI9_5, Anda dapat menyingkirkan pemeriksaan register yang tertunda sama sekali, mengurangi jumlah siklus lebih jauh.
Ini mengarah ke potensi optimasi lain: gunakan jalur EXTI yang memiliki interupsi tunggal seperti salah satu dari EXTI1 ke EXTI4. Di sana Anda tidak perlu melakukan pemeriksaan apakah jalur yang benar telah memicu interupsi Anda.
sumber
volatile
kompiler tidak diperbolehkan untuk mengoptimalkan banyak fungsi di atas dan jika fungsi tidak diimplementasikan secara inline di header, panggilan biasanya tidak dioptimalkan juga.Mengikuti saran PeterJ saya telah menghilangkan penggunaan SPL. Keseluruhan kode saya terlihat seperti ini:
dan instruksi perakitan terlihat seperti ini:
Ini sedikit meningkatkan masalah, karena saya telah berhasil mendapatkan respons di ~ 440 ns @ 64 MHz (yaitu, 28 siklus clock).
sumber
BRR |=
danBSRR |=
menjadi adilBRR =
danBSRR =
, register itu hanya menulis, kode Anda membacanya,ORR
nilainya dan kemudian menulis. yang dapat dioptimalkan untuk satuSTR
instruksi.Jawabannya sangat mudah: pustaka HAL (atau SPL) yang hebat. Jika Anda melakukan sesuatu yang sensitif terhadap waktu, gunakan register periferal yang kosong. Maka Anda akan mendapatkan latensi yang benar. Saya tidak mengerti apa gunanya menggunakan perpustakaan konyol ini untuk beralih pin !! atau untuk memeriksa daftar patung.
sumber
Ada beberapa kesalahan dalam kode Anda = Daftar BSRR hanya menulis. Jangan gunakan | = operator, cukup sederhana "=". Ini akan mengatur / mengatur ulang pin yang tepat. Nol diabaikan.
Ini akan menghemat beberapa jam. Petunjuk lain: pindahkan tabel vektor & interupsi rutin Anda ke CCMRAM. Anda akan menyimpan beberapa kutu lainnya (flash waitstates dll)
PS Saya tidak bisa berkomentar karena saya tidak memiliki reputasi yang cukup :)
sumber