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

张开发
2026/4/13 0:31:30 15 分钟阅读

分享文章

分享一个CW32 IO拓展项目:使用CW32L010做GPIO/ADC 扩展
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扩展高端MOS控制高端电流采样。PCB3. 通信协议介绍本系统通过 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 A0x01: 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) - 用于 ADC0x03: 中断模式 (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] 00ADCValue(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进行编译需要将依赖的文件添加。

更多文章