Friday, October 16, 2026

Komunikasi CubeSat Space Protocol (libcsp) lewat CAN Bus FDCAN1 pada STM32H743 dengan FreeRTOS

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:

FieldUkuranKeterangan
Priority2 bit0 = critical, 1 = high, 2 = normal, 3 = low
Source5 bitalamat pengirim (0..31)
Destination5 bitalamat tujuan (31 = broadcast)
Destination port6 bitport layanan tujuan (0..63)
Source port6 bitport pengirim (dipakai untuk balasan)
Flags8 bitopsi 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:

PortService
0CMP (manajemen: identitas, routing, statistik)
1Ping
2Daftar task (process list)
3Sisa memori
4Reboot
5Sisa buffer CSP
6Uptime

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:

Format ID CAN 29-bit CFP pada libcsp

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

Arsitektur libcsp FreeRTOS FDCAN STM32H743

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:

  1. Buka Connectivity → FDCAN1, centang Activated. Pin otomatis menjadi PA11 = FDCAN1_RX dan PA12 = FDCAN1_TX.
  2. Di Clock Configuration, sumber clock FDCAN adalah PLL1Q = 50 MHz (DIVQ1 = 3).
  3. Di Parameter Settings isi nilai berikut untuk kecepatan 1 Mbit/s:
