【技术分享】GD32台阶流水灯项目之软件BUG排查与解决



书接上回,之前写了两篇关于台阶流水灯项目的帖子,里面介绍了项目背景和硬件相关内容,以及流水灯运行逻辑处理过程。
 


链接:
【技术分享】GD32台阶流水灯项目之硬件问题与改善
https://bbs.21ic.com/icview-3319996-1-1.html?fromuser=blust5

【技术分享】GD32台阶流水灯项目之亮灯流程完善
https://bbs.21ic.com/icview-3320762-1-1.html?fromuser=blust5

实物照片:
 



 

台阶流水灯的运行逻辑已经在上篇文章中介绍完毕,这里介绍一下其他内容。
程序参数的设定。
程序参数最开始是准备通过按键进行参数设定,设定完毕之后保存在FLASH里。后面考虑到流水灯的灯数量偏多,于是考虑自动检测灯的数量来进行参数设定,减少人员操作次数。
那么怎么自动检测灯的数量呢?可以通过驱动电流的大小来确定灯的数量。由于不同流水灯带可能会在同样灯数量的情况下具有不同的电流值,因此不能以电流的绝对值来判断,需要用相对值来判断。
于是在台阶灯和扶手流水灯的驱动接口的GND上串接一个0.5R的电阻,通过AD采样其两端电压来确认电流情况。

接口处电路。

信号放大与采样电路。
通过上述电路,将电流信号转换为电压信号,并经过放大之后连到单片机的AD口上,通过AD采样进行数值采样。
复制
// ADC功能初始化

void adc_gpio_config(void)

{

    /* enable the GPIO clock */

    rcu_periph_clock_enable(RCU_GPIOA);

    /* ADCCLK = PCLK2/6 */

    rcu_adc_clock_config(RCU_ADCCK_APB2_DIV6);

    /* enable ADC clock */

    rcu_periph_clock_enable(RCU_ADC);



    gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_2|GPIO_PIN_3);

}



void adc_config(void)

{

    /* ADC channel length config */

    adc_channel_length_config(ADC_REGULAR_CHANNEL, 1);

    /* ADC external trigger source config */

    adc_external_trigger_source_config(ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_NONE);

    /* ADC external trigger enable */

    adc_external_trigger_config(ADC_REGULAR_CHANNEL, ENABLE);

    /* ADC data alignment config */

    adc_data_alignment_config(ADC_DATAALIGN_RIGHT);

    /* ADC special function config */

    adc_special_function_config(ADC_SCAN_MODE, ENABLE);

    /* ADC oversample mode config */

    adc_oversample_mode_config(ADC_OVERSAMPLING_ALL_CONVERT, ADC_OVERSAMPLING_SHIFT_4B, ADC_OVERSAMPLING_RATIO_MUL16);

    /* ADC oversample mode enable */

    adc_oversample_mode_enable();

    /* enable ADC interface */

    adc_enable();

    delay_1ms(1);

    /* ADC calibration and reset calibration */

    adc_calibration_enable();

}



void adc_init(void)

{

    adc_gpio_config();

    adc_config();

}
复制
// ADC采样函数

uint16_t ADC_Read(uint8_t channel)

{

    uint16_t adcValue = 0;



    // 配置ADC通道转换顺序,采样时间为55.5个时钟周期

    adc_regular_channel_config(0, channel, ADC_SAMPLETIME_55POINT5);

    // 由于没有采用外部触发,所以使用软件触发ADC转换

    adc_software_trigger_enable(ADC_REGULAR_CHANNEL);



    while(!adc_flag_get(ADC_FLAG_EOC));                       // 等待采样完成

    adc_flag_clear(ADC_FLAG_EOC);                             // 清除结束标志



    adcValue = adc_regular_data_read();                         // 读取ADC数据

    return adcValue;

}

配合亮灯驱动函数,从1开始逐颗增加亮灯数量,然后采样ADC,确认ADC值是否变化超过一定值。如果有变化一定值,则确认有新的灯亮起,否则认为已亮起所有灯,数量不再变化,由此确认灯的数量。
自动检测灯的数量的同时,数码管进行“-”的跑马显示,防止被误认为死机状态。
复制
void led_num_init()

