> 技术文档 > STM32CubeMX IIC实现六轴姿态陀螺仪模块JY61P

STM32CubeMX IIC实现六轴姿态陀螺仪模块JY61P

之前写的有点糙,重新完善一下,尝试传了一个我改好的工程文件,刚发现下载竟然要钱,下面的链接里有我改好的工程,可以直接下载用

通过网盘分享的文件:jy61p.rar
链接: https://pan.baidu.com/s/18lbZYb13cdgsT7MkhKhcUw?pwd=9rm3 提取码: 9rm3 
--来自百度网盘超级会员v8的分享

最近需要用到陀螺仪这个模块,选取的是JY61P这个模块,单片机是正点原子f103的mini板,查资料的过程中发现用串口的代码很多,用iic的几乎没有,而我又需要iic,所以写移植iic代码的笔记

一、官方例程

用的是维特智能的JY61P模块,商家会给资料链接:https://wit-motion.yuque.com/wumwnr/docs/np25sf?singleDoc#%20%E3%80%8AJY61P%E4%BA%A7%E5%93%81%E8%B5%84%E6%96%99%E3%80%8B

选协议说明,接着在左边目录找到IIC协议

 我用的是stm32,选对应选项

就来到了对应的教程界面,下载链接下的对应例程代码,我用的是STM32F1的

 然后打开工程文件,就能看到keil工程了,需要wit_c_sdk和Drive里的文件,

wit_c_sdk里的全都要

Drive里的要这三个,其中REG.h和上一级目录里的wit_c_sdk是一样的,有一个就行,因为后面我要用STM32CubeMX生成工程,所以delay,uart相关的设置用STM32CubeMX来配置

二、 STM32CubeMX生成工程

2.1系统

文件找好以后,开始新建工程,STM32CubeMX生成,按照下面三部配置System Core里的东西

2.2外设 

记下来选配外设,模块用的iic,和用来打印的串口,

2.2.1 iic

用GPIO模拟,原因如下

看手册可以知道,f1系列的板子留给IIC的是 PB6 PB7,正点原子的mini板这两个引脚是没有上拉电阻的,所以用PC11 PC12,正点的板子给这两个引脚加了上拉电阻,CubeMX里只需要给这两个引脚选择开漏输出即可

补充,我另一块自研的板用了PB6 PB7这两个引脚,这里只要选择有上拉电阻的引脚即可,本来也不用硬件iic,两个引脚均为开漏输出

2.2.2 uart

用串口一,默认引脚

波特率根据需要自己设置 

外设就配置完成了,接下来就是给工程命名,还要选择MDK-ARM,这个才是keil工程

记得勾选分文件编程

最后点击generate code生成代码,然后来到自己选择的工程路径下,找到MDK-ARM文件夹,就能看到生成的工程代码了

三、移植 

下面开始移植 ,所谓移植,其实就是复制粘贴,把例程里的变成自己的

我习惯在工程里专门建一个文件夹,存放我所有自己写的代码,这里也是,在生成的工程文件的Driver里建了一个bsp

3.1 wit_c_sdk

wit_c_sdk里的文件都放进来,这三个文件不需要改动

因为我的工程不止这一个部分,所以按照我的需要改成了图里的这几个文件,下面介绍要改动的部分 

3.2  IOI2C.c和IOI2C.h

这两个文件需要一些改动,看头文件部分,包含了两个固件库头文件,会和CubeMX生成的HAL库冲突,所以我自己新建了一个iic.c和iic.h

 iic.h

例程里的stm32f10x.h会和HAL库冲突,后面编译会有一堆报错,所以注释掉,添加条件编译和#include \"main.h\",其余的直接复制就好,

这里有一点需要改动,把引脚改成自己需要的

改完代码如下

