STM32F10*系列单片机开发

开发方式

常用于开发STM32的有三种方式:

  • 直接操作寄存器

  • 使用官方库函数

  • ST官方推出的HAL库

这三者区别在于:操作寄存器过于麻烦,库函数只适合某系列的STM32单片机(比如STM32F1的代码就不能直接移植到STM32F4上去),HAL库则适用于STM32全系列单片机。且目前库函数版本官方已经停止更新,大势所趋是使用__HAL库__。

不同芯片的开发

不同的STM32芯片可以共用同一份HAL库,但是并不意味着代码可以完全不改,不同的芯片引脚数、引脚用处可能不尽相同。比如STM32F103ZET6有144个引脚,STM32F103C8T6只有48个引脚。

在开发时,我们需要查看相应的数据手册和电路板的原理图。

以网上找到的STM32F103C8T6资料为例(方便后续查找):

1

2

3

4

可以看到虽然引脚数不同,但是对于同一引脚对应的主要功能和额外功能都是相同的。因此我们在将其他芯片的代码迁移过来时,需要额外关注使用的芯片是否具有该引脚,没有则需要寻找合适的引脚替代以实现想要的功能。此外我们还需要关注芯片的外设,以下图为例,正点原子的精英版STM32F1的LED灯的引脚为PB5和PE5,而STM32F103C8T6的LED灯引脚为PC13。

schematic

示例

下面以STM32F103C8T6实现红外遥控实验:

主要是根据正点原子提供的HAL库修改实现

代码

main函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "remote.h"

int main(void)
{
u8 key;

HAL_Init(); //初始化HAL库
Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M
delay_init(72); //初始化延时函数
LED_Init(); //初始化LED


Remote_Init(); //初始化 红外接收
while(1)
{
key=Remote_Scan();
if(key)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET); //引脚PC3拉低,亮
}
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET); //引脚PC13拉高,灭
}
}

红外接收源文件__remote.c:__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#include "remote.h"
#include "delay.h"
TIM_HandleTypeDef TIM4_Handler; //定时器4句柄

//红外遥控初始化
//设置IO以及TIM4_CH4的输入捕获
void Remote_Init(void)
{
TIM_IC_InitTypeDef TIM4_CH1Config;

TIM4_Handler.Instance=TIM4; //通用定时器4
TIM4_Handler.Init.Prescaler=(72-1); //预分频器,1M的计数频率,1us加1.
TIM4_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; //向上计数器
TIM4_Handler.Init.Period=10000; //自动装载值
TIM4_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
HAL_TIM_IC_Init(&TIM4_Handler);

//初始化TIM1输入捕获参数
TIM4_CH1Config.ICPolarity=TIM_ICPOLARITY_RISING; //上升沿捕获
TIM4_CH1Config.ICSelection=TIM_ICSELECTION_DIRECTTI;//映射到TI4上
TIM4_CH1Config.ICPrescaler=TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM4_CH1Config.ICFilter=0x03; //IC4F=0003 8个定时器时钟周期滤波
HAL_TIM_IC_ConfigChannel(&TIM4_Handler,&TIM4_CH1Config,TIM_CHANNEL_4);//配置TIM4通道4
HAL_TIM_IC_Start_IT(&TIM4_Handler,TIM_CHANNEL_4); //开始捕获TIM4的通道4
__HAL_TIM_ENABLE_IT(&TIM4_Handler,TIM_IT_UPDATE); //使能更新中断
}

//定时器1底层驱动,时钟使能,引脚配置
//此函数会被HAL_TIM_IC_Init()调用
//htim:定时器1句柄
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_TIM4_CLK_ENABLE(); //使能TIM4时钟
__HAL_RCC_GPIOB_CLK_ENABLE(); //开启GPIOB时钟

GPIO_Initure.Pin=GPIO_PIN_9; //PB9
GPIO_Initure.Mode=GPIO_MODE_AF_INPUT; //复用输入
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);

HAL_NVIC_SetPriority(TIM4_IRQn,1,3); //设置中断优先级,抢占优先级1,子优先级3
HAL_NVIC_EnableIRQ(TIM4_IRQn); //开启ITM4中断
}

//遥控器接收状态
//[7]:收到了引导码标志
//[6]:得到了一个按键的所有信息
//[5]:保留
//[4]:标记上升沿是否已经被捕获
//[3:0]:溢出计时器
u8 RmtSta=0;
u16 Dval; //下降沿时计数器的值
u32 RmtRec=0; //红外接收到的数据
u8 RmtCnt=0; //按键按下的次数

//定时器4中端服务函数
void TIM4_IRQHandler(void)
{
HAL_TIM_IRQHandler(&TIM4_Handler);//定时器共用处理函数
}

