
引言通读demo/demo_gpio.c后可归纳出该示例的全部实现可压缩为五个要点GPIO 初始化、UART 初始化、消息队列、两个回调的消息分配、AT 指令识别 其余代码均为这五点的展开或辅助。要点一GPIO 初始化职责将指定引脚配置为输出或输入中断模式。分两步顺序不可颠倒demo_pin_init();// ① 配置引脚复用retdemo_gpio_init();// ② 配置方向/上下拉 注册中断1.1demo_pin_init()—— 引脚复用#ifdefTYPE_EC718Mif(s_gpio_portGPIO_20s_gpio_portGPIO_28)yopen_aon_power_on();#elseif(s_gpio_portGPIO_10s_gpio_portGPIO_16)yopen_aon_power_on();#endifswitch(s_gpio_port){caseGPIO_0:yopen_pin_set_func(YOPEN_PIN_GPIO0,0);return;/* ... 至 GPIO_38 ... */}操作说明yopen_aon_power_on()AON 引脚需先上电。716/718芯片型号范围不同yopen_pin_set_func(pin, func_sel)func_sel 0复用功能设0易错点#else分支中GPIO_17~GPIO_20使用功能号4而非0。同一物理引脚在716/718芯片芯片上的复用表存在差异必须查手册。1.2demo_gpio_init()—— 参数生成核心是把s_gpio_mode映射为四个参数方向输入 / 输出、上下拉、电平高 / 低、触发方式边沿 / 电平s_gpio_modedirpulltrigger注册中断GPIO_OUTPUT_LVL_HIGH(0)输出无—否GPIO_OUTPUT_LVL_LOW(1)输出无—否GPIO_INPUT_EDGE_INT_PULL_NONE(2)输入无边沿是GPIO_INPUT_EDGE_INT_PULL_UP(3)输入上拉边沿是GPIO_INPUT_EDGE_INT_PULL_DOWN(4)输入下拉边沿是GPIO_INPUT_LEVEL_INT_PULL_NONE(5)输入无电平是GPIO_INPUT_LEVEL_INT_PULL_UP(6)输入上拉电平是GPIO_INPUT_LEVEL_INT_PULL_DOWN(7)输入下拉电平是8 个枚举仅三组行为0/1 输出2/3/4 输入边沿5/6/7 输入电平组内差异仅在上下拉。int_flag这个标志是输入输出做一个区分。1.3 中断注册if(int_flag){ret|yopen_int_register(s_gpio_port,gpio_trigger,DEBOUNCE_DIS,s_gpio_int_trig_mode,gpio_pull,demo_gpio_cb,(void*)s_gpio_port);ret|yopen_int_enable(s_gpio_port);}#参数当前值说明1引脚号s_gpio_port监听目标2触发类型gpio_triggerEDGE_TRIGGER/LEVEL_TRIGGER3防抖DEBOUNCE_DIS关闭防抖机械触点建议开启4边沿方向EDGE_BOTH双沿触发5上下拉gpio_pull避免悬空误触发6回调demo_gpio_cb中断服务入口7回调参数(void *)s_gpio_port整型强转为指针回调内还原以上几点规整下就可以做一个简单的GPIO demo。要点二UART 初始化职责打开串口并注册数据到达回调。yopen_uart_config_s uart_config{0};uart_config.baudrateYOPEN_UART_BAUD_115200;uart_config.data_bitYOPEN_UART_DATABIT_8;uart_config.stop_bitYOPEN_UART_STOP_1;uart_config.parity_bitYOPEN_UART_PARITY_NONE;uart_config.flow_ctrlYOPEN_FC_NONE;uart_config.isPrintfPortTRUE;switch(s_uart_port){caseYOPEN_UART_PORT_0:yopen_pin_set_func(YOPEN_PIN_GPIO6,1);// TXyopen_pin_set_func(YOPEN_PIN_GPIO7,1);// RXbreak;}yopen_uart_close(s_uart_port);yopen_uart_set_dcbconfig(s_uart_port,uart_config);yopen_uart_open(s_uart_port);yopen_uart_register_cb(s_uart_port,_uart_notify_cb);步骤操作1填充配置结构体115200-8-N-1无流控2配置引脚复用为 UART 功能3close→set_dcbconfig→open4注册回调_uart_notify_cb易错点步骤 2 串口复用不可省略。引脚复位后默认功能非 UART配置缺失则无法通信后续遇到问题可以排查。demo端口为YOPEN_USB_PORT_COM通过 USB 连接即可收发无需额外接线。两个接口均为非阻塞TX 缓冲 4096 字节RX 缓冲 512 字节。发送未完成时写接口返回YOPEN_UART_BUSY。要点三消息队列职责连接回调与主任务实现回调投递、任务处理。staticyopen_queue_ts_queue;yopen_rtos_queue_create(s_queue,sizeof(gpio_msg_s),50);// 创建yopen_rtos_queue_release(s_queue,sizeof(msg),(uint8*)msg,YOPEN_NO_WAIT);// 投递yopen_rtos_queue_wait(s_queue,(uint8*)msg,sizeof(msg),YOPEN_WAIT_FOREVER);// 等待接口参数要点上下文约束create句柄 / 单条长度 4 字节 / 深度 50任务初始化阶段releaseYOPEN_NO_WAIT回调需要设置非阻塞回调 / 中断 / 任务waitYOPEN_WAIT_FOREVER仅任务上下文release使用 YOPEN_NO_WAIT 表示队列满时丢弃事件而非阻塞。在通知/中断上下文中阻塞会导致事件积压甚至系统异常因此该标志不可改为等待。拷贝语义release投递时按sizeof(msg)复制数据。投递方可以放心使用栈上的局部变量返回后不会产生悬空引用。要点四两个回调的消息分配职责确定每种事件由谁投递、投递哪条消息。这是整个 demo 的核心设计。三类消息、四个投递点消息类型投递者触发条件GPIO_MSG_UART_RECV_uart_notify_cb通知回调GPIO_MSG_GPIO_INITdemo_gpio_cb中断回调GPIO_MSG_GPIO_CTRL主任务自身任务设计方法两个回调只投外部事件任务自己投内部动作。由此形成两级处理外部事件GPIO_MSG_UART_RECVGPIO_MSG_GPIO_INITGPIO_MSG_GPIO_CTRL自投递·上电自检串口回调队列GPIO 中断回调主任务 while(1)第一级外部事件进入队列唤醒任务第二级任务处理后自行设置GPIO_MSG_GPIO_CTRL两处设置代码如下。while(1) 之前msgGPIO_MSG_GPIO_CTRL;yopen_rtos_queue_release(s_queue,sizeof(msg),(uint8*)msg,YOPEN_NO_WAIT);case GPIO_MSG_UART_RECV之后caseGPIO_MSG_UART_RECV:{...msgGPIO_MSG_GPIO_CTRL;yopen_rtos_queue_release(s_queue,sizeof(msg),(uint8*)msg,YOPEN_NO_WAIT);}串口回调要怎么写ind_type是什么怎么要单个case这么个东西staticvoid_uart_notify_cb(uint32 ind_type,yopen_uart_port_number_e port,uint32 size){gpio_msg_s msg;// ① 栈上分配switch(ind_type){caseYOPEN_UART_RX_RECV_DATA_IND:{msgGPIO_MSG_UART_RECV;// ② 赋值yopen_rtos_queue_release(s_queue,sizeof(msg),(uint8*)msg,YOPEN_NO_WAIT);break;// ③ 投递即返回不做其他操作}default:break;}}ind_type是什么它是 UART 回调的事件类型编号demo 做的是AT 指令下发只关心有没有数据可读这一件事假设去掉它、直接投递 GPIO_MSG_UART_RECV那么任何事件都会唤醒任务去读串口——包括 TX 完成事件。而 demo 每回一条 \r\nOK\r\n 都会产生一次 TX 事件任务于是被反复误唤醒读到空数据空转。还有以下哪些事件中断回调的额外动作staticvoiddemo_gpio_cb(void*param){intgpio(int)param;gpio_msg_s msg;yopen_int_disable(gpio);// 先关中断msgGPIO_MSG_GPIO_INIT;yopen_rtos_queue_release(s_queue,sizeof(msg),(uint8*)msg,YOPEN_NO_WAIT);}与串口回调相比多一步yopen_int_disable()中断上下文须尽快返回电平触发模式下电平有效期间中断会持续产生不关闭将快速填满队列深度仅 50。对应的重新使能位于任务侧二者配对caseGPIO_MSG_GPIO_INIT:{yopen_rtos_task_sleep_ms(50);// 等待电平稳定yopen_gpio_get_level(s_gpio_port,gpio_lvl);yopen_int_enable(s_gpio_port);// 恢复监听/* 串口上报 */}要点五AT 指令识别5.1 指令格式ATGPIOport,mode[,level] port : 0 ~ 38 mode : 0 输出 / 1 输入·边沿中断 / 2 输入·电平中断 / 3 读取电平 level: 输出 1 高 / 0 低 输入 1 上拉 / 0 下拉 / 缺省 高阻响应\r\nOK\r\n、\r\nERROR\r\n、\r\nGPIO LVL n\r\n、\r\nGPIO INT n\r\n。5.2parse_at_command解析函数参数解析staticintparse_at_command(constchar*input,char*command,int*param1,int*param2,int*param3);参数方向说明input入待解析字符串如ATGPIO2,0,1command出命令名缓冲区返回ATGPIOparam1出端口号param2出模式param3出电平 / 上下拉未提供时为0xff返回值—0成功-1格式非法完整源码demo_gpio.c:60-124strchr —— 找字符strncpy —— 拷贝 n 个字符atoi —— 文本转整数staticintparse_at_command(constchar*input,char*command,int*param1,int*param2,int*param3){// 检查输入是否为空if(inputNULL||commandNULL||param1NULL||param2NULL){DEMO_GPIO_TRACE(Invalid input or output parameters.\n);return-1;}// 初始化变量*param1-1;*param2-1;command[0]\0;// 找到等号位置constchar*equal_signstrchr(input,);if(equal_signNULL){DEMO_GPIO_TRACE(Invalid AT command format. No found.\n);return-1;}// 提取命令部分intcommand_lengthequal_sign-input;if(command_length0){strncpy(command,input,command_length);command[command_length]\0;// 确保字符串以null结尾}else{DEMO_GPIO_TRACE(Invalid command length.\n);return-1;}// 提取参数部分constchar*params_startequal_sign1;constchar*commastrchr(params_start,,);if(commaNULL){DEMO_GPIO_TRACE(Invalid parameter format. No , found.\n);return-1;}// 提取第一个参数intparam1_lengthcomma-params_start;charparam1_str[param1_length1];strncpy(param1_str,params_start,param1_length);param1_str[param1_length]\0;*param1atoi(param1_str);// 提取第二个参数constchar*param2_startcomma1;*param2atoi(param2_start);commastrchr(param2_start,,);if(comma){constchar*param3_startcomma1;*param3atoi(param3_start);}else{*param30xff;}return0;}逐步走查输入ATGPIO2,0,1字符串索引与关键指针位置0 1 2 3 4 5 6 7 8 9 10 11 12 A T G P I O 2 , 0 , 1 ↑ ↑ ↑ │ │ └─ 第二个逗号comma2 │ └────── 第一个逗号comma └────────── 等号equal_signstrchr 找到 ‘’ 第 7 个字符指针相减 7 - 0 7 command_length 7strncpy 拷 7 个字符 command “ATGPIO”手动补第 8 字节 command “ATGPIO\0”指针 1 跳过 ‘’ 从 ‘2’ 开始atoi(“2,0,1”) 遇 ‘,’ 停 param1 2 param2 0 param3 1解析结果对照输入commandparam1param2param3返回ATGPIO2,0,1ATGPIO2010四个关键点①-1哨兵与主任务判断形成闭环*param1-1;// 函数开头预设为无效值解析失败提前返回时param1保持-1。主任务中的if (gpio ! -1)判断正依赖该约定——-1在全流程中一致表示无有效端口。② 指针相减求长度intcommand_lengthequal_sign-input;两个char *相减得到的是元素个数而非字节数。对char恰好等价若换成wchar_t *结果须再乘sizeof(wchar_t)。③strncpy之后必须手动补结束符strncpy只拷贝指定字节数不保证写入\0。函数内两处均手动补齐command[command_length]\0;param1_str[param1_length]\0;5.3 模式映射param2param3s_gpio_mode0输出非 0 /0..._LVL_HIGH/..._LVL_LOW1边沿中断1/0xff/0PULL_UP/PULL_NONE/PULL_DOWN2电平中断1/0xff/0PULL_UP/PULL_NONE/PULL_DOWN3读电平—不进入配置流程三个消息消息投递者消费分支后续动作GPIO_MSG_UART_RECV串口回调GPIO_MSG_UART_RECV读数据 → 解析 → 投递CTRLGPIO_MSG_GPIO_INITGPIO 中断回调GPIO_MSG_GPIO_INIT读电平 → 重新使能中断 → 串口上报GPIO_MSG_GPIO_CTRL主任务两处GPIO_MSG_GPIO_CTRLdeinit → 引脚复用 → GPIO 初始化 → 回响应三个要点#做法理由①局部变量非动态分配回调中不允许malloc可能阻塞、内存碎片②投递前立即赋值队列按值拷贝无需长期持有③投递后立即返回通知/中断上下文不可执行耗时操作