OpenEdv-开源电子网

 找回密码
 立即注册

扫一扫,访问微社区

正点原子新作:阿波罗STM32F767&F429&探索者STM32F4开发板&赶快来下载资料哦。

查看: 414|回复: 8

程序问题:调用多个子程序,出现不能动态更新数据的问题

[复制链接]

  离线 

6

主题

55

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
284
金钱
284
注册时间
2017-5-21
在线时间
43 小时
发表于 2018-3-7 21:26:09 | 显示全部楼层 |阅读模式
正点原子公众号
小弟遇到一个棘手的程序问题,我的main函数调用了另外一个.c文件里面的数据采集的子函数,然后这个子函数还调用了adc.c里的获取AD的子函数(DMA方式)。然后主函数还有调用了OLED显示的函数,发现AD值不能动态更新,屏幕要按键下翻页之后才能刷新数值。但是我如果直接在主函数中调用ADC.c里的获取AD的子函数,OLED是能动态刷新的。我的AD变量是采用结构体的方式弄的,不知道会不会是这个问题,我还是很少用结构体的,可能会有出现问题,还望大佬们指点一二。
附上部分代码:
mian.c
[C] 纯文本查看 复制代码
PidType TemperaturePid;//PID控制器参数   该结构体在control.h中定义
SensorParameterType SensorParameter[8];//传感器参数    该结构体在collect.h中定义
ControlParameterType ControlParameter;//系统控制结构体参数  该结构体在ui.h中定义

extern PidType TemperaturePid;
extern SensorParameterType SensorParameter[8];
extern ControlParameterType ControlParameter;
    for(;;)
    {
        ControlParameter.WorkNumTemp = ControlParameter.WorkNum;
        switch(ControlParameter.WorkNum)
        {
            case 0://任务0 数据采集(AD) + 数据处理(滤波)
                //AnalogCollcet();
                AdcCollect();
//                SensorParameter[0].Value = AdcDmaGetAverageValue(0);
//                SensorParameter[1].Value = AdcDmaGetAverageValue(1);
//                SensorParameter[2].Value = AdcDmaGetAverageValue(2);

                break;
            case 1://任务1 输出控制(PID) + 数据上传(UART)
                //PidControl();
                break;
            case 2://任务2 按键
                KeyControl();
                break;
            case 3://任务3 数据显示(oled)
                UiShow();
                break;
            default:
                break;
        }
        
        while(ControlParameter.WorkNum == ControlParameter.WorkNumTemp);//完成一次任务之后等待编号改动再执行下一个任务,防止重复执行任务
    }

collect.c
[AppleScript] 纯文本查看 复制代码
extern SensorParameterType SensorParameter[8];
void AdcCollect(void)
{
    //6模拟灰度传感器+2个红外传感器
    u8 i;
    for(i = 0; i < 8; i++)
    {
        SensorParameter[i].Value = AdcDmaGetAverageValue(i);
    }
}


