搜索
bottom↓
回复: 727

STM32串口驱动(拼音检索测试通过)(环形队列+内存动态分配+DMA)(申请加酷,让更多的人

  [复制链接]

出30入0汤圆

发表于 2011-1-16 15:34:59 | 显示全部楼层 |阅读模式
串口设备驱动接口
STM32的2.0固件库的工程文档ourdev_611401K0IJZU.rar(文件大小:227K) (原文件名:串口发送模板(第二版).rar)
STM32的3.0固件库的工程文档ourdev_611402L6BK0Z.rar(文件大小:801K) (原文件名:串口发送模板(第三版).rar)

在设计串口驱动的过程中,要遵循的两条准则是:
1:尽量的减少程序运行的时间。
2:尽量的减少程序所占用的内存。
譬如,下面的一段程序:
程序段1-1
/*指针是指向ptr,需要发送count个数据*/
void  USART1WriteDataToBuffer(*ptr,u8 count)
{
    /*判断数据是否发送完毕*/
    while(count--)
    {
        /*发送数据*/
        USART1SendByte(*ptr++);
        /*等待这个数据发送完毕,然后进入下一个数据的发送过程*/
        while(USART_GetFlagStatus(USART1,USART_FLAG_TC);
    }
    /*数据发送完毕,返回*/
}
很明显,这段程序在实际应用中将会产生灾难性的后果,首先,当发送数据送到发送寄存器启动发送以后,CPU就一直在等待这个数据发送完成,然后进入下一个数据的发送,这样,直到所有要发送的数据完成,CPU才能做其他的事情。相对于CPU内核运行的速度而言,串口外设的运行速度是非常快的,让一个速度非常快的设备去等待相对很慢的设备,程序的效率是非常低下的。
所以必须采用中断的方式发送数据。
程序段1-2
/*将数据写入发送缓冲区*/
void  USART1WriteDataToBuffer(*ptr,u8 count)
{
    while(count!='\0')                                                                                 
    {
        USART1SendTCB[Index++]=*ptr++;
        Count=count;
    }
    /......判断溢出等其他代码省略...../
}
/......发送中断的ISR...../
void USART1SendUpdate(void)
{       
    /......判断发送缓冲区中的数据是否发送完毕...../
    /将发送缓冲区的数据发送出去/
    USART1SendByte(*ptr++);
    /......发送指针加一,待发送的字节数减一等代码...../
}
    这样,当调用USART1WriteDataToBuffer函数将待发送的数据写入发送缓冲区以后,CPU就可以执行其他的任务,待一个数据发送完成以后,中断ISR就会触发,在中断服务程序里面将下一个数据写入发送寄存器,启动下一次发送,知道完全发送完毕为止。
    很明显,上述的程序的设计比较好,不用占用过多的CPU时间。
    在实际的工程应用中,经常会出现类似这种情况:串口显示屏需要显示1000个点,通过串口发送这1000个点的颜色的RGB亮度值。将这1000个数据写入发送缓冲区以后,启动发送。在115200的波特率,一位起始位,一位停止位,无校验位的情况下,至少需要(10*1000*2)/115200=0.1736秒,在这期间以内,时钟更新了,需要再发送给串口一串时间更新的数据,这个数据大约有100个,这样这串数据需要写入到发送缓冲区的发送字节的后面。
同样道理,在这个时候如果有显示任务更新的话,将会有其他的数据写入到发送缓冲区。

串口1 (原文件名:串口1.JPG)

    从图上可以看出,程序段1-2虽然满足了时间上的要求,却没有满足空间上的要求,它的数据缓冲区是单向的,这样,当发送缓冲区的所有的数据全部发送完毕后,或者当发送缓冲区撑满了以后才能将发送缓冲区内的数据清空,以便装入下次的缓冲数据。这样内存较小的嵌入式系统来说是不能容忍的。
因此,可以将发送缓冲区建立成一个环形的缓冲区,在这个环形缓冲区内,通过头指针(HostIndex)和尾指针(HostIndex)来定位空白区和数据区。
(1):头指针(HostIndex)指向有数据区的顶部,每次写入数据,都更新头指针,如果到了缓冲区的末端(EndIndex),就自动返回到缓冲区的起始处(StartIndex),直到写入到尾指针处为止,这时缓冲区已经被装满,不能再装入数据。
(2):尾指(TailIndex)针指向有数据区的尾部,当数据发送完毕后,更新尾指针的位置,如果到了缓冲区的末端(EndIndex),就自动返回到缓冲区的起始处(StartIndex),直到遇到头指针为止,这是证明所有的数据已经发送完毕。

串口2 (原文件名:串口2.JPG)

    这样就实现了发送缓冲区的动态调整空白区和数据区,刚刚发送完毕的数据,马上就被开辟出来用于存放下个数据,最大可能的节省了宝贵的发送缓冲区的空间,提高了使用效率。
    这个程序比较复杂,大致的流程如下(省略了状态的判定,保护措施等代码)
程序段1-3
/*将数据写入发送缓冲区*/
void  USART1WriteDataToBuffer(*ptr,u8 count)
{
     while(count!='\0')                                                                                 
    {
        /*头指针不等于尾指针,缓冲区没有撑满*/
        if(USART1HosIndext!=USART1TailIndex){
        USART1SendTCB[USART1HosIndex]=*ptr++;
        /*更新头指针,如果到了缓冲区的末端,就自动返回到缓冲区的起始处*/
        if(++USART1HosIndext>=USART1_SEND_MAX_BOX)USART1HosIndext=0;}
    }
    /......判断溢出等其他代码省略...../
}
/......发送中断的ISR...../
void USART1SendUpdate(void)
{       
    /*头指针不等于尾指针,缓冲区尚有未发生完的数据*/
    if(USART1HosIndext!=USART1TailIndex){
    /*将发送缓冲区的数据发送出去*/
    USART1SendByte(*USART1TailIndex);
    /*更新尾指针的位置,如果到了缓冲区的末端,就自动返回到缓冲区的起始处*/
    if(++USART1TailIndex>=USART1_SEND_MAX_BOX)USART1TailIndex=0;
    /......判断溢出等其他代码省略...../
}
    值得注意的是,一些微控制器中,例如在Cortex-M3的微控制器架构中,有DMA传送模式,可以配置一个内部的通道,将指定的地址处的数据,在无须CPU的管理下,直接将其发送到串口发送寄存器里去。通过这个方法,可以大大的降低了发送过程中重复进入中断的次数,从而大大提高了效率。这样,如果使用了这个芯片,就可以使用DMA模式进行发送。但是DMA发送模式下,对于头指针和尾指针就得做出一些修改,因为DMA传送过程中,是不能让头指针到达缓冲区终点后,自动将指针调整到起点位置的。
    但是,加入发送管理结构体以后,上述问题可以得到解决。
    利用内存块动态分配可以大大减少提高内存的使用效率,尤其是对于串口通信而言,更是如此。利用内存管理模块可以将微控制器除全局变量和静态结构变量以外的剩余的内存统一管理,在需要时候申请,在不用的时候释放,如串口的发送缓冲区,以太网,SD卡,外部数据存储器等等均可以用内存来管理,可以重复使用,大大提高了使用效率。
内存分配1 (原文件名:内存分配1.JPG) </center>

        首先定义发送缓冲区管理块的结构体
        typedef struct{
        unsigned char Num;        //该存储区保存的有效字节数量
        unsigned char *Index;        //该存储区申请的内存块的指针
        unsigned char *MemIndex;        //该存储区申请的内存块管理区的指针
        }USART1SendTcb;
        例如需要200字节:


串口3 (原文件名:串口3.JPG)

    这样,加入动态内存与发送缓冲区管理块以后,无论是采用DAM模式发送数据还是普通的方式,都可以轻易的配置。将内存块的指针值Index传给DMA的发送地址,将待发送的字节数Num传给DMA的发送字节计数寄存器,就可以完成无须CPU管理的操作,最大的减少了CPU的使用,大大提高了内存效率。或者采用普通的中断模式。
    具体的代码较长,见工程文件,不再详细列出。
       
                                                        内存管理
    在嵌入式设备中,往往会存在一些任务需要大量的内存,在内存相对较少的微控制器中,怎样有效管理这些宝贵的资源,是必须解决的一个重要问题。
    在上位机的编程中,我们通常使用malloc()函数以及Free()函数来完成对内存的管理。这是因为相对嵌入式系统而言,上位机的内存非常大,而且Windows提供了很好的内存管理接口,所以不存在问题。但是在嵌入式系统中,大量使用上述函数会出现两个问题:
    (1)产生内存碎片的问题。
    在运行的过程中,各个任务频繁的调用内存分配和释放,会导致原本一整块空间地址连续的区域分散成一堆物理地址上相互独立的区域,这样有可能导致一个程序需要一个较大的内存,空余的内存块没有一个连续的地址,无法分配给任务。久而久之,最后系统可能连一个很小的物理地址都分配不到,最后导致系统的崩溃。
如下图所示:

内存分配2 (原文件名:内存分配2.JPG)


    在上图中可以看到,虽然起始地址为20000的内存区有16个空白的字节,但是仍然无法为任务分配到四个字节的物理内存。
    (2)运行的时间不确定的问题
    在free()函数中,存在着一些内存合并等功能,例如将释放完成以后,将空间上相近的两个空白区域合并为同一个,将存在内存碎片的区域重新整合,甚至可能使用了二叉树等非线性数据结构,等等操作。
    而这些函数所耗费的时间是无法确定的,在实际的应用中,对于内存这种全局变量,多个任务都要用到,为避免会存在可重入性的问题,必须采用信号同步的方法,或者暂时关闭中断的方法,来同步对各个任务对共享资源的使用。这样,导致了系统死区时间的增加,响应速度的变慢,不确定性增加。
    因此,在大多数嵌入式系统中,通常采用静态内存块池的方法。将系统空余的内存统一管理,生成一系列的大小固定的内存块池,在实际的操作中,以这一整个内存块进行操作。
    实现过程
    首先定义内存管理块的结构体
         typedef struct OSMEMTCB{
                void                 *OSMemFreeList;//用于指向该管理区中的空白的内存块
                u8                         OSMemBlkSize;//用于该管理区中的每个内存块的字节数
                u8                         OSMemNBlks;//用于该管理区中的分为多少个内存块
                u8                         OSMemFreeNBlks;//用于该管理区还剩多少空白内存块

         }OSMEMTcb;
    将一个静态的存储区分配给内存配置函数,内存管理块的各个列表的含义如下图所示:

内存分配3 (原文件名:内存分配3.JPG)
    每个内存块的头四个字节用于存储下一个内存块的指针地址,直到倒数第一个为止,最后一个指针指向一个空的指针,表明已经到达内存区的的末端。
    在实际运用过程中,OSMemFreeList是指向空白的内存块的指针,通过它来申请内存,当申请到内存块以后,OSMemFreeList指向当前数据块的下一个内存块节点地址(内存管理函数已经自动将所有内存块通过指针链接成一个单向链表),当释放内存块的时候,将OSMemFreeList指向当前释放的内存块,将当前内存块的下一个内存块指针指向先前的OSMemFreeList。OSMemFreeNBlks保存着该内存区空白块的数量,若内存块已满,返回错误代码,OSMemBlkSize指的是每个内存块内字节数量,它的大小可以根据需要指定,理论上是它越小,内块的利用率就越高,例如保存一个101个字节的数据,若一个内存块的大小是10个字节,则需要11个内存块,若一个内存块的大小是100个字节,则需要2个内存块,最后一个内存块仅仅使用了一个字节。但并非内存块的越小越好,因为保存下个内存块节点的地址需要4个地址位,内存块越小,保存地址的数据所占比例越高。在实际操作32字节过程中,可以定义大小不同的内存块,灵活运用。

内存分配4 (原文件名:内存分配4.JPG)
   内存配置函数的核心代码:OSMemCreate(......)


内存分配5 (原文件名:内存分配5.JPG)


    for(i=0;i<nblks-1;i++)                               
    {
        plink=(void **)(link);                        //将二维指针定位到框的首位
        *plink=(void *)(link+blksize);        //该内存块的地址存放的
        //是第二片内存区的首地址
        link+=blksize;                                        //一维指针重新定位
    }
    //最后一个二维指针指向一个空指针

    获取内存块的核心代码:OSMemGet(......)
     tcb=(*ptr).OSMemFreeList;
    if((*ptr).OSMemFreeNBlks==0){return (void *)0;}//如果空白内存块的数量为                                                                            //返回,若正确返回,收到的数据应该是0
    (*ptr).OSMemFreeNBlks--;                        //空白内存块块数量减一
    //空白内存块指针指向下一个内存区
     //tcb指向的是内存块节点指针,不能直接使用,加上偏移值4个字节
    index=(u8 *)tcb;
    index+=4;
    //返回内存块指针
    return index;
       
    释放内存块的核心代码:OSMemDelete(......)
    (void **)tcb=(*ptr).OSMemFreeList;        //将OSMemFreeList重新指向这个已经变成空白了的指针
    (*ptr).OSMemFreeList=tcb;        //将这个空白的指针的下个指针指向原先的空白区指针
    (*ptr).OSMemFreeNBlks++;        //空白内存块数量加1

值得说明的是,工程文件中的OSQMem.h文件中
OS_MEM_MAX                    //最多允许的内存块管理区
OS_MEM_USART1_MAX     1024    //发送缓冲区的内存大小
OS_MEM_USART1_BLK     32      //每一个块的长度
       
而 USART.h文件中
DMA_MODE     //定义是采用DMA模式,还是普通的中断模式       
推荐是用DMA模式

再就是很多朋友可能觉得奇怪的是为什么一个是
USART1.c
USART1Cinfig.c
USART1.c是上层文件,与硬件无关,
USART1Cinfig.c是底层文件,与硬件相关,为了方便移植,只需改变USART1Cinfig.c的内容就可以,我只有STM32的板子,
Mega16的板子,和340的板子,都是我自己做的,这个程序经过移植到上述三个板子以后已经用在项目中了,在下是个菜鸟,
希望朋友们多多指教。
一直在这里学习到了很多东西,本人比较懒,老是索取而没有回报,希望能对初学的朋友们有用。
我的邮箱是linquan315@gmail.com欢迎朋友们多多交流。

2011年1月16日加上:
在补上几句话,告诉兄弟们怎么使用,
把工程文档的‘驱动’这个文件夹的内容加到你们的工程中就可以了,如果要使用USART2,3,直到5,
只需将USART1.c,和USART1Config.c中的‘USART1’直接全部替换成‘USART2’,等等就可以了。
再就是如果你们用2.0的库的话,你们中断向量地址UsageFault_Handler默认是在stm32f10x_it.c 文件里的,你要将它们注释掉,否则会出现重复定义的问题,3.0就没有这个问题了。
另外接收的部分我没有说,其实接收的可以在调用USART1RecvData(count,flag)函数来配置,count用于定义一帧接收字节数,flag用于是否开启接收超时中断,里面的USART1_RECV_MAX_Q用于定义接收缓冲区的最大字节,我以前是爱用收到多少字节后进入接收中断的,发觉其实没有必要,我们在接收时开启定时中断,每个接收字节的ISR里面更新TIM的时间,最后一个字节结束了以后,TIM没有更新了,就会触发超时中断,在超时中断里面处理接收到的命令就可以了。所以这个count事实上没有作用。flag用于是否开启超时中断,若朋友们是手动调试,请关闭flag,若正常工作了,打开flag就是了。       
注意:
USART1接收超时中断使用了TIM2
USART1接收超时中断使用了TIM3
USART1接收超时中断使用了TIM4
USART1接收超时中断使用了TIM6
USART1接收超时中断使用了TIM7
STM的定时器很多,随便用,在AVR里面,我用的是Timer2

340里面也有很多,AVR,340的工程以后再挂上。

多谢兄弟们捧场,刚刚把中文输入法加上去了,有需要加入中文输入法的朋友们可以用。

拼音输入法 (原文件名:拼音输入法.JPG)
串口发送模板(加入拼音检索)ourdev_611446EN129F.rar(文件大小:816K) (原文件名:串口发送模板(第三版).rar)

出0入0汤圆

发表于 2011-1-16 16:47:22 | 显示全部楼层
不错 顶一下!

出0入0汤圆

发表于 2011-1-16 17:03:11 | 显示全部楼层
终于看到了一个对串口通讯比较系统研究的帖子

出0入0汤圆

发表于 2011-1-16 17:08:06 | 显示全部楼层
学习!!!

出0入0汤圆

发表于 2011-1-16 17:09:20 | 显示全部楼层
学习了

出0入0汤圆

发表于 2011-1-16 17:09:30 | 显示全部楼层
这种程序结构是做好的,经典帖

出0入0汤圆

发表于 2011-1-16 17:12:20 | 显示全部楼层
今天学到好东西了!复制到evernote,:-D

出0入0汤圆

发表于 2011-1-16 17:17:03 | 显示全部楼层
很详细。

出0入0汤圆

发表于 2011-1-16 17:17:49 | 显示全部楼层
写的好,学习

出0入0汤圆

发表于 2011-1-16 17:21:39 | 显示全部楼层
谢谢分享~~

出0入0汤圆

发表于 2011-1-16 17:22:54 | 显示全部楼层
学习

出0入0汤圆

发表于 2011-1-16 17:25:38 | 显示全部楼层
应该穿“裤”子的帖

出0入0汤圆

发表于 2011-1-16 17:28:51 | 显示全部楼层
非常感谢~学习

出0入0汤圆

发表于 2011-1-16 17:37:10 | 显示全部楼层
来学习

出0入0汤圆

发表于 2011-1-16 17:50:25 | 显示全部楼层
Very Good!学习了。

出0入0汤圆

发表于 2011-1-16 17:55:18 | 显示全部楼层
Mark

出0入0汤圆

发表于 2011-1-16 18:03:30 | 显示全部楼层
mark 学习。

出30入0汤圆

 楼主| 发表于 2011-1-16 18:12:55 | 显示全部楼层
在补上几句话,告诉兄弟们怎么使用,
把工程文档的‘驱动’这个文件夹的内容加到你们的工程中就可以了,如果要使用USART2,3,直到5,
只需将USART1.c,和USART1Config.c中的‘USART1’直接全部替换成‘USART2’,等等就可以了。
再就是如果你们用2.0的库的话,你们中断向量地址UsageFault_Handler默认是在stm32f10x_it.c 文件里的,你要将它们注释掉,否则会出现重复定义的问题,3.0就没有这个问题了。
另外接收的部分我没有说,其实接收的可以在调用USART1RecvData(count,flag)函数来配置,count用于定义一帧接收字节数,flag用于是否开启接收槽设计中断,里面的USART1_RECV_MAX_Q用于定义接收缓冲区的最大字节,我以前是爱用收到多少字节后进入接收中断的,发觉其实没有必要,我们在接收时开启定时中断,每个接收字节的ISR里面更新TIM的时间,最后一个字节结束了以后,TIM没有更新了,就会触发超时中断,在超时中断里面处理接收到的命令就可以了。所以这个count事实上没有作用。flag用于是否开启超时中断,若朋友们是手动调试,请关闭flag,若正常工作了,打开flag就是了。

出0入0汤圆

发表于 2011-1-16 18:18:24 | 显示全部楼层
MARK

出0入0汤圆

发表于 2011-1-16 18:23:08 | 显示全部楼层
支持穿裤子

出0入0汤圆

发表于 2011-1-16 18:35:55 | 显示全部楼层
mark!~

出0入0汤圆

发表于 2011-1-16 18:40:37 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-1-16 18:43:45 | 显示全部楼层
这个得顶

出0入0汤圆

发表于 2011-1-16 19:04:29 | 显示全部楼层
顶起来

出0入9汤圆

发表于 2011-1-16 19:05:59 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-1-16 19:12:00 | 显示全部楼层
如果不考虑DMA的话,简单的环形缓冲区可以参考Keil自带的串口例程,是基于中断和环形缓冲的,另外它对指针的处理可以在出入队时不需要关中断或是进入临界区(VxWorks中的ringbuf基本上也是这样)

出0入0汤圆

发表于 2011-1-16 19:29:51 | 显示全部楼层
学习之

出0入24汤圆

发表于 2011-1-16 19:37:27 | 显示全部楼层
Mark!
cool

出0入0汤圆

发表于 2011-1-16 20:11:45 | 显示全部楼层
STM32串口驱动(拼音检索测试通过)(环形队列+内存动态分配+DMA)

出0入0汤圆

发表于 2011-1-16 20:24:21 | 显示全部楼层
这个可以顶

出0入0汤圆

发表于 2011-1-16 20:28:30 | 显示全部楼层
这个可以COOL

出0入0汤圆

发表于 2011-1-16 20:28:53 | 显示全部楼层
顶呀

出0入0汤圆

发表于 2011-1-16 20:46:19 | 显示全部楼层
好 记号一下

出0入0汤圆

发表于 2011-1-16 20:48:36 | 显示全部楼层
写得不错 学习一下

出5入8汤圆

发表于 2011-1-16 21:22:47 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-1-16 21:30:41 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-1-16 21:37:18 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-1-16 22:09:44 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-1-16 22:28:02 | 显示全部楼层
置顶,学习

出0入0汤圆

发表于 2011-1-16 22:34:30 | 显示全部楼层
MARK

出0入0汤圆

发表于 2011-1-16 22:49:00 | 显示全部楼层
m一个,写的不错

出0入0汤圆

发表于 2011-1-16 22:53:00 | 显示全部楼层
牛人!这个得学习了!

出0入0汤圆

发表于 2011-1-16 23:02:29 | 显示全部楼层
楼主所述这些,在RT-THREAD的串口驱动中,可以看到。一模一样

出0入0汤圆

发表于 2011-1-16 23:41:12 | 显示全部楼层
非常详细的学习贴!过后好好拜读下!

出30入0汤圆

 楼主| 发表于 2011-1-17 01:06:59 | 显示全部楼层
回42楼:楼主所述这些,在RT-THREAD的串口驱动中,可以看到。一模一样

RT-Thread是一款优秀的ROTS,在下十分佩服,也正在学习,说实话,也略有所知。
但是“一模一样”这四字,何处谈起?
在下有感于此,刚刚拜读了RT-Thread的串口驱动源代码,基本原理差不多,现列于下,
/*
* File      : usart.c
* This file is part of RT-Thread RTOS
* COPYRIGHT (C) 2009, RT-Thread Development Team
*
* The license and distribution terms for this file may be
* found in the file LICENSE in this distribution or at
* http://www.rt-thread.org/license/LICENSE
*
* Change Logs:
* Date           Author       Notes
* 2009-01-05     Bernard      the first version
* 2010-03-29     Bernard      remove interrupt Tx and DMA Rx mode
*/

#include "usart.h"
#include <serial.h>
#include <stm32f10x_dma.h>

/*
* Use UART1 as console output and finsh input
* interrupt Rx and poll Tx (stream mode)
*
* Use UART2 with interrupt Rx and poll Tx
* Use UART3 with DMA Tx and interrupt Rx -- DMA channel 2
*
* USART DMA setting on STM32
* USART1 Tx --> DMA Channel 4
* USART1 Rx --> DMA Channel 5
* USART2 Tx --> DMA Channel 7
* USART2 Rx --> DMA Channel 6
* USART3 Tx --> DMA Channel 2
* USART3 Rx --> DMA Channel 3
*/

#ifdef RT_USING_UART1
struct stm32_serial_int_rx uart1_int_rx;
struct stm32_serial_device uart1 =
{
        USART1,
        &uart1_int_rx,
        RT_NULL
};
struct rt_device uart1_device;
#endif

#ifdef RT_USING_UART2
struct stm32_serial_int_rx uart2_int_rx;
struct stm32_serial_device uart2 =
{
        USART2,
        &uart2_int_rx,
        RT_NULL
};
struct rt_device uart2_device;
#endif

#ifdef RT_USING_UART3
struct stm32_serial_int_rx uart3_int_rx;
struct stm32_serial_dma_tx uart3_dma_tx;
struct stm32_serial_device uart3 =
{
        USART3,
        &uart3_int_rx,
        &uart3_dma_tx
};
struct rt_device uart3_device;
#endif

#define USART1_DR_Base  0x40013804
#define USART2_DR_Base  0x40004404
#define USART3_DR_Base  0x40004804

/* USART1_REMAP = 0 */
#define UART1_GPIO_TX                GPIO_Pin_9
#define UART1_GPIO_RX                GPIO_Pin_10
#define UART1_GPIO                        GPIOA
#define RCC_APBPeriph_UART1        RCC_APB2Periph_USART1
#define UART1_TX_DMA                DMA1_Channel4
#define UART1_RX_DMA                DMA1_Channel5

#if defined(STM32F10X_LD) || defined(STM32F10X_MD) || defined(STM32F10X_CL)
#define UART2_GPIO_TX            GPIO_Pin_5
#define UART2_GPIO_RX            GPIO_Pin_6
#define UART2_GPIO                    GPIOD
#define RCC_APBPeriph_UART2        RCC_APB1Periph_USART2
#else /* for STM32F10X_HD */
/* USART2_REMAP = 0 */
#define UART2_GPIO_TX                GPIO_Pin_2
#define UART2_GPIO_RX                GPIO_Pin_3
#define UART2_GPIO                        GPIOA
#define RCC_APBPeriph_UART2        RCC_APB1Periph_USART2
#define UART2_TX_DMA                DMA1_Channel7
#define UART2_RX_DMA                DMA1_Channel6
#endif

/* USART3_REMAP[1:0] = 00 */
#define UART3_GPIO_RX                GPIO_Pin_11
#define UART3_GPIO_TX                GPIO_Pin_10
#define UART3_GPIO                        GPIOB
#define RCC_APBPeriph_UART3        RCC_APB1Periph_USART3
#define UART3_TX_DMA                DMA1_Channel2
#define UART3_RX_DMA                DMA1_Channel3

static void RCC_Configuration(void)
{
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

#ifdef RT_USING_UART1
        /* Enable USART1 and GPIOA clocks */
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
#endif

#ifdef RT_USING_UART2

#if (defined(STM32F10X_LD) || defined(STM32F10X_MD) || defined(STM32F10X_CL))
    /* Enable AFIO and GPIOD clock */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOD, ENABLE);

    /* Enable the USART2 Pins Software Remapping */
    GPIO_PinRemapConfig(GPIO_Remap_USART2, ENABLE);
#else
    /* Enable AFIO and GPIOA clock */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA, ENABLE);
#endif

        /* Enable USART2 clock */
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
#endif

#ifdef RT_USING_UART3
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
        /* Enable USART3 clock */
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);

        /* DMA clock enable */
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
#endif
}

