STM32理论——其他应用笔记

1. 搭建软件框架


打开Keil 创建一个工程,在工程文件夹中新建如下图文件夹:
在这里插入图片描述

  • USER:存放用户代码,main.c 和一些STM32 官方初始化源文件;
  • HARDWARE:存放每个工程用到的外设驱动代码文件(非官方固件库),他的实现通过调用文件夹FWLib中的固件库文件实现;

led.c 里面调用 stm32f10x_gpio.c 里面的函数对GPIO 进行初始化;

  • SYSTEM:存放共享代码,即几乎每个工程都会用到的代码,包含 Systick 延时函数, IO 口位带操作以及串口相关函数;
  • CORE:存放固件库必须的核心文件和启动文件,这里面的文件用户不需要修改;
  • FWLib 存放ST 官方提供的外设驱动固件库文件,这些文件可根据工程需要来添加删除,每个.c源文件对应一个.h头文件;

2. 系统


2.1 复位

为保护MCU寿命,一般把按键复位功能配置为各个模组恢复复位状态而不是对MCU直接软件复位(断电也算是对MCU进行软件复位),而软件复位则配置在串口指令中;

函数索引:
void ResetMCU(void);
void ResetButtonScan(void);
/**********************************************************************************************************
*	函 数 名: ResetMCU
*	功能说明: 对MCU进行软件复位
*	形    参:无
*	返 回 值: 无
**********************************************************************************************************/
void ResetMCU(void)
{
			delay_ms(100);
			__set_FAULTMASK(1); //关闭所有中断
			NVIC_SystemReset(); //MCU软件复位
}
/**********************************************************************************************************
*	函 数 名: ResetButtonScan
*	功能说明: 判断复位按钮是否被按下,是则恢复各种参数到初始值
*	形    参:无
*	返 回 值: 无
**********************************************************************************************************/
void ResetButtonScan(void)
{
   if(ResetButton==0)  
   {
		 delay_ms(10); //消抖
		 if(ResetButton==0) 
		 {
			 OutputControl(11,ON); // Reset 按钮灯亮
			/* 各种参数恢复原值 */	
			PortInfom  *pb=&InOutMessage;
			// ....
			pb->StartKey = FALSE;
			state.Button_flag=0;
			delay_ms(1000);
			printfU4("Reset Pass\r\n@_@"); //返回复位成功的消息
		 }
	}
}

2.2 初始化

MCU初始化应该完成各种数据的初始化、所有IO的复位,以备MCU正常工作。按实际功能需求,流程如下:

  • 变量、结构体等参数的数据初始化;
  • MCU IO初始化
  • IC器件初始化
  • 延时功能初始化
  • IIC、SPI等通信功能初始化

4. 通信


4.1 串口

寄存器相关配置、函数定义等的头文件与源文件分别位于官方文件:stm32f10x_usart.hstm32f10x_usart.c

串口配置的一般步骤
在这里插入图片描述

stm32f10x_usart.c

// 串口初始化函数的应用
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
{
...
}
// 入口参数:
// 1. USART_TypeDef* USARTx : 串口标号,即哪个串口;
// 2. USART_InitTypeDef* USART_InitStruct : 串口相关参数配置结构体指针;

typedef struct
{
  uint32_t USART_BaudRate;    			// 波特率      
  uint16_t USART_WordLength;  			// 字长        
  uint16_t USART_StopBits;    			// 停止位          
  uint16_t USART_Parity;     		    // 奇偶校验位            
  uint16_t USART_Mode;       		    // 发送接收使能        
  uint16_t USART_HardwareFlowControl;   // 硬件流控制 
} USART_InitTypeDef;

main.c

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"

 int main(void)
 {		
 	u16 t;  
	u16 len;	
	u16 times=0;
	delay_init();	    	 
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 
 	LED_Init();			    
	KEY_Init();          //
 	while(1)
	{
		if(USART_RX_STA&0x8000)  // 查看 USART_RX_STA的最高位,是否接收到 0X0A
		{					   
			len=USART_RX_STA&0x3fff;  //得到此次接收到的数据长度	
			printf("\r\n your info is:\r\n\r\n");
			for(t=0;t<len;t++)
			{
				USART_SendData(USART1, USART_RX_BUF[t]);  //向串口1发送数据,这里是把串口接收到的数据再通过串口发送出去
				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 test\r\n");
			}
			if(times%200==0)printf("input data:\n");  
			if(times%30==0)LED0=!LED0;  //闪烁LED,提示系统正在运行.
			delay_ms(10);   
		}
	}	 
 }