ParameterNilaiKeterangan
Frame FormatClassic modeCAN 2.0B (bukan CAN FD)
ModeNormaldi kode diganti ke internal loopback untuk uji 1 board
Nominal Prescaler550 MHz / 5 = 10 MHz → 1 tq = 100 ns
Nominal Sync Jump Width2
Nominal Time Seg17
Nominal Time Seg221 bit = 1 + 7 + 2 = 10 tq = 1 µs → 1 Mbit/s, sample point 80%
Auto RetransmissionEnable
Ext Filters Nbr2filter alamat sendiri + broadcast
Rx Fifo0 Elmts Nbr32antrian frame masuk
Tx Fifo Queue Elmts Nbr32antrian frame keluar
  1. 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).
  2. 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

  1. Download source libcsp dari GitHub github.com/libcsp/libcsp, pilih tag v1.6.
  2. Buat folder Middlewares/Third_Party/libcsp di project, lalu salin folder include dan file-file di src berikut:
    • src/*.c dan src/*.h (inti CSP: buffer, connection, router, io, service, dll)
    • src/arch/*.c dan src/arch/freertos/*.c (port untuk FreeRTOS)
    • src/interfaces/csp_if_can.c, csp_if_can_pbuf.c/.h, csp_if_lo.c
    • src/rtable/csp_rtable.c, csp_rtable_static.c, csp_rtable_internal.h
    • src/transport/*.c dan *.h
  3. Tambahkan include path di Project → Properties → C/C++ Build → Settings → MCU GCC Compiler → Include paths:
    ../Middlewares/Third_Party/libcsp/include dan ../Middlewares/Third_Party/libcsp/src
  4. libcsp aslinya memakai build system waf yang membuat file csp_autoconfig.h secara otomatis. Karena kita memakai STM32CubeIDE, file ini dibuat manual di include/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 project FreeRTOS libcsp STM32CubeIDE

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 over CAN STM32H743 serial monitor

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

  1. Ubah #define CSP_CAN_LOOPBACK 0.
  2. Beri alamat CSP berbeda di setiap board, misalnya board A CSP_MY_ADDR 1 dengan CSP_SERVER_ADDR 2, dan board B CSP_MY_ADDR 2.
  3. Pasang transceiver CAN di setiap board: PA12 → TXD/CTX, PA11 ← RXD/CRX, 3,3 V dan GND.
  4. Sambungkan CAN_H ke CAN_H dan CAN_L ke CAN_L, lalu pasang resistor 120 Ω di kedua ujung bus.
  5. Pastikan semua node memakai kecepatan yang sama (1 Mbit/s).

Kesalahan yang sering terjadi

MasalahPenyebabSolusi
Paket tidak pernah sampai / tersusunFrame dikirim dengan ID standar 11-bitPakai FDCAN_EXTENDED_ID, CFP butuh 29-bit
Panjang data salahNilai DataLength dihitung dengan geser bit (format HAL lama)Di HAL STM32H7 ini, FDCAN_DLC_BYTES_8 = 8, jadi pakai langsung jumlah byte
Crash acak / HardFaultBuffer CSP di-free dua kali, atau di-free setelah csp_send() berhasilFree hanya saat csp_send() gagal
Heap habis perlahanmalloc() di setiap pengiriman tanpa free()Gunakan buffer CSP (csp_buffer_get) saja
Program berhenti di configASSERTPrioritas interrupt FDCAN lebih tinggi dari 5Set prioritas FDCAN1_IT0 ≥ 5
Tidak ada frame di bus (mode normal)Tanpa transceiver / tanpa terminasi / bitrate bedaCek 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.

Tuesday, October 13, 2026

Simulasi Nilai SPMB Jakarta 2026: Kalkulator Indeks Prestasi SMA & SMP

Halo, para orang tua dan calon peserta didik! Masa Sistem Penerimaan Murid Baru (SPMB) 2026 di DKI Jakarta sudah semakin dekat, dan salah satu pertanyaan yang paling sering muncul adalah: "Berapa sih nilai akhir saya nanti?"

Perhitungan nilai SPMB tidak lagi sekadar rata-rata rapor. Ada banyak komponen yang ikut dihitung, mulai dari nilai rapor, nilai TKA (Tes Kemampuan Akademik), persentil peringkat di sekolah yang disesuaikan dengan Rapor Pendidikan sekolah, prestasi akademik dan non-akademik, kepemimpinan OSIS/MPK dan ekstrakurikuler, hingga seleksi ketat seperti Pramuka, Paskibra, atau Hafiz Al-Qur'an. Setiap komponen punya bobot yang berbeda untuk jalur Prestasi Akademik dan Prestasi Non-Akademik.

Supaya lebih mudah, saya membuat kalkulator simulasi nilai SPMB 2026 berikut ini, berdasarkan Keputusan Kepala Dinas Pendidikan No. 261 Tahun 2026 (Lampiran II). Cukup pilih jenjang (SMA atau SMP), isi data yang diminta, dan hasilnya langsung muncul:

  • Indeks Prestasi Akademik – untuk Jalur Prestasi Akademik
  • Indeks Prestasi Non-Akademik – untuk Jalur Prestasi Non-Akademik
  • Nilai Rapor 75% + Persentil 25% – untuk jalur Afirmasi P2, Domisili, Mutasi, dan Tahap 2

Catatan: hasil kalkulator ini hanya perkiraan untuk membantu persiapan, bukan hasil resmi. Selalu cek kembali ketentuan terbaru di situs resmi SPMB Jakarta.

Friday, February 28, 2025

Implementasi FreeRTOS pada STM32H7 series STM32H7xx / STM32H743 / STM32H750 menggunakan software Arduino IDE

 Assalamu'alaikum warohmatulohi wabarakatuh,


tunggu wangsit dulu ya...

Implementasi FreeRTOS pada STM32H7 series STM32H7xx / STM32H743 / STM32H750 menggunakan software STM32CubeIDE

Assalamu'alaikum warohmatulohi wabarakatuh,

Kali ini kita akan membahas cara menjalankan FreeRTOS di mikrokontroler STM32H743VIT6 dengan software STM32CubeIDE. Sebelum masuk ke praktik, kita bahas dulu apa itu RTOS, apa kelebihannya, dan mengapa FreeRTOS banyak dipakai.

Apa itu RTOS?

RTOS (Real Time Operating System) adalah sistem operasi kecil untuk mikrokontroler yang tugas utamanya mengatur kapan setiap pekerjaan dijalankan. Pekerjaan-pekerjaan itu dipecah menjadi beberapa task (disebut juga thread). Setiap task adalah fungsi dengan perulangan sendiri, punya stack sendiri, dan punya prioritas.

Inti dari RTOS adalah scheduler. Scheduler memilih task mana yang boleh memakai CPU. Pada STM32 (Cortex-M), scheduler berjalan setiap tick (biasanya 1 ms) dan setiap kali ada task yang menunggu sesuatu. Karena perpindahan antar task terjadi sangat cepat, beberapa task terlihat berjalan bersamaan, padahal CPU-nya hanya satu.

Kata real time di sini bukan berarti "cepat", tetapi tepat waktu dan bisa diprediksi. RTOS menjamin task berprioritas tinggi langsung dijalankan begitu siap, sehingga waktu respons sistem bisa dihitung dan dijamin.

Super loop vs RTOS

Tanpa RTOS, program mikrokontroler biasanya ditulis dengan pola super loop: semua pekerjaan dipanggil berurutan di dalam while(1). Cara ini sederhana, tetapi makin sulit diatur ketika pekerjaannya bertambah banyak. Satu fungsi yang lambat (misalnya menunggu data atau memakai HAL_Delay()) membuat semua pekerjaan lain ikut tertahan.

AspekSuper loop (tanpa RTOS)Dengan RTOS
Struktur programSemua pekerjaan di satu while(1)Setiap pekerjaan punya task sendiri
Delay / menungguHAL_Delay() menghentikan seluruh programosDelay() hanya menghentikan task itu, task lain tetap jalan
PrioritasTidak ada, kecuali lewat interruptSetiap task punya prioritas, task penting bisa menyela task lain
Komunikasi antar bagianVariabel global dan flagQueue, semaphore, mutex, event flag yang aman
Penambahan fiturHarus mengatur ulang urutan dan timing di loopCukup menambah task baru
Kebutuhan memoriSangat kecilButuh RAM tambahan untuk stack tiap task dan kernel

Kelebihan memakai RTOS

  • Program lebih modular. Setiap fungsi (baca sensor, kirim data, tampilkan ke LCD) ditulis sebagai task terpisah, sehingga lebih mudah dibaca, diuji, dan dikerjakan bersama tim.
  • Respons lebih cepat dan terjamin. Task berprioritas tinggi langsung menyela task berprioritas rendah (preemption), sehingga pekerjaan penting tidak menunggu pekerjaan lain selesai.
  • CPU tidak terbuang untuk menunggu. Task yang sedang menunggu (delay, data, atau sinyal) masuk keadaan blocked dan tidak memakai CPU sama sekali. Waktu CPU dipakai task lain.
  • Komunikasi antar task aman. RTOS menyediakan queue untuk mengirim data, semaphore untuk sinyal (misalnya dari interrupt), dan mutex untuk berbagi resource seperti UART atau SPI, sehingga data tidak rusak saat diakses beberapa task.
  • Timing lebih rapi. Fungsi seperti osDelay(), osDelayUntil() dan software timer membuat pekerjaan periodik mudah dibuat tanpa menghitung timing secara manual.
  • Mudah dikembangkan. Menambah fitur baru cukup dengan menambah task, tanpa mengubah timing bagian program yang sudah berjalan.
  • Hemat daya. Saat semua task sedang menunggu, RTOS menjalankan idle task yang bisa dipakai untuk membuat MCU masuk mode sleep.

Tentu ada konsekuensinya: RTOS membutuhkan RAM tambahan (stack untuk setiap task dan memori kernel), dan programmer perlu memahami konsep baru seperti prioritas, deadlock, dan race condition. Untuk MCU sebesar STM32H743 dengan RAM sekitar 1 MB, kebutuhan memori ini sama sekali bukan masalah.

Mengenal FreeRTOS

FreeRTOS adalah RTOS open source yang paling banyak dipakai di dunia mikrokontroler. FreeRTOS dikembangkan oleh Richard Barry sejak awal tahun 2000-an, dan sejak 2017 dikelola oleh Amazon Web Services (AWS) dengan lisensi MIT, sehingga gratis dipakai termasuk untuk produk komersial.

Beberapa alasan FreeRTOS populer:

  • Kecil dan ringan. Kernel-nya hanya beberapa file C (tasks.c, queue.c, list.c, timers.c, dan lain-lain) dan cukup beberapa kilobyte flash.
  • Mendukung banyak arsitektur. Berjalan di ARM Cortex-M, Cortex-A, RISC-V, ESP32, AVR, PIC, dan puluhan arsitektur lain.
  • Fiturnya lengkap. Ada task dengan prioritas, scheduler preemptive, queue, semaphore (binary dan counting), mutex (dengan priority inheritance), software timer, event group, stream buffer dan message buffer.
  • Sudah terintegrasi di STM32CubeIDE. FreeRTOS bisa diaktifkan cukup dengan beberapa klik di STM32CubeMX, lalu kode inisialisasinya dibuat otomatis.
  • Dokumentasi dan komunitasnya besar, jadi contoh program dan solusi masalah mudah ditemukan.

Di STM32CubeIDE, FreeRTOS dibungkus dengan lapisan CMSIS-RTOS buatan ARM. Ada dua pilihan, CMSIS_V1 dan CMSIS_V2. Pada tutorial ini kita memakai CMSIS_V2, sehingga fungsi yang dipakai di program berawalan os..., misalnya osThreadNew(), osDelay(), osMessageQueuePut() dan osMutexAcquire(). Fungsi-fungsi ini memanggil fungsi asli FreeRTOS seperti xTaskCreate() dan vTaskDelay(). Kelebihannya, kode kita lebih mudah dipindah ke RTOS lain yang juga mendukung CMSIS-RTOS. Versi FreeRTOS yang dipakai di project ini adalah 10.3.1.

Beberapa istilah penting di FreeRTOS:

IstilahPenjelasan singkat
Task / ThreadFungsi yang berjalan sendiri dengan stack dan prioritas sendiri
SchedulerBagian kernel yang memilih task mana yang dijalankan CPU
TickInterrupt periodik (di sini 1 ms dari SysTick) sebagai satuan waktu RTOS
State taskRunning (sedang jalan), Ready (siap jalan), Blocked (menunggu delay/data/sinyal), Suspended (dihentikan sementara)
QueueAntrian untuk mengirim data dari satu task ke task lain
SemaphoreSinyal antar task atau dari interrupt ke task
MutexKunci supaya satu resource (misalnya UART) hanya dipakai satu task pada satu waktu
Software timerTimer berbasis tick yang memanggil fungsi callback secara periodik atau sekali
Idle taskTask prioritas paling rendah yang dijalankan saat semua task lain sedang menunggu
HeapArea memori tempat kernel membuat task, queue, dan objek lain (di project ini memakai heap_4)

Praktik: FreeRTOS di STM32H743VIT6

Konfigurasi FreeRTOS di tutorial ini saya ambil dari project yang saya pakai sehari-hari. Agar mudah dipahami, kita mulai dari project baru dan cukup membuat test LED berkedip di dalam task FreeRTOS: LED nyala 1 detik, lalu mati 2 detik. Kalau LED sudah berkedip dengan pola itu, berarti scheduler FreeRTOS sudah berjalan dengan benar dan project siap dikembangkan.

Tutorial ini saya praktikkan di STM32H743VIT6, tetapi langkah-langkahnya juga berlaku untuk seri STM32H7 lain seperti STM32H750. Cukup pilih chip yang sesuai di Langkah 1.

Alat dan bahan

  1. Board STM32H743VIT6 (LED terhubung ke pin PA1)
  2. ST-Link V2 / V3 untuk upload dan debug
  3. STM32CubeIDE (saya memakai STM32CubeIDE 1.17.0, dengan project dibuat di STM32CubeMX 6.9.1 dan firmware package STM32Cube FW_H7 V1.11.2)

Langkah 1 – Membuat project baru

  1. Buka STM32CubeIDE, lalu pilih File → New → STM32 Project.
  2. Di tab MCU/MPU Selector, ketik STM32H743VIT6, pilih chip tersebut, lalu klik Next.
  3. Beri nama project, misalnya H743_FreeRTOS_LED, lalu klik Finish.

Langkah 2 – Konfigurasi System Core

GPIO: klik pin PA1, pilih GPIO_Output, lalu beri label LED (klik kanan → Enter User Label). Biarkan setting default: Output Push Pull, No pull-up and no pull-down, output level Low.

Konfigurasi GPIO PA1 sebagai LED di STM32CubeIDE

Pin PA1 diset sebagai GPIO_Output dengan label LED

SYS: ubah Timebase Source dari SysTick ke TIM1.
Ini penting! FreeRTOS memakai SysTick sebagai tick RTOS, jadi HAL harus memakai timer lain. Kalau tidak diubah, CubeIDE akan memberi peringatan saat generate code.

Timebase Source TIM1 di menu SYS

Timebase Source HAL dipindah ke TIM1

RCC: set High Speed Clock (HSE) dan Low Speed Clock (LSE) ke Crystal/Ceramic Resonator. Pada tutorial ini sumber clock sistem tetap memakai HSI internal, jadi HSE/LSE disiapkan saja untuk pengembangan berikutnya.

Konfigurasi RCC HSE dan LSE crystal

HSE dan LSE diset ke Crystal/Ceramic Resonator

NVIC: pastikan Priority Group adalah 4 bits for pre-emption priority (default saat FreeRTOS diaktifkan).

Langkah 3 – Konfigurasi Clock

Di project ini saya memakai clock 75 MHz yang berasal dari HSI (64 MHz) lewat PLL1. Clock ini sudah cukup untuk FreeRTOS dan lebih hemat daya. Buka tab Clock Configuration lalu atur:

ParameterNilai
PLL Source MuxHSI (64 MHz)
DIVM1/4 (input PLL = 16 MHz)
DIVN1X 9, dengan fracn1 = 3072 (VCO = 150 MHz)
DIVP1/2 (PLL1P = 75 MHz)
DIVQ1/3 (PLL1Q = 50 MHz)
System Clock MuxPLLCLK
SYSCLK / HCLK / APBx75 MHz (semua prescaler = /1)
Clock Configuration STM32H743 75 MHz dari HSI

Clock Configuration: HSI 64 MHz → PLL1 → SYSCLK 75 MHz

Di RCC → Parameter Settings, Power Regulator Voltage Scale cukup Scale 3 dan Supply Source = PWR_LDO_SUPPLY. Untuk clock 75 MHz, flash latency yang dihasilkan CubeMX adalah FLASH_LATENCY_1.

Kalau nanti butuh performa maksimal, clock bisa dinaikkan sampai 480 MHz (revisi V) dengan Voltage Scale 0.

Langkah 4 – Mengaktifkan FreeRTOS

  1. Buka Middleware and Software Packs → FREERTOS.
  2. Pada Interface, pilih CMSIS_V2.
  3. Di tab Config parameters, saya memakai nilai berikut:
ParameterNilaiKeterangan
USE_PREEMPTIONEnabledTask prioritas tinggi bisa menyela task prioritas rendah
TICK_RATE_HZ10001 tick = 1 ms, jadi osDelay(1000) = 1 detik
MAX_PRIORITIES56Default CMSIS_V2
MINIMAL_STACK_SIZE7680 wordsStack minimal setiap task
TOTAL_HEAP_SIZE70360 bytesMemori untuk membuat task, queue, mutex
USE_NEWLIB_REENTRANTEnabledWajib kalau memakai printf/sprintf/malloc di dalam task
USE_MUTEXES / USE_RECURSIVE_MUTEXESEnabledDefault
USE_COUNTING_SEMAPHORESEnabledDefault
USE_TIMERSEnabledSoftware timer
FreeRTOS Config parameters CMSIS_V2

FreeRTOS dengan interface CMSIS_V2, TICK_RATE_HZ 1000 dan MINIMAL_STACK_SIZE 7680 words

Catatan: nilai MINIMAL_STACK_SIZE dan TOTAL_HEAP_SIZE di atas cukup besar karena project asli saya juga menjalankan protokol komunikasi yang butuh banyak memori. Untuk test LED saja, nilai default CubeMX sebenarnya sudah cukup, tapi nilai di atas aman dipakai karena RAM STM32H743 besar.

  1. Di tab Tasks and Queues, biarkan task bawaan defaultTask: Priority osPriorityNormal, Entry Function StartDefaultTask, Allocation Dynamic. Stack Size otomatis mengikuti MINIMAL_STACK_SIZE, yaitu 7680 words, karena CubeMX tidak mengizinkan stack task lebih kecil dari nilai minimal tersebut.
FreeRTOS Tasks and Queues defaultTask

Tab Tasks and Queues: defaultTask dengan entry function StartDefaultTask

Simpan (Ctrl+S) lalu klik Yes untuk generate code.

Langkah 5 – Menulis program test LED

Setelah generate code, CubeIDE otomatis membuat definisi pin di Core/Inc/main.h:

#define LED_Pin GPIO_PIN_1
#define LED_GPIO_Port GPIOA

Di Core/Src/main.c, fungsi main() sudah berisi urutan inisialisasi FreeRTOS. Kita tidak perlu mengubahnya:

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();

  /* Init scheduler */
  osKernelInitialize();

  /* Create the thread(s) */
  defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);

  /* Start scheduler */
  osKernelStart();

  /* Program tidak akan sampai ke sini karena sudah diambil alih scheduler */
  while (1)
  {
  }
}