static void GPIO_Configuration(void)
{
        GPIO_InitTypeDef GPIO_InitStructure;

#ifdef RT_USING_UART1
        /* Configure USART1 Rx (PA.10) as input floating */
        GPIO_InitStructure.GPIO_Pin = UART1_GPIO_RX;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(UART1_GPIO, &GPIO_InitStructure);

        /* Configure USART1 Tx (PA.09) as alternate function push-pull */
        GPIO_InitStructure.GPIO_Pin = UART1_GPIO_TX;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_Init(UART1_GPIO, &GPIO_InitStructure);
#endif

#ifdef RT_USING_UART2
        /* Configure USART2 Rx as input floating */
        GPIO_InitStructure.GPIO_Pin = UART2_GPIO_RX;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(UART2_GPIO, &GPIO_InitStructure);

        /* Configure USART2 Tx as alternate function push-pull */
        GPIO_InitStructure.GPIO_Pin = UART2_GPIO_TX;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(UART2_GPIO, &GPIO_InitStructure);
#endif

#ifdef RT_USING_UART3
        /* Configure USART3 Rx as input floating */
        GPIO_InitStructure.GPIO_Pin = UART3_GPIO_RX;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(UART3_GPIO, &GPIO_InitStructure);

        /* Configure USART3 Tx as alternate function push-pull */
        GPIO_InitStructure.GPIO_Pin = UART3_GPIO_TX;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(UART3_GPIO, &GPIO_InitStructure);
#endif
}