uart.c

// 串口初始化函数
void uart_init(u32 bound){
    // 定义结构体
    GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
  
	//USART1_TX   GPIOA.9
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
    // 关于GPIO 的模式配置,详见中文参考手册 - 8.1.11 外设的GPIO
    GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
	   
    //USART1_RX	  GPIOA.10初始化
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

    //Usart1 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
  
   //USART 初始化设置
	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式

    USART_Init(USART1, &USART_InitStructure); //初始化串口1
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接收中断
    USART_Cmd(USART1, ENABLE);                    //使能串口1 
}

//串口1中断服务程序,串口接收到数据,则执行该函数
void USART1_IRQHandler(void)                	
{
	u8 Res;
#if SYSTEM_SUPPORT_OS 		//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntEnter();    
#endif
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  // 查看串口状态,确定进入中断(接收到的数据必须是0x0d 0x0a结尾)
		{
		Res =USART_ReceiveData(USART1);	// 读取接收到的数据并保存到Res 变量中
		
		if((USART_RX_STA&0x8000)==0) // 确认接收是否完成
			{
			if(USART_RX_STA&0x4000)// 接收完成,接收到了0x0d
				{
				if(Res!=0x0a)USART_RX_STA=0;// 接收错误,重新开始
				else USART_RX_STA|=0x8000;	// 接收完成
				}
			else // 还没收到0X0D
				{	
				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 	//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntExit();  											 
#endif
} 

uart.h

#ifndef __USART_H
#define __USART_H
#include "stdio.h"	
#include "sys.h" 

#define USART_REC_LEN  			200  	//定义最大接收字节数 200
#define EN_USART1_RX 			1		//使能(1)/禁止(0)串口1接收
	  	
extern u8  USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 
extern u16 USART_RX_STA;         		//接收状态标记	
//如果想串口中断接收,请不要注释以下宏定义
void uart_init(u32 bound);
#endif

串口状态标志位USART_RX_STA 的讲解
u16 USART_RX_STA 共有 16位,其各位功能如下:
在这里插入图片描述

  • bit15:bit14 已置1,即已接收到0X0D后跟接着又接收到0X0A(换行),则该位置1,表示此次串口接收结束;
  • bit14:串口接收数据协议规定接收到的数据要以0X0D(回车)结束,若程序接收到0X0D,则该位置1
  • bit13 ~ 0:即如果接收到 100字节的数据(不含回车换行),则bit13 ~ 0为 100;

串口调试

  1. 向电路板烧录程序;
  2. 设备与电路板通过串口连接;
  3. 打开串口调试助手软件,并配置相关参数(勾选发送新行可自动在发送的数据后添加回车换行);

在这里插入图片描述

4.1.1 获取串口中断标志位

参考:关于STM32的USART_GetFlagStatus和USART_GetITStatus解析

while(USART_GetFlagStatus(USART3,USART_FLAG_TC)==RESET);  // 等待串口发送完成,

4.2 RS485

原理还是串口相关知识,此以串口 2作为485进行配置;

  • 硬件连接
    如下原理图中,USART2的发送引脚接收发器SP3485的接收引脚,另一脚同理;
    R19和R22为偏置电阻,用于保证总线空闲时,AB间电压差大约为 200mV,避免总线空闲时逻辑混乱,引脚A 通过R22接到VCC3.3,引脚B 通过R19接到GND;
    R25为匹配电阻;
    在这里插入图片描述
  • 软件

main.c

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"	 
#include "rs485.h"
 				 	
 int main(void)
 {	 
	u8 key;
	u8 i=0,t=0;
	u8 cnt=0;
	u8 rs485buf[5]; 
	 
	delay_init();	    	 //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 	//串口初始化为115200
	LED_Init();		  		//初始化与LED连接的硬件接口
	LCD_Init();			   	//初始化LCD 	
	KEY_Init();				//按键初始化		 	 
	RS485_Init(9600);	//初始化RS485
	 
 	POINT_COLOR=RED;//设置字体为红色 
	LCD_ShowString(30,50,200,16,16,"STM32-RS485 build success");	
 
 	POINT_COLOR=BLUE;//设置字体为蓝色	  
	LCD_ShowString(30,150,200,16,16,"Count:");			//显示当前计数值	
	LCD_ShowString(30,170,200,16,16,"Send Data:");		//提示发送的数据	
	LCD_ShowString(30,210,200,16,16,"Receive Data:");	//提示接收到的数据		
 									  
	while(1)
	{
		key=KEY_Scan(0);  // 扫描按键
		if(key==KEY0_PRES)// KEY0按下,发送一次数据
		{
			for(i=0;i<5;i++)
			{
				rs485buf[i]=cnt+i;//填充发送缓冲区
				LCD_ShowxNum(30+i*32,190,rs485buf[i],3,16,0X80);	//显示数据
 			}
			RS485_Send_Data(rs485buf,5);// 发送5个字节数据									   
		}		 
		RS485_Receive_Data(rs485buf,&key);  // 扫描式接收数据
		if(key)//接收到有数据
		{
			if(key>5)key=5;// 限制最大接收5 个数据
 			for(i=0;i<key;i++)LCD_ShowxNum(30+i*32,230,rs485buf[i],3,16,0X80);	//显示数据
 		}
		// 空闲状态代码
		t++; 
		delay_ms(10);
		if(t==20)
		{
			LED0=!LED0;//提示系统正在运行	
			t=0;
			cnt++;
			LCD_ShowxNum(30+48,150,cnt,3,16,0X80);	//显示数据
		}		   
	} 
}

rs485.h

#ifndef __RS485_H
#define __RS485_H			 
#include "sys.h"	 								  
	  		  	
extern u8 RS485_RX_BUF[64]; 		//接收缓冲,最大64个字节
extern u8 RS485_RX_CNT;   			//接收到的数据长度

//485模式控制.对应硬件电路中收发器SP3485的RE、DE引脚,0-接收;1-发送.
#define RS485_TX_EN		PDout(7)	
//如果想串口中断接收,请不要注释以下宏定义
#define EN_USART2_RX 	1			// 串口 2接收使能,0-不接收,1-接收;

/*
	函数声明
*/
void RS485_Init(u32 bound);  // 485初始化,实际是初始化USART2
void RS485_Send_Data(u8 *buf,u8 len);
void RS485_Receive_Data(u8 *buf,u8 *len);

#endif	   

rs485.c

#include "sys.h"		    
#include "rs485.h"	 
#include "delay.h"

#ifdef EN_USART2_RX   	// 如果使能了接收

// 定义数据接收缓冲区,最大64个字节.
u8 RS485_RX_BUF[64];  	
// 接收到的数据长度
u8 RS485_RX_CNT=0;   		  

/*
	USART2 中断服务函数
		1. 把USART2 中断接收到的数据保存到数据接收缓冲区 RS485_RX_BUF 中
*/
void USART2_IRQHandler(void)
{
	u8 res;	    
 
 	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到数据
	{	 
		res =USART_ReceiveData(USART2); 	//读取接收到的数据
		if(RS485_RX_CNT<64)
		{
			RS485_RX_BUF[RS485_RX_CNT]=res;		//记录接收到的值
			RS485_RX_CNT++;						//接收数据增加1 
		} 
	}  											 
} 
#endif			

/*
	RS485 初始化函数
		1. 初始化IO 串口2;
		2. pclk1:PCLK1时钟频率(Mhz);
		3. bound:波特率;
*/

void RS485_Init(u32 bound)
{  
	// 定义结构体
	GPIO_InitTypeDef GPIO_InitStructure;
  	USART_InitTypeDef USART_InitStructure;
 	NVIC_InitTypeDef NVIC_InitStructure;
 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD, ENABLE);//使能GPIOA,D时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟
	
	// 使能PD7 作为 485模式控制引脚
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;				
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOD, &GPIO_InitStructure);
 // 使能PA2、PA3作为 485收发引脚
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;	//PA2
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  	GPIO_Init(GPIOA, &GPIO_InitStructure);
   
  	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
  	GPIO_Init(GPIOA, &GPIO_InitStructure);  

	RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART2,ENABLE);//复位串口2
	RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART2,DISABLE);//停止复位
 
	
	