Atribut task yang dibuat CubeMX (stack dalam byte = words × 4):

osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
  .name = "defaultTask",
  .stack_size = 7680 * 4,
  .priority = (osPriority_t) osPriorityNormal,
};

Sekarang cari fungsi StartDefaultTask di bagian bawah main.c, lalu isi dengan kode test LED berikut. LED dinyalakan selama 1 detik, lalu dimatikan selama 2 detik:

void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  for(;;)
  {
    HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);    // LED PA1 nyala
    osDelay(1000);                                              // nyala 1 detik
    HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);  // LED PA1 mati
    osDelay(2000);                                              // mati 2 detik
  }
  /* USER CODE END 5 */
}
Kode StartDefaultTask LED FreeRTOS di main.c

Kode test LED di dalam StartDefaultTask (main.c)

Pastikan kode ditulis di antara USER CODE BEGIN dan USER CODE END supaya tidak terhapus saat generate code ulang.

Di sini kita memakai HAL_GPIO_WritePin(), bukan HAL_GPIO_TogglePin(), supaya lama nyala dan lama mati bisa diatur berbeda.

Karena timebase HAL dipindah ke TIM1, CubeMX juga membuat callback berikut di main.c. Callback ini yang menjaga HAL_Delay() dan HAL_GetTick() tetap berjalan:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == TIM1) {
    HAL_IncTick();
  }
}

