1. 設計目標
CW32L010是一款極具性價比的ARM Cortex-M0+內核微控制器。其核心優勢在于以極致成本,集成了豐富的外設資源:包括一個12位精度的ADC、最多16個可配置的GPIO口以及四個通用定時器。
正是基于這些特性,該芯片非常適合用于兩類核心應用場景:
功能簡單的獨立設備,如傳感器節點、小家電主控等。
當應用于IO/ADC擴展時,CW32L010不再是一顆簡單的“膠合邏輯”芯片,而是一個可編程的智能外設。這意味著開發者需要為其獨立設計和燒錄固件,使其能夠按照預設的協議與主控制器進行協同工作。
本項目(固件設計)當前階段的目標,正是實現這樣一個智能擴展模塊的雛形。目前已完成的核心功能是:
基于串口(UART)的通信協議,確保與主控穩定、高效的數據交換。
完整的GPIO控制能力,支持引腳模式動態配置(輸入/輸出、上拉/下拉等)和狀態讀寫。
ADC采樣功能,可通過命令對指定通道進行模擬量采集并返回數據。
下一階段的開發路線圖已明確規劃,將重點增加以下功能:
I2C從機接口擴展:將CW32L010本身模擬為一個I2C從設備,為僅具備I2C接口的主控提供GPIO和ADC擴展能力,增加應用靈活性。
PWM輸出功能:充分利用其定時器資源,實現多通道、可調頻率與占空比的PWM信號生成,用于控制LED亮度、電機速度或生成特定波形。
2. 使用電路圖
隔離IO/ADC擴展

高端MOS控制,高端電流采樣。


