顯示包含「MCU」標籤的文章。顯示所有文章
顯示包含「MCU」標籤的文章。顯示所有文章

2026年4月14日星期二

NK-N9H31A2 開發板:GPIO input

在 NK-N9H31A2 開發板:GPIO output 中,已經可以控制 LED 亮滅,接下來偵測按鈕並控制 LED 亮滅,關於 GPIO input 的知識可以參考韌體工程師需要懂一點硬體:GPIO 輸入輸出模式與實務應用整理

實驗目的

使用提供的函式偵測板子上的按鈕,按下後 LED 亮,反之則滅掉

使用範例

在官方的 SampleCode 中,找到 GPIO,匯入方式就不再贅述,可以參考上一篇

電路說明
        由於下載下來的範例程式中,對應的腳位不一樣,所以要依照板子上的電路圖稍作調整將程式碼中的按鍵腳位設定改為 PI9 及 PH1
        由於按鍵電路使用外部 pull-up 電阻,未按下時 GPIO 讀到為 1,按下時接地,GPIO 讀到為 0,因此按鍵為 Active Low 設計。
        按鍵狀態最終會反映在 GPIO 的輸入數值中,透過函式讀取即可判斷目前按鍵狀態。
       

程式碼

        以下的程式碼可以直接複製貼上,此篇文章中主要學會使用函式偵測按鍵是否按下的數值,並控制對應的 LED 亮滅,所以將原本官方的程式進行修改,僅保留偵測按鍵及控制 LED 的部分
int main(void)
{
    int32_t i32Err;

    sysDisableCache();
    sysFlushCache(I_D_CACHE);
    sysEnableCache(CACHE_WRITE_BACK);
    sysInitializeUART();

    sysprintf("+-------------------------------------------------+\n");
    sysprintf("|                 GPIO Sample Code                |\n");
    sysprintf("+-------------------------------------------------+\n\n");

    /* Configure Port F10 to output mode and pull-up, LED_1 */
	GPIO_OpenBit(GPIOF, BIT10, DIR_OUTPUT, PULL_UP);
    /* Configure Port I10 to output mode and pull-up, LED_2 */
	GPIO_OpenBit(GPIOI, BIT10, DIR_OUTPUT, PULL_UP);
    
    /* Configure Port I10 to input mode and pull-up, KEY_2 */
	GPIO_OpenBit(GPIOH, BIT1, DIR_INPUT, PULL_UP);
    /* Configure Port I9 to input mode and pull-up, KEY_1 */
	GPIO_OpenBit(GPIOI, BIT9, DIR_INPUT, PULL_UP);

    while (1)
    {
        /* use KEY_1 control LED_1 */
        if (GPIO_ReadBit(GPIOI, BIT9) == 0) /* KEY_1 is push down */
        {
        	GPIO_SetBit(GPIOF, BIT10); /* LED_1 OFF */
        }
        else
        {
            GPIO_ClrBit(GPIOF, BIT10); /* LED_1 ON */
        }
        
        /* use KEY_2 control LED_2 */
        if (GPIO_ReadBit(GPIOH, BIT1) == 0) /* KEY_2 is push down */
        {
        	GPIO_SetBit(GPIOI, BIT10); /* LED_2 OFF */
        }
        else
        {
        	GPIO_ClrBit(GPIOI, BIT10); /* LED_2 ON */
        }
    }
    
//    GPIO_CloseBit(GPIOF, BIT10);
//    GPIO_CloseBit(GPIOI, BIT10);
//    GPIO_CloseBit(GPIOH, BIT1);
//    GPIO_CloseBit(GPIOI, BIT9);

    while(1);
}

函式說明

這裡只針對上面程式使用到的 GPIO 函式說明,更多說明可以參考 gpio.h gpio.c
  • GPIO_OpenBit 
    • 設定單一 GPIO 腳位的模式(port, bit, direction, pull)
  • GPIO_SetBit 
    • 將指定 port 的 bit 驅動為高電位(1)
  • GPIO_ClrBit 
    • 將指定 port 的 bit 驅動為低電位(0)
  • GPIO_CloseBit 
    • 將單一 bit 恢復為預設/關閉狀態
  • GPIO_ReadBit  
    • 讀取偵測到的 GOIO 狀態,高電位(1)低電位(0)

實際效果



程式參考

        Nuvoton 官方 github https://github.com/OpenNuvoton/N9H31_NonOS

延伸閱讀

         

2026年4月10日星期五

韌體工程師需要懂一點硬體:GPIO 輸入輸出模式與實務應用整理

