/**********************************************************************
文件名: 基于STM8S207的PAJ7620U2手势识别测试程序
编 写: Michael Lee
单 位:
时 间: 2020年10月20日
修 改:
备 注:
1. CPU主时钟选择内部16MHz RC振荡器;
2. 接口说明:
STM8S207CBT6 ----- 外部设备
******** 12864液晶显示器 ********
PD2 ----- YJ_RS
PD3 ----- YJ_RW
PD4 ----- YJ_E
PC1 ----- YJ_PSB
PB0-7 ----- YJ_DB0-7
******** STM8S硬件I2C ********
PE1 ----- PAJ7620_SCL
PE2 ----- PAJ7620_SDA
声明:
**********************************************************************/
//加载头文件
#include "stm8s207cb.h"
//宏定义
#define uchar unsigned char
#define uint unsigned int
#define ushort unsigned short
#define EEP_BASE 0x4000 //STM8S大容量Data EEPROM起始地址
#define EEPROM_Lock() FLASH_IAPSR&=(~0x08)
#define EEPROM_Wait_Read_Over() while(!(FLASH_IAPSR&0x04))
#define EEPROM_Wrover_Clear() FLASH_IAPSR&=(~0x04)
#define PAJ7620_ID 0x73<<1 //PAJ7620设备地址(写命令)
//全局变量
uchar Gesture_Flag=0; //手势识别结果标志
//PAJ7620初始化数据及宏定义
#define INIT_SIZE sizeof(Init_Array)/2
//上电初始化寄存器数组
const unsigned char Init_Array[][2]=
{
{0xEF,0x00},
{0x37,0x07},
{0x38,0x17},
{0x39,0x06},
{0x41,0x00},
{0x42,0x00},
{0x46,0x2D},
{0x47,0x0F},
{0x48,0x3C},
{0x49,0x00},
{0x4A,0x1E},
{0x4C,0x20},
{0x51,0x10},
{0x5E,0x10},
{0x60,0x27},
{0x80,0x42},
{0x81,0x44},
{0x82,0x04},
{0x8B,0x01},
{0x90,0x06},
{0x95,0x0A},
{0x96,0x0C},
{0x97,0x05},
{0x9A,0x14},
{0x9C,0x3F},
{0xA5,0x19},
{0xCC,0x19},
{0xCD,0x0B},
{0xCE,0x13},
{0xCF,0x64},
{0xD0,0x21},
{0xEF,0x01},
{0x02,0x0F},
{0x03,0x10},
{0x04,0x02},
{0x25,0x01},
{0x27,0x39},
{0x28,0x7F},
{0x29,0x08},
{0x3E,0xFF},
{0x5E,0x3D},
{0x65,0x96},
{0x67,0x97},
{0x69,0xCD},
{0x6A,0x01},
{0x6D,0x2C},
{0x6E,0x01},
{0x72,0x01},
{0x73,0x35},
{0x74,0x00},
{0x77,0x01},
};
#define GESTURE_SIZE sizeof(Gesture_Arry)/2
//手势识别初始化寄存器数组
const unsigned char Gesture_Arry[][2]=
{
{0xEF,0x00},
{0x41,0x00},
{0x42,0x00},
{0xEF,0x00},
{0x48,0x3C},
{0x49,0x00},
{0x51,0x10},
{0x83,0x20},
{0x9F,0xF9},
{0xEF,0x01},
{0x01,0x1E},
{0x02,0x0F},
{0x03,0x10},
{0x04,0x02},
{0x41,0x40},
{0x43,0x30},
{0x65,0x96},
{0x66,0x00},
{0x67,0x97},
{0x68,0x01},
{0x69,0xCD},
{0x6A,0x01},
{0x6B,0xB0},
{0x6C,0x04},
{0x6D,0x2C},
{0x6E,0x01},
{0x74,0x00},
{0xEF,0x00},
{0x41,0xFF},
{0x42,0x01},
};
/********** 延时函数 ********/
/***********************************************************
函数名: delay_us()、delay_ms()
功 能: 微秒级、毫秒级延时程序
参 数: us --- 欲延时微秒数
ms --- 欲延时毫秒数
/**********************************************************/
void delay_us(unsigned int us)
{
//24M 1个int us延时0.68uS
//16M 1.02us
while(us--);
}
void delay_ms(unsigned int ms)//16MHZ 延时1ms
{
unsigned int i;
while(ms--)
{
for(i=0;i<1421;i++){} //1421
}
}
/******* 系统时钟切换操作 *******/
/***********************************************************
函数名: Clk_Init()
功 能: 时钟初始化
参 数: 无
返回值: 无
/**********************************************************/
void Clk_Init(void)
{
CLK_CKDIVR = 0x00;
//配置系统内部时钟分频系数为1(16M),CPU时钟分频系数为1(16M)
}
/********* 端口初始化操作 **********/
/***********************************************************
函数名: Port_Init()
功 能: 端口初始化配置
参 数: 无
返回值: 无
/**********************************************************/
void Port_Init(void)
{
//12864液晶控制接口
PC_DDR |= 0x04; //配置PC2为输出方式(液晶显示器背光控制端口)
PC_CR1 |= 0x04; //配置端口推挽输出
PC_CR2 = 0x00; //普通2M的输出摆率
PC_ODR |= 0x04; //YJ_ON=1,选择开启液晶背光
PD_DDR |= 0x1C; //配置PD2-4为输出方式(液晶显示器RS,RW,E端口)
PD_CR1 |= 0x1C; //配置端口推挽输出
PD_CR2 = 0x00; //普通2M的输出摆率
PB_DDR |= 0xFF; //配置PB0-7输出(液晶数据DB0-7端口)
PB_CR1 |= 0xFF; //推挽输出
PB_CR2 = 0x00; //普通2M摆率
}
/******* 12864液晶(ST7920主控IC)显示器驱动及相关操作 *******/
//底层驱动函数
//写入一字节指令/数据
/***********************************************************
函数名: lcd_w_char()
功 能: 向12864液晶写入一字节指令/数据
参 数: cd --- 写入指令/数据选择标志(0=指令,1=数据)
cdata --- 写入指令/数据内容
返回值: 无
/**********************************************************/
void lcd_w_char(uchar cd, uchar cdata)
{
if(cd == 0) //选择写入"指令"
{
PD_ODR &= (~0x04); //RS=0,选择写"指令"
}
if(cd == 1) //选择写入"数据"
{
PD_ODR |= 0x04; //RS=1,选择写"数据"
}
PD_ODR &= (~0x08); //RW=0,选择"写"操作
PB_ODR = cdata; //加载指令/数据内容
PD_ODR |= 0x10; //E=1,开始写入指令/数据
delay_us(1); //延时,等待液晶端口读入指令/数据内容
PD_ODR &= (~0x10); //E=0,写入完成
delay_us(80); //液晶处理指令时间一般为72us
}
//写入一串字符数据
/***********************************************************
函数名: lcd_w_word()
功 能: 向12864液晶写入一串字符数据(最长64字符: 8个汉字或16个数字/英文字符)
(注: ST7920主控IC的12864液晶内部已集成汉字库,程序可直接写入汉字字符)
参 数: str --- 字符串内容
返回值: 无
***********************************************************/
void lcd_w_word(uchar *str)
{
while(*str != '\0') //字符串未结束
{
lcd_w_char(1,*str++); //写入每个字符串数据内容
}
*str = 0;
}
//初始化函数
/***********************************************************
函数名: Lcd_Init()
功 能: 12864液晶初始化
参 数: 无
返回值: 无
/**********************************************************/
void Lcd_Init(void)
{
lcd_w_char(0,0x30); //写入指令,选择8位界面,基本指令集,绘图显示关
lcd_w_char(0,0x0c); //显示开,光标关闭
lcd_w_char(0,0x01); //清屏
delay_ms(5); //液晶处理指令时间4.6ms
}
/********* STM8S硬件I2C相关操作 **********/
//初始化函数
/***********************************************************
函数名: I2C_Init()
功 能: STM8S207 MCU硬件I2C初始化
(注: 主模式,两线连接,7位地址模式,40Kbps通信速率)
参 数: 无
返回值: 无
***********************************************************/
void I2C_Init(void)
{
I2C_CR1 &= 0xFE; //PE=0,禁用硬件I2C模块(所有位置0)
CLK_PCKENR1 |= 0x01; //使能I2C时钟
I2C_CR2 |= 0x80; //I2C_CR2控制寄存器SWRST=1,软件复位硬件I2C模块
I2C_CR2 &= 0x7F; //I2C_CR2控制寄存器SWRST=0,软件复位硬件I2C模块结束
I2C_FREQR = 0x02; //配置I2C外设时钟2MHz
I2C_CCRH = 0x00; //配置I2C为标准模式及速率
I2C_CCRL = 0x32; //I2C通信速率半周期=((1/2)*50us=25us,速率=1/25us=40KHz
I2C_TRISER = 0x03; //配置I2C上升时间寄存器,1000ns(SCL最大值)/500ns(2MHz周期)+1
I2C_CR1 |= 0x01; //启动I2C硬件模块
}
/******** PAJ7620手势识别IC驱动及相关数据处理操作 *********/
/***********************************************************
函数名: PAJ7620_Read()
功 能: STM8S207 MCU通过硬件I2C通信读取PAJ7620内多个寄存器数据
参 数: reg_address --- 欲读取数据的寄存器起始地址
data_buff --- 从PAJ7620读取的多字节数据指针
data_len --- 从PAJ7620读取的多字节数据长度
返回值: 无
***********************************************************/
void PAJ7620_Read(uchar reg_address,uchar *data_buff,uchar data_len)
{
uchar temp; //定义一下,读取MCU状态寄存器用
uchar i; //定义一下,供循环使用
uint time; //定义一下,供等待延时用
//以下程序为I2C通信防锁死操作,非常重要!
if((PE_IDR&0x04) == 0x00) //检测总线是否真的忙碌中(忙碌则SCL=1,SDA=0)
{
I2C_CR1 &= 0xFE; //PE=0,禁用硬件I2C模块,准备直接操作MCU端口
PE_DDR |= 0x02; //设置SCL端口为输出
PE_CR1 |= 0x02; //上拉输出
for(i=0;i<9;i++) //SCL端口发出9个时钟脉冲,让从设备DS3231释放总线(恢复SDA=1)
{
PE_ODR &= 0xFD; //SCL=0;
delay_us(50); //延时(I2C通信频率10KHz)
PE_ODR |= 0x02; //SCL=1
delay_us(50); //延时(I2C通信频率10KHz)
}
time = 500; //变量赋值,准备延迟等待500次
while(!(PE_IDR&0x04)) //等待数据线SDA=1
{
if(!--time) //变量自减
return; //延迟等待500次后,未达到预期效果,退出子函数以防程序死机状态
}
PE_DDR &= 0xFD; //SCL端口恢复输入方式
PE_CR1 &= 0xFD; //浮空输入
PE_ODR &= 0xFD; //清零输出寄存器
I2C_CR1 |= 0x01; //重新启动硬件I2C模块,接管SCL、SDA端口
return; //退出函数,放弃本次数据读取操作
}
//以上程序为I2C通信防锁死操作,非常重要!
//以下程序为STM8S硬件I2C通信防BUSY锁死操作,非常重要!
if((I2C_SR3&0x02) == 0x02) //检测总线是否正确释放空闲中(总线正确释放则I2C_SR3状态寄存器BUSY=0,否则BUSY=1)
{
I2C_CR2 |= 0x80; //I2C_CR2控制寄存器SWRST=1,软件复位硬件I2C模块
I2C_CR2 &= 0x7F; //I2C_CR2控制寄存器SWRST=0,软件复位硬件I2C模块结束
I2C_FREQR = 0x02; //配置I2C外设时钟2MHz
I2C_CCRH = 0x00; //配置I2C为标准模式及速率
I2C_CCRL = 0x64; //I2C通信速率半周期=((1/2)*100us=50us,速率=1/100us=10KHz
I2C_TRISER = 0x03; //配置I2C上升时间寄存器,1000ns(SCL最大值)/500ns(2MHz周期)+1
I2C_CR1 |= 0x01; //启动I2C硬件模块
return; //退出函数,放弃本次数据读取操作
}
//以上程序为STM8S硬件I2C通信防BUSY锁死操作,非常重要!
/****** 第一步: 发送写指令(器件地址) ******/
time = 500; //变量赋值,准备延迟等待500次
while(I2C_SR3&0x02) //检测总线是否忙碌中(I2C_SR3状态寄存器BUSY位=1表示总线上有通信/忙碌)
{
if(!--time) //变量自减
return; //延迟等待500次后,未达到预期效果,退出子函数以防程序死机状态
}
I2C_CR2 |= 0x01; //发起始条件(置位I2C_CR2控制寄存器START位)
time = 500; //变量赋值,准备延迟等待500次
while(!(I2C_SR1&0x01)) //I2C_SR1状态寄存器SB位=1表示发送成功
{
if(!--time) //变量自减
return; //延迟等待500次后,未达到预期效果,退出子函数以防程序死机状态
}
delay_us(5); //短暂延时
temp = I2C_SR1; //读取状态寄存器I2C_SR1值
I2C_DR = PAJ7620_ID; //发送PAJ7620写指令(器件地址)
/****** 第二步: 写入欲读取的寄存器首地址 ******/
time = 500; //变量赋值,准备延迟等待500次
while(!(I2C_SR1&0x02)) //I2C_SR1状态寄存器ADDR位=1时表示地址发送结束(主模式)
{
if(!--time) //变量自减
return; //延迟等待500次后,未达到预期效果,退出子函数以防程序死机状态
}
delay_us(5); //短暂延时
temp = I2C_SR1; //读取状态寄存器I2C_SR1数值
temp = I2C_SR3; //清除状态寄存器I2C_SR1中ADDR标志位(地址已发送结束)
I2C_DR = reg_address; //发送欲读取数据的寄存器首地址
/****** 第三步: 发送读指令(器件地址|0x01) ******/
time = 500; //变量赋值,准备延迟等待500次
while(!(I2C_SR1&0x84)) //等待数据发送完毕(I2C_SR1状态寄存器TxE、BTF位为1时,表示发送时数据寄存器为空且数据字节发送完毕)
{
if(!--time) //变量自减
return; //延迟等待500次后,未达到预期效果,退出子函数以防程序死机状态
}
I2C_CR2 |= 0x01; //重复发起始条件(置位I2C_CR2控制寄存器START位)
time = 500; //变量赋值,准备延迟等待500次
while(!(I2C_SR1&0x01)) //I2C_SR1状态寄存器SB位=1表示发送成功
{
if(!--time) //变量自减
return; //延迟等待500次后,未达到预期效果,退出子函数以防程序死机状态
}
delay_us(5); //短暂延时
temp = I2C_SR1; //读取状态寄存器I2C_SR1值
I2C_DR = PAJ7620_ID|0x01; //发送PAJ7620读指令
/****** 第四步: 读取数据 ******/
time = 500; //变量赋值,准备延迟等待500次
while(!(I2C_SR1&0x02)) //I2C_SR1状态寄存器ADDR位=1时表示地址发送结束(主模式)
{
if(!--time) //变量自减
return; //延迟等待500次后,未达到预期效果,退出子函数以防程序死机状态
}
delay_us(5); //短暂延时
temp = I2C_SR1; //读取状态寄存器I2C_SR1数值
temp = I2C_SR3; //清除状态寄存器I2C_SR1中ADDR标志位(地址已发送结束)
I2C_CR2 |= 0x04; //使能ACK应答(置位I2C_CR2控制寄存器ACK位,收到一个字节数据或地址后返回应答)
while(data_len) //循环读取数据
{
if(data_len == 1) //如果是接收最后一位数据(特殊处理)
{
I2C_CR2 &= 0xFB; //关闭ACK应答(清零I2C_CR2控制寄存器ACK位,收到一个字节数据或地址后不返回应答)
I2C_CR2 |= 0x02; //发停止条件(置位I2C_CR2控制寄存器STOP位///必须先清除I2C_SR1状态寄存器BTF位)
}
if(I2C_SR1 & 0x40) //数据寄存器为满(I2C_SR1状态寄存器RxNE位为1时,表示接收寄存器数据非空)
{
*data_buff = I2C_DR; //读取数据寄存器内数值,同时清除BTF位
data_buff++; //准备接收下一字节数据
data_len--;
}
}
/****** 第五步: 收尾 ******/
temp = I2C_SR1; //读取状态寄存器I2C_SR1值
temp = I2C_DR; //清除I2C_SR1中BFT位
}