static void NVIC_Configuration(void)
{
        NVIC_InitTypeDef NVIC_InitStructure;

#ifdef RT_USING_UART1
        /* Enable the USART1 Interrupt */
        NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
#endif

#ifdef RT_USING_UART2
        /* Enable the USART2 Interrupt */
        NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
        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 = USART3_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);

        /* Enable the DMA1 Channel2 Interrupt */
        NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
#endif
}

static void DMA_Configuration(void)
{
#if defined (RT_USING_UART3)
        DMA_InitTypeDef DMA_InitStructure;

        /* fill init structure */
        DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
        DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
        DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
        DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
        DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
        DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

        /* DMA1 Channel5 (triggered by USART3 Tx event) Config */
        DMA_DeInit(UART3_TX_DMA);
        DMA_InitStructure.DMA_PeripheralBaseAddr = USART3_DR_Base;
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
        DMA_InitStructure.DMA_MemoryBaseAddr = (u32)0;
        DMA_InitStructure.DMA_BufferSize = 0;
        DMA_Init(UART3_TX_DMA, &DMA_InitStructure);
        DMA_ITConfig(UART3_TX_DMA, DMA_IT_TC | DMA_IT_TE, ENABLE);
        DMA_ClearFlag(DMA1_FLAG_TC5);
#endif
}