此篇說明 GPIO 在輸入時的上拉/下拉電阻配置、輸出模式的種類,以及實際應用案例,幫助建立基本且實用的設計觀念。

一、GPIO 基本分類

GPIO 從功能上可分為兩大類:
  1. 輸出(Output)
  2. 輸入(Input)
雖然在部分晶片中,GPIO 設定看似只有「輸入」與「輸出」兩種,甚至在暫存器中僅提供如 PULL_UP 或 NO_PULL_UP 的選項,但實際電氣行為與應用模式遠不只如此。
GPIO 晶片內部示意圖

 二、GPIO 輸出模式

GPIO 輸出模式主要分為:
  1. 推挽輸出(Push-Pull)
  2. 開漏輸出(Open-Drain)
※ 部分文件中提到的「一般輸出(General Output)」,多半是指 Push-Pull 模式。
 
推挽輸出(Push-Pull)
Push-Pull 為最常見的輸出方式,能主動輸出 High 或 Low,具備較佳的驅動能力與穩定性,適用於大多數數位控制情境,例如 LED 控制或一般控制訊號輸出。
(參考 GPIO 晶片內部示意圖中的藍色區塊)
 
Push-Pull 的特性為:
  1. 可主動拉高
  2. 可主動拉低
  3. 驅動能力較完整
  4. 適合單一裝置控制
開漏輸出(Open-Drain)
Open-Drain 的特性為:
  1. 只能主動輸出 Low
  2. 無法主動輸出 High
  3. High 需依靠外部或內部上拉電阻
※ Open-Drain 的 High 並不是由 GPIO 主動輸出,而是「釋放線路,讓電阻將訊號拉高」。
Open Drain 內部組合
常見應用:
  • I2C 通訊
  • 多裝置共用訊號線
  • 避免訊號衝突的設計

三、GPIO 輸入模式

GPIO 輸入模式主要包含:
  1. 上拉(Pull-up)
  2. 下拉(Pull-down)
  3. Tri-state(高阻抗 / 浮接)
※Tri-state(高阻抗)代表腳位未被明確拉高或拉低,容易受到外界雜訊影響,造成判斷不穩定。
(參考 GPIO 晶片內部示意圖中的黃色區塊) 

上拉電阻(Pull-up)
功能:
  1. 將預設電位維持在 High
  2. 避免腳位浮接
  3. 減少外部電阻需求
應用:
  1. 按鍵偵測(Active Low)
  2. Open-Drain 設計
下拉電阻(Pull-down)
功能:
  1. 將預設電位維持在 Low
  2. 避免浮接
  3. 減少外部元件
應用:
  1. Active High 設計

四、GPIO 設定與實際行為差異

在實際應用中,需考慮:
  1. Push-Pull 與 Open-Drain 輸出結構
  2. 電流驅動能力
  3. 外部電路影響
GPIO 不只是數位訊號,而是與電氣設計密切相關。

五、GPIO 驅動能力與設計觀念

許多新手會習慣將 GPIO 設為輸出 HIGH,直接點亮 LED,甚至驅動小型馬達。
雖然在簡單測試中可能可行,但在工程實務上存在風險。
原因如下:
  1. GPIO 腳位電流有限制
  2. 超過負載可能導致:
    1. 電壓不穩
    2. 晶片過熱
    3. IO Driver 損壞

六、較正確的工程設計方式

GPIO 應視為「控制訊號」,而非「電源」。
設計方式應為:
  • 控制電流路徑
  • 搭配外部電路(電阻 / MOSFET / BJT)
應用說明:
  • LED 驅動
    1. 建議使用 GPIO 輸出 LOW(Sink 電流)
    2. 搭配限流電阻
LED 驅動電路示意圖
  • 馬達或高負載
    1. 不可直接由 GPIO 驅動
    2. 必須透過外部元件控制
    3. 通常控制的電源超過 MCU 本身
    4. 增加隔離避免 MCU 被干擾(ex. 光耦合或繼電器)
馬達驅動隔離電路示意圖
 註: 基礎 GPIO 結構與隔離概念參考自 Analog Devices 及 Arduino Forum 。
  • I2C 共用訊號線
    1. 使用 Open-Drain
    2. 搭配 Pull-up 電阻
I2C 匯流排之開漏輸出 (Open-Drain) 與上拉電路架構

七、GPIO 實務應用:按鍵輸入與除彈跳

在按鍵輸入應用中,除了電位判斷外,還需要考慮「彈跳(Bounce)」問題。機械按鍵在切換瞬間,訊號會在短時間內快速震盪 High / Low,若直接讀取,容易造成誤判。

