#申请原创# @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中断
程序修改完之后,再次进行测试,异常现象排除,问题解决。 到这里,这个台阶流水灯项目已经完成了,功能也达到了预期。 详细代码放到附件里,供大家参考。
|
自动检测灯珠数量不愧为一个好的想法,作者通过产生想法-理论推导-动手实践达到自己的目的,尽管过程曲折,还是通过探索完成了设计,点赞