/*
* Init all related hardware in here
* rt_hw_serial_init() will register all supported USART device
*/
void rt_hw_usart_init()
{
        USART_InitTypeDef USART_InitStructure;
        USART_ClockInitTypeDef USART_ClockInitStructure;

        RCC_Configuration();

        GPIO_Configuration();

        NVIC_Configuration();

        DMA_Configuration();

        /* uart init */
#ifdef RT_USING_UART1
        USART_InitStructure.USART_BaudRate = 115200;
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;
        USART_InitStructure.USART_StopBits = USART_StopBits_1;
        USART_InitStructure.USART_Parity = USART_Parity_No;
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
        USART_ClockInitStructure.USART_Clock = USART_Clock_Disable;
        USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;
        USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge;
        USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable;
        USART_Init(USART1, &USART_InitStructure);
        USART_ClockInit(USART1, &USART_ClockInitStructure);

        /* register uart1 */
        rt_hw_serial_register(&uart1_device, "uart1",
                RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_STREAM,
                &uart1);

        /* enable interrupt */
        USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
#endif

#ifdef RT_USING_UART2
        USART_InitStructure.USART_BaudRate = 115200;
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;
        USART_InitStructure.USART_StopBits = USART_StopBits_1;
        USART_InitStructure.USART_Parity = USART_Parity_No;
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
        USART_ClockInitStructure.USART_Clock = USART_Clock_Disable;
        USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;
        USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge;
        USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable;
        USART_Init(USART2, &USART_InitStructure);
        USART_ClockInit(USART2, &USART_ClockInitStructure);

        /* register uart2 */
        rt_hw_serial_register(&uart2_device, "uart2",
                RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_STREAM,
                &uart2);

        /* Enable USART2 DMA Rx request */
        USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
#endif

#ifdef RT_USING_UART3
        USART_InitStructure.USART_BaudRate = 115200;
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;
        USART_InitStructure.USART_StopBits = USART_StopBits_1;
        USART_InitStructure.USART_Parity = USART_Parity_No;
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
        USART_ClockInitStructure.USART_Clock = USART_Clock_Disable;
        USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;
        USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge;
        USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable;
        USART_Init(USART3, &USART_InitStructure);
        USART_ClockInit(USART3, &USART_ClockInitStructure);

        uart3_dma_tx.dma_channel= UART3_TX_DMA;

        /* register uart3 */
        rt_hw_serial_register(&uart3_device, "uart3",
                RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_DMA_TX,
                &uart3);

        /* Enable USART3 DMA Tx request */
        USART_DMACmd(USART3, USART_DMAReq_Tx , ENABLE);

        /* enable interrupt */
        USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
#endif
}