常見設計方式:
  • 使用 Pull-up(Active Low)
  • 按下時接地(GND)
  • 未按為 High,按下為 Low
除彈跳(Debounce)解法:
  1. 軟體方式
    1. 延遲判斷(delay)
    2. 多次取樣確認穩定狀態
  2. 硬體方式
    1. RC 濾波
    2. Schmitt Trigger 
核心觀念總結
  • GPIO 分為輸入與輸出,但實際應用更複雜
  • Pull-up / Pull-down 可避免浮接問題
  • Tri-state 容易造成不穩定
  • Open-Drain 需搭配 Pull-up 使用
  • GPIO 電流有限,不可直接驅動高負載
  • 按鍵應考慮除彈跳問題
  • GPIO 的本質是「控制」,不是「供電」

參考文獻 (References)

  • 工業級驅動與隔離設計
    • Analog Devices, "Implementing an Isolated Half-Bridge Gate Driver". 提供了關於高壓負載隔離、半橋驅動架構與位準移位(Level Shift)的專業設計原則.
  • 硬體實作與社群案例
    • Arduino Forum, "About Optoisolator with MOSFETs for DC Motor PWM Control - General Electronics". 參考其在微控制器環境下,利用光耦合器(Optocoupler)隔離控制高功率負載的實務電路佈局.
  • 國際通訊標準規範
    • NXP Semiconductors, "UM10204: I2C-bus specification and user manual". 確立了 I2C 匯流排的電氣特性,包含 Open-Drain(開漏輸出)要求、Wired-AND(線與)邏輯、以及多電壓系統(混合電壓)的兼容性規範.

2026年1月18日星期日

NK-N9H31A2 開發板:選擇背景與硬體規格

在 AI 不斷進化的這幾年,為了因應 Edge AI(終端 AI)的應用需求,可以預期終端裝置所使用的晶片效能將會越來越高, 從各大 MCU 晶片廠相繼推出高階 MCU 的趨勢中,其實也可以略知一二。

這一類晶片的定位,通常介於一般 MCU 與需要跑 Linux 的 CPU 之間,不論是在處理頻率或內部記憶體容量上,都比傳統 MCU 強上不少,但又不至於承擔完整作業系統的負擔, 因此在應用層面,開始具備影像加速處理,甚至執行簡易機器學習的能力。

在這樣的背景下,我開始尋找一個適合作為終端應用實驗的平台,也因此注意到了 Nuvoton 的這塊開發板, 它在周邊介面上,已經預先整合了工業常用的 RS232、RS485、CAN,並同時支援語音與網路相關功能,整體設計方向相當符合未來工廠端設備的使用情境。


開發板硬體規格

下面整理 NK-N9H31A2 開發板的主要硬體規格,內容以官方資料為主。

核心處理器與 SoC
    1. SoC:N9H31K51IFC
    2. CPU 核心:ARM926EJ-S
    3. CPU 主頻:最高可達 300 MHz
    4. 封裝:LQFP128
    5. MCP(Multi-Chip Package):內建 32 MB 記憶體
顯示與圖形處理能力
    1. 內建 TFT-LCD 顯示控制器
    2. 內建 2D 圖形加速器
    3. 內建 JPEG 編解碼器
    4. 支援最高 1024 × 768 顯示解析度
    5. 支援 16.7 百萬色(RGB 24 位元)
    6. 5 吋 800 × 480 LCD,搭配 CTP 電容式觸控面板
記憶體與儲存介面
    1. DRAM 介面頻率:最高 150 MHz
    2. 支援記憶體類型:DDR2 SDRAM
    3. SPI Flash:W25Q256JVEIQ(32 MB),支援 Quad SPI
    4. SD0 介面:
    5. 支援 SD / eMMC 記憶卡
    6. 可作為資料儲存或 SDIO(Wi-Fi)裝置使用
通訊與工業介面
    1. UART0:虛擬 COM Port,用於系統開發與除錯
    2. CAN:一組 CAN 收發器與接頭
    3. RS232 / RS485:兩組收發器,提供 DB9 與接頭連接器
    4. 乙太網路:10 / 100 Mbps Ethernet(RJ45)
    5. 音訊、多媒體與 USB
    6. 音訊編解碼器:NAU88C22(24 位元立體聲)
    7. 麥克風輸入
    8. 耳機 / 喇叭輸出
    9. 使用 I²S 介面
    10. USB_0:Device / Host 模式
    11. USB_1:Host 模式
    12. 支援隨身碟、鍵盤、滑鼠、印表機