Perhatikan: di dalam task, selalu gunakan osDelay(), bukan HAL_Delay(). osDelay() memberi kesempatan task lain untuk berjalan, sedangkan HAL_Delay() hanya membuat CPU menunggu (busy wait).

Langkah 6 – Build dan upload

  1. Klik Project → Build Project (atau ikon palu) dan pastikan tidak ada error.
  2. Sambungkan ST-Link (SWDIO, SWCLK, GND, 3.3V).
  3. Klik Run → Run As → STM32 C/C++ Application, lalu klik OK pada jendela konfigurasi.
  4. Kalau muncul tawaran update firmware ST-Link, boleh pilih No untuk melanjutkan upload.

Kalau berhasil, di Console akan muncul tulisan Download verified successfully:

Upload program STM32H743 berhasil via ST-Link

Upload ke STM32H743 (Rev V, flash 2 MB) berhasil diverifikasi

Hasilnya, LED di PA1 akan nyala 1 detik lalu mati 2 detik, berulang terus. Artinya FreeRTOS sudah berjalan: osKernelStart() menjalankan scheduler, lalu scheduler menjalankan defaultTask.

Langkah 7 – Demo multitasking: Queue, Mutex, Semaphore dan Software Timer

Test LED sudah membuktikan FreeRTOS berjalan, tetapi belum memperlihatkan kelebihan RTOS yang sebenarnya. Pada langkah ini kita membuat demo dengan 5 task yang saling berkomunikasi, lalu melihat hasilnya lewat serial monitor.

Project demo ini saya buat terpisah dengan nama H743_FreeRTOS_Demo, dengan konfigurasi yang sama seperti project LED ditambah USART1 untuk mencetak log.

Rancangan demo

TaskPrioritasTugasFitur RTOS yang diperlihatkan
defaultTaskNormalLED PA1 nyala 1 detik, mati 2 detikosDelay
sensorTaskNormalSetiap 500 ms membaca data sensor (simulasi suhu) dan mengirimnya ke queueQueue, osDelayUntil
processTaskAboveNormalMenunggu data di queue, lalu menghitung rata-rata setiap 4 sampelBlocking pada queue, preemption
eventTaskHighMenunggu sinyal dari software timer setiap 3 detikBinary semaphore, software timer
monitorTaskLowSetiap 10 detik menampilkan status semua task, sisa stack dan sisa heaposThreadGetState, osThreadGetStackSpace

Semua task mencetak ke UART lewat fungsi log_printf() yang dilindungi mutex, supaya teks dari task yang berbeda tidak tercampur.

Konfigurasi USART1 dan sambungan kabel

Di STM32CubeIDE, buka Connectivity → USART1 lalu set Mode ke Asynchronous dengan baudrate 115200, 8 bit, tanpa parity, 1 stop bit. Pin yang dipakai adalah PA9 (TX) dan PA10 (RX).

Board yang saya pakai adalah DevEBox STM32H7XX_M, sedangkan debugger-nya WeAct Mini Debugger. Debugger ini selain untuk upload (SWD) juga punya USB to UART, jadi tidak perlu USB-TTL terpisah. Sambungannya seperti ini (TX dan RX disilang):

DevEBox STM32H7XX_MWeAct Mini Debugger
PA9 (TX1)RXD
PA10 (RX1)TXD
GNDGND
DIO / DCLK (header SWD)SWDIO / SWCLK
Sambungan UART DevEBox STM32H7 ke WeAct Mini Debugger

Sambungan PA9/PA10 board DevEBox ke TXD/RXD WeAct Mini Debugger

Catatan: PA9/PA10 berbeda dengan pin CAN bus. Pada project saya yang lain, FDCAN1 memakai PA11 (RX) dan PA12 (TX), yang letaknya bersebelahan, jadi hati-hati saat memasang kabel.

Membuat objek RTOS

Task tambahan dan objek RTOS dibuat di dalam blok USER CODE pada fungsi main(), supaya tidak hilang saat generate code ulang:

/* USER CODE BEGIN RTOS_MUTEX */
uartMutexHandle = osMutexNew(&uartMutex_attributes);
/* USER CODE END RTOS_MUTEX */

/* USER CODE BEGIN RTOS_SEMAPHORES */
/* Binary semaphore: nilai maksimum 1, nilai awal 0 (belum ada sinyal) */
eventSemHandle = osSemaphoreNew(1, 0, &eventSem_attributes);
/* USER CODE END RTOS_SEMAPHORES */

/* USER CODE BEGIN RTOS_TIMERS */
eventTimerHandle = osTimerNew(EventTimerCallback, osTimerPeriodic, NULL, &eventTimer_attributes);
/* USER CODE END RTOS_TIMERS */

/* USER CODE BEGIN RTOS_QUEUES */
/* Queue 8 elemen, tiap elemen berukuran uint32_t */
sensorQueueHandle = osMessageQueueNew(8, sizeof(uint32_t), &sensorQueue_attributes);
/* USER CODE END RTOS_QUEUES */

/* USER CODE BEGIN RTOS_THREADS */
sensorTaskHandle  = osThreadNew(StartSensorTask,  NULL, &sensorTask_attributes);
processTaskHandle = osThreadNew(StartProcessTask, NULL, &processTask_attributes);
eventTaskHandle   = osThreadNew(StartEventTask,   NULL, &eventTask_attributes);
monitorTaskHandle = osThreadNew(StartMonitorTask, NULL, &monitorTask_attributes);
/* USER CODE END RTOS_THREADS */

Contoh atribut salah satu task (stack 512 words, prioritas AboveNormal):

const osThreadAttr_t processTask_attributes = {
  .name = "processTask",
  .stack_size = 512 * 4,
  .priority = (osPriority_t) osPriorityAboveNormal,
};

Mutex: printf yang aman untuk banyak task