adc.c
[AppleScript] 纯文本查看 复制代码
void AdcInit(void)//加入DMA初始化
{
    ADC_InitTypeDef ADC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    DMA_InitTypeDef DMA_InitStructure;
    
    RCC_AHBPeriphClockCmd(DmaClockCmd, ENABLE);//!!!
    RCC_APB2PeriphClockCmd(AdcGpioAClockCmd |AdcGpioCClockCmd | AdcClockCmd, ENABLE);
    
    
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);//设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
    
	//作为模拟通道输入引脚  PA2~7
    GPIO_InitStructure.GPIO_Pin = AdcPinChannel2 | AdcPinChannel3 | AdcPinChannel4 | AdcPinChannel5 | AdcPinChannel6 | AdcPinChannel7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入引脚
	GPIO_Init(AdcGpioA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = AdcPinChannel0 | AdcPinChannel1; //PC0 PC1
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入引脚
	GPIO_Init(AdcGpioC, &GPIO_InitStructure);
    
    ADC_DeInit(ADC1);  //复位ADC1
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式:ADC1和ADC2工作在独立模式
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;	//模数转换工作在多通道模式,扫描 !!!!
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;	//模数转换工作在多次转换模式,连续  !!!
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = N;	//顺序进行规则转换的ADC通道的数目 N !!!!
	ADC_Init(ADC1, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
    
    //设置指定ADC的规则组通道,设置它们的转化顺序和采样时间
    //ADC1,ADC通道x,规则采样顺序值为y,采样时间为239.5周期!!!!!!!!!1
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_239Cycles5 );
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 2, ADC_SampleTime_239Cycles5 );
    ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 3, ADC_SampleTime_239Cycles5 );
    ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 4, ADC_SampleTime_239Cycles5 );
    ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 5, ADC_SampleTime_239Cycles5 );
    ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 6, ADC_SampleTime_239Cycles5 );
    ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 7, ADC_SampleTime_239Cycles5 );
    ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 8, ADC_SampleTime_239Cycles5 );
    
    
    DMA_DeInit(DMA1_Channel1); //将DMA的通道1寄存器重设为缺省值
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR; //DMA外设ADC基地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&AdcConvertValue; //DMA内存基地址!!!!!!
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //内存作为数据传输的目的地
    DMA_InitStructure.DMA_BufferSize = M*N; //DMA通道的DMA缓存的大小   !!!!!
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //数据宽度为16位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //数据宽度为16位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //工作在循环缓存模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_High; //DMA通道 x拥有高优先级
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
    DMA_Init(DMA1_Channel1, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道
    
    DMA_Cmd(DMA1_Channel1,ENABLE);  
    ADC_DMACmd(ADC1, ENABLE);// 开启ADC的DMA支持(要实现DMA功能,还需独立配置DMA通道等参数)
    
    ADC_Cmd(ADC1, ENABLE);	//使能指定的ADC1
    
	ADC_ResetCalibration(ADC1);	//使能复位校准   
	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
    
	ADC_StartCalibration(ADC1);	 //开启AD校准
	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
    
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);//由于没有采用外部触发,所以使用软件触发ADC转换 !!!!!
}

u16 AdcDmaGetAverageValue(u8 ch)
{
    u32 sum = 0;
    u8 count;
    for ( count=0;count<M;count++)
    {
        sum += AdcConvertValue[count][ch];
    }
    AdcAverageValue[ch]=sum/M;
    return AdcAverageValue[ch];
}


ui.c

[AppleScript] 纯文本查看 复制代码
 
extern PidType TemperaturePid;
extern ControlParameterType ControlParameter;
extern SensorParameterType SensorParameter[8];

OledPrintf(0,0,"%02d",ControlParameter.PageIndex+1);//显示页码
            OledPrintf(32,0,"%s","Environment");//显示标题
            OledPrintf(0,1,"%s","Num      Val      Max");//显示标题
            OledPrintf(0,2,"%-4d    %4d    %4d",1,SensorParameter[0].Value,SensorParameter[0].Max);//显示1号灰度传感器
            OledPrintf(0,3,"%-4d    %4d    %4d",2,SensorParameter[1].Value,SensorParameter[1].Max);//显示2号灰度传感器
            OledPrintf(0,4,"%-4d    %4d    %4d",3,SensorParameter[2].Value,SensorParameter[2].Max);//显示3号灰度传感器
            OledPrintf(0,5,"%-4d    %4d    %4d",4,SensorParameter[3].Value,SensorParameter[3].Max);//显示4号灰度传感器
            OledPrintf(0,6,"%-4d    %4d    %4d",5,SensorParameter[4].Value,SensorParameter[4].Max);//显示5号灰度传感器
            OledPrintf(0,7,"%-4d    %4d    %4d",6,SensorParameter[5].Value,SensorParameter[5].Max);//显示6号灰度传感器

回复

使用道具 举报

  离线 

6

主题

55

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
284
金钱
284
注册时间
2017-5-21
在线时间
43 小时
 楼主| 发表于 2018-3-10 18:04:45 | 显示全部楼层
后来更改了系统结构,问题才得以解决。小弟之前采用的是方式是,中断程序里让一个全局变量任务编号加加,然后主程序里去查询任务编号在做相应的任务,此法只能适用于各项任务的时间都小于中断的时间。实际应用的时候,会经常遇到有些任务耗时严重,例如OLED显示任务,非常耗时,我自己写的OLED大概显示一次需要60ms左右,因此不便于采用以上的方法进行时间分片(我理解此法为假时间分片)。后来我听我同学告诉我另外一种系统设计结构,前后台结构,主函数前台(优先级低,费时),中断后台(优先级高任务量小),个人认为此种方法更有优势一些。
回复 支持 反对

