هدف از این آموزش نشان دادن چگونگی استفاده از PlatformIO IDE در VS Code برای ایجاد، اجرا و اشکال زدایی یک پروژه نمونه چشمک زن با فریموورک STM32Cube برای برد STM32 Nucleo-F401RE است.
نیازمندی ها
افزونهPlatformIO برای VS Code دانلود و نصب شده باشد
درایورهای ST-LINK برای ابزار اشکال زدایی نصب شده باشد
برد توسعه Nucleo-F401RE
راه اندازی پروژه
در مرحله اول، ما باید با استفاده از صفحه اصلی PlatformIO یک پروژه جدید ایجاد کنیم (برای باز کردن این صفحه آیکون Home را در نوار ابزار کلیک کنید)
در مرحله بعدی، باید ST Nucleo-F401RE را به عنوان یک برد توسعه، STM32Cube به عنوان یک فریموورک و در نهایت مسیری برای مکان پروژه انتخاب کنیم (یا از حالت پیش فرض استفاده کنید)
پردازش پروژه ممکن است مدتی طول بکشد (PlatformIO تمام بسته های مورد نیاز را دانلود و نصب خواهد کرد) و پس از این مراحل، ما یک پروژه کاملاً پیکربندی شده داریم که آماده تهیه کد با فریموورک STM32Cube است.
افزودن کد به پروژه تولید شده
بریم کد اصلی را به پروژه اضافه کنیم. اولا، ما دو فایل اصلی main.c و main.h را در پوشه src_dir ایجاد می کنیم. بر روی src کلیک راست کنید
کد زیر را به main.h اضافه کنید:
main.h
#ifndef MAIN_H
#define MAIN_H
#include "stm32f4xx_hal.h"
#define LED_PIN GPIO_PIN_5
#define LED_GPIO_PORT GPIOA
#define LED_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#endif // MAIN_H
و کد زیر را به main.c اضافه کنید:
main.c
#include "main.h"
void LED_Init();
int main(void) {
HAL_Init();
LED_Init();
while (1)
{
HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_PIN);
HAL_Delay(1000);
}
}
void LED_Init() {
LED_GPIO_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = LED_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
HAL_GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct);
}
void SysTick_Handler(void) {
HAL_IncTick();
}
بعد از این مرحله ، یک پروژه اولیه چشمک زن ایجاد کردیم که آماده کامپایل و آپلود است.
کامپایل و آپلود
اکنون می توانیم پروژه را بیلد کنیم. برای کامپایل کردن می توانیم از گزینه های زیر استفاده کنیم:
- گزینه Build را در منوی Project Tasks
- دکمه Build روی PlatformIO Toolbar
- با استفاده از دستور View: Command Palette> PlatformIO: Build
- با استفاده از منو Task: Task... > PlatformIO: Build یا از طریق کلیدهای میانبر cmd-alt-b/ctrl-alt-b
اگر همه چیز خوب پیش رفت ، باید نتیجه موفقیت آمیز را در پنجره ترمینال مشاهده کنیم:
برای آپلود فریموورک در برد می توانیم از گزینه های زیر استفاده کنیم:
- گزینه آپلود در منوی Project Tasks
- دکمه آپلود در PlatformIO Toolbar
- با استفاده از دستورView: Command Palette> PlatformIO
- با استفاده از منوی Task: Task... > PlatformIO: Upload یا از طریق کلیدهای میانبر cmd-alt-u/ctrl-alt-u
بعد از آپلود موفقیت آمیز ، چراغ سبز LED2 باید چشمک بزند.
اشکال زدایی
برای شروع اشکال زدایی می توانید از گزینه Start debugging در منوی دسترسی سریع PlatformIO ، یا از منوی بالا یا دکمه کلید F5 استفاده کنید.
لازم است مدتی صبر کنیم تا زمانی که PlatformIO در حال اینیشیال اشکال زدایی است و وقتی اولین خط بعد از تابع main برجسته شد، آماده رفع اشکال می شویم.
می توانیم با استفاده از دکمه های کنترل، کد را پیمایش کنیم، breakpoint ها را تنظیم کنیم، رجیستر های پریفرال ها را مشاهده کنیم، متغیرهایی را به پنجره Watch اضافه کنیم:
نوشتن تست های واحد
خب حالا بریم تست هایی را با استفاده از ویژگی تست واحد PIO بنویسیم که می تواند به ما کمک کند تا کد را مستقیماً روی برد آزمایش کنیم. موتور PIO Unit Testing به طور پیش فرض تنها از سه فریموورک پشتیبانی می کند: Arduino ، ESP-IDF ، Mbed و از آنجا که تصمیم گرفتیم از STM32Cube استفاده کنیم، برای چاپ نتایج آزمایش باید یک test_transport سفارشی را پیاده سازی کنیم و آن شرایط را درplatformio.ini (فایل پیکربندی پروژه) مشخص کنیم:
[env:nucleo_f401re]
platform = ststm32
board = nucleo_f401re
framework = stm32cube
test_transport = custom
همچنین، باید یک پوشه test جدید ایجاد کنیم که تست ها و پیاده سازی test_transport در آن قرار گیرد:
ما از USART2در برد ST Nucleo-F401RE استفاده خواهیم کرد زیرا مستقیماً به رابط اشکال زدایی STLink متصل است و در فریموورک می تواند به عنوان یک پورت کام مجازی قابل مشاهده باشد ، بنابراین ما به مبدل USB-UART اضافی احتیاج نداریم. برای پیاده سازی test_transport سفارشی، باید دو فایل unittest_transport.h و unittest_transport.c ایجاد کنیم و آنها را در پوشه root در پروژه خود در test_dir قرار دهیم. در این فایل ها باید چهار تابع زیر را پیاده سازی کنیم:
void unittest_uart_begin();
void unittest_uart_putchar(char c);
void unittest_uart_flush();
void unittest_uart_end();
اجرای unittest_transport.h:
#ifndef UNITEST_TRANSPORT_H
#define UNITEST_TRANSPORT_H
#ifdef __cplusplus
extern "C" {
#endif
void unittest_uart_begin();
void unittest_uart_putchar(char c);
void unittest_uart_flush();
void unittest_uart_end();
#ifdef __cplusplus
}
#endif
#endif // UNITEST_TRANSPORT_H
اجرای unittest_transport.c:
#include "unittest_transport.h"
#include "stm32f4xx_hal.h"
#define USARTx USART2
#define USARTx_CLK_ENABLE() __HAL_RCC_USART2_CLK_ENABLE()
#define USARTx_CLK_DISABLE() __HAL_RCC_USART2_CLK_DISABLE()
#define USARTx_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define USARTx_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define USARTx_RX_GPIO_CLK_DISABLE() __HAL_RCC_GPIOA_CLK_DISABLE()
#define USARTx_TX_GPIO_CLK_DISABLE() __HAL_RCC_GPIOA_CLK_DISABLE()
#define USARTx_FORCE_RESET() __HAL_RCC_USART2_FORCE_RESET()
#define USARTx_RELEASE_RESET() __HAL_RCC_USART2_RELEASE_RESET()
#define USARTx_TX_PIN GPIO_PIN_2
#define USARTx_TX_GPIO_PORT GPIOA
#define USARTx_TX_AF GPIO_AF7_USART2
#define USARTx_RX_PIN GPIO_PIN_3
#define USARTx_RX_GPIO_PORT GPIOA
#define USARTx_RX_AF GPIO_AF7_USART2
static UART_HandleTypeDef UartHandle;
void unittest_uart_begin()
{
GPIO_InitTypeDef GPIO_InitStruct;
USARTx_TX_GPIO_CLK_ENABLE();
USARTx_RX_GPIO_CLK_ENABLE();
USARTx_CLK_ENABLE();
GPIO_InitStruct.Pin = USARTx_TX_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
GPIO_InitStruct.Alternate = USARTx_TX_AF;
HAL_GPIO_Init(USARTx_TX_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = USARTx_RX_PIN;
GPIO_InitStruct.Alternate = USARTx_RX_AF;
HAL_GPIO_Init(USARTx_RX_GPIO_PORT, &GPIO_InitStruct);
UartHandle.Instance = USARTx;
UartHandle.Init.BaudRate = 115200;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;
if(HAL_UART_Init(&UartHandle) != HAL_OK) {
while(1){}
}
}
void unittest_uart_putchar(char c)
{
HAL_UART_Transmit(&UartHandle, (uint8_t*)(&c), 1, 1000);
}
void unittest_uart_flush(){}
void unittest_uart_end() {
USARTx_CLK_DISABLE();
USARTx_RX_GPIO_CLK_DISABLE();
USARTx_TX_GPIO_CLK_DISABLE();
}
حال باید موارد آزمایش را اضافه کنیم. آزمایشات را می توان به یک پرونده C واحد اضافه کرد که ممکن است شامل چندین آزمایش باشد. اول از همه، ما باید سه تابع پیش فرض را اضافه کنیم: setUp ، tearDown و main
از setUp و tearDown برای اولیه و نهایی کردن شرایط آزمایش استفاده می شود. اجرای این توابع برای آزمونهای در حال اجرا ضروری نیست اما اگر قبل از انجام آزمایش نیاز به تنظیم برخی متغیرها دارید ، از تابع setUp استفاده می کنید و در صورت clean up کردن متغیرها از عملکرد tearDown استفاده می کنید. در مثال ما از این توابع استفاده خواهیم کرد تا بر این اساس LED را اولویت بندی و deinitialize کنیم.
بریم یک فایل جدید test_main.c را به پوشه test اضافه کنیم. تست های زیر برای روتین چشمک زن در این فایل اجرا خواهد شد:
- test_led_builtin_pin_number تضمین می کند که LED_PIN از مقدار صحیح برخوردار باشد
- توابع تست_led_state_high تست های HAL_GPIO_WritePin و HAL_GPIO_ReadPin با مقدار GPIO_PIN_SET
- توابع تست_led_state_low تست های HAL_GPIO_WritePin و HAL_GPIO_ReadPin با مقدار GPIO_PIN_RESET
تأخیر 2 ثانیه ای لازم است زیرا برد از تنظیم مجدد نرم افزار پشتیبانی نمی کند از طریق Serial.DTR/RTS
#include "../src/main.h"
#include <unity.h>
void setUp(void) {
LED_GPIO_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = LED_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
HAL_GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct);
}
void tearDown(void) {
HAL_GPIO_DeInit(LED_GPIO_PORT, LED_PIN);
}
void test_led_builtin_pin_number(void) {
TEST_ASSERT_EQUAL(GPIO_PIN_5, LED_PIN);
}
void test_led_state_high(void) {
HAL_GPIO_WritePin(LED_GPIO_PORT, LED_PIN, GPIO_PIN_SET);
TEST_ASSERT_EQUAL(GPIO_PIN_SET, HAL_GPIO_ReadPin(LED_GPIO_PORT, LED_PIN));
}
void test_led_state_low(void) {
HAL_GPIO_WritePin(LED_GPIO_PORT, LED_PIN, GPIO_PIN_RESET);
TEST_ASSERT_EQUAL(GPIO_PIN_RESET, HAL_GPIO_ReadPin(LED_GPIO_PORT, LED_PIN));
}
int main() {
HAL_Init(); // initialize the HAL library
HAL_Delay(2000); // service delay
UNITY_BEGIN();
RUN_TEST(test_led_builtin_pin_number);
for (unsigned int i = 0; i < 5; i++)
{
RUN_TEST(test_led_state_high);
HAL_Delay(500);
RUN_TEST(test_led_state_low);
HAL_Delay(500);
}
UNITY_END(); // stop unit testing
while(1){}
}
void SysTick_Handler(void) {
HAL_IncTick();
}
حالا آماده آپلود تست ها در برد هستیم. برای این کار می توان از
گزینه Test از منوی Project Tasks
Tasks: Run Task... > PlatformIO
گزینه Test از منوی بالا یا دکمه Test در PlatformIO Toolbar استفاده کرد
پس از پردازش باید گزارش مفصلی در مورد نتایج آزمایش مشاهده کنیم:
همانطور که از گزارش می بینیم، تمام تست های با موفقیت انجام شد!
اکنون یک الگوی مناسب و معقول داریم که می توانیم برای پروژه های پیچیده تر بعدی خود بهبود ببخشیم.
موفق باشید.