呼吸主导在Tiva C系列TM4C123G



我必须写一个C代码,以便板上的RGB LED呼吸。我的代码闪烁不呼吸。我的老师说,不同的亮度是通过改变占空比来实现的,所以在这种情况下我不能使用pwm。请帮助我理解此代码。

#include <stdint.h>
#include <stdlib.h>
#define SYSCTL_RCGC2_R          (*((volatile unsigned long *)0x400FE108))
#define SYSCTL_RCGC2_GPIOF       0x00000020  //port F clock gating control
#define GPIO_PORTF_DATA_R       (*((volatile unsigned long *)0x400253FC))
#define GPIO_PORTF_DIR_R        (*((volatile unsigned long *)0x40025400))
#define GPIO_PORTF_DEN_R        (*((volatile unsigned long *)0x4002551C))

void delay (double sec);
int cond;
int main(void){
SYSCTL_RCGC2_R = SYSCTL_RCGC2_GPIOF;

GPIO_PORTF_DIR_R=0x0E;
GPIO_PORTF_DEN_R=0x0E;
cond=0;
while(1){

GPIO_PORTF_DATA_R = 0x02;
delay(12.5);
GPIO_PORTF_DATA_R = 0x00;
delay(0);
GPIO_PORTF_DATA_R = 0x02;
delay(2.5);
GPIO_PORTF_DATA_R = 0x00;
delay(10);

GPIO_PORTF_DATA_R = 0x02;
delay(5);
GPIO_PORTF_DATA_R = 0x00;
delay(7.5);
GPIO_PORTF_DATA_R = 0x02;
delay(7.5);
GPIO_PORTF_DATA_R = 0x00;
delay(5);
GPIO_PORTF_DATA_R = 0x02;
delay(12.5);
GPIO_PORTF_DATA_R = 0x00;
delay(0);
GPIO_PORTF_DATA_R = 0x02;
delay(7.5);
GPIO_PORTF_DATA_R = 0x00;
delay(5);
GPIO_PORTF_DATA_R = 0x02;
delay(5);
GPIO_PORTF_DATA_R = 0x00;
delay(7.5);


}
return 0;

}
void delay(double sec){
int c=1, d=1;
for(c=1;c<=sec;c++)
for(d=1;d<= 4000000;d++){}
}

有两种方法可以驱动LED:通过某些通用I/O保持恒定电流,或者通过PWM重复占空比。PWM意味着脉宽调制,它将发生在人眼无法注意到的太快的脉冲上,可能是从100Hz到10kHz左右的任何地方。

PWM的主要优点是可以轻松控制电流。RGB的情况意味着3个单独LED的颜色强度。大多数较小的LED的额定电流为20mA,因此这通常是您的目标最大电流,对应于100%占空比。 实现此目的的正确方法是使用PWM。

但是您当前的代码所做的是通过拉动GPIO引脚来"位爆炸"模拟PWM。这是非常粗糙和低效的。通常,微控制器内置定时器和/或PWM硬件外设,您只需提供占空比,硬件就会从那里处理所有事情。在这种情况下,您将设置3个PWM硬件通道,理想情况下应同时计时。

LED是具有不同正向电压的二极管,具体取决于化学性质。因此,您很可能对 3 种颜色中的每种颜色都有不同的正向电压。您必须检查RGB的数据表,并寻找坎德拉的发光强度。在这种情况下,很可能是毫坎德拉,mcd。假设您的绿色 LED 有 300mcd,但红色和蓝色有 100mcd。它们在某种程度上是线性的,或者你可以假设它们是线性的。因此,在这种情况下,一个粗略的等式是给绿色LED的电流比其他LED少3倍,以获得均匀的颜色混合。补偿后,您可以为您的 3 个 PWM 通道提供 RGB 代码,并希望获得相应的颜色。


作为旁注,代码中的延迟函数在很多方面都完全被破坏了。这种繁忙延迟的循环迭代器必须是易失性的,否则任何半体面的编译器都会在启用优化时简单地消除延迟。也没有理由使用浮点数。

