如果你是来看情感片的,那么,恭喜你 ~~~~ 你现在已经看完了,可以走了~~~~
这是一篇准跑题文,所以整个副标题吧:
"多年后再用STM32点亮LED"
先贴上文章总览:
- 故事起源(为什么要做这个);
- 故事发展(分析如何实现);
- 故事高潮(开始Ctrl-C/Ctrl-V);
- 故事结尾(下载调试);
- 升华主题(接下来要干啥);
开始发车了,,,小司机老司机们要坐稳了。
第一章:故事的起源:
真的没有故事,还不死心想看故事的人,你们可以return;了。故事是这样的。。。
之前因某某(暂时保密)原因做了个电路,电路中MCU控制LED进行亮和灭。感觉我是描述不清了,直接看图吧,如下:
细心的人一眼就看出了这个MCU是STM32,对的,这里用的其实是STM32F103Cx。MCU和外设电路都是采用同一个3.3V供电。可以看到两颗LED和MCU的PA9管脚的连接网络是STA_LED信号。本来的需求很简单,就是LED_B亮的时候LED_R灭,LED_B灭的时候LED_R亮(以后就叫LED_B是蓝灯,LED_R是红灯)。
这样很好实现啊,那我控制PA9输出高电平就可以蓝灯灭红灯亮了。再控制PA9输出低电平就可以蓝灯亮红灯灭了。
以为事情就这样简单的结束了?不能这么天真。所以接下来开始作了。。。
新需求:
1. 在某种运行状态下蓝色LED单独闪烁,此时红色LED处于熄灭状态;
2. 在某种运行状态下红色LED单独闪烁,此时蓝色LED处于熄灭状态;
3. 在某种运行状态下蓝色LED是呼吸灯,此时红色LED处于熄灭状态且CPU可执行其他任务;
啥?在说啥?第一反应是这个需求实现不了啊,很明显两个LED处于互斥状态,其中一个亮另外一个必定是灭的啊~ 先别急往下看,分析一下能实现这个三个需求吗?要是能想到怎么办就可以return;了。也没必要往下浪费时间了。
第二章 故事发展
不知道你们是怎么想的,反正我一开始是拒绝的,直到再看了一眼电路。。。 从此心里就有了你(好像画风有点不对)
可以看到LED(0603封装)电路的连接情况是: 3.3V --> D1(蓝) --> R10(1.5K) --> PA9 --> D2(红) --> R12(1.5K) --> GND。
有什么用呢?我们先不管PA9,此时电路的连接是:3.3V --> D1(蓝) --> R10(1.5K) --> D2(红) --> R12(1.5K) --> GND。这个电路会亮吗?会不会亮需要数据来推测:
咦~ 原来蓝色这么大啊。就是说红色 + 蓝色 范围是4.92 ~ 5.18V,所以3.3V < 4.92V不能同时点亮这两个LED喽,是这样算的吗?真的不太懂,有专业的可以来给计算一下。不会算怎么办,那就直接动手测试吧,找了一个红色LED(0603)测试发现1.6V就可以点亮了,只是非常的微弱几乎看不到。到这里就基本就证实3.3V无法同时点亮这两个LED了,额,,,还不明白的自己想办法明白去吧。
不过上面的分析到底是要干嘛?你想想,如果控制两颗同时熄灭那只要PA9处于floating状态,或者高阻状态就好了吗?所以需求1在理论上已经解决了。啥?还不明白?细说一下吧,当想让蓝色LED亮的时候设置PA9为低并且推挽输出,当想让蓝色LED灭的时候设置PA9为开漏输出并且为高(此时PA9处于高阻态),整个过程中PA9一直没有高电平,所以红色LED一直处于熄灭状态。
有了需求1 的解决思路那就可以尝试用这个思路去解决需求2了。“需求2. 在某种运行状态下红色LED单独闪烁,此时蓝色LED处于熄灭状态;” 我们这样做:当点亮红色LED的时候设置PA9为高并且推挽输出,当熄灭红色LED的时候设置PA9为高并且开漏输出(此时PA9处于高阻态),整个过程中PA9一直没有低电平,所以蓝色LED一直处于熄灭状态。
现在解决了需求1和需求2接下来可以分析需求3了。需求3:“在某种运行状态下蓝色LED是呼吸灯,此时红色LED处于熄灭状态且CPU可执行其他任务;”。最后一点CPU可执行其他任务就是说需求3的呼吸灯执行过程中CPU基本不参与呗,那只能通过CPU的外设来实现了,毫无疑问就是用定时器和中断处理了。用哪个定时器呢?我们看到蓝色LED是连接在PA9上的,查一下STM32的数据手册吧。
我查了半天发现PA9可以作为TIM1的CH2通道输出,输出啥呢?要想实现呼吸灯那就用PWM吧,所以就用TIM1的CH2通道在PA9输出PWM波吧,但是怎么调节亮度呢?可以选择调节PWM频率也可以选择调节占空比,我们就选择经典的调节占空比的方式吧。多久调节一次?我们知道人眼能分辨的频率一般在24-30帧(是这样吗?),所以我们的调节时间间隔应该不大于33mS,否则就能看到明显的亮度阶梯。那就10mS调节一次好了。谁去计时10mS?CPU?它还有其他的活干呢,所以再启动一个定时器TIM3去计时吧。TIM3每计时10mS更新一次TIM1的PWM的占空比就好了。最外头的PA9要怎么设置?既然全程需要红色LED熄灭,那就只能开漏了,又要输出PWM,所以PA9要设置成复用开漏输出。
至此,分析完毕,接下来就是干货时间了。
第三章 故事高潮
开始写代码了,理论分析已经完成,接下来就是把理论变成代码的时候了。
怎么写?不会写怎么办?Ctrl-C / Ctrl-V 总该会吧。
// 0.包含头文件
#include "timer.h"
#include "delay.h"
#include "sys.h"
/***************************************************************************/
// 1. 首先我们定义一下PA9管脚:我们将PA9这个连接到LED的端口定义为DBG_LED
#define DBG_LED_RCC RCC_APB2Periph_GPIOA
#define DBG_LED_GPIO GPIOA
#define DBG_LED_PIN GPIO_Pin_9
#define DBG_LED_INDEX 9
#define DEG_LED_MODE GPIO_Mode_Out_OD
#define DBG_LED_SPEED GPIO_Speed_50MHz
// 2. 通过以上分析我们知道总共有以下这么几种状态模式:
typedef enum
{
All_Off = 0, //全部熄灭模式
General, //普通模式,就是红灯亮绿灯灭、绿灯亮红灯灭的互斥状态
Single_Blue, //绿灯单独闪烁模式
Single_Red, //红灯单独闪烁的模式
Pwm_Blue //绿灯呼吸的模式
}LedStatus;
volatile LedStatus currentState = All_Off; //定义一个全局的用来表示DBG_LED的当前状态的变量
// 3. 每种状态时PA9输出寄存器的值设置
typedef enum
{
OFF = 0,
ON
}LedValue;
volatile LedValue currentLedValue = OFF; //定义一个全局变量用来存储当前LDBG_LED输出寄存器的值
// 4. 初始化DBG_LED 管脚
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(DBG_LED_RCC , ENABLE);
GPIO_InitStructure.GPIO_Pin = DBG_LED_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //开漏
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_ResetBits(DBG_LED_GPIO, DBG_LED_PIN);
GPIO_Init(DBG_LED_GPIO, &GPIO_InitStructure);
}
// 5. 输出状态寄存器(GPIOx->BRR 、 GPIOx->BSRR)配置值
void LED_ConfigValue(GPIO_TypeDef* GPIOx, uint8_t GPIO_Pin, LedValue nextValue)
{
uint8_t posbit = 0;
if(currentLedValue == nextValue) return;
if((currentState == All_Off) || (currentState == Pwm_Blue)) return;
switch(currentState)
{
case General:
{
if(nextValue == ON) GPIOx->BSRR = 0x01 << GPIO_Pin;
else GPIOx->BRR = 0x01 << GPIO_Pin;
}
break;
case Single_Blue:
{
if(nextValue == ON) GPIOx->BRR = 0x1 << GPIO_Pin;
else GPIOx->BSRR = 0x1 << GPIO_Pin;
}
break;
case Single_Red:
{
posbit = (GPIO_Pin >= 8) ? (4 * (GPIO_Pin - 8)) : (4 * GPIO_Pin);
if(nextValue == ON ) {
if(GPIO_Pin >= 8) GPIOx->CRH &= ~(0xC << posbit);
else GPIOx->CRL &= ~(0xC << posbit);
}
else {
if(GPIO_Pin >= 8) GPIOx->CRH |= (0x4 << posbit);
else GPIOx->CRL |= (0x4 << posbit);
}
}
break;
default: break;
}
currentLedValue = nextValue;
}
// 6. 各种状态模式切换处理函数
void LED_State_Handler(GPIO_TypeDef* GPIOx, uint8_t GPIO_Pin, LedStatus nextState)
{
uint8_t posbit = 0;
if(currentState == nextState) return;
posbit = (GPIO_Pin >= 8) ? (4 * (GPIO_Pin - 8)) : (4 * GPIO_Pin);
switch(nextState)
{
case All_Off: {
if(currentState == Pwm_Blue) {
PWM_TIM_OFF();
}
GPIOx->BSRR = 0x1 << GPIO_Pin;
if(GPIO_Pin >= 8) {
GPIOx->CRH &= ~(0xC << posbit);
GPIOx->CRH |= 0x4 << posbit;
} else {
GPIOx->CRL &= ~(0xC << posbit);
GPIOx->CRL |= 0x4 << posbit;
}
}
break;
case General: {
if(currentState == Pwm_Blue) {
PWM_TIM_OFF();
}
if(GPIO_Pin >= 8) {
GPIOx->CRH &= ~(0xC << posbit);
} else {
GPIOx->CRL &= ~(0xC << posbit);
}
GPIOx->BSRR = 0x1 << GPIO_Pin;
}
break;
case Single_Blue: {
if(currentState == Pwm_Blue) {
PWM_TIM_OFF();
}
GPIOx->BSRR = 0x1 << GPIO_Pin;
if(GPIO_Pin >= 8) {
GPIOx->CRH &= ~(0xC << posbit);
GPIOx->CRH |= (0x4 << posbit);
} else {
GPIOx->CRL &= ~(0xC << posbit);
GPIOx->CRL |= (0x4 << posbit);
}
}
break;
case Single_Red: {
if(currentState == Pwm_Blue) {
PWM_TIM_OFF();
}
GPIOx->BSRR = 0x1 << GPIO_Pin;
if(GPIO_Pin >= 8) {
GPIOx->CRH &= ~(0xC << posbit);
GPIOx->CRH |= (0x4 << posbit);
} else {
GPIOx->CRL &= ~(0xC << posbit);
GPIOx->CRL |= (0x4 << posbit);
}
}
break;
case Pwm_Blue: {
PWM_TIM_ON();
GPIOx->BRR = 0x1 << GPIO_Pin;
if(GPIO_Pin >= 8) {
GPIOx->CRH &= ~(0xC << posbit);
GPIOx->CRH |= 0xC << posbit;
} else {
GPIOx->CRL &= ~(0xC << posbit);
GPIOx->CRL |= 0xC << posbit;
}
}
break;
default: break;
}
currentState = nextState;
}
// 7. 获取引脚编号算法,如传入GPIO_Pin_9 返回9,这里没有使用这个算法。
uint8_t GetPinPos(uint16_t PINx)
{
uint8_t pos = 0;
assert_param(IS_GET_GPIO_PIN(PINx));
if(PINx >= GPIO_Pin_8)
{
while((0x01<<(pos + 8)) != PINx) pos++;
return pos + 8;
}
else
{
while((0x01<<pos) != PINx) pos++;
return pos;
}
}
// 8. 主函数
int main(void)
{
uint8_t cnt = 0;
delay_init(72); //延时初始化
NVIC_Configuration(); //设置中断分组
LED_Init(); //DBG_LED初始化
TIM1_PWM_Init(99, 71); //PWM = 72000000/(99+1)/(71+1)=10Khz
TIM3_Int_Init(9999,71);//T = 10ms not enable
//************第一个测试:普通模式,红绿LED交替闪烁****************************
LED_State_Handler(DBG_LED_GPIO, DBG_LED_INDEX, General);//配置成普通模式,交替闪烁5次
for(cnt = 0; cnt < 5; cnt++)
{
LED_ConfigValue(DBG_LED_GPIO, DBG_LED_INDEX, ON);
delay_ms(500);
LED_ConfigValue(DBG_LED_GPIO, DBG_LED_INDEX, OFF);
delay_ms(500);
}
LED_State_Handler(DBG_LED_GPIO, DBG_LED_INDEX, All_Off);//切换到全部熄灭模式
delay_ms(1000);
//************第二个测试:绿灯单独闪烁模式************************************
LED_State_Handler(DBG_LED_GPIO, DBG_LED_INDEX, Single_Blue);//配置成绿灯单独闪烁模式,闪烁5次
for(cnt = 0; cnt < 5; cnt++)
{
LED_ConfigValue(DBG_LED_GPIO, DBG_LED_INDEX, ON);
delay_ms(500);
LED_ConfigValue(DBG_LED_GPIO, DBG_LED_INDEX, OFF);
delay_ms(500);
}
LED_State_Handler(DBG_LED_GPIO, DBG_LED_INDEX, All_Off);
delay_ms(1000);
//************第三个测试:红灯单独闪烁模式************************************
LED_State_Handler(DBG_LED_GPIO, DBG_LED_INDEX, Single_Red);//配置成红灯单独闪烁模式,闪烁5次
for(cnt = 0; cnt < 5; cnt++)
{
LED_ConfigValue(DBG_LED_GPIO, DBG_LED_INDEX, ON);
delay_ms(500);
LED_ConfigValue(DBG_LED_GPIO, DBG_LED_INDEX, OFF);
delay_ms(500);
}
LED_State_Handler(DBG_LED_GPIO, DBG_LED_INDEX, All_Off);
delay_ms(1000);
//************第四个测试:绿灯呼吸模式******************************************
LED_State_Handler(DBG_LED_GPIO, DBG_LED_INDEX, Pwm_Blue);//配置成绿灯呼吸模式,执行5s
for(cnt = 0; cnt < 5; cnt++)
{
delay_ms(1000);
}
LED_State_Handler(DBG_LED_GPIO, DBG_LED_INDEX, All_Off);
delay_ms(1000);
//************第五个测试:绿灯呼吸模式,CPU去执行其他,这里CPU去执行空操****************
LED_State_Handler(DBG_LED_GPIO, DBG_LED_INDEX, Pwm_Blue);
while(1)
{
;
}
}
接下来就是timer.c文件的代码了,不要怪我没有注释,Keil中的注释在这里编码就乱了,所以被我删了。
#include "timer.h"
//********************************************************************************
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//TIM_Cmd(TIM3, ENABLE);
}
uint8_t cnt10ms = 8, flag = 1, cnt = 0, ledPwmOpen = 0;
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update );
flag ? cnt++ : cnt--;
if(cnt >= 99) flag = 0;
if(cnt == 0) flag = 1;
TIM_SetCompare2(TIM1, cnt);
}
}
void TIM3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
//A6 A7 B0 B1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
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_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//³õʼ»¯GPIO
//B4 B5 B0 B1
// GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_0 | GPIO_Pin_1;
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// GPIO_Init(GPIOB, &GPIO_InitStructure);
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_OC3Init(TIM3, &TIM_OCInitStructure);
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_Cmd(TIM3, ENABLE);
}
void TIM1_PWM_Init(u16 arr,u16 psc)
{
//GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// GPIO_Init(GPIOA, &GPIO_InitStructure);//³õʼ»¯GPIO
// GPIO_ResetBits(GPIOA,GPIO_Pin_9);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//TIM_OCInitStructure.TIM_Pulse = 5000;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OC2Init(TIM1, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_Cmd(TIM1, ENABLE);
TIM_CtrlPWMOutputs(TIM1, DISABLE);
}
接下来是timer.h的代码
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
/***************************************************************************/
#define PWM_TIM_ON() {TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); TIM_Cmd(TIM3, ENABLE);}
#define PWM_TIM_OFF() {TIM_CtrlPWMOutputs(TIM1, DISABLE); TIM_Cmd(TIM1, DISABLE); TIM_Cmd(TIM3, DISABLE);}
void TIM1_PWM_Init(u16 arr,u16 psc);
void TIM3_Int_Init(u16 arr,u16 psc);
void TIM3_PWM_Init(u16 arr,u16 psc);
#endif
至此,所有编码过程结束。。。
第四章 故事结尾
没啥好说的,直接下进去执行看看对不对,其实调试的过程还是出现的一些状况。比如在LED初始化之前初始化了uart,uart正好也复用了PA9所以一直没有出结果,最后去掉了uart的初始化就好了,那有小司机就问题,我就是想用uart1怎么办,那你就去重映射IO吧。
下载到硬件,执行代码一切如所愿。。。故事结束。
第五章 升华主题
接下来要干嘛?接下来要实现红色led呼吸灯效果,这个可能比较复杂一点,,,就交给你们了。。。哈哈。。。
为什么定义为DBG_LED呢?因为我觉得这个电路和这套代码很适合作为调试的指示器,用最小的电路实现丰富的效果。当然,你也可以根据需求将PA9移植到其他IO。怎么样?没想到两个LED还能这么玩吧~
平淡而颓废的18年过去了,19年会多多输出一些有趣好玩的东西,额,,,不知道这句话会不会打脸,再见了。