//定时器更新(溢出)中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM4)
{
if(RmtSta&0x80)//上次有数据被接收到了
{
RmtSta&=~0X10; //取消上升沿已经被捕获标记
if((RmtSta&0X0F)==0X00)RmtSta|=1<<6;//标记已经完成一次按键的键值信息采集
if((RmtSta&0X0F)<14)RmtSta++;
else
{
RmtSta&=~(1<<7);//清空引导标识
RmtSta&=0XF0; //清空计数器
}
}
}
}

//定时器输入捕获中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//捕获中断发生时执行
{
if(htim->Instance==TIM4)
{
if(RDATA)//上升沿捕获
{
TIM_RESET_CAPTUREPOLARITY(&TIM4_Handler,TIM_CHANNEL_4); //一定要先清除原来的设置!!
TIM_SET_CAPTUREPOLARITY(&TIM4_Handler,TIM_CHANNEL_4,TIM_ICPOLARITY_FALLING);//CC4P=1 设置为下降沿捕获
__HAL_TIM_SET_COUNTER(&TIM4_Handler,0); //清空定时器值
RmtSta|=0X10; //标记上升沿已经被捕获
}else //下降沿捕获
{
Dval=HAL_TIM_ReadCapturedValue(&TIM4_Handler,TIM_CHANNEL_4);//读取CCR4也可以清CC4IF标志位
TIM_RESET_CAPTUREPOLARITY(&TIM4_Handler,TIM_CHANNEL_4); //一定要先清除原来的设置!!
TIM_SET_CAPTUREPOLARITY(&TIM4_Handler,TIM_CHANNEL_4,TIM_ICPOLARITY_RISING);//配置TIM4通道4上升沿捕获
if(RmtSta&0X10) //完成一次高电平捕获
{
if(RmtSta&0X80)//接收到了引导码
{

if(Dval>300&&Dval<800) //560为标准值,560us
{
RmtRec<<=1; //左移一位.
RmtRec|=0; //接收到0
}else if(Dval>1400&&Dval<1800) //1680为标准值,1680us
{
RmtRec<<=1; //左移一位.
RmtRec|=1; //接收到1
}else if(Dval>2200&&Dval<2600) //得到按键键值增加的信息 2500为标准值2.5ms
{
RmtCnt++; //按键次数增加1次
RmtSta&=0XF0; //清空计时器
}
}else if(Dval>4200&&Dval<4700) //4500为标准值4.5ms
{
RmtSta|=1<<7; //标记成功接收到了引导码
RmtCnt=0; //清除按键次数计数器
}
}
RmtSta&=~(1<<4);
}
}
}

//处理红外键盘
//返回值:
// 0,没有任何按键按下
//其他,按下的按键键值.
u8 Remote_Scan(void)
{
u8 sta=0;
u8 t1,t2;
if(RmtSta&(1<<6))//得到一个按键的所有信息了
{
t1=RmtRec>>24; //得到地址码
t2=(RmtRec>>16)&0xff; //得到地址反码
if((t1==(u8)~t2)&&t1==REMOTE_ID)//检验遥控识别码(ID)及地址
{
t1=RmtRec>>8;
t2=RmtRec;
if(t1==(u8)~t2)sta=t1;//键值正确
}
if((sta==0)||((RmtSta&0X80)==0))//按键数据错误/遥控已经没有按下了
{
RmtSta&=~(1<<6);//清除接收到有效按键标识
RmtCnt=0; //清除按键次数计数器
}
}
return sta;
}

红外接收头文件__remote.h:__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef __REMOTE_H
#define __REMOTE_H
#include "sys.h"

#define RDATA PBin(9) //红外数据输入脚

//红外遥控识别码(ID),每款遥控器的该值基本都不一样,但也有一样的.
//我们选用的遥控器识别码为0
#define REMOTE_ID 0

extern u8 RmtCnt; //按键按下的次数

void Remote_Init(void); //红外传感器接收头引脚初始化
u8 Remote_Scan(void);
#endif

LED灯__led.c__:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "led.h"


void LED_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;

__HAL_RCC_GPIOC_CLK_ENABLE(); //开启GPIOC时钟


GPIO_Initure.Pin=GPIO_PIN_13; //PC13
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //高速
HAL_GPIO_Init(GPIOC,&GPIO_Initure);


HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET); //PC13置1,默认初始化后灯灭

}

接线

该代码实现红外遥控器发送信号,在红外接收模块接收到信号后,单片机控制LED灯亮。主要参考了【正点原子】精英STM32F103开发板资料的实验1 跑马灯实验和实验27 红外遥控实验。连接的引脚为:

负极 —> GND

正极 —> 3.3V

输出 —> PB9

remote

总结

在做某工程的开发时,先找到别人已经做好的项目,然后在此基础上实现会轻松很多。正所谓,要站在巨人的肩膀上。