/*
	配置串口参数
*/
 #ifdef EN_USART2_RX		  	//如果使能了接收
	USART_InitStructure.USART_BaudRate = bound;//波特率设置
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据长度
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;///奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式

  	USART_Init(USART2, &USART_InitStructure); ; //初始化串口2
  
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //使能串口2中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; //先占优先级2级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级2级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
	NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
 
  	USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启中断
   
  	USART_Cmd(USART2, ENABLE);                    //使能串口2 

 #endif

  	RS485_TX_EN=0;			//默认为接收模式
 
}

/*
	485发送数据函数
		1. 从数据接收缓冲区 *buf 中接收并向串口发送len 个字节数据;
		2. buf:发送区首地址
		3. len:发送的字节数(为了和本代码的接收匹配,这里建议不要超过64个字节)
*/
void RS485_Send_Data(u8 *buf,u8 len)
{
	u8 t;
	RS485_TX_EN=1;			//设置为发送模式
  	for(t=0;t<len;t++)		//循环发送数据
	{		   
		while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);	  
		USART_SendData(USART2,buf[t]);
	}	 
 
	while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);		
	RS485_RX_CNT=0;	  
	RS485_TX_EN=0;				//设置为接收模式	
}

/*
	485接收数据函数
		1. RS485取数据接收缓冲区 RS485_RX_BUF 中的数据
		2. buf:接收缓存首地址
		3. len:读到的数据长度
*/
void RS485_Receive_Data(u8 *buf,u8 *len)
{
	u8 rxlen=RS485_RX_CNT;
	u8 i=0;
	*len=0;				//默认为0
	delay_ms(10);		//等待10ms,连续超过10ms没有接收到一个数据,则认为接收结束
	if(rxlen==RS485_RX_CNT&&rxlen)//接收到了数据,且接收完成了
	{
		for(i=0;i<rxlen;i++)
		{
			buf[i]=RS485_RX_BUF[i];	
		}		
		*len=RS485_RX_CNT;	//记录本次数据长度
		RS485_RX_CNT=0;		//清零
	}
}

