[STM32] 在 Windows 下使用 VSCode 架設 STM32 開發環境

1. 所需安裝的軟體 GNU Arm Embedded Toolchain : 下載壓縮檔,解壓到你想擺放的目錄。例如:c:\stm32\gcc-arm\ GNU MCU Eclipse Windows Build Tools : 這是 make 和相關的軟體,也可以使用 cygwin 或 mingw 內的工具,但我發現這包最簡潔。下載壓縮檔後將其解開,把裡面 bin 目錄下的執行檔複製到你想擺放的目錄。例如:c:\stm32\build tools\ OpenOCD : 這是燒 code 和 debug 的工具。下載壓縮檔,解壓到你想擺放的目錄。例如:c:\stm32\openocd\ VSCode : 這次的主角,編輯器和整合的操作介面。下載安裝程式執行,依指示安裝。安裝過程中有選項可將開啟檔案和開啟目錄加入滑鼠右鍵選單,建議打勾,以後開啟專案比較方便。 ST-Link Utility : STM32 原廠開發版的驅動程式和燒錄工具 STM32CubeMX (非必備): ST 提供的程式碼生成工具,利用它產生 makfile 的專案比較快 2. VSCode 的設定 由於 VSCode 的 Build 和 Debug 設定是存放在專案目錄下 .vscode/ 裡的 tasks.json 和 launch.json ,以下的操作必需是以 Folder 的方式開啟專案目錄才能用。你可以在檔案總管中,在專案的目錄上按右鍵,選"Open with Code"(安裝時必需有打開滑鼠右鍵選單功能)。或是在 VSCode 內用 "Open Folder" 打專案的目錄。 2.1 安裝 Extensions 第一次使用 VSCode 先從左側打開 Extensions 的側欄,安裝下列 Extensions。 C/C++ (Microsoft) Cortex-Debug 打開 settings,設定 cortex-debug 需要的路徑 "cortex-debug.openocdPath": "c:/stm32/openocd/bin/openocd.exe", "cortex-debug.armToolchainPat

[STM32] 3. USART 的發送與接收

UART 是一般嵌入式系統最常用的 debug 介面,通常 PC 端必需要有接收的硬體。不過 neculeo 的電路版已經包含了一個 virtual com port 連接到 USART2。所以只要設定好 USART2 就能在 PC 上透過終端機程式(ex: putty )與 neculeo 連線了。

STM32 週邊的初始化包含下面的步驟
  1. 設定腳位:由於腳位的數量有限,所以有些週邊會共用接腳。而且為了使用上的彈性,週邊也可能有一組以上的腳位可選擇。所以使用前要將腳位設定為正確的模式。
  2. 設定週邊:設定週邊的硬體參數
  3. 設定中斷:如果週邊在應用上會使用到中斷,需要設定 NVIC
另外,STM32 的週邊預設 clock 是關閉的,所以使用任何的週邊都需先開啟該週邊的 clock ,包含 GPIO 也是要先開啟 clock 。

3.1 設定 GPIO

USART2 使用的腳位是 GPIOA2 和 GPIOA3,先將這兩隻腳位設定給 USART 使用。
  1. 呼叫 RCC_AHB1PeriphClockCmd() 打開 GPIOA 的 clock
  2. 宣告一個 GPIO_InitTypeDef 的 struct 準備給 GPIO_Init() 使用
  3. 呼叫 GPIO_StructInit() 將 GPIO_InitTypeDef 的 struct 進行初始化。當然你也可以手動對 struct 內的 member 設定,但如果未來更新了 stm32 提供的 library , GPIO_InitTypeDef 的 member 可能會改變,那這段程式碼可能就會出問題。使用第三方提供的 library 時,最好依它提供的方式使用
  4. 修改 GPIO_InitTypeDef struct 的內容
  5. 呼叫 GPIO_Init() 設定 GPIO ,目前只是將 GPIO_Mode 設定為週邊使用,但還沒指定給哪個週邊
  6. 呼叫 GPIO_PinAFConfig() 將 GPIOA2 和 GPIOA3 指定為 USART2 使用
詳細的程式碼如下:
    // Enable clock of GPIOA
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

    // Configure pins PA2 and PA3 for Alternate function Mode
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_StructInit(&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // Configure pins PA2 and PA3 for USART2
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);

3.2 設定 USART

