总体思路
通过串口输入数据到单片机,单片机判断串口数据中的关键字,然后判定是哪一种模式(或者根据中断按键输入改变的模式)
再去对应的模式实现相应的功能
整个程序分为两个部分,程序的轮询和中断两部分
中断部分包括
定时器中断,串口中断,外部中断
定时器中断:LED2利用定时器进行延时,中断中执行相应的LED2的操作
串口中断:接收PC向单片机发送的数据。
外部中断:判断按键状态,改变MODE的值
轮询部分
数据识别:对串口中断中的数据识别,判定模式
数据输出:将可选MODE输出到串口,方便用户在PC机上查看
MODE输出:根据串口数据识别到的MODE或外部中断识别到的MODE进行相关模式的操作。
部分函数说明
void USART1_IRQHandler(void); 串口中断函数,里面定义了一个协议,所发送的字符串必须以回车键结束,很重要!!!
这个函数的数据接收过程参考https://blog.csdn.net/weixin_47586883/article/details/110959012
void usart_check(void); 这个函数的作用是对串口的数据进行识别,判定MODE
void usart_out(void); 这个函数的作用是将用户选择的MODE输出到串口
void led_out(void);这个函数的作用是根据用户选择的MODE进行相应的操作,主要是LED1,LED2的操作在相应的TIM3中断中
void TIM3_IRQHandler(void);TIM3中断函数,也包含了对相应的MODE对LED2的操作
void EXTI2_IRQHandler(void);根据外部按键,记录MODE
LED1连接A0采用systick控制时间,LED2连接A1,采用TIM3控制
外部中断按键连接A2,A2采取上拉输入,外部中断下降沿触发
A0,A1,均使用PWM输出
文件结构
主函数
#include"configuration.h"
int main(void)
{
GPIO_Configuration();
SysTick_Init(72);
TIM3configuration(4999,7199);//定时时间500ms
TIM2_CH1_CH2_PWM_Init(999,71);//1000HzPWM
USART1_Init(9600);//波特率9600
NVIC_Configuration();
EXTI_Configuration();
while(1)
{
printf("\r\n可供选择的模式有:\r\n");
printf("\r\n MODE1:LED1亮,LED2秒闪\r\n");
printf("\r\n MODE2:LED1,LED2交替秒闪\r\n");
printf("\r\n MODE3:LED1,LED2同时秒闪\r\n");
printf("\r\n MODE4:LED1秒闪,LED2亮\r\n");
printf("\r\n MODE5:LED1,LED2为呼吸灯\r\n");
printf("\r\n请输入选择的MODE\r\n");
usart_check();
usart_out();
led_out();
}
}
#include"configuration.h"
#ifndef __CONFIGURATION_H
#define __CONFIGURATION_H
#include "stm32f10x.h"
#include "stdio.h"
void GPIO_Configuration(void);//有关GPIO的配置和时钟配置
void NVIC_Configuration(void);//中断向量管理
void EXTI_Configuration(void);//外部中断
void USART1_Init(u32 bound);//串口初始化
void TIM3configuration(u16 per,u16 psc);//TIM3定时器初始化
void SysTick_Init(u8 SYSCLK); //systick定时器初始化
void delay_us(u32 nus);//延时微秒
void delay_ms(u16 nms);//延时毫秒
//数据接收的相关宏
#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; //接收状态标记寄存器
extern uint8_t MODE; //模式
extern uint8_t led2_value; //读取LED2的状态
void usart_check(void);
void usart_out(void);
void led_out(void);
void TIM2_CH1_CH2_PWM_Init(u16 per,u16 psc); //TIM输出PWM到A0,A1
#endif
gpio_configuration
#include"configuration.h"
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//复用时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//TIM3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//USART1时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//TIM2时钟
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_0; //Led引脚A1,A0
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //外部中断A2
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//TX //串口输出PA9
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA,&GPIO_InitStructure); /* 初始化串口输入IO */
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//RX //串口输入PA10
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA,&GPIO_InitStructure); /* 初始化GPIO */
}
EXTI中断
#include"configuration.h"
void EXTI_Configuration(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource2);//配置中断线
EXTI_InitStructure.EXTI_Line=EXTI_Line2;//中断线PA0—PG0
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断方式
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//下降沿触发
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
void EXTI2_IRQHandler(void)
{
static uint8_t EXTflag=0;
if(EXTI_GetITStatus(EXTI_Line2)!=RESET)
{
delay_ms(10);
EXTI_ClearITPendingBit(EXTI_Line2);
EXTflag++;
switch(EXTflag)
{
case 1:MODE=1;break;
case 2:MODE=2;break;
case 3:MODE=3;break;
case 4:MODE=4;break;
case 5:MODE=5;break;
}
if(EXTflag==5)EXTflag=0;
}
}
NVIC_Configuration配置
#include"configuration.h"
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//管理分组
NVIC_InitStruct.NVIC_IRQChannel=TIM3_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStruct.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = EXTI2_IRQn;//外部中断2通道
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级3
NVIC_InitStruct.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStruct);
}
Systick
#include"configuration.h"
static u8 fac_us=0; //us延时倍乘数
static u16 fac_ms=0; //ms延时倍乘数
//初始化延迟函数
//SYSTICK的时钟固定为AHB时钟的1/8
//SYSCLK:系统时钟频率
void SysTick_Init(u8 SYSCLK)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us=SYSCLK/8;
fac_ms=(u16)fac_us*1000;
}
/*******************************************************************************
* 函 数 名 : delay_us
* 函数功能 : us延时,
* 输 入 : nus:要延时的us数
注意:nus的值,不要大于798915us(最大值即2^24/fac_us@fac_us=21)
* 输 出 : 无
*******************************************************************************/
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
/*******************************************************************************
* 函 数 名 : delay_ms
* 函数功能 : ms延时,
* 输 入 : nms:要延时的ms数
注意:nms的值,SysTick->LOAD为24位寄存器,
不要大于0xffffff*8*1000/SYSCLK
对72M条件下,nms<=1864ms
* 输 出 : 无
*******************************************************************************/
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
TIM3_init
#include "configuration.h"
void TIM3configuration(u16 per,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_Period=per; //定时周期
TIM_TimeBaseInitStruct.TIM_Prescaler=psc; //分频系数
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分频
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;//计数模式
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);//打开更新中断
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
TIM_Cmd(TIM3,ENABLE);
}
void TIM3_IRQHandler(void)
{
static uint8_t TIMflag=0;
if(TIM_GetITStatus(TIM3,TIM_IT_Update)!=RESET)//判断标志位状态
{
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清楚中断标志位
TIMflag++;
switch(MODE)
{
case 1:
if(TIMflag%2)
{
TIM_SetCompare2(TIM2,1000);
}
else
{
TIM_SetCompare2(TIM2,0);
}
break;
case 2:
if(TIMflag%2)
{
TIM_SetCompare2(TIM2,1000);
led2_value=1;
}
else
{
TIM_SetCompare2(TIM2,0);
led2_value=0;
}
break;
case 3:
if(TIMflag%2)
{
TIM_SetCompare2(TIM2,1000);
led2_value=1;
}
else
{
TIM_SetCompare2(TIM2,0);
led2_value=0;
}
break;
}
if(TIMflag==200)TIMflag=0;
}
}
usart配置
#include"configuration.h"
#if SYSTEM_SUPPORT_OS
#include "includes.h" //ucos 使用
#endif
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记
void USART1_Init(u32 bound)
{
USART_InitTypeDef USART_InitStructure;
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_Cmd(USART1, ENABLE); //使能串口1
USART_ClearFlag(USART1, USART_FLAG_TC);//清除标志位
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启接收中断
}
void USART1_IRQHandler(void) //串口1中断服务程序
{
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); //读取接收到的数据
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
}
#endif
check
#include"configuration.h"
uint8_t MODE;
void usart_check(void)
{
u16 t;
u16 len;
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n您选择的模式是:\r\n");
for(t=0;t<len;t++)
{
USART_SendData(USART1, USART_RX_BUF[t]); //向串口1发送数据,将输入的MODE发送到串口窗口
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
if(USART_RX_BUF[t]==0x31) //根据关键字判别模式
{
MODE=1;
}
if(USART_RX_BUF[t]==0x32)
{
MODE=2;
}
if(USART_RX_BUF[t]==0x33)
{
MODE=3;
}
if(USART_RX_BUF[t]==0x34)
{
MODE=4;
}
if(USART_RX_BUF[t]==0x35)
{
MODE=5;
}
}
USART_RX_STA=0; //置零串口接收状态寄存器
}
}
void usart_out(void) //根据识别到的模式,在串口显示
{
switch(MODE)
{
case 1:
printf("\r\n当前模式MODE1\r\n");
break ;
case 2:
printf("\r\n当前模式MODE2\r\n");
break ;
case 3:
printf("\r\n当前模式MODE3\r\n");
break ;
case 4:
printf("\r\n当前模式MODE4\r\n");
break ;
case 5:
printf("\r\n当前模式MODE5\r\n");
break ;
default:printf("\r\n输入MODE错误,请从新输入MODE\r\n");
}
}
LED_out
#include"configuration.h"
uint8_t led2_value;
void led_out(void)
{
unsigned int i;//MODE5呼吸灯用
switch(MODE)
{
case 1:
TIM_SetCompare1(TIM2,1000); //A0高电平
break;
case 2:
if(led2_value)
{
TIM_SetCompare1(TIM2,0); //A0低电平
}
else
{
TIM_SetCompare1(TIM2,1000);
}
break;
case 3:
if(led2_value)
{
TIM_SetCompare1(TIM2,1000);
}
else
{
TIM_SetCompare1(TIM2,0);
}
break;
case 4:
TIM_SetCompare1(TIM2,0);
TIM_SetCompare2(TIM2,1000);//A1高电平
delay_ms(500);
TIM_SetCompare1(TIM2,1000);
delay_ms(500);
break;
case 5: //呼吸灯
for(i=0;i<999;i++)
{
TIM_SetCompare2(TIM2,i);
TIM_SetCompare1(TIM2,i);
delay_ms(8);
}
for(i=999;i>0;i--)
{
TIM_SetCompare2(TIM2,i);
TIM_SetCompare1(TIM2,i);
delay_ms(8);
}
break;
}
}
PWM配置
#include"configuration.h"
void TIM2_CH1_CH2_PWM_Init(u16 per,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_TimeBaseInitStructure.TIM_Period=per; //自动装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //分频系数
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //设置向上计数模式
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
//TIM2CH2通道
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
//TIM_OCInitStructure.TIM_Pulse = 800;//需要固定PWM时使用,改变PWM使用TIM_SetCompare2(TIM2,i);
TIM_OC2Init(TIM2,&TIM_OCInitStructure); //输出比较通道2初始化
TIM_OC2PreloadConfig(TIM2,TIM_OCPreload_Enable); //使能TIMx在 CCR2 上的预装载寄存器
//TIM2CH1通道
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
//TIM_OCInitStructure.TIM_Pulse = 800;//需要固定PWM时使用,改变PWM使用TIM_SetCompare1(TIM2,i);
TIM_OC1Init(TIM2,&TIM_OCInitStructure); //输出比较通道2初始化
TIM_OC1PreloadConfig(TIM2,TIM_OCPreload_Enable); //使能TIMx在 CCR2 上的预装载寄存器
TIM_ARRPreloadConfig(TIM2,ENABLE);//使能预装载寄存器
TIM_Cmd(TIM2,ENABLE); //使能定时器
}
实验效果
模式1
模式2
模式3
模式4
模式5
实验问题,当控制两个灯同时闪烁时,会出现时序不同步的现象,推断是使用的库函数,程序运行相对较慢,不能保持和TIM定时器一致。