電源配置
    1. 3V I/O 電源
    2. 1.8V 記憶體電源
    3. 1.2V 核心電源
後  記:
    此篇為系列文章的第一篇,後續的文章皆由此展開

參考資料

2023年6月12日星期一

STM32F103 小藍板晶片問題

        近日有空重新拿出很久以前買的STM32F103 經典小藍板,LED閃爍程式寫完後,一直無法順利切換至除錯模式,最後只能透過STM32 Cube Programer 並透過uart介面才可進行燒錄,以下分享一下除錯的過程,減少走冤枉路的時間。


        相信剛入門的朋友,較多的一定這樣的搭配,STLINK V2 + STM32F103小藍板。使用STM32 Cube IDE 開發功能時,有遇過不能順利透過IDE切換除錯模式,明明線路都接對啊,但仍是顯示錯誤訊息,在本篇實驗中會介紹簡易方式確認晶片,為避免花過多時間對硬體除錯。


實驗材料

硬體設備

1. STM32F103 小藍板 x 2

2. STLINK V3(為避免被假的ST Link V2雷到,此處使用正版的V3)

軟體

1. STM32 Cube IDE

2. Keil MDk


實驗步驟

1. 先使用Logo看起來怪怪的板子,使用放大鏡看清楚



2. 參考此篇GPIO專案建立

3. 當透過IDE將程式燒錄至MCU時會發生以下的情況,Target no Device found


4. 接下來開啟keil MDK,進行再次確認

點擊魔術棒

開啟Debug 分頁

這裡將會發現,沒有找到任何SW Device

5. 接下來換成LOGO 比較正常的,並重複步驟3~步驟4

這次會發現,STM32CubeIDE 可以順利載入程式並切換至Debug模式

keil MDK 也可以辨識到晶片

實驗結果:

1. 當STM32 Cube IDE 無法透過SWDIO 順利燒錄程式時,可以透過keil MDK 確認是否 SW Device 辨識不到。
2. 如果不在意Debug 可以使用STM32 Cube Programer 改成uart進行燒錄。
3. 有些仿的晶片會連LOGO都長得一樣,必須透過連線確認Device 才知道。


希望這篇文章有幫助你,若內容有錯有歡迎糾正,謝謝!

2019年6月6日星期四

2019年6月1日星期六

STM32 焊接開發板

今天因為用麵包板用到不爽,杜邦線太多且會不小心掉線,於是花了一段時間焊接開發板


原本

焊接完畢
 前後對比XD


焊接完後雖然花了不少時間但是也比較舒服~~~


2019年5月12日星期日

開啟STM32F103C8T6的三個 uart open 3 uart with stm32f103c8t6 by using keil

使用keil 開發stm32f103c8t6 開啟三組硬體 uart

description

use stm32f103c8t6 3 uarts
send char and can recive each uart port
main.c
#include "stm32f10x.h"
#include "UART.h"

int main(void)
{
 USART1_Init(460800);
 USART2_Init(460800);
 USART3_Init(460800);
 USART1_PutString("HI~");
 USART2_PutString("HI~");
 USART3_PutString("HI~");
 while(1)
 {
  char recive1 = USART1_GetChar();
  USART1_PutChar(recive1);
  char recive2 = USART2_GetChar();
  USART2_PutChar(recive2);
  char recive3 = USART3_GetChar();
  USART3_PutChar(recive3);
 }
}

UART.h
#ifndef __UART_H
#define __UART_H

#ifdef __cplusplus
 extern "C" {
#endif

#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_usart.h"
#include 

#define BUF_SIZE1 16
#define BUF_SIZE2 16
#define BUF_SIZE3 16
//uart1
void USART1_Init(uint32_t baudrate);
void USART1_PutChar(char c);
void USART1_PutString(char *s);
uint16_t USART1_GetChar(void);

//uart2
void USART2_Init(uint32_t baudrate);
void USART2_PutChar(char c);
void USART2_PutString(char *s);
uint16_t USART2_GetChar(void);

//uart3
void USART3_Init(uint32_t baudrate);
void USART3_PutChar(char c);
void USART3_PutString(char *s);
uint16_t USART3_GetChar(void);

#ifdef __cplusplus
}
#endif

#endif

UART.c
#include "UART.h"
char buf1[BUF_SIZE1];
char buf2[BUF_SIZE2];
char buf3[BUF_SIZE3];

