Main Menu
Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - dec

#1
ARM Processors / Re: STM32F1 interrupt
September 20, 2020, 02:11:55 PM
Quote from: tha on September 20, 2020, 11:21:53 AM
ท่านใดให้อธิบายการเกิดอินเตอรัพท์สักหน่อย ก็จะอธิบายตามความเข้าใจนะ ถ้าเกิด interrupt อย่างเช่น EXTI0 interrupt, Program Counter จะกระโดดมาที่ Address 0x0000 0058 ก่อน แล้วที่ Address 0x0000 0058 นี้ จะบรรจุคำสั่งให้กระโดดไปที่โปรแกรมย่อย Interrupt Service Routine EXTI0 อีกที เมื่อปฏิบัติตามคำสั่งใน Interrupt Service Routine EXTI0 เสร็จแล้ว ก็จะกระโดกลับไปที่โปรแกรม main(); ในบรรทัดก่อนที่จะเกิดการอินเตอรัพท์ เพื่อปฏิบัติคำสั่งต่อไป
ปล. อ่านแล้วเข้าใจกันไหม พอจะจำได้เคยแปล Atmega328p มาก่อน คงจะเหมือนกัน ท่านมีอะไรเสริมก็ว่ามาได้นะครับ

ใน ARM Cortex M ขั้นตอนตรงนี้มันไม่อยู่ในส่วนที่เราเข้าไป track ดูได้ครับ
ใน Address 0x00000058 จะแค่เก็บ Address ของ Interrupt Service Routine EXTI0 เท่านั้นครับ
ไม่มี Code สำหรับ Jump ไปที่ Interrupt Service Routine ของ EXTI0 แต่อย่างใดครับ
ผมก็ไม่แน่ใจว่ามันมีการ Jump ไปยัง code ส่วนไหนก่อนรึเปล่า ใน Code เราจะเห็นแค่ว่าโปรแกรมทำงานอยู่ใน main
แล้วอยู่ๆ ก็มาโผล่ใน Interrupt Service Routine เลย พอทำงานใน Interrupt Service Routine เสร็จ ก็กลับมาที่ main

บางทีการโหลด Address แล้ว Jump ไป Interrupt Service Routine อาจทำโดย Hardware แต่เราสามารถดูได้ว่า
ทันทีที่มีเข้าสู่ Interrupt Service Routine มันจะมีการ Backup Register R0 - R3, R12, LR, PSR และ Address
ที่จะ Return กลับไป ลง Stack ส่วนเวลา Return กลับไปยัง Main มันไม่ได้ Return ไป Address ที่จะกลับไปใน Main โดยตรง
มันจะ Return ไปส่วนที่เรียกว่า EXC_RETURN code (มี Address อยู่ในช่วง 0xF0000000 - 0xFFFFFFFF) ซึ่งมันจะเป็นการ
execute แบบพิเศษ ไม่ได้เป็น instruction set แบบปกติ
#2
ARM Processors / Re: STM32F1 ADC HAL
September 19, 2020, 10:03:15 PM
Quote from: tha on September 19, 2020, 03:38:14 PM
ผมลองเปลี่ยนชื่อฟังชั่นใหม่เป็นอย่างนี้  void HAL_ADC_MspInit1(ADC_HandleTypeDef* hadc); แล้วมันไม่ทำงานนะ จอขาวเลย ผมเอา 1 ออก ก็ทำงานได้ปกติ แสดงว่าของเขาทำมาถูกแล้ว ผมก็ยังมือใหม่ยังต้องงม HAL ไปอีกนาน คุณ dec พอจะทราบการทำงานของมันมั๊ย ท่านใดพอจะทราบ ช่วยอธิบายหน่อยครับ

ถ้าลองไปดูฟังชั่น HAL_ADC_Init ในไฟล์ stm32fxxx_hal_adc.c ลองไล่ดูจะเห็นว่ามันมีการเรียก HAL_ADC_MspInit
ิอยู่ครับ เวลาเรียก HAL_ADC_Init มันจะไปเรียก HAL_ADC_MspInit ให้ด้วยเลย

แล้วทีนี้ถ้าเปลี่ยนชื่อเป็น HAL_ADC_MspInit1 มันยัง compile ได้ปกติ ไม่ได้ฟ้อง error อะไร ลองไล่ code ไปอีกหน่อย
จะเจอ

__weak void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(hadc);

  /* NOTE : This function should not be modified. When the callback is needed,
            function HAL_ADC_MspInit must be implemented in the user file.
   */
}