#ifndef _IIC_H_#define _IIC_H_#include \"main.h\"//#include \"stm32f10x.h\"#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<CRL&=0X0FFFFFFF;GPIOB->CRL|=0x80000000;}#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=0x30000000;} #define IIC_SCL PBout(6) //SCL#define IIC_SDA PBout(7) //SDA#define READ_SDA PBin(7) void IIC_Init(void);  void IIC_Start(void);void IIC_Stop(void);void IIC_Ack(void);void IIC_NAck(void);void IIC_Send_Byte(uint8_t txd);uint8_t IIC_Read_Byte(unsigned char ack);int32_t IICreadBytes(uint8_t dev, uint8_t reg, uint8_t *data, uint32_t length);int32_t IICwriteBytes(uint8_t dev, uint8_t reg, uint8_t* data, uint32_t length);#endif 

iic.c

包含自己的头文件,例程里的IIC_Init(void)关于GPIO初始化的东西全部注释掉,我们前面再CubeMX里已经选配过了,其他的直接复制即可,改完代码如下

#include \"iic.h\"void Delay(uint32_t count)//400KHzIIC{unsigned int uiCnt = count*8;while (uiCnt --);}void IIC_Init(void){//GPIO_InitTypeDef GPIO_InitStructure;// RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_11;//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//GPIO_Init(GPIOC, &GPIO_InitStructure);SDA_OUT(); IIC_SDA=1; IIC_SCL=1;}void IIC_Start(void){SDA_OUT(); IIC_SDA=1; IIC_SCL=1;Delay(5); IIC_SDA=0;//START:when CLK is high,DATA change form high to low Delay(5);IIC_SCL=0;}void IIC_Stop(void){SDA_OUT();IIC_SCL=0;IIC_SDA=0;//STOP:when CLK is high DATA change form low to high Delay(5);IIC_SCL=1; IIC_SDA=1;Delay(5); }uint8_t IIC_Wait_Ack(void){uint8_t ucErrTime=0; SDA_IN(); IIC_SDA=1;Delay(5); while(READ_SDA){ucErrTime++;if(ucErrTime>50){IIC_Stop();return 1;}Delay(5);} IIC_SCL=1;Delay(5); IIC_SCL=0;return 0; } void IIC_Ack(void){IIC_SCL=0;SDA_OUT();IIC_SDA=0;Delay(5);IIC_SCL=1;Delay(5);IIC_SCL=0;} void IIC_NAck(void){IIC_SCL=0;SDA_OUT();IIC_SDA=1;Delay(5);IIC_SCL=1;Delay(5);IIC_SCL=0;} void IIC_Send_Byte(uint8_t txd){ uint8_t t; SDA_OUT(); IIC_SCL=0; for(t=0;t>7; txd<<=1; Delay(2); IIC_SCL=1;Delay(5);IIC_SCL=0;Delay(3); } } uint8_t IIC_Read_Byte(unsigned char ack){unsigned char i,receive=0;SDA_IN(); for(i=0;i<8;i++ ){ IIC_SCL=0; Delay(5);IIC_SCL=1; receive<<=1; if(READ_SDA)receive++; Delay(5); } if (ack) IIC_Ack(); else IIC_NAck(); return receive;}int32_t IICreadBytes(uint8_t dev, uint8_t reg, uint8_t *data, uint32_t length){ uint32_t count = 0; IIC_Start(); IIC_Send_Byte(dev); if(IIC_Wait_Ack() == 1)return 0; IIC_Send_Byte(reg); if(IIC_Wait_Ack() == 1)return 0; IIC_Start(); IIC_Send_Byte(dev+1); if(IIC_Wait_Ack() == 1)return 0; for(count=0; count<length; count++) { if(count!=length-1)data[count]=IIC_Read_Byte(1); else data[count]=IIC_Read_Byte(0); } IIC_Stop(); return 1;}int32_t IICwriteBytes(uint8_t dev, uint8_t reg, uint8_t* data, uint32_t length){ uint32_t count = 0; IIC_Start(); IIC_Send_Byte(dev); if(IIC_Wait_Ack() == 1)return 0; IIC_Send_Byte(reg); if(IIC_Wait_Ack() == 1)return 0; for(count=0; count<length; count++) { IIC_Send_Byte(data[count]); if(IIC_Wait_Ack() == 1)return 0; } IIC_Stop(); return 1; }

