Hướng dẫn cách nạp code cho stm-32c8t6 bằng file hex năm 2024

Lưu ý: Bạn có thể mất vài lần cắm nếu phần mềm không kết nối được với MCU do một vài lỗi(Cắm dây lỏng, đấu nối dây sai hay thiếu, mạch bị hư…). Hãy reset lại mạch và kiểm tra kỹ lại.

  • Kích thước bộ nhớ flash của MCU sẽ xuất hiện. Nhấn next để tiếp tục.

Hướng dẫn cách nạp code cho stm-32c8t6 bằng file hex năm 2024

  • Loại MCU và các page nhớ xuất hiện, nhấn Next để tiếp tục.

Hướng dẫn cách nạp code cho stm-32c8t6 bằng file hex năm 2024

  • Xuất hiện Tab tiếp theo với nhiều lựa chọn, ở đây mình chọn là Nạp code mới và chọn đường dẫn đến file hex mà mình đã chuẩn bị. Nhấn Next để tiếp tục

Hướng dẫn cách nạp code cho stm-32c8t6 bằng file hex năm 2024

  • Nhấn Next để nạp, sau khi nạp xong sẽ có thông báo đã nạp xong.

Hướng dẫn cách nạp code cho stm-32c8t6 bằng file hex năm 2024

  • Sau khi nạp xong, đưa mạch trở về trạng thái Main flash memory(BOOT0 nối đất, BOOT1 tùy chỉnh). Reset mạch, mạch chạy bình thường.
Ưu điểm của phương pháp này là không cần những mạch nạp đắt tiền vẫn nạp được code cho MCU, ứng dụng để tiết kiệm chân VĐK(PA14, PA13, PB3.. những chân cho mạch nạp) mà vẫn đầy đủ các chức năng cơ bản. Đó cũng là công nghệ giúp bảo mật các thiết kế(tìm hoài trên mạch không thấy chân nạp thông thường….).
Nhược điểm: khá nhiều thao tác(cắm, cắm, reset, lỏng dây..), rườm rà, không debug được lỗi, dễ xảy ra sai sót, cũng như không nhiều chức năng được thêm vào. Và thường chỉ được sử dụng khi không có mạch nạp.

Mình muốn chia sẻ những kiến thức có được trong quá trình học tập và làm việc, hy vọng điều đó sẽ giúp ích cho các bạn.

Khi làm 1 project có tính thương mại, việc update và bảo trì chương trình cho khách hàng nhất thiết phải có, bạn không thể đưa mạch nạp cho khách rồi bảo họ làm theo hướng dẫn để nạp file hex được. Thay vào đó chúng ta sẽ update firmwave khách qua chương trình bootloader

Một vài ứng dụng của chương trình bootloader

  • Nạp code không cần mạch nạp gốc, chỉ cần 1 module UART-USB bất kì
  • Làm mạch nạp offline (máy nạp code hàng loạt)
  • Tạo chức năng FOTA (Firmware Over The Air) tức là cập nhật từ xa qua internet, wifi, bluetooth

Về phần lí thuyết các bạn có thể tìm hiểu về OTA tại đây:

  • https://tapit.vn/cap-nhat-chuong-trinh-tu-xa-tren-vi-dieu-khien-stm32-p1/
  • https://tapit.vn/cap-nhat-chuong-trinh-tu-xa-tren-vi-dieu-khien-stm32-firmware-air-fota-p2/
  • https://tapit.vn/cap-nhat-chuong-trinh-tu-xa-tren-vi-dieu-khien-stm32-firmware-air-fota-p3/
  • https://tapit.vn/cap-nhat-chuong-trinh-tu-xa-tren-vi-dieu-khien-stm32-firmware-air-fota-p4/
  • https://tapit.vn/cap-nhat-chuong-trinh-tu-xa-tren-vi-dieu-khien-stm32-firmware-air-fota-p5/

