จะเก็บตัวแปร float ใน eeprom ได้อย่างไรหรือครับ [stm8]

Started by TaoTao, July 20, 2018, 05:28:58 AM

Previous topic - Next topic

TaoTao

กำลังหาวิธีเก็บ ตัวแปร float 32 bits ใน eeprom อ่ะครับ
lib eeprom ของ stm มีแต่ function ที่เก็บตัวแปร uint32_t
ซึ่งมันก็ 4 byte เท่ากับ float

FLASH_ProgramWord(uint16_t addr, uint32_t data);

แต่จะเก็บอย่างไรอ่ะครับ ใครพอดี idea หรือเคยทำบ้าง

ขอบคุณล่วงหน้าครับ ^_^

TaoTao

อ่อ ได้ไอเดียจาก forum นี้ ตอนนี้ได้แว้ว ๆ ๆ ๆ ๆ
https://www.microchip.com/forums/m622032.aspx


#include <stddef.h>

extern unsigned char eeprom_read(unsigned int ee_addr);
extern void eeprom_write(unsigned int ee_addr, unsigned char byte);

void eeprom_read_object(unsigned int ee_addr, void *obj_p, size_t obj_size)
{
     unsigned char *p = obj_p;

     while (obj_size--) {
         *p++ = eeprom_read(ee_addr++);
     }
}

void eeprom_write_object(unsigned int ee_addr, void *obj_p, size_t obj_size)
{
     unsigned char *p = obj_p;

     while (obj_size--) {
         eeprom_write(ee_addr++, *p++);
     }
}

void eeprom_write_object(unsigned int ee_addr, void *obj_p, size_t obj_size)
{
     unsigned char *p = obj_p;

     while (obj_size--) {
         eeprom_write(ee_addr++, *p++);
     }
}

float my_float_var;

void main(void)
{
     /* Restore my_float_var from EEPROM after power-up.
      */
     eeprom_read_object(0, &my_float_var, sizeof my_float_var);

     my_float_var *= 3;

     /* Record my_float_var's current value in EEPROM.
      */
     eeprom_write_object(0, &my_float_var, sizeof my_float_var);

     /* ... */
}

ผมลอง copy & paste ตามสูตรที่ถนัด โคดเค้าใช้ได้จริงครับ(แต่ต้องปรับการเรียก Function บ้าง)

สำหรับ STM8 เร็วขึ้นอีกนิส เพราะ internal eeprom สั่ง write ได้ทีละ 4 bytes เลย ไม่ต้องใช้ while และ sizeof..

เค้าเอาตัวแปร void มารับค่า และ link ด้วย pointer_var  *p
ซึ่งสามารถเปลี่ยน type ได้เลย ตาม *p ที่สร้างขึ้น เพิ่งเคยเห็นโคดแบบนี้ ง่ายดี ^_^!

ว่าแต่ มันมีตัวแปร void ด้วยหรือนี่  :o :o :o

dec

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

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

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

int x = 10000;

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

x:   0x00100000 : 0x10
     0x00100001 : 0x27
     0x00100002 : 0x00
     0x00100003 : 0x00


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

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

10000

printf("0x%.8X\n", x);

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;

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]);

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;

x:   0x00100000 : 0x10  <- c
     0x00100001 : 0x27
     0x00100002 : 0x01
     0x00100003 : 0x02


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

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

33629968

printf("0x%.8X\n", x);

0x02012710

ค่าของตัวแปร x ก็จะเปลี่ยนไปด้วย

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

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

float f = 1.0;

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

1.000000

printf("0x%.8X\n", f);

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]);

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 นั่นเองครับ แบบนี้

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);

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);

-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 ต้องการก่อน มันเลยไม่สะดวกครับ

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


TaoTao

โอ้ว ความรู้เพียบบบ
"เราสามารถเอา pointer type อื่น ชี้แล้วใช้งานเป็น type ของ pointer ตัวใหม่ได้เลย"

ผมได้ลอง โค้ด และ write ดูแหล่วครับ ไม่ฟ้อง error ด้วย ใช้ได้จริงๆ เด็ดเลย

มุมนี้ของ pointer ผมว่า โค-ตะ-ระ มีประโยชน์เลย โค้ด ดูสะอาดตาขึ้น มั่กๆ
ขอบคุณมากมายก๊าบ  ;D ;D  :o :o :o