搜索
bottom↓
回复: 154

通信方案软件设计(环形动态申请内存,支持USART+IIC+SPI+CAN协议,提供AVR+C8051F+LPC200

  [复制链接]

出30入0汤圆

发表于 2011-10-7 09:46:32 | 显示全部楼层 |阅读模式
通信方案软件设计(环形动态申请内存发送,支持USART+IIC+SPI+CAN在内所有的协议,提供AVR+C8051F+LPC2000+STM32例程及仿真实例)
我已经移植好了AVR系列,STM32系列,c8051f系列和LPC2000系列的IIC、SPI、CAN(有些芯片没有)和USART总线的所有版本,现在比较忙,全部整理需要很多时间,稍后再穿上吧。
Mega16的USART(CVAVR版本)ourdev_682745DZPEC4.rar(文件大小:119K) (原文件名:Mega16的USART(CVAVR版本).rar)
Mega16的USART(ICCVAVR版本)ourdev_682746AK16GN.rar(文件大小:240K) (原文件名:Mega16的USART(ICCAVR版本).rar)
Mega128的USART(CVAVR版本)ourdev_682751ZM8EMV.rar(文件大小:119K) (原文件名:Mega128的USART(CVAVR版本).rar)

mega128仿真图片 (原文件名:Mega128.JPG)

stm32的CAN总线与USART总线 (原文件名:STM32-CAN.JPG)

通信方案软件设计ourdev_682757JDMBNY.pdf(文件大小:299K) (原文件名:通信方案软件设计.pdf)
STM32F103的CAN总线ourdev_682758X9YMX7.rar(文件大小:193K) (原文件名:STM32F103的CAN总线.rar)
C8051F020ourdev_682759E5G4DV.rar(文件大小:59K) (原文件名:C8051F020.rar)
LPC2114ourdev_682760CBS2LU.rar(文件大小:282K) (原文件名:LPC2114.rar)
空的demo版本ourdev_682761NV8N5I.rar(文件大小:8K) (原文件名:demo.rar)
1 环形队列串口通信方式的回顾
  1.1 接口函数简单的介绍
        在前面《串口设备驱动接口》一章中(http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=4516795&bbs_page_no=1&bbs_id=3020 ),介绍了环形队列动态发送接收数据的好处,同时介绍了内存分配的相关内容,但是有些朋友,特别是初学者可以会比较晕,主要是不知道怎么用,以及为什么要这样设计,下文就是一些应用,我会提供很多典型微控制器的应用案例与工程源代码。但是希望朋友们知道怎么用了以后还是返回原文看看,知道原理以后,稍加修改,就可以使用到很多通讯接口和通信芯片上。
        笔者也是发大宏愿,希望能在IIC、SPI、CAN中广泛使用这种通信方案,呵呵, 借用乔布斯的一句名言:我们来到这个世界是为了改变世界的,不然的话,我们为什么来到这里!
        然而,现实总是残酷地,经过无数打击以后,我们接受了这个现状,自顶向下的腐_败、社会的全面溃败、高通胀、高房价、没有出路,我们的未来昏暗而渺茫,至少,我们有共同的爱好,也许在这里我们才能找到一些心灵上的慰藉。
        1.1.1 demo工程包内的文件
        我们先打开Demo文件夹,在这个文件夹里存放着源文件,所有的代码都在这里。
        (1)  OSQMem.c:内存分配的相关函数文件
        (2)  OSQMem.h:内存分配的配置头文件,这个文件非常重要,里面的参数直接设置运行必不可少的参数:
#define     OS_MEM_MAX                 8                //最多允许的内存块管理区
#define     OS_MEM_USART1_MAX        1024             //发送缓冲区的内存大小
#define     OS_MEM_USART1_BLK         32               //每一个块的长度
        其中OS_MEM_MAX分配内存管理区的数量,前已述及,内存管理区内内存块的大小是必须能够修改的,所以在这里预留了8个管理区,例程中只使用了一个,主要是为以后升级方便。对于CAN总线包而言,应该设置成8个(CAN数据包最大为8个字节数据),而对于SPI存储设备而言,其实可以设置成512字节(Flash每一页大约是占512字节), 而对于IIC存储设备而言,有些每页16字节,有些8字节不等。   
        这里只使用了一个管理区,不过就如例程中演示的,不同的应用程序申请同一管理区的内存块是不会相互干扰的。
        OS_MEM_USART1_MAX指内存缓冲区的大小,这个数值取决于于应用程序需求,我这里设置的是1024,对于8位机的AVR而言,设置应该适度减小。
        OS_MEM_USART1_BLK指每一个块的长度,大小_参考《串口设备驱动接口 》一文。
         USART1.h:USART1.c的头文件,包含了其所有函数的预定义。
         USART1.c:里面有串口发送的所有函数,用户需要设置的参数有:
#define USART1_SEND_MAX_Q          (OS_MEM_USART1_BLK-4)    //发送内存块内的最大空间
#define USART1_SEND_MAX_BOX        OS_MEM_USART1_MAX/OS_MEM_USART1_BLK
                                                                                //发送内存块的最大数量
#define USART1_RECV_MAX_Q          32                          //接收内存块内的最大空间
        具体的含义不再详细介绍了,这里解释一下为什么每个发送块内有用的空间是OS_MEM_USART1_BLK-4,因为每个内存块中头四个字节已经存储了下一个链表的地址,是不能给用户使用的(对于8位机3个字节已经足够,具体依赖于硬件)。
        (5)  USART1ConFig.c
        这个函数与底层相关,如果实例库中没有需要用户自己移植,不过很简单,和你们自己写驱动函数一样,你们只需实现简单的几个设置、发送、接收函数,就可以方便的使用这个功能比较强大的通信方案,换句话说,你们自己写底层驱动,这个过程也是必须的,何不试试呢~~保证你会有意外惊喜。
        1.1.2 使用配置
        以下几个参数是每次使用项目前根据需求必须配置的。
#define     OS_MEM_USART1_MAX        1024             //发送缓冲区的内存大小
#define     OS_MEM_USART1_BLK         32               //每一个块的长度
#define     USART1_SEND_MAX_Q         (OS_MEM_USART1_BLK-4)  //发送内存块内的最大空间
#define     USART1_SEND_MAX_BOX       OS_MEM_USART1_MAX/OS_MEM_USART1_BLK
                                                                        //发送内存块的最大数量
#define     USART1_RECV_MAX_Q         32               //接收内存块内的最大空间

  1.2 使用介绍
        首先建立工程,配置好路径参数什么的,然后添加移植好了的工程,编译通过以后,再加入demo内的除了main.c之外的源文件。
        (2)在OSQMem.h中设置以下参数:
#define     OS_MEM_MAX                 8                //最多允许的内存块管理区
#define     OS_MEM_USART1_MAX        1024             //发送缓冲区的内存大小
#define     OS_MEM_USART1_BLK         32               //每一个块的长度
        这些参数与内存管理相关,具体的各位看源代码吧,这些东西不是什么高难度的东西,各位都应该能够看懂,不懂的可以给我发邮件。
    (3)USART1.c:里面有串口发送的所有函数,用户需要设置的参数有:
#define USART1_SEND_MAX_Q          (OS_MEM_USART1_BLK-4)    //发送内存块内的最大空间
#define USART1_SEND_MAX_BOX        OS_MEM_USART1_MAX/OS_MEM_USART1_BLK
                                                                                //发送内存块的最大数量
#define USART1_RECV_MAX_Q          32                          //接收内存块内的最大空间
     到这里配置就结束了,如果编译可以通过,就可以进入下一步了。
        定义内存缓冲区,和一些运行相关的变量,建立内存管理区,参数的含义见注释,函数会返回一个内存管理区指针,以后申请内存全部是通过它来完成,然后配置串口等等,串口函数的参数是波特率,这个功能我还没有使用,仅仅是把stm32的配置加进去了,其他的微处理器我不熟,没有时间去深究,波特率还需要你们自己去查手册。
char MemUSART1TestBuf[OS_MEM_USART1_MAX];       //空白缓冲区地址,用于建立内存块
OSMEMTcb *OSQUSART1Index;                         //内存块管理区指针            
char MemTestErr;                                      //指示错误用的,非0时错误  

//在内存管理区注_册,返回一个内存管理区指针,这个指针很重要,申请内存全部需要它完成
    //                           空白缓冲区地址,用于建立内存块
    //                            |        每一个内存块的长度
    //                            |        |            共有多少个内存块
    //                            |        |            |            错误标志
    //                            |        |            |            |
OSQUSART1Index=(OSMEMTcb     |        |            |            |                                       *)OSMemCreate(MemUSART1TestBuf,OS_MEM_USART1_BLK,OS_MEM_USART1_MAX/OS_MEM_USART1_BLK,&MemTestErr);            
//初始化串口端口,波特率为115200,目前只针对stm32有用,其他的硬件需要查手册去完成配置
USART1_Configuration(115200);
        接下来就是怎么发送和接收数据了,提供了两种发送方法,一个是USART1DispFun()函数,这个函数通过指针传递参数,一遇到0x00就截止了:
        /*******************************************************************************
        * 文件名         : USART1DispFun
        * 描述           : 检查发送缓冲区的大小,若空间足够,将待发送的数据放入到发送缓冲
                           区中去,并且启动发送,与USART1WriteDataToBuffer不同的是,启动发送
                           函数时不需要指定文件大小的,这就给调用提供了方便.
        * 输入           : buffer待发送的数据的指针
        * 输出           : 无
        * 返回           : 若正确放入到发送缓冲区中去了,就返回0x00,否则返回0x01
        *******************************************************************************/
        unsigned char USART1DispFun(unsigned char *buffer)
    一个是USART1WriteDataToBuffer()函数,这个函数可以发送带0x00数据,但须指定待发送字节数量:
        /*******************************************************************************
        * 文件名         : USART1WriteDataToBuffer
        * 描述           : 检查发送缓冲区的大小,若空间足够,将待发送的数据放入到发送缓冲
                           区中去,并且启动发送
        * 输入           : buffer待发送的数据的指针,count待发送的数据的数量
        * 输出           : 无
        * 返回           : 若正确放入到发送缓冲区中去了,就返回0x00 ,否则返回0x01
        *******************************************************************************/
        unsigned char USART1WriteDataToBuffer(unsigned char *buffer,unsigned int count)
        同样,接收数据的方法也有两种,马潮老师在《AVR微控制器与嵌入式系统》一书中介绍了基于状态机的方法,这种方法对接收时间没有要求,只要数据传送正确就可以了。这种接收数据的方式需要指定一帧数据的大小,只需调用函数USART1RecvData(countt,0),count只数据帧的大小,后面代表不会启动超时中断。
/*******************************************************************************
* 文件名         : USART1RecvData
* 描述           : 当接收到完整的一帧数据以后的处理函数
* 输入           : count:要接收到的一帧数据数据的个数,flag:1开启超时中断
                   0关闭超时中断
* 输出           : 无
* 返回           : 无
*******************************************************************************/   
unsigned char USART1RecvData(unsigned int count,unsigned char flag)
        接收数据包后,在USART1.c文件中的USART1RecvResetBufferIndex()函数中处理数据,朋友们在此函数中写应用函数,但是不要把该函数中的任何数据删除,只需在函数的最后一行加上你自己的代码就可以了。其实我最初的想法是在固定的设置每一个帧的大小,但是在项目的过程中,遇到了很多串口线上设备没有固定的大小,有时需要传送一些数据流,有时是指令,这样我们怎么判定一帧已经结束了呢,就是超时中断。     这就相当于一个看门狗程序,启动串口定时定时器以后,譬如在9600波特率下(此时发送一个字节的数据需要1.014ms),每隔2ms中断一次,如果在这2ms内收到了串口数据,将串口定时定时器内的计数器变为0,重新计数,如此循环,直到最后一个字节时,不再有程序将计数器变为0,定时器将会发生中断,这时一帧数据就结束了。可以对该命令或者数据进行处理了。     同样,接收数据包后,在USART1.c文件中的USART1RecvResetBufferIndex()函数中处理数据,
    (6)申请和释放内存函数OSMemGet()、OSMemDelete()。
/*******************************************************************************
* 文件名         : OSMemGet
* 描述           : 从一个内存管理区获取一个内存块
* 移植步骤       : 无
* 输入           : ptr内存管理区的指针
* 输出           : 无
* 返回           : 获取的空白内存块的首地址
*******************************************************************************/
u8 *OSMemGet(OSMEMTcb *ptr,u8 *err)
/*******************************************************************************
* 文件名         : OSMemDelete
* 描述           : 从一个内存管理区删除一个内存块
* 移植步骤       : 无
* 输入           : ptr内存管理区的指针,index,申请到的内存块的指针
* 输出           : 无
* 返回           : 如果要删除的内存块是一个空指针,则返回0xff,若能够删除,返回0
*******************************************************************************/
u8 OSMemDelete(OSMEMTcb *ptr,u8 *index)
2 通信数据块结构与内存管理模块
  2.1 为何要在缓冲区中建立通信数据块,以及加入内存管理模块
        在嵌入式系统中,很多外围设备的速度是比较慢的,譬如串口和IIC设备,这些设备的输出输入的速度大约在数十微秒到数百微秒。在较复杂的应用中,例如在笔者的一个控制器的项目中,开机后主设备必须检测从设备的枚举情况,其流程图如下:
        通讯使用的是232总线,经过我自己画的一块小板来完成232—CAN的转换。在网络中存在着60个从设备,对应不同的数据帧(具体的指令不详述),每一个设备的枚举要通过发送一帧指令,经过CAN转换,到达从设备,从设备响应以后返回给主设备。在这里计算一下,如果是在9600波特率的情况下,每个帧发送大约需要10ms的时间,加上返回的时间大约是20ms,那么60个设备大约需要1200ms,如果不使用操作系统,同时使用传统的等待方式发送,这1200ms微控制器什么也做不了,这在学校、研究所、“教授”、“专家”们那里糊弄糊弄还行,到了社会上,就不能这么干了。
        以下是我使用环形队列发送一个欢迎界面的(一共374个字符)所耗费的时间,使用MDK4.02的性能分析器进行分析:可见在发送的大部分时间中,CPU实际上都是在睡大觉(35ms在DelayMs函数中),只有大约不到2ms的时间是在做事情,如果加上笔者曾经所写的合作式操作系统的方式(见笔者的另外一篇帖子http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=3719375&bbs_page_no=1&search_mode=3&search_text=linquan315&bbs_id=9999 ),CPU同时还可以做很多其他的事情。
        当然使用RT-thread或者UCOS那就更加锦上添花了。
       
图2-1 主控制器枚举从设备
       
图2-2 使用MDK4.02的性能分析器分析各个函数的使用情况
       
  2.2 内存管理模块的申请、释放内存测试
        测试方法如下,
(1)首先定义相关变量,建立内存管理区,各个参数的定义见注释。
char MemUSART1TestBuf[OS_MEM_USART1_MAX];       //空白缓冲区地址,用于建立内存块
OSMEMTcb *OSQUSART1Index;                         //内存块管理区指针            
char *MemTestIndex[10];                                //分配到了的内存块的指针,测试用的
                                                     //在这里观察分配的内存块的地址,若
                                                     //是出现了异常,证明出现了错误
char MemTestErr;                                      //指示错误用的,非0时错误
    值得注意的是为了便于观察内存分配情况,在这里定义了一个指针数组*MemTestIndex[10],用于观察分配的内存块的地址,若是出现了异常,证明出现了错误。
//在内存管理区注_册,返回一个内存管理区指针,这个指针很重要,申请内存全部需要它完成
    //                           空白缓冲区地址,用于建立内存块
    //                            |        每一个内存块的长度
    //                            |        |            共有多少个内存块
    //                            |        |            |            错误标志
    //                            |        |            |            |
OSQUSART1Index=(OSMEMTcb     |        |            |            |                                       *)OSMemCreate(MemUSART1TestBuf,OS_MEM_USART1_BLK,OS_MEM_USART1_MAX/OS_MEM_USART1_BLK,&MemTestErr);            
    //初始化串口端口,波特率为115200,目前只针对stm32有用,其他的硬件需要查手册去完成配置
    USART1_Configuration(115200);
    //显示欢迎界面
    Welcome();
    DelayMs(100);
    ————————————
    ————————————
   (2)在超级循环中不断申请、释放内存,同时启动串口发送。由于串口发送的同时也在不停的申请和释放内存块,两者交替进行,为了最大层度的仿真实际情况,申请和释放内存的顺序被人为的打乱,不同的应用程序同时申请一个管理区的内存,经过测试,没有发现异常。
  While(1)
  {
        //测试该内存块还可以他用,除了发送串口,还可以发送CAN数据包,IIC、SPI等等
        for(i=0;i<10;i++)
        {
            //申请内存
            MemTestIndex=(u8 *)OSMemGet(OSQUSART1Index,&MemTestErr);
        }
        //发送数据        
        USART1WriteDataToBuffer(&count,1);count++;
        DelayMs(2);
        //测试该内存块还可以他用,除了发送串口,还可以发送CAN数据包,IIC、SPI等等
        //此处故意打乱顺序,测试能否通过
        for(i=10;i>0;i--)
        {
            //释放内存
            OSMemDelete(OSQUSART1Index,MemTestIndex[i-1]);
        }
    }                              

图 2-3 内存块测试
3 移植要点以及范例
  3.1 移植步骤与注意事项
        移植其实很简单,当然,前提是你对这个器件有足够的了解,我发觉这个过程其实比较痛苦,我没有用过AT91SRAM64、LPC2148、LM3S1138等芯片,但是想将其移植,苦于没有代码库或者例程,要完整的搭建一个工程其实是比较困难的,至少需要一段时间,但是我没有足够的时间。仅仅完成了LPC的移植,LPC的寄存器比较简单。或者说大家都是极其自私的人,总是盼望别人来拯救自己(笔者不喜欢那些经常在网上求救的帖子,相信很多人也不喜欢,每个人遇到的问题千差万别,需要自己去解决,工作了以后,有问题谁鸟你),自己学了点东西就捂着,别人做出了东西就希望人家免费的教你,还把原理图、源代码、PCB都供上。我在网上找了许久都没有找到合适的简单的串口收发例程,使我十分生气。
        这些工作后来我也不想移植了,如果朋友们有简单的串口、IIC、SPI、CAN的例程包及仿真软件,不妨发给我,我帮你们移植,不然看一堆数据手册和编译环境从头到尾,我没那个耐心(ST的除外,我对ST非常熟悉)。
        废话少说,首先需要的外设是:
        通讯接口,可以是串口(232或者485),SPI、IIC、CAN,以太网和USB我不熟悉。我们需要提供配置代码、接收及发送代码、中断接口代码。
        一个通用定时器,用来检测帧超时。我们需要配置代码、开始计数代码、结束计数代码、清零计数器代码。
   3.1.1通讯接口的移植
    如图3-1所示,需要配置如下函数。

图 3-1 需要移植的函数  
在USAR1Config.c中,其他的函数不是非常重要,仅仅供以后的程序升级之用。
  3.2 在51内核系列的C8051F020上的移植范例与MKD仿真
        以下开始介绍C8051F020的移植范例。在C8051F020中使用的是传统的8051的内核,我在移植时发觉当加入内存分配的时候,就会出现程序跑飞的情况,经过仿真后,在内存释放的时候总是出现程序跳转异常,感觉是51的寄存器不适合比较复杂的指针运算以及函数嵌套,在将优化登记调至最低后,还是出现问题,笔者不得不放弃了内存分配的方法,与此同时将函数的块发送也取消了。
(1)USART1PinConfiguration函数  
void USART1PinConfiguration(void)
{//交叉开关配置
        XBR0 = 0x04;
        XBR1 = 0x00;
        XBR2 = 0x40;
        // P0 口分配状况
        // P0.0 = UART0 TX
        // P0.1 = UART0 RX
           //输出方式
           P74OUT = 0x08;       
}
(2)USART1NVIC_Configuration函数,由于C8051f020不需通道配置,这里仅仅简单的打开USART中断允许寄存器。有些微处理器没有发送完毕中断使能,而仅仅只有发送寄存器空的中断(特别是SPI通信中,很多处理器没有提供发送数据完成的中断),这时情况有些变化,不能在这里就使能了该中断,需要在发送开始的时候再使能,发送结束以后关闭这个中断。
void USART1NVIC_Configuration(void)
{
        IE|=0x90;
}
USART1_Configuration函数,配置波特率,发送接收模式等函数。必须使能发送和接收,
USART1_Configuration
{
        USART1PinConfiguration();
        TMOD &= 0x0f;//选择T1工作模式
        TMOD |= 0x20;
        SCON0 = 0x50;
        TH1 = 256 - 48000000 / 9600 / 32 / 12;
        TR1 = 1;
        USART1NVIC_Configuration();
}
(4)USART1发送函数
void USART1SendByte(unsigned char temp)
{
        SBUF0=temp;
}
(5)USART的接收函数
unsigned int USART1RecvByte(void)
{
        return SBUF0;
}
(6)USART的中断函数,在有些微处理器中,发送后中断通道与接收的通道不同,有些在同一个通道中,但是,这个函数的主要作用是,如果是发送中断,就必须调用USARTSendUpdate函数,如果是接受中断,就必须调用USARTRecvUpdate函数,至于清除中断标志等等代码,诸位应该知道,不再累述了。
void USART1_IRQHandler(void) interrupt 4 using 1
{
        if(TI0)
        {
                USART1SendUpdate();
                TI0=0;
        }
        else if(RI0)
        {
                USART1RecvUpdate();       
                RI0=0;
        }
}
(7)TIM2_Configuration函数,用于产生超时中断,配置的时候要注意,定时器的时间是不能定的太长,也不能太短,大约是接收一个字符的2倍时间左右,至于定时器怎么配置和怎么分频各位自己去看手册,这里不再累述。
void TIM2_Configuration(void)
{
        TMOD|=0x01;                                  //定时器016位模式
        CKCON|=        0x08;                          //定时器0使用系统时钟
        TIM2NVIC_Configuration();
}
(8)TIM2NVIC_Configuration函数,用于配置中断。
void TIM2_IRQHandler(void)         interrupt 1
{
        ET0|=0x02;                                //允许TIM0中断       
}
(9)TIM2_IRQHandler配置检测通信帧超时的定时器的中断通道,此时需要关闭定时器,以及调用USART1RecvResetBufferIndex函数,这个函数是收到一帧数据以后的处理函数。上次一些朋友说不知道怎么接收数据,这里就是。
void TIM2_IRQHandler(void)         interrupt 1
{
     TCON&=~0x20;
        USART1RecvResetBufferIndex();
}
(10)USART1ClearCounter函数,清零定时器的值
void USART1ClearCounter(void)
{
        TL0=0;
        TH0=0;       
}
(11)USART1StartCounter函数,定时器开始计时
void USART1StartCounter(void)
{
        TCON|=0x10;                                   //打开TIM0       
}
(12)USART1StartCounter函数,定时器开停止始计时
void USART1StopCounter(void)
{
        TCON&=~0x10;                                  //关闭TIM0
}
以下是正在写的内容。
  3.3 在8位AVR系列的Mega16、Mega128上的移植范例与Proteus仿真
  3.3 在ARM7TDMI内核的NXP系列的LPC2148上的移植范例与MKD仿真
  3.4 在Cortex-M3内核的STM32F系列的STM32F103ZET6上的移植范例
4 代码性能分析
  4.1 使用MDK4.02的性能分析器进行分析
  4.2 死区时间测试与可重入性分析
5 移植到SPI、IIC、CAN接口上
  5.1 移植到STM32F103ZET6上,驱动SPI存储器芯片AT45DB161上
  5.2 移植到STM32F103ZET6上,使用IIC总线,软件仿真
  5.3 移植到STM32F103ZET6上,使用CAN总线,软件仿真

出0入0汤圆

发表于 2011-10-7 10:13:05 | 显示全部楼层
坐沙发

出30入0汤圆

 楼主| 发表于 2011-10-7 10:28:52 | 显示全部楼层
唉,实在是没有时间整理,比较繁琐,每一个器件的移植都是比较繁琐的,需要看一堆数据手册,还要仿真。
如果你们需要移植的话,把通信的配置函数、中断函数、发送和接收的函数、以及一个定时器(除了USART需要其他的不要)的配置函数、中断函数、定时启动函数、定时停止函数、定时清零函数给我,我有空的话帮你们移植。不懂原理的同学请看这个帖子,这里不会再讲了。http://www.ourdev.cn/bbs/bbs_content_all.jsp?bbs_sn=4516795

出0入0汤圆

发表于 2011-10-7 10:37:56 | 显示全部楼层
谢谢啦,学习了~~

出0入0汤圆

发表于 2011-10-7 10:44:10 | 显示全部楼层
mark~,仔细看看

出0入0汤圆

发表于 2011-10-7 10:44:54 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-7 10:44:59 | 显示全部楼层
学习,Mark

出0入0汤圆

发表于 2011-10-7 10:48:58 | 显示全部楼层
mark,好资料

出0入0汤圆

发表于 2011-10-7 10:54:36 | 显示全部楼层
好东西
研究研究

出0入0汤圆

发表于 2011-10-7 11:37:38 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-7 11:40:00 | 显示全部楼层
能不顶吗你说!!

出0入0汤圆

发表于 2011-10-7 11:42:52 | 显示全部楼层
mark~

出0入0汤圆

发表于 2011-10-7 11:45:19 | 显示全部楼层
好资料,不错滴。。。

出0入0汤圆

发表于 2011-10-7 12:00:32 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-7 16:33:19 | 显示全部楼层
这么好的帖子,不顶对不住!

出0入0汤圆

发表于 2011-10-7 16:40:34 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-7 16:58:59 | 显示全部楼层
好东西

出0入0汤圆

发表于 2011-10-7 17:09:37 | 显示全部楼层
这个要顶

出0入9汤圆

发表于 2011-10-7 19:55:11 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-7 20:29:29 | 显示全部楼层
顶一下!

出0入0汤圆

发表于 2011-10-7 21:02:56 | 显示全部楼层
这个必须顶

出0入0汤圆

发表于 2011-10-7 22:13:21 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-7 22:18:47 | 显示全部楼层
MARK

出0入0汤圆

发表于 2011-10-8 11:48:23 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-8 12:37:17 | 显示全部楼层
学习了

出0入0汤圆

发表于 2011-10-8 15:32:50 | 显示全部楼层
学习了

出0入0汤圆

发表于 2011-10-8 15:58:21 | 显示全部楼层
不得不说,好贴强帖!要顶!

出0入0汤圆

发表于 2011-10-8 22:40:06 | 显示全部楼层
留念

出0入0汤圆

发表于 2011-10-8 22:48:21 | 显示全部楼层
留下记号````

出0入0汤圆

发表于 2011-10-9 09:20:10 | 显示全部楼层
绝对的好东西呀。一定要顶的!!!

出0入0汤圆

发表于 2011-10-9 14:05:48 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-9 17:33:31 | 显示全部楼层
好东西

出30入0汤圆

 楼主| 发表于 2011-10-10 00:24:35 | 显示全部楼层

使用CAN发送数据 (原文件名:使用CAN发送数据.JPG)

CAN 入门书.ourdev_683456A1XSAU.pdf(文件大小:1.87M) (原文件名:CAN 入门书..pdf)
CAN-bus 规范V2.0 版本ourdev_683457UUW5XP.pdf(文件大小:376K) (原文件名:CAN-bus 规范V2.0 版本.pdf)
STM32F103的CAN总线例程ourdev_683458CMB80X.rar(文件大小:216K) (原文件名:STM32F103的CAN总线.rar)

5.3 移植到STM32F103ZET6上,使用CAN总线,软件仿真
        CAN协议的重要性,适用性不言而喻,不懂得朋友们可以看这两册资料《CAN 入门书.》和《CAN-bus 规范V2.0 版本》。Stm32F103上面有一路CAN,通信配置接口十分简单,笔者多次在项目中使用,非常方便。
CAN1PinConfiguration函数
    由于我的项目上使能的CAN管脚的重映射,所以多了一个AFIO的使能。
void USART1PinConfiguration(void)
{
        GPIO_InitTypeDef  GPIO_InitStructure;

        /*使能CAN,AFIO的时钟 */
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

        /* PB8->CANRX */
        GPIO_InitStructure.GPIO_Pin   =  GPIO_Pin_8;
        GPIO_InitStructure.GPIO_Mode  =  GPIO_Mode_IPU;
        GPIO_Init(GPIOB, &GPIO_InitStructure);

        /* PB9->CANTX */
        GPIO_InitStructure.GPIO_Pin          =  GPIO_Pin_9;
        GPIO_InitStructure.GPIO_Speed =  GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Mode  =  GPIO_Mode_AF_PP;
        GPIO_Init(GPIOB, &GPIO_InitStructure);

        /* 使能PB8,PB9的接口重映射 */
        GPIO_PinRemapConfig(GPIO_Remap1_CAN, ENABLE);
}
(2)CAN1NVIC_Configuration函数,CAN的发送中断接口与接收终端不同,分别设置。
void CAN1NVIC_Configuration(void)
{
        NVIC_InitTypeDef NVIC_InitStructure;
        /* CAN-RX*/
        NVIC_InitStructure.NVIC_IRQChannel=USB_LP_CAN_RX0_IRQChannel;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);

        /* CAN-TX*/
        NVIC_InitStructure.NVIC_IRQChannel=USB_HP_CAN_TX_IRQChannel;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
}
(3)CAN1_Configuration函数,配置波特率,发送接收模式等函数。必须使能发送和接收,这里屏蔽位可以介绍一下,STM32的CAN滤波分为两种,一种是屏蔽列表模式,里面是能通过的ID标识符,但是STM32的ID列表屏蔽寄存器只有十四个,还有一种段屏蔽模式,可以在14位ID上选择那些屏蔽,那些可以放过,这个可以配置的范围就多多了。CAN_InitTypeDef 具体的内容得看STM32的数据手册。
CAN1_Configuration
{
        CAN_InitTypeDef   CAN_InitStructure;
        CAN_FilterInitTypeDef  CAN_FilterInitStructure;
        /* CAN register init */
          CAN_DeInit();
        CAN1PinConfiguration();

          CAN_StructInit(&CAN_InitStructure);
        CAN1NVIC_Configuration();

        /* CAN cell init */
          CAN_InitStructure.CAN_TTCM=ENABLE;
          CAN_InitStructure.CAN_ABOM=ENABLE;
          CAN_InitStructure.CAN_AWUM=ENABLE;
          CAN_InitStructure.CAN_NART=ENABLE;
          CAN_InitStructure.CAN_RFLM=DISABLE;
          CAN_InitStructure.CAN_TXFP=DISABLE;
          CAN_InitStructure.CAN_Mode=CAN_Mode_Normal;//CAN_Mode_LoopBack
          CAN_InitStructure.CAN_SJW=CAN_SJW_1tq;
          CAN_InitStructure.CAN_BS1=CAN_BS1_8tq;
          CAN_InitStructure.CAN_BS2=CAN_BS2_7tq;
          CAN_InitStructure.CAN_Prescaler=5;
          CAN_Init(&CAN_InitStructure);

        /* CAN filter init */
        //CAN_FilterMaskId寄存器的排列顺序是STDID[10:0]、EXTID[17:0]、IDE和RTR位
          CAN_FilterInitStructure.CAN_FilterNumber=0;
          CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
          CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;

        CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;                     //设置屏蔽位为0,普通标识符模式
          CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;                        //偏移值,左移五位,可以快捷设置相应位
          CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;                 //设置过滤器模式,低三位的数据不用过滤器


          CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
          CAN_FilterInitStructure.CAN_FilterFIFOAssignment=0;
          CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;
          CAN_FilterInit(&CAN_FilterInitStructure);

        CAN_ITConfig(CAN_IT_RQCP0|CAN_IT_RQCP1|CAN_IT_FMP0|CAN_IT_FMP1, ENABLE);}