{

    static uint8_t com = 0;

    uint16_t i, cycle;

    uint16_t prev_adc1=0, prev_adc2=0, now_adc1=0, now_adc2=0;



    gpio_bit_set(LED_ABC_PORT,LED_A_PIN|LED_B_PIN|LED_C_PIN);

    gpio_bit_set(LED_D_DP_PORT,LED_D_PIN|LED_E_PIN|LED_F_PIN|LED_DP_PIN);

    gpio_bit_reset(LED_D_DP_PORT, LED_G_PIN);



    para_data[DATA_STEP_NUM] = 0;

    para_data[DATA_COLOR_NUM] = 0;



    for(cycle=1; cycle<STEP_NUM_MAX*2; cycle++)

    {

        gpio_bit_set(LED_COM_PORT,LED_COM1_PIN|LED_COM2_PIN|LED_COM3_PIN|LED_COM4_PIN);

        switch(com)

        {

        case 0:

            gpio_bit_reset(LED_COM_PORT,LED_COM1_PIN);

            break;

        case 1:

            gpio_bit_reset(LED_COM_PORT,LED_COM2_PIN);

            break;

        case 2:

            gpio_bit_reset(LED_COM_PORT,LED_COM3_PIN);

            break;

        case 3:

            gpio_bit_reset(LED_COM_PORT,LED_COM4_PIN);

            break;

        default:

            com = 0;

            gpio_bit_reset(LED_COM_PORT,LED_COM1_PIN);

            break;

        }

        com ++;



        for(i=0; i<STEP_NUM_MAX; i++)

        {

            if(i < cycle)

            {

                ws_step_set1(0xFFFFFF);

            }

            else

            {

                ws_step_set1(0);

            }

        }

        ws_step_reset();



        for(i=0; i<STEP_NUM_MAX*2; i++)

        {

            if(i < cycle)

            {

                ws_color1_set1(0xFFFFFF);

            }

            else

            {

                ws_color1_set1(0);

            }

        }

        ws_color1_reset();

        for(i=0; i<STEP_NUM_MAX*2; i++)

        {

            if(i < cycle)

            {

                ws_color2_set1(0xFFFFFF);

            }

            else

            {

                ws_color2_set1(0);

            }

        }

        ws_color2_reset();



        delay_1ms(300);

        now_adc1 = ADC_Read(STEP_LED_ADC);

        now_adc2 = ADC_Read(COLOR_LED_ADC);

        if((now_adc1 > prev_adc1)&&(now_adc1 - prev_adc1 > 20))

        {

            para_data[DATA_STEP_NUM] = cycle;

        }

        if((now_adc2 > prev_adc2)&&(now_adc2 - prev_adc2 > 10))

        {

            para_data[DATA_COLOR_NUM] = cycle;

        }

        prev_adc1 = now_adc1;

        prev_adc2 = now_adc2;



        if((cycle > para_data[DATA_STEP_NUM])&&(cycle > para_data[DATA_COLOR_NUM]))

        {

            break;

        }

    }



    if((para_data[DATA_STEP_NUM] == 0)||(para_data[DATA_STEP_NUM] > STEP_NUM_MAX))

    {

        para_data[DATA_STEP_NUM] = 1;

    }

    if((para_data[DATA_COLOR_NUM] == 0)||(para_data[DATA_COLOR_NUM] > STEP_NUM_MAX*2))

    {

        para_data[DATA_COLOR_NUM] = 1;

    }

}

以上逻辑在原理上没有问题,但是在实际测试过程中发现,台阶灯由于单颗灯(单颗LED驱动芯片)对应的是一个灯条,因此有明显的电流变化,可以准确的测出数量;但是扶手流水灯在数量大道一定值后,基本上已经测不到电流变化情况了(硬件源头上已经无法测出,因此软件无法解决),因此所测数量偏小。
最终该方案被丢弃,返回最开始确定的直接通过按键设置灯的数量,但是增加了在设置灯数量时,同步亮起对应的灯,进行提示,方便人员观察设置是否准确,而不需要去一颗颗数出来数量之后再设置。
同时将灯的亮度参数设为隐藏参数,需同时长按1、3按键三秒以上才可进行修改。
复制
void key_press_process()

