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.


Messages - dec

Pages: 1 2 [3] 4 5 ... 10
17
เกิดจากใน 1 statement มีการ access ตัวแปร volatile มากกว่า 1 ตัวครับ

เดาว่าน่าจะเป็น CNTRH กับ CNTRL เพราะส่วนใหญ่ Register มักจะมีการนิยามให้เป็น volatile

การที่เรานิยามตัวแปรให้เป็น volatile คือการนิยามว่าตัวแปรตัวนั้นมีการเปลี่ยนแปลงได้ตลอดเวลา
คาดการณ์ไม่ได้ ดังนั้น compiler จึงไม่ optimize ตัวแปรที่นิยามว่าเป็น volatile เพื่อให้เวลา
cpu ทำการ access ตัวแปร volatile จะต้องไปอ่านค่าจาก memory จริง ๆ ของตัวแปร volatile ตัวนั้น

การ access ตัวแปร volatile มากกว่า 1 ตัว ใน 1 statement นั้นไม่ได้ผิด แต่จะทำให้เกิดการคาดเดาพฤติกรรม
ของโปรแกรมไม่ได้

เช่น statement นี้
Code: [Select]
tim1_rec_[0] = (( TIM1->CNTRH << 8 ) | (TIM1->CNTRL) );
ใน statement นี้มีการอ่านค่าจาก CNTRH และ CNTRL พร้อมกัน แต่ในความเป็นจริงแล้ว
การทำงานของ cpu มันจะอ่านทีละตัว จะอ่านตัวไหนก่อนก็ขึ้นอยู่กับ compiler ที่ใช้ในการ compile และ
ช่วงเวลาที่ cpu อ่านค่าจาก volatile ตัวแรกอยู่ ค่าของ volatile ตัวที่ 2 อาจเปลี่ยนแปลงไปแล้ว

เรื่องนี้ไม่ได้เป็นเรื่องร้ายแรงถ้าเรารู้ตัวว่าเรากำลังทำอะไรอยู่ เช่นกรณีนี้ เรารู้ว่าเราตัวว่าเราอ่านค่า counter ของ timer
ซึ่งมีการเพิ่มขึ้นเป็นเชิงเส้นเสมอ แต่ compiler มันไม่รู้ด้วย IAR มันเลยเตือนไปตาม ISO/ANSI standard

แต่ถ้าจะต้องการเลี่ยงไม่ให้เกิดการ warning เราก็ต้องทำให้ใน 1 statement มีการ access ตัวแปร volatile ตัวเดียว

เช่น
Code: [Select]
tim1_rec_[0] = TIM1->CNTRH;
tim1_rec_[0] = (( tim1_rec_[0] << 8 ) | (TIM1->CNTRL));

