Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
RT-Thread一般讨论
关于串口驱动框架的一些建议
发布于 2010-01-15 15:18:53 浏览:10872
订阅该版
在 serial.c 的 rt_serial_write 中 ```c /* DMA mode Tx */ /* allocate a data node */ struct stm32_serial_data_node* data_node = (struct stm32_serial_data_node*) rt_malloc (sizeof(struct stm32_serial_data_node)); if (data_node == RT_NULL) { /* set error code */ err_code = -RT_ENOMEM; } ``` 只看到rt_malloc 申请内存块,没有看到释放内存块,时间长了就申请不到内存了。好象以前看到过这个问题,应该被修正了,但今天重新check out代码,还是这样的,不知道何故。 另外,建议将串口封装得更加方便些,可不可以这样做: ```c #ifdef RT_USING_UART1 #if (UART1_RX_MODE == INT) struct stm32_serial_int_rx uart1_int_rx; #endif #if (UART1_RX_MODE == DMA) struct stm32_serial_dma_rx uart1_dma_rx; #endif #if (UART1_TX_MODE == INT) struct stm32_serial_int_tx uart1_int_tx; #endif #if (UART1_TX_MODE == DMA) struct stm32_serial_dma_tx uart1_dma_tx; #endif struct stm32_serial_device uart1 = { USART1, #if (UART1_RX_MODE==INT) &uart1_int_rx, #else RT_NULL, #endif #if (UART1_RX_MODE==DMA) &uart1_dma_rx, #else RT_NULL, #endif #if (UART1_TX_INT==INT) &uart1_int_tx, #else RT_NULL, #endif #if (UART1_TX_DMA==DMA) &uart1_dma_tx #else RT_NULL #endif }; struct rt_device uart1_device; #endif ``` 可以将所有串口的参数定义一个二维数组,初始化采用条件编译,不会增加空间,用户也可以不用管细节,直接定义一下,就能初始化。 当然工作量有些大,毕竟`uart.c uart.h serial.c serial.h stm32f10x_it.c`这些都需要改动。 但我想单片机用得最多的外设还是串口,这样能减少系统出错的可能性,用户方便,才是好推广的条件之一。
查看更多
12
个回答
默认排序
按发布时间排序
bernard
2010-01-16
这家伙很懒,什么也没写!
好啊,你做一个版本出来看看吧。 目前对串口设备实现确实也不太满意,因为开始时我也没想到在真实使用中串口会应用这么广泛,而目前的STM32实现其实是比较蹩脚的。(设备框架的设计还比较中规中矩,只是到了串口上。。。)
coldfish
2010-01-16
这家伙很懒,什么也没写!
版主提要求啦,可我是业余的,怕是心有余而力不足啊。 以前做的东西都是裸奔,这个东西怕是不知道什么时候才能完成。为RTT做一份贡献,尽力而为吧。 [s:182]
coldfish
2010-01-20
这家伙很懒,什么也没写!
抽空把serial device改了一下,将中断发送、接收缓冲改为动态申请,缓冲大小在rtconfig.h中定义 同时将接收、发送模式定义也放到rtconfig.h中,采用条件编译。 例如在rtconfig.h中 ```c #define RT_USING_UART1 #define UART1_BAUDRATE 115200 #define UART1_TX_MODE INT #define UART1_RX_MODE POLL #define UART1_RX_BUF_SIZE 512 #define UART1_TX_BUF_SIZE 128 #define RT_USING_UART2 #define UART2_BAUDRATE 9600 #define UART2_TX_MODE INT #define UART2_RX_MODE POLL #define UART2_RX_BUF_SIZE 256 #define UART2_TX_BUF_SIZE 128 #define RT_USING_UART3 #define UART3_BAUDRATE 115200 #define UART3_TX_MODE INT #define UART3_RX_MODE POLL #define UART3_RX_BUF_SIZE 512 #define UART3_TX_BUF_SIZE 128 ``` 在serial.c中设备初始化修改为 ```c rt_err_t rt_hw_serial_register(rt_device_t device, const char* name, rt_uint32_t flag, struct stm32_serial_device *serial,rt_uint16_t rx_bufsize,rt_uint16_t tx_bufsize) { RT_ASSERT(device != RT_NULL); rt_uint8_t* uart1_rx_buf = rt_malloc (rx_bufsize);//仅为测试,未用内存申请检查 rt_uint8_t* uart1_tx_buf = rt_malloc (tx_bufsize); serial->int_rx->rx_buffer=uart1_rx_buf; serial->int_rx->rx_buffer_size=rx_bufsize; serial->int_tx->tx_buffer=uart1_tx_buf; serial->int_tx->tx_buffer_size=tx_bufsize; device->type = RT_Device_Class_Char; device->rx_indicate = RT_NULL; device->tx_complete = RT_NULL; device->init = rt_serial_init; device->open = rt_serial_open; device->close = rt_serial_close; device->read = rt_serial_read; device->write = rt_serial_write; device->control = rt_serial_control; device->private = serial; /* register a character device */ return rt_device_register(device, name, RT_DEVICE_FLAG_RDWR | flag); } ``` 在stm32f10x_it.c中对中断也进行了条件编译 例如: ```c void USART2_IRQHandler(void) { #ifdef RT_USING_UART2 extern struct rt_device uart2_device; extern void rt_hw_serial_isr(struct rt_device *device); /* enter interrupt */ rt_interrupt_enter(); rt_hw_serial_isr(&uart2_device); /* leave interrupt */ rt_interrupt_leave(); #endif } ``` 现在DMA还没考虑好,因为一个DMA通道有几个设备共用,需要好好规划一下,看用什么方式比较好。 请bernard 提些意见和建议
coldfish
2010-01-20
这家伙很懒,什么也没写!
可能在设备打开时进行缓冲区检查,没有申请就申请, 在设备关闭时释放内存,这样比较好 对了,关于串口设备的int_rx,int_tx也重新定义了 ```c struct stm32_serial_int_rx { rt_uint8_t *rx_buffer; rt_uint16_t rx_buffer_size; rt_uint32_t read_index, save_index; }; struct stm32_serial_int_tx { rt_uint8_t *tx_buffer; rt_uint16_t tx_buffer_size; rt_uint32_t write_index, save_index; }; ```
coldfish
2010-01-21
这家伙很懒,什么也没写!
很奇怪,在编译中选择优化为低,长时间后会死,而且不出现RTT的错误信息(没有SP什么的寄存器值打印出来),中断后能继续执行,但单步不知道会走到什么地方,经常执行到USART_GetITStatus里面。是不是系统负荷太重? 在编译优化选择为最高时,好象正常(已经测试了2个小时) 测试程序是创建两个线程,一个为GPS,一个为GSM,分别从usart1和usart2读数据,然后通过usart3(rt_kpringf())输出,对于usart3用了信号量进行访问互诉。 在NVIC中现在设置为: ```c static void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; /* Configure the NVIC Preemption Priority Bits */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); #ifdef RT_USING_UART1 /* Enable the USART1 Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = uart1_conf.INT_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); #endif #ifdef RT_USING_UART2 /* Enable the USART2 Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = uart2_conf.INT_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); #endif #ifdef RT_USING_UART3 /* Enable the USART3 Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = uart3_conf.INT_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); #endif } ``` 自己想不明白,请版主指点 gsm.c为: ```c #include "wchar.h" #include "gsm.h" struct rt_thread gsm_thread; static char gsm_thread_stack[1024]; rt_device_t gsm_device; extern struct rt_semaphore uart3_sem; struct rt_semaphore gsm_sem; static rt_err_t gsm_rx_ind(rt_device_t dev, rt_size_t size) { /* release semaphore to let gps thread rx data */ rt_sem_release(&gsm_sem); return RT_EOK; } void gsm_set_device(char* device_name) { rt_device_t dev = RT_NULL; dev = rt_device_find(device_name); if (dev != RT_NULL && rt_device_open(dev, RT_DEVICE_OFLAG_RDWR) == RT_EOK) { if (gsm_device != RT_NULL) { /* close old finsh device */ rt_device_close(gsm_device); } rt_device_set_rx_indicate(dev, gsm_rx_ind); gsm_device = dev; } else { rt_kprintf("Can not find GSM device:%s ", device_name); } } void gsm_buf_clear(void) { char ch; while(rt_sem_take(&gsm_sem,0)==RT_EOK){}; while(1==rt_device_read(gsm_device,0,&ch,1)){}; } rt_err_t at_cmd(char * cmd,char * resultchar,int size,int waittime) { unsigned char len; char *ptr; char ch; ptr=resultchar; rt_memset(resultchar,0,size); len=rt_strlen(cmd); gsm_buf_clear(); rt_device_write(gsm_device,0,cmd,len); rt_thread_delay(waittime); while(rt_sem_take(&gsm_sem, waittime) == RT_EOK) { if(1==rt_device_read(gsm_device, 0, &ch,1)) *ptr++=ch; } return (ptr-resultchar); } rt_bool_t at_test(void) { rt_err_t len; char buf[16]; rt_device_write(gsm_device,0,"AT ",3); rt_thread_delay(20); rt_memset(buf,0,16); len=rt_device_read(gsm_device, 0, &buf[0],16); if ((len>5)&&(rt_strstr(buf,"OK "))) return RT_TRUE; else return RT_FALSE; } void gsm_thread_entry(void* parameter) { // int pos ; static rt_uint16_t cont=0; static char buf[512]; // rt_thread_delay(1500); gsm_set_device("gsm"); // while (RT_TRUE!=at_test()); while (1) { at_cmd("AT+CMGF=0 ",buf,sizeof(buf),20); if (rt_sem_take(&uart3_sem, -1) == RT_EOK) { rt_kprintf("%s ",&buf[0]); rt_sem_release(&uart3_sem); } at_cmd("AT+CMGR=8 ",buf,sizeof(buf),20); if (rt_sem_take(&uart3_sem, -1) == RT_EOK) { rt_kprintf("%s ",&buf[0]); rt_kprintf("COUNTER=>> %d ",cont++); rt_sem_release(&uart3_sem); } // rt_thread_delay(100); } } void rt_gsm_thread_init(void) { rt_sem_init(&gsm_sem, "gsm_sem", 0, 0); rt_thread_init(&gsm_thread, "gsm", gsm_thread_entry, RT_NULL, &gsm_thread_stack[0], sizeof(gsm_thread_stack), 21, 100); rt_thread_startup(&gsm_thread); } ``` gps.c为: ```c #include
#include
#include "gps.h" enum gps_stat { WAIT_DOLLAR, WAIT_G, WAIT_P, WAIT_R, WAIT_M, WAIT_C, WAIT_Data, }; struct POI { double LON; double LAT; }; struct POI LastP,CurP; /* gps thread */ struct rt_thread gps_thread; char gps_thread_stack[512]; char GPSBuf[128]; struct rt_semaphore gps_sem; extern struct rt_semaphore uart3_sem; rt_device_t gps_device; static rt_err_t gps_rx_ind(rt_device_t dev, rt_size_t size) { /* release semaphore to let gps thread rx data */ rt_sem_release(&gps_sem); return RT_EOK; } void gps_set_device(char* device_name) { rt_device_t dev = RT_NULL; dev = rt_device_find(device_name); if (dev != RT_NULL && rt_device_open(dev, RT_DEVICE_OFLAG_RDWR) == RT_EOK) { if (gps_device != RT_NULL) { /* close old finsh device */ rt_device_close(gps_device); } gps_device = dev; rt_device_set_rx_indicate(dev, gps_rx_ind); } else { rt_kprintf("Can not find GPS device:%s ", device_name); } } void gps_thread_entry(void* parameter) { static char line[256]; int pos ; enum gps_stat gpsstat; gpsstat = WAIT_DOLLAR; gps_set_device("gps"); while (1) { pos = 0; while (1) { if (rt_sem_take(&gps_sem, -1) == RT_EOK) { /* read one character from serial */ char ch; rt_err_t rx_result; rx_result = rt_device_read(gps_device, 0, &ch, 1); if (ch != 0 && rx_result == 1) { if (gpsstat == WAIT_DOLLAR) { if (ch == '$') { gpsstat = WAIT_G; continue; } } if (gpsstat == WAIT_G) { if (ch == 'G') { gpsstat = WAIT_P; continue; } else gpsstat = WAIT_DOLLAR; } if (gpsstat == WAIT_P) { if (ch == 'P') { gpsstat = WAIT_R; continue; } else gpsstat = WAIT_DOLLAR; } if (gpsstat == WAIT_R) { if (ch == 'R') { gpsstat = WAIT_M; continue; } else gpsstat = WAIT_DOLLAR; } if (gpsstat == WAIT_M) { if (ch == 'M') { gpsstat = WAIT_Data; continue; } else gpsstat = WAIT_DOLLAR; } if (gpsstat == WAIT_Data) { if (ch != '*') { line[pos++]=ch; continue; } else { line[pos]=0; rt_memcpy(GPSBuf,line,rt_strlen(line)); if (1==DecodeGPS()) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)((1-GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_8)))); if ((rt_strlen(GPSBuf)>10)&&(rt_sem_take(&uart3_sem, -1) == RT_EOK)) { rt_kprintf("%s ", GPSBuf); rt_sem_release(&uart3_sem); } pos=0; gpsstat = WAIT_DOLLAR; } } } } } } } void rt_gps_thread_init(void) { rt_sem_init(&gps_sem, "gps_sem", 0, 0); // rt_sem_init(&gps_buf, "gps_buf", 0, 0); rt_thread_init(&gps_thread, "gps", gps_thread_entry, RT_NULL, &gps_thread_stack[0], sizeof(gps_thread_stack), 30, 100); rt_thread_startup(&gps_thread); } double DFM2D(double D,double F,double M) { return (D+F/60+M/3600); } u8 CheckNumber(char * txt)//正确返回0 { u8 i,j; i=rt_strlen(txt); if (i==0) return 1; for (j=0;j
0x39)||(txt[j]<0x2E)) return 1; return 0; } char DecodeGPS(void) { //$GPRMC,121252.000,A,3958.3032,N,11629.6046,E,15.15,359.95,070306,,,A*54 //C,152757.067,A,3202.3859,N,11844.0654,E,0.00,358.24,100809,,,A*6F //0123456789012345678901234567890123456789012345678901234567890123456789 //0 1 2 3 4 5 6 struct GPRMC { u8 PosE[15]; // u8 PosEF[2]; u8 PosN[15]; // u8 PosNF[2]; u8 PosDate[15]; u8 PosTime[15]; // u8 PosV[15]; // u8 PosR[150]; // u8 PosA[2]; }; struct GPRMC rmc; u8 i,j; int E,N; int Day,Month,Year; int Hour,Minute,Second; float Ed,Em,Es; float Nd,Nm,Ns; u8 DayOfMonth[12]={31,28,31,30,31,30,31,31,30,31,30,31}; rt_memset(&rmc,0,sizeof(rmc)); for (j=0,i=2;GPSBuf[i]!=',';i++) rmc.PosTime[j++]=GPSBuf[i]; for (j=0,i++;GPSBuf[i]!=',';i++) ;//rmc.PosA[j++]=GPSBuf[i]; for (j=0,i++;GPSBuf[i]!=',';i++) rmc.PosN[j++]=GPSBuf[i]; for (j=0,i++;GPSBuf[i]!=',';i++) ;//rmc.PosNF[j++]=GPSBuf[i]; for (j=0,i++;GPSBuf[i]!=',';i++) rmc.PosE[j++]=GPSBuf[i]; for (j=0,i++;GPSBuf[i]!=',';i++) ;//rmc.PosEF[j++]=GPSBuf[i]; for (j=0,i++;GPSBuf[i]!=',';i++) ;//rmc.PosV[j++]=GPSBuf[i]; for (j=0,i++;GPSBuf[i]!=',';i++) ;//rmc.PosR[j++]=GPSBuf[i]; for (j=0,i++;GPSBuf[i]!=',';i++) rmc.PosDate[j++]=GPSBuf[i]; // GPSCounter=0; // if (CheckNumber((char*)rmc.PosDate)||CheckNumber((char*)rmc.PosTime)||CheckNumber((char*)rmc.PosE)||CheckNumber((char*)rmc.PosN)) // return 0; // sscanf((char*)rmc.PosDate, "%02d%02d%02d", &Day,&Month,&Year); // sscanf((char*)rmc.PosTime, "%02d%02d%02d", &Hour,&Minute,&Second); // sscanf((char*)rmc.PosE, "%3d%2d.%4d", &Ed,&Em,&E); // sscanf((char*)rmc.PosN, "%2d%2d.%4d", &Nd,&Nm,&N); // Es=0.006*(double)E+0.005; // Ns=0.006*(double)N+0.005; Hour+=8;Year+=2000; if (Hour>23)//TimeZone+8 { Hour-=24;Day++; if (Year%400==0||(Year%4==0&&Year%100!=0)) //闰年2月为29日 DayOfMonth[1]=29; if (Day>DayOfMonth[Month-1]) { Day=1;Month++; } if (Month>12) Year++; } // CurP.LAT=DFM2D(Ed,Em,Es); // CurP.LON=DFM2D(Nd,Nm,Ns); if (GPSBuf[13]!='A') return 0; return 1; } //返回当前和上次保存两点间的距离,单位米 double GetDistance(void) { const double Pi = 3.1415926535898; const double Dt = 111199.233; double a,b,s; a=(LastP.LAT-CurP.LAT)*Dt; b=(LastP.LON-CurP.LON)*Dt*cos((LastP.LAT-CurP.LAT)*Pi/180); s=sqrt(a*a+b*b); return s; } void SaveCurPOI(void) { LastP.LAT=CurP.LAT; LastP.LON=CurP.LON; } /*@}*/ ```
bernard
2010-01-21
这家伙很懒,什么也没写!
串口这块,我原来把它分离成serial.c和usart.c,是打算把一些公共的东西方放到serial.c中(这个是在`libcpu\arm\stm32`目录下),而和硬件初始化相关的放到usart.c(`bsp\stm3110`目录下)中。 只是解决得不是很好,可能有些方面还需要考虑考虑,实际上主要是从灵活性上考虑的,如果只有一种模式那么怎么做都好。 你说的这个问题,很可能在中断处理中转不出来了,你可以用仿真器带一带,当出问题时把它停下来然后看看大致是哪个位置。另外注意一个地方, ```c /* Configure the NVIC Preemption Priority Bits */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); ``` 这个是设置中断的优先级位多少,每设置一次就会把系统中中断优先级位方式重新配置一次,所以最好整个系统中只有一个地方去配置这个(可能原来STM32分支0.3.0 rc1分支也有这个问题),不要每用一次中断都配置下优先级组。
coldfish
2010-01-21
这家伙很懒,什么也没写!
我现在的想法是将串口的一些基本操作在usart中实现,比如将rt_hw_usart_init()改为初始化一个串口硬件,包括管脚定义,发送/接收模式,发送/接收缓冲区大小等。重新定义一个rt_hw_usart_init_all()用来初始化所用串口。 现在的结构和你原先的有些不一样。每个串口可以定义不同的接收/送发缓冲区大小,在设备注册时指定大小,设备打开时申请缓冲区、打开中断,设备关闭时释放缓冲区、关闭中断。这样灵活些,因为实际使用时,可能有的串口只是小量数据,而有的串口需要大量的数据,将缓冲区定义成一样大,有些浪费(单片机RAM是最珍贵资源)。 关于DMA传输,现在还没有想好,打算也用动态申请的办法。 关于NVIC优先级分组的问题,是刚开始时老是跑跑就死,所以调整一下优先级试试的,后来还是改了编译优化级别才正常的。回头再改回原来的测试下(顺便问一下,现有的系统调度支持抢占优先级吗)。 其他的芯片不太了解,版主认为将usart.c和serial.c分开来,这个serial.c的重用性如何?如果不是很高的话,就没有多大必要了,就针对不同的平台采用不同的serial好了。 看了一下,版主的`rt_hw_serial_isr()`好象不可重入吧,请明确一下 因为如果3个串口同时有大量的数据的话,可重入的话就不怕了。我猜测我上面碰到的问题可能是数据量太大了 (`usart1,115200;usart2,9600;usart3,115200`),而且三个口一直处于收发状态,可能串口这方面的负荷太重了。
heky
2010-02-02
这家伙很懒,什么也没写!
cold_fish 遇到的问题可能是串口没有及时接收。 stm32在串口收到数据后,如果在下个数据来之前没有及时从寄存器中收取数据的话,可能会导致这样的问题出现。所以我觉得rtt在处理stm32的串口时,应该给每个串口分配静态缓冲区。硬件中断接收,dma发送。
bernard
2010-02-03
这家伙很懒,什么也没写!
嗯,或许开始的时候RTT在串口上就走入了一个误区,视图把它设计成最通用的那种,但现实是串口是最不通用的东西。 例如,今天一位网友说,STM32F103ZE能否同时支持多个(3个?)串口115200波特率接收,如果是中断模式,又担心它会不会因为性能不够而丢包。这些花样太多了
danju
2010-02-03
这家伙很懒,什么也没写!
这个要支持,STM32有5个串口呢,要是都能用上,那么很多传统的仪器设备接口都可以方便地接入网络了
撰写答案
登录
注册新账号
关注者
0
被浏览
10.9k
关于作者
coldfish
这家伙很懒,什么也没写!
提问
3
回答
17
被采纳
0
关注TA
发私信
相关问题
1
有关动态模块加载的一篇论文
2
最近的调程序总结
3
晕掉了,这么久都不见layer2的踪影啊
4
继续K9ii的历程
5
[GUI相关] FreeType 2
6
[GUI相关]嵌入式系统中文输入法的设计
7
20081101 RT-Thread开发者聚会总结
8
嵌入式系统基础
9
linux2.4.19在at91rm9200 上的寄存器设置
10
[转]基于嵌入式Linux的通用触摸屏校准程序
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
最新文章
1
【24嵌入式设计大赛】基于RT-Thread星火一号的智慧家居系统
2
RT-Thread EtherKit开源以太网硬件正式发布
3
如何在master上的BSP中添加配置yml文件
4
使用百度AI助手辅助编写一个rt-thread下的ONVIF设备发现功能的功能代码
5
RT-Thread 发布 EtherKit开源以太网硬件!
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
MicroPython
ulog
C++_cpp
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
a1012112796
16
个答案
2
次被采纳
张世争
9
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
13
个答案
1
次被采纳
本月文章贡献
程序员阿伟
9
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
5
次点赞
RTT_逍遥
1
篇文章
2
次点赞
ThinkCode
1
篇文章
1
次点赞
回到
顶部
发布
问题
分享
好友
手机
浏览
扫码手机浏览
投诉
建议
回到
底部