มันคือ HAL_ADC_MspInit นั่นแหละครับ แต่มี attribute __weak นำหน้า (สำหรับ GNU C Compiler และ IAR ตัวใหม่ๆ จะใช้ __attribute__((weak)) )
มันหมายถึงถ้าทั้ง project ไม่มี HAL_ADC_MspInit อยู่เลย linker จะใช้ฟังชั่นนี้ แต่ถ้ามีการนิยาม HAL_ADC_MspInit ไว้ที่อื่นอีก
อันที่มี attribute __weak จะถูกตัดออก


การเขียน code ลักษณะนี้ยังใช้ในไฟล์ startup ที่มี vector table อีกด้วยครับ

g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
.word MemManage_Handler
.word BusFault_Handler
.word UsageFault_Handler
.word 0
        ....


ปกติเวลาเราจะประกาศฟังชั่น interrupt เราก็แค่ประกาศชื่อให้ตรงกับชื่อที่อยู่ใน vector table แต่ฟังชั่นไหน
ไม่ได้ประกาศมันก็ไม่ได้ error อะไรเพราะมันมี weak ฟังชั่นอยู่เหมือนกัน แต่จะแตกต่างจากภาษาซีนิดหน่อยตรง
assembly มันไม่ได้ประกาศฟังชั่นมาทั้งอัน มันประกาศ alias ของฟังชั่นได้


  .weak NMI_Handler
.thumb_set NMI_Handler,Default_Handler

  .weak HardFault_Handler
.thumb_set HardFault_Handler,Default_Handler

  .weak MemManage_Handler
.thumb_set MemManage_Handler,Default_Handler

  .weak BusFault_Handler
.thumb_set BusFault_Handler,Default_Handler

.weak UsageFault_Handler
.thumb_set UsageFault_Handler,Default_Handler

.weak SVC_Handler
.thumb_set SVC_Handler,Default_Handler

.weak DebugMon_Handler
.thumb_set DebugMon_Handler,Default_Handler

.weak PendSV_Handler
.thumb_set PendSV_Handler,Default_Handler
       
        ...

.weak CRS_IRQHandler
.thumb_set CRS_IRQHandler,Default_Handler

.weak I2C4_EV_IRQHandler
.thumb_set I2C4_EV_IRQHandler,Default_Handler

.weak I2C4_ER_IRQHandler
.thumb_set I2C4_ER_IRQHandler,Default_Handler


หรือพูดง่ายๆ ก็คือมันสามารถประกาศ Default_Handler ให้มีหลายๆ ชื่อได้นั่นเอง แล้วชื่่ออื่นๆ ก็มี attribute weak กำกับไว้
เวลาเราไปประกาศฟังชั่น interrupt ในไฟล์ stm32xxxx_it.c ซ้ำกับชื่อฟังชั่น interrupt ในไฟล์ startup มันก็จะตัดชื่อในไฟล์ startup ออก
แล้วไปใช้ฟังชั่น interrupt ในไฟล์ stm32xxxx_it.c แทน
#3
รูปในโพสแรกคือ Register ของ CPU ครับ ปกติเราจะไม่ค่อยได้มายุ่งในส่วนนี้
ถ้าเราไม่ได้เขียน assembly หรือเขียน OS

รายละเอียดก็ตามที่คุณ tha ลงมาในโพสที่ 2 เลยครับ แต่จะเห็นว่ามันมีส่วนแปลกๆ อยู่
เช่น Stack pointer มันมี 2 ตัว ตรงนี้ arm ทำมาเพื่อรองรับการทำงานของ OS ครับ
โดยปกติ CPU จะใช้ MSP เป็น Stack pointer ตัวหลักในการทำงานทั้งหมด แต่เราสามารถ
แยกให้การทำงานปกติใช้ PSP และเวลาอยู่ใน Interrupt จะใช้ MSP ได้ครับ เป็นการแยก
ให้การทำงาน 2 ส่วนใช้ stack memory คนละส่วน โดยเฉพาะพวก RTOS ที่แต่ละ Task
เราจะแบ่ง Stack Memory ไว้ให้แต่ละ Task ไม่เยอะ แล้วระหว่างนั้นถ้ามี Interrupt เกิดขึ้น
มันจะได้ไม่ไปแย่ง Stack Memory ของ Task มาใช้ใน Interrupt

Link Register ถ้าอธิบายง่ายๆ ก็คือเวลา code มีการ Call Function มันก็จะเอา Address
ที่ต้อง Return กลับไปฝากไว้ใน Link Register ถ้ามีการ Call Function ใน Function อีกที
มันก็จะเก็บค่า Address ที่อยู่ใน Link Register ลง Stack Memory ก่อนแล้วค่อยฝาก Address ใหม่ทับลงไป