USART 的設定使用預設值即可,只要修改 BaudRate 為需要的速度,這邊是設為 115200。設定的流程與 GPIO 類似。USART 的 API 放在 stm32f4xx_usart.c 中,記得要修改 makefile 將它加入編譯。
  1. 呼叫 RCC_AHB1PeriphClockCmd() 打開 USART2 的 clock
  2. 宣告一個 USART_InitTypeDef 的 struct 準備給 USART_Init() 使用
  3. 呼叫 USART_StructInit() 將 USART_InitTypeDef 的 struct 進行初始化
  4. 修改 USART_InitTypeDef struct 的內容
  5. 呼叫 USART_Init() 設定 USART2
  6. 呼叫 USART_Cmd 讓 USART2 開始工作
詳細的程式碼如下:
    // Enable clock of USART2
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

    // Configure USART2
    USART_InitTypeDef USART_InitStructure;
    USART_StructInit(&USART_InitStructure);
    USART_InitStructure.USART_BaudRate = 115200;
    USART_Init(USART2, &USART_InitStructure);

    // Enable USART2
    USART_Cmd(USART2, ENABLE);
USART 設定完成後,即可透過 USART_SendData() 輸出字元。每送出一個字元必須等待硬體完成傳送才能送出下一個,所以要使用 USART_GetFlagStatus() 等待 USART_FLAG_TXE 這個 flag。
    USART_SendData(USART2, Data);
    while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET );

3.3 將 printf 導向 UART

USART_SendData 只能送出單一字元,如果想要輸出變數還是 printf() 來得方便。想要使用 printf() 就必需實現它所使用到的系統呼叫。新增一個檔案 syscall.c ,將下面的程式碼貼上,加入 makefile 一起編譯就可以使用 printf() 了。
#include <sys/stat.h>
#include <errno.h>
#undef errno
extern int errno;
register char *stack_ptr asm ("sp");
/**
 * @brief Close a file
 * @param file
 * @return
 */
int _close(int file)
{
    return -1;
}

/**
 * @brief Status of an open files
 * @param file
 * @param st
 * @return
 */
int _fstat(int file, struct stat *st)
{
    st->st_mode = S_IFCHR;
    return 0;
}

/**
 * @brief Query whether output stream is a terminal
 * @param file
 * @return
 */
int _isatty(int file)
{
    return 1;
}

/**
 * @brief Set position in a file
 * @param file
 * @param ptr
 * @param dir
 * @return
 */
int _lseek(int file, int ptr, int dir)
{
    return 0;
}

/**
 * @brief Read from a file
 * @param file
 * @param ptr
 * @param len
 * @return
 */
int _read(int file, char *ptr, int len)
{
    return 0;
}

/**
 * @brief Write to a file
 * @param file
 * @param ptr
 * @param len
 * @return
 */
int _write(int file, char *ptr, int len)
{
    int todo;

    for (todo = 0; todo < len; todo++)
    {
        USART_SendData(USART2, *ptr);
        while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
        ptr++;
    }
    return len;
}

/**
 * @brief Increase program data space
 * @param incr
 * @return
 */
caddr_t _sbrk(int incr)
{
    extern char _end; /* Defined by the linker */
    static char *heap_end;
    char *prev_heap_end;

    if (heap_end == 0)
    {
        heap_end = &_end;
    }
    prev_heap_end = heap_end;
    if (heap_end + incr > stack_ptr)
    {
        _write(1, "Heap and stack collision\n", 25);
        while(1);
    }

    heap_end += incr;
    return (caddr_t) prev_heap_end;
}

3.4 設定 USART 接收中斷

在 USART_Cmd(USART2, ENABLE ) 之前加入開啟 USART RX 中斷的遮照,並設定 NVIC。
    // Configure USART2 interrupt
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 10;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
    NVIC_EnableIRQ(USART2_IRQn);
新增 USART interrupt handler 處理中斷
void USART2_IRQHandler(void)
{
    while (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
    {
        char data = USART_ReceiveData(USART2);
        USART_SendData(USART2, data);
        while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    }
}
上面的程式碼只是將接收到的字元再傳送出去, 如果要對接收的資料進一步的分析,需再另外撰寫程式處理。

留言

這個網誌中的熱門文章

[STM32] 在 Windows 下使用 VSCode 架設 STM32 開發環境

[STM32] 4. 移植 FreeRTOS