4.3 外接程序下载

如下图,其中MCU3.3接MCU的MCU3.3引脚,用于烧录时供电,JTMSJTCK注意包地;
在这里插入图片描述

5. 中断


misc.c

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
  /* Check the parameters */
  assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
  
  /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
  SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}

// 如配置NVIC 为中断分组2,用以下方式:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //设置NVIC中断分组2,即2位抢占优先级,2位响应优先级

misc.c

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct){...} // 中断配置初始化

// 入口结构体参数:
typedef struct
{
  uint8_t NVIC_IRQChannel;   // 中断通道选择,
  uint8_t NVIC_IRQChannelPreemptionPriority;  // 抢占优先级
  uint8_t NVIC_IRQChannelSubPriority;    // 响应优先级
  FunctionalState NVIC_IRQChannelCmd;    // 中断使能
} NVIC_InitTypeDef;

中断配置初始化函数的使用示例:
在这里插入图片描述

5.1 中断服务函数

中断服务函数在官方启动文件startup_stm32f10x_hd.s中找到,如需要定时器 3的中断服务函数,在文件中找到TIM3_IRQHandler,然后以此命名在对应源文件添加中断服务函数即可;

  • 注:中断服务函数不需在头文件中声明;

示例:
timer.c

void TIM3_IRQHandler(void)   //TIM3中断
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //检查TIM3更新中断发>生与否
		{
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx更新中断标志 
		LED1=!LED1;
		}
}

6. 定时器


6.1 定时器中断

