隨著 AI 工具的普及,特別是 cline 和 curie,讓大家看到了「未來的開發方式」
越來越多的開發者開始使用 VSCode 作為主要的編輯器。本文將介紹如何安裝 VSCode 並配置相關的插件,以便開發者能更高效地進行嵌入式開發。
1. 安裝 VSCode
下載並安裝 VSCode,下載地址:https://code.visualstudio.com/
2. 安裝外掛
VSCode 是一個開源的編輯器,功能強大,但功能並不全面,需要安裝插件來擴展功能。
2.1 安裝 C/C++ 擴充功能
C/C++ 插件是 VSCode 官方提供的插件,可以高亮顯示 C/C++ 程式碼,
提供語法檢查、程式碼格式化、程式碼跳轉等功能。這些就不用多說了,網路上的資料非常多。
2.2 安裝嵌入式插件
嵌入式插件是專門為嵌入式開發者設計的插件,提供了各種工具,也是我介紹的重點,例如:
1、EIDE 開發環境插件
一位國內開發者開發的插件,為我們的嵌入式開發帶來了全新的開發體驗
大家有問題也可以去官方論壇提問,或者在 GitHub 上提 issue。
2、Cortex-Debug 調試插件
開源的除錯工具,支援 JTAG、SWD、Serial 等多種除錯方式,可以方便地進行除錯。
3. 配置 VSCode
VSCode 的設定非常簡單,只需要依需求安裝擴充套件,並設定相關參數即可。
EIDE 插件設定
編譯
EIDE 本質上是將其他開發環境的配置移植到 VSCode,使得 VSCode 可以直接匯入其他開發環境的專案。例如 KEIL、eclipse 的專案,仍然需要下載 KEIL 或 DS IDE 來為我們提供官方編譯器。

上次的文章介紹了 DS IDE 的安裝和使用方法,這裡就不再重複說明了。DS IDE 是基於 Eclipse 進行二次開發的。在導入 EIDE 時,需要選擇“導入專案”的 Eclipse 選項:

然後選擇我們要匯入的 Eclipse 專案的 .cproject:例如
之後我們就有了一個工程區了

但是畢竟我們是根據插件進行開發,我們需要再進行一些配置才能正常編譯:
首先是編譯器路徑,我們需要配置編譯器的路徑,這裡我選擇的是 DS IDE 的編譯器:右鍵點擊「建置配置」
可以參考我的編譯器路徑自己找一下

然後我們需要配置編譯器包含專案路徑,大部分不需要我們配置,可以直接編譯專案來看看有哪些標頭檔案我們沒有包含進來:
例如 RTD 的庫檔案路徑,大家可以參考我的來找一下自己的路徑:
最後我們的編譯器還需要連接一個 LD 檔案,可能需要你進行修改
編譯器選項的連結器
選擇自己需要的 ld 檔案路徑

如果新增了新的資料夾或其他資源,需要將它們添加到專案資源中。
這個就是我自己添加的程式碼
之後就可以正常編譯了,編譯完成後我們就可以看到編譯的結果:
和 S32DS 一樣,設定更加方便
燒錄
我的燒錄是使用 JLink,所以我們需要配置一下 JLink 的路徑:
燒錄比較麻煩,完全是使用 JFlash 進行燒錄,所以每次編譯完,我一般使用 debug 模式進行調試來燒錄。因此,Cortex-Debug 調試插件就非常好用,我們只需要配置一下燒錄的設定就可以了:
在 .VSCode 資料夾中建立 launch.json 檔案,內容如下,大家可以參考
// 使用 IntelliSense 來了解可能的屬性。
// 將滑鼠懸停在現有屬性上可查看描述。
// 更多資訊請參訪:https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "cortex-debug",
"request": "launch",
"name": "Debug J-Link",
"cwd": "${workspaceRoot}",
"executable": "F:\S32_workspace\LED_test\build\Debug_FLASH\LED_test.elf",
"serverpath": "D:\JLink_V812\JLinkGDBServerCL.exe",
"servertype": "jlink",
"device": "S32K312",
"interface": "jtag",
"serialNumber": "" // 如果您有多個 J-Link 探測器,請在此處添加序列號。
}
]
}
按下除錯按鍵,程式碼會自動燒錄並進入除錯模式
試用
有了基本的嵌入式開發工具,我們就可以感受到 VSCode 的強大之處了,比起 KEIL 和 eclipse 現代化許多。

