GIỚI THIỆU 1.1 Nhiệm vụ được giao thực tập Giao tiếp STM32F103C8T6 với module GPS-NEOM9 qua giao thức UART.. NỘI DUNG THỰC TẬP 2.1 Giao tiếp với module GPS-NEOM9 qua giao thức UART Bộ
GIỚI THIỆU
Nhiệm vụ được giao thực tập
Giao tiếp STM32F103C8T6 với module GPS-NEOM9 qua giao thức
Giao tiếp CAN bằng vi điều khiển STM32.
Lập trình ngoại vi I2C với CMSIS trên STM32.
Thời gian và lịch trình thực tập
Thời gian thực tập: 3 tháng (từ ngày 1/6/2024 đến ngày 31/8/2024)
Thời gian Công việc thực hiện
Tuần 1 và Tuần 2 Tìm hiểu về GPS NEOM9N Tuần 3 và Tuần 4 Cấu hình GPS bằng phần mềm và bằng cổng UART Tuần 5 và Tuần 6 Tìm hiểu lý thuyết về mô đun MCP2515 và giao thức
Lập trình giao tiếp mô đun MCP2515 sử dụng giao thức
SPI (trên bo STM32F1) với CAN của bo STM32F4 Tuần 10 Nghiên cứu lý thuyết giao thức I2C
Lập trình ngoại vi I2C bằng CMSIS trên bo STM32F1
NỘI DUNG THỰC TẬP
Giao tiếp với module GPS-NEOM9 qua giao thức UART
Bộ thu GNSS NEO-M9N sử dụng nền tảng GNSS tiêu chuẩn u-blox M9 Nó cung cấp độ nhạy và thời gian thu nhận tín hiệu xuất sắc cho tất cả các hệ thống GNSSL1 Các bộ thu u-blox M9 có sẵn trong các biến thể khác nhau để phục vụ cho các ứng dụng theo dõi ô tô và công nghiệp, chẳng hạn như điều hướng, viễn thông và UAV.
Các bộ thu u-blox M9 hỗ trợ thu đồng thời bốn hệ thống GNSS Số lượng vệ tinh nhìn thấy cao cho phép bộ thu chọn các tín hiệu tốt nhất, tối đa hóa độ chính xác vị trí, đặc biệt trong các điều kiện khó khăn như các hẻm sâu trong đô thị.
Các bộ thu u-blox M9 phát hiện các sự kiện gây nhiễu và giả mạo tín hiệu, báo cáo chúng về máy chủ, cho phép hệ thống phản ứng với những sự kiện này Các thuật toán lọc tiên tiến giảm thiểu tác động của nhiễu RF và gây nhiễu, giúp sản phẩm hoạt động như mong đợi.
Bộ thu cũng cung cấp tốc độ điều hướng cao hơn và các tính năng bảo mật cải tiến so với các thế hệ GNSS u-blox trước đó.
2.2.1 Sử dụng phần mềm Ucenter của ublox
Bật phần mềm Ucenter và chọn kết nối với GPS NEO-9M
Trên thanh Toolbar phía trên, chọn View -> Generation 9 Configuration View để mở cửa sổ cài đặt.
Sau đó chọn Advanced Configuration
Chọn vào CFG-UART1 -> CFG-UART1-BAUDRATE Ở ô value ta điền 115200, sau đó bấm Set in RAM, Set in BBR và Set in Flash.
Sau đó bấm Send config changes.
Chú ý: Các thao tác trên áp dụng cho tất cả các thay đổi ta muốn cho GPS
NEO-9M qua phần mềm Ucenter.
Tắt các dạng dữ liệu không cần thiết
MSGOUT-NMEA_ID_GGA_UART1 bằng cách đưa Value từ 1 về 0
Tắt CFG-MSGOUT-NMEA_ID_GSA_UART1 bằng cách đưa Value từ 1 về 0
Tắt CFG-MSGOUT-NMEA_ID_GSV_UART1 bằng cách đưa Value từ 1 về 0
Tắt CFG-MSGOUT-NMEA_ID_RMC_UART1 bằng cách đưa Value từ 1 về 0
Tắt CFG-MSGOUT-NMEA_ID_VTG_UART1
Tắt CFG-INFMSG-NMEA_UART1 và CFG-INFMSG-NMEA_USB
Bỏ chọn các tùy chọn dưới đây cho UART1 và USB Sau đó, thực hiện ghi lên RAM & BBR và tải lên module theo hướng dẫn ở trên.
2.2 2 Truyền mã lệnh thông qua Hercules
Chỉ cắm chân Tx của module UART(USB to TTL CP2102) vào chân Rx của module GPS Kết nối chân 3V3 và GND của module UART với chân 3V3 và GND của module GPS.
Lệnh thay đổi Baudrate thành 115200 b5 62 06 8a 0c 00 00 04 00 00 01 00 52 40 00 c2 01 00 f6 c6 b5 62 06 8a 0c 00 00 02 00 00 01 00 52 40 00 c2 01 00 f4 b0 b5 62 06 8a 0c 00 00 01 00 00 01 00 52 40 00 c2 01 00 f3 a5
Ta sao chép và dán 3 dòng lệnh trên vào ô gửi của Hercules và tích vào ô
Sau đó nhấn send lần lượt 3 lệnh theo thứ tự từ trên xuống và ta được kết quả như dưới là đã thành công cài đặt baudrate cho gps
Nếu thay đổi baudrate trước cho GPS thì cần thay đổi lại baudrate của Hercules thành tương ứng, sau đó mới thực hiện các thay đổi khác.
Các thao tác trên áp dụng cho tất cả các thay đổi ta muốn cho GPS NEO-9M qua phần mềm Hercules.
Ngoài ra thay vì sao chép 3 lệnh vào 3 ô như phía trên, ta có thể sao chép 3 lệnh lên cùng một ô (miễn các byte cách nhau 1 khoảng trắng).
Disable GGA msgout b5 62 06 8a 09 00 00 04 00 00 bb 00 91 20 00 09 e3 FLASH b5 62 06 8a 09 00 00 02 00 00 bb 00 91 20 00 07 d3 BBR b5 62 06 8a 09 00 00 01 00 00 bb 00 91 20 00 06 cb RAM
Disable GSA msgoutv b5 62 06 8a 09 00 00 04 00 00 c0 00 91 20 00 0e fc FLASH b5 62 06 8a 09 00 00 02 00 00 c0 00 91 20 00 0c ec BBR b5 62 06 8a 09 00 00 01 00 00 c0 00 91 20 00 0b e4 RAM
Disable GSV msgout b5 62 06 8a 09 00 00 04 00 00 c5 00 91 20 00 13 15 FLASH b5 62 06 8a 09 00 00 02 00 00 c5 00 91 20 00 11 05 BBR b5 62 06 8a 09 00 00 02 00 00 c5 00 91 20 00 11 05 RAM
Disable RMC msgout b5 62 06 8a 09 00 00 04 00 00 ac 00 91 20 00 fa 98 FLASH b5 62 06 8a 09 00 00 02 00 00 ac 00 91 20 00 f8 88 BBR b5 62 06 8a 09 00 00 01 00 00 ac 00 91 20 00 f7 80 RAM
Disable VTG msgout b5 62 06 8a 09 00 00 04 00 00 b1 00 91 20 00 ff b1 FLASH b5 62 06 8a 09 00 00 02 00 00 b1 00 91 20 00 fd a1 BBR b5 62 06 8a 09 00 00 01 00 00 b1 00 91 20 00 fc 99 RAM
NMEA_UART1 b5 62 06 8a 09 00 00 04 00 00 07 00 92 20 00 56 62 FLASH b5 62 06 8a 09 00 00 02 00 00 07 00 92 20 00 54 52 BBR b5 62 06 8a 09 00 00 01 00 00 07 00 92 20 00 53 4a RAM
INFMSG-NMEA_USB b5 62 06 8a 09 00 00 04 00 00 09 00 92 20 00 58 6c FLASH b5 62 06 8a 09 00 00 02 00 00 09 00 92 20 00 56 5c BBR b5 62 06 8a 09 00 00 01 00 00 09 00 92 20 00 55 54 RAM
B5 62 06 09 0D 00 FF FF 00 00 00 00 00 00 FF FF 00 FLASH
Giao tiếp CAN bằng vi điều khiển STM32
Giao thức CAN (Controller Area Network) là một hệ thống mạng truyền thông được thiết kế để cho phép các vi điều khiển và thiết bị giao tiếp với nhau mà không cần sử dụng máy tính trung gian Được phát triển bởi Bosch vào những năm 1980,
CAN chủ yếu được sử dụng trong ngành ô tô nhưng cũng được ứng dụng rộng rãi trong các lĩnh vực công nghiệp và tự động hóa.
Các đặc điểm chính của giao thức CAN bao gồm:
Nhận diện Tin nhắn: CAN sử dụng các ID để phân loại và nhận diện các tin nhắn, giúp tổ chức giao tiếp giữa các thiết bị trên mạng.
Giao tiếp Nhiều Điểm: Cho phép nhiều thiết bị (hoặc nút mạng) giao tiếp qua một đường truyền chung.
Đảm bảo Tính Toàn vẹn Dữ liệu: Sử dụng cơ chế kiểm tra lỗi và tự động sửa lỗi để đảm bảo tính chính xác của dữ liệu.
Tốc độ Cao và Hiệu Suất Tốt: CAN có khả năng truyền dữ liệu với tốc độ lên đến 1 Mbps và có độ trễ thấp.
Đơn giản và Hiệu quả: Cấu trúc đơn giản giúp giảm chi phí và tăng độ tin cậy của hệ thống.
CAN có hai phiên bản chính: CAN 2.0A (11-bit ID) và CAN 2.0B (29-bit ID), với CAN 2.0B hỗ trợ nhiều ID hơn và do đó có khả năng mở rộng tốt hơn.
Sử dụng CAN trên vi điều khiển STM32F405 với thư viện CAN_LL, giao tiếp với module MCP2515 sử dụng với vi điều khiển Atmega32 thông qua SPI
Dưới đây là một số phần thư viện LL mà em đã viết :
Định nghĩa CAN Handler, Filter, and Header
LL_CAN_GPIO_Init(&hcan);
NVIC_SetPriority(CAN1_TX_IRQn,NVIC_EncodePriority(NVIC_GetPri orityGrouping(), 15, 0));
NVIC_EnableIRQ(CAN1_TX_IRQn);
LL_CAN_ActivateInterrupt(&hcan,_CAN_IT_RX_FIFO0_MSG_PENDI NG_Pos | _CAN_IT_TX_MAILBOX_EMPTY_Pos);
4 Khởi tạo thông số CAN (Baudrate, Mode, Status) hcan.Init.Prescaler = 2; hcan.Init.SyncJumpWidth = _CAN_SJW_1TQ; hcan.Init.TimeSeg1 = _CAN_BS1_10TQ; hcan.Init.TimeSeg2 = _CAN_BS2_1TQ; hcan.Init.Mode = _LOOPBACK_MODE; hcan.Init.status.AutoBusOff = DISABLE; hcan.Init.status.AutoRetransmission = ENABLE; hcan.Init.status.AutoWakeUp = DISABLE; hcan.Init.status.ReceiveFifoLocked = DISABLE; hcan.Init.status.TimeTriggeredMode = DISABLE; hcan.Init.status.TransmitFifoPriority = DISABLE;
5 Cấu hình bộ lọc CAN hfilter1.FilterActivation = _CAN_FILTER_ENABLE; hfilter1.FilterBank = 0; hfilter1.FilterFIFOAssignment = _CAN_FILTER_FIFO0; hfilter1.FilterMaskIdHigh = 0; hfilter1.FilterMaskIdLow = 0; hfilter1.FilterMode = _CAN_FILTERMODE_IDMASK; hfilter1.FilterScale = _CAN_FILTERSCALE_32BIT;
LL_CAN_ConfigFilter(&hcan1, &hfilter1);
Lưu ý: Phải bật bộ lọc để sử dụng chế độ nhận
Bắt đầu ngoại vi CAN
Gọi hàm sau khi đã cấu hình xong mọi thứ
Hàm truyền và nhận của CAN
1 Hàm truyền (ở chế độ polling)
LL_CAN_AddTxMessage(LL_CAN_Handler_t *hcan, const uint8_t data[], LL_CAN_TxHeaderTypeDef_t *htxheader, uint32_t *TxMailBox);
LL_CAN_IsTxMessagePending(LL_CAN_Handler_t *hcan, uint32_t
Nếu sử dụng chế độ ngắt thì chỉ gọi hàm:
Hàm nhận (ở chế độ polling)
LL_CAN_GetRxFifoFillLevel(LL_CAN_Handler_t *hcan, uint32_t RxFifo);
LL_CAN_GetRxMessage(LL_CAN_Handler_t *hcan,
LL_CAN_RxHeaderTypeDef_t *hrxheader, uint8_t rxdata[], uint32_t RxFifo);
Nếu sử dụng chế độ ngắt thì chỉ gọi hàm:
LL_CAN_GetRxMessage(LL_CAN_Handler_t *hcan,
LL_CAN_RxHeaderTypeDef_t *hrxheader, uint8_t rxdata[], uint32_t RxFifo);LL_CAN_IsTxMessagePending(LL_CAN_Handler_t *hcan, uint32_t *TxMailBox);
Lập trình ngoại vi I2C với CMSIS trên STM32
Giao thức Inter-Integrated Circuit (hay còn gọi là I2C) là một đặc tả phần cứng và giao thức được phát triển bởi bộ phận bán dẫn của Philips (hiện nay là NXP Semiconductors) vào năm 1982 Đây là một đặc tả bus nối tiếp 8-bit hướng đơn, bán song công, đa slaves, chỉ sử dụng hai dây để kết nối một số thiết bị slave với một thiết bị master.
Cho đến tháng 10 năm 2006, việc phát triển các thiết bị dựa trên I2C phải chịu phí bản quyền cho Philips, nhưng hạn chế này đã được bãi bỏ. Ở giao thức I2C chúng ta cần nắm về: điều kiện Start và điều kiện Stop, định dạng byte, khung địa chỉ, bit ACK và NACK, khung dữ liệu, truyền/nhận kết hợp và kéo dãn xung clock.
2.3.1 Giới thiệu thông số kỹ thuật của I2C:
I2C là giao thức bus nối tiếp 8-bit, bán song công, do Philips (nay là NXP) phát triển năm 1982 I2C dùng hai dây SDA và SCL để kết nối master với nhiều slave Trước 2006, phát triển thiết bị I2C phải trả phí bản quyền Hiện nay, STM32 hỗ trợ cấu hình GPIO dạng open-drain với điện trở kéo lên, nhưng điện trở nội bộ có giá trị cao (khoảng 20KΩ) nên không phù hợp cho tốc độ cao và ) nên không phù hợp cho tốc độ cao và dây dài Vì vậy, nên dùng điện trở kéo lên ngoài nếu có thể Mỗi slave I2C có địa chỉ riêng, 7-bit hoặc 10-bit Các tốc độ phổ biến là 100kHz (standard mode) và 400kHz (fast mode), với các phiên bản mới hỗ trợ tốc độ cao hơn.
Giao thức I2C gồm các khung địa chỉ và dữ liệu Các thiết bị master điều khiển việc truyền/nhận, bắt đầu bằng điều kiện Start và kết thúc bằng Stop Dữ liệu được truyền theo định dạng byte với bit ACK/NACK để xác nhận Khung địa chỉ 7-bit hoặc 10-bit xác định slave, theo sau là khung dữ liệu Một số slave hỗ trợ kéo dãn xung clock để điều chỉnh tốc độ truyền/nhận Giao thức cũng hỗ trợ truyền/nhận kết hợp, cho phép đảo chiều dữ liệu sau một số byte mà không cần Stop.
STM32 hỗ trợ đến bốn ngoại vi I2C tùy dòng vi điều khiển Các chân SDA và
SCL có thể thay thế trên các gói MCU, như STM32F401RE dùng PB7, PB6 hoặc PB9, PB8 Điều này giúp tăng tính linh hoạt khi thiết kế bo mạch.
Chúng ta tìm hiểu về độ phân cực và pha, quản lý chọn các thiết bị slave, chế độ TI của SPI và những ngoại vi sử dụng giao thức SPI Và sử dụng
CubeMX để cấu hình SPI.
Cấu hình GPIO cho ngoại vi I2C (sử dụng CMSIS): void i2c_I2C1_GPIO_config(void)
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
GPIOB->CRL &= ~(GPIO_CRL_MODE6 | GPIO_CRL_MODE7); // Xóa MODE6 và MODE7
GPIOB->CRL |= (GPIO_CRL_MODE6_0 | GPIO_CRL_MODE7_0); // CNF: Alternate function Open-Drain
GPIOB->CRL |= (GPIO_CRL_CNF6 | GPIO_CRL_CNF7);
Cấu hình các thông số và bật ngoại vi I2C (sử dụng thư viện LL) void MX_I2C1_Config_Init(void)
// Kích hoạt xung clock cho ngoại vi I2C1
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C1);
LL_I2C_DisableOwnAddress2(I2C1); // Vô hiệu hóa địa chỉ riêng thứ 2 LL_I2C_DisableGeneralCall(I2C1); // Vô hiệu hóa GenerallCall
LL_I2C_EnableClockStretching(I2C1); // Kích hoạt kéo dài xung clock
I2C_InitStruct.PeripheralMode = LL_I2C_MODE_I2C; // Chế độ ngoại vi là I2C
I2C_InitStruct.ClockSpeed = 100000; // Tốc độ xung clock I2C là 100kHz I2C_InitStruct.DutyCycle = LL_I2C_DUTYCYCLE_2; // Duty cycle là 2 I2C_InitStruct.OwnAddress1 = 0; // Địa chỉ riêng đầu tiên của I2C là 0 I2C_InitStruct.TypeAcknowledge = LL_I2C_ACK; // Kiểu xác nhận là ACK
I2C_InitStruct.OwnAddrSize = LL_I2C_OWNADDRESS1_7BIT; // Kích thước địa chỉ riêng là 7 bit
// Khởi tạo I2C1 với các tham số cấu hình đã thiết lập
LL_I2C_SetOwnAddress2(I2C1, 0); // Thiết lập địa chỉ riêng thứ 2 là 0 LL_I2C_Enable(I2C1); // Kích hoạt I2C1
Có thể chúng ta sẽ thắc mắc làm sao để biết địa chỉ thiết bị slave mà chúng ta dự định giao tiếp có tồn tại hay không Thì hàm kiểm tra địa chỉ slave dưới đây sẽ giải quyết vấn đề này (sử dụng CMSIS): bool i2c_I2C1_isSlaveAddressExist(uint8_t Addr)
//Bit POS được xóa để đảm bảo I2C hoạt động trong chế độ chuẩn
// Gửi điều kiện Start ra
// Chờ bit start được tạo while (!(I2C1->SR1 & I2C_SR1_SB))
// Xóa SB bằng cách đọc thanh ghi SR1, sau đó ghi địa chỉ vào thanh ghi DR
// Gửi địa chỉ slave ra
// Chờ ACK while (!(I2C1->SR1 & I2C_SR1_ADDR))
{ if (++count > 100) return false; count = 0;
// Xóa cờ Addr bằng cách đọc SR1 trước rồi tiếp đến SR2
IO uint32_t tempRd = I2C1->SR1; tempRd = I2C1->SR2;
(void)tempRd; // Bỏ qua biến tempRd
// Chờ I2C vào trạng thái bận while ((I2C1->SR1 & I2C_SR2_BUSY))
Hàm truyền dữ liệu (sử dụng CMSIS): bool i2c_I2C1_masterTransmit(uint8_t Addr, uint8_t reg, uint8_t *pData, uint8_t len, uint32_t timeout)
// Chờ I2C vào trạng thái bận while ((I2C1->SR1 & I2C_SR2_BUSY))
{ if (++count > timeout) return false;
// Bit POS được xóa để đảm bảo I2C hoạt động trong chế độ chuẩn (standard mode).
// Chờ bit start được tạo while (!(I2C1->SR1 & I2C_SR1_SB))
{ if (++count > timeout) return false;
// Chờ ACK while (!(I2C1->SR1 & I2C_SR1_ADDR))
{ if (++count > timeout) return false;
IO uint32_t tempRd = I2C1->SR1; tempRd = I2C1->SR2;
// Gửi thanh ghi thiết bị cần ghi ra
// Truyền dữ liệu while (len > 0U)
// Kiểm tra bộ đệm Tx có trống không while (!(I2C1->SR1 & I2C_SR1_TXE))
{ if (++count > timeout) return false;
// Nếu truyền xong BTF=1 và len != 0 thì truyền tiếp if ((I2C1->SR1 & I2C_SR1_BTF) && (len != 0))
I2C1->CR1 |= I2C_CR1_STOP; return true;
Hàm nhận dữ liệu từ thiết bị slave (sử dụng CMSIS): bool i2c_I2C1_masterReceive(uint8_t Addr, uint8_t reg, uint8_t *pData, uint8_t len, uint32_t timeout)
// Chờ I2C vào trạng thái bận while ((I2C1->SR1 & I2C_SR2_BUSY))
{ if (++count > timeout) return false;
// Bit POS được xóa để đảm bảo I2C hoạt động trong chế độ chuẩn (standard mode).
// Trả về ACK sau mỗi lần nhận được địa chỉ đúng và dữ liệu
// Chờ bit start được tạo while (!(I2C1->SR1 & I2C_SR1_SB))
{ if (++count > timeout) return false;
I2C1->DR = Addr | 0x01; // chế độ đọc
// Chờ ACK while (!(I2C1->SR1 & I2C_SR1_ADDR))
{ if (++count > timeout) return false;
// Nhận dữ liệu if (len == 0)
I2C1->CR1 |= I2C_CR1_STOP; return true;
IO uint32_t tempRd = I2C1->SR1; tempRd = I2C1->SR2;
// Bit POS được xóa để đảm bảo I2C hoạt động trong chế độ chuẩn (standard mode).
IO uint32_t tempRd = I2C1->SR1; tempRd = I2C1->SR2;
IO uint32_t tempRd = I2C1->SR1; tempRd = I2C1->SR2;
(void)tempRd; while (dataIndex < len)
// Chờ bộ đệm nhận trống while (!(I2C1->SR1 & I2C_SR1_RXNE))
{ if (++count > timeout) return false;
} count = 0; pData[dataIndex] = (uint8_t)I2C1->DR; dataIndex++;
// Chờ BTF=1 while (!(I2C1->SR1 & I2C_SR1_BTF))
{ if (++count > timeout) return false;
// Gửi dữ liệu 2 lần pData[dataIndex] = (uint8_t)I2C1->DR; dataIndex++; pData[dataIndex] = (uint8_t)I2C1->DR; dataIndex++;
// Chờ BTF=1 while (!(I2C1->SR1 & I2C_SR1_BTF))
// Gửi dữ liệu pData[dataIndex] = (uint8_t)I2C1->DR; dataIndex++;
// Gửi thêm pData[dataIndex] = (uint8_t)I2C1->DR; dataIndex++;
// Chờ bộ đệm nhận trống while (!(I2C1->SR1 & I2C_SR1_RXNE))
{ if (++count > timeout) return false;
} count = 0; pData[dataIndex] = (uint8_t)I2C1->DR; dataIndex++;
// Chờ bộ đệm nhận trống while (!(I2C1->SR1 & I2C_SR1_RXNE))
{ if (++count > timeout) return false;
} count = 0; pData[dataIndex] = (uint8_t)I2C1->DR; dataIndex++;
TỔNG KẾT CÔNG VIỆC THỰC TẬP
Kết quả công việc thực tập
Sử dụng CAN trong truyền nhận dữ liệu và viết thư viện LL cho CAN STM32
Nắm được giao thức DAC, I2C và SPI.
Kinh nghiệm học được sau khi thực tập
Sau 3 tháng thực tập và làm việc cùng Thầy, em đã học hỏi, tiếp thu được nhiều kiến thức và kỹ năng mới. Đầu tiên, em được tiếp xúc với một môi trường làm việc mới, những đàn anh đi trước có nhiều kinh nghiệm, cách hoạt động đội nhóm để thực hiện một dự án thực tế.
Thứ hai, em được học thêm nhiều kiến thức mới như cách cài đặt, cấu hình cho mô đun GPS NEOM9N, sử dụng MCP2515 qua giao thức SPI và giao tiếp với RTC qua giao thức I2C Tuy nhiên, vì phần kiến thức khá rộng và thời gian tìm hiểu chưa lâu,nên em còn gặp khá nhiều khó khăn trong quá trình nghiên cứu cũng như thử nghiệm chương trình Do đó, em chỉ mới hoàn thiện được một phần của đề tài, nhưng những điều mà em học tập được trong kì thực tập này sẽ giúp em vững vàng hơn về kĩ năng cũng như kiến thức chuyên ngành và có thể hoàn thiện tốt hơn các học phần tốt nghiệp sau này.