如果您使用延迟函数执行此操作,并且延迟分辨率以代码中建议的秒为单位,那么它当然会"闪烁" - 频率需要比人类视觉感知更快 - 例如大约 50Hz,然后为了获得平滑的变化,您可以将其划分为 20 个级别, 需要毫秒级延迟。

在任何情况下,您的delay()函数都会通过获取浮点数秒数但将其与整数循环计数器进行比较来击败自己 - 它只会在整整秒内工作。

因此,给定一个函数delayms( unsigned millisec )(我将在后面讨论):

#define BREATHE_UPDATE_MS 100
#define BREATHE_MINIMUM 0
#define PWM_PERIOD_MS 20
unsigned tick = 0 ;
unsigned duty_cycle = 0 ;
unsigned cycle_start_tick= 0 ;
unsigned breath_update_tick = 0 ;
int breathe_dir = 1 ;
for(;;)
{
// If in PWM "mark"...
if( tick - cycle_start_tick < duty_cycle )
{
// LED on
GPIO_PORTF_DATA_R |= 0x02 ;
}
// else PWM "space"
else
{
// LED off
GPIO_PORTF_DATA_R &= ~0x02 ;
}
// Update tick counter
tick++ ;
// If PWM cycle complete, restart
if( tick - cycle_start_tick >= PWM_PERIOD_MS )
{
cycle_start_tick = tick ;
}
// If time to update duty-cycle...
if( tick - breath_update_tick > BREATHE_UPDATE_MS )
{
breath_update_tick = tick ;
duty_cycle += breathe_dir ;
if( duty_cycle >= PWM_PERIOD_MS )
{
// Breathe in
breathe_dir = -1 ;
}
else if( duty_cycle == BREATHE_MINIMUM )   
{
// Breathe out
breathe_dir = 1 ;
}
}

delayms( 1 ) ;
}

BREATHE_UPDATE_MS改为呼吸更快,将BREATHE_MINIMUM改为"浅呼吸"——即不变暗。

如果您的延迟函数确实以秒为单位产生延迟分辨率,那么大约相当粗略:

void delayms( unsigned millisec )
{
for( int c = 0; c < millisec; c++ )
{
for( volatile int d = 0; d < 4000; d++ ) {}
}
}

但是,这对我来说表明内核时钟速率相当低,因此您可能需要进行调整。 请注意使用volatile来防止优化器删除空循环。 这种延迟的问题在于,您需要将其校准为目标的时钟速度,并且在任何情况下,其时序都可能有所不同,具体取决于您使用的编译器和使用的编译器选项。 这通常是一个糟糕的解决方案。

在实践中,为此使用"繁忙循环"延迟是不明智和粗糙的,最好使用 Cortex-M SYSTICK:

volatile uint32_t tick = 0 ;

void SysTick_Handler(void)  
{
tick++ ;
}

。从原件上取下ticktick++;法典。 然后,您不需要在上面的循环中出现延迟,因为所有时序都与tick的值挂钩。 但是,如果您因其他原因想要延迟,那么:

delayms( uint32_t millisec )
{
uint32_t start = tick ;
while( tick - start < millisec ) ;
}

然后,您将在启动时初始化 SYSTICK,以便:

int main (void)  
{
SysTick_Config(SystemCoreClock / 1000) ;

...
}

这假定您正在使用 CMSIS,但您的代码表明您没有这样做(甚至没有使用供应商提供的寄存器标头)。 在这种情况下,如果您(或您的导师)坚持这一点,您将需要弄脏 SYSTICK 和 NVIC 寄存器。SysTick_Config()的来源如下:

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
{
return (1UL);                                                   /* Reload value impossible */
}
SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
SysTick->VAL   = 0UL;                                             /* Load the SysTick Counter Value */
SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk   |
SysTick_CTRL_ENABLE_Msk;                         /* Enable SysTick IRQ and SysTick Timer */
return (0UL);                                                     /* Function successful */
}

最新更新