这里是在下的代码,
我的工程文件有五个部分,
USART1.c
USART1ConFig.c
OSMem.c
USART.H
OSMem.H
现随便将一个代码贴出:











/*******************************************************************************
* 文件名                   : USART1.c
* 描述                 : USART的驱动函数
* 移植步骤                 : 中间层函数
* 输入           : 无
* 输出           : 无
* 返回           : 无
*******************************************************************************/
#include "stm32f10x_lib.h"

#define USART1_SEND_MAX_Q                  16                        //发送内存块内的最大空间
#define USART1_SEND_MAX_BOX                128                           //发送内存块的最大数量

unsigned char USART1SendQBuffer[USART1_SEND_MAX_BOX][USART1_SEND_MAX_Q];//发送内存块       
unsigned char USART1SendQBoxHost=0;                        //内存块头指针                                                       
unsigned char USART1SendQBoxTail=0;                        //内存块尾指针
unsigned int  USART1SendQFree=USART1_SEND_MAX_BOX;   
unsigned char USART1SendOVF=0;                                 //USART1发送任务块溢出标志
unsigned char USART1RunningFlag=0;
typedef struct{
unsigned char Num;
unsigned char Index[USART1_SEND_MAX_Q];
}USART1SendTCB;
USART1SendTCB USART1TCB[USART1_SEND_MAX_BOX];

