前言
如今智能信息时代发展迅猛,年轻一代享受着时代红利,而上了年纪的长辈却越来越迷茫,对于智能手机根本搞不清使用方法,很多老人只能退而求其次,使用老人机满足打电话这样的就基本需求,同时老人机存在很多局限:有时老人家会因为心痛电话费刻意控制时长;大多数老人机只能打电话,不能视频通话。如今网络如此发达,微信作为全球统一的及时通讯软件,不但通话免费,还能打视频电话,为何不开发针对老人的微信版本呢?只知道儿童智能手表可以打微信电话,但还是无法满足所有痛点,开造:
项目说明
思路:基于STM32的USB鼠标控制手机完成自动化控制操作,一般的手机都支持USB OTG功能。但OTG功能存在一个很大的弊端,就是无法边充电边使用,本文设计功能切换电路解决了这一问题,可闲时给手机充电。制作此项目需要参考我的前两篇博文。
所需材料
- 旧安卓智能手机
- 5pin的MicroUSB接头
- 矩阵键盘
- STM32F103C8T6最小系统板
- 手机支架
- mos管或继电器

一、先看成果
 
1 | 2 | 3 | A |
---|
选择联系人1 | 选择联系人2 | 选择联系人3 | 解锁 | 4 | 5 | 6 | B | 选择联系人4 | 选择联系人5 | 选择联系人6 | 进入微信 | 7 | 8 | 9 | C | 选择联系人4 | 选择联系人5 | 选择联系人6 | 开始呼叫 | * | 0 | # | D | 解锁并进入微信 | 选择联系人0 | 开始呼叫 | 结束通话 |
二、硬件设计
1 接线示意图

1.1 矩阵键盘
矩阵键盘内部接线示意图如下: 
1.2 MicroUSB OTG接口功能切换电路
MicroUSB有5个引脚,其中两个用来供电(VCC、GND),两个用来传输数据(D+、D-),还有一个ID引脚是用来选择OTG功能的。当ID接地时,手机会启用OTG功能,当ID悬空时为普通数据线功能。经过反复实验,发现手机在MicroUSB刚插入时才检测是否开启OTG功能,为了**模拟插入和拔出的操作,用MOS管来断电,用单片机控制ID引脚的电平,实现功能切换,达到空闲时充电的目的。**手头只要一颗IRF540N的N型MOS管,只能用如下电路控制。
 根据数据手册,此MOS管过于大材小用,VDSS = 100V RDS(on) = 44m? ID = 33A。 同时VGSth在2-4V之间,而单片机IO在输出电压为3.3V,虽然在这个范围内,但为了使MOS能够完全导通,利用开漏输出功能配合上拉电阻可将IO口输出电压提高到5V。 
