玩玩Stm32
文件结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 G:. ├───CORE | ├───HARDWARE ├───OBJ ├───STM32F10x_FWLib │ ├───inc │ └───src ├───SYSTEM │ ├───delay │ ├───sys │ └───usart └───USER | ├───Listings └───Objects
编写规范:
用户编写的执行代码写在main.c中,其中#include "stm32f10x.h"
作用相当于C51的#include <reg51.h>
,是操作寄存器的主要固件库文件,在任何地方引用到固件库函数时都需要导入这个文件。
stm32f10x_it.c、stm32f10x_it.h
, 专门存放中断服务函数的C文件 ,大多中断函数都 写在此文件中,方便 管理中断函数,但并不是一定要写在这里面。
GPIO
◆端口复用功能
STM32的大部分端口都具有复用功能。
所谓复用,就是一些端口不仅仅 可以做为通用lO口,还可以复用 为一些外设引脚,比如PA9,PA10可以复用为STM32的串口 1引脚。
▲作用:最大限度的利用端口资源
◆端口重映射功能
就是可以把某些功能引脚映射到其他引脚。
比如串口1默认引脚是PA9,PA10可以通过配置重映射映射到PB6,PB7
作用:为了方便布线
▲所有I0口都可以作为中断输入
工作模式:
推挽输出:可以输出强高低电平
上拉输入: 一端是接地低电平,所以默认情况 下另一端需要检测到高电平 (按键扫描中,三个引脚需要设置为IPU,按下时<==>输入口检测到低电平)
下拉输入:(按下时<==>输入口检测到低电平)
GPIO重要函数
1 2 3 4 5 6 7 8 9 10 11 12 13 void GPIO_Init(GPIO_TypeDef* GPIOx,GPIO_InitTypeDef* GPIO_InitStruct); uint8t GPIO_ReadlinputDataBit(GPIO TypeDef* GPIOx,uint16_t GPIO_Pin); uint16_t GPIO_ReadinputData(GPIO_TypeDef* GPIOx);uint8t GPIO_ReadOutputDataBit(GPiO_TypeDef* GPiOx,uint16_t GPIO_Pin); uint16t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx); void GPIO_SetBits(GPIOTypeDef* GPIOx, uint16_t GPIOPin); void GP1O_ResetBits(GPIOTypeDef GPIOx,uint16_t GPIOPin); void GPIO_WriteBit(GPIOTypeDef* GPIOx,uint16_t GPIO_Pin,BitAction BitVal); void GPIO_Write (GPIOTypeDef* GPIOx, uint16_t PortVal)
具体说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void GPIO_Init (GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
初始化示例
1 2 3 4 5 6 7 8 9 10 GPIO_InitTypeDef GPIO_InitStructure; GPIO InitStructure.GPIO Pin =GPIO_Pin_5; GPIO InitStructure.GPIO Mode=GPIO_Mode_Out_PP; GPIO InitStructure.GPIO Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStructure);
▲在使用GPIO前,需要使能IO口时钟,调用函数RCC_APB2PeriphColckCmd();
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 void RCC_APB2PeriphClockCmd (uint32_t RCC_APB2Periph, FunctionalState NewState)
提示:不能通过IO口直接驱动大功率器件。
△复位之后,IO口默认为浮空状态,如果不接下拉电阻,那么电平不确定为高还是低电平。(到是小电流的时候,电流会直接通过下拉电阻到地,不会经过三极管;只有电流足够大,才会经过三极管)
demo : 跑马灯实验
当将PE5设置为低电平时,通过上拉电阻连到VCC后,LED就能点亮。PE5被设置为高电平时与上拉高电平之间没有压差,此时LED熄灭。
完整代码:
led.c文件内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include "led.h" #include "stm32f10x.h" void LED_Init (void ) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE,ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOB,&GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOE,&GPIO_InitStruct); GPIO_SetBits(GPIOB, GPIO_Pin_5); }
main.c文件内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include "stm32f10x.h" #include "led.h" #include "delay.h" int main (void ) { delay_init(); LED_Init(); while (1 ){ GPIO_SetBits(GPIOB,GPIO_Pin_5); GPIO_SetBits(GPIOE,GPIO_Pin_5); delay_ms(500 ); GPIO_ResetBits(GPIOB,GPIO_Pin_5); GPIO_ResetBits(GPIOE,GPIO_Pin_5); delay_ms(500 ); } }
操作IO口的三种方式:
位操作:#define BEEP PBout(8);
后BEEP = 1
库函数:GPIO_SetBits(GPIOB, GPIO_Pin_8);
寄存器:
中断管理
对STM32中断进行分组,组0~4。同时,对每个中断设置一个抢占优先级和一个响应优先级值。IP bit决定了对每个中断共有2^4(位) = 16级的中断优先级设置
高优先级的抢占优先级是可以打断 正在进行的低抢占优先级中断的。
抢占优先级相同的中断,高响应优先级不可以打断 低响应优先级的中断。
抢占优先级相同的中断,当两个中断同时发生 的情况下,哪个响应优先级高,哪个先执行。
如果两个中断的抢占优先级和响应优先级都是一样 的话,则看哪个中断先发生就先执行;
总结:中断嵌套执行看抢占优先级;占优先级相同时,响应优先级高的先响应;两者都一样的话,执行顺序看发生的时间
△.优先级0最高,4最低。
▲.系统代码执行过程中,只设置一次中断优先,一般不会再改变分组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void NVIC_PriorityGroupConfig (uint32_t NVIC_PriorityGroup) void NVIC_Init (NVIC_InitTypeDef* NVIC_InitStruct)
中断优先级控制的寄存器组:IP[240]对每个中断进行管理,STM32F10x系列一共有60个可屏蔽中断。全称是:Interrupt Priority Registers
中断优先级设置步骤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void NVIC_Priority_GroupConfig(uint32_t NVIC_PriorityGroup); void NVIC Init (NVIC_InitTypeDef* NVIC_Initstructy;
串口通信
异步: 跟系统时钟无关
同步: 跟系统时钟有关
波特率计算方法:T x / R x B a u d r a t e = f P C L K x ( 16 ∗ U S A R T D I V ) \mathrm{Tx} / \mathrm{Rx} \quad Baud rate=\frac{f_{P C L K x}}{(16 * U S A R T D I V)} T x / R x B a u d r a t e = ( 1 6 ∗ U S A R T D I V ) f P C L K x
配置的一般步骤:
串口作为外设,需要使能:RCC_APB2PeriphClockCmdO;
、以及使能GPIO的时钟
GPIO端口模式设置GPIOInit0;
,模式设置为GPIO_Mode_AFPP
复用推挽(PA.9/10复用为串口1)
串口参数初始化
使能串口USART Cma);
串口数据收发
▲ UART串口通信 只需连接TX,RX,GND , 一般不需要连接VCC
A:TX、RX是正负压的,所以有个地做参考就行了
A: 通信两端一般都有各自的供电电压,所以不需要VCC,只有一端没有电源的情况下才会用VCC向对方输送电源
A:就像像耳机只要联地、音频左、音频右,而不联vcc一个道理
正点原子提供的USART库:
以回车换行结束的协议
usart.h
1 2 3 4 5 6 7 #define USART_REC_LEN 200 #define EN_USART1_RX 1 extern u8 USART_RX_BUF[USART_REC_LEN]; extern u16 USART_RX_STA; void uart_init (u32 bound) ;
数据全保存在USART_RX_BUF中。根据STA的有效数据个数比如50个,将USART_RX_BUF中前50个数据拿出处理。处理完所有标志位将被清零
▲程序要求,发送的字符是以回车换行结束(Ox0D,0x0A)。
△串口调试助手里勾选"发送新行"选项
usart.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 void USART1_IRQHandler (void ) { u8 Res; #if SYSTEM_SUPPORT_OS OSIntEnter(); #endif if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { Res =USART_ReceiveData(USART1); if ((USART_RX_STA&0x8000 )==0 ) { if (USART_RX_STA&0x4000 ) { if (Res!=0x0a )USART_RX_STA=0 ; else USART_RX_STA|=0x8000 ; } else { if (Res==0x0d )USART_RX_STA|=0x4000 ; else { USART_RX_BUF[USART_RX_STA&0X3FFF ]=Res ; USART_RX_STA++; if (USART_RX_STA>(USART_REC_LEN-1 ))USART_RX_STA=0 ; } } } } #if SYSTEM_SUPPORT_OS OSIntExit(); #endif }
Res =USART_ReceiveData(USART1);
获得的是当前接收的字符,如果使用中断,那么调用USART_SendData(USART1, Res)
就能接收一个字符,发送一个字符。
USART_RX_BUF的作用是,保存一次字符串发送过来的所有数据
USART_RX_STA是个寄存器,通过Bit14,Bit15来判断接收是否有效
main.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 int main (void ) { u16 t; u16 len; u16 times=0 ; delay_init(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); uart_init(115200 ); LED_Init(); KEY_Init(); while (1 ){ if (USART_RX_STA&0x8000 ){ len=USART_RX_STA&0x3fff ; printf ("\r\n您发送的消息为:\r\n\r\n" ); for (t=0 ;t<len;t++){ USART_SendData(USART1, USART_RX_BUF[t]); while (USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET); } printf ("\r\n\r\n" ); USART_RX_STA=0 ; }else { times++; if (times%5000 ==0 ){ printf ("\r\n战舰STM32开发板 串口实验\r\n" ); printf ("正点原子@ALIENTEK\r\n\r\n" ); } if (times%200 ==0 )printf ("请输入数据,以回车键结束\n" ); if (times%30 ==0 )LED0=!LED0; delay_ms(10 ); } } }
▲printf可以将发送到串口,默认是USART1,如果需要修改,在usart.c的fputc函数中,将USART1修改即可
外部中断
每个IO口都可以作为外部中断输入
IO与中断线的映射,16* 7 = 112, 一共有16个中断线
Q:什么是中断线,能干什么? A:中断线能发出中断请求
△.同一时刻只有一个引脚能映射到某根中断线
原理:
GPIOX.0映射到EXT10
GPIOX.1映射到EXT11
GPIOX.15映射到EXTI15
e.g.PA.0~PG.0可以映射到EXIT0
I0口外部中断在中断向量表中只分配了7个中断向量 ,也就是只能使用7个中断服务函数
常用库函数
1 2 3 4 5 6 7 8 9 void GPIO_EXTILineConig (uint8_t GPIO,uint8_t PortSource,uint8_t GPIO_PinSource) exp:GPIO_EXTILineContig(GPIO_PortSourceGPIOE, GPIO_PinSource2) void EXTIInit (EXTI_InitTypeDef* EXTI_InitStruct) ;ITStatus EXTI_GetlTStatus (uint32_t EXTI_hLine) ;void EXTI_ClearlTPendingBit (uint32_t EXTI_Line)
1 2 3 4 5 EXTI_InitStructure.EXTI_Line=EXTI_Line2; EXTI_InitStructure.EXTI_Mode =EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger =EXTI_Trigger_Faling; EXTI_InitStructure.EXTI_LineCmd =ENABLE; EXTI_Init(& EXTI_InitStructure);
配置的一般步骤
demo:
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 void EXTIX_Init (void ) { EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; KEY_Init(); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2); EXTI_InitStructure.EXTI_Line=EXTI_Line2; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3); EXTI_InitStructure.EXTI_Line=EXTI_Line3; EXTI_Init(&EXTI_InitStructure); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); EXTI_InitStructure.EXTI_Line=EXTI_Line0; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } void EXTI2_IRQHandler (void ) { delay_ms(10 ); if (KEY2==0 ) { LED0=!LED0; } EXTI_ClearITPendingBit(EXTI_Line2); } void EXTI3_IRQHandler (void ) { delay_ms(10 ); if (KEY1==0 ) { LED1=!LED1; } EXTI_ClearITPendingBit(EXTI_Line3); } void EXTI4_IRQHandler (void ) { delay_ms(10 ); if (KEY0==0 ) { LED0=!LED0; LED1=!LED1; } EXTI_ClearITPendingBit(EXTI_Line4); }
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int main (void ) { delay_init(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); uart_init(115200 ); LED_Init(); BEEP_Init(); KEY_Init(); EXTIX_Init(); LED0=0 ; while (1 ){ printf ("OK\r\n" ); delay_ms(1000 ); } }
附录:
u8、u16、Size_t是什么类型?
u8、u16
1 2 3 4 5 6 7 8 9 typedef signed char int8_t ; typedef signed short int16_t ; typedef signed long int32_t ; typedef unsigned char uint8_t ; typedef unsigned short uint16_t ; typedef unsigned long uint32_t ;
size_t
size_t是C++标准在stddef.h中定义的。这个类型足以用来表示对象的大小。size_t的真实类型与操作系统有关。size_t在32位架构上是4字节,在64位架构上是8字节,在不同架构上进行编译时需要注意这个问题。而int在不同架构下都是4字节,与size_t不同;且int为带符号数,size_t为无符号数。
电平相关知识
单片机是一种数字集成芯片,数字电路中只有两种电平高电平和低电平。为了让大家在刚起步的时候对电平特性有一个清晰的认识,我们暂且定义单片机输出与输入为TTL 电平,其中高电平为+5V,低电平为0V。计算机的串口为RS-232C 电平。这里要强调的是,RS-232C电平为负逻辑电平。因此当计算机与单片机之间要通信时,需要加电平转换芯片,我们在TX-1C单片机实验板上所加的电平转换芯片是MAX232 。
常用的逻辑电平有TTL、CMOS、LVTTL、ECL、PECL、 GTL 、RS-232. RS-422. RS-485、LVDS等.其中TTL和CMOS的逻辑电平按典型电压可分为四类:5V系列(5V TL和5V CMOS)、3.3V 系列,2.5V 系列和1.8V系列,
T电平信号用的最多,这是因为,数据表示通常采用二进制,+5V等价于逻辑1,0V等价于逻辑0).这被称为TTL(晶体管一晶体管逻辑电平)信号系统,这是计算机处理器控制的设备内部各部分之间通信的标准技术。TTL电平信号对于计算机处理器控制的设备内部的数据传输是很理想的,首先计算机处理器控制的设备内部的数据传输对于电源的要求不高,热损耗也较低,另外TTL电平信号直接与集成电路连接而不需要价格昂贵的线路驱动器
功能函数
判断u8数组开头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <string.h> int u8cmp (u8 *arr, const char *str) { int i; int len = strlen (str); for ( i = 0 ; i < len; i++){ if ( arr[i] != str[i]){ if (arr[i] == '\0' ) return -1 ; return 0 ; } } return 1 ; }