有了按鍵的引腳定義,我們可以先在 DS IDE 中自動生成程式碼。之後完全可以用 VSCode 進行開發了。
強大的跳轉以及插件的支援讓我們可以很快找到庫函式中我們需要的函式,然後進行修改。
/**
函數名稱:Siul2_Dio_Ip_ReadPins
描述:此函數返回目前的輸入值,僅配置為輸入的引腳才會有意義的值。
@implements Siul2_Dio_Ip_ReadPins_Activity
*/
Siul2_Dio_Ip_PinsChannelType Siul2_Dio_Ip_ReadPins(const Siul2_Dio_Ip_GpioType * const base)
{
SIUL2_DIO_IP_DEV_ASSERT(NULL_PTR != base);
Siul2_Dio_Ip_PinsChannelType returnValue = 0U;
returnValue = Siul2_Dio_Ip_ReadPinsRunTime(base);
return returnValue;
}
找到讀取電平的函數,調試一下看看具體怎麼使用。
vale = Siul2_Dio_Ip_ReadPins(SW3_PORT); vale = Siul2_Dio_Ip_ReadPins(SW4_PORT);
添加到監視裡,和 Keil 的調試方法比較類似。可以看到變數的值,可以設定斷點,可以單步調試,可以查看堆疊等等。
按下 SW3 按鈕,調試時可以查看按下後數值的變化
按下 SW4 按鈕,調試時可以查看按下後數值的變化