Do stm32f103c8t6 có 128 Page bắt đầu từ 0 và mỗi page là 128KB nên mình sẽ tạo 1 buffer đúng bằng kích cỡ của 1page, bởi mỗi lần ghi vào flash chúng ta phải ghi cả page chứ không thể ghi lẻ tẻ được.

Khơi tạo ram cho 1 page

unsigned char code_save[1024];

Chương trình xóa page, do mỗi lần ghi vào xóa page đó đi

void Erase_PAGE(uint32_t addr)

{

Flash_Unlock();

while((FLASH->SR&FLASH_SR_BSY));

FLASH->CR |= FLASH_CR_PER; //Page Erase Set

FLASH->AR \= addr; //Page Address

FLASH->CR |= FLASH_CR_STRT; //Start Page Erase

while((FLASH->SR&FLASH_SR_BSY));

FLASH->CR &= ~FLASH_SR_BSY;

FLASH->CR &= ~FLASH_CR_PER; //Page Erase Clear

Flash_Lock();

}

Chương trình đọc 1byte kiểu 16 ra từ 1 địa chỉ

uint16_t F_read(uint32_t addr)

{

uint16_t* val \= (uint16_t *)addr;

return *val;

}

Chương trình ghi data vào 1 page + check lại, chúng ta sẽ cần nhập vào tham số là page vần ghi và size (thực ra cái này size mặc định là 1024 rồi)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

char Write_PAGE(uint32_t size,uint32_t page)

{

uint32_t ADDRESS_PAGE \= ((uint32_t)0x08000000 + (page*1024)); //tinh dia chi cua page

uint32_t addr;

uint16_t data;

Flash_Erase(ADDRESS_PAGE);

Flash_Unlock();

for(int i\=0;i<size/2;i++)

{

addr \= ADDRESS_PAGE + (i*2);

FLASH->CR |= FLASH_CR_PG; /*!< Programming */

while((FLASH->SR&FLASH_SR_BSY));

data\=((uint16_t)code_save[i*2+1]<<8) | ((uint16_t)code_save[i*2]);

*(__IO uint16_t*)addr \= data;

while((FLASH->SR&FLASH_SR_BSY));

FLASH->CR &= ~FLASH_CR_PG;

}

//check lại

for(int i\=0;i<size/2;i++)

{

addr \= ADDRESS_PAGE + (i*2);

data\=((uint16_t)code_save[i*2+1]<<8) | ((uint16_t)code_save[i*2]);

uint16_t test2\=F_read(addr);

if(data != test2)

{

Flash_Lock();

return 1;

}

}

Flash_Lock();

return 0 ;

}

Phương pháp giao tiếp với máy tính

Đầu tiên phần mềm sẽ gửi dữ liệu gồm số lượng byte của code, ở dưới đọc xong sẽ phẩn hồi OK cho app biết nó sẵn sàng nhận data. Sau đó app sẽ gửi liên tục gửi 1page (1024byte) xuống cho ở dưới nạp vào flash, sau khi nạp xong sẽ nhảy tới phần vùng thực thi code

Chương trình ngắt UART nhận dữ liệu

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

