• [技术干货] windows下侦听全局鼠标事件(利用win32-API)
    一、需求说明正常情况下,Qt获取鼠标事件都是获取窗口内的鼠标事件,当窗口、控件 获取焦点之后,点击窗口内的控件,Qt都可以获取到鼠标事件。 如果窗口没有获取到焦点,或者 点击软件范围外的其他地方,Qt正常的事件捕获就无法监听鼠标的动作了。现在的需求是: 需要在软件之外,任何地方点击鼠标,Qt程序里都能获取到数据事件,实现全局鼠标事件侦听。这个可以利用win32的API接口SetWindowsHookEx来实现侦听。具体实现代码看下面。二、实现代码侦听全局鼠标事件用到了windows系统API函数。完整的测试代码如下: #include "widget.h"  #include "ui_widget.h"  #include <Windows.h>  #include <QDebug>  #pragma execution_character_set("utf-8")  ​  #pragma comment(lib, "user32.lib")  HHOOK hHook = NULL;  using namespace std;  ​  ​  /*  WM_MOUSEMOVE = 0x200  WM_LBUTTONDOWN = 0x201  WM_LBUTTONUP = 0x202  WM_LBUTTONDBLCLK = 0x203  WM_RBUTTONDOWN = 0x204  WM_RBUTTONUP = 0x205  WM_RBUTTONDBLCLK = 0x206 双击事件  WM_MBUTTONDOWN = 0x207  WM_MBUTTONUP = 0x208  WM_MBUTTONDBLCLK = 0x209  WM_MOUSEWHEEL = 0x20A  WM_XBUTTONDOWN = 0x20B  WM_XBUTTONUP = 0x20C  WM_XBUTTONDBLCLK = 0x20D  WM_MOUSEHWHEEL = 0x20E  */  LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)  {      switch(wParam)     {        case WM_LBUTTONDOWN:  //鼠标左键按下             qDebug() << "鼠标左键按下";          break;        case WM_RBUTTONDOWN://鼠标右键键按下           qDebug() << "鼠标右键键按下";        break;     }      return CallNextHookEx(hHook, nCode, wParam, lParam);  }  ​  ​  Widget::Widget(QWidget *parent)     : QWidget(parent)     , ui(new Ui::Widget)  {      ui->setupUi(this);  ​      hHook = SetWindowsHookEx(WH_MOUSE_LL, MouseProc, NULL, 0);      if (hHook == NULL)     {          qDebug() << "Hook failed";     }      else     {          qDebug() << "Hook Success";     }  }  ​  ​  Widget::~Widget()  {      delete ui;  }  ​  ​三、利用win32-API模拟鼠标事件发送3.1 mouse_event函数 VOID WINAPI mouse_event(    _In_ DWORD     dwFlags,    _In_ DWORD     dx,    _In_ DWORD     dy,    _In_ DWORD     dwData,    _In_ ULONG_PTR dwExtraInfo  );dwFlags的常用选项如下: MOUSEEVENTF_ABSOLUTE 是否使用绝对坐标  ​  MOUSEEVENTF_LEFTDOWN 鼠标左键按下  ​  MOUSEEVENTF_LEFTUP 鼠标左键松开  ​  MOUSEEVENTF_MIDDLEDOWN 鼠标中键按下  ​  MOUSEEVENTF_MIDDLEUP 鼠标中键松开  ​  MOUSEEVENTF_MOVE 鼠标移动  ​  MOUSEEVENTF_RIGHTDOWN 鼠标右键按下  ​  MOUSEEVENTF_RIGHTUP 鼠标右键按下  ​  MOUSEEVENTF_WHEEL 鼠标滑轮3.2 包含的头文件与库文件 #include <Windows.h>  #pragma comment(lib, "user32.lib")3.3 使用案例 使用鼠标模拟单击事件  int x = 50;  int y = 50;  mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_LEFTDOWN,x, y, 0, 0);  ​  双击事件  mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_LEFTDOWN,x, y, 0, 0);  mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_LEFTUP,x, y, 0, 0);  mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_LEFTDOWN,x, y, 0, 0);  mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_LEFTUP,x, y, 0, 0);  ​  滚轮事件,delta为滚轮的值  mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_WHEEL,p.x(), p.y(), delta, 0);
  • [技术干货] 利用Proteus仿真STM32实现DHT11温湿度检测
    1. 前言Proteus是英国著名的EDA工具(仿真软件),从原理图布图、代码调试到单片机与外围电路协同仿真,一 键切换到PCB设计,真正实现了从概念到产品的完整设计。是世界上唯一将电路仿真软件、PCB设计软 件和虚拟模型仿真软件三合一的设计平台,其处理器模型支持8051、HC11、 PIC10/12/16/18/24/30/DSPIC33、AVR、ARM、8086和MSP430等,2010年又增加了Cortex和DSP系 列处理器,并持续增加其他系列处理器模型。在编译方面,它也支持IAR、Keil和MATLAB等多种编译 器。 前面文章介绍了Proteus的下载,安装,建立工程,完成LED灯仿真运行。这篇文章在这基础上增加串口打印,DHT11温湿度检测。2. 设计程序先使用keil软件就将程序设计设计好,然后生成HEX文件,等待设计好原理图后进行仿真测试。注意: 当前使用的芯片是STM32F103。Proteus的版本是8.9 #include "stm32f10x.h"  #include "led.h"  #include "delay.h"  #include "key.h"  #include "dht11.h"  ​  /*  (3)温湿度传感器: DHT11  VCC--VCC  GND---GND  DAT---PA5  */  ​  #include "stm32f10x.h"  #include <stdio.h>  #include <stdarg.h>  #include "sys.h"  #include <string.h>  ​  #define USART1_RX_LENGTH 1024  extern u8 USART1_RX_BUFFER[USART1_RX_LENGTH]; //保存接收数据的缓冲区  extern u32 USART1_RX_CNT;  //当前接收到的数据长度  extern u8 USART1_RX_FLAG; //1表示数据接收完毕 0表示没有接收完毕  ​  #define USART2_RX_LENGTH 1024  extern u8 USART2_RX_BUFFER[USART2_RX_LENGTH]; //保存接收数据的缓冲区  extern u32 USART2_RX_CNT;  //当前接收到的数据长度  extern u8 USART2_RX_FLAG; //1表示数据接收完毕 0表示没有接收完毕  ​  #define USART3_RX_LENGTH 1024  extern u8 USART3_RX_BUFFER[USART3_RX_LENGTH]; //保存接收数据的缓冲区  extern u32 USART3_RX_CNT;  //当前接收到的数据长度  extern u8 USART3_RX_FLAG; //1表示数据接收完毕 0表示没有接收完毕  ​  void USART1_Init(u32 baud);  void USART2_Init(u32 baud);  void USART3_Init(u32 baud);  void USARTx_StringSend(USART_TypeDef *USARTx,char *str);  void USARTx_DataSend(USART_TypeDef *USARTx,u8 *data,u32 len);  ​  //定义按键IO口  #define KEY_S3 PAin(1)  ​  //函数声明  void KEY_Init(void);  u8 KEY_Scan(u8 mode);  ​  ​  //LED定义  #define LED1 PBout(6)  #define LED2 PBout(7)  #define LED3 PBout(8)  #define LED4 PBout(9)  ​  //蜂鸣器IO口定义  #define BEEP PAout(6)  ​  //函数声明  void LED_Init(void);  void BEEP_Init(void);  ​  ​  ​  //IO方向设置  #define DHT11_IO_IN() {GPIOA->CRL&=0XFF0FFFFF;GPIOA->CRL|=0x00800000;}  #define DHT11_IO_OUT() {GPIOA->CRL&=0XFF0FFFFF;GPIOA->CRL|=0x00300000;}  ////IO操作函数    #define DHT11_DQ_OUT PAout(5) //数据端口 PA5  #define DHT11_DQ_IN PAin(5)  //数据端口 PA5  ​  ​  u8 DHT11_Init(void); //初始化DHT11  u8 DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度  u8 DHT11_Read_Byte(void); //读出一个字节  u8 DHT11_Read_Bit(void); //读出一个位  u8 DHT11_Check(void); //检测是否存在DHT11  void DHT11_Rst(void); //复位DHT11      ​  //复位DHT11  void DHT11_Rst(void)    {                    DHT11_IO_OUT(); //SET OUTPUT      DHT11_DQ_OUT=0; //拉低DQ      DelayMs(20);   //拉低至少18ms      DHT11_DQ_OUT=1; //DQ=1    DelayUs(30);     //主机拉高20~40us  }  //等待DHT11的回应  //返回1:未检测到DHT11的存在  //返回0:存在  u8 DHT11_Check(void)    {     u8 retry=0;   DHT11_IO_IN();//SET INPUT    while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us   {   retry++;   DelayUs(1);   };   if(retry>=100)return 1;   else retry=0;      while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us   {   retry++;   DelayUs(1);   };   if(retry>=100)return 1;       return 0;  }  //从DHT11读取一个位  //返回值:1/0  u8 DHT11_Read_Bit(void)  {   u8 retry=0;   while(DHT11_DQ_IN&&retry<100)//等待变为低电平   {   retry++;   DelayUs(1);   }   retry=0;   while(!DHT11_DQ_IN&&retry<100)//等待变高电平   {   retry++;   DelayUs(1);   }   DelayUs(40);//等待40us   if(DHT11_DQ_IN)return 1;   else return 0;    }  ​  //从DHT11读取一个字节  //返回值:读到的数据  u8 DHT11_Read_Byte(void)      {              u8 i,dat;      dat=0;   for (i=0;i<8;i++)   {     dat<<=1;      dat|=DHT11_Read_Bit();     }          return dat;  }  ​  ​  //从DHT11读取一次数据  //temp:温度值(范围:0~50°)  //humi:湿度值(范围:20%~90%)  //返回值:0,正常;1,读取失败  u8 DHT11_Read_Data(u8 *temp,u8 *humi)      {           u8 buf[5];   u8 i;   DHT11_Rst();   //printf("------------------------\r\n");   if(DHT11_Check()==0)   {   for(i=0;i<5;i++)//读取40位数据   {   buf[i]=DHT11_Read_Byte();   }   if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])   {   *humi=buf[0];   *temp=buf[2];   }   }else return 1;   return 0;      }  ​  ​  //初始化DHT11的IO口 DQ 同时检测DHT11的存在  //返回1:不存在  //返回0:存在    u8 DHT11_Init(void)  {   RCC->APB2ENR|=1<<2;    //使能PORTA口时钟   GPIOA->CRL&=0XFF0FFFFF;//PORTA.5 推挽输出   GPIOA->CRL|=0X00300000;   GPIOA->ODR|=1<<5;      //输出1       DHT11_Rst();   return DHT11_Check();  }  ​  ​  /*  函数功能:按键初始化  硬件连接:PA1  特性: 按下为低电平---没按下高电平  */  void KEY_Init(void)  {      //开时钟      RCC->APB2ENR|=1<<2;      //配置模式      GPIOA->CRL&=0xFFFFFF0F;      GPIOA->CRL|=0x00000080;      //上拉      GPIOA->ODR|=1<<1;  }  ​  ​  /*  函数功能:函数扫描函数  函数参数: mode=1表示使用连续模式 mode=0使用单击模式  返回值: 2 3 4 5 表示具体的按钮   0表示没有按下  */  u8 KEY_Scan(u8 mode)  {     static u8 flag=1; //记录上一次按下的状态     if(mode)flag=1;     if(flag&&(KEY_S3==0))     {         flag=0;         delay_ms(20);         if(KEY_S3==0)return 3;     }     else if(KEY_S3)     {         flag=1;     }     return 0;  }  ​  ​  /*  函数功能: LED初始化  硬件连接: PB6 PB7 PB8 PB9  特性: 低电平点亮  */  void LED_Init(void)  {      //开时钟      RCC->APB2ENR|=1<<3;      //配置GPIO口      GPIOB->CRL&=0x00FFFFFF;      GPIOB->CRL|=0x22000000;      GPIOB->CRH&=0xFFFFFF00;      GPIOB->CRH|=0x00000022;      //上拉      GPIOB->ODR|=1<<6;      GPIOB->ODR|=1<<7;      GPIOB->ODR|=1<<8;      GPIOB->ODR|=1<<9;  }  ​  /*  函数功能: 蜂鸣器初始化  硬件连接: PA6  特性: 高电平响  */  void BEEP_Init(void)  {     RCC->APB2ENR|=1<<2;     GPIOA->CRL&=0xF0FFFFFF;     GPIOA->CRL|=0x02000000;  }  ​  ​  /*  函数功能: 串口1的初始化  硬件连接: PA9(TX) 和 PA10(RX)  */  void USART1_Init(u32 baud)  {      /*1. 开时钟*/      RCC->APB2ENR|=1<<14; //USART1时钟      RCC->APB2ENR|=1<<2;  //PA      RCC->APB2RSTR|=1<<14; //开启复位时钟      RCC->APB2RSTR&=~(1<<14);//停止复位      /*2. 配置GPIO口的模式*/      GPIOA->CRH&=0xFFFFF00F;      GPIOA->CRH|=0x000008B0;      /*3. 配置波特率*/      USART1->BRR=72000000/baud;      /*4. 配置核心寄存器*/      USART1->CR1|=1<<5; //开启接收中断      STM32_SetPriority(USART1_IRQn,1,1); //设置中断优先级      USART1->CR1|=1<<2; //开启接收      USART1->CR1|=1<<3; //开启发送      USART1->CR1|=1<<13;//开启串口功能  }  ​  /*  函数功能: 串口2的初始化  硬件连接: PA2(TX) 和 PA3(RX)  */  void USART2_Init(u32 baud)  {      /*1. 开时钟*/      RCC->APB1ENR|=1<<17; //USART2时钟      RCC->APB2ENR|=1<<2;  //PA      RCC->APB1RSTR|=1<<17; //开启复位时钟      RCC->APB1RSTR&=~(1<<17);//停止复位            /*2. 配置GPIO口的模式*/      GPIOA->CRL&=0xFFFF00FF;      GPIOA->CRL|=0x00008B00;      /*3. 配置波特率*/      USART2->BRR=36000000/baud;      /*4. 配置核心寄存器*/      USART2->CR1|=1<<5; //开启接收中断      STM32_SetPriority(USART2_IRQn,1,1); //设置中断优先级      USART2->CR1|=1<<2; //开启接收      USART2->CR1|=1<<3; //开启发送      USART2->CR1|=1<<13;//开启串口功能  }  ​  /*  函数功能: 串口3的初始化  硬件连接: PB10(TX) 和 PB11(RX)  */  void USART3_Init(u32 baud)  {      /*1. 开时钟*/      RCC->APB1ENR|=1<<18; //USART3时钟      RCC->APB2ENR|=1<<3;  //PB      RCC->APB1RSTR|=1<<18; //开启复位时钟      RCC->APB1RSTR&=~(1<<18);//停止复位            /*2. 配置GPIO口的模式*/      GPIOB->CRH&=0xFFFF00FF;      GPIOB->CRH|=0x00008B00;      /*3. 配置波特率*/      USART3->BRR=36000000/baud;      /*4. 配置核心寄存器*/      USART3->CR1|=1<<5; //开启接收中断      STM32_SetPriority(USART3_IRQn,1,1); //设置中断优先级      USART3->CR1|=1<<2; //开启接收      USART3->CR1|=1<<3; //开启发送      USART3->CR1|=1<<13;//开启串口功能  }  ​  u8 USART1_RX_BUFFER[USART1_RX_LENGTH]; //保存接收数据的缓冲区  u32 USART1_RX_CNT=0;  //当前接收到的数据长度  u8 USART1_RX_FLAG=0; //1表示数据接收完毕 0表示没有接收完毕  ​  //串口1的中断服务函数  void USART1_IRQHandler(void)  {      u8 data;      //接收中断      if(USART1->SR&1<<5)     {          TIM1->CNT=0; //清除计数器          TIM1->CR1|=1<<0; //开启定时器1          data=USART1->DR; //读取串口数据        // if(USART1_RX_FLAG==0) //判断上一次的数据是否已经处理完毕         {              //判断是否可以继续接收              if(USART1_RX_CNT<USART1_RX_LENGTH)             {                 USART1_RX_BUFFER[USART1_RX_CNT++]=data;             }              else  //不能接收,超出存储范围,强制表示接收完毕             {                  USART1_RX_FLAG=1;             }         }     }  }  ​  ​  u8 USART2_RX_BUFFER[USART2_RX_LENGTH]; //保存接收数据的缓冲区  u32 USART2_RX_CNT=0;  //当前接收到的数据长度  u8 USART2_RX_FLAG=0; //1表示数据接收完毕 0表示没有接收完毕  ​  //串口2的中断服务函数  void USART2_IRQHandler(void)  {      u8 data;      //接收中断      if(USART2->SR&1<<5)     {          TIM2->CNT=0; //清除计数器          TIM2->CR1|=1<<0; //开启定时器2          data=USART2->DR; //读取串口数据        // if(USART2_RX_FLAG==0) //判断上一次的数据是否已经处理完毕         {              //判断是否可以继续接收              if(USART2_RX_CNT<USART2_RX_LENGTH)             {                 USART2_RX_BUFFER[USART2_RX_CNT++]=data;             }              else  //不能接收,超出存储范围,强制表示接收完毕             {                  USART2_RX_FLAG=1;             }         }     }  }  ​  u8 USART3_RX_BUFFER[USART3_RX_LENGTH]; //保存接收数据的缓冲区  u32 USART3_RX_CNT=0;  //当前接收到的数据长度  u8 USART3_RX_FLAG=0; //1表示数据接收完毕 0表示没有接收完毕  ​  //串口3的中断服务函数  void USART3_IRQHandler(void)  {      u8 data;      //接收中断      if(USART3->SR&1<<5)     {          TIM3->CNT=0; //清除计数器          TIM3->CR1|=1<<0; //开启定时器3          data=USART3->DR; //读取串口数据        // if(USART3_RX_FLAG==0) //判断上一次的数据是否已经处理完毕         {              //判断是否可以继续接收              if(USART3_RX_CNT<USART3_RX_LENGTH)             {                 USART3_RX_BUFFER[USART3_RX_CNT++]=data;             }              else  //不能接收,超出存储范围,强制表示接收完毕             {                  USART3_RX_FLAG=1;             }         }     }  }  ​  ​  /*  函数功能: 字符串发送  */  void USARTx_StringSend(USART_TypeDef *USARTx,char *str)  {     while(*str!='\0')     {         USARTx->DR=*str++;         while(!(USARTx->SR&1<<7)){}     }  }  ​  /*  函数功能: 数据发送  */  void USARTx_DataSend(USART_TypeDef *USARTx,u8 *data,u32 len)  {     u32 i;     for(i=0;i<len;i++)     {         USARTx->DR=*data++;         while(!(USARTx->SR&1<<7)){}     }  }  ​  //printf函数底层函数接口  int fputc(int c, FILE* stream)  {      USART1->DR=c;      while(!(USART1->SR&1<<7)){}      return c;  }  ​  ​  u8 dht11_temp;  u8 dht11_humidity;  ​  int main()  {     u8 key_val;     u32 time=0;     LED_Init();     BEEP_Init();     KEY_Init();     USART1_Init(115200);    //串口1初始化-打印调试信息     //初始化DHT11     DHT11_Init();           while(1)     {        key_val=KEY_Scan(0); //PA1        if(key_val)       {           BEEP=!BEEP;           LED1=!LED1;   //PB6       }        delay_ms(5);                time++;        if(time>=10)       {          time=0;          LED2=!LED2; //PB7                      //读取温湿度          if(DHT11_Read_Data(&dht11_temp,&dht11_humidity))         {              printf("温度读取失败.\r\n");         }                  printf("T:%d,H:Õ2e9a052-d49f-48d4-a6fb-b11e60234304r\n",dht11_temp,dht11_humidity);                    //湿度大于80以上就关闭插座          if(dht11_humidity>80)         {              LED1=1;         }       }     }  }3. 设计电路图3.1 添加DHT11器件打开Proteus,搜索DHT11元器件。鼠标选择空白区域,点击鼠标右键,放置电源和GND。设计好的效果如下:3.2 添加虚拟串口终端为了方便查看程序的串口输出,添加一个串口终端显示框。在虚拟仪表模式下,选择virtual terminal工具,然后在原理图空白区域点击一下就可以放virtual terminal工具。在绘制原理图的经常遇到连线复杂,或者布线很乱,如果元器件的引脚不方便直接与MCU单片机连接,可以采用标签的形式或者总线方式布线。这里以串口终端演示,采用标签方式连接IO口。首先在坐标的菜单栏里选择终端模式,然后鼠标点击DEFAULT,然后在原理图的空白区域,点击一下鼠标左键,会出现一个空心的连接线条,将这个连接线条连接到元器件的IO口上就行。放置好之后,鼠标点击这个接线端子--空心圆圈,弹出对话框,设置连接的IO口。然后MCU的PA9和PA10的端子上也设置好标签名称。设置虚拟串口显示器的波特率为:115200如果在调试仿真时, Virtual Terminal无法自动弹出窗口,可以点击菜单栏的调试,选择恢复弹出窗口。设置STM32芯片的晶振为:71MHZ3.3 开始仿真
  • [技术干货] 得到唯一文件名-当前时间与GUID
    一、前言在程序开发过程中,程序里经常会保存一些临时文件到本地,为了文件不重名,一般可以使用GUID或者当前时间来作为命名方式。二、代码(1)获取GUIDQString GetName_GUID() { QUuid guid = QUuid::createUuid(); QString str = guid.toString(); str.remove('{'); str.remove('}'); str+=".txt"; return str; }(2)获取当前时间QString GetName_TimeDate() { //获取当前时间用来设置当前视频文件的名称 QDateTime dateTime(QDateTime::currentDateTime()); //时间效果: 2020-03-05 16:25::04 周四 QString VideoSavePath; VideoSavePath=dateTime.toString("yyyy-MM-dd-hh-mm-ss"); VideoSavePath+=".txt"; return VideoSavePath; }(3)获取当前ms时间QString GetName_TimeDate() { //秒级时间戳(十位) //QString timestamp = QString::number(QDateTime::currentMSecsSinceEpoch() / 1000); //毫秒级时间戳(十三位) QString timestamp = QString::number(QDateTime::currentMSecsSinceEpoch()); timestamp+=".txt"; return timestamp; }
  • [技术干货] QSettings管理用户环境变量(修改、输出)
    1. 前言在软件开发中可能有需求修改用户环境变量,添加新的值进行。比如:添加某些可执行文件、某些动态库的路径到系统环境PATH中,能够让可执行文件运行过程中可以找到对应的dll。在Qt里可以使用QSettings来实现,QSettings类提供一个独立于平台的应用程序设置,Qt已经封装好,修改、读取用户的环境变量不需要管理员权限,并且修改也是直接针对系统的环境配置进行修改,并非当前进程有效(所以修改要谨慎操作)。2. QSettingsQSettings可以修改注册表,支持存储自定义数据格式,通常可以保存应用程序设置,保存和恢复应用程序设置。QSettings的详细功能在Qt帮助页面有详细的介绍,当前这里只是列出QSettings修改用户环境变量的一个使用案例,其他功能不做详细介绍。下面是来至Qt帮助页面的介绍: QSettings类提供持久的独立于平台的应用程序设置。  用户通常期望应用程序在会话中记住其设置(窗口大小和位置、选项等)。这些信息通常存储在Windows上的系统注册表中,以及macOS和iOS上的属性列表文件中。在Unix系统上,在没有标准的情况下,许多应用程序(包括KDE应用程序)使用INI文本文件。  QSettings是围绕这些技术的抽象,使能够以可移植的方式保存和恢复应用程序设置。它还支持自定义存储格式。  QSettings的API基于QVariant,允许以最小的工作量保存大多数基于值的类型,如QString、QRect和QImage。  如果只需要一个基于非持久内存的结构,请考虑使用QMap<QString,QVariant>。  基本用法  创建QSettings对象时,必须传递公司或组织的名称以及应用程序的名称。例如,如果的产品名为Star Runner,而的公司名为MySoft,则可以按照如下方式构造QSettings对象:  Q设置("MySoft"、"Star Runner");  QSettings对象可以在堆栈上或堆上创建(即使用new)。构造和销毁QSettings对象非常快。  如果在应用程序中使用来自多个位置的QSettings,则可能需要使用QCoreApplication::setOrganizationName()和qCoreApp::setApplicationName()指定组织名称和应用程序名称,然后使用默认的QSetting构造函数:  QCoreApplication::setOrganizationName("MySoft");  QCoreApplication::setOrganizationDomain("mysoft.com");  QCoreApplication::setApplicationName("明星跑步者");  ...  Q设置设置;  (这里,我们还指定了组织的Internet域。当设置Internet域时,它将在macOS和iOS上使用,而不是组织名称,因为macOS和iOS应用程序通常使用Internet域来标识自己。如果未设置域,则从组织名称派生假域。有关详细信息,请参阅下面的平台特定说明。)  QSettings存储设置。每个设置由指定设置名称(键)的QString和存储与键关联的数据的QVariant组成。要编写设置,请使用setValue()。例如:  设置setValue("编辑器/包装边缘",68);  如果已经存在具有相同键的设置,则现有值将被新值覆盖。为了提高效率,更改可能不会立即保存到永久存储中。(可以随时调用sync()来提交更改。)  可以使用value()返回设置的值:  int margin = settings.value("editor/wrapMargin").toInt();  ​  如果没有指定名称的设置,QSettings将返回空QVariant(可以转换为整数0)。可以通过向value()传递第二个参数来指定另一个默认值:   int margin = settings.value("editor/wrapMargin", 80).toInt();  ​  要测试给定键是否存在,请调用contains()。要删除与键关联的设置,请调用remove()。要获取所有键的列表,请调用allKeys()。要删除所有键,请调用clear()。3. 实现代码Demo #include "widget.h"  #include "ui_widget.h"  ​  Widget::Widget(QWidget *parent)     : QWidget(parent)     , ui(new Ui::Widget)  {      ui->setupUi(this);  ​      this->setWindowTitle("用户环境变量管理");  }  ​  ​  Widget::~Widget()  {      delete ui;  }  ​  ​  //打印系统环境变量  void Widget::on_pushButton_print_env_val_clicked()  {      QString env_name=ui->lineEdit_env_path_name->text();      if(env_name.isEmpty())return;  ​      //参数解释      //【1】. "HKEY_CURRENT_USER\Environment": 用户环境变量      //【2】. QSettings::NativeFormat: 使用最适合平台的存储格式存储设置。      QSettings seting("HKEY_CURRENT_USER\Environment", QSettings::NativeFormat);  ​      //打印用户环境变量path的值。      QString text_val = seting.value(env_name).toString();      ui->plainTextEdit->setPlainText(text_val);  }  ​  ​  //设置环境变量的值  void Widget::on_pushButton_set_env_val_clicked()  {      QString env_name=ui->lineEdit_set_env_name->text();      if(env_name.isEmpty())return;  ​      QString env_val=ui->lineEdit_env_add_val->text();      if(env_val.isEmpty())return;  ​      //参数解释      //【1】. "HKEY_CURRENT_USER\Environment": 用户环境变量      //【2】. QSettings::NativeFormat: 使用最适合平台的存储格式存储设置。      QSettings seting("HKEY_CURRENT_USER\Environment", QSettings::NativeFormat);  ​      //获取原环境变量的值      QString text_val = seting.value(env_name).toString();  ​      //遵循windows下环境变量里的路径      env_val = env_val.replace("/", "\");  ​      //windows环境变量;作为间隔      text_val.append(";");  ​      //添加用户设置的值      text_val.append(env_val);  ​      //添加新的值      seting.setValue(env_name,text_val);  ​      QMessageBox::about(this,"提示",tr("新值设置成功!"));  }  ​  ​  //清空环境变量  void Widget::on_pushButton_clean_env_clicked()  {      QString env_name=ui->lineEdit_clean_env_name->text();      if(env_name.isEmpty())return;  ​      //参数解释      //【1】. "HKEY_CURRENT_USER\Environment": 用户环境变量      //【2】. QSettings::NativeFormat: 使用最适合平台的存储格式存储设置。      QSettings seting("HKEY_CURRENT_USER\Environment", QSettings::NativeFormat);      //清空环境变量      seting.setValue(env_name,"");  ​      QMessageBox::about(this,"提示",tr("清空成功!"));  }
  • [技术干货] 基于深度学习框架设计的货运管家(功能总结)
    项目背景:在快递行业发达的今天,有数不胜数的货运公司、快递公司,这些公司都有自己的运输车辆,请师傅开车送货。比如:快递公司、烟草运输公司、货物运输公司等等。 为了能方便管理货车,了解车辆行驶路线(是否是公司规定的路线)、行驶过程中是否违规吸烟、疲劳驾驶、未系安全带等等。根据需求开发了这一套智能货运管家系统。功能框架:货运管家的车载主机--显示屏界面:下面介绍这套货运管家系统功能实现的技术指标:1 识别过程原理通过采集数据,做二值化分类,给神经网络输入正向的图片(例如抽烟)和负向的图片(例如不抽烟),进行训练学习。神经网络会把这个特征学会。神经网络并不知道某个行为是什么意思。但是通过卷积神经网络学到了这个特征。所以过程需要采集各类正负向图片。识别的思路,首先需要先识别图像中是否有人体,若检测到至少1个人体,将目标最大的人体作为驾驶员,进一步识别驾驶员的属性行为,再逐步分析识别是否使用手机、抽烟、未系安全带、双手离开方向盘、视线未朝前方、未佩戴口罩、闭眼、打哈欠、低头等典型行为姿态。 通过分析人体行为的这项技术还可以针对出租车、客车、公交车、货车等各类营运车辆,实时监控车内情况,识别驾驶员抽烟、使用手机、未系安全带、未佩戴口罩、疲劳、视线偏离等违规行为,及时预警,降低事故发生率,保障人身财产安全。识别的图像需要提前使用算法训练得到模型,深度学习算法里面的基本模型大致分为了3类:多层感知机模型;深度神经网络模型和递归神经网络模型。其代表分别是DBN(Deep belief network) 深度信念网络、CNN(Convolution Neural Networks)卷积神经网络、RNN(Recurrent neural network) 递归神经网络。2 动作定义对于某些“动作”其实可以理解为单帧图片识别。例如,抽烟这个动作。我们对抽烟的严谨定义就是,一个张图片里只要嘴巴位置叼着烟就是吸烟。宽松定义就是,图片中的嘴巴位置出现烟,或者手上出现烟就认为这个特征是吸烟。再宽松一点,图片中的嘴巴位置或者手上的位置出现类似烟的东西(例如白纸),就认为是抽烟。3 单个动作识别和连续动作识别单个动作识别就是只需要对任意一帧图片进行识别,便可以得出该帧图片到底是包含还是不不包含某个特征。它和前后帧没有多大联系。例如,抽烟、未系安全带、打电话等,基本通过单帧图像识别。而对于疲劳驾驶,则要求更复杂一些,因为某些情况下一帧图片(例如张嘴)不能代表疲劳。它也有可能表示正常的张嘴。此时需要对连续动作进行识别,根据前后帧的结果进行计算。来判断当前连续动作是否属于疲劳的范畴。4 一些要求1 自带NPU的芯片。2 采集摄像头要带有夜视功能。5 识别抽烟类别:单帧图像识别。识别率:85%6 识别未系安全带类别:单帧图像识别。识别率:85%7 识别打电话类别:单帧图像识别。识别率:85%8 识别疲劳驾驶疲劳驾驶主要是通过检测眼睛的闭合频率来识别的,眼睛闭合的频率和持续时间在某种程度上可以反映疲劳的状态。卡内基梅隆研究所经过反复试验和论证,提出了度量疲劳的物理PERCLOS。PERCLOS定义为一定时间内眼睛的闭合程度,它已经成为度量疲劳状态的一种科学有效的方法。当一定时间间隔内眼睛闭合所占的时间比例超过15%时即认为是疲劳状态。PERCLOS方法通过眼睛闭合所占的时间比例进行疲劳驾驶的判定。但是,眼睛的大小因人而异,眼睛的面积因受所在场景和头部运动的影响也是动态变化的,眼睛的睁开程度是相对于自身的最大睁开状态而言的。当然,时间可以转换为视频帧数,在判断眼睛的状态特征时我们使用的是类PERCLOS的方法。目前,PERCLO方法有三种判断疲劳的不同准则,分别E准则、P70准则、P80准则。其具体含义如下:EM准则:瞳孔被眼睑覆盖超50%的面积,则认为眼睛是闭合的;P70准则:瞳孔被眼睑覆盖超70%的面积,则认为眼睛是闭合的;P80准则:瞳孔被眼睑覆盖超过80%的面积,则认为眼睛是闭合的。当人注意力特别集中或处在沉思状态时可能也会有眼睑覆盖瞳孔超过50%甚至70%的可能,所以系统采用的是P80准则。正常情况下,人在一分钟之内要眨十次左右的眼睛,每次需要0.30.4秒左右,两次眨眼之间的间隔约为2.84.0秒。然而,由于管制员工作性质的不同,需要其在工作中注意力高度集中,所以眨眼次数略少,约5~10次。眼睛闭合的频率以及闭合时间的长短与疲劳有密切联系,如果连续监测到管制员的PERCLOS>30%且平均闭眼时长>0.25s,就判定管制员处于疲劳状态,并发出报警。类别:连续帧识别。识别率:80%~85%。动作定义:①连续n秒内取出x帧画面出现y次张嘴动作(涉及张嘴幅度的计算)n取3-5,是一个根据反馈调节的参数。x取10~25,是一个根据反馈调节的参数。y取10~25。②连续n秒内取出x帧画面出现y次眼睛闭合动作(涉及闭眼幅度的计算)。n取3-5,是一个根据反馈调节的参数。x取10~25,是一个根据反馈调节的参数。y取10~25。
  • [技术干货] C语言_printf、sprintf补0、补空格占位
    一般在处理时间的时候,界面上显示,打印输出这些场景下,左边补0或者补空格占位是很常见的。补0或者补空格之后,长度是固定的;这样显示更加美观、不会因为数字变短、变长造成闪烁感。示例代码:int main() { printf("Ü9de7ea9-554e-479d-a4be-5ff20925f61dn",12345); //正常打印 printf("d\n",12345); //右对齐.位数不够,左边自动补空格 printf("%-10d,Ì9de7ea9-554e-479d-a4be-5ff20925f61dn", 12345,'A');//左对齐.位数不够,右边自动补空格 printf("0d\n",12345); //右对齐.位数不够,左边自动补0 //sprintf用法一样. return 0; } 输出结果: 12345 12345 12345 ,A 0000012345在vs2017里使用sprintf需要在属性--C/C++---预处理器---增加(_CRT_SECURE_NO_WARNINGS)案例: 将ms时间转为时分秒.  控制位数std::string MStoString(long nMicroSecond) { int second = nMicroSecond / 1000; int hours, mins, secs, minSecs; secs = second % 60; mins = (second / 60) % 60; hours = second / 3600; minSecs = nMicroSecond - (hours * 3600 + mins * 60 + secs) * 1000; char buff[1024]; //sprintf数字补0 sprintf(buff,"d:d:d.d", hours, mins, secs, minSecs); std::string strTime = buff; return strTime; } int main() { printf("%s\n", MStoString(50000).c_str()); return 0; }
  • [技术干货] STM32+BH1750光敏传感器获取光照强度
    一、环境介绍MCU:  STM32F103ZET6光敏传感器:  BH1750数字传感器(IIC接口)开发软件: Keil5代码说明: 使用IIC模拟时序驱动,方便移植到其他平台,采集的光照度比较灵敏.  合成的光照度返回值范围是 0~255。 0表示全黑  255表示很亮。实测:   手机闪光灯照着的状态返回值是245左右,手捂着的状态返回值是10左右. 二、BH1750介绍三、核心代码BH1750说明:  ADDR引脚接地,地址就是0x463.1 iic.c#include "iic.h" /* 函数功能:IIC接口初始化 硬件连接: SDA:PB7 SCL:PB6 */ void IIC_Init(void) { RCC->APB2ENR|=1<<3;//PB GPIOB->CRL&=0x00FFFFFF; GPIOB->CRL|=0x33000000; GPIOB->ODR|=0x3<<6; } /* 函数功能:IIC总线起始信号 */ void IIC_Start(void) { IIC_SDA_OUTMODE(); //初始化SDA为输出模式 IIC_SDA_OUT=1; //数据线拉高 IIC_SCL=1; //时钟线拉高 DelayUs(4); //电平保持时间 IIC_SDA_OUT=0; //数据线拉低 DelayUs(4); //电平保持时间 IIC_SCL=0; //时钟线拉低 } /* 函数功能:IIC总线停止信号 */ void IIC_Stop(void) { IIC_SDA_OUTMODE(); //初始化SDA为输出模式 IIC_SDA_OUT=0; //数据线拉低 IIC_SCL=0; //时钟线拉低 DelayUs(4); //电平保持时间 IIC_SCL=1; //时钟线拉高 DelayUs(4); //电平保持时间 IIC_SDA_OUT=1; //数据线拉高 } /* 函数功能:获取应答信号 返 回 值:1表示失败,0表示成功 */ u8 IIC_GetACK(void) { u8 cnt=0; IIC_SDA_INPUTMODE();//初始化SDA为输入模式 IIC_SDA_OUT=1; //数据线上拉 DelayUs(2); //电平保持时间 IIC_SCL=0; //时钟线拉低,告诉从机,主机需要数据 DelayUs(2); //电平保持时间,等待从机发送数据 IIC_SCL=1; //时钟线拉高,告诉从机,主机现在开始读取数据 while(IIC_SDA_IN) //等待从机应答信号 { cnt++; if(cnt>250)return 1; } IIC_SCL=0; //时钟线拉低,告诉从机,主机需要数据 return 0; } /* 函数功能:主机向从机发送应答信号 函数形参:0表示应答,1表示非应答 */ void IIC_SendACK(u8 stat) { IIC_SDA_OUTMODE(); //初始化SDA为输出模式 IIC_SCL=0; //时钟线拉低,告诉从机,主机需要发送数据 if(stat)IIC_SDA_OUT=1; //数据线拉高,发送非应答信号 else IIC_SDA_OUT=0; //数据线拉低,发送应答信号 DelayUs(2); //电平保持时间,等待时钟线稳定 IIC_SCL=1; //时钟线拉高,告诉从机,主机数据发送完毕 DelayUs(2); //电平保持时间,等待从机接收数据 IIC_SCL=0; //时钟线拉低,告诉从机,主机需要数据 } /* 函数功能:IIC发送1个字节数据 函数形参:将要发送的数据 */ void IIC_WriteOneByteData(u8 data) { u8 i; IIC_SDA_OUTMODE(); //初始化SDA为输出模式 IIC_SCL=0; //时钟线拉低,告诉从机,主机需要发送数据 for(i=0;i<8;i++) { if(data&0x80)IIC_SDA_OUT=1; //数据线拉高,发送1 else IIC_SDA_OUT=0; //数据线拉低,发送0 IIC_SCL=1; //时钟线拉高,告诉从机,主机数据发送完毕 DelayUs(2); //电平保持时间,等待从机接收数据 IIC_SCL=0; //时钟线拉低,告诉从机,主机需要发送数据 DelayUs(2); //电平保持时间,等待时钟线稳定 data<<=1; //先发高位 } } /* 函数功能:IIC接收1个字节数据 返 回 值:收到的数据 */ u8 IIC_ReadOneByteData(void) { u8 i,data; IIC_SDA_INPUTMODE();//初始化SDA为输入模式 for(i=0;i<8;i++) { IIC_SCL=0; //时钟线拉低,告诉从机,主机需要数据 DelayUs(2); //电平保持时间,等待从机发送数据 IIC_SCL=1; //时钟线拉高,告诉从机,主机现在正在读取数据 data<<=1; if(IIC_SDA_IN)data|=0x01; DelayUs(2); //电平保持时间,等待时钟线稳定 } IIC_SCL=0; //时钟线拉低,告诉从机,主机需要数据 (必须拉低,否则将会识别为停止信号) return data; }3.2 iic.h#ifndef _IIC_H #define _IIC_H #include "stm32f10x.h" #include "sys.h" #include "delay.h" #define IIC_SDA_OUTMODE() {GPIOB->CRL&=0x0FFFFFFF;GPIOB->CRL|=0x30000000;} #define IIC_SDA_INPUTMODE() {GPIOB->CRL&=0x0FFFFFFF;GPIOB->CRL|=0x80000000;} #define IIC_SDA_OUT PBout(7) //数据线输出 #define IIC_SDA_IN PBin(7) //数据线输入 #define IIC_SCL PBout(6) //时钟线 void IIC_Init(void); void IIC_Start(void); void IIC_Stop(void); u8 IIC_GetACK(void); void IIC_SendACK(u8 stat); void IIC_WriteOneByteData(u8 data); u8 IIC_ReadOneByteData(void); #endif3.3 BH1750.h#ifndef _BH1750_H #define _BH1750_H #include "delay.h" #include "iic.h" #include "usart.h" u8 Read_BH1750_Data(void); #endif3.4 BH1750.c#include "bh1750.h" u8 Read_BH1750_Data() { unsigned char t0; unsigned char t1; unsigned char t; u8 r_s=0; IIC_Start(); //发送起始信号 IIC_WriteOneByteData(0x46); r_s=IIC_GetACK();//获取应答 if(r_s)printf("error:1\r\n"); IIC_WriteOneByteData(0x01); r_s=IIC_GetACK();//获取应答 if(r_s)printf("error:2\r\n"); IIC_Stop(); //停止信号 IIC_Start(); //发送起始信号 IIC_WriteOneByteData(0x46); r_s=IIC_GetACK();//获取应答 if(r_s)printf("error:3\r\n"); IIC_WriteOneByteData(0x01); r_s=IIC_GetACK();//获取应答 if(r_s)printf("error:4\r\n"); IIC_Stop(); //停止信号 IIC_Start(); //发送起始信号 IIC_WriteOneByteData(0x46); r_s=IIC_GetACK();//获取应答 if(r_s)printf("error:5\r\n"); IIC_WriteOneByteData(0x10); r_s=IIC_GetACK();//获取应答 if(r_s)printf("error:6\r\n"); IIC_Stop(); //停止信号 DelayMs(300); //等待 IIC_Start(); //发送起始信号 IIC_WriteOneByteData(0x47); r_s=IIC_GetACK();//获取应答 if(r_s)printf("error:7\r\n"); t0=IIC_ReadOneByteData(); //接收数据 IIC_SendACK(0); //发送应答信号 t1=IIC_ReadOneByteData(); //接收数据 IIC_SendACK(1); //发送非应答信号 IIC_Stop(); //停止信号 t=(((t0<<8)|t1)/1.2); return t; }3.5 main.c#include "stm32f10x.h" #include "led.h" #include "delay.h" #include "key.h" #include "usart.h" #include "at24c02.h" #include "bh1750.h" int main() { u8 val; LED_Init(); BEEP_Init(); KeyInit(); USARTx_Init(USART1,72,115200); IIC_Init(); while(1) { val=KeyScan(); if(val) { val=Read_BH1750_Data(); printf("光照强度=Ý4e5bbd4-21d6-4ce1-a196-44124210eb90r\n",val); // BEEP=!BEEP; LED0=!LED0; LED1=!LED1; } } }  3.6  运行效果图 
  • [技术干货] win10系统下搭建FTP服务器(完成文件上传与下载)
    一、环境介绍操作系统: win10 (64位)二、FTP介绍FTP (File Transfer Protocol) 可说是最古老的协议之一了,主要是用来进行文件的传输,尤其是大型文件的传输使用 FTP 更是方便。在FTP的使用当中,用户经常遇到两个概念:"下载"(Download)和"上载"(Upload)。"下载"文件就是从远程主机拷贝文件至自己的计算机上;"上载"文件就是将文件从自己的计算机中拷贝至远程主机上。用Internet 语言来说,用户可通过客户机程序向(从)远程主机上载(下载)文件。TCP/IP 协议中,FTP 标准命令 TCP 端口号为 21,Port 方式数据端口为 20。FTP 协议的任务是从一台计算机将文件传送到另一台计算机,它与这两台计算机所处的位置、联接的方式、甚至是是否使用相同的操作系统无关。假设两台计算机通过 ftp 协议对话,并且能访问 Internet, 你可以用 ftp 命令来传输文件。每种操作系统使用上有某一些细微差别,但是每种协议基本的命令结构是相同的。三、win10系统下搭建FTP服务器3.1  开启FTP服务器鼠标放在此电脑选项上,鼠标右键选择属性:进入控制面板:进入程序更改页面\启动windows自带的功能启动FTP服务器与客户端程序功能安装成功进入控制面板页面\所有控制面板选项:  选择管理工具:选择Internet管理器:鼠标右键选择添加FTP站点:设置站点名称与物理路径:设置本机IP地址:设置登录的用户权限查看FTP服务器状态3.2 登录FTP访问文件浏览器上直接访问FTP服务器站点:在浏览器上直接下载FTP站点的内容:电脑文件管理系统里访问FTP站点:3.3 安装FileZilla FTP客户端软件登录FTP服务器下载地址: cid:link_03.4 FTP服务器设置指定用户登录要设置FTP服务器使用指定的账户登录,需要先在windows上创建一个本地的新账户或者原来电脑的本地账户,用于FTP服务器登录。3.5 linux下登录FTP服务器站点(浏览器方式)说明: 下面Linux系统以Redhat6.3为例。如果Linux系统跑在VM虚拟机环境下,想要与windows系统进行通信,需要设置VM桥接到windows当前使用的网卡即可,可以手动设置IP地址在同一个网段。比如: windows系统当前使用的WIFI方式上网,IP地址为172.16.21.69。那么在VM虚拟机里就设置桥接模式,桥接到WIFI网卡上。在虚拟机设置里也设置成桥接模式。然后在命令行手动设置网卡IP地址:[wbyq@wbyq ~]$ sudo ifconfig eth0 172.16.21.123完支持ping一下windows的IP地址,测试网络是否畅通。能ping通windowsIP地址,就可以打开浏览器,直接访问FTP站点。3.6 linux系统下安装FTP软件登录FTP服务器站点3.6.1 安装FTP客户端软件红帽 6.3 系统光盘中自带 ftp 安装包,挂载红帽 6.3 光盘,找到 ftp 安装包安装即可。软件安装之后,在命令行就多了一个可用的ftp命令,用于登录FTP服务器站点。查看命令的帮助:[wbyq@wbyq ~]$ man ftp3.6.2 FTP命令登录FTP服务器[wbyq@wbyq ~]$ ftp <服务器的IP地址>   实名用户登录首先#ftp +IP(server)输入用户名(server的用户名)输入密码(server的密码)匿名用户登录#ftp +IP(server)用户名:anonymous (匿名用户固定的名字)密码:直接回车 (不用输入密码)实例:3.6.3 查看FTP命令帮助进入FTP命令行之后,输入一个?号即可看当前命令行支持的功能命令。3.6.4 文件的上传和下载文件的上传:#put  filename(上传登录之前所在目录的内容)文件的下载:#get  filename不允许下载目录,如果想操作目录,得先打包文件在登陆之前先确保当前所在目录3.6.5 退出服务器#bye #quit#exit 3.7 linux系统下安装lftp工具登录FTP服务器3.7.1 安装lftp工具#lftp  服务器ip    //第一步#login             //第二步#pwd#put    //上传文件#mput  filename  filename  //同时上传同个文件#get  下载文件#mget   下载多个文件#mirror  下载整个目录及其子目录#mirror   -R  上传整个目录及其子目录3.7.2 登录FTP服务器站点如果FTP服务器支持匿名用户登录,直接输入服务器IP地址即可登录。2.7.3 文件和目录的上传上传单个文件使用put命令,用法格式: put <本地目录路径下将要上传的文件>示例:lftp 172.16.21.69:/> put ../work/nfs_restart.sh112 bytes transferred多个文件使用mput命令,用法格式: mput <本地文件1> <本地文件2> … ….示例:lftp 172.16.21.69:/> mput x264-master/x264.c x264-master/libx264.a9111669 bytes transferred in 1 second (8.32M/s)            Total 2 files transferred整个目录使用mirror命令,加上-R参数。用法格式:mirror -R <本地目录路径>示例:lftp 172.16.21.69:/> mirror -R x264-master/New: 372 files, 0 symlinks60426636 bytes transferred in 8 seconds (6.98M/s)3.7.4 文件和目录的下载下载单个文件使用get命令,用法格式:get <服务器上的xx文件>示例:lftp 172.16.21.69:/> get F407-霸天虎原理图.pdf253762 bytes transferred下载多个文件使用mget命令,用法格式:mget <服务器上的xx文件1> <服务器上的xx文件1> …示例:lftp 172.16.21.69:/> mget libx264.a 123.h264 x264.c9392401 bytes transferred in 62 seconds (147.3K/s)              Total 3 files transferred下载目录使用mirror命令,用法格式:mirror <服务器上的xx目录路径>示例:lftp 172.16.21.69:/> mirror X264_X86_Video/3.7.5 输入指定的用户名和密码登录FTP服务器如果访问的FTP服务器不支持匿名登录,就需要输入指定的账号密码登录.方式1: 直接登录格式: lftp 用户名:密码@ftp地址:传送端口(默认21-可以不填)示例: lftp 1126626497@qq.com:123456@192.168.2.16方式2: 使用命令行的login 命令登录[wbyq@wbyq mnt]$ lftp 192.168.2.16lftp 192.168.2.16:~> login 1126626497@qq.com 1234563.8 (关闭匿名登录)windows 下创建FTP服务器3.8.1 查看当前电脑的上的账号也可以创建新的账号专门用于FTP服务器访问。3.8.2 关闭匿名账号使用普通账号登录
  • [专题汇总] 嵌入式Linux开发技术-驱动篇(二)
    Linux 是一个开源操作系统,可以移植到任意硬件平台,目前有很多物联网操作系统都基于Linux进行开发,从广义角度来看,Linux 是物联网生态系统的核心,从最小的物联网设备到边缘网关和云。最近一项由 Eclipse IoT 工作组、AGILE IoT、IEEE 和开放移动联盟赞助的在线调查发现,在物联网开发人员中,大约 72% 的受访者将 Linux 用于他们的物联网设备。其开源操作系统、可扩展性、安全特性和广泛的发行版等因素使 Linux 成为物联网开发的热门选择。物联网是通过约定的协议将原本独立存在的设备相互连接起来,并最终实现智能识别、定位、跟踪、监测、控制和管理的一种网络,无需人与人、或人与设备的互动。通俗来说物联网就是“物物相连的网”,主要应用于智能交通、智能医疗、智能家居、智能物流、智能电力等领域。目前物联网产业正在飞快发展着,从智能电视、智能家居、智能汽车、医疗健康、智能玩具、机器人等延伸到可穿戴设备领域。 物联网将赋能智能硬件向多元的消费场景渗透,从而创造更加便捷、舒适、安全、节能的生活环境。以智能家居为例,我们借助物联网可远程控制家庭里面的每一件智能家居,像电灯,电视,空调等,给我们的生活带来更多的便利。随着5G 技术的快速发展,物联网将应用到更多领域,由此可见物联网产业仍具有较大的发展空间。下面列出本月Linux驱动开发的帖子总汇地址:【1】Linux驱动开发-编写按键驱动 https://bbs.huaweicloud.com/forum/thread-0241111226447836021-1-1.html这篇文章介绍,如何使用杂项设备框架编写一个简单的按键驱动,完成编写、编译、安装、测试等流程,了解一个杂项字符设备驱动的开发流程。【2】Linux驱动开发-编写W25Q64(Flash)驱动 W25Q64是一颗SPI接口的Flash存储芯片,是华邦W25QXX系列里的一个具体型号,这个系列里包含了W25Q16,W25Q32,W25Q64,W5Q128等等。编程代码逻辑都差不多,主要是容量的区别。本篇文章就介绍如何在Linux系统下编写W25Q64芯片的驱动,完成数据存储,W25Q64支持标准SPI总线,当前驱动程序底层的代码写了两种方式,一种是采用内核提供的SPI子系统框架,一种直接采用软件模拟SPI时序的方式驱动。https://bbs.huaweicloud.com/forum/thread-0223111226920817030-1-1.html【3】Linux驱动开发-编写VS1053芯片音频驱动 VS1053是一款硬件编解码的音频芯片,提供SPI接口和IIS接口两种通信协议,这篇文章是介绍在Linux下如果模拟SPI时序来操作VS1053完成录音、播放音频歌曲功能。但是没有注册标准的音频驱动,没有对接音频框架,只是在驱动层完成VS1053的直接控制,本篇的重点主要是介绍如何初始化开发板的GPIO口,使用Linux的延时函数,模拟SPI时序,代码写了两种版本,一种是直接通过ioremap直接映射GPIO口地址,完成配置,一种是直接调用官方内核提供的库函数接口,完成GPIO口初始化,控制。当前采用的开发板是友善之臂的Tiny4412,芯片是三星的EXYNOS4412,这款芯片出来有很长一段时间了,之前用在三星的S系列手机上的,最高主频是1.5GZ,稳定推荐主频是1.4GHZ,内核是三星提供的demon,友善之臂在基础上完成了移植适配,也就是现在拿到的Tiny4412开发板内核,Linux 版本是3.5,不支持设备树。https://bbs.huaweicloud.com/forum/thread-0288111227483839022-1-1.html【4】Linux驱动开发-编写RFID-RC522射频刷卡模块驱动 MFRC522是应用于13.56MHz非接触式通信中高集成度的读写卡芯片,针对“三表”应用推出的一款低电压、低成本、体积小的非接触式读写卡芯片,是智能仪表和便携式手持设备研发的较好选择。便携式手持设备研发的较好选择。MFRC522利用了先进的调制和解调概念,集成了在13.56MHz下所有类型的被动非接触式通信方式和协议。支持14443A兼容应答器信号。数字部分处理ISO14443A帧和错误检测。此外,还支持快速CRYPTO1加密算法,用语验证MIFARE系列产品。MFRC522支持MI FARE系列更高速的非接触式通信,双向数据传输速率高达424kbit/s。作为13.56MHz高集成度读写卡系列芯片族的新成员,MFRC522与MF RC500和MFRC530有不少相似之处,同时也具备许多特点和差异。它与主机间通信采用SPI模式,有利于减少连线,缩小PCB板体积,降低成本。https://bbs.huaweicloud.com/forum/thread-0270111227547670025-1-1.html【5】Linux驱动开发-编写PCF8591(ADC)芯片驱动 PCF8591是一个IIC总线接口的ADC/DAC转换芯片,功能比较强大,这篇文章就介绍在Linux系统里如何编写一个PCF8591的驱动,完成ADC数据采集,DAC数据输出。https://bbs.huaweicloud.com/forum/thread-0223111227661816031-1-1.html【6】Linux驱动开发-编写OLED显示屏驱动 OLED显示屏在是智能手环,智能手表上用的非常的多,功耗低,不刺眼,优点特别多。本篇文章就介绍,在Linux系统里如何使用OLED显示屏,要使用OLED显示屏,大致分为两步: (1) 针对OLED显示屏编写一个驱动 (2) 编写应用层程序进行测试。采用的OLED显示屏是0.96寸SPI接口显示屏,分辨率是128*64,比较便宜,淘宝上非常多。测试开发板采用友善之臂Tiny4412,三星的EXYNOS-4412芯片,4核1.5GHZ,板载8G-EMMC,2G-DDR。https://bbs.huaweicloud.com/forum/thread-0201111227856826018-1-1.html【7】采用华为云IOT平台设计的高速公路多节点温度采集系统(STM32+NBIOT) 当前的场景是,在高速公路上部署温度采集设备,在高速路地表安装温度检测传感器,检测当前路段的路面实际温度。一段高速路上有多个地点需要采集温度数据。 采集温度数据需要上传到云平台进行数据存储,并且通过可视化界面展示温度变化曲线,支持查询最近几天的温度信息。https://bbs.huaweicloud.com/forum/thread-0254111722643458007-1-1.html【8】Linux驱动开发-编写NEC红外线协议解码驱动 光谱中波长自760nm至400um的电磁波称为红外线,它是一种不可见光。目前几乎所有的视频和音频设备都可以通过红外遥控的方式进行遥控,比如电视机、空调、影碟机等,都可以见到红外遥控的影子。这种技术应用广泛,相应的应用器件都十分廉价,因此红外遥控是我们日常设备控制的理想方式。红外线遥控是目前使用最广泛的一种通信和遥控手段。由于红外线遥控装置具有体积小、功耗低、功能强、 成本低等特点,因而,继彩电、录像机之后,在录音机、音响设备、空凋机以及玩具等其它小型电器装置上也纷 纷采用红外线遥控。工业设备中,在高压、辐射、有毒气体、粉尘等环境下,采用红外线遥控不仅完全可靠而且 能有效地隔离电气干扰。NEC协议是众多红外线协议中的一种,以前广泛用在电视机,投影仪设备里,很早之前经常说的万能电视遥控器就是NEC协议的。当前文章就介绍如何在Linux下通过红外线接收模块,编写一个NEC协议的红外线解码驱动,解析遥控器传输过来的各种控制指令,完成对应的动作响应;驱动里用到了外部中断接收数据,通过定时器计算间隔时间完成解码。https://bbs.huaweicloud.com/forum/thread-0234112465202130033-1-1.html【9】Linux驱动开发-编写MMA7660三轴加速度传感器 MMA7660FC 是 ± 1.5 克的三轴数字输出、超低功率、紧凑型电容式微电机的三轴加速度计,是非常低功耗,小型容性 MEMS 的传感器。具有低通滤波器,用于偏移和增益误差补偿, 以及用户可配置的转换成 6 位分辨率,用户可配置输出速率等功能。MMA7660芯片可以通过中断引脚(INT)向外通知传感器数据变化、方向、姿态识别等信息。模拟工作电压范围是 2.4V 至 3.6V,数字工作电压范围是 1.71V 到 3.6V 。常用在手机、掌上电脑、车载导航,便携式电脑的防盗,自动自行车刹车灯、运动检测手环、数码机、自动叫醒闹钟里等等。特别是计步的功能是现在最常见,不管是智能手环、还是手机都带有三轴加速度计,可以记录每天的步数,计算运动量等。现在很多的不倒翁,无人机、相机云台,很多常见的产品里都能看到三轴加速计的身影。通过MMA7660可以做出很多项目: 比如: 老人防跌倒手环、自行车自动刹车灯,智能闹钟,烤火炉跌倒自动断电、运动手环等等。这篇文章就介绍如何在Linux下编写MMA7660三轴加速度芯片的驱动,读取当前芯片的方向姿态,得到X,Y,Z三个轴的数据。MMA7660是IIC接口的,当前驱动就采用标准的IIC子系统编写驱动,使用字符设备框架将得到的数据上传递给应用层。https://bbs.huaweicloud.com/forum/thread-0234112465325735034-1-1.html【10】Linux驱动开发-编写FT5X06触摸屏驱动 这篇文章介绍在Linux下如何编写FT5X06系列芯片驱动,完成触摸屏的驱动开发, FT5X06是一个系列,当前使用的具体型号是FT5206,它是一个电容屏的触摸芯片,内置了8位的单片机(8051内核),完成了坐标换算等很多处理,在通过IIC,SPI方式传递给外部单片机。https://bbs.huaweicloud.com/forum/thread-0234112931665585061-1-1.html【11】Linux驱动开发-编写DS18B20驱动 当前文章介绍如何在Linux系统下编写一个DS18B20温度传感器驱动,测量环境温度,并将DS18B20注册成字符设备,通过文件接口将温度数据传递给应用层。当前使用的开发板是友善之臂的Tiny4412开发板,CPU是三星的Exynos-4412,主频是4核1.5GHZ,当前运行的Linux内核版本是3.5。使用的温度传感器是DS18B20,是一个数字温度传感器,非常经典的一款温度传感器,常年应用在各大高校毕设、实验室、毕设、课设场景。DS1820接线比较简单,只需要一根线就行,加上两根电源线,一共3根线,并且DS18B20支持硬件序列号寻址,支持一个IO口上挂载多个DS18B20。https://bbs.huaweicloud.com/forum/thread-0275112931782443061-1-1.html【12】Linux驱动开发-编写(EEPROM)AT24C02驱动 AT24C02是IIC接口的EEPROM存储芯片,这颗芯片非常经典,百度搜索可以找到非常多的资料,大多都是51、STM32单片机的示例代码,大多采用模拟时序、裸机系统运行。当前文章介绍在Linux系统里如何编写AT24C02的驱动,并且在应用层完成驱动读写测试,将AT24C02的存储空间映射成文件,在应用层,用户可以直接将AT24C02当做一个普通文件的形式进行读写,偏移文件指针;在Linux内核里有一套标准的IIC子系统框架专门读写IIC接口设备,采用平台设备模型框架,编写驱动非常方便。当前开发板采用友善之臂的Tiny4412,CPU是三星的EXYNOS4412,4412是三星的第一款四核处理器,主频是1.5GHZ,稳定频率是1.4GHZ。https://bbs.huaweicloud.com/forum/thread-0275112932059972062-1-1.html【13】Linux驱动开发-安装驱动参数传递 在Linux下进行C语言开发时,经常在命令行传递参数给C程序,常见的Linux命令也是需要传参的,这样用起来就很灵活,根据不同的参数可以执行不同的效果。Linux驱动安装时也支持传递参数,和命令行上运行的命令原理类似。只不过在编写驱动的时候,需要在驱动代码里提前将相关信息声明好才可以使用。这篇文章就介绍如果在命令安装驱动时,传递参数给驱动代码,演示各种类型的参数传输情况。https://bbs.huaweicloud.com/forum/thread-0267112932162172062-1-1.html【14】Linux驱动开发-proc接口介绍 Linux系统上的/proc目录是一种文件系统,即proc文件系统。与其它常见的文件系统不同的是,/proc是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。当前的实验平台是嵌入式Linux开发板,根文件系统挂载成功后,进入命令就能看到proc目录,这个目录里正常情况下已经生成了很多文件。通过cat命令读取这些文件,可以得到很多内核的信息。比如:查看中断有哪些注册了,中断从上电到现在响应了多少次,杂项设备注册了哪些,帧缓冲节点有哪些,RTC时间查看,等等。https://bbs.huaweicloud.com/forum/thread-0236112948111487053-1-1.html
  • [技术干货] Linux驱动开发-proc接口介绍
    1. 前言Linux系统上的/proc目录是一种文件系统,即proc文件系统。与其它常见的文件系统不同的是,/proc是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。当前的实验平台是嵌入式Linux开发板,根文件系统挂载成功后,进入命令就能看到proc目录,这个目录里正常情况下已经生成了很多文件。通过cat命令读取这些文件,可以得到很多内核的信息。比如:查看中断有哪些注册了,中断从上电到现在响应了多少次,杂项设备注册了哪些,帧缓冲节点有哪些,RTC时间查看,等等。下面是proc目录下文件的功能的详细介绍(资源来源与网络):2.1、/proc/apm 高级电源管理(APM)版本信息及电池相关状态信息,通常由apm命令使用;2.2、/proc/buddyinfo 用于诊断内存碎片问题的相关信息文件;2.3、/proc/cmdline 在启动时传递至内核的相关参数信息,这些信息通常由lilo或grub等启动管理工具进行传递;2.4、/proc/cpuinfo 处理器的相关信息的文件;2.5、/proc/crypto 系统上已安装的内核使用的密码算法及每个算法的详细信息列表;2.6、/proc/devices 系统已经加载的所有块设备和字符设备的信息,包含主设备号和设备组(与主设备号对应的设备类型)名;2.7、/proc/diskstats 每块磁盘设备的磁盘I/O统计信息列表;(内核2.5.69以后的版本支持此功能)2.8、/proc/dma 每个正在使用且注册的ISA DMA通道的信息列表;2.9、/proc/execdomains 内核当前支持的执行域(每种操作系特“个性”)信息列表;2.10、/proc/fb 帧缓冲设备列表文件,包含帧缓冲设备的设备号和相关驱动信息;2.11、/proc/filesystems 当前被内核支持的文件系统类型列表文件,被标示为nodev的文件系统表示不需要块设备的支持;通常mount一个设备时,如果没有指定文件系统类型将通过此文件来决定其所需文件系统的类型;2.12、/proc/interrupts X86或X86_64体系架构系统上每个IRQ相关的中断号列表;多路处理器平台上每个CPU对于每个I/O设备均有自己的中断号;2.13、/proc/iomem 每个物理设备上的记忆体(RAM或者ROM)在系统内存中的映射信息;2.14、/proc/ioports 当前正在使用且已经注册过的与物理设备进行通讯的输入-输出端口范围信息列表;如下面所示,第一列表示注册的I/O端口范围,其后表示相关的设备;2.15、/proc/kallsyms 模块管理工具用来动态链接或绑定可装载模块的符号定义,由内核输出;(内核2.5.71以后的版本支持此功能);通常这个文件中的信息量相当大;2.16、/proc/kcore 系统使用的物理内存,以ELF核心文件(core file)格式存储,其文件大小为已使用的物理内存(RAM)加上4KB;这个文件用来检查内核数据结构的当前状态,因此,通常由GBD通常调试工具使用,但不能使用文件查看命令打开此文件;2.17、/proc/kmsg 此文件用来保存由内核输出的信息,通常由/sbin/klogd或/bin/dmsg等程序使用,不要试图使用查看命令打开此文件;2.18、/proc/loadavg 保存关于CPU和磁盘I/O的负载平均值,其前三列分别表示每1秒钟、每5秒钟及每15秒的负载平均值,类似于uptime命令输出的相关信息;第四列是由斜线隔开的两个数值,前者表示当前正由内核调度的实体(进程和线程)的数目,后者表示系统当前存活的内核调度实体的数目;第五列表示此文件被查看前最近一个由内核创建的进程的PID;2.19、/proc/locks 保存当前由内核锁定的文件的相关信息,包含内核内部的调试数据;每个锁定占据一行,且具有一个惟一的编号;如下输出信息中每行的第二列表示当前锁定使用的锁定类别,POSIX表示目前较新类型的文件锁,由lockf系统调用产生,FLOCK是传统的UNIX文件锁,由flock系统调用产生;第三列也通常由两种类型,ADVISORY表示不允许其他用户锁定此文件,但允许读取,MANDATORY表示此文件锁定期间不允许其他用户任何形式的访问;2.20、/proc/mdstat 保存RAID相关的多块磁盘的当前状态信息,在没有使用RAID机器上,其显示为如下状态:2.21、/proc/meminfo 系统中关于当前内存的利用状况等的信息,常由free命令使用;可以使用文件查看命令直接读取此文件,其内容显示为两列,前者为统计属性,后者为对应的值;2.22、/proc/mounts 在内核2.4.29版本以前,此文件的内容为系统当前挂载的所有文件系统,在2.4.19以后的内核中引进了每个进程使用独立挂载名称空间的方式,此文件则随之变成了指向/proc/self/mounts(每个进程自身挂载名称空间中的所有挂载点列表)文件的符号链接;/proc/self是一个独特的目录,后文中会对此目录进行介绍;2.23、/proc/modules 当前装入内核的所有模块名称列表,可以由lsmod命令使用,也可以直接查看;如下所示,其中第一列表示模块名,第二列表示此模块占用内存空间大小,第三列表示此模块有多少实例被装入,第四列表示此模块依赖于其它哪些模块,第五列表示此模块的装载状态(Live:已经装入;Loading:正在装入;Unloading:正在卸载),第六列表示此模块在内核内存(kernel memory)中的偏移量;2.24、/proc/partitions 块设备每个分区的主设备号(major)和次设备号(minor)等信息,同时包括每个分区所包含的块(block)数目(如下面输出中第三列所示);2.25、/proc/pci 内核初始化时发现的所有PCI设备及其配置信息列表,其配置信息多为某PCI设备相关IRQ信息,可读性不高,可以用“/sbin/lspci –vb”命令获得较易理解的相关信息;在2.6内核以后,此文件已为/proc/bus/pci目录及其下的文件代替;2.26、/proc/slabinfo 在内核中频繁使用的对象(如inode、dentry等)都有自己的cache,即slab pool,而/proc/slabinfo文件列出了这些对象相关slap的信息;详情可以参见内核文档中slapinfo的手册页;2.27、/proc/stat 实时追踪自系统上次启动以来的多种统计信息;如下所示,其中, “cpu”行后的八个值分别表示以1/100(jiffies)秒为单位的统计值(包括系统运行于用户模式、低优先级用户模式,运系统模式、空闲模式、I/O等待模式的时间等); “intr”行给出中断的信息,第一个为自系统启动以来,发生的所有的中断的次数;然后每个数对应一个特定的中断自系统启动以来所发生的次数; “ctxt”给出了自系统启动以来CPU发生的上下文交换的次数。 “btime”给出了从系统启动到现在为止的时间,单位为秒; “processes (total_forks) 自系统启动以来所创建的任务的个数目; “procs_running”:当前运行队列的任务的数目; “procs_blocked”:当前被阻塞的任务的数目;2.28、/proc/swaps 当前系统上的交换分区及其空间利用信息,如果有多个交换分区的话,则会每个交换分区的信息分别存储于/proc/swap目录中的单独文件中,而其优先级数字越低,被使用到的可能性越大;下面是作者系统中只有一个交换分区时的输出信息;2.29、/proc/uptime 系统上次启动以来的运行时间,如下所示,其第一个数字表示系统运行时间,第二个数字表示系统空闲时间,单位是秒;2.30、/proc/version 当前系统运行的内核版本号,在作者的RHEL5.3上还会显示系统安装的gcc版本,如下所示;2.31、/proc/vmstat 当前系统虚拟内存的多种统计数据,信息量可能会比较大,这因系统而有所不同,可读性较好;下面为作者机器上输出信息的一个片段;(2.6以后的内核支持此文件)2.32、/proc/zoneinfo 内存区域(zone)的详细信息列表,信息量较大2. 获取CPU使用率下面这份代码是利用/proc/stat文件获取当前CPU的占用率详细信息,通过C语言代码读取数据后,进行分析,处理。#include <stdio.h> #include <stdlib.h> #include <unistd.h> ​ typedef struct cpu_occupy_          //定义一个cpu occupy的结构体 {    char name[20];                  //定义一个char类型的数组名name有20个元素    unsigned int user;              //定义一个无符号的int类型的user    unsigned int nice;              //定义一个无符号的int类型的nice    unsigned int system;            //定义一个无符号的int类型的system    unsigned int idle;              //定义一个无符号的int类型的idle    unsigned int iowait;    unsigned int irq;    unsigned int softirq; }cpu_occupy_t; ​ double cal_cpuoccupy (cpu_occupy_t *o, cpu_occupy_t *n) {    double od, nd;    double id, sd;    double cpu_use ; ​    od = (double) (o->user + o->nice + o->system +o->idle+o->softirq+o->iowait+o->irq);//第一次(用户+优先级+系统+空闲)的时间再赋给od    nd = (double) (n->user + n->nice + n->system +n->idle+n->softirq+n->iowait+n->irq);//第二次(用户+优先级+系统+空闲)的时间再赋给od ​    id = (double) (n->idle);    //用户第一次和第二次的时间之差再赋给id    sd = (double) (o->idle) ;    //系统第一次和第二次的时间之差再赋给sd    if((nd-od) != 0)        cpu_use =100.0 - ((id-sd))/(nd-od)*100.00; //((用户+系统)乖100)除(第一次和第二次的时间差)再赋给g_cpu_used    else        cpu_use = 0;    return cpu_use; } ​ void get_cpuoccupy (cpu_occupy_t *cpust) {    FILE *fd;    int n;    char buff[256];    cpu_occupy_t *cpu_occupy;    cpu_occupy=cpust; ​    fd = fopen ("/proc/stat", "r");    if(fd == NULL)   {            perror("fopen:");            exit (0);   }    fgets (buff, sizeof(buff), fd); ​    sscanf (buff, "%s %u %u %u %u %u %u %u", cpu_occupy->name, &cpu_occupy->user, &cpu_occupy->nice,&cpu_occupy->system, &cpu_occupy->idle ,&cpu_occupy->iowait,&cpu_occupy->irq,&cpu_occupy->softirq); ​    fclose(fd); } ​ double get_sysCpuUsage() {    cpu_occupy_t cpu_stat1;    cpu_occupy_t cpu_stat2;    double cpu;    get_cpuoccupy((cpu_occupy_t *)&cpu_stat1);    sleep(1);    //第二次获取cpu使用情况    get_cpuoccupy((cpu_occupy_t *)&cpu_stat2); ​    //计算cpu使用率    cpu = cal_cpuoccupy ((cpu_occupy_t *)&cpu_stat1, (cpu_occupy_t *)&cpu_stat2); ​    return cpu; } ​ int main(int argc,char **argv) {    while(1)   {        printf("CPU占用率:õ36ecc0e-ec3a-4858-a0b4-b5817b482b88n",get_sysCpuUsage());   }    return 0; }3. proc驱动相关接口Proc文件接口,主要用于驱动代码调试,获取内核信息,可以直接使用cat命令访问proc目录下的对应文件接口即可。需要使用的头文件:#include <linux/proc_fs.h> #include <linux/fs.h>下面介绍内核里proc接口实现的相关函数接口:1. 在proc目录下创建子目录函数 static inline struct proc_dir_entry *proc_mkdir(const char *name,struct proc_dir_entry *parent) 示例: //注意只能创建单层目录 //在proc目录下创建aaa文件夹 proc_mkdir("aaa",NULL); ​ 2. 在proc目录下创建文件 static inline struct proc_dir_entry *proc_create(const char *name,    //文件名称 umode_t mode,      //模式,默认为0 struct proc_dir_entry *parent,   //父目录结构 const struct file_operations *proc_fops)  //文件集合 示例: //在proc目录下创建一个文件 proc_create("aaa/tiny4412_proc_test", 0, NULL, &fops_proc); ​ 3. 删除proc目录下之前创建的文件或者目录 void remove_proc_entry(const char *name,   //文件的路径 struct proc_dir_entry *parent  //父目录结构 ) 示例: remove_proc_entry("aaa/tiny4412_proc_test", NULL); 注意: 如果是删除目录,需要先把目录下的文件删除掉,每次删除必须保证目录是空的。4. 编写proc接口测试驱动4.1 案例1下面驱动代码注册之后,会在proc目录下创建一个tiny4412_proc文件,通过cat读取这个文件,可以打印驱动代码里设置好的信息。驱动卸载时会删除这个tiny4412_proc文件。#include <linux/kernel.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/delay.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/proc_fs.h> ​ static int tiny4412_open(struct inode *inode, struct file *file) {      printk("tiny4412_open ok\n");    return 0; } ​ static ssize_t tiny4412_read(struct file *file, char __user *buf, size_t cnt, loff_t *loff) {    copy_to_user(buf,"123456",6);    printk("tiny4412_read调用成功.\n");    return 0; } ​ static int tiny4412_release(struct inode *inode, struct file *file) {    return 0; } ​ static struct file_operations tiny4412_fops= {   .open=tiny4412_open,   .read=tiny4412_read,   .release=tiny4412_release, }; ​ static int __init tiny4412_init(void) {    proc_mkdir("wbyq",0);    /*创建内核接口: proc 存放内核信息*/    proc_create("wbyq/tiny4412_proc",0, NULL, &tiny4412_fops);        printk("驱动安装成功.\n");    return 0; } ​ static void __exit tiny4412_exit(void) {    remove_proc_entry("wbyq/tiny4412_proc", NULL);    remove_proc_entry("wbyq", NULL);    printk("驱动卸载成功.\n"); } ​ /*驱动的入口:insmod xxx.ko*/ module_init(tiny4412_init); /*驱动的出口: rmmod xxx.ko*/ module_exit(tiny4412_exit); /*模块的许可证*/ MODULE_LICENSE("GPL"); /*模块的作者*/ MODULE_AUTHOR("wbyq");4.2 案例2下面这份代码是在字符设备框架代码里增加了proc接口,驱动安装之后,会在proc目录下创建tiny4412_proc文件,通过cat命令读取tiny4412_proc文件,可以打印出当前主设备号下所有的子设备信息。#include <linux/kernel.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/io.h> #include <asm/uaccess.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/device.h> #include <linux/mutex.h> #include <linux/list.h> #include <linux/proc_fs.h> static struct class *tiny4412_beep_class; static unsigned int major=0; //主设备号 ​ static LIST_HEAD(tiny4412_beep_list);   //链表头 static DEFINE_MUTEX(tiny4412_beep_mtx); //互斥锁 ​ #define DYNAMIC_MINORS 64 /* like dynamic majors */ static DECLARE_BITMAP(beep_minors, DYNAMIC_MINORS); ​ struct tiny4412_beep_device {    int minor;   /*次设备号*/    const char *name; /*设备节点的名称*/    const struct file_operations *fops; /*文件操作集合*/    struct list_head list; //链表 }; ​ int tiny4412_beep_register(struct tiny4412_beep_device *beep_dev) {    struct tiny4412_beep_device *c;    dev_t dev;        INIT_LIST_HEAD(&beep_dev->list);        mutex_lock(&tiny4412_beep_mtx); ​    //查找传入的次设备号是否冲突    list_for_each_entry(c, &tiny4412_beep_list, list)   {        if(c->minor == beep_dev->minor)       {            mutex_unlock(&tiny4412_beep_mtx);            return -EBUSY;       }   }        //自动分配    if(beep_dev->minor == MISC_DYNAMIC_MINOR)   {        int i = find_first_zero_bit(beep_minors,DYNAMIC_MINORS);        if (i >= DYNAMIC_MINORS)       {            mutex_unlock(&tiny4412_beep_mtx);            return -EBUSY;       }        beep_dev->minor = DYNAMIC_MINORS - i - 1;        set_bit(i,beep_minors);   } ​    //合成设备号    dev = MKDEV(major, beep_dev->minor); ​    //创建设备节点    device_create(tiny4412_beep_class,NULL,dev,NULL,"%s", beep_dev->name);        list_add(&beep_dev->list,&tiny4412_beep_list);        //解锁    mutex_unlock(&tiny4412_beep_mtx);    return 0; } ​ int tiny4412_beep_deregister(struct tiny4412_beep_device *beep_dev) {    int i = DYNAMIC_MINORS - beep_dev->minor - 1; ​    mutex_lock(&tiny4412_beep_mtx);    list_del(&beep_dev->list); ​    //将dev目录下的文件删除掉    device_destroy(tiny4412_beep_class, MKDEV(major, beep_dev->minor));    if (i < DYNAMIC_MINORS && i >= 0)        clear_bit(i, beep_minors);    mutex_unlock(&tiny4412_beep_mtx);    return 0; } ​ EXPORT_SYMBOL_GPL(tiny4412_beep_register); EXPORT_SYMBOL_GPL(tiny4412_beep_deregister); ​ //底层open函数 static int tiny4412_beep_open(struct inode * inode, struct file * file) {    //得到次设备号    int minor = iminor(inode);        struct tiny4412_beep_device *c;    struct file_operations *new_fops,*old_fops;    mutex_lock(&tiny4412_beep_mtx);    //遍历链表--找到链表里相同的次设备号    list_for_each_entry(c,&tiny4412_beep_list, list)   {        if (c->minor == minor)       {            new_fops = fops_get(c->fops);    //得到47次设备号对应的结构体地址            break;       }   }    file->f_op = new_fops; //改变指向--文件操作集合的指向    if(file->f_op->open)   {        file->f_op->open(inode,file);   }    fops_put(old_fops);    mutex_unlock(&tiny4412_beep_mtx);    return 0; } ​ static const struct file_operations tiny4412_beep_fops = {   .owner      = THIS_MODULE,   .open       = tiny4412_beep_open, }; ​ static ssize_t tiny4412_read(struct file *file, char __user *buf, size_t cnt, loff_t *loff) {    struct tiny4412_beep_device *c;    //遍历链表--找到链表里相同的次设备号    list_for_each_entry(c,&tiny4412_beep_list, list)   {        printk("%d %s\n",c->minor,c->name);   }    return 0; } ​ static struct file_operations tiny4412_fops= {   .read=tiny4412_read, }; ​ static int __init tiny4412_beep_class_init(void) {    /*1. 创建设备类*/    tiny4412_beep_class=class_create(THIS_MODULE,"tiny4412_beep");    /*2. 注册字符设备*/    major=register_chrdev(0,"tiny4412_beep",&tiny4412_beep_fops);        proc_mkdir("wbyq",0);    /*创建内核接口: proc 存放内核信息*/    proc_create("wbyq/tiny4412_proc",0, NULL, &tiny4412_fops);        return 0; } ​ static void __exit tiny4412_beep_class_cleanup(void) {    remove_proc_entry("wbyq/tiny4412_proc", NULL);    remove_proc_entry("wbyq", NULL);        //注销设备类    class_destroy(tiny4412_beep_class);    //注销字符设备    unregister_chrdev(major,"tiny4412_beep"); } ​ module_init(tiny4412_beep_class_init); module_exit(tiny4412_beep_class_cleanup); ​ MODULE_LICENSE("GPL"); MODULE_AUTHOR("wbyq");
  • [技术干货] Linux驱动开发-安装驱动参数传递
    一、简介在Linux下进行C语言开发时,经常在命令行传递参数给C程序,常见的Linux命令也是需要传参的,这样用起来就很灵活,根据不同的参数可以执行不同的效果。Linux驱动安装时也支持传递参数,和命令行上运行的命令原理类似。只不过在编写驱动的时候,需要在驱动代码里提前将相关信息声明好才可以使用。这篇文章就介绍如果在命令安装驱动时,传递参数给驱动代码,演示各种类型的参数传输情况。在驱动代码里声明传入参数的类型、权限,接收的变量名称。module_param(变量的名称,类型,权限)二、在驱动代码里声明传递参数的格式/*传递整型类型数据*/ int int_data = 0; module_param(int_data, int ,0664); MODULE_PARM_DESC(int_data, "是一个整型的参数."); ​ /*传递指针类型数据*/ char *p_data = NULL; module_param(p_data, charp, 0664); MODULE_PARM_DESC(p_data, "是一个指针类型数据."); ​ /* 传递数组类型数据 module_param_array(数组名, 元素类型, 元素个数(取地址), 权限); */ int array_data[3] = {}; int num = 3; module_param_array(array_data, int, &num, 0664); MODULE_PARM_DESC(array_data, "是一个数组类型数据."); ​ /* 传递字符串: module_param_string (传递参数时的字符串名称, 字符串名称, 字符串大小, 权限); */ char str_data[12] = {}; module_param_string(str_data, str_data, sizeof(str_data), 0664); MODULE_PARM_DESC(str_data, "是一个字符串类型数据.");三、完整代码示例#include <linux/kernel.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> ​ /*传递整型类型数据*/ int int_data = 0; module_param(int_data, int ,0664); MODULE_PARM_DESC(int_data, "是一个整型的参数."); ​ /*传递指针类型数据*/ char *p_data = NULL; module_param(p_data, charp, 0664); MODULE_PARM_DESC(p_data, "是一个指针类型数据."); ​ /* 传递数组类型数据 module_param_array(数组名, 元素类型, 元素个数(取地址), 权限); */ int array_data[3] = {}; int num = 3; module_param_array(array_data, int, &num, 0664); MODULE_PARM_DESC(array_data, "是一个数组类型数据."); ​ /* 传递字符串: module_param_string (传递参数时的字符串名称, 字符串名称, 字符串大小, 权限); */ char str_data[12] = {}; module_param_string(str_data, str_data, sizeof(str_data), 0664); MODULE_PARM_DESC(str_data, "是一个字符串类型数据."); ​ static int __init tiny4412_param_dev_init(void) { printk("安装驱动成功.\n"); printk("int_data=×1250823-e909-4c77-9215-b062373e2141n",int_data); printk("p_data=%s\n",p_data); printk("array_data=×1250823-e909-4c77-9215-b062373e2141n",array_data[0]); printk("str_data=%s\n",str_data); return 0; } ​ static void __exit tiny4412_param_dev_exit(void) { printk("卸载驱动成功.\n"); } ​ module_init(tiny4412_param_dev_init); module_exit(tiny4412_param_dev_exit); ​ MODULE_LICENSE("GPL"); MODULE_AUTHOR("wbyq");四、查看驱动提示信息[root@wbyq code]#modinfo led_drv.ko filename:       led_drv.ko license:       GPL author:         wbyq depends:         vermagic:       3.5.0-FriendlyARM SMP preempt mod_unload ARMv7 p2v8 parm:           str_data:是一个字符串类型数据. parm:           array_data:是一个数组类型数据. parm:           p_data:是一个指针类型数据. parm:           int_data:是一个整型的参数.五、安装驱动时传递参数[root@wbyq code]#insmod led_drv.ko str_data="123" int_data=666 p_data="789" array_data=6,7,8 [ 2692.220000] 安装驱动成功. [ 2692.220000] int_data=666 [ 2692.220000] p_data=789 [ 2692.220000] array_data=6 [ 2692.220000] str_data=123六、驱动安装成功在sys目录下查看传递的参数[root@wbyq code]#cd /sys/module/led_drv/parameters/ [root@wbyq parameters]#ls array_data int_data   p_data     str_data [root@wbyq parameters]#cat array_data 6,7,8 [root@wbyq parameters]#cat int_data 666 [root@wbyq parameters]#cat p_data 789 [root@wbyq parameters]#cat str_data 123 [root@wbyq parameters]#七、权限定义用户 #define S_IRWXU 00700 #define S_IRUSR 00400 #define S_IWUSR 00200 #define S_IXUSR 00100 ​ 用户组 #define S_IRWXG 00070 #define S_IRGRP 00040 #define S_IWGRP 00020 #define S_IXGRP 00010 ​ 其他用户 #define S_IRWXO 00007 #define S_IROTH 00004 #define S_IWOTH 00002 #define S_IXOTH 00001示例代码:/*传递整型类型数据*/ int int_data = 0; module_param(int_data, int ,S_IRUSR|S_IWUSR|S_IXUSR); MODULE_PARM_DESC(int_data, "是一个整型的参数."); ​ /*传递指针类型数据*/ char *p_data = NULL; module_param(p_data, charp, S_IRUSR|S_IWUSR|S_IXUSR); MODULE_PARM_DESC(p_data, "是一个指针类型数据.");
  • [技术干货] Linux驱动开发-编写(EEPROM)AT24C02驱动
    1. 前言AT24C02是IIC接口的EEPROM存储芯片,这颗芯片非常经典,百度搜索可以找到非常多的资料,大多都是51、STM32单片机的示例代码,大多采用模拟时序、裸机系统运行。当前文章介绍在Linux系统里如何编写AT24C02的驱动,并且在应用层完成驱动读写测试,将AT24C02的存储空间映射成文件,在应用层,用户可以直接将AT24C02当做一个普通文件的形式进行读写,偏移文件指针;在Linux内核里有一套标准的IIC子系统框架专门读写IIC接口设备,采用平台设备模型框架,编写驱动非常方便。当前开发板采用友善之臂的Tiny4412,CPU是三星的EXYNOS4412,4412是三星的第一款四核处理器,主频是1.5GHZ,稳定频率是1.4GHZ。2. 硬件原理图当前的开发板上自带了一颗EEPROM存储芯片(具体型号是24AA025E48,代码与AT24C02一样的),原理图如下:自带的内核里没有内置EEPROM的驱动:存储芯片的数据手册介绍:设备地址:写字节、页写时序:读数据时序:3. 示例代码3.1 EEPROM驱动端代码#include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <asm/uaccess.h> #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/slab.h> #include <asm/uaccess.h> #include <linux/miscdevice.h> static struct work_struct work; static struct i2c_client *eeprom_client; ​ #define MAX_SIZE 255  //EEPROM大小 #define EEPROM_PAGE 16 //页字节大小 ​ static u8 eeprom_buff[255]; static int tiny4412_open(struct inode *inode, struct file *file) { printk("tiny4412_open-->ok\n"); return 0; } ​ static ssize_t tiny4412_read(struct file *file, char __user *buf, size_t size, loff_t *seek) { unsigned long err; //判断位置是否超出范围 if(*seek+size>MAX_SIZE) { size=MAX_SIZE-*seek; } //读取数据 i2c_smbus_read_i2c_block_data(eeprom_client,*seek,size,eeprom_buff); err=copy_to_user(buf,eeprom_buff,size); if(err!=0)return -1; *seek+=size; return size; } ​ static ssize_t tiny4412_write(struct file *file, const char __user *buf, size_t size, loff_t *seek) { size_t write_ok_cnt=0; unsigned long err; err=copy_from_user(eeprom_buff,buf,size); if(err!=0)return -1; //判断位置是否超出范围 if(*seek+size>MAX_SIZE) { size=MAX_SIZE-*seek; } int write_byte=0; u8 *write_p=eeprom_buff; while(1) { if(size>EEPROM_PAGE) { write_byte=EEPROM_PAGE; size-=EEPROM_PAGE; } else { write_byte=size; } //写数据 i2c_smbus_write_i2c_block_data(eeprom_client,*seek,write_byte,write_p); *seek+=write_byte; write_p+=write_byte; write_ok_cnt+=write_byte;  //记录写成功的字节数 //等待写完成 msleep(10); if(write_byte==size)break; //写完毕 } return write_ok_cnt; } ​ /* filp:待操作的设备文件file结构体指针 off:待操作的定位偏移值(可正可负) whence:待操作的定位起始位置 返回:返回移位后的新文件读、写位置,并且新位置总为正值 定位起始位置  SEEK_SET:0,表示文件开头  SEEK_CUR:1,表示当前位置  SEEK_END:2,表示文件尾 */ static loff_t tiny4412_llseek(struct file *filp, loff_t offset, int whence) { loff_t newpos = 0; switch(whence) { case SEEK_SET: newpos = offset; break; case SEEK_CUR: newpos = filp->f_pos + offset; break; case SEEK_END: if(MAX_SIZE+offset>=MAX_SIZE) { newpos=MAX_SIZE; } else { newpos = MAX_SIZE + offset; } break; default: return -EINVAL;//无效的参数 } filp->f_pos = newpos; return newpos; } ​ static int tiny4412_release(struct inode *inode, struct file *file) { printk("tiny4412_release-->ok\n"); return 0; } ​ static struct file_operations fops= { .open=tiny4412_open, .read=tiny4412_read, .write=tiny4412_write, .release=tiny4412_release, .llseek=tiny4412_llseek }; ​ /* Linux内核管理驱动---设备号 设备号是一个unsigned int 的变量--32位。 设备号=主设备号+次设备号 */ static struct miscdevice misc= { .minor = MISC_DYNAMIC_MINOR,  /*次设备号填255表示自动分配     主设备号固定为10*/ .name = "tiny4412_eeprom",  /*/dev目录下文件名称*/ .fops = &fops, /*文件操作接口*/ }; ​ ​ static int tiny4412_probe(struct i2c_client *client, const struct i2c_device_id *device_id) { printk("probe调用成功:%#X\n",client->addr); eeprom_client=client; /*1. 杂项设备的注册函数*/ misc_register(&misc); return 0; } ​ static int tiny4412_remove(struct i2c_client *client) { /*2. 杂项设备的注销函数*/ misc_deregister(&misc); printk("remove调用成功.\n"); return 0; } ​ static struct i2c_device_id id_table[]= { {"tiny4412_eeprom",0}, {} }; ​ static struct i2c_driver drv= { .probe=tiny4412_probe, .remove=tiny4412_remove, .driver= { .name="eeprom_iic" }, .id_table=id_table }; ​ static int __init tiny4412_drv_init(void) { /*注册IIC驱动端*/ i2c_add_driver(&drv);    printk("IIC驱动端: 驱动安装成功\n");    return 0; } ​ static void __exit tiny4412_drv_cleanup(void) { /*注销IIC驱动端*/ i2c_del_driver(&drv);    printk("IIC驱动端: 驱动卸载成功\n"); } ​ module_init(tiny4412_drv_init);    /*驱动入口--安装驱动的时候执行*/ module_exit(tiny4412_drv_cleanup); /*驱动出口--卸载驱动的时候执行*/ ​ MODULE_LICENSE("GPL");  /*设置模块的许可证--GPL*/3.2 EEPROM设备端代码#include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> ​ static struct i2c_client *i2c_dev=NULL; static struct i2c_adapter *adap=NULL; static struct i2c_board_info info= { .type="tiny4412_eeprom", .addr=0x50, /*设备地址*/ }; ​ static int __init tiny4412_drv_init(void) { /*根据总线编号获取是适配器*/ adap=i2c_get_adapter(0); /*注册IIC设备端*/ i2c_dev=i2c_new_device(adap,&info);    printk("IIC设备端: 驱动安装成功\n");    return 0; } ​ static void __exit tiny4412_drv_cleanup(void) { /*注销IIC设备*/ i2c_unregister_device(i2c_dev); i2c_put_adapter(adap);    printk("IIC设备端: 驱动卸载成功\n"); } ​ module_init(tiny4412_drv_init);    /*驱动入口--安装驱动的时候执行*/ module_exit(tiny4412_drv_cleanup); /*驱动出口--卸载驱动的时候执行*/ ​ MODULE_LICENSE("GPL");  /*设置模块的许可证--GPL*/3.3 应用层测试代码#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> ​ #define EEPROM_DEV "/dev/tiny4412_eeprom" ​ int main(int argc,char **argv) {    /*1. 打开设备文件*/    int fd=open(EEPROM_DEV,O_RDWR);    if(fd<0)   {        printf("%s 设备驱动打开失败.\n",EEPROM_DEV);        return 0; }    /*3.读写数据*/ unsigned char buff[255]; int cnt; int i; for(i=0;i<255;i++)buff[i]=i; cnt=write(fd,buff,255);    printf("write成功:%d Byte\n",cnt); //偏移文件指针 lseek(fd,SEEK_SET,0); unsigned char buff_r[255]; cnt=read(fd,buff_r,255); printf("read成功:%d Byte\n",cnt); for(i=0;i<cnt;i++) { printf("%d ",buff_r[i]); } printf("\n");    return 0; }
  • [技术干货] Linux驱动开发-编写DS18B20驱动
    1. 前言当前文章介绍如何在Linux系统下编写一个DS18B20温度传感器驱动,测量环境温度,并将DS18B20注册成字符设备,通过文件接口将温度数据传递给应用层。当前使用的开发板是友善之臂的Tiny4412开发板,CPU是三星的Exynos-4412,主频是4核1.5GHZ,当前运行的Linux内核版本是3.5。使用的温度传感器是DS18B20,是一个数字温度传感器,非常经典的一款温度传感器,常年应用在各大高校毕设、实验室、毕设、课设场景。DS1820接线比较简单,只需要一根线就行,加上两根电源线,一共3根线,并且DS18B20支持硬件序列号寻址,支持一个IO口上挂载多个DS18B20。2. DS18B20介绍DS18B20特性:(1)全数字温度转换及输出。 (2)先进的单总线数据通信。 (3)最高 12 位分辨率,精度可达土 0.5 摄氏度。 (4)12 位分辨率时的最大工作周期为 750 毫秒。 (5)可选择寄生工作方式。 (6)检测温度范围为–55° C ~+125° C (–67° F ~+257° F) (7)内置 EEPROM,限温报警功能。 (8)64 位光刻 ROM,内置产品序列号,方便多机挂接。 (9)多样封装形式,适应不同硬件系统。DS18B20引脚功能GND 电压地 DQ 单数据总线 VDD 电源电压 NC 空引脚DS18B20读取温度的步骤:发送复位信号--> 检测回应信号---> 发送0xCC-->发送0x44-> 发送复位信号—> 检测回应信号—> 写0xcc---> 写0xbe---> 循环8次读取温度低字节---> 循环8次读取温度高字节----> 打印温度信息DS18B20温度转换示例:u16 temp; u8 TL,TH; u16 intT,decT;              //温度值的整数和小数部分 TL=DS18B20_Read_Byte();       //读取温度低8位LSB   TH=DS18B20_Read_Byte();       //读取温度高8位MSB   temp=((u16)TH<<8)|TL;          //将读出的温度高低位组合成16位的值 intT = temp >> 4;                //分离出温度值整数部分 decT = temp & 0xF;              //分离出温度值小数部分 printf("A: %d.×3de37b2-7d54-45f4-9548-db7d79362dcbr\n",(int)intT,(int)decT); //打印实际温度值3. 硬件接线图Tiny4412开发板扩展GPIO口:4. 示例代码#include <linux/module.h> #include <linux/kernel.h> #include <linux/miscdevice.h>   /*杂项字符设备头文件*/ #include <linux/fs.h>           /*文件操作集合*/ #include <linux/delay.h>       /*延时函数*/ ​ #include <linux/uaccess.h> #include <asm/io.h> ​ ​ /*DS18B20 GPIO接口: GPB_4*/ ​ /*定义指针,用于接收虚拟地址*/ volatile unsigned int *DS18B20_GPBCON; volatile unsigned int *DS18B20_GPBDAT; ​ #define DS18B20_INPUT() {*DS18B20_GPBCON &= ~(0xf << 4 * 4);} #define DS18B20_OUTPUT() {*DS18B20_GPBCON &= ~(0xf << 4 * 4);*DS18B20_GPBCON |= (0x1   << 4 * 4);} ​ /* 函数功能:等待DS18B20的回应 返回1:未检测到DS18B20的存在 返回0:存在 */ unsigned char DS18B20_Check(void)   {   unsigned char retry=0; DS18B20_INPUT() ///SET PG11 INPUT    while((*DS18B20_GPBDAT & (1 << 4))&&retry<200) { retry++; udelay(1); }; if(retry>=200)return 1; else retry=0;    while(!(*DS18B20_GPBDAT & (1 << 4))&&retry<240) { retry++; udelay(1); }; if(retry>=240)return 1;     return 0; } ​ ​ /* 从DS18B20读取一个位 返回值:1/0 */ unsigned char DS18B20_Read_Bit(void) // read one bit {    unsigned char data; DS18B20_OUTPUT();    *DS18B20_GPBDAT &= ~(1 << 4);//输出0 udelay(2);    *DS18B20_GPBDAT |= (1 << 4);//输出1 DS18B20_INPUT() udelay(12); if((*DS18B20_GPBDAT & (1 << 4)))data=1;    else data=0;    udelay(50);              return data; } ​ ​ ​ /* 从DS18B20读取一个字节 返回值:读到的数据 */ unsigned char DS18B20_Read_Byte(void)    // read one byte {            unsigned char i,j,dat;    dat=0; for(i=1;i<=8;i++) {        j=DS18B20_Read_Bit(); dat=dat>>1; if(j)        //主机对总线采样的数 判断-------读数据-1就是1,否则就是0 dat|=0x80;   //先收低位数据--一步一步向低位移动>>   }        return dat; } ​ ​ /* 写一个字节到DS18B20 dat:要写入的字节 */ void DS18B20_Write_Byte(unsigned char dat)     {                unsigned char j;    unsigned char testb; DS18B20_OUTPUT();    for(j=1;j<=8;j++) {        testb=dat&0x01;        dat=dat>>1;        if(testb)       {            *DS18B20_GPBDAT &= ~(1 << 4);//输出0// Write 1            udelay(2);                                        *DS18B20_GPBDAT |= (1 << 4);//输出1            udelay(60);                   }        else       {            *DS18B20_GPBDAT &= ~(1 << 4);//输出0// Write 0            udelay(60);                        *DS18B20_GPBDAT |= (1 << 4);//输出1            udelay(2);                                 }   } } ​ ​ /* 从ds18b20得到温度值 精度:0.1C 返回值:温度值 (-550~1250) */ short DS18B20_Get_Temp(void) { unsigned short aaa;    unsigned char temp; unsigned char TL,TH; DS18B20_OUTPUT();    *DS18B20_GPBDAT &= ~(1 << 4);//输出0 //拉低DQ    udelay(750);    //拉低750us    *DS18B20_GPBDAT |= (1 << 4);//输出1 //DQ=1 udelay(15);     //15US      DS18B20_Check();    DS18B20_Write_Byte(0xcc);    DS18B20_Write_Byte(0x44);    DS18B20_OUTPUT();    *DS18B20_GPBDAT &= ~(1 << 4);//输出0 //拉低DQ    udelay(750);    //拉低750us    *DS18B20_GPBDAT |= (1 << 4);//输出1 //DQ=1    udelay(15);     //15US    DS18B20_Check();    DS18B20_Write_Byte(0xcc);// skip rom    DS18B20_Write_Byte(0xbe);// convert        TL=DS18B20_Read_Byte();  // LSB      TH=DS18B20_Read_Byte();  // MSB      aaa=((unsigned short)TH<<8)|TL; return aaa; } ​ ​ /* 杂项字符设备注册示例----->DS18B20 */ static int tiny4412_open(struct inode *my_inode, struct file *my_file) { /*映射物理地址*/ DS18B20_GPBCON=ioremap(0x11400040,4); DS18B20_GPBDAT=ioremap(0x11400044,4); printk("DS18B20初始化成功!\r\n"); /*设置ds18b20为输出模式*/    *DS18B20_GPBCON &= ~(0xf  << 4 * 4);    *DS18B20_GPBCON |= (0x1  << 4 * 4); return 0; } ​ ​ static int tiny4412_release(struct inode *my_inode, struct file *my_file) { /*释放虚拟地址*/ iounmap(DS18B20_GPBCON); iounmap(DS18B20_GPBDAT); printk("DS18B20释放成功\r\n"); return 0; } ​ ​ static ssize_t tiny4412_read(struct file *my_file, char __user *buf, size_t len, loff_t *loff) { /*读取温度信息*/ short temp=DS18B20_Get_Temp(); copy_to_user(buf,&temp,2);    //拷贝温度至应用层 return 0; } ​ ​ static ssize_t tiny4412_write(struct file *my_file, const char __user *buf, size_t len, loff_t *loff) { return 0; } ​ ​ /*文件操作集合*/ static struct file_operations tiny4412_fops= { .open=tiny4412_open, .read=tiny4412_read, .write=tiny4412_write, .release=tiny4412_release }; ​ ​ /* 核心结构体 */ static struct miscdevice tiny4412_misc= { .minor=MISC_DYNAMIC_MINOR,  /*自动分配次设备号*/ .name="DS18B20",     /*设备文件,指定/dev/生成的文件名称*/ .fops=&tiny4412_fops }; ​ static int __init DS18B20_dev_init(void) {    /*杂项设备注册*/    misc_register(&tiny4412_misc); return 0; } ​ static void __exit DS18B20_dev_exit(void) { /*杂项设备注销*/ misc_deregister(&tiny4412_misc); } module_init(DS18B20_dev_init); module_exit(DS18B20_dev_exit); MODULE_LICENSE("GPL");
  • [技术干货] Linux驱动开发-编写FT5X06触摸屏驱动
    1. 前言这篇文章介绍在Linux下如何编写FT5X06系列芯片驱动,完成触摸屏的驱动开发, FT5X06是一个系列,当前使用的具体型号是FT5206,它是一个电容屏的触摸芯片,内置了8位的单片机(8051内核),完成了坐标换算等很多处理,在通过IIC,SPI方式传递给外部单片机。所说起触摸屏大家都不会陌生,现在手机、手表、家电、很多地方都支持触摸了。最开始的触摸屏都是电阻屏,在诺基亚时代的时候,使用的触摸屏都是电阻屏,后来Android兴起的时候,手机都向电容屏发展了。电阻屏需要自己去校准,电阻屏的手机上都有这个功能,发现触摸不灵敏之后,打开校准选项,根据屏幕上十字图标指引,按顺序点一下,完成坐标校准,电阻屏的屏幕还是软材质,必须要手指去戳才可以完成控制,而且只能支持单点触控。现在电容屏就很方便了,只需要手指去触摸屏即可完成操作,比电阻屏方便很多,还支持多点触控,当初Android手机刚兴起的时候,大街小巷的体验店,广告都是切水果游戏,切水果这个游戏就充分体验了多点触摸的效果,可以多个手指去切水果,当初这个游戏还是火爆的。当前文章介绍的FT5206就是一颗电容屏的驱动芯片,最高支持2点触控,可以通过获取两个坐标点,这个系列的芯片最高支持10点触控。当前使用的屏幕型号是S702,这个屏幕是友善之臂生产的LCD屏,S702这款屏幕采用的触摸芯片就是FT5206,引出了IIC接口,支持笔中断,官方的内核里也提供了例子驱动可以参考。开发板与触摸芯片的连线示例:屏幕的实物图详情看下图的介绍:2. FT5206寄存器介绍FT5206支持通过IIC和SPI接口与外部主机通信,当前使用的屏幕硬件上只是引出了IIC接口,下面就介绍下IIC接口的时序,设备地址,还有FT5206的寄存器。IIC传输时序:读写时序流程:字段的解释:下面的截图是介绍FT5206内部的寄存器地址,一些关键的地方我做了翻译:从图上可以看出,基本上后面的寄存器地址都是重复的功能,只是坐标点不一样了,其中的TOUCH2,TOUCH3.......这些都是存放触摸屏的坐标点的值。当前的FT5206只是支持2点触控,所有就只能读取两个寄存器坐标的值。在前面第一个寄存器TD_STATUS里的低4位,存放了当前同时按下的点数量,可以将两个手指按在屏幕上测试读取的值。 这些寄存器里读取的坐标值就是已经转换过后的值,也就是屏幕坐标,不需要再进行二次转换校准,非常方便。3. 编写触摸屏驱动Linux下编写标准的触摸屏驱动需要使用到输入子系统,当前文章的重点是读取触摸屏的坐标,所以示例代码里不会加输入子系统的代码,只是在驱动层完成触摸屏笔中断响应,触摸屏的坐标点获取并打印。驱动代码里涉及的技术点有: IIC子系统、工作队列、内核中断等知识点。这是开发板LCD屏幕的硬件原理图:3.1 设备端代码(FT5206)#include <linux/kernel.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/delay.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/i2c.h> ​ #define DEVICE_NAME "FT5X06_DEV" ​ static struct i2c_adapter *iic_adapter; static struct i2c_client *iic_client; static struct i2c_board_info  iic_info; ​ static int __init iic_dev_init(void) { /*1. 根据总线编号获取IIC适配器结构体*/ iic_adapter=i2c_get_adapter(1); /*2. 填充板级信息*/ iic_info.addr=0x38; iic_info.irq=gpio_to_irq(EXYNOS4_GPX1(6)); strcpy(iic_info.type,DEVICE_NAME); /*3. 注册IIC设备端*/ iic_client=i2c_new_device(iic_adapter,&iic_info);    printk("IIC设备端驱动安装成功.\n");    return 0; } ​ ​ static void __exit iic_dev_exit(void) { /*1. 完成设备端注销*/ i2c_unregister_device(iic_client);    printk("IIC设备端驱动卸载成功.\n"); } ​ /*驱动的入口:insmod xxx.ko*/ module_init(iic_dev_init); /*驱动的出口: rmmod xxx.ko*/ module_exit(iic_dev_exit); /*模块的许可证*/ MODULE_LICENSE("GPL"); /*模块的作者*/ MODULE_AUTHOR("wbyq");3.2 驱动端代码#include <linux/kernel.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/delay.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/workqueue.h> ​ static struct work_struct touch_work; static struct i2c_client *touch_client; ​ /*工作函数*/ void tiny4412_touch_work_func(struct work_struct *work) { u8 touch_buff[7]; u16 x,y; /*1. 读取坐标数据*/ i2c_smbus_read_i2c_block_data(touch_client,0,7,touch_buff); /*2. 打印数据*/ x=(touch_buff[3]&0xF)<<8|touch_buff[4]; y=(touch_buff[5]&0xF)<<8|touch_buff[6]; printk("x=%d,y=%d,p=ß0aab9bb-300f-425c-a435-bd74c802bc3dn",x,y,touch_buff[2]&0xF); } ​ /* 中断的服务函数 */ irqreturn_t tiny4412_touch_irq_handler(int irq, void *dev) { /*调度工作: 将工作加入到工作队列*/ schedule_work(&touch_work); return IRQ_HANDLED; } ​ static int iic_probe(struct i2c_client *client, const struct i2c_device_id *dev_id) { printk("设备地址:0x%X\n",client->addr); printk("设备名称:%s\n",client->name); ​ touch_client=client; /*1. 初始化工作队列*/ INIT_WORK(&touch_work,tiny4412_touch_work_func); /*2. 注册中断*/ request_irq(client->irq,tiny4412_touch_irq_handler,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,client->name,NULL); ​ return 0; } ​ static int iic_remove(struct i2c_client *client) { /*1. 注销中断*/ free_irq(client->irq,NULL); return 0; } ​ static  struct i2c_device_id iic_dev_id[]= { {"FT5X06_DEV",0}, {} }; ​ static struct i2c_driver iic_driver= { .probe=iic_probe, .remove=iic_remove, .driver= { .name="iic_driver" }, .id_table=iic_dev_id }; ​ static int __init iic_drv_init(void) { /*1. 注册IIC驱动端*/ i2c_add_driver(&iic_driver);    printk("驱动安装成功.\n");    return 0; } ​ static void __exit iic_drv_exit(void) { /*2. 注销IIC驱动端*/ i2c_del_driver(&iic_driver);    printk("驱动卸载成功.\n"); } ​ /*驱动的入口:insmod xxx.ko*/ module_init(iic_drv_init); /*驱动的出口: rmmod xxx.ko*/ module_exit(iic_drv_exit); /*模块的许可证*/ MODULE_LICENSE("GPL"); /*模块的作者*/ MODULE_AUTHOR("wbyq");
  • Linux驱动开发-编写MMA7660三轴加速度传感器
    1. MMA7660芯片介绍MMA7660FC 是 ± 1.5 克的三轴数字输出、超低功率、紧凑型电容式微电机的三轴加速度计,是非常低功耗,小型容性 MEMS 的传感器。具有低通滤波器,用于偏移和增益误差补偿, 以及用户可配置的转换成 6 位分辨率,用户可配置输出速率等功能。MMA7660芯片可以通过中断引脚(INT)向外通知传感器数据变化、方向、姿态识别等信息。模拟工作电压范围是 2.4V 至 3.6V,数字工作电压范围是 1.71V 到 3.6V 。常用在手机、掌上电脑、车载导航,便携式电脑的防盗,自动自行车刹车灯、运动检测手环、数码机、自动叫醒闹钟里等等。特别是计步的功能是现在最常见,不管是智能手环、还是手机都带有三轴加速度计,可以记录每天的步数,计算运动量等。现在很多的不倒翁,无人机、相机云台,很多常见的产品里都能看到三轴加速计的身影。通过MMA7660可以做出很多项目: 比如: 老人防跌倒手环、自行车自动刹车灯,智能闹钟,烤火炉跌倒自动断电、运动手环等等。这篇文章就介绍如何在Linux下编写MMA7660三轴加速度芯片的驱动,读取当前芯片的方向姿态,得到X,Y,Z三个轴的数据。MMA7660是IIC接口的,当前驱动就采用标准的IIC子系统编写驱动,使用字符设备框架将得到的数据上传递给应用层。2. 硬件连线当前使用的开发板是友善之臂Tiny4412开发板,使用三星EXYNOS4412芯片,板子本身自带了一颗MMA7660芯片,芯片的原理图如下:内核本身有MMA7660的驱动,下面是源码的路径:如果加载自己编写的驱动,还需要去掉原来内核自带的驱动,不然无法匹配。Device Drivers  ---> <*> Hardware Monitoring support  --->          <*>   Freescale MMA7660 Accelerometer   (将*号去掉,编译内核、烧写内核即可)3. 源代码3.1 mma7660设备端代码: IIC子系统#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> ​ static struct i2c_client *i2cClient = NULL; ​ static unsigned short  i2c_addr_list[]= {0x4c, I2C_CLIENT_END};/*地址队列*/ ​ /* 1. 获取控制器(总线) 2. 探测设备是否存在 3. 定义一个名字用于找到驱动端 */ static int __init mma7660_dev_init(void) { /*mach-tiny4412.c*/ struct i2c_adapter *i2c_adap=NULL;  /*获取到的总线存放在这个结构体*/ struct i2c_board_info i2c_info;     /*设备描述结构体,里面存放着设备的名字还有地址*/ ​ /*1. 获取IIC控制器*/ i2c_adap = i2c_get_adapter(3);     /*要使用IIC_3号总线*/ if(!i2c_adap) { printk("获取IIC控制器信息失败!\n"); return -1; } memset(&i2c_info,0,sizeof(struct i2c_board_info));     /*清空结构体*/ strlcpy(i2c_info.type,"mma7660_drv",I2C_NAME_SIZE);    /*名称的赋值*/ i2c_info.irq=EXYNOS4_GPX3(1); /*中断IO口*/ ​ /*2. 创建IIC设备客户端*/ i2cClient = i2c_new_probed_device(i2c_adap,&i2c_info,i2c_addr_list,NULL); if(!i2cClient) { printk("mma7660_探测地址出现错误!!\n"); return -1; } ​ i2c_put_adapter(i2c_adap);/*设置模块使用计数*/ printk("mma7660_dev_init!!\n"); return 0; } ​ ​ static void __exit mma7660_dev_exit(void)//平台设备端的出口函数 { printk(" mma7660_dev_exit ok!!\n"); ​ /*注销设备*/ i2c_unregister_device(i2cClient); ​ /*释放*/ i2c_release_client(i2cClient); } ​ ​ module_init(mma7660_dev_init); module_exit(mma7660_dev_exit); MODULE_LICENSE("GPL");3.2 mma7660驱动端代码: IIC子系统#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/irq.h> #include <linux/interrupt.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/workqueue.h> #include <linux/delay.h> /* MMA7760 Registers */ #define MMA7660_XOUT 0x00 // 6-bit output value X #define MMA7660_YOUT 0x01 // 6-bit output value Y #define MMA7660_ZOUT 0x02 // 6-bit output value Z #define MMA7660_TILT 0x03 // Tilt status #define MMA7660_SRST 0x04 // Sampling Rate Status #define MMA7660_SPCNT 0x05 // Sleep Count #define MMA7660_INTSU 0x06 // Interrupt Setup #define MMA7660_MODE 0x07 // Mode #define MMA7660_SR 0x08 // Auto-Wake/Sleep and Debounce Filter #define MMA7660_PDET 0x09 // Tap Detection #define MMA7660_PD 0x0a // Tap Debounce Count static const struct i2c_device_id mma7660_id[] = { {"mma7660_drv",0}, /*设备端的名字,0表示不需要私有数据*/ {} }; static u32 mma7660_irq; /*触摸屏的中断编号*/ static struct i2c_client *mma7660_client=NULL; static int last_tilt = 0; #define __need_retry(__v) (__v & (1 << 6)) #define __is_negative(__v) (__v & (1 << 5)) static const char *mma7660_bafro[] = { "未知", "前面", "背面" }; static const char *mma7660_pola[] = { "未知", "左面", "向右", "保留", "保留", "向下", "向上", "保留", }; /* 函数功能:读取一个字节的数据 */ static int mma7660_read_tilt(struct i2c_client *client, int *tilt) { int val; do { val = i2c_smbus_read_byte_data(client, MMA7660_TILT); } while (__need_retry(val)); *tilt = (val & 0xff); return 0; } /* 函数功能: 读取XYZ坐标数据 */ static int mma7660_read_xyz(struct i2c_client *client, int idx, int *xyz) { int val; do { val = i2c_smbus_read_byte_data(client, idx + MMA7660_XOUT); } while (__need_retry(val)); *xyz = __is_negative(val) ? (val | ~0x3f) : (val & 0x3f); return 0; } /* 工作队列处理函数 */ static void mma7660_worker(struct work_struct *work) { int bafro, pola, shake, tap; int val = 0; mma7660_read_tilt(mma7660_client,&val); /* TODO: report it ? */ bafro = val & 0x03; if (bafro != (last_tilt & 0x03)) { printk("%s\n", mma7660_bafro[bafro]); } pola = (val >> 2) & 0x07; if (pola != ((last_tilt >> 2) & 0x07)) { printk("%s\n", mma7660_pola[pola]); } shake = (val >> 5) & 0x01; if (shake && shake != ((last_tilt >> 5) & 0x01)) { printk("Shake\n"); } tap = (val >> 7) & 0x01; if (tap && tap != ((last_tilt >> 7) & 0x01)) { printk("Tap\n"); } /* Save current status */ last_tilt = val; int axis[3]; int i; for (i = 0; i < 3; i++) { mma7660_read_xyz(mma7660_client, i, &axis[i]); } printk("ABS_X=Ü1a32df6-c5b9-406f-b98a-588d0d0feff6n",axis[0]); printk("ABS_Y=Ü1a32df6-c5b9-406f-b98a-588d0d0feff6n",axis[1]); printk("ABS_Z=Ü1a32df6-c5b9-406f-b98a-588d0d0feff6n",axis[2]); } /* 函数功能: mma7660初始化 */ static int mma7660_initialize(struct i2c_client *client) { int val; /* Using test mode to probe chip */ i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00); mdelay(10); i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x04); mdelay(10); i2c_smbus_write_byte_data(client, MMA7660_XOUT, 0x3f); i2c_smbus_write_byte_data(client, MMA7660_YOUT, 0x01); i2c_smbus_write_byte_data(client, MMA7660_ZOUT, 0x15); val = i2c_smbus_read_byte_data(client, MMA7660_ZOUT); if (val != 0x15) { dev_err(&client->dev, "no device\n"); return -ENODEV; } /* Goto standby mode for configuration */ i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00); mdelay(10); /* Sample rate: 64Hz / 16Hz; Filt: 3 samples */ i2c_smbus_write_byte_data(client, MMA7660_SR, ((2<<5) | (1<<3) | 1)); /* Sleep count */ i2c_smbus_write_byte_data(client, MMA7660_SPCNT, 0xA0); /* Tap detect and debounce ~4ms */ i2c_smbus_write_byte_data(client, MMA7660_PDET, 4); i2c_smbus_write_byte_data(client, MMA7660_PD, 15); /* Enable interrupt except exiting Auto-Sleep */ i2c_smbus_write_byte_data(client, MMA7660_INTSU, 0xe7); /* IPP, Auto-wake, auto-sleep and standby */ i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x59); mdelay(10); /* Save current tilt status */ mma7660_read_tilt(client, &last_tilt); mma7660_client = client; return 0; } /* 静态方式初始化工作队列 */ DECLARE_WORK(mma7660_work,mma7660_worker); static irqreturn_t mma7660_interrupt(int irq, void *dev_id) { /*调度共享工作队列*/ schedule_work(&mma7660_work); return IRQ_HANDLED; } /* 匹配成功时调用 */ static int mma7660_probe(struct i2c_client *client, const struct i2c_device_id *device_id) { printk("mma7660_probe!!!\n"); printk("驱动端IIC匹配的地址=0x%x\n",client->addr); mma7660_client=client; /*1. 注册中断*/ mma7660_irq=gpio_to_irq(client->irq);/*获取中断编号*/ if(request_irq(mma7660_irq,mma7660_interrupt,IRQF_TRIGGER_FALLING,"mma7660_irq",NULL)!=0) { printk("mma7660_中断注册失败!\n"); } /*2. 初始化mma7660*/ if(mma7660_initialize(client) < 0) { printk(" 初始化mma7660失败!\n"); } return 0; } static int mma7660_remove(struct i2c_client *client) { free_irq(mma7660_irq,NULL); printk("mma7660_remove!!!\n"); return 0; } struct i2c_driver i2c_drv = { .driver = { .name = "mma7660", .owner = THIS_MODULE, }, .probe = mma7660_probe, //探测函数 .remove = mma7660_remove, //资源卸载 .id_table = mma7660_id, //里面有一个名字的参数用来匹配设备端名字 }; static int __init mma7660_drv_init(void) { /*向iic总线注册一个驱动*/ i2c_add_driver(&i2c_drv); return 0; } static void __exit mma7660_drv_exit(void) { /*从iic总线注销一个驱动*/ i2c_del_driver(&i2c_drv); } module_init(mma7660_drv_init); module_exit(mma7660_drv_exit); MODULE_LICENSE("GPL");
总条数:501 到第
上滑加载中