有了調試的值,我們就可以完成按鍵的全部功能開發了。
按鍵的電平很好判斷,分別是 vale 的 bit8 和 bit9,所以我們可以根據按鍵的電平來完成我們的按鍵結構體的構建。
按鍵通常需要辨識單擊、雙擊、長按等邏輯,我移植了自己比較喜歡的庫「multi_button」,可以實現多種按鍵功能。
multi_button.h
/*
版權所有 (c) 2016 Zibin Zheng <znbin@qq.com>
保留所有權利
*/
#ifndef _MULTI_BUTTON_H_
#define _MULTI_BUTTON_H_
#include "inc.h"
#include "Siul2_Port_Ip.h"
#include "Siul2_Dio_Ip.h"
#include <stdint.h>
#include <string.h>
// 根據需求修改常數。
#define TICKS_INTERVAL 10 // 毫秒
#define DEBOUNCE_TICKS 3 // 最大值 7 (0 ~ 7)
#define SHORT_TICKS (300 / TICKS_INTERVAL)
#define LONG_TICKS (1000 / TICKS_INTERVAL)
// 按鈕處理列表的頭部。
static struct Button *head_handle = NULL;
static struct Button btn_SW3;
static struct Button btn_SW4;
typedef void (*BtnCallback)(void *);
typedef enum {
PRESS_DOWN = 0, // 按鈕按下事件
PRESS_UP, // 按鈕釋放事件
PRESS_REPEAT, // 按鈕重複按下事件
SINGLE_CLICK, // 單擊事件
DOUBLE_CLICK, // 雙擊事件
LONG_PRESS_START, // 長按開始事件
LONG_PRESS_HOLD, // 長按保持事件
number_of_event, // 事件數量
NONE_PRESS // 無按下狀態
} PressEvent;
typedef struct Button Button; // 先聲明 Button 結構體
typedef struct Button {
uint16_t ticks; // 記錄按鍵觸發的時鐘周期數
uint8_t repeat : 4; // 記錄按鍵重複觸發的次數
uint8_t event : 4; // 按鍵事件類型
uint8_t state : 3; // 按鍵當前狀態
uint8_t debounce_cnt : 3; // 消抖計數器
uint8_t active_level : 1; // 按鍵有效電平
uint8_t button_level : 1; // 當前按鍵物理電平
const Siul2_Dio_Ip_GpioType *base; // 按鍵引腳基地址
uint8_t id; // 按鍵 ID 號
uint8_t (*hal_button_Level)(Button *); // 獲取按鍵物理電平
BtnCallback cb[number_of_event]; // 按鍵事件回調函數數組
struct Button *next; // 指向下一個按鍵結構體的指針
} Button;
#ifdef __cplusplus
extern "C" {
#endif
void button_init(struct Button *handle, uint8_t active_level,
const Siul2_Dio_Ip_GpioType *button_id, uint8_t id);
void button_attach(struct Button *handle, PressEvent event, BtnCallback cb);
PressEvent get_button_event(struct Button *handle);
int button_start(struct Button *handle);
void button_stop(struct Button *handle);
void button_ticks(void);
#ifdef __cplusplus
}
#endif
#endif
multi_button.C
/*
版權所有 (c) 2016 Zibin Zheng <znbin@qq.com>
保留所有權利
*/
#include "multi_button.h"
#define EVENT_CB(ev) \
if (handle->cb[ev]) \
handle->cb[ev]((void *) handle)
// 定義了一個宏 EVENT_CB,用於檢查並調用指定事件的回調函數,
// 如果該回調函數已設置,則傳遞 handle 作為參數。
#define PRESS_REPEAT_MAX_NUM 15 // 按鍵重複次數最大值
static void button_handler(struct Button *handle);
uint8_t Button_read(Button *this)
{
Siul2_Dio_Ip_PinsChannelType val = Siul2_Dio_Ip_ReadPins(this->base);
val = (val >> 8) & (this->id);
if (val == this->id)
return 1;
return 0;
}
/**
@brief 初始化按鍵結構體
@param handle: 按鍵處理器
@param pin_level: 讀取連接按鍵的 HAL GPIO
@param active_level: 按下的 GPIO
@param button_id: 按鍵
@retval 無
*/
void button_init(struct Button *handle, uint8_t active_level,
const Siul2_Dio_Ip_GpioType *button_id, uint8_t id)
{
memset(handle, 0, sizeof(struct Button));
handle->event = (uint8_t) NONE_PRESS;
handle->button_level = !active_level;
handle->active_level = active_level;
handle->base = button_id;
handle->id = id;
handle->hal_button_Level = Button_read;
}
/**
@brief 附加按鍵事件回調
@param handle: 按鍵處理器
@param event: 觸發事件
@param cb: 回調函數
@retval 無
*/
void button_attach(struct Button *handle, PressEvent event, BtnCallback cb)
{
handle->cb[event] = cb;
}
/**
@brief 查詢按鍵事件
@param handle: 按鍵處理器
@retval 按鍵事件
*/
PressEvent get_button_event(struct Button *handle)
{
return (PressEvent) (handle->event);
}
/**
@brief 按鍵驅動核心函數,驅動狀態
@param handle: 按鍵處理器
@retval 無
*/
static void button_handler(struct Button *handle)
{
uint8_t read_gpio_level = handle->hal_button_Level(handle);
// 時鐘計數器工作中...
if ((handle->state) > 0)
handle->ticks++;
/* 按鍵消抖處理 */
if (read_gpio_level != handle->button_level) { // 與之前的電平不相等
// 連續讀取 3 次相同的新電平變化
if (++(handle->debounce_cnt) >= DEBOUNCE_TICKS) {
handle->button_level = read_gpio_level;
handle->debounce_cnt = 0;
}
} else { // 電平未變化,計數器重置。
handle->debounce_cnt = 0;
}
/* 狀態機 */
switch (handle->state) {
case 0:
if (handle->button_level == handle->active_level) { // 開始按下
handle->event = (uint8_t) PRESS_DOWN;
EVENT_CB(PRESS_DOWN);
handle->ticks = 0;
handle->repeat = 1;
handle->state = 1;
} else {
handle->event = (uint8_t) NONE_PRESS;
}
break;
case 1:
if (handle->button_level != handle->active_level) { // 釋放按鍵
handle->event = (uint8_t) PRESS_UP;
EVENT_CB(PRESS_UP);
handle->ticks = 0;
handle->state = 2;
} else if (handle->ticks > LONG_TICKS) {
handle->event = (uint8_t) LONG_PRESS_START;
EVENT_CB(LONG_PRESS_START);
handle->state = 5;
}
break;
case 2:
if (handle->button_level == handle->active_level) { // 再次按下
handle->event = (uint8_t) PRESS_DOWN;
EVENT_CB(PRESS_DOWN);
if (handle->repeat != PRESS_REPEAT_MAX_NUM) {
handle->repeat++;
}
EVENT_CB(PRESS_REPEAT); // 重複觸發
handle->ticks = 0;
handle->state = 3;
} else if (handle->ticks > SHORT_TICKS) { // 釋放超時
if (handle->repeat == 1) {
handle->event = (uint8_t) SINGLE_CLICK;
EVENT_CB(SINGLE_CLICK);
} else if (handle->repeat == 2) {
handle->event = (uint8_t) DOUBLE_CLICK;
EVENT_CB(DOUBLE_CLICK); // 重複觸發
}
handle->state = 0;
}
break;
case 3:
if (handle->button_level != handle->active_level) { // 釋放按鍵
handle->event = (uint8_t) PRESS_UP;
EVENT_CB(PRESS_UP);
if (handle->ticks < SHORT_TICKS) {
handle->ticks = 0;
handle->state = 2; // 重複按下
} else {
handle->state = 0;
}
} else if (handle->ticks > SHORT_TICKS) { // SHORT_TICKS < 按下保持時間 < LONG_TICKS
handle->state = 1;
}
break;
case 5:
if (handle->button_level == handle->active_level) {
// 持續保持觸發
handle->event = (uint8_t) LONG_PRESS_HOLD;
EVENT_CB(LONG_PRESS_HOLD);
} else { // 釋放
handle->event = (uint8_t) PRESS_UP;
EVENT_CB(PRESS_UP);
handle->state = 0; // 重置
}
break;
default:
handle->state = 0; // 重置
break;
}
}
/**
@brief 開始按鍵工作,將處理器加入工作
@param handle: 目標處理器
@retval 0: 成功,-1: 已存在。
*/
int button_start(struct Button *handle)
{
struct Button *target = head_handle;
while (target) {
if (target == handle)
return -1; // 已存在。
target = target->next;
}
handle->next = head_handle;
head_handle = handle;
return 0;
}
/**
@brief 停止按鍵工作,移除處理器
@param handle: 目標處理器
@retval 無
*/
void button_stop(struct Button *handle)
{
struct Button **curr;
for (curr = &head_handle; *curr;) {
struct Button *entry = *curr;
if (entry == handle) {
*curr = entry->next;
return; // glacier 添加於 2021-8-18
} else {
curr = &entry->next;
}
}
}
/**
@brief 背景時鐘,定時重複調用間隔
@param 無
@retval 無
*/
void button_ticks(void)
{
struct Button *target;
for (target = head_handle; target; target = target->next) {
button_handler(target);
}
}
main.C
int main(void)
{
Siul2_Port_Ip_Init(NUM_OF_CONFIGURED_PINS_PortContainer_0_BOARD_InitPeripherals, g_pin_mux_InitConfigArr_PortContainer_0_BOARD_InitPeripherals);
button_init(&btn_SW3, 1, SW3_PORT, 1);
button_init(&btn_SW4, 1, SW4_PORT, 2);
button_start(&btn_SW3);
button_attach(&btn_SW3, PRESS_DOWN, app1); // 單擊
button_start(&btn_SW4);
button_attach(&btn_SW4, DOUBLE_CLICK, app2); // 雙擊
while (1) {
Delay_ms(5); // 延遲
button_ticks();
if (val == 1) {
Siul2_Dio_Ip_TogglePins(red_LED_PORT, (1 << red_LED_PIN));
val = 0;
} else if (val == 2) {
Siul2_Dio_Ip_TogglePins(red_LED_PORT, (1 << red_LED_PIN));
val = 0;
}
}
return exit_code;
}編譯時需要添加頭文件路徑,請在 EIDE 的設定中添加我們的頭文件路徑