3.3 main.c

因为我的主函数里有额外要做的事情,所以我把例程main.c里的部分单独分出来又做了一个jy61p.c和jy61p.h

jy61p.h

#ifndef _JY61P_H#define _JY61P_H#include \"stdio.h\"#include #include \"main.h\"#include \"usart.h\"#include \"gpio.h\"#include \"iic.h\"#include \"wit_c_sdk.h\"#define ACC_UPDATE0x01#define GYRO_UPDATE0x02#define ANGLE_UPDATE0x04#define MAG_UPDATE0x08#define READ_UPDATE0x80void ShowHelp(void);void CmdProcess(void);void AutoScanSensor(void);void CopeSensorData(uint32_t uiReg, uint32_t uiRegNum);void Delayms(uint16_t ucMs);void delay_ms(uint16_t Delay); #endif

jy61p.c 

例程mian.c的函数定义都复制到jy61p.c里,delay_ms会引起冲突,这个函数HAL库里的HAL_Delay作用一样,重新写下就行,复制HAL_Delay的代码到这个函数里

代码如下 

#include \"jy61p.h\"extern volatile char s_cDataUpdate;extern volatile char s_cCmd;extern int16_t fAcc[3], fGyro[3], fAngle[3];void CopeCmdData(unsigned char ucData){static unsigned char s_ucData[50], s_ucRxCnt = 0;s_ucData[s_ucRxCnt++] = ucData;if(s_ucRxCnt= 50) s_ucRxCnt = 0;if(s_ucRxCnt >= 3){if((s_ucData[1] == \'\\r\') && (s_ucData[2] == \'\\n\')){s_cCmd = s_ucData[0];memset(s_ucData,0,50);//s_ucRxCnt = 0;}else {s_ucData[0] = s_ucData[1];s_ucData[1] = s_ucData[2];s_ucRxCnt = 2;}}}void ShowHelp(void){printf(\"\\r\\n************************ WIT_SDK_DEMO************************\");printf(\"\\r\\n************************ HELP  ************************\\r\\n\");printf(\"UART SEND:a\\\\r\\\\n Acceleration calibration.\\r\\n\");printf(\"UART SEND:m\\\\r\\\\n Magnetic field calibration,After calibration send: e\\\\r\\\\n to indicate the end\\r\\n\");printf(\"UART SEND:U\\\\r\\\\n Bandwidth increase.\\r\\n\");printf(\"UART SEND:u\\\\r\\\\n Bandwidth reduction.\\r\\n\");printf(\"UART SEND:B\\\\r\\\\n Baud rate increased to 115200.\\r\\n\");printf(\"UART SEND:b\\\\r\\\\n Baud rate reduction to 9600.\\r\\n\");printf(\"UART SEND:h\\\\r\\\\n help.\\r\\n\");printf(\"******************************************************************************\\r\\n\");}void CmdProcess(void){switch(s_cCmd){case \'a\':if(WitStartAccCali() != WIT_HAL_OK) printf(\"\\r\\nSet AccCali Error\\r\\n\");break;case \'m\':if(WitStartMagCali() != WIT_HAL_OK) printf(\"\\r\\nStart MagCali Error\\r\\n\");break;case \'e\':if(WitStopMagCali() != WIT_HAL_OK) printf(\"\\r\\nEnd MagCali Error\\r\\n\");break;case \'u\':if(WitSetBandwidth(BANDWIDTH_5HZ) != WIT_HAL_OK) printf(\"\\r\\nSet Bandwidth Error\\r\\n\");break;case \'U\':if(WitSetBandwidth(BANDWIDTH_256HZ) != WIT_HAL_OK)printf(\"\\r\\nSet Bandwidth Error\\r\\n\");break;case \'B\':if(WitSetUartBaud(WIT_BAUD_115200) != WIT_HAL_OK) printf(\"\\r\\nSet Baud Error\\r\\n\");break;case \'b\':if(WitSetUartBaud(WIT_BAUD_9600) != WIT_HAL_OK) printf(\"\\r\\nSet Baud Error\\r\\n\");break;case \'h\':ShowHelp();break;default : return ;}s_cCmd = 0xff;}void CopeSensorData(uint32_t uiReg, uint32_t uiRegNum){int i; for(i = 0; i < uiRegNum; i++) { switch(uiReg) {// case AX:// case AY: case AZ:s_cDataUpdate |= ACC_UPDATE; break;// case GX:// case GY: case GZ:s_cDataUpdate |= GYRO_UPDATE; break;// case HX:// case HY: case HZ:s_cDataUpdate |= MAG_UPDATE; break;// case Roll:// case Pitch: case Yaw:s_cDataUpdate |= ANGLE_UPDATE; break; default:s_cDataUpdate |= READ_UPDATE;break; }uiReg++; }}void delay_ms(uint16_t Delay){ uint32_t tickstart = HAL_GetTick(); uint32_t wait = Delay; /* Add a freq to guarantee minimum wait */ if (wait < HAL_MAX_DELAY) { wait += (uint32_t)(uwTickFreq); } while ((HAL_GetTick() - tickstart) < wait) { }}void Delayms(uint16_t ucMs){delay_ms(ucMs);}void AutoScanSensor(void){int i, iRetry;for(i = 0; i < 0x7F; i++){WitInit(WIT_PROTOCOL_I2C, i);iRetry = 2;do{s_cDataUpdate = 0;WitReadReg(AX, 3);delay_ms(5);if(s_cDataUpdate != 0){//printf(\"find %02X addr sensor\\r\\n\", i);//ShowHelp();return ;}iRetry--;}while(iRetry);}printf(\"can not find sensor\\r\\n\");printf(\"please check your connection\\r\\n\");}