PSR หรือ Program Status Registers ตัวนี้ประหลาดดี ตรงมี Register จริงๆ ตัวเดียว แต่เวลาเรียกใช้จะแยกเป็น 3 ตัวตือ
- APSR - Application Program Status Registers ตัวนี้เหมือนเป็น Flags เวลาเราบวกลบคูณหารเลข
มันจะมี Bit คอยบอกว่าผลลัพทํมีค่าติดลบนะ ผลลัพท์มีค่าเป้น 0 ผลลัพท์มีการทดเลข ผลลัพท์ Overflow อะไรพวกนี้
- IPSR - Interrupt Program Status Registers ตัวนี้จะบอกว่า Interrupt ที่รันอยู่นี่คือ Interrupt อะไร
มันจะมีค่าเป็นเลข Number ของ Interrupt อยู่
- EPSR - Execution Program Status Registers อันนี้ไม่เคยใช้ครับ ไม่รู้เหมือนกัน ;D

PRIMASK คือ Interrupt mask register มีให้เซ็ตแค่บิตเดียว ถ้าเซ็ตคือ Block Interrupt ทั้งหมดยกเว้น
NMI กับพวก fault Handler ทั้งหลาย

FAULTMASK เหมือน PRIMASK แต่เอาไว้ Block พวก NMI กับพวก fault Handler ทั้งหลาย

BASEPRI มันนี้พิเศษหน่อย คือให้เรา Block Interrupt โดยการระบุ Priority เช่น Interrupt จะมี
Priority ได้ตั้งแต่ 15 - 0 (0 คือสูงที่สุด) ถ้าเซ็ต BASEPRI = 5 Interrupt ที่มี Priority ตั้งแต่ 15 ถึง 5 จะถูก Block
แต่ถ้า BASEPRI = 0 จะหมายถึงการเลิกใช้งาน BASEPRI เพราะฉะนั้น BASEPRI จะใช้ Block Interrupt ที่มี Priority 0 ไม่ได้

สุดท้ายคือ CONTROL Register อันนี้เอาไว้คุม Stack pointer ว่าจะให้ใช้ MSP หรือ PSP นั่นแหละครับ
แล้วก็ยังกำหนดให้ Thread ทำงานแบบ Privileged หรือ Unprivileged ได้ด้วย (แทรกนิดนึง ปกติเวลา
CPU ทำงานใน function main เราจะเรียกว่ามันทำงานใน Thread mode ส่วนเวลาที่ทำงานอยู่ใน Interrupt
เราจะเรียกว่า Handler mode ครับ) ปกติ CPU จะรัน Thread แบบ Privileged คือเข้าถึง Register หรือ
Config ค่า Register ได้ทุกอย่าง แต่ถ้าเราปรับเป็นแบบ Unprivileged จะทำให้ Thread เข้าถึง Register
บางตัวไม่ได้ มีปะรโยชน์คือเป็นการจำกัดสิทธิ์ของโปรแกรมที่ทำงานใน MCU ครับ ถ้าเราซีเรียสเรื่องความปลอดภัยมากๆ

ปล. เอาแค่นี้ก่อน ช่วงนี้ผมไม่ค่อยว่างเท่าไหร่ครับ ถ้าสนใจเรื่องสถาปัตยกรรมของ ARM Cortex M ผมแนะนำหนังสือ
The Definitive Guide to ARM® Cortex®-M3 and Cortex®-M4 Processors ของ โจเซฟ ยู ครับ เขียนได้ละเอียดดีครับ
#4
ARM Processors / Re: STM32F103 Clock RCC
September 15, 2020, 08:56:56 PM
Quote from: tha on September 15, 2020, 08:50:49 AM
ก็มาดูแต่ละจุดกันนะ ว่ามันคืออะไรกันบ้าง
จุดแรกก็เลือก OSCILLATORTYPE เราก็เลือกเป็น crytal 8MHz ภายนอก ดูที่บิต register ก็ไม่มีบิตนี้อยู่นะ ก็ละเป็นที่เข้าใจก่อน มากมายเหลือเกิน หาไม่ไหว ท่านใดทราบก็อธิบายเสริมมานะครับ
Quote
oscinitstruct.OscillatorType  = RCC_OSCILLATORTYPE_HSE;

Quote from: tha on September 15, 2020, 09:44:36 AM
อันนี้คงจะเป็น library ของมันว่าเรา จะใช้ clock ไปทางไหนบ้าง ไม่มีบิต register ให้เลือก
Quote
clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);