快捷鍵 F7 編譯
這樣單擊、雙擊的功能就實現了,可以根據自己的需求進行修改。接下來就可以讓按鍵的功能綁定我們的其他功能了,先寫一個 ADC 的讀取。
在 DS IDE 中的外設設定裡添加 ADC 模組和 platform ADC 不用多說,platform 是為了啟用中斷。

看一下 ADC 的配置
adcconfigset
中斷

通道


讀取

2. Adc一般
電位計.h
#ifndef POTENTIOMETER_H_ #define POTENTIOMETER_H_
#include "Inc.h" #include "IntCtrl_Ip.h" #include "Adc_Sar_Ip.h" #include "Adc.h" #include "Platform.h"
#define NUM_RESULTS (1u) // 緩衝區大小
#define RESULT_BUFF_VAL (0xaaaa) // 初始化
#define ADC_RESULT_BUFF_VAL (0xbbbb) // 初始化
extern ISR(Adc_Sar_0_Isr);
void Potentiometer_Init(void);
void ADC_get();
#endif /* POTENTIOMETER_H_ */
電位計.c
#include "Potentiometer.h"
Adc_ValueGroupType ResultBuffer[NUM_RESULTS] = {RESULT_BUFF_VAL};
volatile uint8 VarNotification_0 = 0u;
Std_ReturnType StdReturn = E_NOT_OK;
volatile boolean bStatus = TRUE;
void ADC0_Notification(void) {
VarNotification_0++;
} // 中斷函數
void Potentiometer_Init(void)
{
#if (ADC_CALIBRATION == STD_ON)
Adc_CalibrationStatusType CalibStatus;
#endif
// 註冊中斷函數
Platform_InstallIrqHandler(ADC0_IRQn, Adc_Sar_0_Isr, NULL_PTR);
Platform_SetIrq(ADC0_IRQn, TRUE);
#if (ADC_PRECOMPILE_SUPPORT == STD_ON)
Adc_Init(NULL_PTR);
#else
Adc_Init(&Adc_Config);
#endif /* (ADC_PRECOMPILE_SUPPORT == STD_ON) */
#if (ADC_CALIBRATION == STD_ON)
/* 校準第一個用於中斷的硬體單元。 */
/* 多次調用校準函數以減少板源的不穩定性 */
for (uint8 Index = 0; Index <= 5; Index++) {
Adc_Calibrate(AdcHwUnit_0, &CalibStatus);
if (CalibStatus.AdcUnitSelfTestStatus == E_OK) {
break;
}
}
/* 如果多次嘗試後校準未成功則失敗。 */
if (Index > 5) {
bStatus = FALSE;
}
#endif /* (ADC_CALIBRATION == STD_ON) */
if (bStatus) {
/* 部分 1:使用軟體觸發的一次性轉換模式的範例,數據轉換由中斷更新。 */
/************************************************************************************************
/* ResultBuffer 在 Adc_Sar_0_Isr 處理程序中更新新數據 */
Adc_SetupResultBuffer(AdcGroup_0, ResultBuffer);
Adc_EnableGroupNotification(AdcGroup_0);
for (uint8 Index = 0u; Index < 10u; Index++) {
VarNotification_0 = 0;
Adc_StartGroupConversion(AdcGroup_0);
/* 檢查通知是否被調用 */
while (VarNotification_0 == 0u) {
}
StdReturn = Adc_ReadGroup(AdcGroup_0, ResultBuffer);
if (E_OK != StdReturn) {
bStatus = FALSE;
break;
}
}
}
}
void ADC_get(void)
{
Adc_SetupResultBuffer(AdcGroup_0, ResultBuffer);
Adc_EnableGroupNotification(AdcGroup_0);
Adc_StartGroupConversion(AdcGroup_0);
Adc_ReadGroup(AdcGroup_0, ResultBuffer);
}
綁定雙擊按鍵並讀取 ADC 取樣值
雙擊讀取到 ADC 取樣的範圍 0X00(右)~0X3FF9(左)
利用 DS IDE 生成底層程式碼後,功能撰寫和除錯程式碼的過程全部是使用 vscode 來進行的,非常方便,而且有很多插件可以提升效率,比如 C/C++ 的語法高亮、程式碼格式化、程式碼補全、編譯、除錯等等。
更不用說 AI 的智能提示、程式碼範本和提問、git 的版本管理、編譯器的錯誤提示等等,這些都可以讓你在開發過程中少走很多彎路。
文章來自「S32K312 開發板評測活動」測評者:劉瑞陽
歡迎在文章下方留言評論,我們會及時回覆您的問題。
如有更多需求,歡迎聯繫大聯大世平集團 ATU 部門:atu.sh@wpi-group.com 作者:WPIg
更多資訊,請掃碼關注我們!
評論