0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

分享一个CW32 IO拓展项目:使用CW32L010做GPIO/ADC 扩展

CW32生态社区 作者:CW32生态社区 2026-03-31 21:43 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

1. 设计目标

CW32L010是一款极具性价比的ARM Cortex-M0+内核微控制器。其核心优势在于以极致成本,集成了丰富的外设资源:包括一个12位精度的ADC、最多16个可配置的GPIO口以及四个通用定时器

正是基于这些特性,该芯片非常适合用于两类核心应用场景:

功能简单的独立设备,如传感器节点、小家电主控等。

作为主控系统的扩展单元,尤其是IO扩展和模拟信号采集。

当应用于IO/ADC扩展时,CW32L010不再是一颗简单的“胶合逻辑”芯片,而是一个编程的智能外设。这意味着开发者需要为其独立设计和烧录固件,使其能够按照预设的协议与主控制器进行协同工作。

本项目(固件设计)当前阶段的目标,正是实现这样一个智能扩展模块的雏形。目前已完成的核心功能是:

基于串口(UART)的通信协议,确保与主控稳定、高效的数据交换。

完整的GPIO控制能力,支持引脚模式动态配置(输入/输出、上拉/下拉等)和状态读写。

ADC采样功能,可通过命令对指定通道进行模拟量采集并返回数据。

下一阶段的开发路线图已明确规划,将重点增加以下功能:

I²C从机接口扩展:将CW32L010本身模拟为一个I²C从设备,为仅具备I²C接口的主控提供GPIO和ADC扩展能力,增加应用灵活性。

PWM输出功能:充分利用其定时器资源,实现多通道、可调频率与占空比的PWM信号生成,用于控制LED亮度、电机速度或生成特定波形。

2. 使用电路图

隔离IO/ADC扩展

wKgZO2nLz4-ACWnAAACXUi9hCnw194.jpg

高端MOS控制,高端电流采样

wKgZPGnLz5CAN751AACsmCg6JoA150.jpg

PCB

wKgZO2nLz5GAE7tQAAEtNGZ_naE636.jpg

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
    adc
    +关注

    关注

    100

    文章

    7943

    浏览量

    556860
  • GPIO
    +关注

    关注

    16

    文章

    1333

    浏览量

    56430
  • CW32
    +关注

    关注

    1

    文章

    323

    浏览量

    1953
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    CW32L010_ADC介绍

    CW32L010_ADC特性概述 分辨率与采样率:CW32L010ADC具有12位分辨率,这意味着它能够提供较高的精度。同时,其采样率可达2M,这使得
    发表于 11-28 07:52

    CW32L010的串口输出

    CW32L010款集成了丰富功能的低功耗微控制器,其串口输出功能是通过其内部的通用异步收发器(UART)实现的。 、串口硬件资源 CW32L010提供了二路低功耗UART,这些U
    发表于 11-27 07:27

    CW32L010用FLASH模拟EEPROM

    CW32L010横空出世,定时器和ADC变化很大,FLASH基本和以前型号样,但有点改动,BUSY位从CR1寄存器改到ISR寄存器了。 把F003的程序改改就能用,太棒了,拿走不谢
    发表于 11-24 07:40

    CW32L010的内部框图

    CW32L010系列产品是基于 eFlash 的单芯片低功耗微控制器,集成了主频高达 48MHz 的 ARM® Cortex®-M0+ 内核、高速嵌入式存储器(多至 64K 字节 FLASH 和多至 4K 字节 SRAM)以及系列全面的增强型外设和 I/O 口。
    发表于 11-21 06:40

    CW32L010用jlink能去除读保护吗?

    如题,板上的CW32L010有读保护,JLINK能识别到内核,但无法擦除下载程序。 要怎么才能去除读保护呢
    发表于 11-20 06:23

    CW32L010ADC采样值波动导致电机调速不平滑怎么解决?

    CW32L010ADC采样值波动导致电机调速不平滑
    发表于 11-18 06:30

    CW32L010高速风筒方案的特点

    CW32L010F8P6集成了多种外设接口,所有型号都提供全套的通信接口(二路 UART、路 SPI 和路 I2C)、12 位高速 ADC、四组通用和基本定时器、
    发表于 11-13 06:21

    请问CW32L010 能否使用SysTick中断?

    CW32L010 能否使用SysTick中断?
    发表于 11-12 07:25

    CW32L010系列的特点

    CW32L010系列MCU采用了独特的工艺制程,使其待机电流降低至仅3uA,这在高温环境下也表现得尤为出色,漏电仅为竞品平均水平的四分之到八分之。此外,我们还全新设计了高级定时器,不仅兼容G4
    发表于 11-12 06:51

    基于CW32L010的高性能温控器方案

    武汉芯源半导体的明星产品CW32L010系列MCU凭借其ARM Cortex-M0+内核、超低功耗特性以及丰富的外设接口,为温控器设计提供了理想的解决方案。 本文将介绍无锡梓轩电子基于武汉芯源半导体低功耗CW32L010单片机开发的温控器方案,功能全面,性价比突出。
    的头像 发表于 07-02 09:47 1468次阅读
    基于<b class='flag-5'>CW32L010</b>的高性能温控器方案

    基于CW32L010的高性能温控器方案

    功耗设计: CW32L010采用先进的工艺制程,待机电流仅0.3μA,即使在85℃高温环境下,漏电流也仅为1.2μA,远低于同类竞品。这特性使得采用电池供电的温控器能够实现长达数年的工作寿命,大大减少了维护需求
    发表于 07-02 09:46

    基于CW32L010单片机的扫振体电动牙刷应用方案

    大幅摆动实现了高效的刷牙方式,这种设计能够更高效地清洁牙齿和牙龈沟,为用户带来全新的洁牙体验‌。本文将介绍武汉芯源半导体CW32L010单片机在扫振体电动牙刷上的应用方案。 图1:电动牙刷方案
    发表于 06-17 09:38

    CW32L010 ESC Driver 电机控制套件使用

    CW32L010ESC_Driver电机驱动板是武汉芯源科技推出的款低成本BLDC无刷直流电机开发板。在立创开源硬件平台可以查看该项目的详情。 https://oshwhub.com
    发表于 06-13 18:12

    武汉芯源半导体CW32L010在两轮车仪表的应用介绍

    CW32L010凭借其优异的性能、丰富的外设资源和超低功耗特性,为两轮车仪表盘应用提供了高性价比的解决方案。其宽电压工作范围和工业级温度特性,特别适合车辆电子应用的严苛环境。对于想采用CW32L010进行两轮车仪表盘开发的客户,武汉芯源半导体可提供全面的技术支持,助力客户
    的头像 发表于 05-13 14:07 963次阅读
    武汉芯源半导体<b class='flag-5'>CW32L010</b>在两轮车仪表的应用介绍

    武汉芯源半导体CW32L010在两轮车仪表的应用介绍

    通用和基本定时器、组低功耗定时器以及组高级控制 PWM 定时器。 CW32L010 可以在 -40℃到 85℃的温度范围内工作,供电电压宽达 1.62V ~ 5.5V。支持 Sleep
    发表于 05-13 14:06