在这里插入图片描述
stm32f10x_tim.c

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct){...}  // 定时器基础参数配置

// 输入参数:
// 1. 选定哪个定时器;
// 2. 定时器参数配置:
typedef struct
{
  uint16_t TIM_Prescaler;  // 预分频系数      
  uint16_t TIM_CounterMode;  // 计数模式     
  uint16_t TIM_Period;  // 自动装载值          
  uint16_t TIM_ClockDivision;  // 输入捕获(高级定时器用到)   
  uint8_t TIM_RepetitionCounter;  // (高级定时器用到)
} TIM_TimeBaseInitTypeDef;    

// 状态标志位获取与清除
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);  // 获取定时器标志位
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);  // 清除定时器标志位
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);  // 获取中断标志位
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);  // 清除中断标志位

timer.c

//通用定时器3中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;  // 定义结构体
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
	
	//定时器TIM3初始化
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
 
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能TIM3中断,中断模式选择更新中断,即允许更新定时器中断

	//中断优先级NVIC设置
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器


	TIM_Cmd(TIM3, ENABLE);  //使能TIMx					 
}

//定时器3中断服务程序
void TIM3_IRQHandler(void)   //TIM3中断
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
		{
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //手动清除TIMx更新中断标志 
		LED1=!LED1;
		}
}

定时器溢出时间公式可知,若需要设置定时器为 500ms,则只需:

  • arr = 4999;
  • psc = 7199;
  • Tclk 为内部时钟频率 72MHz;
    在这里插入图片描述

即:

  • 7200 /72M = 0.1ms
  • Tout = 5000 *0.1ms = 500ms

main.c

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "timer.h"
 
 int main(void)
 {		
	delay_init();	    	 //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
 	LED_Init();			     //LED端口初始化
	TIM3_Int_Init(4999,7199);//10Khz的计数频率,计数到5000为500ms  
   	while(1)
	{
		LED0=!LED0;
		delay_ms(200);		   
	}	 
}	 

6.2 输入捕获

初始化配置与定时器中断实现步骤一样,下以定时器 5 - 通道 1输入捕获为例:
timer.c

#include "timer.h"
#include "led.h"

//定时器5通道1输入捕获配置
TIM_ICInitTypeDef  TIM5_ICInitStructure;

void TIM5_Cap_Init(u16 arr,u16 psc)
{	 
	GPIO_InitTypeDef GPIO_InitStructure;  // 定义结构体
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
   	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);	//使能TIM5时钟
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  //使能GPIOA时钟
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;  //PA0 清除之前设置  
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 输入  
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_ResetBits(GPIOA,GPIO_Pin_0);						 //PA0 下拉
	
	//初始化定时器5 TIM5	 
	TIM_TimeBaseStructure.TIM_Period = arr; //设定计数器自动重装值 
	TIM_TimeBaseStructure.TIM_Prescaler =psc; 	//预分频器   
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
  
	//初始化TIM5输入捕获参数
	TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //CC1S=01,选择输入端 IC1映射到TI1上,选择定时器输入通道 1
  	TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;	//上升沿捕获
  	TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //通道 1直接映射到TI1上
  	TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;	 //配置输入分频,不分频,即对每一个上升沿都进行捕获,若选择 TIM_ICPSC_DIV2,则为每两个上升沿进行一次捕获
  	TIM5_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波
  	TIM_ICInit(TIM5, &TIM5_ICInitStructure);
	
	//中断分组初始化
	NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;  //TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;  //先占优先级2级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  //从优先级0级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 
	
	TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断 ,允许CC1IE输入捕获中断	
	
   	TIM_Cmd(TIM5,ENABLE ); 	//使能定时器5
}

u8  TIM5CH1_CAPTURE_STA=0;	//输入捕获状态		    				
u16	TIM5CH1_CAPTURE_VAL;	//输入捕获值
 
