你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32 定时器实现红外遥控数据接收

[复制链接]
STMCU小助手 发布时间:2023-1-6 17:57
一、原理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- m
9 r# c% n3 i! ~6 }
20170808230617825.jpg
& |1 g$ {: T4 f' j$ Y/ n' N0 I8 R+ f8 ]6 r  y& r4 {
  • 下图可看出,同步头为 9ms 低电平加上 4.5ms 高电平,控制码为 8 个 0,控制反码为 8 个 1。


    + d! m" a# ~3 Q6 |$ t
20170808231029615.jpg & 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
    20170808225212279.jpg

    ! |( 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
  1. void IR_Pin_init(), F0 b1 h! t- B2 j
  2. {
    ' _- I/ i6 I! q# i$ A
  3.   GPIO_InitTypeDef GPIO_InitStructure;
    / q6 j. g! D- b
  4.   EXTI_InitTypeDef EXTI_InitStructure;
    ; o. x* p' D: M  y. {7 a
  5.   NVIC_InitTypeDef NVIC_InitStructure;
    * @' }' S  M6 m  ~- c5 O
  6.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);+ k# F5 V, j7 ^7 T; ~

  7. 6 v, }8 H2 N) N
  8.   GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
    * ?7 t4 l; z/ n3 T2 T& |
  9.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;- H0 d1 W+ i2 I% f$ p3 @6 `
  10.   GPIO_Init(GPIOA,&GPIO_InitStructure);
    $ {3 z; G! A* |8 c+ r( j0 T) n

  11. - I$ ^! [# e0 y" J8 q
  12.   EXTI_ClearITPendingBit(EXTI_Line7);# [3 r& G* r  J9 D
  13.   GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource7); ( L) q; @8 p7 \0 E' R7 ~2 V& B3 O
  14.   EXTI_InitStructure.EXTI_Line=EXTI_Line7;& z5 G; V5 j! `; T& Q; J
  15.   EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
      ~: Y& k2 E2 v$ J+ O2 `6 n
  16.   EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;) V' U% v8 {! t! K) X
  17.   EXTI_InitStructure.EXTI_LineCmd=ENABLE;, r" B! _; b: M% u* @( b
  18.   EXTI_Init(&EXTI_InitStructure); ( {, O* s% q3 }; }) S

  19. 7 D& d: _& [2 f4 P
  20.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    5 T+ @2 ]7 z7 G, k. Q5 v
  21.   NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
    . e) U( N3 X7 o
  22.   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    % o4 {& ]& @, v+ z! ]4 K/ l
  23.   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;     
    + k9 Y: p. m% ]7 I) u' t( u
  24.   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  - O4 h6 x/ N( d5 u4 |4 f- n+ M
  25.   NVIC_Init(&NVIC_InitStructure);4 O8 Q1 R# K, r: `# ^1 A
  26. }