{

if(flag\==0)

{

rx_buff[count++]\=rx_data;

if(rx_data \== '\n')

{

flag\=1;

int ts\=1;

size_program\=0;size_count\=0;end\=0;

page_write\=PROGRAM_START_PAGE;

for(count\=count-2;count\>0;count--)

{

if(rx_buff[count] \== '=')break;

size_program+= (rx_buff[count]-48)*ts;

ts*=10;

}

if(size_program\>1024)HAL_UART_Receive_IT(&huart2,code_save,1024);

else HAL_UART_Receive_IT(&huart2,code_save,size_program);

HAL_UART_Transmit(&huart2,(uint8_t *)"OK MEN !\r\n",10,100);

}

else

{

HAL_UART_Receive_IT(&huart2,(uint8_t *)&rx_data,1);

}

}

else if(flag\==1)

{

if(end\==1)

{

HAL_UART_Receive_IT(&huart2,(uint8_t *)&rx_data,1);

if(Write_PAGE(1024,page_write) \== 0)HAL_UART_Transmit(&huart2,(uint8_t *)"Read OK !\r\n",11,100);

else

{

flag\=0;

HAL_UART_Transmit(&huart2,(uint8_t *)"Write Fall !\r\n",14,100);

HAL_UART_Receive_IT(&huart2,(uint8_t *)&rx_data,1);

}

clear_buffer();

flag\=0;

goto_aplication\=1; //chuyen toi ct BOOT

return;

}

size_count+=1024;

if((size_count+1024) < size_program)

HAL_UART_Receive_IT(&huart2,code_save,1024);

else

{

HAL_UART_Receive_IT(&huart2,code_save,size_program-size_count);

end\=1;

}

if(Write_PAGE(1024,page_write) \== 0)HAL_UART_Transmit(&huart2,(uint8_t *)"Read OK !\r\n",11,100);

else

{

flag\=0;

HAL_UART_Transmit(&huart2,(uint8_t *)"Write Fall !\r\n",14,100);

HAL_UART_Receive_IT(&huart2,(uint8_t *)&rx_data,1);

}

page_write++;

clear_buffer();

}

}

Sau khi nhận dầy đủ mình sẽ set cờ goto_aplication=1

Ở hàm main check cờ và nhảy tới vùng code thực thi

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

if(goto_aplication\==1)

{

void (*SysMemBootJump)(void);

volatile uint32_t addr \= PROGRAM_START_ADDRESS;

if defined(USE_HAL_DRIVER)

HAL_RCC_DeInit();

endif /* defined(USE_HAL_DRIVER) */

if defined(USE_STDPERIPH_DRIVER)

RCC_DeInit();

endif /* defined(USE_STDPERIPH_DRIVER) */

SysTick->CTRL \= 0;

SysTick->LOAD \= 0;

SysTick->VAL \= 0;

__disable_irq();

if defined(STM32F4)

SYSCFG->MEMRMP \= 0x01;

endif

if defined(STM32F0)

SYSCFG->CFGR1 \= 0x01;

endif

SysMemBootJump \= (void (*)(void)) (*((uint32_t *)(addr + 4)));

__set_MSP(*(uint32_t *)addr);

SysMemBootJump();

}

Demo chương trình c# gửi dữ liệu

Hướng dẫn cách nạp code cho stm-32c8t6 bằng file hex năm 2024
Thiết kế giao diện

Hàm mở code

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

private void button3_Click(object sender, EventArgs e)

{

OpenFileDialog diaglog \= new OpenFileDialog();

diaglog.Filter \= "HEX files|*.hex";

if (diaglog.ShowDialog() \== DialogResult.OK)

{

for (int i \= 0; i < max_size; i++)

data_send[i] \= 0xFF; //clear bộ đệm gửi

program_size \= 0;

hex_data \= File.ReadAllText(diaglog.FileName);

textBox1.Text \= hex_data;

int _2c_line1 \= hex_data.IndexOf(":");

int _2c_line_end \= hex_data.LastIndexOf(":");

hex_data \= hex_data.Substring(_2c_line1+1, _2c_line_end-1); // bỏ dòng 1 và dòng cuối

_2c_line1 \= hex_data.IndexOf(":");

_2c_line_end \= hex_data.LastIndexOf(":");

hex_data \= hex_data.Substring(_2c_line1, _2c_line_end - _2c_line1 - 1 ); // bỏ dòng 1 và dòng cuối

//tiến hành lọc lấy phần mã hex

while (true)

{

int count \= 0;

count \= hex_data.IndexOf(":"); //lấy vị tri bắt đầu

if (count != -1) //nếu còn dữ liệu

{

byte[] data_num \= StringToByteArrayFastest(hex_data.Substring(count + 1, 2)); //kiểm tra số lượng byte trong hàng

int byte_num \= (int)data_num[0];

byte[] data_line \= new byte[byte_num]; //số lượng byte data trong line đó

data_line \= StringToByteArrayFastest(hex_data.Substring(count + 9, byte_num * 2));

//đưa data vào mảng

for(int i\=0;i< byte_num;i++)

data_send[program_size++] \= data_line[i];

//lấy xong data thì cắt bỏ luôn

hex_data \=hex_data.Substring(count+1);

}

else

{

label1.Text \= "0/" + program_size.ToString() + " byte";

return;

}

}

}

}