อันนี้เข้าใจถูกแล้วครับ มันแค่ให้เราเลือกว่าเราจะ config อะไรบ้าง เพราะ struct มันมี config หลายอย่าง
แล้วบางอย่างค่า 0 มันก็มีความหมาย เพื่อไม่ให้สบสันว่าเราต้องการ set ค่า 0 หรือว่าเราไม่ได้ต้องการ config อะไร


Quote from: tha on September 15, 2020, 09:44:36 AM
FLASH LATENCY = 2 ผมก็ไม่ทราบนะ ท่านใดทราบช่วยเสริมมาด้วยครับ
Quote
if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2)!= HAL_OK)

Flash latency คือ ช่วงเวลาในการรอ output จาก Flash memory ให้เสถียรครับ ผมเคยอธิบายเรื่องนี้ไปในกระทู้นี้
https://www.electoday.com/index.php/topic,10210.msg40608.html#msg40608


Quote from: tha on September 15, 2020, 10:45:25 AM
เอา RCC_PLL_MUL8 แก้มาเป็น RCC_PLL_MUL2 หลอด LED ก็กระพริบในเวลาเท่าเดิม แสดงว่าโปรแกรมย่อย HAL_Delay(); เขาคงทำมาได้ยืดหยุ่นดี
Quote
HAL_Delay(500);

HAL_Delay ใช้ Interrupt SysTick ในการนับเวลาครับ มันเอาค่า System Clock มาคำนวณค่า Reload Value
เพื่อให้ได้คาบเวลาที่ 1ms เสมอครับ มันอยู่ในฟังก์ชั่น HAL_Init(); ลองเข้าไปดูได้ครับ
#5
ARM Processors / Re: GPIO STM32F1
September 11, 2020, 09:50:34 PM
volatile เป็นการนิยามว่า memory หรือตัวแปรส่วนนี้มีความแปรปรวนครับ
จะมีผลก็ต่อเมื่อเราเปิด optimizer ครับ compiler จะไม่ optimize ตัวแปรหรือ memory ที่เป็น volatile
จะจัดการอย่างตรงไปตรงมา

โดยปกติ compiler จะมองว่า memory หรือตัวแปรจะเปลี่ยนค่าก็ต่อเมื่อถูกเซ็ตค่า

เช่น
a = 1;

แต่ compiler จะพิจารณาแค่ไฟล์ต่อไฟล์เท่านั้น เช่น

ในไฟล์ main.c มีการประกาศตัวแปร global
int a = 1;

แล้วก็มีการใช้งาน
while(a);

แต่ไม่มีการเปลี่ยนค่า a ใน main.c อีก

ส่วนในไฟล์ interrupt.c มีการเปลี่ยนค่าตัวแปร a
a = 0;

ถ้าเราเปิด optimizer ขณะที่ compiler ทำการ compile ไฟล์ main.c compiler ก็จะมองแค่ไฟล์ main.c ไฟล์เดียว
ไม่ได้สนใจว่า interrupt.c จะมี code อะไร ทำให้ compile เข้าใจว่า a มีค่าเป็น 1 แล้วก็ไม่มีการเปลี่ยนค่าอีก
compiler เลยทำการแทนที่ a ใน code main.c ด้วยค่า 1 เลยทั้งหมด แม้โปรแกรมจะทำงานมาถึงไฟล์ interrupt.c
และเปลี่ยนค่าตัวแปร a ไป ก็ไม่สามารถทำให้หลุดจาก while ได้

อีกกรณีก็พวก register ที่จะถูกเซ็ตค่าโดย hardware ตอน compile ตัว compiler จะมองว่ามันเป็นแค่
memory ตำแหน่งหนึ่ง แล้วก็มีการอ่านค่ามาอย่างเดียว ไม่มีการเซ็ตค่า ก็เสี่ยงที่จะถูก optimize ได้
เราจึงมักจะนิยาม register ให้เป็น volatile เพื่อป้องกันไม่ให้ compiler optimize ครับ

ส่วนใน function delay นั่น ถ้าตัวแปร nCount ไม่ใช่ volatile optimizer มันจะมองว่า while มันเป็น loop เปล่าๆ
และ nCount หลังจากหลุด while ไปแล้วมันก็ไม่ได้ใช้งานอะไร มันจะลบ loop ทิ้งเอาดื้อๆ เพราะมองว่ามันเสียเวลาโดยเปล่าประโยชน์
แต่ถ้า nCount เป็นตัวแปร volatile มันก็จะตัด loop ออกไม่ได้ เพราะ compiler จะไม่ optimize แต่แปร nCount และตัวแปร nCount
ต้องถูกนับลดและอ่านค่าอย่างตรงไปตรงมา ทำให้ loop ยังอยู่ครับ
#6
ARM Processors / Re: GPIO STM32F1
September 08, 2020, 08:34:14 PM
Quote from: tha on September 08, 2020, 04:10:57 PM
GPIO_InitTypeDef GPIO_InitStruct;  // มันหมายถึงอะไร ท่านใดทราบก็แนะนำกันด้วยครับ เสริมมาเลย ไม่ต้องเกรงใจ จะได้แน่นๆ