#define USART1_RECV_MAX_Q                  128                        //内存块内的最大空间
#define USART1_RECV_MAX_BOX                32                           //内存块的最大数量

unsigned char USART1QRecvBuffer[USART1_RECV_MAX_BOX][USART1_RECV_MAX_Q];//接收内存块       
unsigned char USART1QRecvBoxHost=0;                        //接收内存块头指针                                                       
unsigned char USART1QRecvBoxTail=0;                        //接收内存块尾指针
unsigned int  USART1QRecvFree=USART1_RECV_MAX_BOX;  
unsigned char USART1RecvOVF=0;                                 //USART2接收任务块溢出标志  
static unsigned char USART1RecvChar;
unsigned char Recv1Index=0x00;

//错误定义
#define ERR_NO_SPACE        0xff       

/*******************************************************************************
* 文件名                   : USART1SendUpdate
* 描述                 : 检查结构体里面有没有数据还未发送完毕,若没有发送,则继续发送,
                                   若发送完毕,退出
* 输入           : 无
* 输出           : 无
* 返回           : 无
*******************************************************************************/
void USART1SendUpdate(void)
{
        static unsigned char count=0;
        USART1StopISR();
        if(USART1SendQFree==USART1_SEND_MAX_BOX)return;
        if((USART1TCB[USART1SendQBoxTail].Num)&&(USART1SendQBoxTail!=USART1SendQBoxHost))
        {
                USART1TCB[USART1SendQBoxTail].Num--;
                USART1SendByte(*(USART1TCB[USART1SendQBoxTail].Index+count));
                count++;
        }
        else if(USART1SendQBoxTail!=USART1SendQBoxHost)
        {               
                if(++USART1SendQBoxTail>=USART1_SEND_MAX_BOX)USART1SendQBoxTail=0;
                if(++USART1SendQFree>=USART1_SEND_MAX_BOX)USART1SendQFree=USART1_SEND_MAX_BOX;
                count=0;
                if((USART1TCB[USART1SendQBoxTail].Num)&&(USART1SendQBoxTail!=USART1SendQBoxHost))
                {
                        USART1TCB[USART1SendQBoxTail].Num--;
                        USART1SendByte(*(USART1TCB[USART1SendQBoxTail].Index+count));
                        count++;
                }
                else
                {
                        USART1RunningFlag=0;
                        USART1SendQFree=USART1_SEND_MAX_BOX;
                        count=0;
                }       
        }
        else
        {
                USART1RunningFlag=0;
                USART1SendQFree=USART1_SEND_MAX_BOX;
                count=0;
        }
        USART1StartISR();       
}
/*******************************************************************************
* 文件名                   : USART1WriteDataToBuffer
* 描述                 : 检查发送缓冲区的大小,若空间足够,将待发送的数据放入到发送缓冲
                                   区中去,并且启动发送
* 输入           : buffer待发送的数据的指针,count待发送的数据的数量
* 输出           : 无
* 返回           : 若正确放入到发送缓冲区中去了,就返回0x00         ,否则返回0x01
*******************************************************************************/
unsigned char USART1WriteDataToBuffer(unsigned char *buffer,unsigned char count)
{
        if(count%USART1_SEND_MAX_Q)count=count/USART1_SEND_MAX_Q+1;
        else count=count/USART1_SEND_MAX_Q;
        if(USART1SendQFree<count)return ERR_NO_SPACE;
        count=0;
        while(*buffer!='\0')
        {
                *(USART1TCB[USART1SendQBoxHost].Index+count)=*buffer;
                count++;
                if(count>=USART1_SEND_MAX_Q)
                {
                        USART1TCB[USART1SendQBoxHost].Num=USART1_SEND_MAX_Q;
                        if(++USART1SendQBoxHost>=USART1_SEND_MAX_BOX)USART1SendQBoxHost=0;       
                        USART1SendQFree--;
                        count=0;
                }
                buffer++;
        }
        if(count!=0)
        {
                USART1TCB[USART1SendQBoxHost].Num=count;
                USART1SendQFree--;
                if(++USART1SendQBoxHost>=USART1_SEND_MAX_BOX)USART1SendQBoxHost=0;       
        }
        if(USART1RunningFlag==0)
        {
                USART1SendUpdate();
                USART1RunningFlag=1;
        }
        return 0x00;
}
/*******************************************************************************
* 文件名                   : USART1DispFun
* 描述                 : 检查发送缓冲区的大小,若空间足够,将待发送的数据放入到发送缓冲
                                   区中去,并且启动发送,与USART1WriteDataToBuffer不同的是,启动发送
                                   函数世不需要指定文件大小的,这就给调用提供了方便.
* 输入           : buffer待发送的数据的指针
* 输出           : 无
* 返回           : 若正确放入到发送缓冲区中去了,就返回0x00         ,否则返回0x01
*******************************************************************************/
unsigned char USART1DispFun(unsigned char *buffer)
{
        unsigned long count=0;
        while(buffer[count]!='\0')count++;
        return(USART1WriteDataToBuffer(buffer,count));
}

