本实验使用到硬件资源如下: (1)D1 指示灯 (2)串口 1 (3)红外遥控器和红外接收头 D1 指示灯、串口 1 电路在前面章节都介绍过,这里就不多说,红外遥控器属于外部器件,只有红外接收头集成在开发板上,其电路如下图所示: $ |: T0 |% w, \3 x
从电路图中可以看到,红外接收头的数据输出管脚连接在 STM32F1 芯片的PG15管脚上,这里我们使用PG15 引脚内部上拉,所以可以省去一个上拉电阻,不会有影响,当然也可以接一个 10K 的上拉电阻。通过配置 PG15 引脚为外部中断功能,按照 NEC 协议进行解码。 D1 指示灯用来提示系统运行状态,红外遥控器用来发射红外键值的编码信 号,通过红外接收头进行解码,并将解码后的数据通过串口 1 打印输出。 本实例所要实现的功能是:使用外部中断功能将遥控器键值编码数据解码后通过串口打印输出,同时控制 D1指示灯闪烁,提示系统运行。程序框架如下: (1)使能 PG15 端口及 AFIO 时钟,映射 PG15 至外部中断线上,初始化 EXTI等 (2)编写红外解码函数(在 EXTI 中断处理) (3)编写主函数 外部中断的配置在“外部中断实验”章节中都有介绍,不清楚的可以回过头 看下。下面我们打开“ 红外遥控实验”工程,在APP 工程组中添加 hwjs.c 文件(里面包含了红外解码驱动程序),在 StdPeriph_Driver 工程组中添加stm32f10x_exti.c 库文件。EXTI 操作的库函数都放在stm32f10x_exti.c和stm32f10x_exti.h文件中, 所以使用到EXTI就必须加入 stm32f10x_exti.c文件,同时还要包含对应的头文件路径。 这里我们分析几个重要函数。
* \; u* |7 s0 ]& s) c$ |
外部中断初始化函数 我们知道红外接收头数据输出管脚是接在 PG15 上, 所以配置 PG15 为外部中断功能,初始化代码如下: - /****************************************************************6 l- E- Z" X7 z0 v
- * 函 数 名 : Hwjs_Init
) k2 y8 T: _" X - * 函数功能 : 红外端口初始化函数 时钟端口及外部中断初始化! f( j; n8 }- l) N: T* ?
- * 输 入 : 无! P5 n- L4 g7 c2 u
- * 输 出 : 无/ l* N l- l) r& v3 k
- *****************************************************************/
( ?4 F% ]+ Q6 l# \ - void Hwjs_Init()9 B# e1 K" X0 A: @
- {7 D1 u$ p* V1 H. q
- GPIO_InitTypeDef GPIO_InitStructure;9 l! H% v5 M! n$ o( V6 J- a; B X
- EXTI_InitTypeDef EXTI_InitStructure;' b8 S( v( A9 k, x
- NVIC_InitTypeDef NVIC_InitStructure;
# Q" V8 e6 i p7 e! e- X1 M" } - /* 开启 GPIO 时钟及管脚复用时钟 */% |5 e: V7 H- J
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG|RCC_APB2Periph_AFIO,ENABLE);2 t; Y9 E( C, z P$ }* U6 p. r4 T
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;//红外接收
) Y8 i& q4 _2 u8 J - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
7 P4 B# r8 ?% k5 x0 A - GPIO_Init(GPIOG,&GPIO_InitStructure);
0 B/ i# a9 v2 ]9 n& w - GPIO_EXTILineConfig(GPIO_PortSourceGPIOG, GPIO_PinSource15); //选择 GPIO 管脚用作外部中断线路8 T/ ?- R/ p2 E
- EXTI_ClearITPendingBit(EXTI_Line15);7 n7 p5 `5 A E/ c: ?& D. }. ~" M
- /* 设置外部中断的模式 */
u1 b+ N! {6 l9 o - EXTI_InitStructure.EXTI_Line=EXTI_Line15;! x3 b# \9 v0 C/ k# @: y2 U% r8 S
- EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
4 P2 U) c1 i# L - EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;. h" f8 p7 C f% j
- EXTI_InitStructure.EXTI_LineCmd=ENABLE;/ X+ _: X" \4 i1 j0 p7 K+ a
- EXTI_Init(&EXTI_InitStructure);
) \4 k) s' i: |$ }' \. h8 F% F - /* 设置 NVIC 参数 */
. f% |4 }: X% M5 F9 A2 L - NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //打开全局中断+ c6 R0 I) A4 {. a
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级为 0
: O1 M: D' n+ L" q - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //响应优先级为1
8 }! i& M* _" E/ _ - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能
! b# B1 L! { A/ E ~" K - NVIC_Init(&NVIC_InitStructure);" C, J7 t, m, |8 N
- }
复制代码
5 c2 d3 o# `, K
在Hwjs_Init()函数中,首先使能 GPIOG 端口和 AFIO 时钟,然后将PG15 映射到外部中断线 15 上, 并配置 PG15 为上拉输入模式, 最后初始化 EXTI 及NVIC,这里需要注意,PG15 的外部中断通道是 EXTI15_10_IRQn。
* T2 x6 o6 ^- d) u( _9 d
红外解码函数2 d/ \9 C/ e0 c7 ~; z( l3 e( q2 `% x
初始化外部中断后,中断就已经开启了,当 PG15 引脚上来了一个下降沿,就会触发一次中断,在中断内我们可以计算高电平时间,通过高电平时间判断是否进入引导码及数据 0 和1。具体代码如下: - void EXTI15_10_IRQHandler(void) //红外遥控外部中断4 ~1 l9 w7 Y0 U( K. i
- {
; l: Z, M, s, v5 R- w - u8 Tim=0,Ok=0,Data,Num=0;
/ M; l5 J7 f. d, V - while(1), a- ?, e0 G- x+ y+ {1 X
- {
. V0 U4 ^; o4 {( F - if(GPIO_ReadInputDataBit(GPIOG,GPIO_Pin_15)==1)
7 I% O' f" ]4 Z - {
9 z/ n. W4 @6 N, F. R - Tim=HW_jssj();//获得此次高电平时间3 Y* @5 F$ I) t' r
- if(Tim>=250) break;//不是有用的信号
9 S t" d. @3 ]' k! J4 Q+ X5 Y - if(Tim>=200 && Tim<250)
% l/ G% V& q9 ?/ c* k: h2 q1 w - {- K, ?8 j) X" h* g. f
- Ok=1;//收到起始信号& Z G9 K& j" m' ^0 B9 J4 F, E6 [) J& {
- }4 Y& O! l [; k, r' n( I- E
- else if(Tim>=60 && Tim<90)
" v* G; c# F, ^ - {
: W8 K, C% g( d4 X: @# B - Data=1;//收到数据 1( d2 S' v# H) Y) c! x
- }8 {& r! ^4 A0 ?6 Y5 H
- else if(Tim>=10 && Tim<50)& H2 e, }3 _4 v( @* k1 d$ s
- {% D# ]$ n5 i6 @9 ], v7 n/ t5 ^
- Data=0;//收到数据 0
. |$ T# r3 J) u* X5 M* E5 b - }; l ]! l: {& L x* y& U
- if(Ok==1)- s3 \8 S7 d! B# m+ R
- {* @+ K7 [$ i1 L m1 ]4 H7 s4 p
- hw_jsm<<=1;
8 \( L9 I9 N% K% H# e& T( m8 T0 L( ? - hw_jsm+=Data;
; Y% W+ v% S8 Q- f2 t - if(Num>=32)
, B3 ], |. X% H" ]8 P2 q7 s - {
. f" Y; p$ |( x7 b - hw_jsbz=1;
+ J" q7 k/ ?3 K. ] ?& Q$ u5 Z - break;! d# ^5 _8 K- U3 ?
- }, G; n8 z6 D( {& w
- }9 e3 u' C; B2 \8 f# z) S
- Num++;
# e( @" A! H+ }6 g) O3 T& F2 F& D# @1 a - }6 j7 g7 R) g9 v
- }6 T5 i; A, z8 D
- EXTI_ClearITPendingBit(EXTI_Line15);: v0 j0 I! y! f, E. a1 q9 \0 Z
- }
复制代码
7 L* _' [# m/ J2 m
中断函数内调用了 HW_jssj函数获取高电平时间,此函数代码如下: - /****************************************************************8 ~7 B) I9 U g. C7 u9 J' x' o
- * 函 数 名 : HW_jssj
3 @. h0 `0 ` z9 ^/ N - * 函数功能 : 高电平持续时间,将记录的时间保存在t中返回,其中一次大约20us8 U& M; I# H2 v
- * 输 入 : 无
! g1 r* h- c' k( Z L - * 输 出 : t
/ b! ^2 h/ ^+ N' I2 l+ {9 w2 v - *****************************************************************/
3 x2 Y& n5 ^/ F% j! V/ w$ y - u8 HW_jssj()
& H# z, L( p/ S0 _% S' a' n - {
9 D5 ?: K* @* u, }2 O$ c- M% n - u8 t=0;
1 p; G" f. j/ s, t5 h - while(GPIO_ReadInputDataBit(GPIOG,GPIO_Pin_15)==1)//高电平
f, a, Z+ Q+ C2 v/ n# {7 T - {
/ p' }3 P; O! o( I; j' [ - t++;
3 |3 c: \0 Q! C, w* _# } - delay_us(20);
+ `2 ^) ]/ U3 [* p - if(t>=250) return t;//超时溢出+ L! |, K* Y; [/ d4 F: s4 E X, @
- }5 T) V e6 b& {0 J$ n
- return t;
, s+ f3 u: Z, q- \ - }
复制代码2 p0 `/ {- B7 p: ^( |
通过此函数返回值就可以计算高电平时间,t 累加一次积累的时间为20us。中断函数内,将此函数返回值保存在变量 Tim 中,通过 Tim 值就可以判断是否进入引导码,如果是引导码,就可以接着判断接收的数据是1 还是 0,然后将数据进行按低位在前高位在后顺序保存在 hw_jsm 内,当接收完 32 位数据后将hw_jsbz 标志位置 1,并退出解码函数。这里要注意,判断是否为引导码、数据1 和 0时,我们不要硬性的固定在高电平区分时间上,而应该给它一个在固定值旁的范围判断,以防止因为干扰而导致误操作。引导码及数据 0 和 1 的高电平区分时间在前面介绍 NEC 码已经讲解了,不清楚的可以回过头看下。
2 N% j$ G; t3 l
主函数
& u, r6 `0 E8 {, C) L 编写好外部中断初始化和红外解码函数后,接下来就可以编写主函数了,代码如下: - /****************************************************************0 {% \( U' h* P# t- C! D5 N5 b9 b
- * 函 数 名 : main( w( P0 s$ h: ~ P% L/ F$ {
- * 函数功能 : 主函数5 ^& I" t# @% b" U% H+ J/ l: Q
- * 输 入 : 无1 ]& m! S- N$ p' x* i2 k3 X6 J* a
- * 输 出 : 无
6 J2 {/ t) [( b, t( F0 G9 L - *****************************************************************/
2 S' } g( n5 y$ D) j - int main()' V% L4 E& v) m6 ]
- {% W0 o9 I8 \7 S9 Y3 |7 w9 V
- u8 i=0;/ R8 m! k' w3 y
- SysTick_Init(72);
- M ^" O& Q: P/ c0 E+ | - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2 组
! Q: v4 w" c+ S) F* ~" m - LED_Init();
4 P6 R$ [7 X4 a: [ - USART1_Init(9600);% _2 G5 T+ h B9 W7 J8 R8 e k5 ?9 S' f
- Hwjs_Init();! B p. U& h6 \2 f0 k8 W
- while(1)
; J( ^& y# R# F7 F: _: U4 E - {
6 c6 o/ w6 d ~0 W/ I+ L6 `3 \ - if(hw_jsbz==1) //如果红外接收到
* R& r) P0 n F - {
& O& b. a7 G- J1 y - hw_jsbz=0; //清零
7 L6 v; T3 g( C6 c! |0 M - printf("红外接收码 %0.8X\r\n",hw_jsm); //打印% z7 S4 J: Y4 A! W- ]& J
- hw_jsm=0; //接收码清零: r+ c! `, V: L6 p
- }9 O% \1 w! L) |% B2 w8 v
- i++;0 g7 i8 b: W2 v
- if(i%20==0)- l; }& E% _1 t8 s) s7 Q; Z3 f+ t
- {
8 A7 _: _+ Y6 X - led1=!led1;6 R% i1 l1 f; m U* Z! P6 s
- }. b) u0 I% O/ C- p B8 V$ d
- delay_ms(10);
, R" m2 H/ j! O9 b. `% ^* K+ c - }
. o, V% p# C# D* w9 u - }
复制代码2 ~( f- _8 e6 Y# S2 s
主函数实现的功能很简单,首先调用之前编写好的硬件初始化函数,包括 SysTick系统时钟, 中断分组, LED初始化等。然后调用我们前面编写的Hwjs_Init函数初始化 PG15 外部中断功能,最后进入 while 循环,通过 hw_jsbz 标志判断是否解码成功,如果成功将解码后的数据 hw_jsm 打印输出,同时控制 D1 指示灯闪烁,提示系统正常运行。 $ W) _0 z* x9 t
实验现象7 O, O7 K6 C' P, s! p6 q
将工程程序编译后下载到开发板内,可以看到 D1 指示灯不断闪烁,表示程序正常运行。当按下红外遥控器键时,串口将打印输出解码后的数据(地址码+地址反码+控制码+控制反码)。如果想在串口调试助手上看到输出信息,可以打开 “串口调试助手” , 首先勾选下标号1 DTR框,然后再取消勾选。这是因为此串口助手启动时会把系统复位住,通过 DTR状态切换下即可。然后设置好波特率等参数后,串口助手上即会收到串口发送过来的信息。(串口助手上先勾选下标号 1 DTR 框,然后再取消勾选)如下图所示:
5 [" u0 I6 a. G0 ~4 A u Q 我们配的遥控器所有键的地址码与反码都是相同的,不同的是控制码和控制反码,其实知道了控制码就知道了控制反码,所以如果要使用红外遥控控制其他设备,可以通过区分控制码来实现。
+ Z4 n! C4 g1 U) @* P' u$ y |