Hàm nạp code xuống

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

private async void button4_Click(object sender, EventArgs e)

{

// nạp xuống từng byte data một

int dem \= 0;

//gửi thông điệp upload code

serialPort.Write("Upload=" + program_size.ToString() + "\n");

flag_check \= 0;

await Task.Delay(50); //chờ phản hồi từ mạch

if (flag_check \== 1)

{

while (true)

{

if(program_size/1024 != dem)serialPort.Write(data_send, dem * 1024, 1024); //tryền từng mảng 1024 byte 1 lần

else serialPort.Write(data_send, dem * 1024, program_size-(1024*dem)); //truyền nốt phần data cuối

flag_check \= 0;

timer1.Enabled \= true;

await Task.Delay(500); //chờ phản hồi

if (flag_check \== 0)

{

MessageBox.Show("Lỗi");

return;

}

if (flag_check \== 2)

{

MessageBox.Show("Lỗi khi ghi");

return;

}

dem++;

String vl \= (dem * 1024).ToString() + "/" + program_size.ToString() + " byte";

if (label1.InvokeRequired)

label1.Invoke(new Action(() \=\> label1.Text \= vl));

else

{ label1.Text \= vl; Application.DoEvents(); } //cập nhật quá trình nạp

if (dem*1024 \> program_size)

{

label1.Text \= "Nạp thành công !";

MessageBox.Show("Xong");

return;

}

}

}

else

{

MessageBox.Show("Kiểm tra kết nối !");

}

}

Chương trình demo

Chương trình code demo mình sẽ cho gửi 1 đoạn văn bản lên mỗi 500 mili giây. Do phần vùng của chương trình này bắt đầu ở địa chỉ 0x08002000 nên ngay khi vào hàm main, chúng ta dời bảng vector ngắt đến địa chỉ này, đồng thười cũng bật isr lên luôn

int main(void)

{

/* USER CODE BEGIN 1 */

SCB->VTOR \= (uint32_t)0x08002000;

__enable_irq();

/* USER CODE END 1 */

Khi biên dịch các bạn tích chọn creat file hex trong build option

Hướng dẫn cách nạp code cho stm-32c8t6 bằng file hex năm 2024

Trong thẻ tagret sửa địa chỉ bắt đầu thành 0x8002000

Hướng dẫn cách nạp code cho stm-32c8t6 bằng file hex năm 2024

Sau đó ấn build để lấy file hex

Khi khởi động mạch, chip sẽ chạy chương trình boot trước, sau khi nạp code xuống thành công chíp sẽ nhảy sang chương trình chính ! Các bạn có thể thêm 1 nút nhấn để khi khởi động chương trình boot sẽ kiểm tra nút nhấn để quyết định xem nó chạy ở bootloader hay nhảy tới phân vùng code chính

DEMO

Download

Tải full project tại đây: https://drive.google.com/open?id=17tEAYjTLKRL0ZrXpkZRS82JDyFM1v0Ek

Từ tác giả:

Nếu có bất kì thắc mắc nào trong bài viết, vui lòng để lại comment dưới mỗi bài ! Mình sẽ không trả lời thắc mắc của các bạn ở facebook hay email !