在下机械专业出生,刻苦自学,经过接近一年的学习习,在下的编程风格已趋稳定,
请问诸位朋友觉得我的程序真的如42楼所说与RT-Thread的程序是“一模一样”吗?
唉,令人寒心~~~

出0入0汤圆

发表于 2011-1-17 01:28:46 | 显示全部楼层
谢谢LZ,正在学习.

出0入0汤圆

发表于 2011-1-17 09:06:23 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-1-17 09:15:40 | 显示全部楼层
不错,学习

出0入0汤圆

发表于 2011-1-17 09:24:40 | 显示全部楼层
标记下,很难得的串口工程应用

出0入0汤圆

发表于 2011-1-17 09:30:11 | 显示全部楼层
要是做什么都研究到这个地步那人真的很累,机器很轻松.   绝对的裤贴,

出0入0汤圆

发表于 2011-1-17 10:17:59 | 显示全部楼层
bjut的校友。支持~~~~~

出0入0汤圆

发表于 2011-1-17 20:35:59 | 显示全部楼层
顶楼主,看的出来楼主下过很大功夫的,朋友们感兴趣的话最好大致看一下代码再评价,随便评价不是一个负责任的态度。
另外,大家的话有时候也就是顶一下的意思,不一定带有针对性,楼主也不必介意,我觉得那位朋友是说环形队列的原理跟RTT的一样而已,呵呵。