มันคือตัวแปร Structure ที่รวม config parameter ต่างๆ ของ GPIO ครับ มีไว้เพื่อใช้ Init GPIO โดยเฉพาะครับ

Style ของ ST เค้าชอบออกแบบ Function ให้การ Init มันจบใน Function เดียว อย่าง HAL_GPIO_Init
ถ้า implement ให้ส่งค่า config เป็น parameter ทั้งหมด parameter มันจะดูยืดยาวและรกมาก

HAL_GPIO_Init(GPIOA, GPIO_PIN_4, GPIO_MODE_OUTPUT_PP, GPIO_PULLUP, GPIO_SPEED_FREQ_HIGH, 0x00);

ST เลยนิยาม Structure มาตัวหนึ่งมารวม config ต่างๆ ที่เป็นไว้ ให้เซ็ตค่า
แล้วก็ส่ง Pointer to Structure ตัวนี้เป็น Parameter ไปแทน

HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
#7
ARM Processors / Re: กลับมาทำ STM32
September 06, 2020, 07:22:20 PM
ใช่ครับ STM32CubeMX ตัวใหม่ๆ รวมถึง CubIDE ด้วย ต้องใช้ OS 64bit ครับ

OpenOCD เป็น Open Source On-Chip Debugging ครับ มันจะทำหน้าที่เป็น GDB Server ให้ และรันอยู่บนคอมเรานี่แหละ
ไม่ต้องใช้เน็ตแต่อย่างใดครับ

ในปัจจุบันการ Debug ภาษา C เราแทบจะใช้ GDB แทบทั้งสิ้นครับ (ย่อมาจาก GNU Debugger) แต่ก็จะมีการ Custom โปรแกรม
ให้เหมาะกับ environment ต่างๆ กันไป สำหรับใน MCU แล้ว GDB Server จะคอยรับคำสั่งจาก GDB Client แล้วไปสั่งการ MCU ให้หยุด
หรือทำงาน หรืออ่านค่าจาก Memory และ Register ผ่าน JTAG Communication Protocol



ในตอนที่เราสั่งให้ Eclipse เข้า Debug Mode ตัว Eclipse มันก็จะไปเรียก GDB Server (OpenOCD) ขึ้นมาทำงาน
แล้วก็จะเรียก GDB Client (arm-none-eabi-gdb.exe) ขึ้นมาเพื่อสั่งการ GDB Server แล้วก็จะเอา Response ต่างๆ
ที่ GDB Client ได้รับมาแสดงผลให้เราดูบนโปรแกรม Eclipse IDE ครับ

OpenOCD มันก็มีข้อดีตรงที่มันฟรี และมันก็รองรับ MCU กับ JTAG Adapter หลายชนิดมาก
แต่ประสิทธิภาพมันยังเป็นรองพวก GDB Server จากผู้ผลิต JTAG Adapter โดยตรงอย่างมากครับ
ถ้าไม่มีทางเลือกจริงๆ หรือโปรแกรมที่จะ Debug ไม่ซับซ้อนมันก็ใช้ได้ครับ ผมเคยต้อง Debug MicroPython
บน STM32 ด้วย OpenOCD บอกตามตรงว่าอารมณ์เสียมาก
#8
ARM Processors / Re: กลับมาทำ STM32
September 06, 2020, 11:14:33 AM
ลองไปใช้ STM32CubeIDE ดูก็ได้นะครับ มันคือ Atollic TrueSTUDIO ที่ ST ซื้อมาพัฒนาต่อ
ตัวโปรแกรมก็เป็น Eclipse base เหมือนกับ System Workbench for STM32 เลย วิธีใช้งานเหมือนๆ กัน

ข้อดีหลักๆ ของ STM32CubeIDE คือเวลาใช้ ST Link Debug มันไม่ได้ใช้ OpenOCD
มันมี ST ink GDB Server ติดมาให้เลย ประสิทธิภาพค่อนข้างจะดีกว่าการใช้ OpenOCD
ถ้าจะใช้ Jlink ก็แนะนำให้ติดตั้ง plugin Segger ใน Eclipse Embedded CDT ลงไปเพิ่มครับ