|
这两个星期都在调试stm32f4的硬件i2c,在这过程中遇到了各种蛋疼的问题,经过参考大量的例程和资料,今天终于把硬件的I2C调通了,我使用定时器3开10ms的中断,然后再10ms的中断里读取GY86的MPU6050和HMC5883L的数据,再经经过4元数法算出3个角。有人说stm32的I2C用起来很难,但其实也不是很难的,我就是直接用官方的库函数实现的。下面来直入主题。 硬件平台: UNCLEO-F401RE: A# i6 t6 Y8 F 首先是I2C的IO和I2C的配置的配置,这个至关重要,一旦初始化不正确,I2C就会出现各种状况。 下面贴上代码: void I2C_Congiguration(void)) v5 J1 Z6 h# R3 X& S {2 k: X! }) J/ z# _ /****IO口的配置*******/ GPIO_InitTypeDef GPIO_InitStructure; I2C_InitTypeDef I2C_InitStructure; # n( G- H! n. ? RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOB,ENABLE);) X) u+ A% `9 ^. ?- o! O 6 ?' I* h8 v6 r; B. G RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);7 G( u! n4 ^! n' G4 f ?/ T3 U% h ! S0 f; p& @' c, @* D5 H. w3 a) } GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1); GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1); \) v5 b# g" l3 r4 H: v" y! @ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //GPIO_Pin_6 | GPIO_Pin_7, h; R5 Q6 e5 a( r, t; j8 n1 A GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;, o3 G, K L% { I; f GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;2 |4 \9 h7 d C6 E3 O GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOB, &GPIO_InitStructure); % m+ k* y" G/ L% J# ?' @; @ /* I2C1 的配置 */ I2C_DeInit(I2C1);6 e" C) j9 |# x3 R' U I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; // I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // I2C_InitStructure.I2C_OwnAddress1 = 0xd0;) v8 z* i( A9 o* B4 p5 v% r I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;( e: j6 C) Q* M- O5 M J+ l$ f( X' g I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed = I2C_Speed; 0 l2 Z% s3 t4 t6 y j" X8 q /*使能 I2C1 */ I2C_Cmd(I2C1, ENABLE); 3 \2 c3 N3 C) g, e2 ~5 _ /* I2C1 初始化*/ I2C_Init(I2C1, &I2C_InitStructure);6 A) ]" G3 {2 Y % t! ]7 h5 W# d6 C- r 允许1字节1应答模式*/, d. W; ?; |8 S. p6 W I2C_AcknowledgeConfig(I2C1, ENABLE); 3 q) A6 H, j+ M7 c( m } / Q2 ?. r, J% R; E+ T0 `! R, [- L }" s8 R" O$ u Z! s1 w* Q# ? ) C8 O+ \; w( o+ E- q) o3 Z* {, n 在用stm32F4的库函数前,我要补充一下stm32F1与stm32F4的区别。 第一个,在F 1 里边你可以使用这样的方式配置时钟复用 R C C _ A P B 2 P e r i p h C l o c k C m d ( R C C _ A P B 2 P e r i p h _ G P I O A | R C C _ A P B 2 P e r i p h _ G P I O B | R C C _ A P B 2 P e r i p h _ A F I O | R C C _ A P B 2 P e r i p h _ U S A R T 1 , E N A B L E ) ; / /+ F% \( m8 }4 I& r: v9 h0 T 打开复用时, A+ o' p. ?' L8 e/ p! q0 @ 但是。。。在F 4 里边这个就不管用了。因为F 4 的变了,以前的那些变量不再爱你了。库里边根本就没有了这个变量R C C _ A P B 2 P e r i p h _ A F I O 第二,G P I O 的输入输出模式也变了。这样的用法:G P I O _ I n i t S t r u c t u r e . G P I O _ M o d e = G P I O _ M o d e _ I N _ F L O A T I N G ; 。已经不行了。原因同上, G P I O _ M o d e _ I N _ F L O A T I N G 这种模式已经不存在了。 在F 4 的库里边只有这个模式:G P I O _ M o d e _ A F 。所有使用复用功能的引脚都应设置成这种模式。 这样比F 1 方便多了。你不用考虑这个引脚应该设置成浮空输入还是推挽输出. . . . 因为引脚设置错误基本可以消失。 Y5 O9 q- B! Q } 引脚的复用功能要用GPIO_PinAFConfig();这个函数了。配置完这些后,就是I2C的协议程序了。代码我就不贴上了,大家就下载附件来看吧。2 @9 ]6 \9 N6 y7 ~! w/ c% Z( L. H 然后就是定时器的配置;代码如下:+ E$ J7 \+ j4 W void TIM_Init()4 x! v$ C; r3 D+ Q: F { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //Tim_Nvic_Init(); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // TIM3 clock enable H; j8 N r# Q1 t2 I9 Q 7 d6 H1 T: v( S! n+ n6 `& k& x TIM_TimeBaseStructure.TIM_Period = 99; TIM_TimeBaseStructure.TIM_Prescaler = 7199;8 `3 u! `& S( i, L. |" I TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); * i1 o6 c, u0 B TIM_ITConfig(TIM3,TIM_IT_Update, ENABLE); /* TIM3 enable counter */6 n P6 w7 Y% D4 { y1 v( X4 \ TIM_Cmd(TIM3, ENABLE); TIM_ClearFlag(TIM3,TIM_FLAG_Update); ( K# b3 l& \6 V" P! s6 l: F }8 N- u, H1 M/ ~, W6 S7 T) ` 我这个板子使用外部晶振的,我用的是12M,经过PLL后得出72M的时钟频率。# C- A7 d1 M3 l* z( p void Sysclock_Init() { RCC->CR |= ((uint32_t)RCC_CR_HSEON); /* 使能HSE *// Q9 h! C! [8 s" V' ~+ ^7 |9 w while ((RCC->CR & RCC_CR_HSERDY) == 0); /* 等待HSE */ 4 l4 l; Q9 [# O v2 c( Z0 \- d6 U9 A; { RCC->CFGR = RCC_CFGR_SW_HSE; % |; s4 |- m$ b* L while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSE); FLASH->ACR = FLASH_ACR_PRFTEN; /* Enable Prefetch Buffer */9 L6 q! d1 v* u8 ` FLASH->ACR |= FLASH_ACR_ICEN; /* Instruction cache enable */ FLASH->ACR |= FLASH_ACR_DCEN; /* Data cache enable */5 l: ?3 R: Z& b8 U FLASH->ACR |= FLASH_ACR_LATENCY_5WS; RCC->CFGR |= RCC_CFGR_HPRE_DIV1; /* HCLK = SYSCLK */ RCC->CFGR |= RCC_CFGR_PPRE1_DIV1; /* APB1 = HCLK */ RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; * D1 i T6 ^8 P P2 _! g$ R+ C 1 ]. a- @; u2 s/ G7 S RCC->CR &= ~RCC_CR_PLLON; RCC-> LLCFGR = ( 20ul | /* PLL_M = 20 */(240ul CFGR |= RCC_CFGR_SW_PLL; O0 r6 B4 s1 Z( X4 X ! i# E9 X# D! m) a9 I% F6 G while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); 0 U$ m' n2 x8 X: |8 f) U: F }; L) U5 \7 g2 o0 f( a 关于系统时钟的设置可以参考stm32f401参考手册第99页。 计算的公式是 : SystemCoreClock = (HSx*(PLL_N/PLL_M))/PLL_P;: R( R4 R( N) ], e. \! r$ ~4 ~) o 如下图:
0 v& W7 |2 W9 t3 N. W& x" H HSx可以选择外部高速晶振HSE和内部高速震荡器HSI,注意内部的高速震荡器是16M的,这个是经过本人用定时器开PWM算出来是正确的,注意系统时钟不能设得太高,否则就会起不了振的。 顺便说一下PLL_Q是用来给USB等外设的。 这一PLL_N和PLL_M,PLL_Q是有范围的,如手册就表明了范围了。: j# `4 |: p! d1 F2 L$ P# ?0 o
/ e8 _3 k. b0 j1 T w% ]3 U 如果你没有外部晶振,就把上面的HSE全都替换成HSI ,这是你就要注意是按16M来算的了。如果你用多少M的外部晶振,要修改头文件里的HSE_VALUE的数值,头文件是默认25M的,我外部是12M的,所以就改成#define HSE_VALUE ((uint32_t)12000000); X- Y+ I$ Y( j$ B \
7 b; y& h) E: G# _# G/ ~ 说了这么多,现在说回时钟的配置吧。 设置完时钟后,就要设置中断的优先级了。( A, F: v) o/ h J6 A void Tim_Nvic_Init(void)) P- o. y$ |% Y3 l( c { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); C f1 C$ f: B /* Enable the TIM3 gloabal Interrupt */; |7 ^9 z3 V. I6 t \! v% D NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;4 k. ~1 g6 K, r' C% `3 @% [7 u NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;" S3 n8 g7 c3 g% P7 P: `8 f NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); b4 L6 V% A! t( b }% H4 [/ N% z5 Y6 `, C& v 当中断多的时候,这里就有很大的学问了,想了解的可以上去百度或google一下。我这里就不多说了。/ H1 \' O. W. G6 S 中断的里的程序:. z! F9 T7 U/ T: t void TIM3_IRQHandler(void) {3 q8 ]+ ~. H$ ]# M4 S. G if(TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM3,TIM_IT_Update); Read_Filter_MPU6050(); //Read_HMC5883L();, z+ d! ~: L1 G Get_Attitude();. [- V m& N+ _7 [5 A //ACC_ANGLE(); " I5 h! x, u5 [7 O, |, R } } 关键的主程序部分了 int main()4 Z; P `5 P! i {- n( _4 F$ j h1 D' o 4 W5 V: k7 F4 K% a- c. w3 l& G 0 z( o$ N( i" M# ` j( q; p; {* b uint8_t counter;) }/ a: f: U' J4 } short value; Sysclock_Init(); Tim_Nvic_Init(); delay_init(72);1 W; U$ j5 I: j9 c I2C_Congiguration();0 P' ]" v+ a; E0 F# w MPU6050_Init(); HMC5883L_Init(); ! Q( ~& U8 C+ c1 K TIM_Init();! h, Z# b9 k& D while (1); O7 r9 w I% ~2 v9 S1 l) O+ a {: T, j# w, B- k- Z4 L }$ L6 K1 I" V: `2 F& I- [! r ( C$ ~/ f) x% H0 H' l }+ `' V, {0 |# ~! Q9 T3 Y- u 注意时钟的配置要放到最后,否则会影响I2C会卡死的 现在已经运行了3个小时一起良好:# W: G, t% E) C 这是我用示波器下看的I2C时序9 c+ F6 E- j4 i) M. p
& |; l) N- t2 t7 z4 l 转载请注!! 强仔00001 2014-7-21写: p8 G2 D9 U4 m# f! T9 F; ]
TIM.zip
(342.99 KB, 下载次数: 452)
|
微信公众号
手机版
RE: STM32f401的硬件I2C
RE: STM32f401的硬件I2C
回复: STM32f401的硬件I2C
这明明是分享帖嘛,怎么放在求助帖里呢?不过感觉挺好的,学习了
是的看完之后楼主自己解决的问题,是分享帖
RE: STM32f401的硬件I2C