完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
2个回答
|
|
快过年了,在家闲来无事,学习研究一下I2C。
第一天 手上有啥资源可利用的? 1.一块MPU6050 2.一块stm32f103rc最小系统版,上面移植了ucos3,跑了几个任务,包括一个LCD任务(优先级最低) 3.st的固件库 4.ALIENTEK的MPU6050例程 5.stm32各种数据手册文档,ucos各种资料 好,开工。 先阅读stm32数据手册了解研究I2C模块原理架构,懵懵懂懂,若有所知的样子,行了不看了。 然后看看ALIENTEK的MPU6050例程。咦?怎么是用软件模拟I2C?stm32集成了I2C控制器都不用,这么浪费,不看了。。。还是再看一下,看看MPU6050大致是怎么操作的。大概就是有个设备地址0x68,有很多寄存器,数据通信通的就是寄存器的数据,每次传输数据都是先发设备地址0x68,然后发寄存器地址,最后收/发数据。 然后复制ALIENTEK例程中操作MPU6050的代码,改写里面所有的函数,用库函数操作STM32硬件I2C重新编制I2C的通信流程。打码完成,测试一下,果然不行。用fprintf打印信息到串口看看吧(没调试器,穷得只剩串口板)。然后开始艰辛的探索(省略一万字)。。。。。。 经过串口打印信息、查阅资料、网上搜索,终于成功改写ALIENTEK例程中的MPU_Write_Byte(INT08U reg, INT08U data) 函数如下, INT08U MPU_Write_Byte(INT08U reg, INT08U data) { while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, (MPU6050_ADDR<<1), I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, reg); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_SendData(I2C1, data); I2C_GenerateSTOP(I2C1, ENABLE); return 1; } 遇到的主要问题有: 1.设备地址0x68要左移一位,然后后面补上读写位,读是1,写是0。注意,不是0x68+1(或+0),而是(0x68<<1)|0x01(或0x00),也就是0xd1是从MPU6050读数据,发0xd0是向MPU6050写数据。 2.**严格按照st的例程给出的通信流程来编码。**网上各种说stm32硬件I2C有问题,好吧可能是有点问题,但是对于我等渣渣是完全不会遇到的,有问题都是自己的问题。严格按照st的例程来,绝对没问题。不要自己想当然的去编码,也不要觉得自己写的代码跟st的例程中的代码是等效的就坚信自己是对的。我参考的是st的EEPROM的那个例程。 然后改写了MPU_Read_Byte(INT08U reg)函数,OK。 就差MPU_Read_Len(INT08U addr, INT08U reg, INT08U len, INT08U *buf)这条函数就可以读MPU6050的数据了。改完,测试一下,果然不行。果断每行代码后面插一条fprintf。测试一下,电脑蓝屏。重启,测试一下,电脑蓝屏。。。OMG!!!唉12点了躺床吃鸡睡觉。 第二天 开电脑,测试一下,电脑蓝屏。。。唉还搞什么,放弃吧。。。去掉fprintf看看,不蓝屏,可是我怎么调试程序能?(没调试器,只能靠串口板打印信息)网上搜索一下。。。苦思冥想。。。(省略一万字) 终于,改好了!!!代码如下, INT08U MPU_Read_Len(INT08U addr, INT08U reg, INT08U len, INT08U *buf) { INT08U rlen = 0; CPU_SR_ALLOC(); while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, (addr<<1), I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, reg); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTOP(I2C1, ENABLE); while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, (addr<<1), I2C_Direction_Receiver); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); I2C_AcknowledgeConfig(I2C1, ENABLE); GPIO_SetBits(GPIOB,GPIO_Pin_7); OS_CRITICAL_ENTER(); for(rlen = 0; rlen < len; rlen++) { if(len == 1+rlen) { I2C_AcknowledgeConfig(I2C1, DISABLE); (void)I2C1->SR2; I2C_GenerateSTOP(I2C1, ENABLE); } while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); *buf = I2C_ReceiveData(I2C1); buf++; } OS_CRITICAL_EXIT(); return rlen; } 遇到的主要问题是: 1.接收数据的时候SDA要输出高电平!!!(3个感叹号代表重要的事情说3遍)stm32的SDA脚已选择了GPIO_Mode_AF_OD模拟开漏输出,MPU6050模块的SDA已外接了上拉电阻。如果stm32的SDA输出低电平,那么SDA就拉不高了。 2.网上看到有种做法是 I2C_AcknowledgeConfig(I2C1, DISABLE); (void)I2C1->SR2; I2C_GenerateSTOP(I2C1, ENABLE); 这3句要连着写,一是必须读SR2寄存器才能完成某些清寄存器标志的操作(看数据手册),这确实是是stm32做的坑,但不能说它有问题,反正正确流程是怎样就怎样,按正确流程来编码。 3.接收数据阶段我就用OS_CRITICAL_ENTER();关中断好了,保证I2C时序正确,保险一点。(其实这样理解也不对,应该整个I2C通信时序都有因为被打断而发生故障的风险,关键是在哪些地方可以被打断(如等待某些标志位置位的时候),哪些地方一定不能被打断(如等到了某事件后要读写寄存器时)) 测试一下,串口打印出加速度、陀螺仪、温度等数据,OK,收工。 咦?怎么LCD显示一卡一卡的。看来I2C读数据太耗时了,还阻止了任务切换。改写一下MPU_Read_Len(INT08U addr, INT08U reg, INT08U len, INT08U *buf)函数吧。把它里面的等待标志位置位的那些while循环,改为pend一个信号量,标志位置位触发中断后再post一个信号量,等待交给中断来做,期间把CPU让给其他任务。 编码编码编码。。。又到12点了,躺床吃鸡睡觉。 第三天 一觉睡到中午饭,下午继续躺床吃鸡睡觉。晚饭后才编码完成,代码如下, INT08U MPU6050_Read(INT08U addr, INT08U reg, INT08U len, INT08U *buf) { OS_ERR err; CPU_TS ts; CPU_SR_ALLOC(); INT08U rlen = 0; INT16U I2CTimeout; I2CReadState = I2C_CHECK_BUSY; I2C_ITConfig(I2C1, I2C_IT_ERR, ENABLE); while(I2C_READ_END > I2CReadState) { switch(I2CReadState) { case I2C_CHECK_BUSY: I2CTimeout = I2C_TIMEOUT*((INT16U)(SystemCoreClock/1000000)); while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)&&(I2CTimeout--)); if(0 == I2CTimeout) delay_ms(fac_ms); else { CPU_CRITICAL_ENTER(); if(I2C_CHECK_BUSY == I2CReadState) I2CReadState = I2C_SEND_START;//I2C bus is not busy else I2CReadState = I2C_READ_END; CPU_CRITICAL_EXIT(); } break; case I2C_SEND_START: I2C_GenerateSTART(I2C1, ENABLE); I2C_ITConfig(I2C1, I2C_IT_EVT, ENABLE); OSSemPend(&MPU6050_SEM, I2C_TIMEOUT*fac_us, OS_OPT_PEND_BLOCKING, &ts, &err); CPU_CRITICAL_ENTER(); if((OS_ERR_NONE == err) && (I2C_SEND_START == I2CReadState)) I2CReadState = I2C_SEND_ADDR_SEND; else I2CReadState = I2C_READ_END; CPU_CRITICAL_EXIT(); break; case I2C_SEND_ADDR_SEND: I2C_Send7bitAddress(I2C1, (addr<<1), I2C_Direction_Transmitter); I2C_ITConfig(I2C1, I2C_IT_EVT, ENABLE); OSSemPend(&MPU6050_SEM, I2C_TIMEOUT*fac_us, OS_OPT_PEND_BLOCKING, &ts, &err); CPU_CRITICAL_ENTER(); if((OS_ERR_NONE == err) && (I2C_SEND_ADDR_SEND == I2CReadState)) I2CReadState = I2C_SELECT_REG; else I2CReadState = I2C_READ_END; CPU_CRITICAL_EXIT(); break; case I2C_SELECT_REG: I2C_SendData(I2C1, reg); I2C_ITConfig(I2C1, I2C_IT_EVT, ENABLE); OSSemPend(&MPU6050_SEM, I2C_TIMEOUT*fac_us, OS_OPT_PEND_BLOCKING, &ts, &err); CPU_CRITICAL_ENTER(); if((OS_ERR_NONE == err) && (I2C_SELECT_REG == I2CReadState)) I2CReadState = I2C_CHECK_BUSY_AGAIN; else I2CReadState = I2C_READ_END; CPU_CRITICAL_EXIT(); I2C_GenerateSTOP(I2C1, ENABLE); break; case I2C_CHECK_BUSY_AGAIN: I2CTimeout = I2C_TIMEOUT*((INT16U)(SystemCoreClock/1000000)); while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)&&(I2CTimeout--)); if(0 == I2CTimeout) delay_ms(fac_ms); else { CPU_CRITICAL_ENTER(); if(I2C_CHECK_BUSY_AGAIN == I2CReadState) I2CReadState = I2C_SEND_START_AGAIN;//I2C bus is not busy else I2CReadState = I2C_READ_END; CPU_CRITICAL_EXIT(); } break; case I2C_SEND_START_AGAIN: I2C_GenerateSTART(I2C1, ENABLE); I2C_ITConfig(I2C1, I2C_IT_EVT, ENABLE); OSSemPend(&MPU6050_SEM, I2C_TIMEOUT*fac_us, OS_OPT_PEND_BLOCKING, &ts, &err); CPU_CRITICAL_ENTER(); if((OS_ERR_NONE == err) && (I2C_SEND_START_AGAIN == I2CReadState)) I2CReadState = I2C_SEND_ADDR_RECV; else I2CReadState = I2C_READ_END; CPU_CRITICAL_EXIT(); break; case I2C_SEND_ADDR_RECV: I2C_Send7bitAddress(I2C1, (addr<<1), I2C_Direction_Receiver); I2C_ITConfig(I2C1, I2C_IT_EVT, ENABLE); OSSemPend(&MPU6050_SEM, I2C_TIMEOUT*fac_us, OS_OPT_PEND_BLOCKING, &ts, &err); CPU_CRITICAL_ENTER(); if((OS_ERR_NONE == err) && (I2C_SEND_ADDR_RECV == I2CReadState)) I2CReadState = I2C_RECV_DATA; else I2CReadState = I2C_READ_END; CPU_CRITICAL_EXIT(); I2C_AcknowledgeConfig(I2C1, ENABLE); GPIO_SetBits(GPIOB,GPIO_Pin_7); break; case I2C_RECV_DATA: if(len == 1+rlen) { I2C_AcknowledgeConfig(I2C1, DISABLE); (void)I2C1->SR2; I2C_GenerateSTOP(I2C1, ENABLE); } I2C_ITConfig(I2C1, I2C_IT_EVT, ENABLE); OSSemPend(&MPU6050_SEM, I2C_TIMEOUT*fac_us, OS_OPT_PEND_BLOCKING, &ts, &err); CPU_CRITICAL_ENTER(); if((OS_ERR_NONE != err) || (I2C_RECV_DATA != I2CReadState)) I2CReadState = I2C_READ_END; CPU_CRITICAL_EXIT(); *buf = I2C_ReceiveData(I2C1); buf++; rlen++; if(len == rlen) I2CReadState = I2C_READ_END; break; default: break; } } I2C_ITConfig(I2C1, I2C_IT_ERR, DISABLE); return rlen; } 里面的delay_ms函数在延时时间超过OS时间片时间时可以引起任务切换。 测试一下,OK,串口输出数据,收工。 咦?怎么LCD卡住完全了。研究一下。原来如此: 我获取MPU6050数据的任务代码如下, while(1) { while(0 < sendmpudata) { sendmpudata–; MPU_Get_Accelerometer(&aacx,&aacy,&aacz); fprintf(UART_OUT, "ax:%5d, ay:%5d, az:%5d ", aacx, aacy, aacz); res=MPU_Get_Gyroscope(&gyrox,&gyroy,&gyroz); fprintf(UART_OUT, "gx:%5d, gy:%5d, gz:%5d ", gyrox, gyroy, gyroz); temp=MPU_Get_Temperature(); fprintf(UART_OUT, “temp:%5dnn”, temp); } } |
|
|
|
sendmpudata是在OS的定时器中递增的,用来定时一段时间就从MPU6050收数据然后发到串口上。问题是当sendmpudata=0时也就是还没到时间去收发MPU6050数据时,当前任务还在while(1)中跑,没有把CPU让给低优先级的LCD任务。于是我在while(0 < sendmpudata)前面插一条pend等待一个信号量。测试一下,LCD不卡,OK,收工。
不是说I2C通信流程要严格按照st的例程,不要打断吗?其实,当你理解这整个流程,就会很容易地把握哪些地方可以被打断,切换到其它任务,让I2C真正地融入多任务系统中。 突然发现,干嘛要搞那么复杂!!!耗时的步骤都交给中断来做,剩下在任务级完成的步骤不就是读写一下DR寄存器吗?把剩下这些极其简单的步骤也交给中断处理函数来做也可以啊,那任务里面就只需pend等待一个信号量,等中断处理函数处理完I2C通信流程post释放出一个信号量,任务级读写I2C数据函数就可以返回了。再改改代码。。。又到12点了,躺床吃鸡睡觉。 第四天 廿二八洗邋遢,晚上继续改代码。改完如下, INT08U I2C_Process(INT08U direction, INT08U addr, INT08U len, INT08U *pbuf) { OS_ERR err; CPU_TS ts; INT08U res; INT16U I2CTimeout; I2CDirection = direction; I2CStage = I2C_DETECT_START; I2CError = I2C_SUCCESS; I2CAddress = addr; I2CLength = len; pI2CBuffer = pbuf; I2CIndex = 0; I2CTimeout = I2C_TIMEOUT*((INT16U)(SystemCoreClock/1000000)); while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)&&(I2CTimeout--)); if(0 < I2CTimeout) { I2C_GenerateSTART(I2C1, ENABLE); I2C_ITConfig(I2C1, I2C_IT_EVT|I2C_IT_ERR, ENABLE); OSSemPend(&I2C_SEM, I2C_TIMEOUT*fac_us, OS_OPT_PEND_BLOCKING, &ts, &err); I2C_ITConfig(I2C1, I2C_IT_EVT|I2C_IT_ERR, DISABLE); if(OS_ERR_NONE == err) res = I2C_SUCCESS; else if(OS_ERR_TIMEOUT == err) { if(OS_ERR_NONE == I2CError) res = I2C_OS_TIMEOUT; else res = I2CError; } else res = I2C_OS_ERROR; } else res = I2C_HW_BUSY; return res; } static INT08U MPU6050_Write(INT08U addr, INT08U reg, INT08U len, INT08U *pbuf) { INT08U buf[MPU6050_WRITE_MAX], i, res; buf[0] = reg; for(i = 0; i < len; i++) buf[i+1] = pbuf; res = I2C_Process(I2C_SEND, addr, len+1, buf); return res; } static INT08U MPU6050_Read(INT08U addr, INT08U reg, INT08U len, INT08U *pbuf) { INT08U res; res = I2C_Process(I2C_SEND, addr, 1, ®); if(I2C_SUCCESS == res) res = I2C_Process(I2C_RECV, addr, len, pbuf); return res; } void I2C1_EV_IRQHandler(void) { OS_ERR err; CPU_SR_ALLOC(); CPU_CRITICAL_ENTER(); OSIntEnter(); CPU_CRITICAL_EXIT(); switch(I2CStage) { case I2C_DETECT_START: if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)) { if(I2C_SEND == I2CDirection) I2C_Send7bitAddress(I2C1, (I2CAddress<<1), I2C_Direction_Transmitter); else I2C_Send7bitAddress(I2C1, (I2CAddress<<1), I2C_Direction_Receiver); I2CStage = I2C_DETECT_ADDR; } break; case I2C_DETECT_ADDR: if(I2C_SEND == I2CDirection) { if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { I2C_SendData(I2C1, *pI2CBuffer); pI2CBuffer++; I2CIndex++; if(I2CLength > I2CIndex) I2CStage = I2C_DETECT_DATA; else { I2C_GenerateSTOP(I2C1, ENABLE); OSSemPost(&I2C_SEM, OS_OPT_POST_1, &err); } } } else { if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) { if(I2CLength > I2CIndex+1) I2C_AcknowledgeConfig(I2C1, ENABLE); else { I2C_AcknowledgeConfig(I2C1, DISABLE); (void)I2C1->SR2; I2C_GenerateSTOP(I2C1, ENABLE); } GPIO_SetBits(GPIOB,GPIO_Pin_7); I2CStage = I2C_DETECT_DATA; } } break; case I2C_DETECT_DATA: if(I2C_SEND == I2CDirection) { if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { I2C_SendData(I2C1, *pI2CBuffer); pI2CBuffer++; I2CIndex++; if(I2CLength <= I2CIndex) { I2C_GenerateSTOP(I2C1, ENABLE); OSSemPost(&I2C_SEM, OS_OPT_POST_1, &err); } } } else { if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)) { *pI2CBuffer = I2C_ReceiveData(I2C1); pI2CBuffer++; I2CIndex++; if(I2CLength == I2CIndex+1) { I2C_AcknowledgeConfig(I2C1, DISABLE); (void)I2C1->SR2; I2C_GenerateSTOP(I2C1, ENABLE); } else if(I2CLength <= I2CIndex) OSSemPost(&I2C_SEM, OS_OPT_POST_1, &err); else{} } } break; default: break; } OSIntExit(); } void I2C1_ER_IRQHandler(void) { if(SET == I2C_GetITStatus(I2C1, I2C_IT_BERR)) I2CError = I2C_HW_BERR; else if(SET == I2C_GetITStatus(I2C1, I2C_IT_ARLO)) I2CError = I2C_HW_ARLO; else if(SET == I2C_GetITStatus(I2C1, I2C_IT_AF)) I2CError = I2C_HW_AF; else if(SET == I2C_GetITStatus(I2C1, I2C_IT_OVR)) I2CError = I2C_HW_OVR; else if(SET == I2C_GetITStatus(I2C1, I2C_IT_PECERR)) I2CError = I2C_HW_PECERR; else if(SET == I2C_GetITStatus(I2C1, I2C_IT_TIMEOUT)) I2CError = I2C_HW_TIMEOUT; else if(SET == I2C_GetITStatus(I2C1, I2C_IT_SMBALERT)) I2CError = I2C_HW_SMBALERT; else{} I2C_ClearITPendingBit(I2C1, I2C_IT_SMBALERT|I2C_IT_TIMEOUT|I2C_IT_PECERR|I2C_IT_OVR|I2C_IT_AF|I2C_IT_ARLO|I2C_IT_BERR); I2C_ITConfig(I2C1, I2C_IT_ERR, DISABLE); } 上面代码就是最终版本。主要思想是,STM32硬件I2C的读写数据流程大致相同,封装成一个函数,通过传入参数来区分读写,这是I2C层。向MPU6050写数据就是写寄存器地址、写数据。从MPU6050读数据就是写寄存器地址、读数据,分别封装成MPU6050读、写两个函数,供操作MPU6050的其他函数调用,这是MPU6050模块层。任务级调用操作MPU6050的函数,获取想要的数据,这是APP层。 效果如下, 收工。写写博客。继续躺床吃鸡睡觉。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1792 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1626 浏览 1 评论
1094 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
732 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1682 浏览 2 评论
1943浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
740浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
577浏览 3评论
600浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
562浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-26 02:36 , Processed in 0.769443 second(s), Total 50, Slave 44 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号