3. 通信協議介紹
本系統通過 UART 串口(波特率 115200)控制微控制器的 GPIO 引腳,支持模式配置、狀態讀寫、ADC 采集及中斷上報功能。
3.1 支持的IO及功能
系統上電后,所有支持的 IO 默認初始化為 輸入模式。
| 端口 (Port) | 引腳 (Pin) | 支持的功能 | 備注 |
| Port A (0x00) | PA02 | 輸入 / 輸出 / 中斷 / ADC (CH2) | |
| PA03 | 輸入 / 輸出 / 中斷 / ADC (CH3) | ||
| PA04 | 輸入 / 輸出 / 中斷 / ADC (CH4) | ||
| PA05 | 輸入 / 輸出 / 中斷 / ADC (CH5) | ||
| PA06 | 輸入 / 輸出 / 中斷 / ADC (CH6) | ||
| Port B (0x01) | PB00 | 輸入 / 輸出 / 中斷 / ADC (CH7) | |
| PB01 | 輸入 / 輸出 / 中斷 / ADC (CH8) | ||
| PB02 | 輸入 / 輸出 / 中斷 / ADC (CH9) | ||
| PB03 | 輸入 / 輸出 / 中斷 / ADC (CH10) | ||
| PB04 | 輸入 / 輸出 / 中斷 / ADC (CH11) | ||
| PB05 | 輸入 / 輸出 / 中斷 / ADC (CH12) | ||
| PB06 | 輸入 / 輸出 / 中斷 / ADC (CH13) |
注意: PA00 和 PA01 為串口通信引腳,禁止用于其他功能。
3.2串口通信協議
通信定義
波特率: 115200
數據格式: 4 字節固定長度 HEX 幀
幀結構: [命令碼] [端口] [引腳] [參數]
端口定義
0x00: Port A
0x01: Port B
引腳定義
0x00 ~ 0x0F: 對應 Pin 0 ~ Pin 15
啟動消息
設備上電或重啟完成后,會自動發送以下 HEX 序列:
HEX: 52 45 41 44 (ASCII: "READ")
3. 3 命令詳解
設置 IO 模式 (Set Mode)
發送: 01 00 02 03
接收: 01 00 02 01
發送: 01 01 00 01
接收: 01 01 00 01
參數 [Mode]:
0x00: 輸入模式 (Input) - 默認
0x01: 輸出模式 (Output Push-Pull)
0x02: 模擬模式 (Analog) - 用于 ADC
0x03: 中斷模式 (Interrupt) - 雙邊沿觸發
配置指定 IO 的工作模式。
命令碼: 0x01
格式: 01 [Port] [Pin] [Mode]
成功響應: 01 [Port] [Pin] 01
示例: 將 PB00 設置為輸出模式
示例: 將 PA02 設置為中斷模式
讀取 IO 電平 (Read Pin)
[State]: 00 (低電平) / 01 (高電平)
讀取指定 IO 當前的電平狀態。
命令碼: 0x02
格式: 02 [Port] [Pin] 00 (末尾字節無效,補0即可)
響應: 02 [Port] [Pin] [State]
示例: 讀取 PB00 電平
發送: 02 01 00 00
接收: 02 01 00 01 (當前為高電平)
3.4 設置 IO 電平 (Write Pin)
控制指定 IO 輸出高低電平(需先配置為輸出模式)。
0x00: 輸出低電平
0x01: 輸出高電平
命令碼: 0x03
格式: 03 [Port] [Pin] [Value]
參數 [Value]:
響應: 03 [Port] [Pin] 01
示例: 設置 PB00 輸出高電平
發送: 03 01 00 01
接收: 03 01 00 01
讀取 ADC 值 (Read ADC)
發送: 04 00 04 00
接收: 04 0A 23 00
計算: 0x0A23 = 2595
命令碼: 0x04
格式: 04 [Port] [Pin] 00
響應: 04 [High Byte] [Low Byte] 00
ADCValue = (High Byte << 8) | Low Byte
讀取指定 IO 的 ADC 轉換值(需先配置為模擬模式)。
示例: 讀取 PA04 的 ADC 值
3.5 中斷自動上報 (Interrupt Notify)
接收: 05 01 00 01 (PB00 變為高電平)
接收: 05 01 00 00 (PB00 變為低電平)
命令碼: 0x05
格式: 05 [Port] [Pin] [State]
[State]: 中斷發生后的當前電平 (00 或 01)
當 IO 配置為中斷模式 (0x03) 后,電平發生變化(上升沿或下降沿)時,設備會自動發送此幀。
示例: PB00 電平跳變
3.6 重啟設備 (System Reset)
復位微控制器。
發送: 06 00 00 00
接收: 06 01 00 00
格式: 06 00 00 00
響應: 06 01 00 00 (收到響應后設備將立即重啟)
命令碼: 0x06
示例: 重啟設備
4. 實現代碼
串口初始化,中斷處理
volatile uint8_t rx_buffer[UART_RX_BUFFER_SIZE];
volatile uint16_t rx_index = 0;
volatile uint8_t cmd_ready = 0;
/**
* @brief Initialize the debug UART1
* @note PA00 RXD AF1 PA01 TXD AF1
*
*/
void Debug_Uart_Init(void)
{
SYSCTRL_AHBPeriphClk_Enable(SYSCTRL_AHB_PERIPH_GPIOA | SYSCTRL_AHB_PERIPH_GPIOB, ENABLE);
SYSCTRL_APBPeriphClk_Enable1(SYSCTRL_APB1_PERIPH_UART1, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure = {0};
GPIO_InitStructure.Pins = GPIO_PIN_0;
GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
GPIO_Init(CW_GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.Pins = GPIO_PIN_1;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init(CW_GPIOA, &GPIO_InitStructure);
PA01_AFx_UART1TXD();
PA00_AFx_UART1RXD();
UART_InitTypeDef UART_InitStructure = {0};
UART_InitStructure.UART_BaudRate = 115200;
UART_InitStructure.UART_Source = UART_Source_PCLK;
UART_InitStructure.UART_UclkFreq = 48000000; // 48MHz
UART_InitStructure.UART_StopBits = UART_StopBits_1;
UART_InitStructure.UART_Parity = UART_Parity_No ;
UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;
UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx;
UART_Init(CW_UART1, &UART_InitStructure);
// Enable UART RX Interrupt
UART_ITConfig(CW_UART1, UART_IT_RC, ENABLE);
NVIC_EnableIRQ(UART1_IRQn);
// Enable ADC Clock
SYSCTRL_APBPeriphClk_Enable1(SYSCTRL_APB1_PERIPH_ADC, ENABLE);
}
int fputc(int ch, FILE *f)
{
UART_SendData_8bit(CW_UART1, (uint8_t)ch);
while (UART_GetFlagStatus(CW_UART1, UART_FLAG_TXE) == RESET);
return ch;
}
void UART1_RxCallback(uint8_t data)
{
if (rx_index < UART_RX_BUFFER_SIZE)
{
rx_buffer[rx_index++] = data;
if (rx_index >= UART_RX_BUFFER_SIZE)
{
cmd_ready = 1;
}
}
}
/**
* @brief This funcation handles UART1
*/
void UART1_IRQHandler(void)
{
/* USER CODE BEGIN */
if(UART_GetITStatus(CW_UART1, UART_IT_RC) != RESET)
{
uint8_t data = UART_ReceiveData_8bit(CW_UART1);
UART_ClearITPendingBit(CW_UART1, UART_IT_RC);
UART1_RxCallback(data);
}
/* USER CODE END */
}
2.循環讀取命令
while (1)
{
Process_UART_Command();
}
void Process_UART_Command(void)
{
if (cmd_ready)
{
uint8_t cmd = rx_buffer[0];
uint8_t port = rx_buffer[1];
uint8_t pin = rx_buffer[2];
uint8_t param = rx_buffer[3];
switch (cmd)
{
case CMD_SET_MODE:
Cmd_SetMode(port, pin, param);
break;
case CMD_READ_PIN:
Cmd_Read(port, pin);
break;
case CMD_WRITE_PIN:
Cmd_Write(port, pin, param);
break;
case CMD_READ_ADC:
Cmd_Read_ADC(port, pin);
break;
case CMD_RESET:
Cmd_Reset();
break;
default:
// Unknown command
break;
}
// Reset buffer
rx_index = 0;
cmd_ready = 0;
memset((void*)rx_buffer, 0, UART_RX_BUFFER_SIZE);
}
}
3.0x01 配置指定 IO 的工作模式函數
void Cmd_SetMode(uint8_t port_idx, uint8_t pin, uint8_t mode)
{
// Validate Pin
if (!Is_Pin_Valid(port_idx, pin)) return;
GPIO_TypeDef* gpio_port = Get_GPIO_Port(port_idx);
uint16_t gpio_pin = Get_GPIO_Pin(pin);
if (gpio_port == NULL) return;
GPIO_InitTypeDef GPIO_InitStructure = {0};
GPIO_InitStructure.Pins = gpio_pin;
if (mode == 0x01) // Output
{
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
}
else if (mode == 0x02) // Analog
{
GPIO_InitStructure.Mode = GPIO_MODE_ANALOG;
}
else if (mode == 0x03) // Interrupt (Double Edge)
{
GPIO_InitStructure.Mode = GPIO_MODE_INPUT; // Standard Input
GPIO_InitStructure.IT = GPIO_IT_RISING | GPIO_IT_FALLING;
// Enable NVIC for the port
if (port_idx == PORT_A)
{
NVIC_EnableIRQ(GPIOA_IRQn);
}
else if (port_idx == PORT_B)
{
NVIC_EnableIRQ(GPIOB_IRQn);
}
}
else // Input (Default to 0x00)
{
GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
}
GPIO_Init(gpio_port, &GPIO_InitStructure);
// Return success: [CMD] [PORT] [PIN] [0x01]
printf("%c%c%c%c", CMD_SET_MODE, port_idx, pin, 0x01);
}
4.0x02 讀取指定 IO 當前的電平狀態。
void Cmd_Read(uint8_t port_idx, uint8_t pin)
{
// Validate Pin
if (!Is_Pin_Valid(port_idx, pin)) return;
GPIO_TypeDef* gpio_port = Get_GPIO_Port(port_idx);
uint16_t gpio_pin = Get_GPIO_Pin(pin);
if (gpio_port == NULL) return;
uint8_t state = GPIO_ReadPin(gpio_port, gpio_pin);
// Return format: [CMD] [PORT] [PIN] [STATE]
printf("%c%c%c%c", CMD_READ_PIN, port_idx, pin, state);
}
5.0x03控制指定 IO 輸出高低電平(需先配置為輸出模式)。
void Cmd_Write(uint8_t port_idx, uint8_t pin, uint8_t value)
{
// Validate Pin
if (!Is_Pin_Valid(port_idx, pin)) return;
GPIO_TypeDef* gpio_port = Get_GPIO_Port(port_idx);
uint16_t gpio_pin = Get_GPIO_Pin(pin);
if (gpio_port == NULL) return;
GPIO_WritePin(gpio_port, gpio_pin, (value ? GPIO_Pin_SET : GPIO_Pin_RESET));
// Return success: [CMD] [PORT] [PIN] [0x01]
printf("%c%c%c%c", CMD_WRITE_PIN, port_idx, pin, 0x01);
}
6.0x04讀取指定 IO 的 ADC 轉換值(需先配置為模擬模式)。
uint32_t Get_ADC_Channel(uint8_t port_idx, uint8_t pin)
{
// Mapping based on CW32L010 datasheet/header
// PA00 -> ADC_IN0 ... PA07 -> ADC_IN7 (Note: PA07 is not available on all packages, check specific map)
// Actually from cw32l010_adc.h comments:
// PA00->CH0, PA01->CH1, PA02->CH2, PA03->CH3, PA04->CH4, PA05->CH5, PA06->CH6
// PB00->CH7, PB01->CH8, PB02->CH9, PB03->CH10, PB04->CH11, PB05->CH12, PB06->CH13
if (port_idx == PORT_A)
{
if (pin <= 6) return (uint32_t)pin; // CH0-CH6
}
else if (port_idx == PORT_B)
{
if (pin <= 6) return (uint32_t)(pin + 7); // CH7-CH13
}
return 0xFFFFFFFF; // Invalid
}
void Cmd_Read_ADC(uint8_t port_idx, uint8_t pin)
{
// Validate Pin
if (!Is_Pin_Valid(port_idx, pin)) return;
uint32_t adc_ch = Get_ADC_Channel(port_idx, pin);
if (adc_ch == 0xFFFFFFFF) return;
ADC_InitTypeDef ADC_InitStructure = {0};
ADC_InitStructure.ADC_ClkDiv = ADC_Clk_Div4;
ADC_InitStructure.ADC_ConvertMode = ADC_ConvertMode_Once;
ADC_InitStructure.ADC_SQREns = ADC_SqrEns0to0;
ADC_InitStructure.ADC_IN0.ADC_InputChannel = adc_ch;
ADC_InitStructure.ADC_IN0.ADC_SampTime = ADC_SampTime12Clk;
ADC_Init(&ADC_InitStructure);
ADC_Enable();
ADC_SoftwareStartConvCmd(ENABLE);
// Wait for conversion (simple polling with timeout)
uint32_t timeout = 10000;
while (timeout--)
{
if (ADC_GetITStatus(ADC_IT_EOC) != RESET)
{
ADC_ClearITPendingBit(ADC_IT_EOC);
break;
}
}
uint16_t result = ADC_GetConversionValue(0); // Get result from SQR0
ADC_Disable();
// Return: [CMD] [High Byte] [Low Byte] [0x00]
printf("%c%c%c%c", CMD_READ_ADC, (uint8_t)(result >> 8), (uint8_t)(result & 0xFF), 0x00);
}
7.0x05當 IO 配置為中斷模式 (0x03) 后,電平發生變化(上升沿或下降沿)時,設備會自動發送此幀。
/**
* @brief This funcation handles GPIOA
*/
void GPIOA_IRQHandler(void)
{
/* USER CODE BEGIN */
GPIO_ISR_Handler(PORT_A);
/* USER CODE END */
}
/**
* @brief This funcation handles GPIOB
*/
void GPIOB_IRQHandler(void)
{
/* USER CODE BEGIN */
GPIO_ISR_Handler(PORT_B);
/* USER CODE END */
}
void GPIO_ISR_Handler(uint8_t port_idx)
{
GPIO_TypeDef* gpio_port = Get_GPIO_Port(port_idx);
if (gpio_port == NULL) return;
uint16_t isr = gpio_port->ISR;
for (uint8_t i = 0; i < 16; i++)
{
// Check validity first
if (!Is_Pin_Valid(port_idx, i)) continue;
uint16_t pin_mask = (1 < i);
if (isr & pin_mask)
{
// Clear interrupt flag
gpio_port-?>ICR = ~pin_mask;
// Read current state
uint8_t state = (gpio_port->IDR & pin_mask) ? 1 : 0;
// Send notification: [CMD_IT_NOTIFY] [PORT] [PIN] [STATE]
printf("%c%c%c%c", CMD_IT_NOTIFY, port_idx, i, state);
}
}
}
8.0x06 復位微控制器。
void Cmd_Reset(void)
{
// Send acknowledgement: [CMD_RESET] [0x01] [0x00] [0x00]
printf("%c%c%c%c", CMD_RESET, 0x01, 0x00, 0x00);
// Wait for UART transmission
delay_ms(50);
// Reset System
NVIC_SystemReset();
}
5. 完整代碼
完成代碼請訪問倉庫:
https://gitee.com/aotengyang/open-source-code
固件使用 VScode EIDE 插件進行開發,如需使用keil進行編譯,需要將依賴的文件添加。
審核編輯 黃宇
-
adc
+關注
關注
100文章
7755瀏覽量
556615 -
GPIO
+關注
關注
16文章
1332瀏覽量
56356 -
CW32
+關注
關注
1文章
323瀏覽量
1924
發布評論請先 登錄
CW32L010_ADC介紹
CW32L010的串口輸出
把CW32L010用FLASH模擬EEPROM
CW32L010的內部框圖
CW32L010高速風筒方案的特點
CW32L010系列的特點
基于CW32L010的高性能溫控器方案
基于CW32L010的高性能溫控器方案
基于CW32L010單片機的掃振一體電動牙刷應用方案
CW32L010 ESC Driver 電機控制套件使用
武漢芯源半導體CW32L010在兩輪車儀表的應用介紹
分享一個CW32 IO拓展項目:使用CW32L010做GPIO/ADC 擴展
評論