使用道具 举报

  离线 

6

主题

55

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
284
金钱
284
注册时间
2017-5-21
在线时间
43 小时
 楼主| 发表于 2018-3-7 22:03:53 | 显示全部楼层
后来我把定时器7中断时间改到10ms,OLED能刷新了,但是非常慢,一卡一卡的,这样的响应速度,怕是车都飞出去了。不知道什么子程序这么耗时间...或者说结构体耗时?那也不至于耗得这么凶吧,还是要把中断时间在改短一些,我以前2ms都能刷新的很快...见鬼了。请大佬们指教
回复 支持 反对

使用道具 举报

  离线 

7

主题

247

帖子

0

精华

高级会员

Rank: 4

积分
745
金钱
745
注册时间
2016-1-20
在线时间
78 小时
发表于 2018-3-8 13:59:21 | 显示全部楼层

回帖奖励 +2

你的ADC多久采集一次数据?你的OLED多久刷新一次?
回复 支持 反对

使用道具 举报

  离线 

6

主题

55

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
284
金钱
284
注册时间
2017-5-21
在线时间
43 小时
 楼主| 发表于 2018-3-8 16:29:13 | 显示全部楼层
正点原子公众号
footprint 发表于 2018-3-8 13:59
你的ADC多久采集一次数据?你的OLED多久刷新一次?

具体我也不知道怎么测量,大佬怎么测量呀
回复 支持 反对

使用道具 举报

  离线 

6

主题

55

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
284
金钱
284
注册时间
2017-5-21
在线时间
43 小时
 楼主| 发表于 2018-3-10 14:05:06 | 显示全部楼层
Anyint 发表于 2018-3-8 16:29
具体我也不知道怎么测量,大佬怎么测量呀

OLED显示耗时 0.321-0.262s
回复 支持 反对

使用道具 举报

  离线 

0

主题

399

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1277
金钱
1277
注册时间
2017-7-4
在线时间
282 小时
发表于 2018-3-10 23:04:21 | 显示全部楼层
这时候你需要一个RTOS,一个线程刷新OLED、一个线程采集ADC,来n个任务都不怕。这就是OS的优势
回复 支持 反对

使用道具 举报

  离线 

7

主题

247

帖子

0

精华

高级会员

Rank: 4

积分
745
金钱
745
注册时间
2016-1-20
在线时间
78 小时
发表于 2018-3-13 14:58:02 | 显示全部楼层
Anyint 发表于 2018-3-10 14:05
OLED显示耗时 0.321-0.262s

OLED刷新时间,你可以通过IO口反转用示波器测量,也可以使用定时器去测量,然后显示。你用定时器控制这AD的采集周期和显示周期看能不能正常。
回复 支持 反对

使用道具 举报

  离线 

7

主题

247

帖子

0

精华

高级会员

Rank: 4

积分
745
金钱
745
注册时间
2016-1-20
在线时间
78 小时
发表于 2018-3-13 15:09:10 | 显示全部楼层
Anyint 发表于 2018-3-10 14:05
OLED显示耗时 0.321-0.262s

假设你对楼下说的os不会应用的话,先使用定时器去做一下试试。设计两个定时器,1,OLED的优先级和ADC的优先级相等,看有没有出现不刷新;2,OLED高于ADC,也就是ADC工作会被打断,看是否正常;3,ADC高于OLED,也就是OLED工作过程中可能会被打断,看是否正常。注意:ADC定时器的时间要大于ADC采集的总时间,OLED定时器的时间要大于OLED工作的总时间。
如果优先级相等,刷新正常,个人认为是当你在函数中调用ADC的时候把某个时序给打乱了;
那么下面的俩个测试你就好好分析下,你是否要严格控制ADC和OLED工作的时序,使其工作时不能相互影响,或者强行指定一个程序不能被另一个程序给打断。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则




QQ|联系我们|手机版|官方淘宝店|新浪微博|微信公众平台|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2018-6-23 16:33

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表
/* */