本帖最后由 netlhx 于 2015-11-8 20:35 编辑
+ h) Z6 [/ [) I6 L
- c" |% E! R- c- [, B( ?NUCLEO-L476RG到手一段时间了,今天来测试一下硬件I2C的操作,听说很多BUG,试一试看如何。
+ D7 I0 \" H/ l. N9 i! ~$ g( w5 m/ O" p) U
准备用I2C来访问AT24C02 EEPROM,将数据写入到EEPROM中,然后再读取出来,验证读写操作的正确性。
* u7 | R* Y+ Z+ U1 b( ?9 ?. Y& w4 \$ T# ]; }% z+ k( i- M
硬件平台如下0 T( C! J# {3 L/ ^7 B" ~
* E! H0 b/ `3 }; B# B
; ?$ I6 d! a+ O* O6 i' h6 VNUCLEO-L476RG
" q0 d( x0 U: v) b0 c$ g' w; C, o: Y/ F0 q G# d* m% R0 u- j$ l, L3 h
& [0 K0 P4 D* `$ e& T5 O3 O* q0 o" e; m
前段时间FSL的KL02Z套装,上面的MINIDOCK扩展板上恰好有AT24C02,所以借过来用一下,FSL不要生气啊 ) O1 L. Z' e) _# J8 Y: L
9 y; w3 \9 B4 t
8 j, e+ K5 v0 O! d0 i5 q4 S
# c" ]& ]5 M+ y5 B) d, ~标准的ARDUINO接口,放到哪都行。合体照。
6 m# c% r( d) {- f# v7 r4 _# e. V- H* b2 ~/ l: @
! w$ n7 m6 w4 t( g
+ p3 y& g/ o9 I3 [2 {6 Y' R7 |4 _硬件连接及原理图4 [" }4 F5 H/ \9 L
x# R" e1 b! g$ H5 B
MINI DOCI上的AT24C02连接
4 A) e6 l+ ?5 }/ m) _* B/ W4 r% h3 A; r3 i
! @- _+ ?! E+ y' \% V5 h: f* }5 Z2 \# I: s$ s! ~
ARDUINO接口:SCL及SDA
8 k% C% W/ D, X# ~) S4 H
5 u! U! h* S* V/ q+ b' k
( z% _ U- D' r4 E1 p! P: G: [' L* j5 [9 O. r( K
NUCLEO-L476RG上对应的接口
; {, k6 V. @! ?6 V: T/ j: l) `- O$ Z7 Z# t1 q9 y) Z" R
* F& Q. H8 Q6 u7 O# b9 D
* p! ~. i9 U% z- S, c. V$ b8 I. ]
4 L& o7 ~" l- s w" b4 X0 S
写程序之前,要了解硬件的基本特性,首先是STM32L476RG上I2C接口支持的特性;其次是AT24C02的特性。
/ d) T& _& m: W- j2 C1 R6 j& Z; {: y2 G
STM32L476RG的I2C功能框图如下
0 P+ e( ^% r0 h6 u& z9 u+ }$ p6 E- j( O$ _
# d; k8 m) @6 d" {% J9 t( @9 {) y
' w' C9 u) U P3 o5 S/ p E/ o
值得一提的是,L4的I2C有自己独立的内核时钟,可以在SYSCLK、PCLK及HSI中任选一种做为I2C的内核时钟。另外L4的I2C支持三种不同的通信速率:100KHZ, 400KHZ,1MHZ。由于AT24C02只支持最高400KHZ,所以这里就选择400KHZ做为内核时钟。
3 B9 Y1 a3 Z& W) T. |' W
, _) l4 i$ p# ]) C& q3 l/ q" S创建I2C测试工程,建立过程略。几点要注意的地方。) l4 ~) s& N2 E+ T3 v! _4 W
0 l" ~' _# \8 O5 L2 |首先,本例程中使用的是I2C1,对应的SCL及SDA引脚为PB8及PB9,时钟配置后SYSCLK为80MHZ。; H& s3 b2 C; u/ w4 w( d
1 U; q) M3 X9 c2 `
I2C硬件配置参数如图8 D1 u* s6 U. u M( }
( t! ~# k, V3 i- i3 \+ N& t, E
/ ?# B4 r# P3 E
2 L2 v' v( b( z速度有三种可选:标准,快速,快速加。另外针对不同的特定应用,可以选择设置上升和下降沿的时间,以纳秒为单位,范围为0-120纳秒之间。注意这个TIMING参数,是灰色的,CUBEMX自动计算并设置该值,不像以前的F0和F3系列,需要一个专门的工具来设置TIMING的值,够麻烦的。
$ a' w$ Z O) a6 b; ]
5 D# v* \6 G9 G9 e) u/ w下面是使用HAL库与EEPROM通信的主要代码。
2 t+ k, k# b/ I4 x
0 c4 \7 ~/ g5 c6 B9 ^/ O* N7 f+ V3 |7 S+ J
- /* USER CODE BEGIN PV */) X2 M8 i T+ K1 W- v2 A3 J/ f
- /* Private variables ---------------------------------------------------------*/) `3 V- l0 Q0 }& D% o6 @
- #define EEPROM_ADDRESS 0xA0
! @5 p8 F8 g( | - #define EEPROM_PAGESIZE 0x11
" B5 e8 b0 i& Z5 }& g - #define EEPROM_TIMEOUT 1000
8 _: M. s0 h: `; S9 O! } [ - 2 q, j1 M. J, w* ?( G
- extern I2C_HandleTypeDef hi2c1;: D" Z/ T( O5 Y# M6 k) `4 r, o$ q
- 6 g7 ~& D. `+ {! p! [2 `* e* x: J
- char msg[] = "This is a test!\r\n";
8 Q( i K8 a/ F% ^+ ] - $ | \& S. O! o6 r
- char buf[40];9 v- u( g& h% U* c8 V
5 @; T8 U8 t8 h! o8 p- int16_t Remaining_Bytes;
. Z' Z9 d) t! p {9 B0 K - uint16_t Memory_Address;
& N3 \) v+ i# E; T, I
8 [! [8 {( H4 b- _( y- uint16_t len;
. l) ?( v. b" f/ T( ?+ L
$ i1 x4 o. S+ v8 \+ k X |! s- ......& k5 a& v0 `$ c) R. ?3 {9 w: a
- * ^7 b* |& ]2 l5 T- x2 L4 S4 A
- //write msg to eeprom
' `0 {' W: l* Z2 O - Remaining_Bytes = strlen(msg);
" T. x3 H2 M" U: C& n/ c( h. E - Memory_Address = 0;
2 A8 K z I/ U - % B( v. I( u) X
- while(Remaining_Bytes > 0); G$ p+ F! |9 d
- {
6 `6 ^) X; r3 A- |& m( H9 R6 \ - if(HAL_I2C_Mem_Write_DMA(&hi2c1, EEPROM_ADDRESS, Memory_Address, I2C_MEMADD_SIZE_8BIT, (uint8_t *)(msg + Memory_Address), EEPROM_PAGESIZE) != HAL_OK)
; e1 ^$ \. y$ F9 [ - {+ n* o2 m; @5 m' e
- Error_Handler();
4 v* d7 w: Z. X8 R. T - ) M0 r: e- g$ D* @! x3 W3 ]: q+ A
- }+ |! r6 t* C& Y3 E" m+ g
-
1 x+ o6 s A1 g1 {# l - while(HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)% T# ~3 n5 P2 V! [/ E
- {
8 [" t' j) Z; z: ?' o' P+ v - }( c( A5 Q' y2 v6 z5 A
-
; _# z4 x" U6 s - while (HAL_I2C_IsDeviceReady(&hi2c1, EEPROM_ADDRESS, 10, 300) == HAL_TIMEOUT);) i# q2 t: k$ U/ Q0 S+ G
-
# n1 S) T x/ ]( L% x( F$ V - while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)' J+ d5 Z9 f: X( `4 v
- {( _8 @% Y" d8 E$ W# X$ l4 o
- }, J. i2 C; h. N5 @
-
% v) }! y: c4 N: n - Remaining_Bytes -= EEPROM_PAGESIZE;& ]7 p; S3 i( K* N% C$ d
- ; X+ \; O' G2 C ?$ ` P
- Memory_Address += EEPROM_PAGESIZE;
% V1 ]7 f' x! K% t/ D - }
' L! M5 L' g" K# G( t -
# w: s2 c% a5 |; ?, v: t - //read msg to buff2 ]- V" Y( k+ U4 [6 l
-
Z5 D* F! U/ i8 x$ T+ s9 H - if(HAL_I2C_Mem_Read_DMA(&hi2c1 , EEPROM_ADDRESS, 0, I2C_MEMADD_SIZE_8BIT, (uint8_t*)buf, strlen(msg))!= HAL_OK)0 M4 t7 B" O M- s) Y! d9 B- J
- {
u7 h4 P: n3 L( L - /* Reading process Error */
2 r5 @! b$ |' S5 C2 C: u - Error_Handler();
' V9 ?4 b: y `& S6 I - }
/ D6 U* _0 x+ C, ^ - 5 n: D7 |6 F! q% U2 M
- /* Wait for the end of the transfer */ , B! g0 s7 E/ O8 A0 g5 v
- while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
& P G3 S# ^9 E - {+ A# M& ?+ ^6 ^! g
- }
& B! S' Q, k6 d" C$ e$ F& M -
复制代码 ' o4 m0 h, U+ z% z$ e2 v) M
根据STD库中用户的反馈,I2C出错的原因主要由几方面造成,一是ST设计的I2C接口,通信过程中一些状态变量及标志位的设置非常严格,如果程序逻辑考虑不周全,会导致I2C停止工作,需要软复位后才能继续;另一方面,I2C接口在处理单个,双字节及多字节传输过程中要分开考虑,进一步将问题复杂化了;还有就是结合具体的I2C设备,不同的时序要求,进一步复杂化了通信过程。
1 a) f$ S- `, x' e. c
+ |5 S* A' Z7 R3 C这些问题在HAL库中都得到了很好的解决,为此,HAL库中设计了几种不同类型的API函数供调用。一类是通用的接收和发送函数,这类API函数将I2C接口视为类似于“流对象”,通信时从流对象中接收或发送数据;另一类则是类似于EEPROM之类的特殊存储对象,从这些存储对象中接收或发送数据时,都需要提供一个地址,以便准确定位。使用EEPROM来实验时,我们使用的是第二类API函数。两个主要的API函数原型及参数如下。
' C! a0 W5 u( m) |* ?9 s
/ D3 T: r. V* G+ I- /**/ {: v8 e$ }9 O- U! {* R" ?8 ]
- * @brief Transmit in master mode an amount of data in non-blocking mode with DMA
3 N+ Q& F- B" ?8 h3 S - * @param hi2c : Pointer to a I2C_HandleTypeDef structure that contains" L; [( G( j9 w: S4 E V9 ^
- * the configuration information for the specified I2C.
- b/ G! q O* z k: B& ^! E! p - * @param DevAddress: Target device address7 P z) s+ p! N' S% s, K6 p+ h& _
- * @param pData: Pointer to data buffer
$ s4 e7 a' }3 \4 [8 ~ - * @param Size: Amount of data to be sent
5 j/ M" G w: B2 Z2 I0 H; |( T0 q - * @retval HAL status
) n5 e( K1 O# r3 I - */' v1 h% f# k6 d0 |% U0 S' H
- HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
复制代码- /** F0 { B* A% `5 A1 p, `
- * @brief Reads an amount of data in non-blocking mode with DMA from a specific memory address.& r/ O( M# K8 v! q
- * @param hi2c : Pointer to a I2C_HandleTypeDef structure that contains: y9 Z- R1 m7 Q$ W) i
- * the configuration information for the specified I2C.0 k. ?7 d) S, Z) M, ?, d% R2 ]1 q
- * @param DevAddress: Target device address
3 t& n6 P2 E Z4 J+ P - * @param MemAddress: Internal memory address5 Q3 V! k* F0 g q( k7 J, X
- * @param MemAddSize: Size of internal memory address
- y: E. U# ?6 t1 s5 e. j' f8 ` - * @param pData: Pointer to data buffer1 u. e7 b2 d8 K1 z7 B: t
- * @param Size: Amount of data to be read
# z g2 S! d6 o0 v% q - * @retval HAL status7 o7 l4 j. N; ?1 D9 e
- */
* d% D1 C1 y4 V5 K) n% l - HAL_StatusTypeDef HAL_I2C_Mem_Read_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size)
复制代码 * n/ O6 b& w2 R. b' k+ `2 m
关于这两个函数的更详细的调用方法及说明,请参考API文档。/ e( U1 o% y. w5 O
?5 M) x( I( A4 U注意在具体操作EEPROM的时候,还要注意,EEPROM分字节读写及页面读写两种类型,对HAL API的调用还要进行适当的修改。
; G. k i3 ]) Y! o- D
4 W( `/ {9 T9 X* ` v v由于ST HAL API做了大量的后台工作,上面的代码看起来过于简单了。但实际上API函数在后台做了大量的工作,这不正是我们期待的么!
Z' F. c- B( f- f. |' _
+ B/ x7 R+ T( X: _最后来看看运行结果。" [1 T b) W' K
* `# n- j' F7 A6 N; B
UART打印的写入及读取取结果
# e ^( [% ^/ H. ~$ i) m7 i9 T& r
4 y o7 U- ]+ Z2 x. v b5 P
! a- {6 W l. y( n9 x0 x$ n3 N. Y4 q K8 ~9 h' k
逻辑分析仪LA! Q3 |2 x/ n) t" n# {
8 j' p# x2 y* P' a5 p. a
* o1 y0 q7 x. M3 i7 W1 c' d ^4 a
* s1 J! N) A6 j+ Y+ @* h1 g. zI2C通信全程,LA分析结果$ `9 k6 u7 Y. z I1 n3 {
% u4 m4 Q% ?. ~
: c! j( y9 U3 X
5 b8 V# _6 a7 @# j5 I. e" }; {% U开始通信,顺序依次是开始信号,地址,EEPROM写入地址,第一个数据字节
: G2 ?8 o& q! R0 \- l: D
8 Y9 W) g J/ j6 I3 _! @2 w0 l
c4 K9 {- B4 M! D! B: y8 _+ ]0 k( S7 ?) O
字符0x0D, 0x0A, 结束信号。* }5 r- v: O: x1 [
X. r+ p+ X3 f! B$ Y
( j9 B& \# e8 B" B$ M* Z- d, j, r/ I% x2 s8 |6 ]
接收开始,顺序依次是开始信号,地址,EEPROM读取地址,第一个数据字节
- X# `: t9 n% Y4 E4 H7 F. r# R
+ c4 c; G( v/ M5 B+ ]
) L$ c/ I* \9 V* |4 j( l1 g
9 c- H# H2 ^6 w7 E: o4 o读取结束
% i. s% I* s8 r
" B3 \' _0 ?: Q$ x
) Z) ~- N- r/ r Q: ?: I: S# y. b
" Q) V# W; _, D6 Z" X5 K M+ ~9 z
6 i) [" n) y+ _ ^% Z. B* E7 c$ ?
2 a9 D T- Y( b附工程
! A" p/ a e, u4 N# m$ s5 p- J p; k$ f# N2 I& h! k
eeprom.zip
(3.17 MB, 下载次数: 203)
|
{ S$ P( d! P3 v. r2 C
Error_Handler();$ N6 c$ o7 Z& Z6 D
};程序直接进入Error_Handler()函数,请问是什么原因呢?我检查了很多地方,还是不行,求指教,非常感谢!(测试环境stm32l476rg+at24c02,后来用了at24c16测试还是失败)
1、移植,直接秒杀不用多说* ]: X6 s. S: T- n5 e6 A+ _; }
2、效率,IIC的硬件通讯过程中用了太多的访问标志位,实际效率不会增加,一样是让CPU在耗,
所以,效率反而没有模拟IIC高,因为GPIO的反转速率轻松满足IIC的通信速率,所以
模拟IIC照样更胜1筹。
3、复杂性,ST的通病,反而模拟IIC更加把协议变得很直观。0 j) ]1 J# h" W6 O' ?/ ~9 q* N
纵上所述,感觉这个硬件IIC就是个鸡肋。
梦源的牌子吧,100M