出0入0汤圆

发表于 2011-1-17 22:11:53 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-1-17 22:17:28 | 显示全部楼层
应给酷!

出0入0汤圆

发表于 2011-1-17 22:56:07 | 显示全部楼层
不错  串口驱动

出0入0汤圆

发表于 2011-3-3 16:46:51 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-3-3 17:16:57 | 显示全部楼层
楼主能将自己的研究无偿贡献给大家,非常感谢!就算“一摸一样”,比你自个看代码也要强多了,总算有个人给你讲解啊!不要无缘无故的打击他人的奉献精神,打击比人、其实就是断自己的路子。。。

出0入0汤圆

发表于 2011-3-3 20:11:18 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-3-3 20:15:02 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-3-3 20:19:23 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-3-8 16:28:00 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-3-9 11:25:16 | 显示全部楼层
真是好东西!

出0入0汤圆

发表于 2011-3-9 11:33:45 | 显示全部楼层
好贴

出0入0汤圆

发表于 2011-3-9 11:39:25 | 显示全部楼层
不错,真得不错啊!

出0入0汤圆

发表于 2011-3-9 21:27:18 | 显示全部楼层
谢谢分享

出0入0汤圆

发表于 2011-3-30 13:05:20 | 显示全部楼层
不错

出110入109汤圆

发表于 2011-3-30 13:48:18 | 显示全部楼层
回复【25楼】hiberhe  
如果不考虑dma的话,简单的环形缓冲区可以参考keil自带的串口例程,是基于中断和环形缓冲的,另外它对指针的处理可以在出入队时不需要关中断或是进入临界区(vxworks中的ringbuf基本上也是这样)
-----------------------------------------------------------------------

keil自带的,在哪里?

出0入0汤圆

发表于 2011-4-1 22:26:47 | 显示全部楼层
这个一定要顶啊

出0入0汤圆

发表于 2011-4-1 23:04:45 | 显示全部楼层
这个真是详细!

出0入0汤圆

发表于 2011-4-2 14:58:59 | 显示全部楼层
mark一下,以后用得着

出0入0汤圆

发表于 2011-4-2 15:27:27 | 显示全部楼层
加酷吧,那个分享一本C语言教程的帖子都置酷了,这个没有理由不吧

出0入0汤圆

发表于 2011-4-8 13:38:57 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-4-19 10:46:50 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-4-19 11:08:44 | 显示全部楼层
标记

出0入0汤圆

发表于 2011-4-19 17:35:04 | 显示全部楼层
经典

出0入0汤圆

发表于 2011-4-19 17:35:17 | 显示全部楼层
经典

出0入0汤圆

发表于 2011-4-19 20:51:05 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-4-19 22:00:23 | 显示全部楼层
不错的经验,学习。

出0入0汤圆

发表于 2011-4-19 22:37:50 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-4-19 23:05:33 | 显示全部楼层
讲的太好了,明天抽时间好好看看~~~~~

出0入0汤圆

发表于 2011-4-19 23:13:30 | 显示全部楼层
不错,学习了

出0入0汤圆

发表于 2011-4-20 12:32:43 | 显示全部楼层
留贴学习……

出0入0汤圆

发表于 2011-4-20 12:38:04 | 显示全部楼层
请编程思想的,支持楼主 

出0入0汤圆

发表于 2011-4-20 17:32:15 | 显示全部楼层
牛人,这个一定要顶~~

出0入0汤圆

发表于 2011-4-21 12:17:05 | 显示全部楼层
学习了

出0入0汤圆

发表于 2011-4-21 12:29:00 | 显示全部楼层
请教下楼主如何考虑RS485的传输机制呢?

出0入0汤圆

发表于 2011-4-21 12:37:36 | 显示全部楼层
谢谢楼主分享,支持加酷!

出0入0汤圆

发表于 2011-4-21 13:49:45 | 显示全部楼层
顶楼主,很好的学习资料!

出0入134汤圆

发表于 2011-4-21 14:07:14 | 显示全部楼层
强帖留名

出0入0汤圆

发表于 2011-4-21 14:24:44 | 显示全部楼层
很象UCOS-II的任务控制块的思想。
楼主真的好厉害啊,崇拜
还没到层次啊不太明白

出0入0汤圆

发表于 2011-4-21 19:12:58 | 显示全部楼层
真的很不错,学习了。

出0入0汤圆

发表于 2011-4-21 20:48:37 | 显示全部楼层
非常经典。。。

出0入0汤圆

发表于 2011-4-21 20:58:34 | 显示全部楼层
英雄所见略同

出0入0汤圆

发表于 2011-4-22 16:55:18 | 显示全部楼层
经典

出0入0汤圆

发表于 2011-4-22 18:22:49 | 显示全部楼层
进来学习了

出0入0汤圆

发表于 2011-5-4 00:06:45 | 显示全部楼层
串口驱动 环形队列

出0入0汤圆

发表于 2011-5-4 00:14:27 | 显示全部楼层
coo;

出0入0汤圆

发表于 2011-5-6 17:13:21 | 显示全部楼层
好贴,正是我需要的

出0入0汤圆

发表于 2011-5-14 22:25:53 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-5-15 19:57:28 | 显示全部楼层
好帖得顶
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-19 13:00

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表