{

    static uint8_t key_press_cnt = 0;



    if(key1_press_flag == 3)

    {

        key_press_cnt = 0;

以上是关于AD采样自动识别灯的数量方案的测试与最终丢弃过程。
在实际测试过程中还发现,在灯的数量达到一定数量以上之后,前面的灯点亮时,后面某一位置的灯可能会出现误点亮情况,所有灯都熄灭时也可能出现该现象。
开始以为灯带过长导致信号畸变,从而被LED驱动芯片误识别的原因。
后面想了一下,如果是误识别,不应该是在固定位置,而且信号是经过LED驱动芯片转发的,实际信号传输距离很短。
然后继续分析该异常情况出现的原因,认为可能是中断响应影响了LED驱动信号的时序。
LED驱动芯片时序如下图。

一个数据位约需要1.5us时间,一颗LED驱动芯片需要24个数据位,约36us,当灯的数量达到28颗时,改变一次灯的状态所发出的驱动信号时间就是1008us,已经超过1ms了。
而GD32MCU使用的滴答定时器systick的中断间隔为1ms,因此当灯的数量超过28个以上时,在发送LED驱动信号的过程中就会出现被中断响应打断的情况。
由于LED驱动芯片识别数据位的时序已经到了100ns级别,中断响应花费的时间完全足以改变其状态,从而使其将0码误识别为1码,导致误亮灯。
而且误亮灯的位置基本上在第28-30颗灯的位置,也与该分析吻合。
于是最终确认出现该异常的原因为中断响应打断了LED驱动信号的发送进程,导致LED驱动芯片误识别驱动信号。
那么怎么解决呢?可以考虑在LED驱动信号发送过程中屏蔽中断。
但是在查看了芯片手册之后,发现GD32的滴答定时器systick中断无法屏蔽。
只能将systick的中断间隔更改为100ms(不足以误触发LED驱动信号的程度),同时另外开启一个普通定时器(这里使用Timer0),设定其定时间隔为1ms,用来代替之前systick的作用,用以给系统提供时间基准。并在发送LED驱动信号时关闭定时器的中断允许。
复制
// 定时器初始化函数

void timer_config(void)

{

    /* TIMER0CLK = SystemCoreClock / 72 = 1MHz */

    timer_parameter_struct timer_initpara;



    rcu_periph_clock_enable(RCU_TIMER0);

    timer_deinit(TIMER0);



    /* TIMER0 configuration */

    timer_initpara.prescaler         = 71;

    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;

    timer_initpara.counterdirection  = TIMER_COUNTER_UP;

    timer_initpara.period            = 1000;

    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;

    timer_initpara.repetitioncounter = 0;

    timer_init(TIMER0,&timer_initpara);



    /* auto-reload preload enable */

    timer_auto_reload_shadow_enable(TIMER0);

    timer_interrupt_flag_clear(TIMER0, TIMER_INT_FLAG_UP);

    timer_interrupt_enable(TIMER0, TIMER_INT_UP);

    timer_enable(TIMER0);

}
复制
// 定时器中断处理函数

void TIMER0_BRK_UP_TRG_COM_IRQHandler(void)

{

    static uint16_t time_cnt_ms = 0;

    static uint8_t key_scan_cnt = 0;



    if(SET == timer_interrupt_flag_get(TIMER0, TIMER_INT_FLAG_UP))

    {

        timer_interrupt_flag_clear(TIMER0, TIMER_INT_FLAG_UP);



        key_scan_cnt ++;

        if((key_scan_cnt%4)==0)

        {

            led_scan_flag = 1;

        }

        if((key_scan_cnt%10) == 0)

        {

            key_scan_flag = 1;

        }

        if(key_scan_cnt >= 20)

        {

            key_scan_cnt = 0;

        }



        time_cnt_ms ++;

        if(time_cnt_ms%20 == 0)

        {

            time_20ms_flag = 1;

        }

        if(time_cnt_ms%100 == 0)

        {

            time_100ms_flag = 1;

        }

        if(time_cnt_ms%500 == 0)

        {

            time_500ms_flag = 1;

        }

        if(time_cnt_ms >= 1000)

        {

            time_1s_flag = 1;

            time_cnt_ms = 0;

        }

    }

}

然后再所有驱动LED改变状态的位置按如下操作进行,在最终信号输出至IO口时,关闭中断。
复制
    nvic_irq_disable(TIMER0_BRK_UP_TRG_COM_IRQn);  // 关闭定时器0中断

    for(i=0; i<para_data[DATA_STEP_NUM]; i++)

    {

        if(led_state_1[i] + led_state_2[i] > 0)

        {

            ws_step_set1(step_light_data);

        }

        else

        {

            ws_step_set1(0);

        }

    }

    ws_step_reset();

    nvic_irq_enable(TIMER0_BRK_UP_TRG_COM_IRQn, 3);  // 开启定时器0中断

程序修改完之后,再次进行测试,异常现象排除,问题解决。
到这里,这个台阶流水灯项目已经完成了,功能也达到了预期。
详细代码放到附件里,供大家参考。
---------------------
作者:blust5
链接:https://bbs.21ic.com/icview-3321054-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值