في المقالة الأخيرة ، تحدثت عن مقاطعة DebugMon والسجلات المرتبطة بها.في هذه المقالة سنكتب تنفيذ مصحح الأخطاء لـ UART.جزء منخفض المستوى
هنا و هنا وهناك وصفا للهيكل الطلبات والردود خادم GDB. على الرغم من أن الأمر يبدو بسيطًا ، إلا أننا لن ننفذه في وحدة التحكم الدقيقة للأسباب التالية:- تكرار البيانات الضخمة. يتم ترميز العناوين وقيم السجلات والمتغيرات كسلسلة سداسية ، مما يزيد من حجم الرسالة مرتين
- يتطلب تحليل الرسائل وجمعها موارد إضافية
- مطلوب تتبع نهاية الحزمة إما عن طريق المهلة (سيكون الموقت مشغولاً) ، أو بواسطة آلة أوتوماتيكية معقدة ، مما سيزيد من الوقت المستغرق في مقاطعة UART
للحصول على أسهل وأسرع وحدة تصحيح ، سنستخدم بروتوكولًا ثنائيًا مع تسلسلات التحكم:- 0xAA 0xFF - بداية الإطار
- 0xAA 0x00 - نهاية الإطار
- 0xAA 0xA5 - المقاطعة
- 0xAA 0xAA - تم استبداله بـ 0xAA
لمعالجة هذه التسلسلات أثناء الاستقبال ، يلزم وجود آلة أوتوماتيكية بأربع حالات:- في انتظار حرف ESC
- في انتظار الحرف الثاني من بداية تسلسل الإطار
- استقبال البيانات
- آخر مرة تم قبول حرف Esc
ولكن لإرسال الدول ، تحتاج بالفعل إلى 7:- إرسال البايت الأول بداية الإطار
- إرسال بايت الثانية بداية الإطار
- إرسال البيانات
- تقديم نهاية الإطار
- جاري إرسال حرف Esc
- إرسال بايت المقاطعة الأول
- إرسال بايت المقاطعة الثانية
دعونا نكتب تعريف الهيكل الذي ستوضع فيه جميع متغيرات الوحدة:typedef struct
{
unsigned tx:1;
unsigned StopProgramm:1;
union {
enum rx_state_e
{
rxWaitS = 0,
rxWaitC = 1,
rxReceive = 2,
rxEsc = 3,
} rx_state;
enum tx_state_e
{
txSendS = 0,
txSendC = 1,
txSendN = 2,
txEsc = 3,
txEnd = 4,
txSendS2 = 5,
txBrk = 6,
} tx_state;
};
uint8_t pos;
uint8_t buf[128];
uint8_t txCnt;
} dbg_t;
#define dbgG ((dbg_t*)DBG_ADDR)
يتم دمج حالات آلات الاستقبال والإرسال في متغير واحد حيث سيتم تنفيذ العمل في وضع نصف مزدوج. يمكنك الآن كتابة automata بأنفسهم باستخدام معالج المقاطعة.معالج UARTvoid USART6_IRQHandler(void)
{
if (((USART6->ISR & USART_ISR_RXNE) != 0U)
&& ((USART6->CR1 & USART_CR1_RXNEIE) != 0U))
{
rxCb(USART6->RDR);
return;
}
if (((USART6->ISR & USART_ISR_TXE) != 0U)
&& ((USART6->CR1 & USART_CR1_TXEIE) != 0U))
{
txCb();
return;
}
}
void rxCb(uint8_t byte)
{
dbg_t* dbg = dbgG;
if (dbg->tx)
return;
switch(dbg->rx_state)
{
default:
case rxWaitS:
if (byte==0xAA)
dbg->rx_state = rxWaitC;
break;
case rxWaitC:
if (byte == 0xFF)
dbg->rx_state = rxReceive;
else
dbg->rx_state = rxWaitS;
dbg->pos = 0;
break;
case rxReceive:
if (byte == 0xAA)
dbg->rx_state = rxEsc;
else
dbg->buf[dbg->pos++] = byte;
break;
case rxEsc:
if (byte == 0xAA)
{
dbg->buf[dbg->pos++] = byte;
dbg->rx_state = rxReceive;
}
else if (byte == 0x00)
{
parseAnswer();
}
else
dbg->rx_state = rxWaitS;
}
}
void txCb()
{
dbg_t* dbg = dbgG;
switch (dbg->tx_state)
{
case txSendS:
USART6->TDR = 0xAA;
dbg->tx_state = txSendC;
break;
case txSendC:
USART6->TDR = 0xFF;
dbg->tx_state = txSendN;
break;
case txSendN:
if (dbg->txCnt>=dbg->pos)
{
USART6->TDR = 0xAA;
dbg->tx_state = txEnd;
break;
}
if (dbg->buf[dbg->txCnt]==0xAA)
{
USART6->TDR = 0xAA;
dbg->tx_state = txEsc;
break;
}
USART6->TDR = dbg->buf[dbg->txCnt++];
break;
case txEsc:
USART6->TDR = 0xAA;
dbg->txCnt++;
dbg->tx_state = txSendN;
break;
case txEnd:
USART6->TDR = 0x00;
dbg->rx_state = rxWaitS;
dbg->tx = 0;
CLEAR_BIT(USART6->CR1, USART_CR1_TXEIE);
break;
case txSendS2:
USART6->TDR = 0xAA;
dbg->tx_state = txBrk;
break;
case txBrk:
USART6->TDR = 0xA5;
dbg->rx_state = rxWaitS;
dbg->tx = 0;
CLEAR_BIT(USART6->CR1, USART_CR1_TXEIE);
break;
}
}
كل شيء بسيط هنا. بناءً على الحدث الذي حدث ، يستدعي معالج المقاطعة إما جهاز استقبال أو جهاز إرسال. للتحقق من عمل كل شيء ، نكتب معالج حزم يستجيب ببايت واحد:void parseAnswer()
{
dbg_t* dbg = dbgG;
dbg->pos = 1;
dbg->buf[0] = 0x33;
dbg->txCnt = 0;
dbg->tx = 1;
dbg->tx_state = txSendS;
SET_BIT(USART6->CR1, USART_CR1_TXEIE);
}
تجميع وخياطة وتشغيل. النتيجة مرئية على الشاشة ، لقد نجحت.بعد ذلك ، تحتاج إلى تنفيذ نظائر الأوامر من بروتوكول خادم GDB:- قراءة الذاكرة
- سجل الذاكرة
- توقف البرنامج
- استمرار التنفيذ
- سجل نواة قراءة
- إدخال سجل kernel
- تحديد نقطة توقف
- حذف نقطة التوقف
سيتم ترميز الأمر بالبايت الأول للبيانات. تحتوي رموز الفرق على أرقام حسب ترتيب تنفيذها:- 2 - قراءة الذاكرة
- 3- سجل الذاكرة
- 4 - توقف
- 5 - تابع
- 6 - اقرأ الحالة
- 7- تثبيت نقطة توقف
- 8 - المقاصة
- 9 - الخطوة (فشل في التنفيذ)
- 10 - تسجيل الدخول (لم ينفذ)
سيتم إرسال المعلمات بالبايت التالي من البيانات.لن تحتوي الإجابة على رقم الأمر ، مثل نحن نعلم بالفعل الفريق الذي أرسل.لمنع الوحدة من رفع استثناءات BusFault أثناء عمليات القراءة / الكتابة ، يجب إخفاءها عند استخدامها على M3 أو أعلى ، أو كتابة معالج HardFault لـ M0.memcpy آمنةint memcpySafe(uint8_t* to,uint8_t* from, int len)
{
static const uint32_t BFARVALID_MASK = (0x80 << SCB_CFSR_BUSFAULTSR_Pos);
int cnt = 0;
SCB->CFSR |= BFARVALID_MASK;
uint32_t mask = __get_FAULTMASK();
__disable_fault_irq();
SCB->CCR |= SCB_CCR_BFHFNMIGN_Msk;
while ((cnt<len))
{
*(to++) = *(from++);
cnt++;
}
SCB->CCR &= ~SCB_CCR_BFHFNMIGN_Msk;
__set_FAULTMASK(mask);
return cnt;
}
يتم تنفيذ إعداد نقطة التوقف بالبحث عن أول سجل غير نشط FP_COMP.تثبيت نقاط التوقف
dbg->pos = 0;
addr = ((*(uint32_t*)(&dbg->buf[1])))|1;
for (tmp = 0;tmp<8;tmp++)
if (FP->FP_COMP[tmp] == addr)
break;
if (tmp!=8)
break;
for (tmp=0;tmp<NUMOFBKPTS;tmp++)
if (FP->FP_COMP[tmp]==0)
{
FP->FP_COMP[tmp] = addr;
break;
}
break;
يتم التنظيف من خلال البحث عن نقطة التوقف المحددة. يؤدي إيقاف التنفيذ إلى تعيين نقطة توقف على جهاز الكمبيوتر الحالي. عند الخروج من مقاطعة UART ، تدخل النواة على الفور إلى DebugMon_Handler.معالج DebugMon نفسه بسيط للغاية:- 1. يتم تعيين العلم لوقف التنفيذ.
- 2. يتم مسح جميع نقاط التوقف المحددة.
- 3. في انتظار اكتمال إرسال رد على الأمر في uart (إذا لم يكن لديه الوقت للذهاب)
- 4. يبدأ إرسال تسلسل المقاطعة
- 5. في الحلقة ، يتم استدعاء معالجات أجهزة الإرسال والاستقبال حتى يتم تخفيض إشارة التوقف.
رمز معالج DebugMonvoid DebugMon_Handler(void)
{
dbgG->StopProgramm = 1;
for (int i=0;i<NUMOFBKPTS;i++)
FP->FP_COMP[i] = 0;
while (USART6->CR1 & USART_CR1_TXEIE)
if ((USART6->ISR & USART_ISR_TXE) != 0U)
txCb();
dbgG->tx_state = txSendS2;
dbgG->tx = 1;
SET_BIT(USART6->CR1, USART_CR1_TXEIE);
while (dbgG->StopProgramm)
{
if (((USART6->ISR & USART_ISR_RXNE) != 0U)
&& ((USART6->CR1 & USART_CR1_RXNEIE) != 0U))
rxCb(USART6->RDR);
if (((USART6->ISR & USART_ISR_TXE) != 0U)
&& ((USART6->CR1 & USART_CR1_TXEIE) != 0U))
txCb();
}
}
قراءة تسجيلات kernel من C-Syshny عندما تكون المهمة صعبة ، لذلك أعدت كتابة بعض التعليمات البرمجية على ASM. والنتيجة أن لا DebugMon_Handler ولا معالج المقاطعة UART ولا الأجهزة تستخدم المكدس. هذا يبسط تعريف قيم تسجيل النواة.خادم Gdb
يعمل جزء وحدة التحكم الدقيقة في المصحح ، والآن دعنا نكتب الرابط بين IDE ووحدتنا.من الصفر ، لا معنى لكتابة خادم تصحيح ، لذا لنأخذ خادمًا جاهزًا كأساس. نظرًا لأن لدي أكثر خبرة في تطوير البرامج على .net ، فقد اتخذت هذا المشروع كأساس وأعدت صياغته لمتطلبات أخرى. سيكون من الأصح إضافة دعم للواجهة الجديدة في OpenOCD ، لكن الأمر سيستغرق المزيد من الوقت.عند بدء التشغيل ، يسألك البرنامج عن أي منفذ COM للعمل معه ، ثم يبدأ في الاستماع على منفذ TCP 3333 وينتظر اتصال عميل GDB.تتم ترجمة جميع أوامر بروتوكول GDB إلى بروتوكول ثنائي.ونتيجة لذلك ، تم إصدار تطبيق تصحيح أخطاء UART قابل للتطبيق.استنتاج
اتضح أن تصحيح وحدة التحكم نفسها ليس أمرًا معقدًا للغاية.نظريًا ، من خلال وضع هذه الوحدة في قسم ذاكرة منفصل ، يمكن أيضًا استخدامها وميض وحدة التحكم.تم نشر ملفات المصدر على GitHub للدراسة العامةلجزء متحكمخادم GDB