发布新日志

  • DEMO_MainMenu

    2014-03-11 13:50:59

    void DEMO_MainMenu(void)
    {
     //DemoRedraw();
     
     GUI_Init();
    //* Draw something on default layer 0
    GUI_SetBkColor(GUI_GREEN);
    GUI_Clear();
    GUI_DispStringHCenterAt("Layer 0", 100, 46);
    //* Draw something on layer 1
    GUI_SelectLayer(1); // Select layer 1
    GUI_SetBkColor(GUI_RED);
    GUI_Clear();
    GUI_SetColor(GUI_BLUE);
    GUI_FillRect(20, 20, 179, 79);
    GUI_SetColor(GUI_WHITE);
    GUI_SetTextMode(GUI_TM_TRANS);
    GUI_DispStringHCenterAt("Layer 1", 100, 46);
    while(1) {
    GUI_Delay(100);
    }
  • FreeRTOS中的软件定时器(Soft Timers)

    2014-03-11 13:30:17

    FreeRTOS内核不提供定时器的功能,但是它却提供了软件定时器的任务进程,这对于有些定时器资源比较紧张的硬件平台而言可有效的帮助解决这一问题。但同时需要注意的是软件定时器的精度是无法和硬件定时器相比较的,因为在软件定时器的定时过程中是极有可能被其它的任务或进程序所打断,由此就引起了时间精度不准的问题(个人实验所测,确实随着时间的推移,定时器精度确实有所下降)。
    关于FreeRTOS软件定时器的原理还未仔细研究源代码,所以具体不是很清晰,但通过官方帮助文档的介绍,软件定时器就是单独作的一个线程在运行,用户代码和软件定时器代码是通过定时器命令队列(Timer Command Queue)来进行通信与交流,当用户根据个人需要对软件定时器作相关的操作后,FreeRTOS内核都会向此队列发送命令,同时软件定时器进程会从这个共用队列中来提取命令,并作出相关的操作。
    其它FreeRTOS的软件定时器功能适用性并不是很高,最简单的应用如官方所说,就是在指定的时间间隔内来调用回调函数(callback Function)关于回调函数一定要注意的是不要尝试在回调函数中引用类似于vTaskDelay()或vTaskDelayUntil()等等阻塞函数,因为这样的话会严重的影响软件定时器的精度与可靠性。同样软件定时器还可配置在单次模式(One-Shot)或自动重载模式(Auto-Reload),具体做法和微控制器硬件定时器做法相差不大。软件定时器也具有运行过程中重设置(Reseting a Timer)功能,相对来说功能还算是比较全面。
    对于软件定时器的使用并不复杂,具体要配置如下宏参数:
    #define configUSE_TIMERS // 使能软件定时器 
    #define configTIMER_TASK_PRIORITY // 确定软件定时器进程优先级(根据具体应用而定,不要设的过低,否则精度也会随之下降)
    #define configQueue_LENGTH // 定时器命令队列(Timer Command Queue)长度
    #define configTIMER_TASK_STACK_DEPTH // 分配给软件定时器任务的内存大小
     
    FreeRTOS为软件定时器提供了如下API函数:
    xTimerCreate();
    xTimerlsTimerActive();
    xTimerStart();
    xTimerStop();
    xTimerChangePeriod();
    xTimerDelete();
    xTimerReset();
    xTimerStartFromISR();
    xTimerStopFromISR();
    xTimerChangePeriodFromISR();
    xTimerResetFromISR();
    xTimerGetTimerID();
    xTimerGetTimerDaemonTaskHandle();
  • uCGUI使用

    2014-03-08 14:27:42

    0:画图函数
       GUI_DrawRect:在当前窗口中的指定位置绘制矩形(不填充,画线颜色为前景色)
           void GUI_DrawRect(int x0, int y0, int x1, int y1);
       GUI_FillRect:在当前窗口中的指定位置绘制填充的矩形区域(填充前景色)
       GUI_ClearRect:清除矩形区域(为矩形区域填充背景颜色,图形界面用填充背景色达到清除屏幕的                               效果
    1:存储设备
        不使用存储设备时,绘制操作直接写入显示器。屏幕在执行绘制操作时随时更新,从而在进行各
    种更新时使屏幕闪烁。如果在此过程中使用存储设备,则所有绘制操作都在存储器中执行。仅在所有操作都完成后才将最终结果显示在屏幕上,其优点是没有闪烁。
        如果不使用存储设备,则可以看到一步步的绘制操作效果,缺点是会出现显示器闪
    烁。使用存储设备时,一次可见到所有例程的效果,就象单次操作一样,不能实际看见中间步骤。
        以下例程是在使用存储设备时通常会调用的,基本用法非常简单:
             1. 创建存储设备(使用GUI_MEMDEV_Create() )。
             2. 激活它(使用GUI_MEMDEV_Select() )。
             3. 执行绘制操作。
             4. 将结果复制到显示器中(使用GUI_MEMDEV_CopyToLCD() )。
             5. 不再需要它时,删除该存储设备(使用 GUI_MEMDEV_Delete() )。

    2:WM窗口管理器
       回调例程:回调例程由用户程序定义,指示在特定事件出现时图形系统调用特定的函数。它们通常用于在窗口内容更改时自动重绘窗口。窗口管理器的默认特性是向每个需要重绘的窗口发送一条 WM_PAINT 。
       当用户对窗口有操作时,WM会发送相应的消息给该窗口,窗口可通过回调函数根据消息直接对屏(没有用存储设备时)或对窗口的存储设备进行操作再拷贝到屏幕上,具体的消息说明可以参考emWin的中文手册。
       WM_SetCallback 
          设置窗口的回调例程:WM_CALLBACK* WM_SetCallback (WM_HWIN hWin, WM_CALLBACK* cb) 
          有了这个函数就可以动态的选择操作的重绘操作等消息的响应动作了。
      WM_SetDesktopColor
          设置桌面窗口的颜色:WGUI_COLOR WM_SetDesktopColor(GUI_COLOR Color); 
             桌面窗口由视窗管理器自动创建,总是覆盖整个显示区域。它始终是一个最底层的窗口,没有定义其          他窗口,它就是默认活动窗口。所有窗口都是桌面窗口的继承窗口。桌面窗口的默认设置不用于自身          重新着色。如果不调用此函数,则桌面窗口不进行重绘;因此其它窗口将保持可见,即使在将它们删          除之后。一旦使用此函数指定了颜色,则桌面窗口将进行自身重新着色。为了恢复默认设置,请调用          此函数并指定GUI_INVALID_COLOR 。
             对桌面窗口颜色设置后,需要调用WM_Exec(),通过执行回调函数重绘无效窗口(针对所有作业),这          样新的桌面窗口的颜色就会显现。
       GUI_Exec【重要、重要,进行操作动作后,一般都要会通过一个GUI延时函数(GUI_Delay)来间接调用该函数,也可以单独建立一个任务来执行GUI_Exec()函数
       GUI_Exec()可以理解为GUI执行函数,调用了很多操作函数或发生了相关的动作,最后都需要通过GUI_Exec()函数通过各个回调函数来一一执行,所以函数名叫GUI_Exec
       此函数将自动重复调用WM_Exec1(),直至完成所有作业 –  实质是直至返回0 值为止。建议调用函数GUI_Exec()作为代替。通常,此函数无需由用户应用程序调用它自动由GUI_Delay() 调用,所以一般作业后都调用一个GUI_Delay(),使作业显现出来。如果使用的是多任务系统,建议通过单独的任务执行此函数,如下所示:
          void ExecIdleTask(void) 
          {
              while(1) {
                 WM_Exec();//调用WM_Exec()完成到显示屏的显示作业(我的理解是:(类似于)调用该函数前,其他的函数执行绘制操作WM_Exec完成将结果复制到显示器中
              }
          }
        WM_InvalidateWindow:调用此函数会告诉WM 指定的窗口未更新
            WM_PAINT消息: 窗口变为无效并应重绘时,发送到窗口。
            void WM_InvalidateWindow(WM_HWIN hWin);
        
        WM_BringToTop:将窗口放在其同属窗口前面
    void WM_BringToTop(WM_HWIN hWin);该窗口将放置在其他所有同属窗口及父窗口的顶部,加入同属于A窗口的B、C两个子窗口,B先C后,则可以通过该函数将B又放在前面。
        WM_BringToBottom:窗口将放置在所有其他同属窗口的下面,但将留在其父窗口的前面。
          void WM_BringToBottom(WM_HWIN hWin); 
          例:
          WM_BringToBottom(_hWindow1);
          GUIDEMO_Delay(SPEED);
        WM_HideWindow:使指定窗口不可见。
          void WM_HideWindow(WM_HWIN hWin);调用此函数后,窗口不会立即“不可见”。在执行WM_Exec() 时,其它窗口的无效区域(出现在要隐藏窗口“后面”的区域)将重绘。隐藏父窗口时,父窗口上的子窗口也会消息。
         WM_HideWindow(hChild2);
         GUIDEMO_Delay(1000/3);
        WM_ShowWindow:使指定窗口显示。
         WM_ShowWindow(hWindow1);
         GUIDEMO_Delay(1000/3);
        WM_GetInsideRect:
          void WM_GetInsideRect(GUI_RECT* pRect);
          返回客户区的坐标,该区域由活动小工具尺寸减去边界尺寸确定。此函数向活动窗口发送一条消息,检
    索内部矩形。如果小工具不处理此消息(也即意味着小工具没有边界),则需使用 WM_GetClientRect函数计算出矩形。结果通过窗口坐标给出。也即, GUI_RECT结构中的 x0和y0相当于x 和y 的边界尺寸, x1和y1相当于窗口尺寸减去边界尺寸 -1。
           客户区:一个窗口的客户区简单地说是它的可使用区。如果一个窗口包括一个边框或标题栏,则客户区是内部的矩形区域。如果没有这样一个 边框或标题栏 ,则客户区等于窗口本身
       WM_GetWindowSizeX/WM_GetWindowSizeY:
          int WM_GetWindowSizeX(WM_HWIN hWin)    ...Y(WM_HWIN hWin) 
          返回窗口的水平尺寸(宽度)              ,返回窗口的垂直尺寸(高度)
          WM_GetWindowSizeX(pMsg->hWin);pMsg为发送给窗口的消息。
      WM_DefaultProc:窗口消息的默认处理函数
          void WM_DefaultProc(WM_MESSAGE* pMsg);
      WM_MoveTo():将指定窗口移动到某个位置
          void WM_MoveTo(WM_HWIN hWin, int x, int y);父窗口移动时,子窗口是跟着做相应移动;
    WM_MoveTo使用说明
       WM_MoveTo刚开始用的时候,移动一个窗口,窗口一直会留下运动的痕迹;
       后来学习别人的例程,了解到,所有窗口都是桌面窗口的继承窗口;因为窗口移动,不仅被移动的窗口要重绘,桌面窗口也要重绘,所以必须给桌面通过 WM_SetCallback(WM_GetDesktopWindow(), BkWindow)函数配置一个桌面的回调函数,在回调函数中处理重绘信息WM_PAINT时,要设置好背景色(与桌面窗口的背景一致),然后调用GUI_Clear(),这样移动窗口的移动痕迹就没有了,同理子窗口在父窗口中移动时,父窗口的WM_PAINT也要如此处理,即一个原则就是要在重绘时清除移动窗口所在的上层窗口,以去掉痕迹。
      WM_MoveWindow():将指定窗口移动某段距离
          void WM_MoveWindow(WM_HWIN hWin, int dx, int dy);
      WM_ResizeWindow:通过增加(或减少)给定差别更改指定窗口的尺寸
          WM_ResizeWindow(_hWindow2, -1, -1);
          GUI_Delay(tDiff);
      WM_DeleteWindow:删除一个窗口
    ///////////////窗口设计shil///////////////////
    下面就是一个窗口的一个设计,建立窗口后,在窗口的区域默认是黑色;且窗口使用存储设备;建好之后,是不会直接显示出来的,但可通过各种途径调用WM_EXEC()来完成作业(这里是GUIDEMO_Delay);
    建立好之后,在它的回调函数里面设置各个消息的处理操作;
    用户对操作的各种操作,都会向串口发送对应的消息;消息都可以通过WM_DefaultProc进行默认处理;如果要想达到一定效果可以分开处理,这里要重绘窗口,处理WM_PAINT消息,在这个消息处理中先设置好环境颜色,在窗口区域内画了一个矩形显示了汉子,做出一个彩色窗口。
    【改变窗口背景两种方法:1.在窗口的回调函数的WM_PAINT中先设置背景色,再GUI_Clear()
                            2.在窗口的回调函数的WM_PAINT中先设置背景色,再可以画框等
    【一个窗口会接收到不同的消息,用回调函数来处理消息是窗口最合适的选择,尤其是窗体重绘WM_PAINT】
    static void Window1(WM_MESSAGE* pMsg) {
      GUI_RECT Rect;
      int x, y;
      switch (pMsg->MsgId) {
      case WM_PAINT:
        WM_GetInsideRect(&Rect);
        GUI_SetBkColor(GUI_RED);
        GUI_SetColor(GUI_YELLOW);
        GUI_ClearRectEx(&Rect);
        GUI_DrawRectEx(&Rect);
        GUI_SetColor(GUI_WHITE);
        GUI_SetFont(&GUI_Font24_ASCII);
        x = WM_GetWindowSizeX(pMsg->hWin);
        y = WM_GetWindowSizeY(pMsg->hWin);
        GUI_DispStringHCenterAt("Window 1", x / 2, (y / 2) - 12);
        break;
      default:
        WM_DefaultProc(pMsg); //要处理WM_GetInsideRect发过来的消息(检索内部矩形的大小)
      }
    }
    static void cbChild(WM_MESSAGE* pMsg) {
      int x, y;
      switch (pMsg->MsgId) {
      case WM_PAINT:
        GUI_SetBkColor(GUI_WHITE);
        GUI_Clear(); //清除的范围是子窗口即该回调函数对应的窗口,其他的窗口均不受影响
        GUI_SetColor(GUI_RED);
        GUI_SetFont(&GUI_Font24_ASCII);
        x = WM_GetWindowSizeX(pMsg->hWin);
        y = WM_GetWindowSizeY(pMsg->hWin);
        GUI_DispStringHCenterAt("Child window", x / 2, (y / 2) - 12);
        break;
      default:
        WM_DefaultProc(pMsg);
      }
    }


    WM_HWIN hWindow1;
    hWindow1 = WM_CreateWindow( 50,  70, 165, 100, WM_CF_SHOW|WM_CF_MEMDEV , Window1, 0);
    GUIDEMO_Delay(1000/3);
    hChild = WM_CreateWindowAsChild(10, 50, 145, 40, hWindow1, WM_CF_SHOW | WM_CF_MEMDEV, cbChild, 0);
    GUIDEMO_Delay(1000);



    /////////////////////////////////////////////
    3:GUI环境设置
      背景、前景颜色设置(这里只是设置颜色,需要通过GUI_Clear()全部换成新的背景色)
          设置(还有有读取)当前背景颜色:GUI_COLOR GUI_SetBkColor(GUI_COLOR Color)
          设置(还有有读取)当前前景色:   void GUI_SetColor(GUI_COLOR Color);
     GUI_Clear()清除背景时,清除的范围是调用函数时所多对应的窗口,其他的窗口(包括桌面窗口)等均不受影响, 
      设置用于文本输出的字体
          GUI_SetFont() 设置当前字体:const GUI_FONT * GUI_SetFont(const GUI_FONT * pNewFont);
                         GUI_SetFont(&GUI_Font24_ASCII);
          GUI_SetDefaultFont() 设置默认字体

    4:通过触摸屏获取输入信息
      int CursorX 、CursorY ;
      CursorX = GUI_TOUCH_GetxPhys();
      CursorY = GUI_TOUCH_GetyPhys();
      GUI_CURSOR_SetPosition(CursorX,CursorY);
      GUI_Delay(100); //调用GUI_EXEC() 完成操作作业

      在UC/OS中,一般通过建立一个高优先级的任务,来通过GUI_TOUCH_Exec()扫描触摸屏,获得
    static void taskA (void *p_arg) 
    {
    u8 tick=0;
    (void)p_arg;
    while(DEF_TRUE) 
     {
     OSTimeDlyHMSM(0,0,0,10); 
     GUI_TOUCH_Exec(); 
      }
    }
    GUI_TOUCH_Exec()在这个函数中调用TOUCH_X_MeasureY  TOUCH_X_MeasureX检测触摸屏的触摸位置,保存在xPhys、yPhys 中,应用层可以通过GUI_TOUCH_GetyPhys来获取这个值。
    yPhys = TOUCH_X_MeasureY();
    xPhys = TOUCH_X_MeasureX();

    int  GUI_TOUCH_GetyPhys(void) {
      return yPhys;
    }
    5:窗口对象(小工具)
     1:BUTTON:可按下的按钮。按钮上可显示文本或位图
            int Key;
            BUTTON_Handle hButton1;
            hButton1 = BUTTON_CreateEx(10,10,50,50,0,WM_CF_SHOW,0,'1'); 
            BUTTON_SetText(hButton1,"hello!");//按键上显示的文字
            do {
                Key = GUI_GetKey();//BUTTON创建后,会响应触摸屏的触摸消息;通过GUI_GetKey()可以获取键缓冲器中的字符编码;若未缓冲键,则返回值为0,缓冲键可以理解为被按下的 BUTTON,若有BUTTON被按下,它的ID值就会传送到键缓冲器中,用GUI_GetKey可以读到。  
                代码://此处需根据Key值或超时退出while循环等待
                GUI_Delay(100);//内部调用GUI_Exec(); 有事件或操作任务需要通过GUI_Exec()来最终执行,
             } while()
     
     2: EDIT:文本编辑框控件,编辑区通常用作输入文本的主要用户接口
            EDIT_Handle   hEdit;
            hEdit = EDIT_Create( 200, 114, 100, 25, ' ', 80, 0 );
            EDIT_SetFont(hEdit, &GUI_Font8x16);
            GUIDEMO_Delay(100);
    do {
      Key = GUIDEMO_WaitKey();//内部包含GUI_GetKey()与GUI_Exec(),获取缓冲键的ID
      switch (Key) {
      case 0:
      case GUI_ID_CANCEL:
       break;
      default:
      EDIT_AddKey(hEdit, Key);//在EDIT中显示Key
       }
    } while ((Key != '3') && (Key!=GUI_ID_CANCEL));
    /////EDIT_GetText() 获取用户输入
  • 【STM32F429-DISCOVERY学习笔记】STM32F429驱动SDRAM(IS42S16400J)详解

    2014-03-07 20:38:02

      驱动SDRAM的时序比较的麻烦一些,不像驱动SRAM,非常简单,网上搜索一下,估计有非常多的FPGA驱
    动SDRAM的资料,而且是各种的给你讲时序问题,现在F429/439集成了控制器以后就方便很多了,用户只需配
    相应的寄存器即可,这里向大家推荐一篇文章,强烈的推荐,不懂SDRAM为何物的,一定要看看。
    《高手进阶,终极内存技术指南——完整/进阶版》 http://bbs.armfly.com/read.php?tid=1930
    1.  学习SDRAM驱动前的准备工作
        学习SDRAM前搞清楚两个问题,一个是SDRAM的基本原理,还有一个就是那几个关键的参数,参数是F429
    配置SDRAM的关键,这几个参数大概知道是什么意思就行了,配置的时候,根据SDRAM的手册配置一下就OK
    了。在STM32F429/439的数据手册里关键参数说明,F429/439是把这几个关键的参数做到了一个存器里面
    了,这些参数,手册上面有一些英文说明,但比较的笼统。

    我推荐的那篇文章,建议大家一定要看,别的可以不看,这个必须得看,讲的实在太好了,我这里把一些关键的
    参数摘录出来:
    tRCD:
           在发送列读写命令时必须要与行有效命令有一个间隔,这个间隔被定义为tRCD,即RAS to CAS
    Delay(RAS至CAS延迟),大家也可以理解为行选通周期,这应该是根据芯片存储阵列电子元件响
    应时间(从一种状态到另一种状态变化的过程)所制定的延迟。tRCDSDRAM的一个重要时序参数
    ,可以通过主板BIOS经过北桥芯片进行调整,但不能超过厂商的预定范围。广义的tRCD以时钟周期
    tCKClock Time)数为单位,比如tRCD=2,就代表延迟周期为两个时钟周期,具体到确切的时
    ,则要根据时钟频率而定,对于PC100SDRAMtRCD=2,代表20ns的延迟,对于PC133则为15ns
    CL(CAS Latency):
            在选定列地址后,就已经确定了具体的存储单元,剩下的事情就是数据通过数据I/O通道(DQ)输
    出到内存总线上了。但是在CAS发出之后,仍要经过一定的时间才能有数据输出,从CAS与读取命令发
    出到第一笔数据输出的这段时间,被定义为CL(CAS Latency,CAS潜伏期)。由于CL只在读取时出现,
    以CL又被称为读取潜伏期(RL,Read Latency)。CL的单位与tRCD一样,为时钟周期数,具体耗时
    时钟频率决定。
           数据写入的操作也是在tRCD之后进行,但此时没有了CL(记住,CL只出现在读取操作中)。
    tWR:
            数据并不是即时地写入存储电容,因为选通三极管(就如读取时一样)与电容的充电必须要有一段
    时间,所以数据的真正写入需要一定的周期。为了保证数据的可靠写入,都会留出足够的写入/校正时间
    tWRWriteRecovery Time),这个操作也被称作写回(Write Back)。
    tRP:
            在发出预充电命令之后,要经过一段时间才能允许发送RAS行有效命令打开新的工作行,这个间隔被
    称为tRP(Precharge command Period,预充电有效周期)。和tRCDCL一样,tRP的单位也是时钟周
    期数,具体值视时钟频率而定。
    我这里就先贴上这几个参数,其它的参数,大家可以查阅相关的资料。
    2.  F429/439手册中对SDRAM的介绍
           如果你看了我在前面推荐的那个文章,现在看这个手册还是比较容易的。我这里就象征性的贴一下F429
    439自带SDRAM控制器的特性和引脚,其它的大家自己看手册就行。



    3.  F429/439驱动SDRAM详细过程
    说明:以官方的Demonstration做例子


    第一步:F429的时钟配置
            F429/439支持最高主频180Mhz,相比于407只提高了12M, 感觉劲还是不足,传说STM32F5系列将
    做到250MHz,就相当的给力了。
    F429配置过程和以前芯片的配置过程是一样的,我这里主要是想通过这个配置得到SDRAM的时钟频率, 先
    说明一下这个开发板外接的是8MHz的晶振,通过锁相环得到180的主频。

    #define PLL_SOURCE_HSE    // HSE (8MHz) used to clock the PLL, and the PLL is used as system clock source
    /*  PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N  */
    #if defined  (PLL_SOURCE_HSI)
    #define PLL_M      16
    #else
    #define PLL_M      8
    #endif
    #define PLL_N      360
    /* SYSCLK = PLL_VCO / PLL_P */  
    #define PLL_P      2
    /* USB OTG FS, SDIO and RNG Clock =  PLL_VCO / PLLQ */
    #define PLL_Q      7
    由上面的公式得到 :SYSCLK = PLL_VCO / PLL_P =( (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N)/PLL_P  = 180
    /* HCLK = SYSCLK / 1 = 180 */RCC->CFGR |= RCC_CFGR_HPRE_DIV1;  
    /* PCLK2 = HCLK / 2 = 90 */RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;  
    /* PCLK1 = HCLK / 4 = 45 */RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;
    第二步:引脚配置
            驱动前要先搞清楚一个问题,驱动SDRAM的行选和列选的地址线是分时复用的,和SRAM不同
    需要完整的地址线才可以访问各个地址空间,官方提供的原理图


    关于这个芯片的特点:


    标准的SDRAM一般都是4个BANK,这个芯片也不例外,芯片的总容量
    1Mbitx 16-bit x 4-bank = 67,108,864 bits = 64-Mbit ,每个BANK的组成
    4096rows x 256 columns x 16 bits, 这个比较重要,配置的时候要用到,也就是
    12行8列。
    第三步:FMC配置
    这里FMC驱动SDRAM只支持两种频率,分别是
    SDCLK period = 2 x HCLK periods
    SDCLK period = 3 x HCLK periods
    根据我们上面的配置,HCLK是180MHz,这里SDCLK就是2分频或者3分频,官方提供的
    这个例子,在注释上面有误,他们是按照主频168MHz,2分频是SDCLK = 84MHz注释的,
    不过不影响使用,只是注释上面是这样的。按照90MHz的话,每个时钟周期就是11.1ns
    7个SDRAM关键参数的配置,下面一个一个的说。
    1.  TMRD
    /* TMRD: 2 Clock cycles */
    FMC_SDRAMTimingInitStructure.FMC_LoadToActiveDelay = 2;
    这里为什么成2,查手册, 手册上提供的是三种速度等级时提供的参数,我们这里是用
    的90MHz,也取2个肯定是没问题的。


    2. TXSR
    /* TXSR: min=70ns (6x11.10ns) */
    FMC_SDRAMTimingInitStructure.FMC_ExitSelfRefreshDelay = 7;
    开发板上面用的SDRAM速度等级的7,最高工作频率时143MHz


    3. TRAS
    /* TRAS: min=42ns (4x11.10ns) max=120k (ns) */
    FMC_SDRAMTimingInitStructure.FMC_SelfRefreshTime = 4;


    4. TRC
    /* TRC:  min=63 (6x11.10ns) */        
    FMC_SDRAMTimingInitStructure.FMC_RowCycleDelay = 7;    


    5. TWR
    /* TWR:  2 Clock cycles */
    FMC_SDRAMTimingInitStructure.FMC_WriteRecoveryTime = 2;  

    6. TRP
    /* TRP:  15ns => 2x11.10ns */
    FMC_SDRAMTimingInitStructure.FMC_RPDelay = 2;


    7. TRCD              
    /* TRCD: 15ns => 2x11.10ns */
    FMC_SDRAMTimingInitStructure.FMC_RCDDelay = 2;


    时序设置好以后就是SDRAM控制器的配置。

    /* 支持两个SDRAM的BANK,这里使用的是bank2  */
      FMC_SDRAMInitStructure.FMC_Bank = FMC_Bank2_SDRAM;
      /* 根据这个SDRAM的特性是12行,8列,在这里配置一下 */
      FMC_SDRAMInitStructure.FMC_ColumnBitsNumber = FMC_ColumnBits_Number_8b;
      FMC_SDRAMInitStructure.FMC_RowBitsNumber = FMC_RowBits_Number_12b;
      /* 数据位宽是16 */
      FMC_SDRAMInitStructure.FMC_SDMemoryDataWidth = SDRAM_MEMORY_WIDTH;
      /* 此芯片支持4个bank */
      FMC_SDRAMInitStructure.FMC_InternalBankNumber = FMC_InternalBank_Number_4;
      /* 设置CAS的延迟是3 */
      FMC_SDRAMInitStructure.FMC_CASLatency = SDRAM_CAS_LATENCY;
      FMC_SDRAMInitStructure.FMC_WriteProtection = FMC_Write_Protection_Disable;
      /* 配置SDCLK的时钟频率 */
      FMC_SDRAMInitStructure.FMC_SDClockPeriod = SDCLOCK_PERIOD;  
      /* 禁止读取时的突发模式 */
      FMC_SDRAMInitStructure.FMC_ReadBurst = SDRAM_READBURST;
      /* 设置ReadPipe时的延迟  */
      FMC_SDRAMInitStructure.FMC_ReadPipeDelay = FMC_ReadPipe_Delay_1;
      FMC_SDRAMInitStructure.FMC_SDRAMTimingStruct = &FMC_SDRAMTimingInitStructure;
    第四步:SDRAM初始化

    1.  使能时钟
      FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_CLK_Enabled;  
      FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2;
      FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1;  FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;
      /* Wait until the SDRAM controller is ready */  

      while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
      {  }
      /* Send the command */  

      FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
    2.  插入100ms的延迟

      __Delay(10);
    3.  预充电配置  
      /* Configure a PALL (precharge all) command */  
      FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_PALL;
      FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2;  
      FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1;
      FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;  
      /* Wait until the SDRAM controller is ready */
      while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)  
      {
      }  
      /* Send the command */
      FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
    4. 自动刷新配置
      /* Configure a Auto-Refresh command */  
      FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_AutoRefresh;
      FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2;  
      FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 4;
      FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;  
      /* Wait until the SDRAM controller is ready */
      while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)  
      {
      }  
      /* Send the  first command */
      FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);  
      /* Wait until the SDRAM controller is ready */  
      while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
      {  }
      /* Send the second command */  
      FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
    5. 配置外部模式寄存器  
      /* Program the external memory mode register */
      tmpr = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_2          |                  
                       SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL   |
                       SDRAM_MODEREG_CAS_LATENCY_3           |                  
                       SDRAM_MODEREG_OPERATING_MODE_STANDARD |
                       SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;  
      /* Configure a load Mode register command*/  
      FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_LoadMode;
      FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2;  
      FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1;
      FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = tmpr;  
      /* Wait until the SDRAM controller is ready */
      while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)  
      {
      }  
      /* Send the command */
      FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
    6. 刷新频率设置
      /* Set the refresh rate counter */  
      /* (7.81 us x Freq) - 20 */
      /* Set the device refresh counter */  
      FMC_SetRefreshCount(683);
      /* Wait until the SDRAM controller is ready */  
      while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET)
      {  }
    关于刷新频率的的数值是这么得到的,这里得详细的说一下,目前公认的标准是,存储体中电容的数据有效保存期上限是
    64ms(毫秒,1/1000秒),也就是说每一行刷新的循环周期是64ms。这样刷新速度就是:行数量/64ms。我们在看内
    存规格时,经常会看到4096 Refresh Cycles/64ms或8192 RefreshCycles/64ms的标识,这里的4096与8192就代表这
    个芯片中每个L-Bank的行数。刷新命令一次对一行有效,发送间隔也是随总行数而变化,4096行时为15.625μs(微秒,
    1/1000毫秒),8192行时就为7.8125μs。        
           SDRAM的手册上说是4096 refresh cycles every 64 ms,这里就应该是15.525us,而注释上面是按8192行计算
    ,郁闷,有时间得研究研究。


    到这里基本就设置完了,剩下就可以像使用SRAM一样,使用SDRAM了。


  • STM32F1和STM32F4 区别

    2014-03-07 20:21:50

    STM32F4相对于STM32F1的改进不只一点点,为了便于初学者了解,我们比对相关资料将改进点进行了汇总。

    STM32F1和STM32F4 区别   (安富莱整理)

    u  F1采用Crotex M3内核,F4采用Crotex M4内核。
    u  F1最高主频 72MHz, F4最高主频168MHz。
    u  F4具有单精度浮点运算单元,F1没有浮点运算单元。
    u  F4的具备增强的DSP指令集。F4的执行16位DSP指令的时间只有F1的30%~70%。F4执行32位DSP指令 的时间只有F1的25%~60%。
    u  F1内部SRAM最大64K字节, F4内部SRAM有192K字节(112K+64K+16K)。
    u  F4有备份域SRAM(通过Vbat供电保持数据),F1没有备份域SRAM。
    u  F4从内部SRAM和外部FSMC存储器执行程序的速度比F1快很多。F1的指令总线I-Bus只接到Flash上,从SRAM和FSMC取指令只能通过S-Bus,速度较慢。F4的I-Bus不但连接到Flash上,而且还连接到SRAM和FSMC上,从而加快从SRAM或FSMC取指令的速度。
    u  F1最大封装为144脚,可提供112个GPIO;F4最大封装有176脚,可提供140个GPIO。
    u  F1的GPIO的内部上下拉电阻配置仅仅针对输入模式有用,输出时无效。而F4的GPIO在设置为输出模式时,上下拉电阻的配置依然有效。即F4可以配置为开漏输出,内部上拉电阻使能,而F1不行。
    u  F4的GPIO最高翻转速度为84MHz,F1最大翻转速度只有18MHz。
    u  F1最多可提供5个UART串口,F4最多可以提供6个UART串口。
    u  F1可提供2个I2C接口,F4可以提供3个I2C接口。
    u  F1和F4都具有3个12位的独立ADC,F1可提供21个输入通道,F4可以提供24个输入通道。F1的ADC最大采样频率为1Msps,2路交替采样可到2Msps(F1不支持3路交替采样)。F4的ADC最大采样频率为2.4Msps,3路交替采样可到7.2Msps。
    u  F1只有12个DMA通道,F4有16个DMA通道。F4的每个DMA通道有4*32位FIFO,F1没有FIFO。
    u  F1的SPI时钟最高速度为 18MHz, F4可以到37.5MHz。
    u  F1没有独立的32位定时器(32位需要级联实现),F4的TIM2和TIM5具有32位上下计数功能。
    u  F1和F4都有2个I2S接口,但是F1的I2S只支持半双工(同一时刻要么放音,要么录音),而F4的I2S支持全双工,放音和录音可以同时进行。
  • emWin 介绍

    2014-03-07 17:18:32

    都是Segger 公司的产品。
    Segger公司的图形系统正式名字叫 emWin。
    ucGui 是Segger公司为 Micrium 公司(推出uCOS-II 和 uCOS-III的公司)量身定制的 emWin,换个名字而已。不过目网上流传的ucGui版本相对比较陈旧,还是 3.X 的版本。
    目前 emWin 版本已经到 5.X 了,底层驱动接口变化有点大。

    最近,Segger 公司为 ST公司量身定制的 emWin 叫 STemWin, 更加适合STM32使用。

    (ST)进一步扩大其微控制器性能领先优势,让智能化技术无处不在,最新的STM32 ARM Cortex微控制器拥有市场领先的性能和图形处理功能,意法半导体与SEGGER达成的软件协议让用户界面可提供更丰富的功能.

    横跨多重电子应用领域、半导体供应商意法半导体宣布新系列微控制器首批样片现已上市。新系列微控制器整合目前性能最高的180MHz ARM Cortex-M4内核和图形增强技术,实现了功能更丰富的用户体验,意法半导体还携手市场领先的软件厂商SEGGER为嵌入式图形开发提供更多的软件支持。此外,意法半导体宣布集成超大存储容量的168MHz系列已投入量产,该产品在2012年起为主要客户提供样片。


    全新180MHz STM32F429/39系列进一步扩大STM32系列在Cortex-M微控制器市场的性能领先优势,新的制造工艺和设计技术降低了停止模式的电流消耗,可延长便携应用的电池寿命。此外,STM32F429/39拥有多项新功能,如TFT-LCD控制器、加快图形处理性能的ST Chrom-ART Accelerator和SDRAM存储器接口,让智能电表、小家电、工业设备及医疗设备等应用的图形用户界面拥有更丰富、更绚丽的内容以及更直观的操作特性。


    I2S TDM(集成电路互连音频时分多路复用)接口可实现多路音频设计。新系列的部分产品还为嵌入式处理器提供最新及最强大的防复制安全功能,全新STemWin图形软件还可访问SEGGER emWin嵌入式图形软件栈,充分发挥这些新的图形硬件功能的优势。


    意法半导体向STM32客户免费提供STemWin软件。该软件内置SEGGER VNC虚拟网络计算系统,可运用互联网协议远程查看用户界面。该软件还为开发人员提供其它强大的功能,其中包括窗口管理器和小工具包、触摸屏/鼠标支持,存储器上下文可实现无闪屏刷新。

    意法半导体微控制器部总经理Michel Buffa表示:“随着STM32F429/39系列上市,STM32系列又树立一个新的重要标准。我们非常高兴与SEGGER携手及时为客户开发出高品质图形开发工具STemWin,让我们的客户能够充分发挥STM32F429/39系列的新功能。”


  • NVIC_PriorityGroupConfig()说明(

    2014-03-05 10:48:21

    STM32(Cortex-M3)中有两个优先级的概念——抢占式优先级和响应优先级,有人把响应优先级称作'亚优先级'或'副优先级',每个中断源都需要被指定这两种优先级。

        具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以嵌套在低抢占式优先级的中断中。

    当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决定先处理哪一个;如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理哪一个。

    看了上面的介绍后,相信大家都明白了这里面的关系了,总结下便是:抢占式优先级>响应优先级>中断表中的排位顺序(其中“>”理解为比较的方向)。

        正是因为每个中断源都需要被指定这两种优先级,就需要有相应的寄存器位记录每个中断的优先级;在Cortex-M3中定义了8个比特位用于设置中断源的优先级,这8个比特位可以有8种分配方式,如下:

    1.     所有8位用于指定响应优先级

    2.     最高1位用于指定抢占式优先级,最低7位用于指定响应优先级

    3.     最高2位用于指定抢占式优先级,最低6位用于指定响应优先级

    4.     最高3位用于指定抢占式优先级,最低5位用于指定响应优先级

    5.     最高4位用于指定抢占式优先级,最低4位用于指定响应优先级

    6.     最高5位用于指定抢占式优先级,最低3位用于指定响应优先级

    7.     最高6位用于指定抢占式优先级,最低2位用于指定响应优先级

    8.     最高7位用于指定抢占式优先级,最低1位用于指定响应优先级

        以上便是优先级分组的概念,但是Cortex-M3允许具有较少中断源时使用较少的寄存器位指定中断源的优先级,因此STM32把指定中断优先级的寄存器位减少到4位,这4个寄存器位的分组方式如下:

       第0组:所有4位用于指定响应优先级

       第1组:最高1位用于指定抢占式优先级,最低3位用于指定响应优先级

       第2组:最高2位用于指定抢占式优先级,最低2位用于指定响应优先级

       第3组:最高3位用于指定抢占式优先级,最低1位用于指定响应优先级

       第4组:所有4位用于指定抢占式优先级

    这里便对于于文章最前提到的固件库里相关的函数了——NVIC_PriorityGroupConfig(u32   NVIC_PriorityGroup),函数的参数共有5种:

        这个函数的参数(NVIC_PriorityGroup值)有下列5种:

        NVIC_PriorityGroup_0 => 选择第0组

        NVIC_PriorityGroup_1 => 选择第1组

        NVIC_PriorityGroup_2 => 选择第2组

        NVIC_PriorityGroup_3 => 选择第3组

        NVIC_PriorityGroup_4 => 选择第4组

        这其实也很好理解,比如选择NVIC_PriorityGroup_1,那么抢占式优先级便占一位,也就是说可以有2^1个级别,可以设置为0和1,而响应优先级则占3位,也就是说可以有2^3个选择,可以设置为0~7;总共来说就可以区别>16种优先级(为什么大于而不是等于,想想就应该明白了)

       举个例子吧,假如现在有4个外部中断,还有一个EXTI9_5中断,那么如果选择优先级分组为第1组,那么抢占式优先级便只有两种,5个中断就至少有3个在抢占式优先级上是相同的优先级上,其他两个在令一优先级别。接着设置响应优先级可以有8种选择;假如现在同时有两个抢占式优先级别相同的中断发生,那么处理的顺序是谁的响应优先级高则谁优先进入中断,另外这点是需要注意的,如果此时进入这个中断之后又来了一个抢占式优先级相同但是响应优先级更高的中断,这时也是不会打断已有的中断的
  • STM32中DMA的使用入门

    2014-03-04 11:43:32

    今天刚看了一个ADC采集的程序,和之前用其他单片机写的程序还是有比较大的差别,这个差别主要就在DMA。这里面总结一下,有一部分是转载别人的。

    什么是STM32DMA?其全称是:Direct Memory Access;根据ST公司提供的相关信息,DMASTM32中一个独立与Cortex-M3内核的模块,有点类似与ADCPWMTIMER等模块;主要功能是通信“桥梁”的作用,可以将所有外设映射的寄存器“连接”起来,这样就可以高速问各寄存器,其传输不受CPU的支配,传输还是双向的;例如,从“表面”上看,它可以将flash中的数据与储存器中变量建立通讯,还可以将一外设的积存器或缓冲器与另外设的寄存器或缓冲器建立双向通讯,有点像把外设硬件之间用“导线”连接在一起了。其间的通讯不占CPU资源,访问速度高,对于实时性强的应用将是一个很好的选择;当然,对于实时性非常强的,建议还是采用专用的DSP芯片。

    怎样启用DMA?首先,众所周知的是初始化,任何设备启用前都要对其进行初始化,要对模块初始化,还要先了解该模块相应的结构及其函数,以便正确的设置;由于DMA较为复杂,我就只谈谈DMA的基本结构和和常用函数,这些都是ST公司提供在库函数中的。

    1、  下面代码是一个标准DMA设置,当然实际应用中可根据实际情况进行裁减:

     

      DMA_DeInit(DMA_Channel1);

      上面这句是给DMA配置通道,根据ST提供的资料,STM3210FxDMA包含7个通道(CH1~CH7),这里需要选择一个传输通道

     

      DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;

      上面语句中的DMA_InitStructure是一个DMA结构体,在库中有声明了,当然使用时就要先定义了;DMA_PeripheralBaseAddr是该结构体中一个数据成员,给DMA一个起始地址,好比是一个buffer起始地址,数据流程是:外设寄存器à DMA_PeripheralBaseAddàmemory中变量空间(或flash中数据空间等),ADC1_DR_Address是ADC1的地址,既然是桥梁,肯定要连接两个端点,这里需要明白所需要连接的外设的地址;

     

      DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADC_ConvertedValue;

      上面这句很显然是DMA要连接在Memory中变量的地址,ADC_ConvertedValue是我自己在memory中定义的一个变量;

     

      DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;

      上面的这句是设置DMA的传输方向,就如前面我所说的,DMA可以双向传输,也可以单向传输,这里设置的是单向传输,如果需要双向传输:把DMA_DIR_PeripheralSRC改成DMA_DIR_PeripheralDST即可。

     

     

     DMA_InitStructure.DMA_BufferSize = 2;

      上面的这句是设置DMA在传输时缓冲区的长度,前面有定义过了buffer的起始地址:ADC1_DR_Address ,为了安全性和可靠性,一般需要给buffer定义一个储存片区,这个参数的单位有三种类型:ByteHalfWordword,我设置的2half-word(见下面的设置)32位的MCU1half-word16 bits

     

     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

      上面的这句是设置DMA的外设递增模式,如果DMA选用的通道(CHx)有多个外设连接,需要使用外设递增模式:DMA_PeripheralInc_Enable;我的例子里DMA只与ADC1建立了联系,所以选用DMA_PeripheralInc_Disable

     

    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

    上面的这句是设置DMA的内存递增模式,DMA访问多个内存参数时,需要使用DMA_MemoryInc_Enable,当DMA只访问一个内存参数时,可设置成:DMA_MemoryInc_Disable

     

     DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;

    上面的这句是设置DMA在访问时每次操作的数据长度。有三种数据长度类型,前面已经讲过了,这里不在叙述。

     

    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

    与上面雷同。在此不再说明。

     

      DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

      上面的这句是设置DMA的传输模式:连续不断的循环模式,若只想访问一次后就不要访问了(或按指令操作来反问,也就是想要它访问的时候就访问,不要它访问的时候就停止),可以设置成通用模式:DMA_Mode_Normal

     

      DMA_InitStructure.DMA_Priority = DMA_Priority_High;

      上面的这句是设置DMA的优先级别:可以分为4级:VeryHighHigh,Medium,Low.

     

      DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

      上面的这句是设置DMA2memory中的变量互相访问的

     

      DMA_Init(DMA_Channel1,&DMA_InitStructure);

      前面那些都是对DMA结构体成员的设置,在次再统一对DMA整个模块做一次初始化,使得DMA各成员与上面的参数一致。

     

      /*DMA Enable*/

      DMA_Cmd(DMA_Channel1,ENABLE);

    使能此通道。

    当然更多的还需要看固件库函数的说明。

     

     

    这里面知识DMA的一个简单应用,后面做很多设计在数据传输上需要做这种转变,特别是像我这样从单片机学过来的,现在里面有了更好的方式要学会去运用,不能固守原来单片机的设计思路。这个相信也是后面学习中很重要的一部分。

  • 使用定时器实现小灯闪烁 (单片机 LM3S811) .

    2013-04-17 17:28:17

    上次我们讲了如何使用延时来做小灯闪烁的程序,相信大家都有所了解了,这次我们来看如何使用定时器实现小灯闪烁。

     

    1、不使用中断的方法

     

         首先我们还是来看头文件,因为这次使用了定时器(timer),所以头文件要包含timer.h。

         选择闪烁的小灯,在此我们选择PC5小灯,将其使能,作为output。

         下一步是使能定时器的过程。有如下步骤:

              1、在系统中使能定时器。   函数:SysCtlPeripheralEnable()

              2、选择定时器的使用类型,函数:TimerConfigure(),  参数可以写 TIMER_CFG_32_BIT_PER (32位周期)/                                         TIMER_CFG_16_BIT_PAIR|TIMER_CFG_A_PERIODIC  (16位TIMER0A周期)  TIMER_CFG_A_CAP_COUNT(计数模式)等..

                  (注:LM3S811一共有4个计数器TIMER0/1/2/3 ,默认使用减计数模式, 每个timer可以作为一个32位计数器使用,也可以作为两个16位计数器使用。具体使用方法请看技术文档)

              3、给定时器装载值:使用TimerLoadSet() 函数。

              4、使能定时器。

     

          剩下的思路很简单,让程序进入一个死循环。使用轮询检测定时器是否溢出,如果溢出则改变小灯的亮灭。因为系统的频率是一定的(在本程序中是6MHZ),所以通过改变定时器中装载的数值就可以控制小灯亮灭的时间。

          代码如下:

     

     

     

    1. //使用定时器实现小灯闪烁   
    2. #include "inc/hw_types.h"   
    3. #include "inc/hw_memmap.h"   
    4. #include "driverlib/sysctl.h"   
    5. #include "driverlib/gpio.h"   
    6. #include "driverlib/timer.h"                //包含定时器操作的API   
    7. int main()  
    8. {  
    9.     SysCtlClockSet(SYSCTL_OSC_MAIN|SYSCTL_XTAL_6MHZ|SYSCTL_USE_OSC|SYSCTL_SYSDIV_1);  
    10.     SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);  
    11.       
    12.     GPIODirModeSet(GPIO_PORTC_BASE,GPIO_PIN_5,GPIO_DIR_MODE_OUT);  
    13.     GPIOPadConfigSet(GPIO_PORTC_BASE,GPIO_PIN_5,GPIO_STRENGTH_2MA,GPIO_PIN_TYPE_STD);  
    14.       
    15.     SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);                                      //使能定时器0   
    16.     TimerConfigure(TIMER0_BASE,TIMER_CFG_32_BIT_PER);                            //配置定时器为32bit周期计数模式   
    17.     TimerLoadSet(TIMER0_BASE,TIMER_A,SysCtlClockGet()/2);                         //设置装载寄存器值   
    18.     TimerEnable(TIMER0_BASE,TIMER_A);                                                        //使能定时器0   
    19.     while(1)  
    20.     {  
    21.         while(!(TimerIntStatus(TIMER0_BASE,false)&TIMER_TIMA_TIMEOUT));       //等待Timer0溢出   
    22.         TimerIntClear(TIMER0_BASE,TIMER_TIMA_TIMEOUT);                                //清除溢出标志   
    23.         GPIOPinWrite(GPIO_PORTC_BASE,GPIO_PIN_5,0xff);                                  //PC5=1   
    24.         while(!(TimerIntStatus(TIMER0_BASE,false)&TIMER_TIMA_TIMEOUT));        //等待Timer0溢出   
    25.         TimerIntClear(TIMER0_BASE,TIMER_TIMA_TIMEOUT);                                //清除溢出标志   
    26.         GPIOPinWrite(GPIO_PORTC_BASE,GPIO_PIN_5,0x00);                                 //PC5=0   
    27.     }  
    28. }  
     

           设置timer的装载值为 SysCtlClockGet()/2,即为系统时钟的一半,即0.5秒溢出一次,所以小灯每秒亮灭一次。

     

    2、使用中断的方法:


          中断是单片机中很重要的一部分,可以说一个完成的程序一般都会用到中断。中断,顾名思义就是在程序正常执行的时候用来打断CPU进程的,比如:当我们的程序在死循环中执行时,外部收到一个按钮的信号,这时程序就先执行我们的中断代码,接着才会继续进行循环操作。可以提高程序的效率。

          更加深刻的了解可以在http://zhidao.baidu.com/question/14979539里查找到一些有趣的解释。

       (1) 初步设置

          因为使用了中断,在头文件中我们要加入 #include "inc/hw_ints.h" 和 #include "driverlib/interrupt.h"。(定义了与中断相关的宏 和 包含中断控制器的API)

          熟练地设置好主频、输出口、定时器之后,为了使用中断我们要学习以下几个函数:

           TimerPrescaleSet(TIMER0_BASE,TIMER_A,100);  这个函数将TIMER0A 设为分频100,即现在为6MHZ/100=60000HZ

          然后用TimerLoadSet()给TIMER0A装载30000这个数值,这样就是每0.5秒造成计数器溢出一次。

          TimerIntEnable() 设置定时器的中断模式,前面一个参数是选择定时器,后面是溢出模式,有TIMER_TIMA_TIMEOUT(溢出) TIMER_CAPA_MATCH(匹配)等,我们以后都会使用。

          IntEnable()用来使中断控制器接受定时器的中断请求, IntMasterEnable()用来让全局的中断使能。

           然后主程序就可以进入死循环,因为我们的小灯会通过中断来控制。

     

        (2) 写中断函数

          在程序中断后,会自动跳转到我们预先写好的中断函数,执行我们的命令。

          在此我们写中断函数 void Timer0ATimeoutIntHandler(void) ; 在函数里我们用TimerIntClear()将中断清除,防止再次进入中断,然后控制小灯的值反向。

          写好后要在程序的最前面声明,并且在startup.s中的相应位置上写好。首先打开工程中的startup.s文件,然后将TIMER0A前的函数改为我们自定义的函数,如图:

          

     

    再在上面进行声明:

     

    小提示,在这里面写的时候,函数只有自己的名字,没有括号,返回值类型等东西。在写的时候,不要顶格写,笔者就因为这个错误郁闷了半天。

     

    下面我们来看看整个程序的代码:

    1. #include "inc/hw_memmap.h"   
    2. #include "inc/hw_types.h"   
    3. #include "inc/hw_ints.h"                                                                                  //定义了与中断相关的宏   
    4. #include "driverlib/sysctl.h"   
    5. #include "driverlib/gpio.h"   
    6. #include "driverlib/timer.h"   
    7. #include "driverlib/interrupt.h"                      //包含中断控制器的API   
    8. void Timer0ATimeoutIntHandler(void);  
    9. int main()  
    10. {  
    11.     SysCtlClockSet(SYSCTL_SYSDIV_1|SYSCTL_USE_OSC|SYSCTL_OSC_MAIN|SYSCTL_XTAL_6MHZ);  
    12.     SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);  
    13.     GPIOPinTypeGPIOOutput(GPIO_PORTC_BASE,GPIO_PIN_4);  
    14.      
    15.     SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);  
    16.     TimerConfigure(TIMER0_BASE,TIMER_CFG_16_BIT_PAIR|TIMER_CFG_A_PERIODIC);    //配置Timer0   
    17.     TimerPrescaleSet(TIMER0_BASE,TIMER_A,100);                                   //设置预分频值   
    18.     TimerLoadSet(TIMER0_BASE,TIMER_A,30000);                                     //设置装载值   
    19.     TimerIntEnable(TIMER0_BASE,TIMER_TIMA_TIMEOUT);           //使能TIMER0A的溢出中断   
    20.     IntEnable(INT_TIMER0A);                                   //使中断控制器接受TIMER0A的中断请求   
    21.     IntMasterEnable();                                         //使能全局中断   
    22.     TimerEnable(TIMER0_BASE,TIMER_A);                     //使能Timer0A   
    23.     while(1);                                         //死循环   
    24. }  
    25. void Timer0ATimeoutIntHandler(void)                 //中断服务进程   
    26. {  
    27.     TimerIntClear(TIMER0_BASE,TIMER_TIMA_TIMEOUT);                    //清除中断标志位   
    28.     GPIOPinWrite(GPIO_PORTC_BASE,GPIO_PIN_4,~GPIOPinRead(GPIO_PORTC_BASE,GPIO_PIN_4));    //PC4取反   
    29. }  
     

          在此给大家说一个小技巧,在设定GPIO端口的模式时,另外有一个函数是 GPIOPinTypeGPIOOutput() ,使用此函数能达到上面第一个代码中GPIODirModeSet()加上GPIOPadConfigSet()的效果,更简单,如GPIOPinTypeGPIOOutput(GPIO_PORTC_BASE,GPIO_PIN_4) 就可以直接将PC4设为输出口,当然如果有高级设置,比如设置上拉电流,还是要使用代码中的方法的。

  • Android中调用webservice

    2013-04-06 20:17:33

    调用 WebService 分以下几步:

      1、指定 WebService 的命名空间和调用方法;

      2、设置调用方法的参数值,如果没有参数,可以省略,设置方法的参数值的代码如下:
      rpc.addProperty("abc", "test");

      要注意的是,addProperty方法的第1个参数虽然表示调用方法的参数名,但该参数值并不一定与服务端的WebService类中的方法参数名一致,只要设置参数的顺序一致即可。

      3、生成调用Webservice方法的SOAP请求信息。
      SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
      envelope.bodyOut = rpc;
      envelope.dotNet = false; //这里如果设置为TRUE,那么在服务器端将获取不到参数值(如:将这些数据插入到数据库中的话)
      envelope.setOutputSoapObject(rpc);
      创建SoapSerializationEnvelope对象时需要通过SoapSerializationEnvelope类的构造方法设置SOAP协议的版本号。
      该版本号需要根据服务端WebService的版本号设置。
      在创建SoapSerializationEnvelope对象后,不要忘了设置SOAPSoapSerializationEnvelope类的bodyOut属性, 该属性的值就是在第一步创建的SoapObject对象。

      4、创建HttpTransportsSE对象。
      这里不要使用 AndroidHttpTransport ht = new AndroidHttpTransport(URL); 这是一个要过期的类
      private static String URL = "http://www.webxml.com.cn/webservices/weatherwebservice.asmx";
      HttpTransportSE ht = new HttpTransportSE(URL);
      ht.debug = true;

      5、使用call方法调用WebService方法
      private static String SOAP_ACTION = "http://WebXml.com.cn/getWeatherbyCityName";
      ht.call(SOAP_ACTION, envelope);

      6、获得WebService方法的返回结果
      有两种方法:
      (1)、使用getResponse方法获得返回数据。
      (2)、使用 bodyIn 及 getProperty。

      7、 这时候执行会出错,提示没有权限访问网络
      需要修改 AndroidManifest.xml 文件,赋予相应权限
      简单来说就是增加下面这行配置:<uses-permission android:name="android.permission.INTERNET"></uses-permission>

  • Android 标题 居中 .

    2013-03-16 21:29:06

    1在onCreate()方法中加上这三句话:

     
     但加之前必须包括:
    import android.view.Window;
    import android.view.WindowManager;
    1. requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);  
    2. setContentView(R.layout.main);  
    3. getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,R.layout.title);  
    在布局文件中新建一个title.xml文件:

    1. <?xml version="1.0" encoding="utf-8"?>  
    2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    3.     android:layout_width="wrap_content"  
    4.  android:layout_height="wrap_content"  
    5.  android:layout_gravity="center">  
    6.         <TextView android:id="@+id/textTile"  
    7.         android:layout_width="wrap_content"  
    8.         android:layout_height="wrap_content"  
    9.         android:text="title" />  
    10. </LinearLayout>  


    注:另外设置字体大小、颜色等在title文件的textTitle中设置


    效果如下图:



  • Android入门 — 模拟器的创建和运行

    2013-03-15 16:34:14

        我们创建了android的工程,接下来有一个问题了,我们该怎么运行创建的程序呢?所以我们需要一个android的模拟器。配置环境的时候我们安装了android的SDK,在eclipse->window下有一个AVD(Android Virtual Device) Manager,点击New…按钮新建一个模拟器,主要要填的有Name,Target,SD Card Size(建议1GB以上,为以后在SD卡中放各种测试文件做准备),填好以后就可以Create AVD了。由于需要建立和SD Card Size一样大小的映像文件,所以创建的size越大所耗费的时间也就越多,不要着急哦。

     

    创建完模拟器后可以直接在AVDManager中选中刚刚创建的模拟器,点击start…就可以启动模拟器了。模拟器的启动也需要4-5分钟的时间。

     

    当然模拟器也可以在启动程序的时候由eclipse自动启动。

    在工程上点击右键,右键菜单中有Debug As/RunAs->Android application;或者选中工程后,run菜单栏中也有同样的菜单项。如果当前模拟器没有启动时,就会先自动启动模拟器。当然前提条件是工程没有错误。

     

    有时候创建出来的模拟器启动后会莫名其妙的异常,可能有效的方法是:

    1、  删除当前模拟器重新创建

    2、  新建的模拟器异常可以加大SDcard Size的大小

    3、  创建模拟器时EnabledSnapshot,该功能会在模拟器关闭时保存当时状态,启动时会自动加载上次的状态

    4、  删掉模拟器镜像路径*\.android下的avd文件夹

    5、  删掉*\.android下的modem-nv-ram*文件



     

  • Android入门 — 新建工程及结构认识

    2013-03-15 16:30:38

    许是因为在大学第一个写的程序就是HelloWorld,以后每次学一个新的东西,总是没有出息的先建一个HelloWorld的程序。也是,任何东西新出来,都需要和这个世界打个招呼吧,hello,world!大笑

    言归正传,开始和这世界打个招呼吧。

    1、新建一个Android的工程

    如果开发环境配置正确,在New的菜单中就会出现AndroidProject,就可以新建工程了。


    我们在工程名称处写入HelloWorld,选择 Createnew project in workspace,点击Next,选择SDK后继续Next,出现输入包名的界面:


    第一次看到这个界面真是犯愁啊,“your.package.namespace”,以前写C++的代码从来没有过包的概念,到底写什么好呢?踌躇了一会,以cs.demo.firstcode为包名,反正都是demo程序,哈哈。

    2、工程结构认识

    src是工程的代码文件夹,代码文件都放在此文件夹下。cs.demo.firstcode是刚刚建的包,下面是自动创建的HelloWorldActivity.java的代码文件;

    gen文件夹下的R.java自动生成的文件,里面记录了程序中用到的各种ID,不要手动修改。当res文件夹下的内容发生变化时,这个R.java文件会自动改变。这个与MFC中的resource.h文件类似。

    assets文件夹主要用来存放一些多媒体数据文件,不会被改变

    res文件夹存放的是资源相关的文件:

    res/drawable*:存放图片文件

    res/layout:存放xml的布局文件。写了近三年的自绘界面的代码,看到这个类似MFC的可以拖拽界面元素的功能,着实让我高兴了一下。

    res/values:存放字符串,颜色,数组等常量数据,常用的字符串存放在string.xml文件里,在程序中各处就可以使用ID实现字符串共享了。

    AndroidManifest.xml:应用的配置文件,声明activity和权限的地方。


    题外话:建立工程的其他方式

    为什么要写这个呢?我第一次拿到别人提供的工程时,根本就不知道从哪里下手。还是因为VC的思维模式,我在文件夹中找不到工程文件时,就很迷茫了。

    一看.project文件还恍然以为找到了入口,不成功又把每个根目录下的文件每个打开了一遍,现在想来真是太可乐了。所以写下简单的创建工程的方式:

    1)File->New->Androidproject->Create project from existing source:只需要选择工程所在文件夹就可以了,其他选项都会有默认的设置。

    2)File->New->Androidproject->Create project from existing sample:android自带了各种示例程序,选择某个示例程序后,会创建对应的工程

    3)File->Import->General->ExistingProjects into Workspace,然后选择文件夹所在的根目录,就可以导入工程了。


  • Android学习之通过Intent实现浏览网页

    2013-03-15 16:21:23

    大家好,在这里和大家分享我刚刚学到的知识:通过Intent打开网页

     

    首先,打开布局文件中main.xml,创建为LinearLayout布局,并且创建一个EditText和Button控件。具体代码如下:

     

    1. <?xml version="1.0" encoding="utf-8"?>  
    2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    3.     android:orientation="horizontal"  
    4.     android:layout_width="fill_parent"  
    5.     android:layout_height="fill_parent"  
    6.     >  
    7. <EditText  
    8.     android:id = "@+id/editText"  
    9.     android:layout_width="wrap_content"  
    10.     android:layout_height="wrap_content"  
    11.     android:layout_weight="1.0"  
    12.     android:lines="1"  
    13.     />  
    14. <Button  
    15.     android:id = "@+id/button1"  
    16.     android:layout_width="wrap_content"  
    17.     android:layout_height="wrap_content"  
    18.     android:text = "go"  
    19.     />  
    20. </LinearLayout>  
     

     

    效果如图:

     

     

     

     

    1. package org.exmaple.MyBrowserIntent;  
    2. import android.app.Activity;  
    3. import android.content.Intent;  
    4. import android.net.Uri;  
    5. import android.os.Bundle;  
    6. import android.view.KeyEvent;  
    7. import android.view.View;  
    8. import android.view.View.OnClickListener;  
    9. import android.view.View.OnKeyListener;  
    10. import android.widget.Button;  
    11. import android.widget.EditText;  
    12. public class MyBrowserIntent extends Activity {  
    13.       
    14.     private EditText editText;  
    15.     private Button goButton;  
    16.       
    17.     /** Called when the activity is first created. */  
    18.     @Override  
    19.     public void onCreate(Bundle savedInstanceState) {  
    20.         super.onCreate(savedInstanceState);  
    21.         setContentView(R.layout.main);  
    22.            
    23.         editText = (EditText) findViewById(R.id.editText);  
    24.         goButton   = (Button) findViewById(R.id.button1);  
    25.           
    26.         editText.setOnKeyListener(new OnKeyListener() {       
    27.             //按钮键盘点击事件   
    28.             @Override  
    29.             public boolean onKey(View arg0, int arg1, KeyEvent arg2) {  
    30.                 // TODO Auto-generated method stub   
    31.                 if(arg1 == KeyEvent.KEYCODE_ENTER) {    
    32.                     //用户按了Enter则调用openBrowser()方法   
    33.                     openBrowser();  
    34.                     return true;  
    35.                 }  
    36.                 return false;  
    37.             }  
    38.               
    39.         });  
    40.         goButton.setOnClickListener(new OnClickListener(){  //按钮绑定点击事件   
    41.             @Override  
    42.             public void onClick(View v) {  
    43.                 // TODO Auto-generated method stub   
    44.                 openBrowser();  
    45.             }  
    46.         });  
    47.     }  
    48.     protected void openBrowser() {  
    49.         // TODO Auto-generated method stub   
    50.         Uri uri = Uri.parse("http://"+editText.getText().toString());   
    51.         //通过Uri获得编辑框里的//地址,加上http://是为了用户输入时可以不要输入   
    52.         Intent intent = new Intent(Intent.ACTION_VIEW,uri);    
    53.         //建立Intent对象,传入uri   
    54.         startActivity(intent);    
    55.         //启动   
    56.     }  
    57. }  

     

    到这里就一切OK啦!

     

    哈哈!看下效果图:

    输入www.google.com

     

    还请大家多多关注我的博客,我会和大家分享我的学习成果的!

     

  • Android学习之利用WebView打开网页

    2013-03-15 16:19:48

    大家好,今天我们一起学习通过webView打开网页:

    首先还是布局文件main.xml的Code:

     

    1. <?xml version="1.0" encoding="utf-8"?>  
    2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    3.     android:orientation="vertical"  
    4.     android:layout_width="fill_parent"  
    5.     android:layout_height="fill_parent"  
    6.     >  
    7.         <LinearLayout   
    8.             android:orientation="horizontal"  
    9.         android:layout_width="fill_parent"  
    10.         android:layout_height="wrap_content"  
    11.         >  
    12.         <EditText  
    13.                 android:id="@+id/editText"  
    14.             android:layout_width="fill_parent"  
    15.             android:layout_height="wrap_content"  
    16.             android:layout_weight="1.0"  
    17.             android:lines="1"  
    18.             />  
    19.          <Button  
    20.             android:id="@+id/button"  
    21.             android:layout_width="wrap_content"  
    22.             android:layout_height="wrap_content"  
    23.             android:text="go"  
    24.             />  
    25.          </LinearLayout>  
    26.           <WebView  
    27.             android:id="@+id/webView"  
    28.             android:layout_width="fill_parent"  
    29.             android:layout_height="wrap_content"  
    30.             android:layout_weight="1.0"  
    31.             />  
    32. </LinearLayout>  

     

    利用两个LineraLayout的嵌套显示,上面显示一个编辑框和按钮,下面的是webView用来显示网页,大家要注意两个LineraLayout的android:orientation的属性,最外面的这个是垂直显示,里面的是水平显示,最终得到效果如图:

     

    效果图下面的白色区域就是webView,等Java的Code完成后就能看出效果啦,期待吧!呵呵!

     

    1. import android.app.Activity;  
    2. import android.widget.*;  
    3. import android.os.Bundle;  
    4. import android.webkit.*;  
    5. import android.view.KeyEvent;  
    6. import android.view.View;  
    7. import android.view.View.*;  
    8. public class MyBrowserView extends Activity {  
    9.       
    10.     private EditText editText;  
    11.     private Button   button;  
    12.     private WebView  webView;  
    13.       
    14.     /** Called when the activity is first created. */  
    15.     @Override  
    16.     public void onCreate(Bundle savedInstanceState) {  
    17.         super.onCreate(savedInstanceState);  
    18.         setContentView(R.layout.main);  
    19.           
    20.         editText = (EditText) findViewById(R.id.editText);  
    21.         button   = (Button)   findViewById(R.id.button);  
    22.         webView  = (WebView)  findViewById(R.id.webView);  
    23.           
    24.         button.setOnClickListener(new OnClickListener() {  
    25.             //同样为按钮绑定点击事件   
    26.             public void onClick(View v) {  
    27.                 openBrowser();  
    28.             }  
    29.         });  
    30.           
    31.         editText.setOnKeyListener(new OnKeyListener() {  
    32.             //同样为编辑框绑定键盘事件   
    33.             @Override  
    34.             public boolean onKey(View v, int keyCode, KeyEvent event) {  
    35.                 // TODO Auto-generated method stub   
    36.                 if(keyCode==KeyEvent.KEYCODE_ENTER) {  
    37.                     openBrowser();  
    38.                     return true;  
    39.                 }  
    40.                 return false;  
    41.             }  
    42.         });  
    43.     }  
    44.     //利用webView的loadUrl方法   
    45.     public void openBrowser() {  
    46.         webView.loadUrl("http://"+editText.getText().toString());  
    47.     }  
    48. }  

     

    除了openBrowser()方法,其他的和用Intent打开网页是差不多的。

    只要利用webView的loadUrl()方法就能加载网页。好的什么也不说啦!看效果图:

     

     

     

     

    哈哈,不错吧,这样就方便打开别的网页啦!哦!差点忘了,要在AndroidManifest.xml中加入

    <uses-permission android:name="android.permission.INTERNET">

    否则就不能访问Internet哟!

    到这里看了上一篇的同学可能就问,为什么上个例子不要加上这条语句呢?

    因为通过Intent就能请求其他应用程序查看网页,而其他应用程序需要在自己的AndroidManifest.xml中请求获得访问Internet的权限。

    不知道解释的清楚不?呵呵!

    好了今天的就到这里了哟!祝大家一起进步!加油!

  • android中两种方式打开网页(

    2013-03-15 16:15:06

    一.你要打开一个网页你可以自己写一个webview,在自己的程序中就可以打开.
    wv = (WebView) findViewById(R.id.webView1);
    wv.getSettings().setJavaScriptEnabled(true);
    wv.setScrollBarStyle(0);
    WebSettings webSettings = wv.getSettings();
    webSettings.setAllowFileAccess(true);
    webSettings.setBuiltInZoomControls(true);
    wv.loadUrl("http://www.baidu.com");
    //加载数据
    wv.setWebChromeClient(new WebChromeClient() {
    @Override
    public void onProgressChanged(WebView view, int newProgress) {
    if (newProgress == 100) {
    MainActivity.this.setTitle("加载完成");
    } else {
    MainActivity.this.setTitle("加载中.......");

    }
    }
    });
    //这个是当网页上的连接被点击的时候
    wv.setWebViewClient(new WebViewClient() {
    public boolean shouldOverrideUrlLoading(final WebView view,
    final String url) {
    loadurl(view, url);
    return true;
    }
    });
     // goBack()表示返回webView的上一页面
    public boolean onKeyDown(int keyCoder, KeyEvent event) {
    if (wv.canGoBack() && keyCoder == KeyEvent.KEYCODE_BACK) {
    wv.goBack();
    return true;
    }
    return false;
    }
    二.不然你就调用android内置安装的浏览器来打开网页.
       1. Uri uri Uri.parse("http://google.com");  
       2. Intent it new Intent(Intent.ACTION_VIEW, uri);  
       3. startActivity(it);
  • Android通过JNI调用驱动程序(完全解析实例) .

    2013-03-15 13:26:44

          要达到的目的:android系统中,用JAVA写界面程序,调用jni中间库提供的接口,去操作某个驱动节点,实现read,writer ioctl等操作!这对底层驱动开发人员是很重要的一个调试通道,也是android 系统下提供一些特殊功能接口的方法!

    本文前提:我们假设已经写了一个驱动程序,它是控制LED的亮灭的,并且创建了一个节点:/dev/vib,也就是通过open这个vib节点,可以read/write/ioctl 操作驱动程序实现LED灯的亮灭控制,具体可以看我另一篇博文《android驱动例子(LED灯控制)

    开发环境 1、ubuntu下的NDK编译环境,2、Esclips开发环境

    一、编写JNI模块

    当安装好NDK编译环境后,会在它的目录下找到sample目录,它里面有一些例子,可以参考这些例子来写我们自已的模块。

    clip_image002

    1、 source文件夹下,新建“LEDSJNI”文件夹。

    2、 Source/LEDSJNI/jni/目录下,新建“vib-jni.c”

    vib-jni.c文件

    #include <string.h>
    #include <jni.h>
    #include <fcntl.h> /*包括文件操作,如open() read() close() write()等*/
    //----for output the debug log message
    #include <android/log.h>
    #define LOG_TAG "vib-jni"
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    #define DEVICE_NAME "/dev/vib" //device point
    #define VIB_ON 0x11
    #define VIB_OFF 0x22
    int fd;
    jstring
    Java_com_auly_control_vibClass_stringFromJNI( JNIEnv* env,
    jobject thiz )
    {
    return (*env)->NewStringUTF(env, "Hello from JNI--Peter for vib!");//打印字符串
    }

    jint
    Java_com_auly_control_vibClass_Init( JNIEnv* env )
    {
    LOGE("vibClass_Init() /n");
    fd = open(DEVICE_NAME,O_RDWR);//打开设备
    LOGE("vibClass_Init()-> fd = %d /n",fd);
    if(fd == -1)
    {
    LOGE("open device %s error /n ",DEVICE_NAME);//打印调试信息
    return 0;
    }
    else
    {
    return 1;
    }
    }

    jint
    Java_com_auly_control_vibClass_IOCTLVIB( JNIEnv* env, jobject thiz, jint controlcode )
    {
    int CTLCODE = controlcode;
    LOGE("IOCTLVIB() = %x --vibClass_IOCTLVIB /n",CTLCODE);
    switch(CTLCODE)
    {
    case VIB_ON:
    {
    ioctl(fd,VIB_ON);//调用驱动程序中的ioctrl接口,把命令VIB_ON传下去,实现硬件操作
    break;
    }
    case VIB_OFF:
    {
    ioctl(fd,VIB_OFF);//调用驱动程序中的ioctrl接口,把命令VIB_OFF传下去,实现硬件操作
    break;
    }
    default:break;
    }
    return 1;
    }

    3、相同目录下的新建Android.mk如下

    Android.mk文件

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE := vib-jni
    LOCAL_SRC_FILES := vib-jni.c
    LOCAL_CFLAGS := -Werror
    LOCAL_LDLIBS := -llog -lGLESv2 //__android_log_print 函数
    include $(BUILD_SHARED_LIBRARY)

    可以看到,主要是修改LOCAL_SRC_FILES指向源文件的名称!

    还有一点很重要,如果要使用调试LOG 打印,也就是__android_log_print 函数。要在LOCAL_LDLIBS中添加-llog,如上面的Android.mk所示。

    4、编译JNI模块

    #cd /home/workspace/android-ndk-r4b/sources/LEDSJNI

    进到刚才写的JNI目录

     

    #ndk-build

    编译JNI,编译成功后,会在LEDSJNI文件夹下生成libs和obj两个文件夹,并在

    LEDSJNI/libs/armeabi下得到目标文件libvib-jni.so

    clip_image002[8]

    (目前LEDSJNI文件夹只有3个目录jni,libs,obj)

    二、JAVA程序

    1、Eclipse新建工程

    拷贝LEDSJNI目录到Windows下,例如C盘下。然后在它里面新建Eclipse 工程。

    clip_image002[10]
    clip_image004

    新键工程后,

    如果出现如下错误:

    ERROR: Unable to open class file C:/LEDSJNI/gen/com/auly/control/R.java: No such file or directory

    解决方法如下:

    对着该工程鼠标右键 》bulid path 》configure build path》java build path》order and Export

     

    clip_image002[12]

    把里面的android 2.1勾上,Build project,就OK了

    clip_image002[14]

    然后 Run as > Android application,就会出现android的模拟器了,里面跑个helloworld出来。

    clip_image002[16]

    2、加入button和文本输出

    程序到上面为止代码是ADT自动生成的,似乎与我们一点关系也没有。那我们来改一下代码,因为我们调用JNI接口是为了访问驱动程序操作硬件的,例如写,读,打开LED,关闭LED等等,由按钮触发的动作。

    第一步是,增加两个Button,,在main.xml里描述一下:打开Res > layout> main.xml 文件

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    androidrientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />
    <Button android:id="@+id/led_on"/*这表示需要一个唯一的UID来作为Button的ID,它的引用名是led_on。*/
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/LEDon">/*表示这个按钮的文本是来源于另一个资源描述文件strings.xml里的文:字资源LEDon */
    <requestFocus/>
    </Button>
    <Button android:id="@+id/led_off"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/LEDoff">
    <requestFocus/>
    </Button>
    </LinearLayout>

    实际代码中,把注释去掉,否则编译不过的。

    3、加入输出字符串资源

    工程 > values > strings.xml 文件

    clip_image002[18]
    修改如下
    
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="hello">Led 控制程序</string>
        <string name="app_name">LEDAPP</string>
    <string name="LEDon">打开LED</string>
    <string name="LEDoff">关闭LED</string>
    </resources>

    上面的”打开LED”等资源,就是用在按钮上显示出来的字符串

    经过上面的修改,现在程序界面上,已经有如下效果了

    鼠标右键工程名>Run as > Android application 运行程序。

    clip_image004[11]

    4、加入按钮对应的动作

    “打开LED”按扭:调用JNI的IOCTLVIB(VIB_ON);

    “关闭LED”按钮:调用JNI的 IOCTLVIB(VIB_OFF);

    操作:

    在LEDAPP > src > com.auly.control > vibrator.java文件

    package com.auly.control;
    /**定义头文件*/
    import android.widget.TextView;
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    import android.util.Log;
    import android.app.Activity;
    import android.os.Bundle;
    public class vibrator extends Activity {
    /** 定义变量 */
    public static final int VIB_ON = 0x11;
    public static final int VIB_OFF = 0x22;
    vibClass mvibClass;/**定义类*/
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    /**----------------初始化------------- */
    mvibClass = new vibClass();/*声明类*/
    mvibClass.Init(); //调用JNI库里的初始化函数
    /**----------------按钮:打开LED------------- */
    Button btn1 = (Button)findViewById(R.id.led_on);/*这里用到的ID,就是在main.xml里定义的 led_on*/
    btn1.setOnClickListener(new View.OnClickListener()
    {
    public void onClick(View v) /**当按钮按下*/
    {
    mvibClass.IOCTLVIB(VIB_ON);
    }
    });
    /**----------------按钮:关闭LED------------- */
    Button btn2 = (Button)findViewById(R.id.led_off);/*声明按钮,id main.xml里有定义*/
    btn2.setOnClickListener(new View.OnClickListener()
    {
    public void onClick(View v) /**当按钮按下*/
    {
    mvibClass.IOCTLVIB(VIB_OFF);
    }
    });
    }
    }

    如果在保存时遇到说 save problems,无法保存,请先复制上面的代码,然后,关闭vibrator.java,提示保存时选不保存,然后在左边资源窗中再次双击打开该文件,把几日才复制下来的内容,粘贴上去,一般就能正常保存,不知道是不是Eclipse的不稳定造成的。

    5、添加类vibClass

    鼠标右键com.auly.control > new > Class

    clip_image002[20]

    填参数:

    clip_image002[22]

    Finish后,在/src/com/auly/control/得到如下的类文件

    vibClass.java

    修改如下

    package com.auly.control;
    /*Class for Vibrator --peter*/
    public class vibClass {
    static {
    System.loadLibrary("vib-jni");/*加载JNI库*/
    }
    /*声明 函数*/
    public static native String stringFromJNI();/*输出字符串
    对应于JNI里面的
    jstring Java_com_auly_control_vibClass_stringFromJNI( JNIEnv* env,jobject thiz )
    */
    public static native int Init();/*初始化函数,对应于JNI里面的
    jint Java_com_auly_control_vibClass_Init( JNIEnv* env )
    */
    public static native int IOCTLVIB(int controlcode);
    /*IO CTRL 接口
    * 对应于JNI里的
    jint Java_com_auly_control_vibClass_IOCTLVIB( JNIEnv* env, jobject thiz, jint controlcode )
    */
    }

    三、 编译运行

    鼠标右键工程名,弹出菜单,选择 Run as > Android Application 就可以看到编译过程,编译完成后,会自动调用android模拟器,看到如下效果

    clip_image002[24]

    安装到开发板:

    在C:/LEDSJNI目录下,会看到bin文件夹,里面的LEDAPP.apk就是这个程序的安装文件,可以把它安装的开发板上,运行本程序,看控制开发板上的LED灯的效果。

    步骤:

    1、开发板上跑的kenel就已经把了LED驱动编译在里面了,

    可以参考《android驱动例子(LED灯控制)》

    2、开发板android跑起来后,PC机打开串口工具例如DNW,打开与开发板连接的COM口,然后敲打回车,就会在终端里看到“#”并有光标,表面进入了开发板的命令行终端,

    输入命令:

    #chmod 777 /dev/vib 

          这是为了使得vib这个节点可以被我们写的JNI操作,不然会open失败的,因为APK安装的JNI模块,权限不够,这个节点是我们的LED驱动生成的控制节点。

    也可以在android文件系统yaffs编译时,通过init.rc 文件来实现这个操作,就是在该文件里随便一行,写上面的命令行,启动时会自动执行!这样就不用手动的改变该节点的属性了。

    3、拷贝LEDAPP.apk到开发板上,通过安装工具把它安装到开发板上,如果不会安,可以GOOGLE一下,

    4、运行程序,就能按程序上的近钮来控制开发板上的LED亮灭了!


  • Android使用JNI实现Java与C之间传递数据

    2013-03-14 15:50:04

    介绍Java如何将数据传递给C和C回调Java的方法。  java传递数据给C,在C代码中进行处理数据,处理完数据后返回给java。C的回调是Java传递数据给C,C需要用到Java中的某个方法,就需要调用java的方法。

    Android中使用JNI七个步骤:

    1.创建一个android工程

    2.JAVA代码中写声明native 方法 public native String helloFromJNI();

    3.用javah工具生成头文件

    4. 创建jni目录,引入头文件,根据头文件实现c代码

    5.编写Android.mk文件

    6.Ndk编译生成动态库

    7.Java代码load 动态库.调用native代码

    Java调用C进行数据传递

     这里分别传递整形、字符串、数组在C中进行处理。

    声明native 方法:

    1. public class DataProvider {  
    2.     // 两个java中的int 传递c 语言 ,  c语言处理这个相加的逻辑,把相加的结果返回给java   
    3.     public native int add(int x ,int y);  
    4.       
    5.     //把一个java中的字符串传递给c语言, c 语言处理下字符串, 处理完毕返回给java    
    6.     public native String sayHelloInC(String s);  
    7.   
    8.     //把一个java中int类型的数组传递给c语言, c语言里面把数组的每一个元素的值 都增加5,    
    9.     //然后在把处理完毕的数组,返回给java   
    10.     public native int[] intMethod(int[] iNum);   
    11. }  


    以上方法要在C中实现的头文件,头文件可以理解为要在C中实现的方法

    其中 JENEnv* 代表的是java环境 , 通过这个环境可以调用java的方法,jobject 表示哪个对象调用了 这个c语言的方法, thiz就表示的是当前的对象

    1. /* DO NOT EDIT THIS FILE - it is machine generated */  
    2. #include <jni.h>   
    3. /* Header for class cn_itcast_ndk3_DataProvider */  
    4.   
    5. #ifndef _Included_cn_itcast_ndk3_DataProvider   
    6. #define _Included_cn_itcast_ndk3_DataProvider   
    7. #ifdef __cplusplus   
    8. extern "C" {  
    9. #endif   
    10. /* 
    11.  * Class:     cn_itcast_ndk3_DataProvider 
    12.  * Method:    add 
    13.  * Signature: (II)I 
    14.  */  
    15. JNIEXPORT jint JNICALL Java_cn_itcast_ndk3_DataProvider_add  
    16.   (JNIEnv *, jobject, jint, jint);  
    17.   
    18. /* 
    19.  * Class:     cn_itcast_ndk3_DataProvider 
    20.  * Method:    sayHelloInC 
    21.  * Signature: (Ljava/lang/String;)Ljava/lang/String; 
    22.  */  
    23. JNIEXPORT jstring JNICALL Java_cn_itcast_ndk3_DataProvider_sayHelloInC  
    24.   (JNIEnv *, jobject, jstring);  
    25.   
    26. /* 
    27.  * Class:     cn_itcast_ndk3_DataProvider 
    28.  * Method:    intMethod 
    29.  * Signature: ([I)[I 
    30.  */  
    31. JNIEXPORT jintArray JNICALL Java_cn_itcast_ndk3_DataProvider_intMethod  
    32.   (JNIEnv *, jobject, jintArray);  
    33.   
    34. #ifdef __cplusplus   
    35. }  
    36. #endif   
    37. #endif  


    C代码出了要引用头文件外,还要引入日志信息,以方便在C 中进行调试

    1. //引入头文件   
    2. #include "cn_itcast_ndk3_DataProvider.h"   
    3. #include <string.h>   
    4. //导入日志头文件   
    5. #include <android/log.h>   
    6. //修改日志tag中的值   
    7. #define LOG_TAG "logfromc"   
    8. //日志显示的等级   
    9. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)   
    10. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)   
    11.   
    12. // java中的jstring, 转化为c的一个字符数组   
    13. char*   Jstring2CStr(JNIEnv*   env,   jstring   jstr)  
    14. {  
    15.      char*   rtn   =   NULL;  
    16.      jclass   clsstring   =   (*env)->FindClass(env,"java/lang/String");  
    17.      jstring   strencode   =   (*env)->NewStringUTF(env,"GB2312");  
    18.      jmethodID   mid   =   (*env)->GetMethodID(env,clsstring,   "getBytes",   "(Ljava/lang/String;)[B");  
    19.      jbyteArray   barr=   (jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode); // String .getByte("GB2312");   
    20.      jsize   alen   =   (*env)->GetArrayLength(env,barr);  
    21.      jbyte*   ba   =   (*env)->GetByteArrayElements(env,barr,JNI_FALSE);  
    22.      if(alen   >   0)  
    23.      {  
    24.       rtn   =   (char*)malloc(alen+1);         //new   char[alen+1]; "\0"   
    25.       memcpy(rtn,ba,alen);  
    26.       rtn[alen]=0;  
    27.      }  
    28.      (*env)->ReleaseByteArrayElements(env,barr,ba,0);  //释放内存   
    29.   
    30.      return rtn;  
    31. }  
    32.   
    33. //处理整形相加   
    34. JNIEXPORT jint JNICALL Java_cn_itcast_ndk3_DataProvider_add  
    35.   (JNIEnv * env, jobject obj, jint x, jint y){  
    36.     //打印 java 传递过来的 jstring ;   
    37.     LOGI("log from c code ");  
    38.     LOGI("x= %ld",x);  
    39.     LOGD("y= %ld",y);  
    40.     return x+y;  
    41. }  
    42.   
    43. //处理字符串追加   
    44. JNIEXPORT jstring JNICALL Java_cn_itcast_ndk3_DataProvider_sayHelloInC  
    45.   (JNIEnv * env, jobject obj, jstring str){  
    46.   
    47.     char* p =  Jstring2CStr(env,str);  
    48.     LOGI("%s",p);  
    49.     char* newstr = "append string";  
    50.   
    51.     //strcat(dest, sorce) 把sorce字符串添加到dest字符串的后面   
    52.     LOGI("END");  
    53.     return (*env)->NewStringUTF(env, strcat(p,newstr));  
    54. }  
    55.   
    56. //处理数组中的每一个元素   
    57. JNIEXPORT jintArray JNICALL Java_cn_itcast_ndk3_DataProvider_intMethod  
    58.   (JNIEnv * env, jobject obj, jintArray arr){  
    59.     // 1.获取到 arr的大小   
    60.   
    61.     int len = (*env)->GetArrayLength(env, arr);  
    62.     LOGI("len=%d", len);  
    63.   
    64.     if(len==0){  
    65.         return arr;  
    66.     }  
    67.     //取出数组中第一个元素的内存地址   
    68.     jint* p = (*env)-> GetIntArrayElements(env,arr,0);  
    69.     int i=0;  
    70.     for(;i<len;i++){  
    71.         LOGI("len=%ld", *(p+i));//取出的每个元素   
    72.         *(p+i) += 5; //取出的每个元素加五   
    73.     }  
    74.     return arr;  
    75. }  

    编写Android.mk文件

    1. LOCAL_PATH := $(call my-dir)  
    2.   
    3. include $(CLEAR_VARS)  
    4.   
    5. LOCAL_MODULE    := Hello  
    6. LOCAL_SRC_FILES := Hello.c   
    7. #增加 log 函数对应的log 库  liblog.so  libthread_db.a   
    8. LOCAL_LDLIBS += -llog  
    9.   
    10. include $(BUILD_SHARED_LIBRARY)  

     Java代码load 动态库.调用native代码

    1. static{  
    2.         System.loadLibrary("Hello");  
    3.     }  
    4.     DataProvider dp;  
    5.       
    6.     @Override  
    7.     public void onCreate(Bundle savedInstanceState) {  
    8.         super.onCreate(savedInstanceState);  
    9.         setContentView(R.layout.main);  
    10.         dp = new DataProvider();  
    11.     }  
    12.       
    13.     //add对应的事件   
    14.     public void add(View view){  
    15.         //执行C语言处理数据   
    16.         int result = dp.add(35);  
    17.         Toast.makeText(this"相加的结果"+ result, 1).show();      
    18.     }  

     

    C中回调java方法

    声明native 方法:

    1. public class DataProvider{  
    2.     public native void callCcode();  
    3.     public native void callCcode1();  
    4.     public native void callCcode2();  
    5.       
    6.     ///C调用java中的空方法     
    7.     public void helloFromJava(){  
    8.         System.out.println("hello from java ");  
    9.     }  
    10.     //C调用java中的带两个int参数的方法   
    11.     public int Add(int x,int y){  
    12.         System.out.println("相加的结果为"+ (x+y));  
    13.         return x+y;  
    14.     }  
    15.     //C调用java中参数为string的方法   
    16.     public void printString(String s){  
    17.         System.out.println("in java code "+ s);  
    18.     }  
    19. }  

    头文件可以用jdk自带的javah进行自动生成,使用javap -s可以获取到方法的签名。

    C代码实现回调需要三个步骤:首先要要获取到 某个对象 , 然后获取对象里面的方法  ,最后 调用这个方法  .

    1. #include "cn_itcast_ndk4_DataProvider.h"   
    2. #include <string.h>   
    3. #include <android/log.h>   
    4. #define LOG_TAG "logfromc"   
    5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)   
    6. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)   
    7.   
    8. //1.调用java中的无参helloFromJava方法   
    9. JNIEXPORT void JNICALL Java_cn_itcast_ndk4_DataProvider_callCcode  
    10.   (JNIEnv * env , jobject obj){  
    11.     // 获取到DataProvider对象   
    12.     char* classname = "cn/itcast/ndk4/DataProvider";  
    13.     jclass dpclazz = (*env)->FindClass(env,classname);  
    14.     if (dpclazz == 0) {  
    15.             LOGI("not find class!");  
    16.         } else  
    17.             LOGI("find class");  
    18.     //第三个参数 和第四个参数 是方法的签名,第三个参数是方法名  , 第四个参数是根据返回值和参数生成的   
    19.     //获取到DataProvider要调用的方法   
    20.     jmethodID methodID = (*env)->GetMethodID(env,dpclazz,"helloFromJava","()V");  
    21.     if (methodID == 0) {  
    22.             LOGI("not find method!");  
    23.         } else  
    24.             LOGI("find method");  
    25.     //调用这个方法   
    26.     (*env)->CallVoidMethod(env, obj,methodID);  
    27. }  
    28.   
    29. // 2.调用java中的printString方法传递一个字符串   
    30. JNIEXPORT void JNICALL Java_cn_itcast_ndk4_DataProvider_callCcode1  
    31.   (JNIEnv * env, jobject obj){  
    32.     LOGI("in code");  
    33.     // 获取到DataProvider对象   
    34.     char* classname = "cn/itcast/ndk4/DataProvider";  
    35.     jclass dpclazz = (*env)->FindClass(env,classname);  
    36.     if (dpclazz == 0) {  
    37.             LOGI("not find class!");  
    38.         } else  
    39.             LOGI("find class");  
    40.     // 获取到要调用的method   
    41.     jmethodID methodID = (*env)->GetMethodID(env,dpclazz,"printString","(Ljava/lang/String;)V");  
    42.     if (methodID == 0) {  
    43.             LOGI("not find method!");  
    44.         } else  
    45.             LOGI("find method");  
    46.   
    47.     //调用这个方法   
    48.     (*env)->CallVoidMethod(env, obj,methodID,(*env)->NewStringUTF(env,"haha"));  
    49. }  
    50.   
    51. // 3. 调用java中的add方法 , 传递两个参数 jint x,y   
    52. JNIEXPORT void JNICALL Java_cn_itcast_ndk4_DataProvider_callCcode2  
    53.   (JNIEnv * env, jobject obj){  
    54.     char* classname = "cn/itcast/ndk4/DataProvider";  
    55.     jclass dpclazz = (*env)->FindClass(env,classname);  
    56.     jmethodID methodID = (*env)->GetMethodID(env,dpclazz,"Add","(II)I");  
    57.     (*env)->CallIntMethod(env, obj,methodID,3l,4l);  
    58. }  
  • 关于实现udev/mdev自动挂载与卸载

    2013-03-12 16:12:18

    在网上有很多关于讲mdev的自动挂载基本上都是一个版本,经过测试自动挂载确实可行,但是关于自动卸载mdev似乎不能很好的支持,经过修改已经可以做到与udev的效果相似。不能在挂载的目录中进行热插拔,否则会出现问题,不过此问题在下次插入U盘时不会造成影响,可能对U盘有损坏。

    本文介绍了mdev与udev两种方法来实现自动挂载,读者可根据需要任选其一即可。

            首先介绍一下mdev与udev之间的关系:

            mdev是busybox中的一个udev管理程序的一个精简版,他也可以实现设备节点的自动创建和设备的自动挂载,只是在实现的过程中有点差异,在发生热插拔时间的时候,mdev是被hotplug直接调用,这时mdev通过环境变量中的 ACTION 和 DEVPATH,来确定此次热插拔事件的动作以及影响了/sys中的那个目录。接着会看看这个目录中是否有“dev”的属性文件,如果有就利用这些信息为这个设备在/dev 下创建设备节点文件。

     

     

    /***********************************************************************************************************************************************************************************/

    1.mdev支持

    ①用busybox制作根文件系统的时候,要选择支持mdev机制

    Linux System Utilities  --->   
               [*] mdev      
               [*]   Support /etc/mdev.conf
               [*]     Support command execution at device addition/removal

    ②在文件系统/etc/init.d/rsC文件中添加如下内容

    Vi  /etc/init.d/rcS
            mount -t tmpfs mdev /dev
            mount -t sysfs sysfs /sys
            mkdir /dev/pts
            mount -t devpts devpts /dev/pts

            echo /sbin/mdev>/proc/sys/kernel/hotplug
            mdev –s

    这些语句的添加在busybox的/doc/mdev.txt中可以找到。

     

    ③添加对热插拔事件的响应,实现U盘和SD卡的自动挂载。

    Vi /etc/mdev.conf
           sd[a-z][0-9]      0:0 666        @/etc/mdev/udisk_insert                        

             sd[a-z]                   0:0 666          $/etc/mdev/udisk_remove

    红色部分,是一个脚本,脚本内容可以根据我们的需要定制,可以实现挂载,卸载或其他一些功能。

    注:@表示是在插入(创建设备结点)后执行后面的脚本,$表示在拔出(删除设备结点)前执行后面的脚本。

    如下是自动挂载和卸载的脚本名称及其内容:

    #!/bin/sh

    if [ -d /sys/block/*/$MDEV ]  ; then

      mkdir -p /media/$MDEV

      mount /dev/$MDEV /media/$MDEV 

    fi

    根文件系统中的etc/mdev/udisk_remove文件内容:
            #!/bin/sh
            umount -l /media/$MDEV
            rm -rf /media/$MDEV
     
            #!/bin/sh
            umount -l /media/sd*
            rm -rf /media/sd*

    修改为红色部分后能够自动挂载

    以上两个脚本需要可执行权限:chmod +x  /etc/mdev/udisk_insert

                   chmod +x etc/mdev/udisk_remove

    /***********************************************************************************************************************************************************************************/

    2.udev支持 

            linux传统上使用静态设备创建的方法,在dev下创建了大量的节点,而不管这些节点相应的硬件设备是否存在。采用udev的方法,系统检测到设备才会去创建这些设备对应的节点。

            这里我们简单的说一下udev的工作原理:

            udev是依赖于sysfs的,当系统中添加一个新的设备后,内核检测到后就会产生一个hotplug event并查找/proc/sys/kernel/hotplug去找出管理设备连接的用户空间程序,若udev已经启动,内核会通知udev去检测sysfs中关于这个新设备的信息并创建设备节点。如/dev/vcs,在/sys/class/tty/vcs/dev存放的是”7:0”,既/dev/vcs的主次设备号。并且udev还会根据/etc/udev/rules.d中的规则文件实现一些相应的功能。

    下面我们介绍一下如何实现设备节点的自动创建及u盘或sd卡的自动挂载。

    因为文件系统中默认是没有对udev进行支持的,所以我们移植一个udev。

    1.下载udev源码udev-100.tar.bz2,并解压

    网址:http://www.us.kernel.org/pub/linux/utils/kernel/hotplug

    2.交叉编译。

    修改makefile,具体修改如下:

            cross = arm-linux-

    保存退出。

    然后执行命令:make 进行编译,然后执行arm-linux-strip udev udevd udevstart udevinfo udevtest,并拷贝这些文件到目标板根文件/bin目录下面。

    3.添加udev的支持

    下面三种方法功能相同

            (1)并修改etc/init.d/rcs脚本,然后添加如下命令:

            /bin/mount -t sysfs sysfs /sys 

            /bin/mount -t tmpfs tmpfs /dev

            /bin/udevd --daemon

            /bin/udevstart

            (2)如果linuxrc是二进制文件的话 

            rm /linuxrc

            vi /linuxrc

            添加如下内容

            /bin/mount -t sysfs sysfs /sys

            /bin/mount -t tmpfs tmpfs /dev

            /bin/udevd --daemon

            /bin/udevstart

            exec /sbin/init

            (3)修改/etc/fstab为

            #device mount-point type options dump fsck order

            proc /proc proc defaults 0 0

            tmpfs /tmp tmpfs defaults 0 0

            sysfs /sys sysfs defaults 0 0

            tmpfs /dev tmpfs defaults 0 0

            修改/etc/init.d/rcs,添加如下内容

    /bin/udevd --daemon

    /bin/udevstart

            重新启动系统,文件系统就能够自动创建节点。

     

    4.在/etc下创建udev目录

    5.在/etc/udev下穿件目录rules.d和文件udev.conf

    6.在udev.conf中添加如下内容

    # udev.conf

            # the initial syslog(3) priority: "err", "info", "debug" or its

            # numerical equivalent. for runtime debugging, the daemons internal

            # state can be changed with: "udevcontrol log_priority=<value>".

            udev_log="err"

    7.在rules.d下创建规则文件

    如实现u盘自动挂载

            vim 11-add-usb.rules

    添加如下内容

            action!="add",goto="farsight"

            kernel=="sd[a-z][0-9]",run+="/sbin/mount-usb.sh %k"

            label="farsight"

    这个文件中action后是说明是什么事件,kernel后是说明是什么设备比如sda1,mmcblk0p1等,run这个设备插入后去执行哪个程序%k是传入这个程序的参数,这里%k=kernel的值也就是sda1等http://www.woaidiannao.com。

    在/sbin/下创建mount-usb.sh文件添加如下内容 计算机

            #!/bin/sh

            /bin/mount -t vfat /dev/$1 /tmp

            sync

    修改文件权限为其添加可执行的权限。

    这样就实现了u盘的自动挂载,下面附上u盘的卸载规则文件和sd卡的文件

    usb卸载

    11-add-remove.rules

            action !="remove",goto="farsight"

            subsystem!="block",goto="farsight"

            kernel=="sd[a-z][0-9]",run+="/sbin/umount-usb.sh"

            label="farsight"

    umount-usb.sh

            #!/bin/sh

            sync

            umount /tmp/

    sd卡挂载

    12-add-sd.rules

    action!="add",goto="farsight"

            kernel=="mmcblk[0-9]p[0-9]",run+="/sbin/mount-sd.sh %k"

            label="farsight"

    mount-sd.sh

            #!/bin/sh

            /bin/mount -t vfat /dev/$1 /tmp

            sync

     

    sd卸载

    12-remove-sd.rules

            action !="remove",goto="farsight"

            subsystem!="block",goto="farsight"

            kernel=="mmcblk*",run+="/sbin/umount-sd.sh"

            label="farsight"

    umount-sd.sh

            #!/bin/sh

            sync

            /bin/umount /tmp/

  • busybox mdev使用 自动创建设备节点

    2013-03-12 15:42:40

    quote]------本文很多关于mdev解释的内容来源于网络,有说的不对的地方,望大家指正。-------
    同时,各位也可以在我的blog里面找到,http://blog.chinaunix.net/space.php?uid=20752341&do=blog&id=3081976

       写Linux 设备驱动程序的时候,很多时候都是利用mknod 命令手动创建设备节点,mdev可以用来在模块加载-- insmod-- 的时候自动在/dev 目录下创建相应设备节点,并在卸载模块-- rmmod --时删除该节点。
       内核同时提供了class_create( …) 函数,可以用它来创建一个类,这个类存放于sysfs 下面,一旦创建好了这个类,再调用device_create(…) 函数来在/dev 目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev 会自动响应device_create( …) 函数,去/sysfs 下寻找对应的类从而创建设备节点。
    举例如下(只是把关键的函数说明下):
    1. #include <linux/module.h>
    2. #include <linux/fs.h>
    3. #include <linux/string.h>
    4. #include <linux/init.h>
    5. #include <linux/platform_device.h>
    6. #include <linux/interrupt.h>
    7. #include <linux/rtc.h>
    8. #include <linux/bcd.h>
    9. #include <linux/clk.h>
    10. #include <linux/device.h>

    11. MODULE_LICENSE("Dual BSD/GPL");

    12. static struct class *firstdrv_class;
    13. static struct class_device *firstdrv_class_dev;


    14. static int first_drv_open(struct inode *inode, struct file *file)
    15. {
    16.     printk("first_drv_open\n");
    17.     
    18.     return 0;
    19. }

    20. static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
    21. {
    22.     printk("first_drv_write\n");
    23.     
    24.     return 0;
    25. }

    26. static struct file_operations first_drv_fops =
    27. {
    28.     .owner = THIS_MODULE,
    29.     .open = first_drv_open,
    30.     .write = first_drv_write,
    31. };

    32. int major;
    33. static int first_drv_init(void)
    34. {
    35.     printk("Hello world!\n");
    36.     major = register_chrdev(0, "first_drv", &first_drv_fops);
    37.     firstdrv_class = class_create(THIS_MODULE, "firstdrv");
    38.     firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz");
    39.     return 0;
    40. }

    41. static void first_drv_exit(void)
    42. {
    43.     printk("Bye, hello world!\n");
    44.     unregister_chrdev(major, "first_drv");
    45.     class_device_unregister(firstdrv_class_dev);
    46.     class_destroy(firstdrv_class);
    47. }

    48. module_init(first_drv_init);
    49. module_exit(first_drv_exit);
    复制代码
    附上Makefile 文件:
    1. KERNELDIR = /opt/linux-2.6.22.6/
    2.     # The current directory is passed to sub-makes as argument
    3. PWD := $(shell pwd)
    4. CC =arm-linux-gcc
    5. obj-m := hello.o
    6. modules:
    7.     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    8. clean:
    9.     rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
    10. .PHONY: modules modules_install clean
    复制代码

    进入目录,make


    生成 hello.ko 文件,拷贝到nfs目录,发现insmod 成功,但是没有自动创建 /dev/xyz设备。

    1. # insmod hello.ko
    2. Hello world!
    3. # lsmod
    4. hello 2188 0 - Live 0xbf000000
    5. # cat /proc/devices
    6. Character devices:
    7.   1 mem
    8.   2 pty
    9.   3 ttyp
    10.   4 /dev/vc/0
    11.   4 tty
    12.   4 ttyS
    13.   5 /dev/tty
    14.   5 /dev/console
    15.   5 /dev/ptmx
    16.   6 lp
    17.   7 vcs
    18. 10 misc
    19. 13 input
    20. 29 fb
    21. 90 mtd
    22. 99 ppdev
    23. 128 ptm
    24. 136 pts
    25. 180 usb
    26. 189 usb_device
    27. 204 s3c2410_serial
    28. 252 first_drv
    29. 253 usb_endpoint
    30. 254 rtc

    31. Block devices:
    32.   1 ramdisk
    33.   7 loop
    34.   8 sd
    35. 31 mtdblock
    36. 65 sd
    37. 66 sd
    38. 67 sd
    39. 68 sd
    40. 69 sd
    41. 70 sd
    42. 71 sd
    43. 128 sd
    44. 129 sd
    45. 130 sd
    46. 131 sd
    47. 132 sd
    48. 133 sd
    49. 134 sd
    50. 135 sd
    51. # ls /dev/xyz
    52. ls: /dev/xyz: No such file or directory
    复制代码


    查看busybox里面的 mdev.txt 文件,于是在 /etc/init.d/rcS里面增加如下命令,蓝色部分(个人觉得 mount -t proc proc /proc 这条可以不用):
    #!/bin/sh
    ifconfig eth0 192.168.1.133
    ifconfig lo up
    mount -a
    mount -t proc proc /proc
    mount -t sysfs sysfs /sys
    echo /sbin/mdev > /proc/sys/kernel/hotplug
    mdev -s

    然后
    reboot,重新insmod 相应的.ko文件

    1. # cd /mnt/
    2. # insmod hello.ko
    3. Hello
    4. # lsmod
    5. hello 2188 0 - Live 0xbf000000
    6. # cat /proc/devices
    7. Character devices:
    8.   1 mem
    9.   2 pty
    10.   3 ttyp
    11.   4 /dev/vc/0
    12.   4 tty
    13.   4 ttyS
    14.   5 /dev/tty
    15.   5 /dev/console
    16.   5 /dev/ptmx
    17.   6 lp
    18.   7 vcs
    19. 10 misc
    20. 13 input
    21. 29 fb
    22. 90 mtd
    23. 99 ppdev
    24. 128 ptm
    25. 136 pts
    26. 180 usb
    27. 189 usb_device
    28. 204 s3c2410_serial
    29. 252 first_drv
    30. 253 usb_endpoint
    31. 254 rtc

    32. Block devices:
    33.   1 ramdisk
    34.   7 loop
    35.   8 sd
    36. 31 mtdblock
    37. 65 sd
    38. 66 sd
    39. 67 sd
    40. 68 sd
    41. 69 sd
    42. 70 sd
    43. 71 sd
    44. 128 sd
    45. 129 sd
    46. 130 sd
    47. 131 sd
    48. 132 sd
    49. 133 sd
    50. 134 sd
    51. 135 sd
    52. # ls /dev/xyz
    53. /dev/xyz
    复制代码

    注意上面的蓝色字体部分,自动创建成功。


    echo /sbin/mdev >/proc/sys/kernel/hotplug 当有热插拔事件产生时,内核就会调用位于/sbin目录的mdev。这时mdev通过环境变量中的 ACTION DEVPATH,(这两个变量是系统自带的)来确定此次热插拔事件的动作以及影响了/sys中的那个目录。接着会看看这个目录中是否有“dev”的属性文件,如果有就利用这些信息为这个设备在/dev 下创建设备节点文件。



    驱动里,那2个函数只是在sysfs里建信息;需要hotplug的mdev根据这些信息来创建节点

661/41234>
Open Toolbar