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

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

[复制链接]
2729|7
 楼主| blust5 发表于 2023-8-15 15:30 | 显示全部楼层 |阅读模式
#申请原创#    @21小跑堂  @21小跑堂  @21小跑堂


书接上回,之前写了两篇关于台阶流水灯项目的帖子,里面介绍了项目背景和硬件相关内容,以及流水灯运行逻辑处理过程。
2034064db244e9ceb2.png
链接:
【技术分享】GD32台阶流水灯项目之硬件问题与改善
https://bbs.21ic.com/icview-3319996-1-1.html?fromuser=blust5

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

实物照片:
2032664db24965c391.png


台阶流水灯的运行逻辑已经在上篇文章中介绍完毕,这里介绍一下其他内容。
程序参数的设定。
程序参数最开始是准备通过按键进行参数设定,设定完毕之后保存在FLASH里。后面考虑到流水灯的灯数量偏多,于是考虑自动检测灯的数量来进行参数设定,减少人员操作次数。
那么怎么自动检测灯的数量呢?可以通过驱动电流的大小来确定灯的数量。由于不同流水灯带可能会在同样灯数量的情况下具有不同的电流值,因此不能以电流的绝对值来判断,需要用相对值来判断。
于是在台阶灯和扶手流水灯的驱动接口的GND上串接一个0.5R的电阻,通过AD采样其两端电压来确认电流情况。
4661564db24c699469.png
接口处电路。
1894964db24cdc5f2f.png
信号放大与采样电路。
通过上述电路,将电流信号转换为电压信号,并经过放大之后连到单片机的AD口上,通过AD采样进行数值采样。
  1. // ADC功能初始化
  2. void adc_gpio_config(void)
  3. {
  4.     /* enable the GPIO clock */
  5.     rcu_periph_clock_enable(RCU_GPIOA);
  6.     /* ADCCLK = PCLK2/6 */
  7.     rcu_adc_clock_config(RCU_ADCCK_APB2_DIV6);
  8.     /* enable ADC clock */
  9.     rcu_periph_clock_enable(RCU_ADC);

  10.     gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_2|GPIO_PIN_3);
  11. }

  12. void adc_config(void)
  13. {
  14.     /* ADC channel length config */
  15.     adc_channel_length_config(ADC_REGULAR_CHANNEL, 1);
  16.     /* ADC external trigger source config */
  17.     adc_external_trigger_source_config(ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_NONE);
  18.     /* ADC external trigger enable */
  19.     adc_external_trigger_config(ADC_REGULAR_CHANNEL, ENABLE);
  20.     /* ADC data alignment config */
  21.     adc_data_alignment_config(ADC_DATAALIGN_RIGHT);
  22.     /* ADC special function config */
  23.     adc_special_function_config(ADC_SCAN_MODE, ENABLE);
  24.     /* ADC oversample mode config */
  25.     adc_oversample_mode_config(ADC_OVERSAMPLING_ALL_CONVERT, ADC_OVERSAMPLING_SHIFT_4B, ADC_OVERSAMPLING_RATIO_MUL16);
  26.     /* ADC oversample mode enable */
  27.     adc_oversample_mode_enable();
  28.     /* enable ADC interface */
  29.     adc_enable();
  30.     delay_1ms(1);
  31.     /* ADC calibration and reset calibration */
  32.     adc_calibration_enable();
  33. }

  34. void adc_init(void)
  35. {
  36.     adc_gpio_config();
  37.     adc_config();
  38. }
  1. // ADC采样函数
  2. uint16_t ADC_Read(uint8_t channel)
  3. {
  4.     uint16_t adcValue = 0;

  5.     // 配置ADC通道转换顺序,采样时间为55.5个时钟周期
  6.     adc_regular_channel_config(0, channel, ADC_SAMPLETIME_55POINT5);
  7.     // 由于没有采用外部触发,所以使用软件触发ADC转换
  8.     adc_software_trigger_enable(ADC_REGULAR_CHANNEL);

  9.     while(!adc_flag_get(ADC_FLAG_EOC));                       // 等待采样完成
  10.     adc_flag_clear(ADC_FLAG_EOC);                             // 清除结束标志

  11.     adcValue = adc_regular_data_read();                         // 读取ADC数据
  12.     return adcValue;
  13. }

