1. 设计目标
CW32L010是一款极具性价比的ARM Cortex-M0+内核微控制器。其核心优势在于以极致成本,集成了丰富的外设资源:包括一个12位精度的ADC、最多16个可配置的GPIO口以及四个通用定时器。
正是基于这些特性,该芯片非常适合用于两类核心应用场景:
功能简单的独立设备,如传感器节点、小家电主控等。
当应用于IO/ADC扩展时,CW32L010不再是一颗简单的“胶合逻辑”芯片,而是一个可编程的智能外设。这意味着开发者需要为其独立设计和烧录固件,使其能够按照预设的协议与主控制器进行协同工作。
本项目(固件设计)当前阶段的目标,正是实现这样一个智能扩展模块的雏形。目前已完成的核心功能是:
基于串口(UART)的通信协议,确保与主控稳定、高效的数据交换。
完整的GPIO控制能力,支持引脚模式动态配置(输入/输出、上拉/下拉等)和状态读写。
ADC采样功能,可通过命令对指定通道进行模拟量采集并返回数据。
下一阶段的开发路线图已明确规划,将重点增加以下功能:
I²C从机接口扩展:将CW32L010本身模拟为一个I²C从设备,为仅具备I²C接口的主控提供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文章
7943浏览量
556860 -
GPIO
+关注
关注
16文章
1333浏览量
56430 -
CW32
+关注
关注
1文章
323浏览量
1953
发布评论请先 登录
CW32L010_ADC介绍
CW32L010的串口输出
把CW32L010用FLASH模拟EEPROM
CW32L010的内部框图
CW32L010高速风筒方案的特点
CW32L010系列的特点
基于CW32L010的高性能温控器方案
基于CW32L010的高性能温控器方案
基于CW32L010单片机的扫振一体电动牙刷应用方案
CW32L010 ESC Driver 电机控制套件使用
武汉芯源半导体CW32L010在两轮车仪表的应用介绍
分享一个CW32 IO拓展项目:使用CW32L010做GPIO/ADC 扩展
评论