接下来是我们自己的main.c

需要定义两个变量

其余的复制过来就行

volatile char s_cDataUpdate = 0, s_cCmd = 0xff;/* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/void SystemClock_Config(void);/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*//* USER CODE BEGIN 0 *//* USER CODE END 0 *//** * @brief The application entry point. * @retval int */int main(void){ /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ printf(\"99\\n\");float fAcc[3], fGyro[3], fAngle[3];int i;IIC_Init();WitInit(WIT_PROTOCOL_I2C, 0x50);WitI2cFuncRegister(IICwriteBytes, IICreadBytes);WitRegisterCallBack(CopeSensorData);WitDelayMsRegister(Delayms);printf(\"\\r\\n********************** wit-motion IIC example ************************\\r\\n\");AutoScanSensor();//HAL_GPIO_WritePin(IO_GPIO_Port, IO_Pin, GPIO_PIN_SET); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */WitReadReg(AX, 12);delay_ms(500);CmdProcess();if(s_cDataUpdate){for(i = 0; i < 3; i++){fAcc[i] = sReg[AX+i] / 32768.0f * 16.0f;fGyro[i] = sReg[GX+i] / 32768.0f * 2000.0f;fAngle[i] = sReg[Roll+i] / 32768.0f * 180.0f;}if(s_cDataUpdate & ACC_UPDATE){printf(\"acc:%.3f %.3f %.3f\\r\\n\", fAcc[0], fAcc[1], fAcc[2]);s_cDataUpdate &= ~ACC_UPDATE;}if(s_cDataUpdate & GYRO_UPDATE){printf(\"gyro:%.3f %.3f %.3f\\r\\n\", fGyro[0], fGyro[1], fGyro[2]);s_cDataUpdate &= ~GYRO_UPDATE;}if(s_cDataUpdate & ANGLE_UPDATE){printf(\"angle:%.3f %.3f %.3f\\r\\n\", fAngle[0], fAngle[1], fAngle[2]);s_cDataUpdate &= ~ANGLE_UPDATE;}if(s_cDataUpdate & MAG_UPDATE){printf(\"mag:%d %d %d\\r\\n\", sReg[HX], sReg[HY], sReg[HZ]);s_cDataUpdate &= ~MAG_UPDATE;}} /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */}

然后编译,没报错 

连接串口,可以看到打印 

这样就移植完成了

四、优化

 我只需要这个芯片检测到的角度,其余的不要,也不需要一直在主函数里打印,所以还需要优化一下代码

float fAcc[3], fGyro[3], fAngle[3];

这三个数组分别存放三位加速度,三维角加速度,三位角度,我只需要最后一个

补充一下三个角的定义

出数据之后自己转动模块就可以确定fAngle[]的定义,我这里测的结果是  fAngle[0]=pitch,  fAngle[1]=yaw,   fAngle[2]=roll

4.1  imu_init 初始化

 初始化也挪到jy61p.c里,单独建一个函数,

 

同时main里也直接替换为imu_init()

4.2 get_angle 获取参数

这部分在while(1)里,因为我只需要角度,所以其他我直接删掉了,只留下了角度的if,然后同样也放到jy61p.c里,函数名随便取

jy61p.c 

优化完以后这个文件变成了

#include \"jy61p.h\"volatile char s_cDataUpdate = 0, s_cCmd = 0xff;float fAcc[3], fGyro[3], fAngle[3];extern int16_t pitch,yaw,roll;void CopeCmdData(unsigned char ucData){static unsigned char s_ucData[50], s_ucRxCnt = 0;s_ucData[s_ucRxCnt++] = ucData;if(s_ucRxCnt= 50) s_ucRxCnt = 0;if(s_ucRxCnt >= 3){if((s_ucData[1] == \'\\r\') && (s_ucData[2] == \'\\n\')){s_cCmd = s_ucData[0];memset(s_ucData,0,50);//s_ucRxCnt = 0;}else {s_ucData[0] = s_ucData[1];s_ucData[1] = s_ucData[2];s_ucRxCnt = 2;}}}void ShowHelp(void){printf(\"\\r\\n************************ WIT_SDK_DEMO************************\");printf(\"\\r\\n************************ HELP  ************************\\r\\n\");printf(\"UART SEND:a\\\\r\\\\n Acceleration calibration.\\r\\n\");printf(\"UART SEND:m\\\\r\\\\n Magnetic field calibration,After calibration send: e\\\\r\\\\n to indicate the end\\r\\n\");printf(\"UART SEND:U\\\\r\\\\n Bandwidth increase.\\r\\n\");printf(\"UART SEND:u\\\\r\\\\n Bandwidth reduction.\\r\\n\");printf(\"UART SEND:B\\\\r\\\\n Baud rate increased to 115200.\\r\\n\");printf(\"UART SEND:b\\\\r\\\\n Baud rate reduction to 9600.\\r\\n\");printf(\"UART SEND:h\\\\r\\\\n help.\\r\\n\");printf(\"******************************************************************************\\r\\n\");}void CmdProcess(void){switch(s_cCmd){case \'a\':if(WitStartAccCali() != WIT_HAL_OK) printf(\"\\r\\nSet AccCali Error\\r\\n\");break;case \'m\':if(WitStartMagCali() != WIT_HAL_OK) printf(\"\\r\\nStart MagCali Error\\r\\n\");break;case \'e\':if(WitStopMagCali() != WIT_HAL_OK) printf(\"\\r\\nEnd MagCali Error\\r\\n\");break;case \'u\':if(WitSetBandwidth(BANDWIDTH_5HZ) != WIT_HAL_OK) printf(\"\\r\\nSet Bandwidth Error\\r\\n\");break;case \'U\':if(WitSetBandwidth(BANDWIDTH_256HZ) != WIT_HAL_OK)printf(\"\\r\\nSet Bandwidth Error\\r\\n\");break;case \'B\':if(WitSetUartBaud(WIT_BAUD_115200) != WIT_HAL_OK) printf(\"\\r\\nSet Baud Error\\r\\n\");break;case \'b\':if(WitSetUartBaud(WIT_BAUD_9600) != WIT_HAL_OK) printf(\"\\r\\nSet Baud Error\\r\\n\");break;case \'h\':ShowHelp();break;default : return ;}s_cCmd = 0xff;}void CopeSensorData(uint32_t uiReg, uint32_t uiRegNum){int i; for(i = 0; i < uiRegNum; i++) { switch(uiReg) {// case AX:// case AY: case AZ:s_cDataUpdate |= ACC_UPDATE; break;// case GX:// case GY: case GZ:s_cDataUpdate |= GYRO_UPDATE; break;// case HX:// case HY: case HZ:s_cDataUpdate |= MAG_UPDATE; break;// case Roll:// case Pitch: case Yaw:s_cDataUpdate |= ANGLE_UPDATE; break; default:s_cDataUpdate |= READ_UPDATE;break; }uiReg++; }}void delay_ms(uint16_t Delay){ uint32_t tickstart = HAL_GetTick(); uint32_t wait = Delay; /* Add a freq to guarantee minimum wait */ if (wait < HAL_MAX_DELAY) { wait += (uint32_t)(uwTickFreq); } while ((HAL_GetTick() - tickstart) < wait) { }}void Delayms(uint16_t ucMs){delay_ms(ucMs);}void AutoScanSensor(void){int i, iRetry;for(i = 0; i < 0x7F; i++){WitInit(WIT_PROTOCOL_I2C, i);iRetry = 2;do{s_cDataUpdate = 0;WitReadReg(AX, 3);delay_ms(5);if(s_cDataUpdate != 0){//printf(\"find %02X addr sensor\\r\\n\", i);//ShowHelp();return ;}iRetry--;}while(iRetry);}printf(\"can not find sensor\\r\\n\");printf(\"please check your connection\\r\\n\");}void imu_init(void){IIC_Init();WitInit(WIT_PROTOCOL_I2C, 0x50);WitI2cFuncRegister(IICwriteBytes, IICreadBytes);WitRegisterCallBack(CopeSensorData);WitDelayMsRegister(Delayms);AutoScanSensor();}void get_angle(void){int i;WitReadReg(AX, 12);delay_ms(500);CmdProcess();if(s_cDataUpdate){for(i = 0; i < 3; i++){fAcc[i] = sReg[AX+i] / 32768.0f * 16.0f;fGyro[i] = sReg[GX+i] / 32768.0f * 2000.0f;fAngle[i] = sReg[Roll+i] / 32768.0f * 180.0f;}if(s_cDataUpdate & ANGLE_UPDATE){printf(\"angle:%.3f %.3f %.3f\\r\\n\", fAngle[0], fAngle[1], fAngle[2]);s_cDataUpdate &= ~ANGLE_UPDATE;}}pitch=fAngle[0] *1000; yaw=fAngle[1] *1000;roll=fAngle[2] *1000;}

jy61p.h

#ifndef _JY61P_H#define _JY61P_H#include \"stdio.h\"#include #include \"main.h\"#include \"adc.h\"#include \"dma.h\"#include \"tim.h\"#include \"usart.h\"#include \"gpio.h\"#include \"iic.h\"#include \"wit_c_sdk.h\"#include \"bsp_uart.h\"#include \"bsp_adc.h\"#define ACC_UPDATE0x01#define GYRO_UPDATE0x02#define ANGLE_UPDATE0x04#define MAG_UPDATE0x08#define READ_UPDATE0x80void ShowHelp(void);void CmdProcess(void);void AutoScanSensor(void);void CopeSensorData(uint32_t uiReg, uint32_t uiRegNum);void Delayms(uint16_t ucMs);void delay_ms(uint16_t Delay);void imu_init(void);void get_angle(void);#endif

 main则变成这样

另外我又把volatile char s_cDataUpdate = 0, s_cCmd = 0xff;
                   float fAcc[3], fGyro[3], fAngle[3];也挪jy61p里了

并且我还在main定义了全局变量pitch,yaw,roll用来在接角度,这样就不用一直打印,直接在main里获取到了角度

以上移植完成