2 焊接
为了省去焊接的麻烦,画了一个PCB板,第一板存在一些小问题(没有画入MOS管,加入了音频功放功能作为拓展可以忽略),以上述接线图为准。 
三、软件设计
根据此文章配置好USB鼠标程序框架
结合此文章添加矩阵键盘功能
根据此文章配置好USB鼠标程序框架
1 引脚配置
根据以上配置好USB鼠标后配置键盘引脚和USB OTG功能切换控制引脚。 将键盘的8个引脚全部配置为上拉模式,其中4个为输出口、4个为输入口  将USB OTG控制的两个引脚配置为开漏输出,可修改名称方便阅读 
2.1 修改usb_device.c文件
在编写鼠标控制程序时发现鼠标只能相对移动,无法移动绝对位置,而点击屏幕最重要的时移动到屏幕指定位置。为了解决这一问题,本文采用的方案为:
一直移动到最左上角
设为原点
相对移动
计算绝对位置
添加静态变量
static int16_t Px=0,Py=0;
static uint8_t L=0,R=0,M=0;
添加鼠标基本控制函数
void Set_Mouse(int8_t x,int8_t y,int8_t z,uint8_t l,uint8_t r,uint8_t m)
{
uint8_t buf[4] = {0,0,0,0};
L=l;R=r;M=m;
buf[0] = L+R*2+M*4;
buf[1]=x;
buf[2]=y;
buf[3]=z;
USBD_HID_SendReport(&hUsbDeviceFS,buf,4);
HAL_Delay(100);
}
void Scroll(int8_t z)
{
Set_Mouse(0,0,z,L,R,M);
}
void Move(int8_t x,int8_t y)
{
Px += x;
Py += y;
if(Px<0)Px=0;
if(Py<0)Py=0;
if(Px>=PX_MAX-1)Px=PX_MAX-1;
if(Py>=PY_MAX-1)Py=PY_MAX-1;
Set_Mouse(x,y,0,L,R,M);
}
void Move_Reset(void)
{
uint8_t i=16;
while(i--)Move(-120,-120);
Px = Py = 0;
}
void Move_To(int16_t x,int16_t y)
{
x -= Px;
y -= Py;
if(x > 0){while(x >= 100)x -= 100,Move(100,0);}
else {while (x <= -100)x += 100,Move(-100,0);}
Move(x,0);
if(y > 0){while(y >= 100)y -= 100,Move(0,100);}
else {while(y <= -100)y += 100,Move(0,-100);}
Move(0,y);
}
void Click_L(void)
{
Set_Mouse(0,0,0,1,0,0);
Set_Mouse(0,0,0,0,0,0);
}
void Click_R(void)
{
Set_Mouse(0,0,0,0,1,0);
Set_Mouse(0,0,0,0,0,0);
}
void Click_M(void)
{
Set_Mouse(0,0,0,0,0,1);
Set_Mouse(0,0,0,0,0,0);
}
void Move_To_Click(int16_t x,int16_t y)
{
Move_To(x,y);
Click_L();
}
添加自动化程序,需要将手机开发者选项打开,显示指针位置,方便确定每个步骤点击屏幕的具体位置。本文使用的安卓手机屏幕分辨率为1080x1920. 手机解锁程序:滑动解锁
void Unlock(void)
{
Move_To_Click(550,1890);
HAL_Delay(1000);
Move_To(600,1400);
Set_Mouse(0,0,0,1,0,0);
Move_To(600,1000);
Set_Mouse(0,0,0,0,0,0);
}
打开微信 先将微信图标放在桌面第2页特定位置,方便点击
void Open_Wechat(void)
{
Move_To_Click(550,1890);
HAL_Delay(1000);
Move_To(550,1000);
Scroll(-100);
Move_To_Click(150,150);
HAL_Delay(1000);
Move_To_Click(400,1740);
}
选择联系人 先将微信中联系人的备注添加前缀,这样就能进行排序。本文将常用的10个联系人备注分别加上 A0、A1、A2、A3、A4、A5、A6、A7、A8、A9。这样就可以精确定位联系人了。
回到微信主页
点击通讯录
点击字母A
点击A分组下的联系人
void Choose_Name(int8_t num)
{
Move_To_Click(40,100);
Move_To_Click(40,100);
Move_To_Click(40,100);
Move_To_Click(40,100);
Move_To_Click(40,100);
Move_To_Click(400,1740);
HAL_Delay(1000);
Move_To_Click(1050,410);
HAL_Delay(1000);
Move_To_Click(200,num*133+350);
}
开始呼叫 这一步较简单。
void Start_Call()
{
Move_To_Click(550,1400);
HAL_Delay(2000);
Move_To_Click(550,1460);
}
结束通话
void Stop_Call(void)
{
Move_To_Click(550,1600);
HAL_Delay(1000);
Move_To_Click(550,1600);
Move_To_Click(350,1600);
}
2.2 修改usb_device.h文件
对以上函数添加声明
void Set_Mouse(int8_t x,int8_t y,int8_t z,uint8_t L,uint8_t R,uint8_t M);
void Scroll(int8_t z);
void Move(int8_t x,int8_t y);
void Move_Reset(void);
void Move_To(int16_t x,int16_t y);
void Click_L(void);
void Click_R(void);
void Click_M(void);
void Move_To_Click(int16_t x,int16_t y);
void Unlock(void);
void Open_Wechat(void);
void Start_Call(void);
void Choose_Name(int8_t num);
void Stop_Call(void);
3 修改main.c文件
添加矩阵键盘扫描函数
uint8_t Key_Scan(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_Delay(10);
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_15)==GPIO_PIN_RESET)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_Delay(10);
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_3)==GPIO_PIN_RESET)return 'A';
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_4)==GPIO_PIN_RESET)return '3';
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_5)==GPIO_PIN_RESET)return '2';
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_6)==GPIO_PIN_RESET)return '1';
}
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_14)==GPIO_PIN_RESET)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_Delay(10);
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_3)==GPIO_PIN_RESET)return 'B';
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_4)==GPIO_PIN_RESET)return '6';
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_5)==GPIO_PIN_RESET)return '5';
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_6)==GPIO_PIN_RESET)return '4';
}
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13)==GPIO_PIN_RESET)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_Delay(10);
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_3)==GPIO_PIN_RESET)return 'C';
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_4)==GPIO_PIN_RESET)return '9';
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_5)==GPIO_PIN_RESET)return '8';
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_6)==GPIO_PIN_RESET)return '7';
}
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12)==GPIO_PIN_RESET)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_Delay(10);
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_3)==GPIO_PIN_RESET)return 'D';
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_4)==GPIO_PIN_RESET)return '#';
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_5)==GPIO_PIN_RESET)return '0';
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_6)==GPIO_PIN_RESET)return '*';
}
return 0;
}
添加USB OTG功能切换函数
static void USB_OTG_ON(void)
{
USB_OTG_FLAG = 1;
HAL_GPIO_WritePin(USB_ID_GPIO_Port, USB_ID_Pin, GPIO_PIN_SET);
HAL_Delay(100);
HAL_GPIO_WritePin(GPIOA,USB_VCC_Pin, GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(USB_ID_GPIO_Port, USB_ID_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA,USB_VCC_Pin, GPIO_PIN_SET);
HAL_Delay(1000);
Move_Reset();
}
static void USB_OTG_OFF(void)
{
USB_OTG_FLAG = 0;
HAL_GPIO_WritePin(USB_ID_GPIO_Port, USB_ID_Pin, GPIO_PIN_SET);
HAL_Delay(100);
HAL_GPIO_WritePin(GPIOA,USB_VCC_Pin, GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(USB_ID_GPIO_Port, USB_ID_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA,USB_VCC_Pin, GPIO_PIN_SET);
}
声明USB OTG状态标志位变量
static uint8_t USB_OTG_FLAG=0;
声明以上三个函数
static void USB_OTG_ON(void);
static void USB_OTG_OFF(void);
uint8_t Key_Scan(void);
编写主函数程序
int main(void)
{
uint8_t key;
uint16_t timeout_10ms;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USB_DEVICE_Init();
USB_OTG_OFF();
while(1)
{
key = Key_Scan();
if(key)
{
if(USB_OTG_FLAG==0)USB_OTG_ON(),timeout_10ms=6000;
switch(key)
{
case 'A':Unlock(); break;
case 'B':Open_Wechat(); break;
case 'C':Start_Call(); break;
case 'D':Stop_Call(); break;
case '0':Choose_Name(0);break;
case '1':Choose_Name(1);break;
case '2':Choose_Name(2);break;
case '3':Choose_Name(3);break;
case '4':Choose_Name(4);break;
case '5':Choose_Name(5);break;
case '6':Choose_Name(6);break;
case '7':Choose_Name(7);break;
case '8':Choose_Name(8);break;
case '9':Choose_Name(9);break;
case '*':Unlock();HAL_Delay(1000);Open_Wechat();break;
case '#':Start_Call();break;
}
}
if(USB_OTG_FLAG){if(timeout_10ms--)HAL_Delay(10);else USB_OTG_OFF();}
}
}
四、结语
可编程鼠标的应用范围远不止此,还有许多实用场景,比如电视盒子一键收看节目、代替手机脚本完成自动化操作,将手机作为小电视一键收看等等,整个制作流程并不复杂,却开了先河。如有错误望批评指正,因春运期间快递停运没有接插件,只能焊接,外观不佳。更多场景和功能等待各位开发者们探索。
|