(4)CAN1发送函数
void CAN1SendByte(CAN1SendTcb tcb)
{
        CanTxMsg  TxMessage;       
        u8 i;
//        TxMessage.StdId   =  0x000;                         //0~0x7FF   标准标识符
        if(tcb.ID&0x10000000)
        {
                tcb.ID&=~0x10000000;
                TxMessage.ExtId=(u32)tcb.ID;
        }
        else
        {
                TxMessage.StdId=(u16)tcb.ID;
        }
//        TxMessage.ExtId   =  0x00000;                         //0~0x3FFFF 扩展标识符
        TxMessage.IDE     =  CAN_ID_EXT;         //消息标识符类型
        TxMessage.RTR     =  CAN_RTR_DATA;         //消息的帧类型
        TxMessage.DLC     =  8;              //0~0x08    消息的帧长度
        for(i=0;i<8;i++) TxMessage.Data =  *((tcb).Index+i);       
        CAN_Transmit(&TxMessage);  (5)USART的接收函数
unsigned int USART1RecvByte(void)
{
        return SBUF0;
}
CAN1接收函数 CAN接口接收结构体也比较简单,见下面的一部分。具体的值对应着《CAN入门手册》,非常简单。
typedef struct
{
  u32 StdId;//标准标识符
  u32 ExtId;//扩展标识符
  u8 IDE;
  u8 RTR;
  u8 DLC;
  u8 Data[8];//数据室放在这里
  u8 FMI;
} CanRxMsg;
以上是CAN接口接收结构体定义
unsigned int CAN1RecvByte(void)
{
    RxMessage.StdId=0;
        CAN_Receive(CAN1,CAN_FIFO0, &RxMessage);   
    USART1WriteDataToBuffer(RxMessage.Data,RxMessage.DLC);
}
(6)CAN的中断函数,在有些微处理器中,发送后中断通道与接收的通道不同,有些在同一个通道中,但是,这个函数的主要作用是,如果是发送中断,就必须调用CANSendUpdate函数,如果是接受中断,就必须调用CANRecvUpdate函数,至于清除中断标志等等代码,诸位应该知道,不再累述了。这里加入静态变量flag是因为有时设置CAN中断接口以后就会进入中断。
void USB_HP_CAN_TX_IRQHandler(void)
{       
        static u8 flag=0;
//        if(flag==0){flag=1;return;}
        if(CAN_GetITStatus(CAN_IT_RQCP0)==SET)
        {
                CAN_ClearITPendingBit(CAN_IT_RQCP0);
                CAN1SendUpdate();
        }
}
void USB_LP_CAN_RX0_IRQHandler(void)
{
        static u8 flag=0;
//        if(flag==0){flag=1;return;}

        CAN1SendUpdate();
}
(7)TIM2_Configuration函数,用于产生超时中断,配置的时候要注意,定时器的时间是不能定的太长,也不能太短,大约是接收一个字符的2倍时间左右,至于定时器怎么配置和怎么分频各位自己去看手册,这里不再累述。
void TIM2_Configuration(void)
{
        TMOD|=0x01;                                  //定时器016位模式
        CKCON|=        0x08;                          //定时器0使用系统时钟
        TIM2NVIC_Configuration();
}
(8)由于CAN有非常完整的通信协议,所以不需要定时器,所以移植到这里就不需要在写了,TIM2NVIC_Configuration函数,用于配置中断。
(9)TIM2_IRQHandler
(10)CANClearCounter函数,清零定时器的值
(11)CANStartCounter函数,定时器开始计时
(12)CANStartCounter函数,定时器开停止始计时
        这里有一个CAN例程,我现在离开学校了没有CAN小板了(以前的那个是FreeRTOS用消息队列内存池做的,太大了,穿不上来),这个版本没有经过测试,用于工程的同学还是自己测试一下。
        这个例程是CAN发送与USART共用的,但是不是使用同一个内存管理区,因为CAN总线的一帧直接最大是8个字节,USART可以做的很大。而对于SPI而言,在使用页存储的时候是512字节。

出0入0汤圆

发表于 2011-10-10 08:10:12 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-10 08:19:45 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-10 08:27:43 | 显示全部楼层
好东西,mark

出0入0汤圆

发表于 2011-10-10 08:42:48 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-10 08:43:05 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-10 08:48:09 | 显示全部楼层
不但有程序还有心得体会。裤子应该没问题!

出0入0汤圆

发表于 2011-10-10 10:51:07 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-10 13:24:42 | 显示全部楼层
不错啊,楼主的这种分享精神可嘉!

出0入0汤圆

发表于 2011-10-10 22:50:39 | 显示全部楼层
留印

出0入0汤圆

发表于 2011-10-11 11:19:51 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-11 12:06:27 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-11 12:16:17 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-12 08:12:55 | 显示全部楼层
mark 通信软件

出0入0汤圆

发表于 2011-10-12 08:20:09 | 显示全部楼层
好文,这个要 mark 通信 串口 环形队列

出0入0汤圆

发表于 2011-10-12 08:29:10 | 显示全部楼层
MARK

出0入0汤圆

发表于 2011-10-12 08:32:50 | 显示全部楼层
marK!

出75入4汤圆

发表于 2011-10-12 08:36:04 | 显示全部楼层
这个该给条裤子穿穿

出30入0汤圆

 楼主| 发表于 2011-10-12 23:49:25 | 显示全部楼层
我有空把我的CAN总线的板子和stm32控制器的板子挂出来,板子使用了UCOS操作系统,带4G的SD卡,挂FATFS操作系统,带TXT解码程序,BitMap和JPEG解码,以及我自己写的GUI(可惜不是很好)。
三一真的是好累,白天培训晚上加班,回来就十点钟了,早上六点半起床------

出30入0汤圆

 楼主| 发表于 2011-10-13 00:15:35 | 显示全部楼层

CAN收发器电路图 (原文件名:CAN收发器电路图.JPG)


CAN收发器原理图 (原文件名:CAN收发器原理图.JPG)


STM控制板电路图 (原文件名:STM控制板电路图.JPG)


STM控制板原理图 (原文件名:STM控制板原理图.JPG)

出30入0汤圆

 楼主| 发表于 2011-10-13 00:17:03 | 显示全部楼层
STM控制板电路图(99SE)ourdev_684443HMGEGE.rar(文件大小:1023K) (原文件名:STM控制板电路图(99SE).rar)
STM控制板电路图(AD)ourdev_684444XJMS2G.rar(文件大小:979K) (原文件名:STM控制板电路图(AD).rar)

出0入0汤圆

发表于 2011-10-13 09:17:48 | 显示全部楼层
很好的例程,谢谢楼主~~

出0入0汤圆

发表于 2011-10-13 09:22:12 | 显示全部楼层
怎么联系不到楼主啊?邮箱发了没反应啊!

加我QQ 11051410 聊聊啊

这个机制用在STM32 和 GPRS通讯中很好啊!

出0入0汤圆

发表于 2011-10-13 10:11:55 | 显示全部楼层
想请教你一下,我现在用STM32F103C8T6这颗芯片。
用到两个串口,一个433M,一个GPRS。
都涉及到串口收发数据的问题。
发送采用查询方式
接收采用中断方式
现在只开了一个数组
但是每次只能处理一条,处理完再处理新来的,有时候一下子来就处理不了,丢包了。
现在看到你的机制非常好,但是不会用,想请教下。
我这个是2个串口接收时候需要用到这个机制。
请问哪里需要修改?
是不是要开两个1K的缓冲?

出50入0汤圆

发表于 2011-10-13 10:18:13 | 显示全部楼层
mark

出30入0汤圆

 楼主| 发表于 2011-10-13 23:43:35 | 显示全部楼层
我现在天天加班,很忙,这个帖子把使用过程写的很详细,你可以看看。你也可以在我的另外一个帖子中找STM32的例程与资料,那个主题是我发的:http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=4516795&bbs_page_no=1&search_mode=3&search_text=linquan315&bbs_id=9999

出0入0汤圆

发表于 2011-10-14 00:21:19 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-10-14 00:57:52 | 显示全部楼层
好帖,留印

出0入0汤圆

发表于 2011-10-14 08:42:37 | 显示全部楼层
谢谢。“”

出0入0汤圆

发表于 2011-10-14 09:18:37 | 显示全部楼层
这个要顶!必须的

出0入0汤圆

发表于 2011-10-14 09:24:49 | 显示全部楼层
顶,是必须的。很好!!!

出0入0汤圆

发表于 2011-10-14 10:01:17 | 显示全部楼层
这个必须要标记,顶LZ,很清晰的思维习惯!

出0入0汤圆

发表于 2011-10-18 09:20:00 | 显示全部楼层
学习。

出0入0汤圆

发表于 2011-10-18 12:51:54 | 显示全部楼层
谢谢楼主,学习ing!!

出0入0汤圆

发表于 2011-10-18 14:04:42 | 显示全部楼层
在嵌入式系统中,往往使用静态内存管理会比较简单高效,建议改成静态的环形队列,维护几个指针就可以了,不用动态内存释放和申请。

出0入0汤圆

发表于 2011-10-18 16:49:35 | 显示全部楼层
mark!!!

出0入0汤圆

发表于 2011-10-18 17:10:01 | 显示全部楼层
MARK!! 通信方案软件设计(环形动态申请内存,支持USART+IIC+SPI+CAN协议

出0入0汤圆

发表于 2011-10-18 19:30:16 | 显示全部楼层
学习!

出30入0汤圆

 楼主| 发表于 2011-10-18 23:45:41 | 显示全部楼层
加IIC的移植模板,全部是用中断完成,占用极低的内存,同时针对aT24C16等芯片全部实行了页编写,其效率大大高于单字节的编写。由于最近实在太忙,工程文档稍后奉上。
Mega16的IIC(ICC版本)ourdev_686213N8TZGQ.rar(文件大小:959K) (原文件名:ICCAVR.rar)

在任意数量数据 (原文件名:1.JPG)


在任意数量数据 (原文件名:2.JPG)


接收任意数量数据 (原文件名:3.JPG)


接收任意数量数据 (原文件名:4.JPG)

出30入0汤圆

 楼主| 发表于 2011-10-19 21:59:17 | 显示全部楼层
回复【69楼】gghyoo
-----------------------------------------------------------------------

呵呵,静态的我早就实现了,为了更先进,我花了很大的气力整成了动态的内存分配,毫无疑问,动态分配比静态分配更好。
所以我十分不理解你为何要我换成静态的内存分配----------------
先进的东西怎能被落后的东西所取代呢?

出0入0汤圆

发表于 2011-10-19 22:13:29 | 显示全部楼层
好贴子

出0入0汤圆

发表于 2011-10-23 15:19:07 | 显示全部楼层
为什么这么好的帖子,阿莫不至酷呢?

出0入0汤圆

发表于 2011-10-25 10:26:27 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-11-1 08:43:07 | 显示全部楼层
cool!

出75入4汤圆

发表于 2011-11-1 09:27:40 | 显示全部楼层
这个要认真学习下

出0入0汤圆

发表于 2011-11-1 11:49:55 | 显示全部楼层
学习一下,谢谢你。。

出0入0汤圆

发表于 2011-11-2 22:27:50 | 显示全部楼层
OSMEMTcb *OSMemCreate(u8 *ptr,u8 blksize,u8 nblks,u8 *err)
{
        u8 *link=(ptr+blksize);
        void **plink=(void **)ptr;
        u16 i=0;
        u16 j=0;
        /*首先查找空白的内存管理区*/
        while((OSMemTcb.OSMemFreeList!=(u8*)0)&&(i<=OS_MEM_MAX))               
        {                                                                                                               
                i++;
        }
        if(i>=OS_MEM_MAX) //?????????????
         {*err=0xff;return (OSMEMTcb *)0;}                //内存块管理区分配不到,返回空指针                                               
        OSMemTcb.OSMemFreeList=ptr;                                                        //内存块管理区的内存的指针
        OSMemTcb.OSMemBlkSize=blksize;                                                //内存块管理区的每个内存块的字节
        OSMemTcb.OSMemNBlks=nblks;                                                        //内存块管理区的内存块的总数量
        OSMemTcb.OSMemFreeNBlks=nblks;                                                //内存块管理区的空白内存块的数量
        j=i;
        OSMemInit(ptr,blksize*nblks);                                                        //将内存块内的数据初始化
        for(i=0;i<nblks-1;i++)                                                                        //
        {
                *plink=(void *)(link);                                                                //该内存块的地址存放的是第二片内存区的首地址
                plink=(void **)(link);                                                                //将二维指针定位到框的首位               
                link+=blksize;                                                                                //一维指针重新定位
        }
//        *plink=(void *)0;                                                                                //最后一块内存区的下一个地址块的数据为0
        return (OSMEMTcb *)(OSMemTcb+j);                                                                                 //返回内存块管理区的首地址
}       

上面打问号这里是不是i恒等于1?这样是不是失去全局性?

出0入0汤圆

发表于 2011-11-2 22:49:15 | 显示全部楼层
伤心病狂的MARK.....
好帖。

出0入0汤圆

发表于 2011-11-3 12:41:17 | 显示全部楼层
回复【楼主位】linquan315  
-----------------------------------------------------------------------

mark

出30入0汤圆

 楼主| 发表于 2011-11-6 01:39:58 | 显示全部楼层
回复【81楼】clever0725
-----------------------------------------------------------------------

不是的,这个i是判定内存管理区的已经使用了多少。
至于可重入性,呵呵我这里没有加入------
可以使用UCOS的方法但是每个芯片都不同,看管们移植起来不方便,所以没有加入。

出0入33汤圆

发表于 2011-11-6 09:44:21 | 显示全部楼层
强帖收藏

出0入0汤圆

发表于 2011-11-6 09:49:12 | 显示全部楼层
不知道 楼主是怎么做的 我自己的环形队列做法是   直接做一个结构体 看我的结构体声明就知道意思了

#define MAX_UART_DATA_LENGTH 16

#pragma pack(1)
typedef struct
{
        volatile unsigned char        Header;
        volatile unsigned char        Command;
        volatile unsigned char        Length;
        volatile unsigned char        DATA[MAX_UART_DATA_LENGTH+2];
} UART_Message_Type;

typedef struct
{
        volatile unsigned char                 Status;
        volatile UART_Message_Type        Message;
}UART_Buffer_Member_Type;

#define UART_BUFFER_SIZE (10)

typedef struct
{
        volatile unsigned char                                 Count;
        volatile unsigned char                                MaxHistory;
        volatile unsigned char                          UART_WRITE_INDEX;
        volatile unsigned char                          UART_READ_INDEX
        volatile UART_Buffer_Member_Type        Message[UART_BUFFER_SIZE];
}UART_Buffer_Type;
#pragma pack()

Count 描述队列使用情况
MaxHistory 描述曾经使用的最大数
WRITE_INDEX 写入数据的下标
READ_INDEX  读出数据的下标
Status      该块的状态描述,含空、接收中、发送中、待处理
目前中断里写数据  主程序里发数据,目前运行良好,未出现数据未同步的情况  至于代码 写起来很简单 其他协议类似

出0入0汤圆

发表于 2011-11-6 11:32:46 | 显示全部楼层
好,写的非常好

出0入0汤圆

发表于 2011-11-6 12:06:24 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-11-8 11:47:50 | 显示全部楼层
楼主这个一定要mark

出0入0汤圆

发表于 2011-11-8 11:49:35 | 显示全部楼层
mark~,仔细看看

出0入0汤圆

发表于 2011-11-8 13:35:06 | 显示全部楼层
mark once

出0入0汤圆

发表于 2011-11-8 13:37:18 | 显示全部楼层
想法很好!~

出0入0汤圆

发表于 2011-11-9 11:35:19 | 显示全部楼层
回复【33楼】linquan315
-----------------------------------------------------------------------

很好,值得学习

出0入0汤圆

发表于 2011-11-10 17:55:04 | 显示全部楼层
markonce

出0入0汤圆

发表于 2011-11-10 20:06:02 | 显示全部楼层
顶一个~~

出0入0汤圆

发表于 2011-11-11 09:01:44 | 显示全部楼层
在AVR上面用动态内存?................

出0入0汤圆

发表于 2011-11-11 23:37:55 | 显示全部楼层
万恶的mark

出0入0汤圆

发表于 2011-12-7 15:41:11 | 显示全部楼层
mark!

出0入0汤圆

发表于 2011-12-16 22:12:48 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-12-16 23:13:15 | 显示全部楼层
good.mark...

出0入0汤圆

发表于 2011-12-20 14:00:37 | 显示全部楼层
mark

出0入0汤圆

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

本版积分规则

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

GMT+8, 2024-4-29 17:30

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

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