复制代码

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
  1. void Tim2_UPCount_Init(u16 Prescaler,u16 Period)5 Q6 n8 [2 \! V/ f! x
  2. {
    5 b& K7 h. w$ y* A8 w  @
  3.   TIM_TimeBaseInitTypeDef TIM_StructInit;
    9 l+ T9 {9 e2 B# U9 c( u- G
  4.   NVIC_InitTypeDef NVIC_StructInit;. L' `' c' k+ u  m: k% j
  5. : D0 W% A0 F1 {- o  |. M
  6.   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);: h3 T; T( O8 E% l, O1 R" Y
  7. 5 i' x% C( z& j+ m5 i
  8.   TIM_StructInit.TIM_Period=Period;% R5 i* C  x# f# ]/ I
  9.   TIM_StructInit.TIM_Prescaler=Prescaler;9 {+ u+ {3 U" y" ?
  10.   TIM_StructInit.TIM_ClockDivision=TIM_CKD_DIV1;
    . C0 ]0 J( v7 h. n
  11.   TIM_StructInit.TIM_CounterMode=TIM_CounterMode_Up;$ ]+ d! @" s4 N, ^. {
  12.   TIM_StructInit.TIM_RepetitionCounter=0;* u& J- b+ Z" E. q* L# n5 \6 M2 c
  13.   TIM_TimeBaseInit(TIM2, &TIM_StructInit);
    : w) u; }# t' u

  14.   z' n# E6 i( I  D$ K$ W
  15.   TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);5 j# w2 ^9 N  o. l# t- R
  16.   TIM_Cmd(TIM2, ENABLE);    0 N$ I' N6 t& d0 i' d( X3 Q
  17.   TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    8 s6 m1 p+ ]5 m2 ?" F! z6 X3 @
  18. 2 a& `( @3 I/ c9 i+ E; C
  19.   NVIC_StructInit.NVIC_IRQChannel=TIM2_IRQn;3 G3 Y8 V1 u* @) n! b$ M
  20.   NVIC_StructInit.NVIC_IRQChannelCmd=ENABLE;, }' C$ v  D$ ?' k6 V
  21.   NVIC_StructInit.NVIC_IRQChannelPreemptionPriority=0;
    - Q: v1 X+ T& g4 Q/ F/ l
  22.   NVIC_StructInit.NVIC_IRQChannelSubPriority=1;9 K; k* P+ b+ i  ?( l3 r
  23.   NVIC_Init(&NVIC_StructInit);
    ! v1 p- _% I4 p7 }  M/ W: R  o
  24. }
复制代码
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、定时器中断函数统计时间
  • 如上所说,定时器每 0.1ms 计数完成,产生中断,在中断函数中对标志位 ucTim2Flag 加 1,意味着时间过去了 0.1ms。

  • 时间标志位原型为 uint16_t ucTim2Flag; 。

  • 示例代码如下:


    ) B- V: m# S$ s0 G2 Q- @$ C
  1. void TIM2_IRQHandler(void)
    $ o! t" x  y5 M1 R& K7 I. n
  2. {
    2 U8 y: S$ [& _& _1 c# l
  3.   TIM_ClearITPendingBit(TIM2, TIM_IT_Update);; @4 d& J( Y+ _1 s2 m) T" _; ]& p1 d) O
  4.   ucTim2Flag++;
    0 a' I/ X& o) q8 q1 ~9 ^" N& ?9 g  I- y
  5. }
复制代码

# 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
  1. uint8_t irdata[33];   //用于记录两个下降沿之间的时间
    . g8 o) M' b+ h  p
  2. bool receiveComplete; //接收完成标志位' R5 g7 T, y: w7 t
  3. uint8_t idx;          //用于索引接收到的数值" T8 u/ b+ w& c6 F
  4. bool startflag;       //表示开始接收# Z$ _! \: G' [' D+ _8 z

  5. " Q5 n% P1 s1 u4 a9 b) F. Y  C
  6. void EXTI9_5_IRQHandler(void)9 n. L* V9 ]2 c2 h
  7. {
    4 v+ D* o( \$ a' X& W2 J7 v
  8.   uint16_t ir_time;7 p$ s3 B& L0 s$ A$ L4 R7 B
  9.   if(startflag)* V6 U4 c) Z8 P$ g  u
  10.   {; B% ?  S" O# Y' |& o5 H
  11.       ir_time = ucTim2Flag;
    % l' C9 `  h2 `4 i; y6 r
  12.       if(ucTim2Flag < 150 && ucTim2Flag >= 50 ) // 接收到同步头9 T: M) [) o5 X) D5 I% N
  13.       {% C/ T( U) }+ ~
  14.           idx=0;  // 数组下标清零
    $ a4 x8 w" w9 D9 K. @3 \5 t
  15.       }7 A" u  W8 H" }, I' A

  16. 4 Y. R7 D( x% X' f
  17.       irdata[idx] = ucTim2Flag;  // 获取计数时间
    3 [$ i5 B( o/ ?! k% D0 G! ?; w& n
  18.       ucTim2Flag = 0;            // 清零计数时间,以便下次统计- K+ A: `) F4 [# N3 F4 e: i! `
  19.       idx++;                     // 接收到一个数据,索引加1; M2 w- `, q5 b1 V) \% o* m% s

  20. 2 o3 E( J- u( j& P/ L( i/ r
  21.       if(idx==33)       // 如果接收到33个数据,包括32位数和以一个同步头! _1 ^# N+ k, ]
  22.       {& h7 F2 I1 s% _2 w( l% t
  23.           idx=0;
    , h; r8 `  W, R
  24.           ucTim2Flag = 0;
    ) w. t" r0 k3 U# h4 }
  25.           receiveComplete = TRUE;/ \3 r: P. x8 F
  26.       }
    * L  c+ u. w. V+ E, E0 M) Y
  27.   }' d# k  U! I8 v; s; B
  28.   else   // 下降沿第一次触发
    ' N8 h" N; h) o2 S" K. S
  29.   {2 W' c' j# m: b$ j) I3 @, l( `- J
  30.       idx = 0;
    ; y" Y2 v" v6 N% l% K- K$ K6 f
  31.       ucTim2Flag = 0;
    - ^/ w: \4 e! z) g2 ~! z. }% Z
  32.       startflag = TRUE;
    + x/ U5 @8 t2 o1 L5 X1 y
  33.   }
    & {/ c1 V! V9 F+ |0 T- B

  34. - R( v3 I# @4 j
  35. EXTI_ClearITPendingBit(EXTI_Line7);  // 清除中断标志
    . ^! `/ W( f+ z" [( N
  36. }
复制代码

0 N7 A" b" M. u# b: V
3 Y; ]6 q; a. I- o$ H2 z9 B$ g5、判断控制码值
  • 由于中断函数中接收并记录下的数据是两个下降沿之间的时间,并不是红外所发送的数据。因此需要根据红外协议,对 32 个时间进行判断,从而获得红外真正发送的数据。

  • 下面这个函数需要在红外完整接收数据后执行,可通过判断接收完成标志位 receiveComplete 来实现。

  • 示例代码如下:

    4 c5 e& {; l  W/ M" U  ]7 u) E+ a" Y
4 |' R: T/ l! R( s# E/ F4 _

+ q' M- s- U& ?; N% ?& d9 H
  1. uint8_t Ir_Server(). u7 P, r- w" A# G+ e
  2. {
    ( E( O6 s. ?4 n/ h' s3 O
  3.   uint8_t i,j,idx=1; //idx 从1 开始表示对同步头的时间不处理
    ; D2 V- w( K/ ]  K
  4.   uint8_t temp;
    ! v2 E3 }' Y; C! v4 U3 Z
  5.   for(i=0; i<4; i++)
    4 d: a% i. [1 M4 z* m0 L
  6.   {
    1 {0 t! M4 p6 {/ f
  7.       for(j=0; j<8; j++)% [+ y3 n( y7 r: L+ G; D7 |
  8.       {
    * V1 _6 w# y( ]: o8 m! a0 g2 z% c" v
  9.           if(irdata[idx] >=8 && irdata[idx] < 15)   //表示 0
    : _" ?* N4 ^% g6 u, ^( U' f. v
  10.           {! k* h% d1 V) z  e
  11.               temp = 0;
    , s/ e6 t1 W% h
  12.           }
    $ q5 O) h/ e8 ~7 G$ g
  13.           else if(irdata[idx] >=18 && irdata[idx]<25) //表示 1" c/ p( K! f1 f9 I2 L: [# y
  14.           {( u6 u5 \/ v  q5 T
  15.               temp = 1;( C3 H  }- f+ y- }
  16.           }
    ' U+ Y/ H8 C4 {& `/ T
  17.           remote_code[i] <<= 1;
    " A" f/ c, m9 G( _
  18.           remote_code[i] |= temp;9 U3 G- x$ G. X4 ~$ Q
  19.           idx++;
    ; O5 w5 w% W$ l; w1 F" |/ k' u
  20.       }& u8 r* w& X4 `4 s. Y- ]6 u- E8 c
  21.   }
    7 y3 x" X/ n- o

  22. , |% y" S7 K9 H' Y" R; I, t9 Z
  23.   return remote_code[2];  // 该数组中记录的是控制码,每个按键不一样
    2 f) b( A9 B5 e: s' [, v; Z
  24.   //for(idx=0; idx<4; idx++)& u0 H  r- {- l8 ^. z
  25.   //{. w* ]) y! `8 P' d3 f" {2 R
  26.   //    printf("remote_code[%d] = %#x\n",idx,remote_code[idx]);
    * m* [( x) _: P$ u3 c: ~
  27.   //}
    ! T! y' G% L4 R$ A
  28. }
复制代码
, 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、主函数
  • 在 main 函数中,对 IO 口和 定时器进行初始化。

  • 主循环中,通过判断接收完成标志位,对接收完成的按键控制码进行打印。

  • 示例代码如下:

    7 ]5 H2 W$ g/ `9 s" o" U4 j$ e( t/ `( a
% L; A! S8 g% r' {! ~

$ D. I5 J* E6 p. Q9 {9 ^
  1. void main()
      a, {: V& I/ X
  2. {
    ( d9 H5 J8 J  m0 F  R
  3.   ...0 U" Z& M$ F$ k! E

  4. 2 L9 q$ ]8 A- U; a
  5.   IR_Pin_init();# Z+ {! r% p0 I% G1 L
  6.   Tim2_UPCount_Init(SystemCoreClock/1000000-1,100-1);7 P1 P; l" Z0 r: E  j& ?2 J4 T& t
  7. ! T' u' t! `  ^2 _+ z+ _! Y, R% a, p& O
  8.   while(1)
    ( Z2 O3 t5 A2 q% r5 P* z
  9.   {
    $ H7 p; e3 o8 o( j. u) g! f/ q: G
  10.       if(repeatEnable)
    " v! `4 R& ^: {) m
  11.       {
    1 u: U2 D* O, a' J  A
  12.           repeatEnable = FALSE;4 l, V( H( G0 g( v; C3 U- l, o% d+ q, \
  13.           Ir_Server();
    1 i  D, I4 T* _  }' ~: a! e
  14.           printf("key_code = %#x\n",remote_code[2]);
    3 v$ L* `& A$ H3 P3 I4 _) j4 P
  15.       }+ Z0 ~; p' q, [5 _# v
  16.   }( S9 W/ u, s, Z0 B2 _: K9 ^

  17. $ S/ ]2 I0 U2 P+ H$ l* {
  18. }
复制代码

# 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

20170808225232539.png


/ 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
赞 收藏 评论0 发布时间:2023-1-6 17:57

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版