static void log_printf(const char *fmt, ...)
{
  char buf[160];
  int len = snprintf(buf, sizeof(buf), "[%7lu ms] ", (unsigned long)osKernelGetTickCount());

  va_list args;
  va_start(args, fmt);
  len += vsnprintf(buf + len, sizeof(buf) - len, fmt, args);
  va_end(args);
  if (len >= (int)sizeof(buf)) len = sizeof(buf) - 1;

  osMutexAcquire(uartMutexHandle, osWaitForever);   /* kunci UART  */
  HAL_UART_Transmit(&huart1, (uint8_t *)buf, (uint16_t)len, 100);
  osMutexRelease(uartMutexHandle);                  /* lepas kunci */
}

Tanpa mutex, dua task yang mencetak hampir bersamaan bisa menghasilkan teks yang campur aduk. Dengan mutex, task kedua akan menunggu (blocked) sampai task pertama selesai memakai UART.

Queue: sensorTask mengirim, processTask menerima

void StartSensorTask(void *argument)
{
  uint32_t n = 0;
  uint32_t lastWake = osKernelGetTickCount();

  for(;;)
  {
    /* Data simulasi: suhu x10, antara 25,0 C dan 27,9 C */
    uint32_t suhu_x10 = 250 + ((n * 7U) % 30U);

    log_printf("[SENSOR ] baca #%lu = %lu.%lu C -> kirim ke queue\r\n",
               (unsigned long)n, (unsigned long)(suhu_x10 / 10), (unsigned long)(suhu_x10 % 10));
    osMessageQueuePut(sensorQueueHandle, &suhu_x10, 0, 0);
    n++;

    /* Periode tetap 500 ms, tidak terpengaruh lama proses */
    lastWake += 500;
    osDelayUntil(lastWake);
  }
}

void StartProcessTask(void *argument)
{
  uint32_t data, sum = 0, count = 0;

  for(;;)
  {
    /* Blocked sampai ada data di queue, tidak memakai CPU sama sekali */
    if (osMessageQueueGet(sensorQueueHandle, &data, NULL, osWaitForever) == osOK)
    {
      sum += data;
      count++;
      log_printf("[PROCESS] terima %lu.%lu C (sampel %lu/4)\r\n",
                 (unsigned long)(data / 10), (unsigned long)(data % 10), (unsigned long)count);
      if (count >= 4)
      {
        uint32_t avg = sum / count;
        log_printf("[PROCESS] >>> rata-rata 4 sampel = %lu.%lu C\r\n",
                   (unsigned long)(avg / 10), (unsigned long)(avg % 10));
        sum = 0;
        count = 0;
      }
    }
  }
}

Karena processTask berprioritas lebih tinggi, begitu osMessageQueuePut() dipanggil, scheduler langsung memindahkan CPU ke processTask (preemption). Di log terlihat baris [PROCESS] muncul hanya sekitar 5 ms setelah [SENSOR].

Software timer dan semaphore

/* Dipanggil oleh Timer Service Task FreeRTOS setiap 3 detik */
void EventTimerCallback(void *argument)
{
  osSemaphoreRelease(eventSemHandle);   /* beri sinyal */
}

void StartEventTask(void *argument)
{
  uint32_t events = 0;
  osTimerStart(eventTimerHandle, 3000);

  for(;;)
  {
    /* Tunggu sinyal dari timer */
    if (osSemaphoreAcquire(eventSemHandle, osWaitForever) == osOK)
    {
      events++;
      log_printf("[EVENT  ] semaphore diterima dari timer (event ke-%lu)\r\n",
                 (unsigned long)events);
    }
  }
}

Pola yang sama dipakai untuk menunggu sinyal dari interrupt, misalnya tombol ditekan atau data UART masuk. Interrupt cukup memanggil osSemaphoreRelease(), lalu pekerjaan yang lebih berat dikerjakan oleh task.

Monitor: melihat isi "dapur" RTOS

void StartMonitorTask(void *argument)
{
  const osThreadId_t tasks[] = { defaultTaskHandle, sensorTaskHandle, processTaskHandle,
                                 eventTaskHandle, monitorTaskHandle };
  for(;;)
  {
    osDelay(10000);
    for (uint32_t i = 0; i < 5; i++)
    {
      log_printf("[MONITOR] %-12s prio=%2d  state=%-8s  sisa stack=%5lu byte\r\n",
                 osThreadGetName(tasks[i]), (int)osThreadGetPriority(tasks[i]),
                 state_name(osThreadGetState(tasks[i])),
                 (unsigned long)osThreadGetStackSpace(tasks[i]));
    }
    log_printf("[MONITOR] jumlah task=%lu  sisa heap=%lu byte  isi queue=%lu\r\n",
               (unsigned long)osThreadGetCount(), (unsigned long)xPortGetFreeHeapSize(),
               (unsigned long)osMessageQueueGetCount(sensorQueueHandle));
  }
}

Hasil di serial monitor

Setelah program di-upload, buka serial monitor di port COM milik Mini Debugger (di laptop saya COM7) dengan baudrate 115200. Di STM32CubeIDE bisa lewat Console → Open Console → Command Shell Console → Serial Port. Hasilnya seperti ini:

Log demo FreeRTOS STM32H743 di serial monitor

Log demo FreeRTOS: SENSOR, PROCESS, EVENT dan MONITOR berjalan bersamaan

Dari log di atas bisa dilihat:

  • Queue dan preemption: setiap 500 ms [SENSOR] mengirim data, dan sekitar 5 ms kemudian [PROCESS] langsung menerimanya. Setiap 4 sampel muncul hasil rata-rata.
  • Software timer dan semaphore: [EVENT] muncul tepat setiap 3 detik.
  • Monitor: ada 7 task yang berjalan, yaitu 5 task buatan kita ditambah Idle task dan Timer Service task bawaan FreeRTOS. Saat laporan dibuat, monitorTask berstatus Running, sedangkan task lain Blocked karena sedang menunggu delay, data, atau sinyal. Artinya CPU tidak terbuang untuk menunggu.
  • Sisa stack setiap task masih 1,3–2,4 KB, dan sisa heap 29.120 byte. Informasi ini berguna untuk menentukan ukuran stack yang pas.
  • Mutex: tidak ada baris log yang tercampur, walaupun 5 task mencetak ke UART yang sama.

Sementara itu LED PA1 tetap berkedip dengan pola 1 detik nyala dan 2 detik mati, tanpa terganggu oleh task lain.

Catatan tentang revisi chip STM32H743VIT6

Ada dua revisi STM32H743 di pasaran. Revisi V bisa berjalan sampai 480 MHz, sedangkan revisi Y (lebih lama) hanya sampai 400 MHz. Revisinya bisa dilihat dari huruf terakhir pada kode di badan chip, atau dari baris Revision ID di Console saat upload (lihat gambar di atas, chip saya Rev V). Pada tutorial ini clock hanya 75 MHz, jadi kedua revisi aman dipakai.

