文章目录
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.h
,stm32f10x_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;
串口调试:
- 向电路板烧录程序;
- 设备与电路板通过串口连接;
- 打开串口调试助手软件,并配置相关参数(勾选
发送新行
可自动在发送的数据后添加回车换行);
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
引脚,用于烧录时供电,JTMS
和JTCK
注意包地;
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.cvoid 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通信控制步进电机
控制过程:
- 检测电机有无响应,是否通信成功;
- 设置电机初始化配置;
- 启动电机;
关于电机的详细信息与配置指令,需到电机品牌官网下载对应型号电机手册查看;
7.1 PWM信号控制步进电机
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程序为例讲解:
- 将中断向量表起始地址从默认的0x8000000改为目标值,比如0x8005000.
- 在主程序
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的整数倍。
- 将boot loader程序的入口地址改为默认的0x8000000.
- 向单片机烧录两个程序即可。
* 其他
*.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;
}