หรือเอาตัวแปรธรรมดามาช่วย
Code: [Select]
uint8_t tmp1, tmp2;
tmp1 = TIM1->CNTRH;
tmp2 = TIM1->CNTRL;
tim1_rec_[0] = ((tmp1  << 8 ) | (tmp2);

หรือถ้าเรามันใจว่าโปรแกรมเราจะทำงานได้ถูกต้องแน่นอน เราสามารถไปปิดการ warning สำหรับ case นี้ได้
โดยคลิกขวาที่ Project เลือก Option ไปที่ C/C++ Compiler เลือกแทบ Diagnostics
แล้วในช่อง Suppress these diagnostics: ให้พิมพ์ Pa082 แล้ว OK
เท่านี้มันจะไม่ warning case Pa082 อีกแล้ว

18
ไม่มีโดยตรงครับ

ในทาง software ภาษา C มีคล้าย ๆ กันคือตัวแปรที่มี const นำหน้า คือตัวแปรคงที่
ต้องมีการกำหนดค่าเริ่มต้นตั้งแต่ประกาศตัวแปร compiler จะห้ามไม่ให้เปลี่ยนค่า
ตัวแปรคงที่ขณะ runtime

การใช้ const กับตัวแปร global และ local มีลักษณะต่างกันนิดหน่อย

ถ้าใช้ const กับตัวแปร global ตัวแปรตัวนั้นจะถูกเก็บลง flash แทนที่จะเก็บไว้ใน ram
เพราะมันจะไม่มีการเปลี่ยนค่าอีก ตอน compile มีค่าเป็นอะไร ก็ต้องมีค่าเท่านั้นไปตลอด

ถ้าใช้ const กับตัวแปร local ตัวแปรตัวนั้นจะยังเก็บไว้ใน ram เพราะค่าเริ่มต้นของ
ตัวแปร local มันจะถูกกำหนดได้ในขณะ runtime เท่านั้น ยังไงก็ต้องเก็บไว้ใน ram
แต่ถึงอย่างนั้นเราก็เอา pointer มาล้วงข้างได้ โดยประกาศตัวแปร pointer ที่ไม่ได้เป็น const
แล้วเอาไปชี้ตัวแปร const local ตัวนั้น แล้วเปลี่ยนค่าผ่าน pointer เอา แบบนี้
Code: [Select]
void function(void)
{
  const int x = 100;
  int *ptr = (int *)&x;

  printf("X is %d.\r\n", x);
  *ptr = 101;
  printf("X is %d.\r\n", x);
}

ผลลัพธ์คือ
Code: [Select]
X is 100.
X is 101.


ในทาง hardware ก็มีวิธีอยู่ 2 วิธีคือ

1. ถ้า mcu ตัวนั้นโปรแกรม flash ตัวเองได้ ก็เอาค่านั้นๆ ที่ไม่ต้องการเปลี่ยนอีก
โปรแกรมลง flash memory ไปเลย แล้วเวลาใช้งานก็เอาตัวแปร pointer to const
ชี้ไปยัง address flash นั้นๆ

สมมุติผมเก็บ address 0x10000000 ไว้โปรแกรมข้อมูล ข้อมูลผมเป็น struct ชื่อ config
ผมก็ประกาศตัวแปร pointer to const ชี้ไป address 0x10000000
Code: [Select]
const struct config *config_storage = (const struct config*)0x10000000;
หลังจากนั้นก็ใช้ mcu โปรแกรม flash address 0x10000000 ใหม่โดยกรอกข้อมูลใส่ตัวแปร struct config
แล้วก็โปรแกรมมันลงไปทั้ง struct โดย cast type เป็น uint8_t* หรือ void* ก็แล้วแต่
ฟังก์ชั่นโปรแกรม flash มันจะต้องการ
Code: [Select]
struct config tmp;

//ใส่ข้อมูลที่จะเขียนในตัวแปร tmp

flash_program((uint8_t*)&tmp, sizeof(struct config));

2. คือ mcu ระดับ high end เช่น arm cortex m4, m7 มันจะมี Memory Protection Unit (MPU)
ใช้สำหรับป้องกันการเข้าถึง memory address ต่างๆ นั่นเอง มันสามารถกำหนดได้ว่าจาก address region ไหน
ถึงไหน ทำอะไรได้ และห้ามทำอะไรได้ เช่น เขียนได้อย่างเดียว, อ่านได้อย่างเดียว, ห้าม execute เป็นต้น

19
มัน convert ไฟล์ elf ไปเป็นไฟล์ .hex ไม่ได้น่ะครับ เหมือนกับว่ามันหาไฟล์ ielftool.exe ไม่เจอ
ปกติจะอยู่ใน "C:\Program Files (x86)\IAR Systems\Embedded Workbench 8.1\arm\bin"
ผมเองก็ไม่เคยเจออาการนี้

ถ้าไม่จำเป็นต้องใช้ไฟล์ hex ก็คลิกขวาที่ Project เลือก Option
แล้วไปเมนู Output Converter แล้วเอาเครื่องหมายถูกหน้า Generate additional output ออกครับ
ถ้า download โปรแกรมผ่าน STLink หรือ Jtag ใช้ไฟล์ .out ก็เพียงพอแล้ว

20
ผมก็ไม่ทราบเหมือนกันครับ
ปกติผมใช้ IAR เป็นหลัก

22
ผมก็ไม่เคยใช้ STM8 มาก่อน แต่เท่าที่รู้ Timer interrupt มันจะมี Capture Compare Channel
ให้ใช้เหมือนกับ STM32 ครับ ต้องดูใน Datasheet ว่า Timer ตัวไหนมี Capture Compare Channel ให้ใช้บ้าง
มันจะเขียนอยู่ในตาราง TIM timer feature ใน Column CAPCOM channel ครับ


ปกติ CAPCOM channel จะใช้ทำ PWM แต่มันสามารถ Interrupt ได้ด้วยครับ โดย Config CAPCOM channel
ให้ทำงานเป็น Output Comparator แล้วก็ Set ค่า CCR ให้แต่ละ Channel พอ Timer มัน Count มาถึงค่า CCR
ของ Channel ไหนมันก็จะเด้งเข้า Interrupt ครับ

ผมไม่รู้ว่าตอนนี้ STM8 เค้าใช้ Library อันไหนกันนะครับ อันนี้ผมก็อปมาจากตัวอย่าง TIM2_OC_InactiveMode
ใน STM8S/A Standard peripheral library
https://www.st.com/en/embedded-software/stsw-stm8069.html

Code ส่วน Init
Code: [Select]
#define CCR1_Val ((uint16_t)976)
#define CCR2_Val ((uint16_t)488)
#define CCR3_Val  ((uint16_t)244)

/**
  * @brief  Configure Output Compare Active Mode for TIM2 Channel1, Channel2 and
  *         channel3 
  * @param  None
  * @retval None
  */
static void TIM2_Config(void)
{
  /* Time base configuration */
  TIM2_TimeBaseInit(TIM2_PRESCALER_2048, 65535);

  /* Prescaler configuration */
  TIM2_PrescalerConfig(TIM2_PRESCALER_2048, TIM2_PSCRELOADMODE_IMMEDIATE);

  /* Output Compare Active Mode configuration: Channel1 */
  /*
  TIM2_OCMode = TIM2_OCMODE_INACTIVE
       TIM2_OCPolarity = TIM2_OCPOLARITY_HIGH
       TIM2_Pulse = CCR1_Val
*/
  TIM2_OC1Init(TIM2_OCMODE_INACTIVE, TIM2_OUTPUTSTATE_ENABLE,CCR1_Val, TIM2_OCPOLARITY_HIGH);
  TIM2_OC1PreloadConfig(DISABLE);

  /* Output Compare Active Mode configuration: Channel2 */
 
  /*TIM2_Pulse = CCR2_Val;  */
  TIM2_OC2Init(TIM2_OCMODE_INACTIVE, TIM2_OUTPUTSTATE_ENABLE,CCR2_Val, TIM2_OCPOLARITY_HIGH);
  TIM2_OC2PreloadConfig(DISABLE);

  /* Output Compare Active Mode configuration: Channel3 */
  /*TIM2_Pulse = CCR3_Val  */
  TIM2_OC3Init(TIM2_OCMODE_INACTIVE, TIM2_OUTPUTSTATE_ENABLE,CCR3_Val, TIM2_OCPOLARITY_HIGH);
  TIM2_OC3PreloadConfig(DISABLE);

  TIM2_ARRPreloadConfig(ENABLE);
 
  /* TIM IT enable */
  TIM2_ITConfig(TIM2_IT_CC1, ENABLE);
  TIM2_ITConfig(TIM2_IT_CC2, ENABLE);
  TIM2_ITConfig(TIM2_IT_CC3, ENABLE);
 
  /* TIM2 enable counter */
  TIM2_Cmd(ENABLE);
}

Code ใน Interrupt
Code: [Select]
/**
  * @brief  Timer2 Capture/Compare Interrupt routine
  * @param  None
  * @retval None
  */
 INTERRUPT_HANDLER(TIM2_CAP_COM_IRQHandler, 14)
{
  if (TIM2_GetITStatus(TIM2_IT_CC1) != RESET)
  {
    /* Clear TIM2 Capture Compare1 interrupt pending bit*/
    TIM2_ClearITPendingBit(TIM2_IT_CC1);

    /* PG.5 toggles after 1000 ms */
    GPIO_WriteLow(GPIOG, GPIO_PIN_5);

  }
 
if (TIM2_GetITStatus(TIM2_IT_CC2) != RESET)
  {
    /* Clear TIM2 Capture Compare2 interrupt pending bit*/
    TIM2_ClearITPendingBit(TIM2_IT_CC2);
 
    /* PG.6 toggles after 500 ms */
    GPIO_WriteLow(GPIOG, GPIO_PIN_6);
  }
 
if (TIM2_GetITStatus(TIM2_IT_CC3) != RESET)
  {
    /* Clear TIM2 Capture Compare3 interrupt pending bit*/
    TIM2_ClearITPendingBit(TIM2_IT_CC3);
   
    /* PG.7 toggles after 250 ms */
    GPIO_WriteLow(GPIOG, GPIO_PIN_7);
  }
}

23
หลังจาก set ค่าให้ delta แล้ว ลองเขียน code เอาตัวแปร delta ไปใช้ดูครับ
ลองเอา delta ไปเซ็ตให้ตัวแปรอื่นก็ได้

บางที compiler มันเห็นว่าตัวแปรไม่ได้ถูกใช้มันก็ตัด code ที่ set ค่าให้ตัวแปรนั้นทิ้งไปดื้อ ๆ

24
อันนี้เสริมให้ครับ

ตอน cpu ดึงข้อมูลหรือเขียนข้อมูลจาก memory นั้น cpu ก็จะมองแค่ว่าเป็นข้อมูลมีขนาดกี่ bytes
แต่ตอนที่มันป้อนเข้า ALU มันถึงจะมีการบอก ALU ว่าให้คำนวนโดยมองว่าข้อมูลนี้เป็น float, int
หรือ char นะ เพราะฉะนั้นไม่ว่าข้อมูลใน memory เราเป็น Type อะไรเราสามารถเปลี่ยน Type มันได้ตลอดเวลา

ยกตัวอย่างนะครับ สมมุติว่ามีตัวแปร int x มีค่า 10000 (0x2710) อยู่ใน address 0x00100000

int x = 10000;

ข้อมูลใน memory ก็จะถูกเก็บแบบนี้

Code: [Select]
x:   0x00100000 : 0x10
     0x00100001 : 0x27
     0x00100002 : 0x00
     0x00100003 : 0x00

ถ้า printf ตัวแปร x ออกมาก็จะได้

printf("%d\n", x);

Code: [Select]
10000
printf("0x%.8X\n", x);

Code: [Select]
0x00002710
cpu จะรู้แค่ว่า x อยู่ address 0x00100000 นะครับ แต่ตอน store หรือ load ก็จะมีการระบุขนาดข้อมูลว่าจะ store หรือ load กี่ byte ถ้าระบุว่า 1 byte ก็จะ store หรือ load ข้อมูลจาก address 0x00100000 แค่ตำแหน่งเดียว ถ้าระบุว่า 2 bytes ก็จะ store หรือ load ข้อมูลจาก address 0x00100000 และ 0x00100000 + 1 ถ้าระบุว่า 4 bytes ก็จะ store หรือ load ข้อมูลจาก address 0x00100000, 0x00100000 + 1, 0x00100000 + 2 และ 0x00100000 + 3

ในตอนนี้ผมก็สามาถเอาตัวแปร pointer ที่มี Type อื่นไปชี้ที่ address ของตัวแปร x ได้
สมมุติผมใช้ unsigned char *c ไปชี้ที่ &x

unsigned char *c = (unsigned char*)&x;

Code: [Select]
x:   0x00100000 : 0x10  <- c
     0x00100001 : 0x27
     0x00100002 : 0x00
     0x00100003 : 0x00

ตอนนี้ผมก็สามารถเข้าถึงตัวแปร x แบบทีละ bytes ได้แล้ว ผมสามารถ printf x ผ่าน c ได้เช่นกัน

printf("c[0]: %d (0x%.2X)\n", c[0], c[0]);
printf("c[1]: %d (0x%.2X)\n", c[1], c[1]);
printf("c[2]: %d (0x%.2X)\n", c[2], c[2]);
printf("c[3]: %d (0x%.2X)\n", c[3], c[3]);

Code: [Select]
c[0]: 16 (0x10)
c[1]: 39 (0x27)
c[2]: 0 (0x00)
c[3]: 0 (0x00)

ถ้าผมแอบเปลี่ยนค่า c[2] และ c[3] เป็น 1 กับ 2 ข้อมูลใน memory ก็จะเปลี่ยนไปดังนี้

c[2] = 1;
c[3] = 2;

Code: [Select]
x:   0x00100000 : 0x10  <- c
     0x00100001 : 0x27
     0x00100002 : 0x01
     0x00100003 : 0x02

แล้วถ้าผม printf x อีกที

printf("%d\n", x);

Code: [Select]
33629968
printf("0x%.8X\n", x);

Code: [Select]
0x02012710
ค่าของตัวแปร x ก็จะเปลี่ยนไปด้วย

เกริ่นมาซะยาว มาเข้าเรื่องดีกว่า มาถึงตัวแปร float กันบ้าง แน่นอนว่า physical ของ memory
มันก็เก็บข้อมูลเป็น bytes เหมือนๆ กับตัวแปร int นั่นแหละครับ

สมมุติผมมีตัวแปร float ชื่อ f มีค่า 1.0 อยู่ที่ address 0x00200000

float f = 1.0;

printf("%f\n", f);

Code: [Select]
1.000000
printf("0x%.8X\n", f);

Code: [Select]
0x00000000
ตรงนี้อาจจะแปลกใจว่าทำไม print hex ของ float มันได้ 0 หมด ไม่ต้องตกใจครับ
float ก็มีการเก็บค่าลง memory ปกตินี่แหละ แต่บังเอิญว่า printf มันแสดงให้ไม่ได้
เราก็ใช้วิธีล้วงข้างเหมือนเดิม คือเอา pointer to unsigned char ไปชี้ที่ &f

unsigned char *d = (unsigned char*)&f;

แล้วก็สั่ง print hex ออกมาดูซะ

printf("d[0]: %d (0x%.2X)\n", d[0], d[0]);
printf("d[1]: %d (0x%.2X)\n", d[1], d[1]);
printf("d[2]: %d (0x%.2X)\n", d[2], d[2]);
printf("d[3]: %d (0x%.2X)\n", d[3], d[3]);

Code: [Select]
d[0]: 0 (0x00)
d[1]: 0 (0x00)
d[2]: 128 (0x80)
d[3]: 63 (0x3F)

ออกมาแล้ว ใช่แล้วครับ เลขฐาน 2 ของ float 1.0 คือ 0x3F800000 นั่นเอง
ดังนั้นเวลาจะเอาค่า float 1.0 ไปเก็บลงใน memory ก็ต้องเก็บ 0x3F800000 นั่นเองครับ แบบนี้

Code: [Select]
f:   0x00200000 : 0x00  <- d
     0x00200001 : 0x00
     0x00200002 : 0x80
     0x00200003 : 0x3F

ทีนี้ผมลองทำตรงข้ามกันบ้าง ผมสร้างตัวแปร float g มา 1 ตัว มีค่าเริ่มต้น 0 แล้วจะเอา pointer to unsigned char e ไปชี้ที่ &g แล้วก็ใส่ค่า 0x3F800000 ลงไป

float g = 0;
unsigned char *e = (unsigned char*)&g;

printf("%f\n", g);

e[0] = 0x00;
e[1] = 0x00;
e[2] = 0x80;
e[3] = 0x3F;

printf("%f\n", g);

Code: [Select]
0.000000
1.000000

ก็จะเห็นว่าเราจะได้ float ที่มีค่า 1.0 เหมือนตัวแปร f นั่นเอง
ทีนี้ผมจะลองปรับค่า float ในตัวแปร g จาก 1.0 เป็น -1.0 ผ่าน pointer e ผมก็ต้องรู้ format วิธีการเก็บค่าของ float ก่อน
ตัวแปร float มีลักษณะดังนี้

31        : signed
30 - 24 : exponential
23 - 0   : mantissa

ผมต้องการเปลี่ยนจาก + เป็น - ผมต้องเซ็ตบิท 31 ให้เป็น 1 ผมก็แก้จาก e[3]

e[3] |= 0x80;

ค่า hex ของตัวแปร g ก็จะเปลี่ยนเป็น 0xBF800000 พอลอง printf ออกมา

printf("%f\n", g);

Code: [Select]
-1.000000
ก็จะเห็นว่ามันกลายเป็นเลข -1.0 เรียบร้อยแล้ว

....

สรุปเลยก็คือ ค่าใน memory ไม่ว่ามันจะเป็น type อะไร สุดท้ายมันก็เป็นเลขฐาน 2 ทั้งหมด
เราสามารถประกอบหรือแยกส่วนมันได้ตลอดเวลา เพียงแค่เราต้องรู้ format ของมันเท่านั้นครับ
ซึ่งการเก็บค่าและโหลดค่าจาก memory มันไม่ได้มีการเปลี่ยนแปลงค่าอะไร
เพราะฉะนั้นเราเรียงลำดับ byte ให้ถูกก็พอครับ

สุดท้ายตัวแปร void อันนี้ไม่มีครับ ที่เค้าใช้คือ pointer to void ครับ ซึ่ง 2 ตัวแปรนี้ต่างกันนะ
เราไม่สามารถประกาศ void a; เพราะไม่รู้ว่า void ต้องมีขนาดเท่าไหร่ แล้วต้องจองพื้นที่แค่ไหน
แต่ถ้าเป็น void *a; มันหมายถึง ตัวแปรที่ใช้ "เก็บค่า address" แต่ไม่รู้ว่า address ปลายทางเป็น type อะไร
จะเห็นว่า ตัวแปรที่ใช้ "เก็บค่า address" เท่านี้ก็รรู้แล้วว่าต้องจอง memory ให้ตัวแปรนี้เท่าไหร่
ก็คือ 4 bytes (ในระบบ 32bits) แต่การไม่รู้ว่า address ปลายทางเป็น type อะไร
ก็มีผลให้เราไม่สามารถใช้ memory นี้ได้ตรงๆ แล้วจะใช้ยังไงนั้น
จากก่อนหน้านี้จะเห็นว่า address ปลายทาง เป็น type อะไรนั้นไม่สำคัญ
เราสามารถเอา pointer type อื่น ชี้แล้วใช้งานเป็น type ของ pointer ตัวใหม่ได้เลย

ถามว่าทำแบบนี้ทำไม เพราะว่าเค้าไม่ต้องการระบุว่า memory เป้าหมายมี type เป็นอะไร
มันจะ make sense กว่าในบาง function

เช่น eeprom_write_object กับ eeprom_read_object ทั้ง 2 function ไม่ได้ต้องการ type อะไรเป็นพิเศษ
แค่ต้องการพื้นที่เก็บข้อมูล 1 ก้อน มีขนาดแค่ไหนเท่านั้น ตัวข้อมูลเป็นอะไรก็ได้
จะเห็นว่าใร function eeprom_write_object กับ eeprom_read_object เค้าก็ประกาศ pointer to unsigned char
มาชี้ไปยัง memory เดียวกับ pointer to void เพื่อใช้งาน memory ตรงนั้นเป็น unsigned char ด้วยเหมือนกัน

นอกจากนั้นก็ยังมี function

void* malloc(size_t size);
void free(void *mem);

จะเห็นว่าเค้าเลือกใช้ void* แทนที่จะระบุ type ตรงๆ เพราะ malloc คือการจอง memory แล้ว user
จะเอาไปใช้เป็น type อะไร ก็เป็นเรื่องของ user นอกจากนั้น parameter void* มันจะไม่มีการเตือนว่าใส่ pointer ผิด type ด้วย
เช่น

int *a;

a = (int*)malloc(sizeof(int));
free(a);

ใน function free จะเห็นว่า parameter a มี type เป็น int* ไม่ตรงกับนิยามที่เป็น void* แต่ compiler จะไม่มีการเตือนอะไร
มันจะสะดวกกว่าการระบุ type ไปตรงๆ ครับ

สมมุติว่า เปลี่ยน void เป็น unsigned char แทนแบบนี้

unsigned char* malloc(size_t size);
void free(unsigned char* mem);

เวลาใช้งานก็จะเป็นแบบนี้

int *a;

a = (int*)malloc(sizeof(int));
free((unsigned char*)a);

ก่อนเราจะป้อน a ให้ free เราต้อง cast type ของ a ให้ตรงกับที่ free ต้องการก่อน มันเลยไม่สะดวกครับ

ปล. ไม่รู้จะอธิบายง่ายๆ ยังไง เลยพิมพ์ซะยืดยาวเลย

Pages: 1 2 [3] 4 5 ... 10