//定时器5中断服务程序
//测量输入信号的一个完整的脉冲宽度	 
void TIM5_IRQHandler(void)
{ 

 	if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获	
	{	  
		if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
		 
		{	    
			if(TIM5CH1_CAPTURE_STA&0X40)//已经捕获到高电平了
			{
				if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
				{
					TIM5CH1_CAPTURE_STA|=0X80;//标记成功捕获了一次
					TIM5CH1_CAPTURE_VAL=0XFFFF;
				}else TIM5CH1_CAPTURE_STA++;
			}	 
		}
	if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)//捕获1发生捕获事件
		{	
			if(TIM5CH1_CAPTURE_STA&0X40)		//捕获到一个下降沿 		
			{	  			
				TIM5CH1_CAPTURE_STA|=0X80;		//标记成功捕获到一次高电平脉宽
				TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);
		   		TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //CC1P=0 设置为上升沿捕获
			}else  								//还未开始,第一次捕获上升沿
			{
				TIM5CH1_CAPTURE_STA=0;			//清空
				TIM5CH1_CAPTURE_VAL=0;
	 			TIM_SetCounter(TIM5,0);
				TIM5CH1_CAPTURE_STA|=0X40;		//标记捕获到了上升沿
		   		TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);		//CC1P=1 设置为下降沿捕获
			}		    
		}			     	    					   
 	}
 
    TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
 
}

main.c

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "timer.h"
 

extern u8  TIM5CH1_CAPTURE_STA;		//输入捕获状态		    				
extern u16	TIM5CH1_CAPTURE_VAL;	//输入捕获值	
 int main(void)
 {		
 	u32 temp=0; 
	delay_init();	    	 //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	 //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
 	LED_Init();			     //LED端口初始化
 	TIM5_Cap_Init(0XFFFF,72-1);	//以1Mhz的频率计数,一个时钟周期为 1微秒
   	while(1)
	{
 		delay_ms(10);	 		 
 		if(TIM5CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿
		{
			temp=TIM5CH1_CAPTURE_STA&0X3F;
			temp*=65536;//溢出时间总和
			temp+=TIM5CH1_CAPTURE_VAL;//得到总的高电平时间
			printf("HIGH:%d us\r\n",temp);//打印总的高点平时间
			TIM5CH1_CAPTURE_STA=0;//开启下一次捕获
		}
	}
 }


7. 电机

7.1 RS485通信控制步进电机

控制过程:

  1. 检测电机有无响应,是否通信成功;
  2. 设置电机初始化配置;
  3. 启动电机;

关于电机的详细信息与配置指令,需到电机品牌官网下载对应型号电机手册查看;

7.1 PWM信号控制步进电机

详见该文章的3.7.4节

8. 算法

8.1CRC校验码计算

/**********************************************************************************************************
*	函 数 名: calculateCRC
*	功能说明: 计算十六进制码的CRC校验码
*	形    参:无
*	返 回 值: 无
**********************************************************************************************************/
//定义全局变量
uint16_t CRC_H; //CRC校验码高位
uint16_t CRC_L;//CRC校验码低位
void calculateCRC(char *hexadecimalCode, uint16_t dataLength) 
{
	uint32_t index;
	CRC_H = 0XFF;//预先给CRC高、低位一个值
	CRC_L = 0XFF;
	while (dataLength--)
	{ 
		index = CRC_H ^ *hexadecimalCode++;
		CRC_H = CRC_L ^ auchCRCHi[index] ; 
		CRC_L = auchCRCLo[index] ; 
	}
}
// CRC 高位字节值表
const uint8_t  auchCRCHi[] = { 
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 
} ; 
// CRC低位字节值表
const uint8_t  auchCRCLo[] = { 
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 
0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 
0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 
0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 
0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 
0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 
0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 
0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 
0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 
0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 
0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 
0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 
0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 
0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 
0x43, 0x83, 0x41, 0x81, 0x80, 0x40 
};
//应用举例
char command[8] = {0x01,0x06,0x00,0x2B,0x00,0x02,0x78,0x03};

calculateCRC(command,6); // 计算CRC校验码
command[6] = CRC_H;
command[7] = CRC_L;

9. 内存管理


9.1 改变程序的入口地址

如果单片机主程序从 flash 启动且 flash 中只有一个程序,一般不需要改变程序的入口地址。否则则需要改变其入口地址,用的最多的是在单片机中增加boot loader程序,以下以在单片机中增加boot loader程序为例讲解:

  1. 将中断向量表起始地址从默认的0x8000000改为目标值,比如0x8005000.
    在这里插入图片描述
  2. 在主程序main()函数中所有执行语句前加上以下代码:
NVIC_SetVectorTable(NVIC_VectTab_FLASH,(0x08005000));	// 改变中断向量表地址,也即是改变主程序的入口地址


其中:
1. 在misc.h文件中有定义:
#define NVIC_VectTab_FLASH           ((uint32_t)0x08000000)

2. 在misc.c文件中有对函数void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset)的定义,显然第一个入口参数为默认的中断向量表起始地址0x08000000,第二个入口参数为偏移地址,它只能是0x200的整数倍。

  1. 将boot loader程序的入口地址改为默认的0x8000000.
    在这里插入图片描述
  2. 向单片机烧录两个程序即可。