Troubleshooting

  • Pola kedip terbalik (nyala 2 detik, mati 1 detik): LED di board kalian aktif-low (menyala saat pin LOW). Tukar saja GPIO_PIN_SET dan GPIO_PIN_RESET di kode.
  • LED tidak berkedip: cek apakah LED di board memang terhubung ke PA1. Kalau LED di board kalian ada di pin lain, ganti pin di CubeMX dan beri label LED yang sama, maka kode tidak perlu diubah.
  • Program masuk HardFault: biasanya karena stack task kurang. Naikkan Stack Size task, terutama task yang memakai sprintf/printf.
  • Program berhenti di configASSERT: cek prioritas interrupt. Interrupt yang memanggil API FreeRTOS (fungsi berakhiran FromISR) harus punya prioritas ≥ 5 (angka lebih besar = prioritas lebih rendah). Nilai ini ditentukan oleh configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY di FreeRTOSConfig.h.
  • Muncul peringatan SysTick saat generate code: Timebase Source di SYS belum diubah ke TIM1.
  • Muncul pesan "No ST-LINK detected": ST-Link belum tersambung ke laptop. Sambungkan lalu klik Run lagi.
  • Board tidak bisa di-upload / terkunci: aktifkan Trace and Debug → DEBUG → Serial Wire supaya pin SWD (PA13/PA14) tidak dipakai fungsi lain. Kalau terlanjur terkunci, gunakan mode Connect Under Reset di STM32CubeProgrammer.

Penutup

Itulah cara dasar mengimplementasikan FreeRTOS di STM32H743VIT6 menggunakan STM32CubeIDE, dengan test LED berkedip sebagai pembuktian. Lewat demo multitasking kita juga sudah melihat cara kerja Queue, Mutex, Semaphore dan Software Timer. Dari sini kalian bisa mengembangkan project dengan sensor sungguhan, interrupt, atau komunikasi seperti CAN bus. InsyaAllah akan dibahas di tutorial berikutnya.

Semoga bermanfaat. Kalau ada pertanyaan, silakan tulis di kolom komentar.

Wassalamu'alaikum warohmatulohi wabarakatuh.

Pemrograman STM32H7 series STM32H7xx / STM32H743 / STM32H750 di STM32Cube IDE menggunakan JTAG downloader WeAct ST-Link V2.1 (STM32CubeProgramming - SWD)

Assalamu'alaikum warohmatullahi wabarakatuh,

Pada tulisan kali ini saya akan berbagi cara memprogram mikrokontroler STM32H7 series, khususnya STM32H743 dan STM32H750, menggunakan STM32CubeIDE dan downloader WeAct ST-Link V2.1 lewat jalur SWD (Serial Wire Debug). Modul ini kompatibel dengan ST-LINK V2, murah, dan sudah dilengkapi USB-UART, sehingga cocok untuk belajar. ST-LINK V2 original maupun clone juga tetap bisa dipakai dengan langkah yang sama.

STM32H7 adalah keluarga ARM Cortex-M7 yang sangat kencang. STM32H743 bisa berjalan sampai 480 MHz dengan flash internal 2 MB, sedangkan STM32H750 adalah versi "value line" dengan core yang sama tetapi flash internal hanya 128 KB. Namun, H7 punya beberapa jebakan yang tidak ada di seri F1 atau F4, terutama soal konfigurasi power supply. Kalau salah setting, chip bisa terlihat seperti "mati" setelah di-flash.

Pada tutorial ini saya menggunakan core board DevEBox STM32H7XX_M buatan mcudev (Ver V2.0) dengan chip STM32H743VIT6: kemasan LQFP100, flash internal 2 MB, RAM 1 MB, dan kecepatan maksimal 480 MHz. Board ini juga dilengkapi kristal 25 MHz dan 32,768 kHz, QSPI flash W25Q64 (8 MB), slot microSD, konektor kamera DCMI, header LCD/OLED SPI, tombol RST/K1/K2, serta LED user D2. Dengan flash internal 2 MB, program bisa langsung disimpan di flash internal tanpa perlu QSPI eksternal.

Tampak depan board DevEBox STM32H7XX_M. Header SWD (J1) ada di sebelah port USB.

Yang akan dibahas:

  1. Alat dan bahan
  2. Wiring ST-LINK ke board STM32H7
  3. Membuat project dan konfigurasi di STM32CubeIDE
  4. Program sederhana: LED berkedip (blink)
  5. Upload dan debug dari STM32CubeIDE
  6. Upload menggunakan STM32CubeProgrammer
  7. Catatan khusus STM32H750
  8. Troubleshooting masalah yang sering muncul

1. Alat dan Bahan

ItemKeterangan
Board STM32H7DevEBox STM32H7XX_M (mcudev) dengan STM32H743VIT6; versi STM32H750VBT6 juga tersedia
Downloader ST-LINKSaya memakai WeAct ST-Link V2.1 (Mini Debugger): port USB-C, SWD, dan USB-UART (virtual COM port) dalam satu modul. ST-LINK V2 original atau clone juga bisa dipakai
Kabel jumper female–femaleMinimal 4 kabel untuk SWD, ditambah 2–3 kabel jika memakai UART
Kabel USBUSB-C untuk debugger, dan kabel micro USB untuk board jika board diberi daya sendiri
STM32CubeIDEIDE gratis dari ST, sudah termasuk STM32CubeMX dan driver ST-LINK
STM32CubeProgrammerUntuk flash file .hex/.bin/.elf dan erase chip

Pastikan driver ST-LINK sudah terpasang. Biasanya driver ikut terpasang bersama STM32CubeIDE. Jika belum, cari paket "STSW-LINK009" di situs ST.

2. Wiring ST-LINK ke STM32H7 (SWD)

SWD hanya butuh dua jalur data, yaitu SWDIO dan SWCLK, ditambah GND. Pada STM32H7, kedua pin tersebut ada di PA13 dan PA14.

Sebagai downloader saya memakai WeAct ST-Link V2.1. Modul ini kompatibel dengan ST-LINK V2, tetapi memakai firmware ST-LINK V2-1 yang menambahkan USB-UART (virtual COM port). Jadi selain untuk flash dan debug lewat SWD, modul yang sama bisa dipakai untuk melihat output printf dari board tanpa USB-TTL tambahan. Modul ini bernama Mini Debugger V1.0 dari WeAct Studio, dengan konektor 10 pin yang labelnya tercetak di bagian belakang PCB.

Modul WeAct ST-Link V2.1: port USB-C di satu sisi, konektor kabel untuk jalur SWD dan UART di sisi lain.

Label pin WeAct Mini Debugger V1.0 di bagian belakang PCB.

KelompokPin (urut dari atas)
SWD3V3, SWDIO, SWCLK, GND, SWO, NRST
UARTTXD, RXD, GND, 5V

Pin SWO dan 5V tidak dipakai di tutorial ini. Pin NRST bisa disambung ke pin NRST board jika tersedia; header J1 di board DevEBox tidak menyediakannya.

Board DevEBox sudah menyediakan header SWD 5 pin (J1) di sebelah port micro USB, dengan label BT0, 3V3, GND, DIO, CLK. Sambungkan seperti tabel berikut:

Pin WeAct Mini DebuggerPin header J1 boardPin STM32H7Keterangan
SWDIODIOPA13Jalur data dua arah
SWCLKCLKPA14Clock dari ST-LINK
GNDGNDGNDWajib tersambung
3V33V33V3Opsional; jangan disambung jika board sudah diberi daya dari micro USB
(tidak disambung)BT0BOOT0Normalnya dibiarkan; BOOT0 sudah ditarik ke GND lewat resistor di board