配合亮灯驱动函数,从1开始逐颗增加亮灯数量,然后采样ADC,确认ADC值是否变化超过一定值。如果有变化一定值,则确认有新的灯亮起,否则认为已亮起所有灯,数量不再变化,由此确认灯的数量。
自动检测灯的数量的同时,数码管进行“-”的跑马显示,防止被误认为死机状态。
  1. void led_num_init()
  2. {
  3.     static uint8_t com = 0;
  4.     uint16_t i, cycle;
  5.     uint16_t prev_adc1=0, prev_adc2=0, now_adc1=0, now_adc2=0;

  6.     gpio_bit_set(LED_ABC_PORT,LED_A_PIN|LED_B_PIN|LED_C_PIN);
  7.     gpio_bit_set(LED_D_DP_PORT,LED_D_PIN|LED_E_PIN|LED_F_PIN|LED_DP_PIN);
  8.     gpio_bit_reset(LED_D_DP_PORT, LED_G_PIN);

  9.     para_data[DATA_STEP_NUM] = 0;
  10.     para_data[DATA_COLOR_NUM] = 0;

  11.     for(cycle=1; cycle<STEP_NUM_MAX*2; cycle++)
  12.     {
  13.         gpio_bit_set(LED_COM_PORT,LED_COM1_PIN|LED_COM2_PIN|LED_COM3_PIN|LED_COM4_PIN);
  14.         switch(com)
  15.         {
  16.         case 0:
  17.             gpio_bit_reset(LED_COM_PORT,LED_COM1_PIN);
  18.             break;
  19.         case 1:
  20.             gpio_bit_reset(LED_COM_PORT,LED_COM2_PIN);
  21.             break;
  22.         case 2:
  23.             gpio_bit_reset(LED_COM_PORT,LED_COM3_PIN);
  24.             break;
  25.         case 3:
  26.             gpio_bit_reset(LED_COM_PORT,LED_COM4_PIN);
  27.             break;
  28.         default:
  29.             com = 0;
  30.             gpio_bit_reset(LED_COM_PORT,LED_COM1_PIN);
  31.             break;
  32.         }
  33.         com ++;

  34.         for(i=0; i<STEP_NUM_MAX; i++)
  35.         {
  36.             if(i < cycle)
  37.             {
  38.                 ws_step_set1(0xFFFFFF);
  39.             }
  40.             else
  41.             {
  42.                 ws_step_set1(0);
  43.             }
  44.         }
  45.         ws_step_reset();

  46.         for(i=0; i<STEP_NUM_MAX*2; i++)
  47.         {
  48.             if(i < cycle)
  49.             {
  50.                 ws_color1_set1(0xFFFFFF);
  51.             }
  52.             else
  53.             {
  54.                 ws_color1_set1(0);
  55.             }
  56.         }
  57.         ws_color1_reset();
  58.         for(i=0; i<STEP_NUM_MAX*2; i++)
  59.         {
  60.             if(i < cycle)
  61.             {
  62.                 ws_color2_set1(0xFFFFFF);
  63.             }
  64.             else
  65.             {
  66.                 ws_color2_set1(0);
  67.             }
  68.         }
  69.         ws_color2_reset();

  70.         delay_1ms(300);
  71.         now_adc1 = ADC_Read(STEP_LED_ADC);
  72.         now_adc2 = ADC_Read(COLOR_LED_ADC);
  73.         if((now_adc1 > prev_adc1)&&(now_adc1 - prev_adc1 > 20))
  74.         {
  75.             para_data[DATA_STEP_NUM] = cycle;
  76.         }
  77.         if((now_adc2 > prev_adc2)&&(now_adc2 - prev_adc2 > 10))
  78.         {
  79.             para_data[DATA_COLOR_NUM] = cycle;
  80.         }
  81.         prev_adc1 = now_adc1;
  82.         prev_adc2 = now_adc2;

  83.         if((cycle > para_data[DATA_STEP_NUM])&&(cycle > para_data[DATA_COLOR_NUM]))
  84.         {
  85.             break;
  86.         }
  87.     }

  88.     if((para_data[DATA_STEP_NUM] == 0)||(para_data[DATA_STEP_NUM] > STEP_NUM_MAX))
  89.     {
  90.         para_data[DATA_STEP_NUM] = 1;
  91.     }
  92.     if((para_data[DATA_COLOR_NUM] == 0)||(para_data[DATA_COLOR_NUM] > STEP_NUM_MAX*2))
  93.     {
  94.         para_data[DATA_COLOR_NUM] = 1;
  95.     }
  96. }

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

  4.     if(key1_press_flag == 3)
  5.     {
  6.         key_press_cnt = 0;

以上是关于AD采样自动识别灯的数量方案的测试与最终丢弃过程。
在实际测试过程中还发现,在灯的数量达到一定数量以上之后,前面的灯点亮时,后面某一位置的灯可能会出现误点亮情况,所有灯都熄灭时也可能出现该现象。
开始以为灯带过长导致信号畸变,从而被LED驱动芯片误识别的原因。
后面想了一下,如果是误识别,不应该是在固定位置,而且信号是经过LED驱动芯片转发的,实际信号传输距离很短。
然后继续分析该异常情况出现的原因,认为可能是中断响应影响了LED驱动信号的时序。
LED驱动芯片时序如下图。
9067564db27fbed2da.png
一个数据位约需要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驱动信号时关闭定时器的中断允许。
  1. // 定时器初始化函数
  2. void timer_config(void)
  3. {
  4.     /* TIMER0CLK = SystemCoreClock / 72 = 1MHz */
  5.     timer_parameter_struct timer_initpara;

  6.     rcu_periph_clock_enable(RCU_TIMER0);
  7.     timer_deinit(TIMER0);

  8.     /* TIMER0 configuration */
  9.     timer_initpara.prescaler         = 71;
  10.     timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
  11.     timer_initpara.counterdirection  = TIMER_COUNTER_UP;
  12.     timer_initpara.period            = 1000;
  13.     timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
  14.     timer_initpara.repetitioncounter = 0;
  15.     timer_init(TIMER0,&timer_initpara);

  16.     /* auto-reload preload enable */
  17.     timer_auto_reload_shadow_enable(TIMER0);
  18.     timer_interrupt_flag_clear(TIMER0, TIMER_INT_FLAG_UP);
  19.     timer_interrupt_enable(TIMER0, TIMER_INT_UP);
  20.     timer_enable(TIMER0);
  21. }
  1. // 定时器中断处理函数
  2. void TIMER0_BRK_UP_TRG_COM_IRQHandler(void)
  3. {
  4.     static uint16_t time_cnt_ms = 0;
  5.     static uint8_t key_scan_cnt = 0;

  6.     if(SET == timer_interrupt_flag_get(TIMER0, TIMER_INT_FLAG_UP))
  7.     {
  8.         timer_interrupt_flag_clear(TIMER0, TIMER_INT_FLAG_UP);

  9.         key_scan_cnt ++;
  10.         if((key_scan_cnt%4)==0)
  11.         {
  12.             led_scan_flag = 1;
  13.         }
  14.         if((key_scan_cnt%10) == 0)
  15.         {
  16.             key_scan_flag = 1;
  17.         }
  18.         if(key_scan_cnt >= 20)
  19.         {
  20.             key_scan_cnt = 0;
  21.         }

  22.         time_cnt_ms ++;
  23.         if(time_cnt_ms%20 == 0)
  24.         {
  25.             time_20ms_flag = 1;
  26.         }
  27.         if(time_cnt_ms%100 == 0)
  28.         {
  29.             time_100ms_flag = 1;
  30.         }
  31.         if(time_cnt_ms%500 == 0)
  32.         {
  33.             time_500ms_flag = 1;
  34.         }
  35.         if(time_cnt_ms >= 1000)
  36.         {
  37.             time_1s_flag = 1;
  38.             time_cnt_ms = 0;
  39.         }
  40.     }
  41. }

然后再所有驱动LED改变状态的位置按如下操作进行,在最终信号输出至IO口时,关闭中断。
  1.     nvic_irq_disable(TIMER0_BRK_UP_TRG_COM_IRQn);  // 关闭定时器0中断
  2.     for(i=0; i<para_data[DATA_STEP_NUM]; i++)
  3.     {
  4.         if(led_state_1[i] + led_state_2[i] > 0)
  5.         {
  6.             ws_step_set1(step_light_data);
  7.         }
  8.         else
  9.         {
  10.             ws_step_set1(0);
  11.         }
  12.     }
  13.     ws_step_reset();
  14.     nvic_irq_enable(TIMER0_BRK_UP_TRG_COM_IRQn, 3);  // 开启定时器0中断

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

GD32E230_Waterfall_Light.zip (1.45 MB, 下载次数: 19)

打赏榜单

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

评论

自动检测灯珠数量不愧为一个好的想法,作者通过产生想法-理论推导-动手实践达到自己的目的,尽管过程曲折,还是通过探索完成了设计,点赞  发表于 2023-8-18 18:32
zhenykun 发表于 2023-8-15 20:43 | 显示全部楼层
这么大板子。。。
 楼主| blust5 发表于 2023-8-16 08:15 | 显示全部楼层
zhenykun 发表于 2023-8-15 20:43
这么大板子。。。

板子是朋友画的 不是我画的。。。
tpgf 发表于 2023-9-11 12:14 | 显示全部楼层
对于一个项目来说 如何排查软件上的问题呢
heimaojingzhang 发表于 2023-9-11 13:06 | 显示全部楼层
可以只通过软件编程来排除逻辑上的错误码
keaibukelian 发表于 2023-9-11 19:00 | 显示全部楼层
可以进行分段识别吗 这样就不用人工参与了
Bowclad 发表于 2023-9-27 20:45 | 显示全部楼层
自动检测灯珠数量用电流估算准不准啊?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

72

主题

2977

帖子

11

粉丝
快速回复 在线客服 返回列表 返回顶部