* 其他


*.1 笔记

  1. 函数尽量用封装的方式调用,main函数尽量简洁,初始化函数只要上电或复位时进行一次即可,故不用放在while(1)死循环中,而扫描类型函数如串口扫描按键扫描就要放到main函数的死循环中;

*.2 将包含所有头文件到public.h

#ifndef __PUBLIC_H
#define __PUBLIC_H


#include "sys.h"      // 将所有的头文件包含进来


extern int i; // 将所有的全局变量包含进来
extern char array[10]; 


#endif

*.3 打印整齐的数据矩阵

应用print()函数的宽精度控制;

printf("%9d %9d %9d\n", val1, val2, val3);

*.4 利用宏定义封装函数

利用宏定义封装函数
#define WaitUntilMotorStop {while(YAK_MOTOR_Read_One_Byte(YAK_Motor_Command5,8)==1) {if(LIGHT_CURTAIN_SENSOR == 1){MotorEStop();return;}}} 

将其等效展开就是:
while(YAK_MOTOR_Read_One_Byte(YAK_Motor_Command5,8)==1);
if(LIGHT_CURTAIN_SENSOR == 1)
{
	MotorEStop();
	return;
}

然后直接调用宏定义
WaitUntilMotorStop;

* 案例


*.1 传感器感应到再进行下一步动作

方式一是为了安全着想,但也会带来程序进入死循环的情况。若安全要求不高,建议使用下面的方式二处理,防止程序进入死循环。

  • 方式一:
while(DOWN_MOTOR_SENSOR==1){;} //电机持续下降,直到电机置位传感器感应到,否则不能做任何动作
  • 方式二:
// 例程,等待3秒,3秒内读不到感应器则报错
u8 TOP_CY_CLOSE(void)
{
    u16 i=0;
    TOP_CY_OFF();
	while(i<3000) // 等待3秒
	{
		if(TOP_CY_OFF_SENSOR==0) break; // 若检测到感应器,则跳出循环
		delay_ms(1);
	    i++;
	}
	if(TOP_CY_OFF_SENSOR==0)return 0; 
    else return 1;
}

*.2 按键的消抖与松开启动

if(START_BUTTON1==0)  
{
	delay_ms(10);  // 延时消抖
	if(START_BUTTON1==0)  
	{
		while(START_BUTTON1==0){;}  // 等待松开按钮后启动,此处注意结合main 函数中的扫描功能使用
		...
	}
}

*.3 双启按钮处理

void Button_startButtonScan(void)   
{
		u8 time= 0;
		if(START_BUTTON1==0||START_BUTTON2==0)
		{
			while(1) 
			{
				delay_ms(10);
				if(time<100)time++;
				if(START_BUTTON1==0&&START_BUTTON2==0){if(time<50)break;} // 双启真的被按下,退出循环
				else if(START_BUTTON1==1&&START_BUTTON2==1)return; // 双启动没被按下,退出函数
				else if(time>50)
				{
					while(START_BUTTON1==0||START_BUTTON2==0); // 等待单边按钮松开
					return;
				}
			}
			// 动作开始
			...
			// 动作结束
			while(START_BUTTON1==0||START_BUTTON2==0); // 等待单边按钮松开
			return;
		}
		else return;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Truffle7电子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值