Ilustrasi wiring WeAct ST-Link V2.1 ke DevEBox STM32H7XX_M: jalur SWD ke header J1 dan jalur UART ke PA9/PA10.

Header J1 tidak memiliki pin NRST. Jika butuh mode connect under reset, gunakan tombol RST di board (lihat bagian 5 dan 8).

Tampak belakang board DevEBox STM32H7XX_M: label nama pin I/O tercetak di kedua sisi board, header SWD (J1) di dekat port USB.

Beberapa tips:

  • Selalu ikuti label pin yang tercetak di debugger dan board. Urutan pin antar merk ST-LINK bisa berbeda.
  • Gunakan kabel jumper pendek (di bawah 20 cm). Kabel panjang sering membuat koneksi SWD gagal di frekuensi tinggi.
  • Jika board diberi daya dari micro USB, cukup sambungkan SWDIO, SWCLK, dan GND.

Bonus: sambungan UART untuk printf

Karena WeAct ST-Link V2.1 punya USB-UART, sambungkan juga kabel berikut ke USART1 board DevEBox:

Pin WeAct Mini DebuggerPin board DevEBoxKeterangan
TXDPA10 (RX1)Data dari PC ke STM32
RXDPA9 (TX1)Data dari STM32 ke PC
GNDGNDCukup satu GND jika GND SWD sudah tersambung

TX selalu disilang ke RX. Di PC, modul ini muncul sebagai port COM bernama STMicroelectronics STLink Virtual COM Port. Buka dengan terminal serial (PuTTY, Tera Term, atau terminal bawaan CubeIDE) pada baud rate yang sama dengan setting USART1, misalnya 115200.

3. Tutorial STM32CubeIDE: Membuat Project dan Konfigurasi

Bagian ini saya praktikkan langsung di STM32CubeIDE 1.17.0 dengan board DevEBox STM32H743VIT6 dan WeAct ST-Link V2.1. Semua gambar di bawah diambil dari software-nya.

3.1 Membuat project baru

Buka STM32CubeIDE, lalu pilih File → New → STM32 Project.

STM32CubeIDE langkah 01

Menu File → New → STM32 Project.

Jendela Target Selection akan terbuka. Saat pertama dibuka, CubeIDE mencoba mengunduh database MCU terbaru. Jika muncul pesan gagal download, klik OK saja; database lokal tetap bisa dipakai.

Di tab MCU/MPU Selector, ketik STM32H743VI pada kolom Commercial Part Number, lalu klik baris STM32H743VIT6 (kemasan LQFP100) di daftar MCU. Klik Next.


Memilih STM32H743VIT6 di MCU/MPU Selector.

Isi Project Name, misalnya H7_Blink. Biarkan Targeted Language = C, Binary Type = Executable, dan Project Type = STM32Cube, lalu klik Finish.

STM32CubeIDE langkah 03

Mengisi nama project H7_Blink.

Akan muncul pertanyaan Memory Protection Unit For Cortex-M7. Pilih Yes agar CubeIDE memasang konfigurasi MPU default yang direkomendasikan untuk Cortex-M7. Setelah itu file H7_Blink.ioc terbuka di tab Pinout & Configuration.

3.2 Aktifkan debug Serial Wire (wajib)

Pada STM32H7, pengaturan debug tidak ada di SYS, melainkan di Trace and Debug → DEBUG. Ubah Debug dari Disable menjadi Serial Wire. Pin PA13 (SWDIO) dan PA14 (SWCLK) akan berubah hijau.

STM32CubeIDE langkah 04

Trace and Debug → DEBUG = Serial Wire. PA13 dan PA14 otomatis terpakai untuk SWD.

Langkah ini paling penting. Jika dibiarkan Disable, setelah program pertama di-flash ST-LINK tidak bisa lagi terhubung secara normal.

3.3 RCC: kristal HSE dan power supply

Buka System Core → RCC, lalu set High Speed Clock (HSE) = Crystal/Ceramic Resonator. Pin PH0 dan PH1 akan terpakai untuk kristal 25 MHz di board.

Di bagian bawah, buka Parameter Settings → Power Parameters dan pastikan SupplySource = PWR_LDO_SUPPLY. Ini sudah menjadi nilai default dan sesuai dengan board DevEBox yang memakai regulator AMS1117.

STM32CubeIDE langkah 05

RCC: HSE = Crystal/Ceramic Resonator, SupplySource = PWR_LDO_SUPPLY.

3.4 GPIO untuk LED (PA1)

LED user D2 di board DevEBox tersambung ke PA1 dan bersifat active low. Klik pin PA1 di gambar chip, lalu pilih GPIO_Output.

STM32CubeIDE langkah 06

Klik pin PA1, lalu pilih GPIO_Output.

Klik kanan PA1 → Enter User Label, lalu isi LED. Setelah itu buka System Core → GPIO, klik baris PA1, dan set GPIO output level = High supaya LED mati saat awal.

STM32CubeIDE langkah 07

Konfigurasi PA1: Output Push Pull, output level High, User Label = LED.

3.5 Clock Configuration: 400 MHz

Buka tab Clock Configuration. Input frequency HSE sudah 25 MHz. Lakukan langkah berikut:

  1. Pada PLL Source Mux, pilih HSE.
  2. Pada System Clock Mux, pilih PLLCLK.
  3. Klik kotak To CPU Clocks (MHz), ketik 400, lalu tekan Enter. CubeIDE akan mencari pembagi clock secara otomatis.

Hasilnya: DIVM1 = /2, DIVN1 = ×64, DIVP1 = /2, sehingga SYSCLK = 400 MHz dan HCLK = 200 MHz.

STM32CubeIDE langkah 08

Clock tree: HSE 25 MHz → PLL1 → SYSCLK 400 MHz.

3.6 Generate code

Tekan Ctrl+S untuk menyimpan file .ioc. Saat muncul pertanyaan Do you want generate Code?, pilih Yes. CubeIDE akan membuat kode inisialisasi dan membuka main.c.

4. Program Sederhana: LED Berkedip

Di Core/Src/main.c, cari loop while (1). Tulis kode di antara komentar USER CODE BEGIN WHILE dan USER CODE END WHILE supaya tidak terhapus saat kode di-generate ulang:

  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    HAL_Delay(500);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
STM32CubeIDE langkah 09

Kode blink LED di dalam loop while(1).

LED_GPIO_Port dan LED_Pin dibuat otomatis oleh CubeMX dari user label LED. Simpan file, klik project H7_Blink di Project Explorer, lalu pilih Project → Build Project (atau Ctrl+B). Pastikan Console menampilkan Build Finished. 0 errors, 0 warnings.

STM32CubeIDE langkah 10

Build berhasil: 0 errors, 0 warnings. Ukuran program sekitar 8 KB.

5. Setting Debugger WeAct ST-Link dan Upload Program

5.1 Debug Configuration

Pastikan WeAct ST-Link sudah tercolok ke USB dan kabel SWD sudah tersambung ke header J1 board. Pilih Run → Debug Configurations, lalu klik dua kali STM32 C/C++ Application. CubeIDE membuat konfigurasi baru H7_Blink Debug dengan file Debug/H7_Blink.elf.

