发新帖本帖赏金 50.00元(功能说明)我要提问
返回列表
打印

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

[复制链接]
1770|7
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
blust5|  楼主 | 2023-8-15 15:30 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
#申请原创#    @21小跑堂  @21小跑堂  @21小跑堂


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

链接:
【技术分享】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中断

程序修改完之后,再次进行测试,异常现象排除,问题解决。
到这里,这个台阶流水灯项目已经完成了,功能也达到了预期。
详细代码放到附件里,供大家参考。

GD32E230_Waterfall_Light.zip (1.45 MB)

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 50.00 元 2023-08-18
理由:恭喜通过原创审核!期待您更多的原创作品~

评论
21小跑堂 2023-8-18 18:32 回复TA
自动检测灯珠数量不愧为一个好的想法,作者通过产生想法-理论推导-动手实践达到自己的目的,尽管过程曲折,还是通过探索完成了设计,点赞 
沙发
zhenykun| | 2023-8-15 20:43 | 只看该作者
这么大板子。。。

使用特权

评论回复
板凳
blust5|  楼主 | 2023-8-16 08:15 | 只看该作者
zhenykun 发表于 2023-8-15 20:43
这么大板子。。。

板子是朋友画的 不是我画的。。。

使用特权

评论回复
地板
tpgf| | 2023-9-11 12:14 | 只看该作者
对于一个项目来说 如何排查软件上的问题呢

使用特权

评论回复
5
heimaojingzhang| | 2023-9-11 13:06 | 只看该作者
可以只通过软件编程来排除逻辑上的错误码

使用特权

评论回复
6
keaibukelian| | 2023-9-11 19:00 | 只看该作者
可以进行分段识别吗 这样就不用人工参与了

使用特权

评论回复
7
Bowclad| | 2023-9-27 20:45 | 只看该作者
自动检测灯珠数量用电流估算准不准啊?

使用特权

评论回复
发新帖 本帖赏金 50.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:业精于勤荒于嬉,行成于思毁于随。

72

主题

2806

帖子

11

粉丝