Assalamu'alaikum warohmatulohi wabarakatuh,
Pada tutorial sebelumnya, Implementasi FreeRTOS pada STM32H7 menggunakan STM32CubeIDE, kita sudah menjalankan FreeRTOS di STM32H743VIT6 lengkap dengan demo multitasking. Kali ini kita lanjutkan ke topik yang banyak dipakai di dunia satelit kecil: CubeSat Space Protocol (CSP) memakai library libcsp, dengan jalur komunikasi CAN bus lewat periferal FDCAN1 (PA11/PA12).
Semua kode di tutorial ini sudah saya build di STM32CubeIDE dan saya jalankan di board DevEBox STM32H7XX_M (STM32H743VIT6). Hasil log-nya bisa dilihat di bagian akhir.
Apa itu CubeSat Space Protocol (CSP)?
CSP adalah protokol jaringan kecil yang dirancang untuk komunikasi antar subsistem di dalam satelit kecil (CubeSat), misalnya antara komputer utama (OBC), sistem daya (EPS), kontrol sikap (ADCS) dan radio. Idenya dikembangkan oleh mahasiswa Aalborg University pada tahun 2008, lalu sejak 2009 dikembangkan di GomSpace dan sekarang tersedia sebagai library open source libcsp.
Konsepnya mirip TCP/IP, tetapi jauh lebih ringan. Setiap paket CSP hanya punya header 32-bit, sehingga cocok untuk mikrokontroler. Setiap node punya alamat, setiap layanan punya port, dan paket bisa dikirim lewat berbagai jalur fisik seperti CAN, I2C, UART (KISS) atau ZeroMQ, tanpa mengubah kode aplikasi.
Struktur header CSP versi 1.x:
| Field | Ukuran | Keterangan |
|---|---|---|
| Priority | 2 bit | 0 = critical, 1 = high, 2 = normal, 3 = low |
| Source | 5 bit | alamat pengirim (0..31) |
| Destination | 5 bit | alamat tujuan (31 = broadcast) |
| Destination port | 6 bit | port layanan tujuan (0..63) |
| Source port | 6 bit | port pengirim (dipakai untuk balasan) |
| Flags | 8 bit | opsi seperti CRC32, HMAC, XTEA, RDP |
Port 0 sampai 6 sudah dipakai oleh service bawaan CSP, sehingga setiap node otomatis bisa di-ping dan ditanya statusnya:
| Port | Service |
|---|---|
| 0 | CMP (manajemen: identitas, routing, statistik) |
| 1 | Ping |
| 2 | Daftar task (process list) |
| 3 | Sisa memori |
| 4 | Reboot |
| 5 | Sisa buffer CSP |
| 6 | Uptime |
Library yang dipakai di tutorial ini adalah libcsp versi 1.6 (lisensi LGPL 2.1), versi yang banyak dipakai di project CubeSat berbasis FreeRTOS.
CSP lewat CAN: CAN Fragmentation Protocol (CFP)
Satu frame CAN klasik hanya membawa maksimal 8 byte data, padahal paket CSP bisa ratusan byte. Karena itu libcsp memakai CAN Fragmentation Protocol (CFP) yang memecah satu paket CSP menjadi beberapa frame CAN, lalu menyusunnya kembali di penerima. Informasi fragmentasi disimpan di ID CAN extended 29-bit:
Pembagian 29 bit ID CAN pada CFP
Hal penting yang sering terlewat: CFP wajib memakai ID extended 29-bit. Kalau frame dikirim dengan ID standar 11-bit, field alamat dan nomor fragmen akan terpotong dan paket tidak bisa disusun kembali di penerima.
Arsitektur dan hardware
Lapisan software dan sambungan hardware CAN
- FDCAN1 STM32H743 memakai pin PA12 (TX) dan PA11 (RX). Pin ini level logika 3,3 V, jadi tidak boleh langsung disambung ke kabel bus CAN. Harus lewat transceiver CAN, misalnya SN65HVD230 (3,3 V).
- Bus CAN (CAN_H dan CAN_L) diberi resistor terminasi 120 Ω di kedua ujung.
- Untuk belajar dengan satu board saja, FDCAN bisa dipasang di mode internal loopback: frame yang dikirim langsung diterima kembali di dalam chip, tanpa transceiver. Mode inilah yang saya pakai untuk pengujian di tutorial ini.
- Log ditampilkan lewat USART1 (PA9/PA10, 115200 baud) ke USB-UART WeAct Mini Debugger, sama seperti tutorial sebelumnya.
Langkah 1 – Konfigurasi FDCAN1 di STM32CubeIDE
Project ini melanjutkan project FreeRTOS dari tutorial sebelumnya (clock 75 MHz dari HSI, timebase TIM1, FreeRTOS CMSIS_V2, LED PA1, USART1). Tambahkan FDCAN1:
- Buka Connectivity → FDCAN1, centang Activated. Pin otomatis menjadi PA11 = FDCAN1_RX dan PA12 = FDCAN1_TX.
- Di Clock Configuration, sumber clock FDCAN adalah PLL1Q = 50 MHz (DIVQ1 = 3).
- Di Parameter Settings isi nilai berikut untuk kecepatan 1 Mbit/s:
| Parameter | Nilai | Keterangan |
|---|---|---|
| Frame Format | Classic mode | CAN 2.0B (bukan CAN FD) |
| Mode | Normal | di kode diganti ke internal loopback untuk uji 1 board |
| Nominal Prescaler | 5 | 50 MHz / 5 = 10 MHz → 1 tq = 100 ns |
| Nominal Sync Jump Width | 2 | |
| Nominal Time Seg1 | 7 | |
| Nominal Time Seg2 | 2 | 1 bit = 1 + 7 + 2 = 10 tq = 1 µs → 1 Mbit/s, sample point 80% |
| Auto Retransmission | Enable | |
| Ext Filters Nbr | 2 | filter alamat sendiri + broadcast |
| Rx Fifo0 Elmts Nbr | 32 | antrian frame masuk |
| Tx Fifo Queue Elmts Nbr | 32 | antrian frame keluar |
- Di tab NVIC Settings, aktifkan FDCAN1 interrupt 0 dengan prioritas 5. Prioritas harus ≥ 5 karena interrupt ini memanggil fungsi FreeRTOS (
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY). - Di Middleware → FREERTOS → Config parameters, aktifkan USE_STATS_FORMATTING_FUNCTIONS. Fungsi ini dipakai libcsp untuk service "process list".
Kode inisialisasi FDCAN1 yang dihasilkan (dengan tambahan mode loopback di blok USER CODE):
hfdcan1.Instance = FDCAN1;
hfdcan1.Init.FrameFormat = FDCAN_FRAME_CLASSIC;
hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
hfdcan1.Init.AutoRetransmission = ENABLE;
hfdcan1.Init.TransmitPause = DISABLE;
hfdcan1.Init.ProtocolException = DISABLE;
hfdcan1.Init.NominalPrescaler = 5;
hfdcan1.Init.NominalSyncJumpWidth = 2;
hfdcan1.Init.NominalTimeSeg1 = 7;
hfdcan1.Init.NominalTimeSeg2 = 2;
hfdcan1.Init.DataPrescaler = 1;
hfdcan1.Init.DataSyncJumpWidth = 1;
hfdcan1.Init.DataTimeSeg1 = 1;
hfdcan1.Init.DataTimeSeg2 = 1;
hfdcan1.Init.MessageRAMOffset = 0;
hfdcan1.Init.StdFiltersNbr = 0;
hfdcan1.Init.ExtFiltersNbr = 2;
hfdcan1.Init.RxFifo0ElmtsNbr = 32;
hfdcan1.Init.RxFifo0ElmtSize = FDCAN_DATA_BYTES_8;
hfdcan1.Init.RxFifo1ElmtsNbr = 0;
hfdcan1.Init.RxFifo1ElmtSize = FDCAN_DATA_BYTES_8;
hfdcan1.Init.RxBuffersNbr = 0;
hfdcan1.Init.RxBufferSize = FDCAN_DATA_BYTES_8;
hfdcan1.Init.TxEventsNbr = 0;
hfdcan1.Init.TxBuffersNbr = 0;
hfdcan1.Init.TxFifoQueueElmtsNbr = 32;
hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;
hfdcan1.Init.TxElmtSize = FDCAN_DATA_BYTES_8;
/* USER CODE BEGIN FDCAN1_Init 2 */
#if CSP_CAN_LOOPBACK
/* Uji tanpa transceiver: frame TX langsung diterima kembali di dalam chip */
hfdcan1.Init.Mode = FDCAN_MODE_INTERNAL_LOOPBACK;
#endif
/* USER CODE END FDCAN1_Init 2 */
Langkah 2 – Menambahkan libcsp 1.6 ke project
- Download source libcsp dari GitHub
github.com/libcsp/libcsp, pilih tag v1.6. - Buat folder
Middlewares/Third_Party/libcspdi project, lalu salin folderincludedan file-file disrcberikut:src/*.cdansrc/*.h(inti CSP: buffer, connection, router, io, service, dll)src/arch/*.cdansrc/arch/freertos/*.c(port untuk FreeRTOS)src/interfaces/csp_if_can.c,csp_if_can_pbuf.c/.h,csp_if_lo.csrc/rtable/csp_rtable.c,csp_rtable_static.c,csp_rtable_internal.hsrc/transport/*.cdan*.h
- Tambahkan include path di Project → Properties → C/C++ Build → Settings → MCU GCC Compiler → Include paths:
../Middlewares/Third_Party/libcsp/includedan../Middlewares/Third_Party/libcsp/src - libcsp aslinya memakai build system waf yang membuat file
csp_autoconfig.hsecara otomatis. Karena kita memakai STM32CubeIDE, file ini dibuat manual diinclude/csp/csp_autoconfig.h:
/*
* csp_autoconfig.h - konfigurasi libcsp 1.6 untuk STM32H743 + FreeRTOS
*
* Pada libcsp asli, file ini dibuat otomatis oleh script build "waf".
* Karena kita memakai STM32CubeIDE (tanpa waf), file ini ditulis manual.
*/
#ifndef CSP_AUTOCONFIG_H
#define CSP_AUTOCONFIG_H
#define GIT_REV "v1.6"
#define LIBCSP_VERSION "1.6"
/* Sistem operasi */
#define CSP_FREERTOS 1
/* Fitur */
#define CSP_DEBUG 1 /* aktifkan log internal CSP (diarahkan ke UART) */
#define CSP_DEBUG_TIMESTAMP 0
#define CSP_USE_RDP 0 /* reliable datagram protocol: tidak dipakai */
#define CSP_USE_RDP_FAST_CLOSE 0
#define CSP_USE_CRC32 1 /* CRC32 opsional per paket */
#define CSP_USE_HMAC 0
#define CSP_USE_XTEA 0
#define CSP_USE_PROMISC 0
#define CSP_USE_QOS 0
#define CSP_USE_DEDUP 0
#define CSP_USE_EXTERNAL_DEBUG 0
/* Level log yang dikompilasi */
#define CSP_LOG_LEVEL_DEBUG 0
#define CSP_LOG_LEVEL_INFO 1
#define CSP_LOG_LEVEL_WARN 1
#define CSP_LOG_LEVEL_ERROR 1
/* Cortex-M7 = little endian */
#define CSP_LITTLE_ENDIAN 1
#endif /* CSP_AUTOCONFIG_H */
Langkah 3 – Driver CAN untuk libcsp (csp_can_stm32.c)
libcsp tidak tahu cara mengakses hardware CAN. Kita perlu menulis driver kecil yang menghubungkan libcsp dengan HAL FDCAN. Driver ini saya letakkan di Core/Src/csp_can_stm32.c dan terdiri dari tiga bagian.
a. Fungsi kirim frame. Dipanggil libcsp untuk setiap fragmen CFP. Perhatikan FDCAN_EXTENDED_ID, dan bahwa pada HAL STM32H7 versi ini nilai DataLength sama dengan jumlah byte (0..8):
static int can_tx_frame(void *driver_data, uint32_t id, const uint8_t *data, uint8_t dlc)
{
FDCAN_HandleTypeDef *hfdcan = (FDCAN_HandleTypeDef *)driver_data;
FDCAN_TxHeaderTypeDef header = {
.Identifier = id, /* ID CFP 29-bit */
.IdType = FDCAN_EXTENDED_ID, /* wajib extended untuk CFP */
.TxFrameType = FDCAN_DATA_FRAME,
.DataLength = dlc, /* 0..8 (FDCAN_DLC_BYTES_x) */
.ErrorStateIndicator = FDCAN_ESI_ACTIVE,
.BitRateSwitch = FDCAN_BRS_OFF,
.FDFormat = FDCAN_CLASSIC_CAN,
.TxEventFifoControl = FDCAN_NO_TX_EVENTS,
.MessageMarker = 0,
};
if (dlc > 8) {
return CSP_ERR_INVAL;
}
/* Tunggu jika TX FIFO penuh (paket panjang = banyak frame), maks ~10 ms */
for (int i = 0; HAL_FDCAN_GetTxFifoFreeLevel(hfdcan) == 0U; i++) {
if (i >= 10) {
csp_can_stats.tx_errors++;
return CSP_ERR_TIMEDOUT;
}
vTaskDelay(1);
}
if (HAL_FDCAN_AddMessageToTxFifoQ(hfdcan, &header, (uint8_t *)data) != HAL_OK) {
csp_can_stats.tx_errors++;
return CSP_ERR_DRIVER;
}
csp_can_stats.tx_frames++;
return CSP_ERR_NONE;
}
b. Inisialisasi. Mendaftarkan interface ke libcsp dan memasang filter hardware supaya FDCAN hanya menerima frame yang field DST-nya sama dengan alamat kita atau alamat broadcast:
/* Data interface CAN untuk libcsp */
static csp_can_interface_data_t can_ifdata = {
.tx_func = can_tx_frame,
};
csp_iface_t csp_if_can_stm32 = {
.name = CSP_IF_CAN_DEFAULT_NAME, /* "CAN" */
.interface_data = &can_ifdata,
};
int csp_can_stm32_init(FDCAN_HandleTypeDef *hfdcan, uint8_t address)
{
FDCAN_FilterTypeDef filter = {0};
can_handle = hfdcan;
csp_if_can_stm32.driver_data = hfdcan;
/* Filter 0: terima frame extended yang field DST = alamat kita */
filter.IdType = FDCAN_EXTENDED_ID;
filter.FilterIndex = 0;
filter.FilterType = FDCAN_FILTER_MASK;
filter.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
filter.FilterID1 = CFP_MAKE_DST(address); /* nilai yang dicari */
filter.FilterID2 = CFP_MAKE_DST(0x1F); /* mask: hanya bit DST */
if (HAL_FDCAN_ConfigFilter(hfdcan, &filter) != HAL_OK) {
return CSP_ERR_DRIVER;
}
/* Filter 1: terima juga alamat broadcast CSP (31) */
filter.FilterIndex = 1;
filter.FilterID1 = CFP_MAKE_DST(CSP_BROADCAST_ADDR);
if (HAL_FDCAN_ConfigFilter(hfdcan, &filter) != HAL_OK) {
return CSP_ERR_DRIVER;
}
/* Frame lain (standard 11-bit / alamat lain) ditolak oleh hardware */
if (HAL_FDCAN_ConfigGlobalFilter(hfdcan, FDCAN_REJECT, FDCAN_REJECT,
FDCAN_REJECT_REMOTE, FDCAN_REJECT_REMOTE) != HAL_OK) {
return CSP_ERR_DRIVER;
}
if (HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK) {
return CSP_ERR_DRIVER;
}
if (HAL_FDCAN_Start(hfdcan) != HAL_OK) {
return CSP_ERR_DRIVER;
}
/* Daftarkan interface ke libcsp (mengisi nexthop = csp_can_tx) */
return csp_can_add_interface(&csp_if_can_stm32);
}
c. Penerimaan lewat interrupt. Setiap frame yang masuk diteruskan ke csp_can_rx(), yang menyusun fragmen menjadi paket CSP lalu menyerahkannya ke router:
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
BaseType_t woken = pdFALSE;
FDCAN_RxHeaderTypeDef header;
uint8_t data[8];
if (hfdcan != can_handle || (RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) == 0U) {
return;
}
/* Ambil semua frame yang ada di FIFO */
while (HAL_FDCAN_GetRxFifoFillLevel(hfdcan, FDCAN_RX_FIFO0) > 0U) {
if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &header, data) != HAL_OK) {
break;
}
if (header.IdType != FDCAN_EXTENDED_ID || header.DataLength > FDCAN_DLC_BYTES_8) {
continue; /* bukan frame CFP */
}
csp_can_stats.rx_frames++;
/* Serahkan ke libcsp: menyusun fragmen jadi paket CSP */
csp_can_rx(&csp_if_can_stm32, header.Identifier, data, (uint8_t)header.DataLength, &woken);
}
portYIELD_FROM_ISR(woken);
}
Tambahkan juga handler interrupt di stm32h7xx_it.c:
void FDCAN1_IT0_IRQHandler(void)
{
HAL_FDCAN_IRQHandler(&hfdcan1);
}
Langkah 4 – Inisialisasi CSP dan routing
Di main.c kita tentukan alamat node, port yang dipakai, dan mode uji:
#define CSP_CAN_LOOPBACK 1 /* 1 = uji 1 board (internal loopback) */
#define CSP_MY_ADDR 1 /* alamat CSP board ini (0..30) */
#if CSP_CAN_LOOPBACK
#define CSP_SERVER_ADDR CSP_MY_ADDR /* loopback: server = diri sendiri */
#else
#define CSP_SERVER_ADDR 2 /* alamat node lain di bus CAN */
#endif
#define CSP_PORT_ECHO 10 /* port request-reply */
#define CSP_PORT_TLM 11 /* port telemetri (paket panjang) */
#define TLM_SIZE 200 /* ukuran paket telemetri (byte) */
Fungsi csp_setup() menginisialisasi libcsp, menghubungkan FDCAN1, mengatur tabel routing, lalu menjalankan task router milik CSP:
/* Inisialisasi libcsp + interface CAN + tabel routing */
static int csp_setup(void)
{
csp_conf_t conf;
csp_conf_get_defaults(&conf);
conf.address = CSP_MY_ADDR;
conf.hostname = "STM32H743";
conf.model = "DevEBox-H7";
conf.revision = "1.0";
conf.buffers = 20; /* jumlah buffer paket CSP */
conf.buffer_data_size = 256; /* ukuran data per paket */
conf.conn_max = 8;
conf.conn_queue_length = 10;
conf.fifo_length = 20;
conf.port_max_bind = 16;
csp_debug_hook_set(csp_debug_to_uart);
if (csp_init(&conf) != CSP_ERR_NONE) {
log_printf("[INIT ] csp_init gagal\r\n");
return -1;
}
/* Hubungkan FDCAN1 ke libcsp (filter alamat + daftar interface "CAN") */
if (csp_can_stm32_init(&hfdcan1, CSP_MY_ADDR) != CSP_ERR_NONE) {
log_printf("[INIT ] inisialisasi CAN gagal\r\n");
return -1;
}
/* Routing: semua alamat tujuan lewat CAN */
csp_rtable_set(CSP_DEFAULT_ROUTE, 0, &csp_if_can_stm32, CSP_NO_VIA_ADDRESS);
#if CSP_CAN_LOOPBACK
/* Uji 1 board: paket ke alamat sendiri juga dipaksa lewat CAN
(normalnya lewat interface "LOOP" software) */
csp_rtable_set(CSP_MY_ADDR, CSP_ID_HOST_SIZE, &csp_if_can_stm32, CSP_NO_VIA_ADDRESS);
#endif
/* Task router CSP: stack 1024 word, prioritas FreeRTOS 40 (= osPriorityHigh) */
if (csp_route_start_task(1024, osPriorityHigh) != CSP_ERR_NONE) {
return -1;
}
return 0;
}
csp_setup() dipanggil dari defaultTask (setelah scheduler berjalan), lalu task server dan client dibuat:
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN 5 */
log_printf("\r\n===== FreeRTOS + libcsp %s lewat FDCAN1 (STM32H743) =====\r\n", LIBCSP_VERSION);
log_printf("[INIT ] alamat CSP=%u, CAN 1 Mbit/s, mode %s\r\n", CSP_MY_ADDR,
CSP_CAN_LOOPBACK ? "INTERNAL LOOPBACK" : "NORMAL");
if (csp_setup() == 0) {
log_printf("[INIT ] libcsp siap, interface CAN terdaftar\r\n");
cspServerTaskHandle = osThreadNew(StartCspServerTask, NULL, &cspServerTask_attributes);
cspClientTaskHandle = osThreadNew(StartCspClientTask, NULL, &cspClientTask_attributes);
}
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); // LED PA1 nyala
osDelay(1000);
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); // LED PA1 mati
osDelay(2000);
}
/* USER CODE END 5 */
}
Langkah 5 – Server dan client CSP
Server mendengarkan semua port. Port 10 dibalas dengan pesan ACK, port 11 menerima telemetri, dan port lain (ping, uptime, dll) diserahkan ke csp_service_handler():
void StartCspServerTask(void *argument)
{
csp_socket_t *sock = csp_socket(CSP_SO_NONE);
csp_bind(sock, CSP_ANY); /* terima semua port */
csp_listen(sock, 10);
for (;;)
{
csp_conn_t *conn = csp_accept(sock, 10000); /* tunggu koneksi baru */
if (conn == NULL) {
continue;
}
csp_packet_t *packet;
while ((packet = csp_read(conn, 50)) != NULL)
{
switch (csp_conn_dport(conn))
{
case CSP_PORT_ECHO:
{
packet->data[packet->length < 200 ? packet->length : 199] = '\0';
log_printf("[SERVER ] port %d dari node %u: \"%s\"\r\n",
CSP_PORT_ECHO, packet->id.src, (char *)packet->data);
/* Pakai ulang buffer yang sama untuk balasan */
int n = snprintf((char *)packet->data, 200, "ACK dari node %u: %s",
CSP_MY_ADDR, "pesan diterima");
packet->length = (uint16_t)n;
if (!csp_send(conn, packet, 0)) {
csp_buffer_free(packet); /* gagal kirim -> buffer milik kita */
}
break;
}
case CSP_PORT_TLM:
{
uint32_t sum = 0;
for (uint16_t i = 0; i < packet->length; i++) sum += packet->data[i];
log_printf("[SERVER ] port %d: telemetri %u byte diterima, checksum=%lu\r\n",
CSP_PORT_TLM, packet->length, (unsigned long)sum);
csp_buffer_free(packet);
break;
}
default:
/* Ping, uptime, dll ditangani libcsp (juga membebaskan buffer) */
csp_service_handler(conn, packet);
break;
}
}
csp_close(conn);
}
}
Client setiap 3 detik melakukan tiga hal: ping, request-reply ke port 10, dan mengirim telemetri 200 byte ke port 11:
void StartCspClientTask(void *argument)
{
uint32_t n = 0;
osDelay(500);
for (;;)
{
uint32_t tx0 = csp_can_stats.tx_frames;
/* 1) Ping (service CSP port 1), payload 32 byte */
int rtt = csp_ping(CSP_SERVER_ADDR, 1000, 32, CSP_O_NONE);
if (rtt >= 0) {
log_printf("[CLIENT ] ping node %u (32 byte): %d ms\r\n", CSP_SERVER_ADDR, rtt);
} else {
log_printf("[CLIENT ] ping node %u: timeout\r\n", CSP_SERVER_ADDR);
}
/* 2) Request-reply ke port 10 */
char req[48];
char resp[96] = {0};
int len = snprintf(req, sizeof(req), "Halo OBC, pesan ke-%lu", (unsigned long)n);
int r = csp_transaction(CSP_PRIO_NORM, CSP_SERVER_ADDR, CSP_PORT_ECHO, 1000,
req, len, resp, -1);
if (r > 0) {
resp[r < (int)sizeof(resp) ? r : (int)sizeof(resp) - 1] = '\0';
log_printf("[CLIENT ] balasan: \"%s\"\r\n", resp);
} else {
log_printf("[CLIENT ] transaksi port %d gagal\r\n", CSP_PORT_ECHO);
}
/* 3) Telemetri 200 byte ke port 11 -> dipecah jadi banyak frame CAN */
csp_conn_t *conn = csp_connect(CSP_PRIO_NORM, CSP_SERVER_ADDR, CSP_PORT_TLM, 1000, CSP_O_NONE);
if (conn != NULL) {
csp_packet_t *packet = csp_buffer_get(TLM_SIZE);
if (packet != NULL) {
uint32_t sum = 0;
for (int i = 0; i < TLM_SIZE; i++) {
packet->data[i] = (uint8_t)(i + n);
sum += packet->data[i];
}
packet->length = TLM_SIZE;
if (csp_send(conn, packet, 0)) {
log_printf("[CLIENT ] kirim telemetri %d byte, checksum=%lu\r\n", TLM_SIZE, (unsigned long)sum);
} else {
csp_buffer_free(packet); /* hanya di-free kalau gagal kirim */
}
}
csp_close(conn);
}
osDelay(300);
log_printf("[STAT ] frame CAN TX=%lu (+%lu) RX=%lu error=%lu buffer CSP bebas=%d\r\n",
(unsigned long)csp_can_stats.tx_frames,
(unsigned long)(csp_can_stats.tx_frames - tx0),
(unsigned long)csp_can_stats.rx_frames,
(unsigned long)csp_can_stats.tx_errors,
csp_buffer_remaining());
n++;
osDelay(2700);
}
}
Perhatikan aturan kepemilikan buffer CSP: setelah csp_send() berhasil, buffer menjadi milik libcsp dan akan dibebaskan oleh libcsp. Kita hanya memanggil csp_buffer_free() kalau csp_send() gagal. Kalau buffer di-free dua kali, sistem bisa crash secara acak.
Langkah 6 – Build, upload dan hasil
Build project dengan Project → Build Project:
Build selesai tanpa error (warning RWX dari linker bisa diabaikan)
Lalu upload dengan Run → Run As → STM32 C/C++ Application, dan buka serial monitor (115200 baud) di port COM Mini Debugger. Hasil di board saya:
Log CSP lewat FDCAN1 (mode internal loopback)
Penjelasan log:
- ping node 1 (32 byte): 1 ms: paket ping pergi lewat CAN, dijawab service ping, dan balasannya kembali lewat CAN dalam waktu sekitar 1 ms.
- [SERVER] port 10 dan [CLIENT] balasan: request-reply berhasil dengan
csp_transaction(). - telemetri 200 byte: checksum yang dikirim client sama dengan yang diterima server, artinya 26 frame CAN berhasil disusun kembali tanpa kesalahan.
- [STAT] frame CAN TX=45 (+45): setiap siklus memakai 45 frame CAN, yaitu ping 5 + 5 frame, request 4 + balasan 5 frame, dan telemetri 26 frame. Jumlah RX sama dengan TX, error = 0, dan buffer CSP kembali utuh (20) setelah setiap siklus, tanda tidak ada kebocoran buffer.
Menghubungkan dua board (mode normal)
Untuk komunikasi sungguhan antar dua board (misalnya OBC dan modul lain):
- Ubah
#define CSP_CAN_LOOPBACK 0. - Beri alamat CSP berbeda di setiap board, misalnya board A
CSP_MY_ADDR 1denganCSP_SERVER_ADDR 2, dan board BCSP_MY_ADDR 2. - Pasang transceiver CAN di setiap board: PA12 → TXD/CTX, PA11 ← RXD/CRX, 3,3 V dan GND.
- Sambungkan CAN_H ke CAN_H dan CAN_L ke CAN_L, lalu pasang resistor 120 Ω di kedua ujung bus.
- Pastikan semua node memakai kecepatan yang sama (1 Mbit/s).
Kesalahan yang sering terjadi
| Masalah | Penyebab | Solusi |
|---|---|---|
| Paket tidak pernah sampai / tersusun | Frame dikirim dengan ID standar 11-bit | Pakai FDCAN_EXTENDED_ID, CFP butuh 29-bit |
| Panjang data salah | Nilai DataLength dihitung dengan geser bit (format HAL lama) | Di HAL STM32H7 ini, FDCAN_DLC_BYTES_8 = 8, jadi pakai langsung jumlah byte |
| Crash acak / HardFault | Buffer CSP di-free dua kali, atau di-free setelah csp_send() berhasil | Free hanya saat csp_send() gagal |
| Heap habis perlahan | malloc() di setiap pengiriman tanpa free() | Gunakan buffer CSP (csp_buffer_get) saja |
| Program berhenti di configASSERT | Prioritas interrupt FDCAN lebih tinggi dari 5 | Set prioritas FDCAN1_IT0 ≥ 5 |
| Tidak ada frame di bus (mode normal) | Tanpa transceiver / tanpa terminasi / bitrate beda | Cek transceiver, resistor 120 Ω, dan bitrate semua node |
Penutup
Dengan FreeRTOS dan libcsp, STM32H743 bisa menjadi node CSP yang lengkap: punya alamat, melayani ping dan service bawaan, bisa melakukan request-reply, dan mengirim paket panjang yang otomatis dipecah menjadi frame CAN. Karena kode aplikasi hanya berurusan dengan alamat dan port, jalur fisiknya nanti bisa diganti (misalnya ke I2C atau UART) tanpa mengubah logika program.
Langkah berikutnya, kita bisa mencoba komunikasi dua board lewat transceiver CAN sungguhan. InsyaAllah akan dibahas di tutorial berikutnya.
Semoga bermanfaat. Kalau ada pertanyaan, silakan tulis di kolom komentar.
Wassalamu'alaikum warohmatulohi wabarakatuh.