uint8_t receivedByte1;
uint8_t receivedByte2;
uint8_t receivedByte3;
//UART1
void USART1_Init(uint32_t baudrate)
{
 //Initialization struct
 USART_InitTypeDef USART_InitStruct;
 GPIO_InitTypeDef GPIO_InitStruct;
 
 //USART1 initialization
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
 USART_InitStruct.USART_BaudRate = baudrate;
 USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
 USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
 USART_InitStruct.USART_Parity = USART_Parity_No;
 USART_InitStruct.USART_StopBits = USART_StopBits_1;
 USART_InitStruct.USART_WordLength = USART_WordLength_8b;
 USART_Init(USART1, &USART_InitStruct);
 USART_Cmd(USART1, ENABLE);
 
 //GPIO initialization for Tx and Rx pin
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
 // Tx pin initialization as push-pull alternate function
 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
 GPIO_Init(GPIOA, &GPIO_InitStruct);
 // Rx pin initialization as input floating
 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
 GPIO_Init(GPIOA, &GPIO_InitStruct);
}

void USART1_PutChar(char c)
{
 // Wait until transmit data register is empty
 while (!USART_GetFlagStatus(USART1, USART_FLAG_TXE));
 // Send a char using USART1
 USART_SendData(USART1, c);
}

void USART1_PutString(char *s)
{
 // Send a string
 while (*s)
 {
  USART1_PutChar(*s++);
 }
}

uint16_t USART1_GetChar()
{
 // Wait until data is received
 while (!USART_GetFlagStatus(USART1, USART_FLAG_RXNE));
 // Read received char
 return USART_ReceiveData(USART1);
}
//UART2
void USART2_Init(uint32_t baudrate)
{
 // Initialization struct
 USART_InitTypeDef USART_InitStruct;
 GPIO_InitTypeDef GPIO_InitStruct;
 
 //USART2 initialization
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
 USART_InitStruct.USART_BaudRate = baudrate;
 USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
 USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
 USART_InitStruct.USART_Parity = USART_Parity_No;
 USART_InitStruct.USART_StopBits = USART_StopBits_1;
 USART_InitStruct.USART_WordLength = USART_WordLength_8b;
 USART_Init(USART2, &USART_InitStruct);
 USART_Cmd(USART2, ENABLE);
 
 //GPIO initialization for Tx and Rx pin
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
 // Tx pin initialization as push-pull alternate function
 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
 GPIO_Init(GPIOA, &GPIO_InitStruct);
 // Rx pin initialization as input floating
 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
 GPIO_Init(GPIOA, &GPIO_InitStruct);
}

void USART2_PutChar(char c)
{
 // Wait until transmit data register is empty
 while (!USART_GetFlagStatus(USART2, USART_FLAG_TXE));
 // Send a char using USART2
 USART_SendData(USART2, c);
}

void USART2_PutString(char *s)
{
 // Send a string
 while (*s)
 {
  USART2_PutChar(*s++);
 }
}

uint16_t USART2_GetChar()
{
 // Wait until data is received
 while (!USART_GetFlagStatus(USART2, USART_FLAG_RXNE));
 // Read received char
 return USART_ReceiveData(USART2);
}

void USART3_Init(uint32_t baudrate)
{
 // Initialization struct
 USART_InitTypeDef USART_InitStruct;
 GPIO_InitTypeDef GPIO_InitStruct;
 
 //USART3 initialization
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
 USART_InitStruct.USART_BaudRate = baudrate;
 USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
 USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
 USART_InitStruct.USART_Parity = USART_Parity_No;
 USART_InitStruct.USART_StopBits = USART_StopBits_1;
 USART_InitStruct.USART_WordLength = USART_WordLength_8b;
 USART_Init(USART3, &USART_InitStruct);
 USART_Cmd(USART3, ENABLE);
 
 //GPIO initialization for Tx and Rx pin
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
 // Tx pin initialization as push-pull alternate function
 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
 GPIO_Init(GPIOB, &GPIO_InitStruct);
 // Rx pin initialization as input floating
 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
 GPIO_Init(GPIOB, &GPIO_InitStruct);
}

void USART3_PutChar(char c)
{
 // Wait until transmit data register is empty
 while (!USART_GetFlagStatus(USART3, USART_FLAG_TXE));
 // Send a char using USART2
 USART_SendData(USART3, c);
}

void USART3_PutString(char *s)
{
 // Send a string
 while (*s)
 {
  USART3_PutChar(*s++);
 }
}
uint16_t USART3_GetChar()
{
 // Wait until data is received
 while (!USART_GetFlagStatus(USART3, USART_FLAG_RXNE));
 // Read received char
 return USART_ReceiveData(USART3);
}
https://github.com/windyman24/STM32F103_learn

打賞按讚