一、原理1、红外发射协议红外通信的协议有很多种。这个实验使用的是NEC协议。这个协议采用PWM的方法进行调制,利用脉冲宽度来表示 0 和 1 。 NEC 遥控指令的数据格式为:同步码头、地址码、地址反码、控制码、控制反码。同步码由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反码、控制码、控制反码均是 8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性。因此,每帧的数据为 32 位,包括地址码,地址反码,控制码,控制反码。反码可用于解码时进行校验比对。 NEC码的位定义:一个脉冲对应 560us 的连续载波,一个逻辑 1 传输需要 2.25ms(560us 脉冲+1680us 低电平),一个逻辑 0 的传输需要 1.125ms(560us 脉冲+560us 低电平)。而遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,在接收头端收到的信号为:逻辑 1 应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。 红外数据的波形如下图:包括一个同步头和 32 帧数据。
/ h% A @0 L4 ?- f" ~4 m
0 v) C2 R. |* C; n- m9 r# c% n3 i! ~6 }
& |1 g$ {: T4 f' j$ Y/ n' N0 I8 R+ f8 ]6 r y& r4 {
& H! ^* G0 }. y/ f5 L
% Q5 Y" D% F1 @" p! m4 }8 h2、定时器计数- 定时器就是按照一个特定的频率对计数值进行加一或减一操作,当数值溢出时则产生一个标志或中断。这里是用定时器计数产生一个周期性的中断。
7 Q$ D& O5 D4 P$ i & Q3 E8 y9 {. d4 U
$ S( n# i ^* E: w/ d! _( h3 H( w3、实现方法- 利用定时器记录两个下降沿之间的时间,通过该时间判断是否是同步头信息、数据 1 或者数据 0。当检测到同步头,开始记录 32 个数据的时间值。 2 Y0 k( e* T6 ]1 k
' `; w- S0 {6 Q8 F

! |( L1 M0 w0 ~% {9 S; R ) o3 W9 X6 Y8 q5 r: C
: t9 e0 Q& I8 T; T5 R
二、实现1、配置 GPIO 口下降沿触发中断示例代码中使用 PA7 管脚,配置为上拉输入模式。 选择下降沿触发,是因为红外接收管默认情况下保持高电平,接收到数据时从高电平转变为低电平。 中断源选择为 EXTI_Line7 ,在库函数中对该中断源定义的服务函数为 EXTI9_5_IRQHandler(),也就是说外部中断 5 到 9 是 共用一个中断服务函数的。 配置代码如下: ) o7 j; F$ }$ G6 S
- void IR_Pin_init(), F0 b1 h! t- B2 j
- {
' _- I/ i6 I! q# i$ A - GPIO_InitTypeDef GPIO_InitStructure;
/ q6 j. g! D- b - EXTI_InitTypeDef EXTI_InitStructure;
; o. x* p' D: M y. {7 a - NVIC_InitTypeDef NVIC_InitStructure;
* @' }' S M6 m ~- c5 O - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);+ k# F5 V, j7 ^7 T; ~
-
6 v, }8 H2 N) N - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
* ?7 t4 l; z/ n3 T2 T& | - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;- H0 d1 W+ i2 I% f$ p3 @6 `
- GPIO_Init(GPIOA,&GPIO_InitStructure);
$ {3 z; G! A* |8 c+ r( j0 T) n -
- I$ ^! [# e0 y" J8 q - EXTI_ClearITPendingBit(EXTI_Line7);# [3 r& G* r J9 D
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource7); ( L) q; @8 p7 \0 E' R7 ~2 V& B3 O
- EXTI_InitStructure.EXTI_Line=EXTI_Line7;& z5 G; V5 j! `; T& Q; J
- EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
~: Y& k2 E2 v$ J+ O2 `6 n - EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;) V' U% v8 {! t! K) X
- EXTI_InitStructure.EXTI_LineCmd=ENABLE;, r" B! _; b: M% u* @( b
- EXTI_Init(&EXTI_InitStructure); ( {, O* s% q3 }; }) S
-
7 D& d: _& [2 f4 P - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
5 T+ @2 ]7 z7 G, k. Q5 v - NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
. e) U( N3 X7 o - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
% o4 {& ]& @, v+ z! ]4 K/ l - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
+ k9 Y: p. m% ]7 I) u' t( u - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; - O4 h6 x/ N( d5 u4 |4 f- n+ M
- NVIC_Init(&NVIC_InitStructure);4 O8 Q1 R# K, r: `# ^1 A
- }
复制代码
5 u. f7 f) ~, i9 t2 c6 V; S/ x5 u+ g2 t; T) H
! d. w; K8 q% a4 f% |/ ?2、配置定时器计数值定时器使用的是 TIM2 通用定时器,模式为向上计数。在该模式中,计数器从 0 计数到自动加载值 (TIMx_ARR计数器的内容) ,然后重新从 0 开始计数并且产生一个计数器溢出事件。 示例函数接收两个参数,分别为预分频器的值和自动加载值。通过调整这两个参数,可以灵活地改变定时器的计数周期。例如在 TIM2 的默认时钟源 PCLK1 为96MHz时,使用语句 Tim2_UPCount_Init(SystemCoreClock/1000000-1,100-1); //0.1ms 进行初始化,可以每 0.1ms 产生一次中断。 示例代码如下: 5 V$ T' y# ?& S5 y: C
?4 u6 ~- O) i# _+ a
p h7 E7 ~* O- void Tim2_UPCount_Init(u16 Prescaler,u16 Period)5 Q6 n8 [2 \! V/ f! x
- {
5 b& K7 h. w$ y* A8 w @ - TIM_TimeBaseInitTypeDef TIM_StructInit;
9 l+ T9 {9 e2 B# U9 c( u- G - NVIC_InitTypeDef NVIC_StructInit;. L' `' c' k+ u m: k% j
- : D0 W% A0 F1 {- o |. M
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);: h3 T; T( O8 E% l, O1 R" Y
- 5 i' x% C( z& j+ m5 i
- TIM_StructInit.TIM_Period=Period;% R5 i* C x# f# ]/ I
- TIM_StructInit.TIM_Prescaler=Prescaler;9 {+ u+ {3 U" y" ?
- TIM_StructInit.TIM_ClockDivision=TIM_CKD_DIV1;
. C0 ]0 J( v7 h. n - TIM_StructInit.TIM_CounterMode=TIM_CounterMode_Up;$ ]+ d! @" s4 N, ^. {
- TIM_StructInit.TIM_RepetitionCounter=0;* u& J- b+ Z" E. q* L# n5 \6 M2 c
- TIM_TimeBaseInit(TIM2, &TIM_StructInit);
: w) u; }# t' u -
z' n# E6 i( I D$ K$ W - TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);5 j# w2 ^9 N o. l# t- R
- TIM_Cmd(TIM2, ENABLE); 0 N$ I' N6 t& d0 i' d( X3 Q
- TIM_ClearFlag(TIM2, TIM_FLAG_Update);
8 s6 m1 p+ ]5 m2 ?" F! z6 X3 @ - 2 a& `( @3 I/ c9 i+ E; C
- NVIC_StructInit.NVIC_IRQChannel=TIM2_IRQn;3 G3 Y8 V1 u* @) n! b$ M
- NVIC_StructInit.NVIC_IRQChannelCmd=ENABLE;, }' C$ v D$ ?' k6 V
- NVIC_StructInit.NVIC_IRQChannelPreemptionPriority=0;
- Q: v1 X+ T& g4 Q/ F/ l - NVIC_StructInit.NVIC_IRQChannelSubPriority=1;9 K; k* P+ b+ i ?( l3 r
- NVIC_Init(&NVIC_StructInit);
! v1 p- _% I4 p7 } M/ W: R o - }
复制代码 8 G4 y& c8 V% b1 e9 V+ W! P
+ V0 j6 ?% C1 v1 W
% _- D; ]# e6 F/ ?) H- b
8 J7 N' G" P5 P1 g8 Z) o" c" @
3、定时器中断函数统计时间- void TIM2_IRQHandler(void)
$ o! t" x y5 M1 R& K7 I. n - {
2 U8 y: S$ [& _& _1 c# l - TIM_ClearITPendingBit(TIM2, TIM_IT_Update);; @4 d& J( Y+ _1 s2 m) T" _; ]& p1 d) O
- ucTim2Flag++;
0 a' I/ X& o) q8 q1 ~9 ^" N& ?9 g I- y - }
复制代码
# P6 w9 {3 Y i" @4 m
5 Q: ~8 r7 e7 X# x5 } g4 [. F& b/ D4 g: |
4、GPIO 中断函数中接收 32 位数据在下降沿触发的 IO 口中断函数中,需要实现统计两个下降沿之间的时间,并将其记录在数组中。 下降沿第一次触发时,清除当前定时器中的计数值,以便统计时间。之后每一次下降沿触发就记录下当前计数值,然后再对其清零。如果该时间在同步头的时间区间内,对索引进行清零,表示重新开始接收数据。 完整接收同步头和 32 个数据之后,表示接收完成。 示例代码如下:
& f5 n4 ~8 Z' W& R/ ^
2 b3 \6 E, N& ^( F) T" O( w
' B& V: z2 N7 e, l- uint8_t irdata[33]; //用于记录两个下降沿之间的时间
. g8 o) M' b+ h p - bool receiveComplete; //接收完成标志位' R5 g7 T, y: w7 t
- uint8_t idx; //用于索引接收到的数值" T8 u/ b+ w& c6 F
- bool startflag; //表示开始接收# Z$ _! \: G' [' D+ _8 z
-
" Q5 n% P1 s1 u4 a9 b) F. Y C - void EXTI9_5_IRQHandler(void)9 n. L* V9 ]2 c2 h
- {
4 v+ D* o( \$ a' X& W2 J7 v - uint16_t ir_time;7 p$ s3 B& L0 s$ A$ L4 R7 B
- if(startflag)* V6 U4 c) Z8 P$ g u
- {; B% ? S" O# Y' |& o5 H
- ir_time = ucTim2Flag;
% l' C9 ` h2 `4 i; y6 r - if(ucTim2Flag < 150 && ucTim2Flag >= 50 ) // 接收到同步头9 T: M) [) o5 X) D5 I% N
- {% C/ T( U) }+ ~
- idx=0; // 数组下标清零
$ a4 x8 w" w9 D9 K. @3 \5 t - }7 A" u W8 H" }, I' A
-
4 Y. R7 D( x% X' f - irdata[idx] = ucTim2Flag; // 获取计数时间
3 [$ i5 B( o/ ?! k% D0 G! ?; w& n - ucTim2Flag = 0; // 清零计数时间,以便下次统计- K+ A: `) F4 [# N3 F4 e: i! `
- idx++; // 接收到一个数据,索引加1; M2 w- `, q5 b1 V) \% o* m% s
-
2 o3 E( J- u( j& P/ L( i/ r - if(idx==33) // 如果接收到33个数据,包括32位数和以一个同步头! _1 ^# N+ k, ]
- {& h7 F2 I1 s% _2 w( l% t
- idx=0;
, h; r8 ` W, R - ucTim2Flag = 0;
) w. t" r0 k3 U# h4 } - receiveComplete = TRUE;/ \3 r: P. x8 F
- }
* L c+ u. w. V+ E, E0 M) Y - }' d# k U! I8 v; s; B
- else // 下降沿第一次触发
' N8 h" N; h) o2 S" K. S - {2 W' c' j# m: b$ j) I3 @, l( `- J
- idx = 0;
; y" Y2 v" v6 N% l% K- K$ K6 f - ucTim2Flag = 0;
- ^/ w: \4 e! z) g2 ~! z. }% Z - startflag = TRUE;
+ x/ U5 @8 t2 o1 L5 X1 y - }
& {/ c1 V! V9 F+ |0 T- B -
- R( v3 I# @4 j - EXTI_ClearITPendingBit(EXTI_Line7); // 清除中断标志
. ^! `/ W( f+ z" [( N - }
复制代码
0 N7 A" b" M. u# b: V
3 Y; ]6 q; a. I- o$ H2 z9 B$ g5、判断控制码值4 |' R: T/ l! R( s# E/ F4 _
+ q' M- s- U& ?; N% ?& d9 H- uint8_t Ir_Server(). u7 P, r- w" A# G+ e
- {
( E( O6 s. ?4 n/ h' s3 O - uint8_t i,j,idx=1; //idx 从1 开始表示对同步头的时间不处理
; D2 V- w( K/ ] K - uint8_t temp;
! v2 E3 }' Y; C! v4 U3 Z - for(i=0; i<4; i++)
4 d: a% i. [1 M4 z* m0 L - {
1 {0 t! M4 p6 {/ f - for(j=0; j<8; j++)% [+ y3 n( y7 r: L+ G; D7 |
- {
* V1 _6 w# y( ]: o8 m! a0 g2 z% c" v - if(irdata[idx] >=8 && irdata[idx] < 15) //表示 0
: _" ?* N4 ^% g6 u, ^( U' f. v - {! k* h% d1 V) z e
- temp = 0;
, s/ e6 t1 W% h - }
$ q5 O) h/ e8 ~7 G$ g - else if(irdata[idx] >=18 && irdata[idx]<25) //表示 1" c/ p( K! f1 f9 I2 L: [# y
- {( u6 u5 \/ v q5 T
- temp = 1;( C3 H }- f+ y- }
- }
' U+ Y/ H8 C4 {& `/ T - remote_code[i] <<= 1;
" A" f/ c, m9 G( _ - remote_code[i] |= temp;9 U3 G- x$ G. X4 ~$ Q
- idx++;
; O5 w5 w% W$ l; w1 F" |/ k' u - }& u8 r* w& X4 `4 s. Y- ]6 u- E8 c
- }
7 y3 x" X/ n- o -
, |% y" S7 K9 H' Y" R; I, t9 Z - return remote_code[2]; // 该数组中记录的是控制码,每个按键不一样
2 f) b( A9 B5 e: s' [, v; Z - //for(idx=0; idx<4; idx++)& u0 H r- {- l8 ^. z
- //{. w* ]) y! `8 P' d3 f" {2 R
- // printf("remote_code[%d] = %#x\n",idx,remote_code[idx]);
* m* [( x) _: P$ u3 c: ~ - //}
! T! y' G% L4 R$ A - }
复制代码 , A$ B3 S0 p/ U' ~: ~+ r
6 I) }( y! n, w. |+ G$ s6 o$ d
3 A, f7 R( \8 `9 U7 ^5 s) t6、主函数% L; A! S8 g% r' {! ~
$ D. I5 J* E6 p. Q9 {9 ^- void main()
a, {: V& I/ X - {
( d9 H5 J8 J m0 F R - ...0 U" Z& M$ F$ k! E
-
2 L9 q$ ]8 A- U; a - IR_Pin_init();# Z+ {! r% p0 I% G1 L
- Tim2_UPCount_Init(SystemCoreClock/1000000-1,100-1);7 P1 P; l" Z0 r: E j& ?2 J4 T& t
- ! T' u' t! ` ^2 _+ z+ _! Y, R% a, p& O
- while(1)
( Z2 O3 t5 A2 q% r5 P* z - {
$ H7 p; e3 o8 o( j. u) g! f/ q: G - if(repeatEnable)
" v! `4 R& ^: {) m - {
1 u: U2 D* O, a' J A - repeatEnable = FALSE;4 l, V( H( G0 g( v; C3 U- l, o% d+ q, \
- Ir_Server();
1 i D, I4 T* _ }' ~: a! e - printf("key_code = %#x\n",remote_code[2]);
3 v$ L* `& A$ H3 P3 I4 _) j4 P - }+ Z0 ~; p' q, [5 _# v
- }( S9 W/ u, s, Z0 B2 _: K9 ^
-
$ S/ ]2 I0 U2 P+ H$ l* { - }
复制代码
# u1 f4 j- X6 G& N
7 j. [: D5 j, _9 h
8 o" @& P1 k* q1 X" X A三、演示如下图为串口打印出接收的红外按键值信息: 2 [$ [% m+ L2 X) C
/ U* A6 O$ t& x
说明1:这只是实现红外接收的其中一种方法,网上还有一种比较常见的方法是利用下降沿触发,在中断中进行延迟,判断高电平持续时间以此来判断信号类别。个人感觉这不是一种很好的方法,因为在中断中进行延时会导致主函数得不到及时的处理。 说明2:在调试时,不要在中断处理中加入过多无关语句,例如打印语句,这会导致结果出错。
; k/ s+ a# r& n. ^- E! v4 E
转载自:xqhrs232 + z' r, R m& j
8 [1 `* N; Z3 d, Z; Q6 H
|