STM32CubeIDE langkah 11

Tab Main: project H7_Blink dan file Debug/H7_Blink.elf.

Buka tab Debugger dan atur seperti berikut:

OpsiNilai
Debug probeST-LINK (ST-LINK GDB server)
InterfaceSWD
Frequency (kHz)Auto (turunkan ke 1000 jika koneksi gagal)
Access port0 - Cortex-M7
Reset behaviourSoftware system reset, karena header J1 board tidak punya pin NRST. Pilih Connect under reset hanya jika NRST disambung.
STM32CubeIDE langkah 12

Tab Debugger: ST-LINK GDB server, SWD, Software system reset.

Klik Apply, lalu Debug.

5.2 Verifikasi firmware ST-LINK

Saat pertama kali terhubung, CubeIDE memeriksa firmware WeAct ST-Link. Jika ada versi lebih baru, muncul pertanyaan untuk update. Pilih Yes untuk update (disarankan), atau No untuk lanjut dengan firmware yang ada. Pada percobaan ini saya pilih No dan upload tetap berhasil.

STM32CubeIDE langkah 13

Pertanyaan update firmware ST-LINK.

Jika yang muncul justru pesan "No ST-LINK detected!", berarti debugger belum terdeteksi. Cek kabel USB WeAct ST-Link dan driver ST-LINK di Device Manager, lalu ulangi Debug.

5.3 Upload berhasil dan program berjalan

CubeIDE mem-flash program, menampilkan "Download verified successfully" di Console, lalu pindah ke perspektif Debug. Program berhenti (Suspended : Breakpoint) di awal fungsi main().

STM32CubeIDE langkah 14

Perspektif Debug: "Download verified successfully", program berhenti di main().

Tekan F8 (Resume). Status thread berubah menjadi Running dan LED D2 di board berkedip setiap 500 ms.

Untuk upload berikutnya tanpa masuk mode debug, cukup klik project lalu pilih Run → Run As → STM32 C/C++ Application. Program akan di-flash dan langsung berjalan.

6. Upload Menggunakan STM32CubeProgrammer (SWD)

STM32CubeProgrammer berguna untuk mem-flash file hasil build tanpa membuka IDE, misalnya saat produksi, atau untuk erase total chip yang bermasalah.

  1. Buka STM32CubeProgrammer. Di panel kanan, pilih ST-LINK.
  2. Atur parameter koneksi:
    • Port: SWD
    • Frequency: 4000 kHz (turunkan bila gagal)
    • Mode: Normal, atau Under reset jika NRST disambung
  3. Klik Connect. Jika berhasil, isi memori dan informasi chip (Device ID, ukuran flash) akan tampil.
  4. Buka menu Erasing & Programming (ikon panah ke bawah di sidebar kiri).
  5. Pada File path, pilih file .elf atau .hex dari folder Debug project. Jika memakai file .bin, isi Start address = 0x08000000.
  6. Centang Verify programming dan Run after programming, lalu klik Start Programming.
  7. Setelah muncul "File download complete", klik Disconnect. Program langsung berjalan.

7. Catatan Khusus STM32H750

Karena STM32H743VIT6 sudah memiliki flash internal 2 MB, bagian ini tidak diperlukan untuk board yang saya pakai. Catatan berikut hanya untuk pembaca yang memakai STM32H750.

STM32H750 hanya memiliki 128 KB flash internal, satu sektor saja. Untuk program kecil seperti blink, langkah di atas berlaku sama persis dengan H743.

Masalah muncul ketika program membesar, misalnya saat memakai FreeRTOS, LwIP, atau library grafis. Solusinya adalah menyimpan program di QSPI flash eksternal (W25Q64 8 MB juga tersedia pada board DevEBox versi H750) dan menjalankannya dari alamat 0x90000000 (XIP, execute in place). Skemanya:

  1. Bootloader kecil di flash internal (0x08000000) yang menginisialisasi QSPI dalam mode memory-mapped, lalu melompat ke 0x90000000.
  2. Aplikasi utama dengan linker script yang diubah supaya region FLASH berada di 0x90000000.
  3. External Loader (file .stldr) supaya ST-LINK bisa menulis ke QSPI flash. File ini didaftarkan di STM32CubeProgrammer (menu EL) dan di CubeIDE (Debug Configurations → Debugger → External Loaders).

Pembahasan lengkapnya akan saya tulis di artikel terpisah.

8. Troubleshooting

GejalaKemungkinan penyebabSolusi
"No STM32 target found"Wiring salah atau longgar, GND tidak tersambung, board tidak mendapat dayaCek ulang SWDIO/SWCLK/GND, pastikan board menyala, gunakan kabel pendek
"Target is not responding, retrying..."Frekuensi SWD terlalu tinggi, atau pin SWD dimatikan oleh programTurunkan frekuensi ke 1000 kHz atau lebih kecil; tahan tombol RST saat klik Connect
Bisa flash sekali, setelah itu tidak bisa connect lagiDebug masih "Disable" (Trace and Debug → DEBUG), atau Supply Source (LDO/SMPS) salahPulihkan chip dengan BOOT0 (lihat di bawah), lalu perbaiki .ioc
ST-LINK tidak terdeteksi oleh PCDriver belum terpasangInstall driver STSW-LINK009, coba port USB lain
"ST-LINK firmware is too old"Firmware ST-LINK lamaUpdate lewat Help → ST-LINK Upgrade
Program H750 gagal di-link ("region FLASH overflowed")Ukuran program melebihi 128 KBOptimasi ukuran (-Os) atau pindahkan program ke QSPI

Memulihkan chip yang "terkunci"

Jika chip tidak bisa connect sama sekali, biasanya karena setting Supply Source salah atau pin SWD dimatikan. Pulihkan dengan cara berikut:

  1. Sambungkan pin BT0 ke 3V3 pada header J1 menggunakan kabel jumper.
  2. Matikan lalu nyalakan lagi board. Chip sekarang masuk ke bootloader sistem, sehingga program yang bermasalah tidak dijalankan.
  3. Di STM32CubeProgrammer, connect dengan mode Under reset atau Hot plug.
  4. Klik Full chip erase (ikon penghapus di sidebar kiri).
  5. Lepaskan jumper BT0 (kembali ke GND), lalu matikan dan nyalakan board.
  6. Perbaiki setting di .ioc (DEBUG = Serial Wire, Supply Source = PWR_LDO_SUPPLY), generate ulang kode, lalu flash kembali.

Penutup

Memprogram STM32H7 dengan ST-LINK sebenarnya tidak jauh berbeda dengan seri STM32 lain. Kuncinya ada di tiga hal: aktifkan DEBUG = Serial Wire, pastikan Supply Source sesuai hardware board, dan manfaatkan pin BT0 di header SWD untuk memulihkan chip yang terkunci. Untuk STM32H750, perhatikan batas flash internal 128 KB sejak awal perencanaan program.

Pada artikel berikutnya, kita akan membahas implementasi FreeRTOS pada STM32H7 series.

Semoga bermanfaat. Jika ada pertanyaan, silakan tulis di kolom komentar.

Wassalamu'alaikum warohmatullahi wabarakatuh.