msOS学习之路(4)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_41863685/article/details/88378551

设备层简单理解

1 设备层相关定义

设备层的相关定义是在device.h文件中定义的,包括按键、模拟量输入、数字量输入/输出枚举或者类型定义等,对于一些结构体的理解,例如:ADC结构体,定义了ADC的使用到的一些成员变量。

typedef struct 
{
    ushort A0;
    ushort A1;
    ushort A2;
    ushort A3;
    ushort * pA0;   # 获取数据所在寄存器地址
    ushort * pA1;
    ushort * pA2;
    ushort * pA3;
}AdcStruct;

2 ADC

ADC的初始化分为两个部分一个是IO初始化,另一个是ADC本身初始化。接着进行DMA设置,进行高速采样。在stm32提高了两到三个ADC端口,对于stm32可以使用DMA做对一个ADC1做高速采样,而ADC2不能使用DMA做高速采样,可以使用节拍或者定时器做低速的采样,这样可以把速度不同的分开,以防止高速的拖累低速的设备,这样两个高低速配合,以调高效率。

DMA把采用过来的数据放到DmaBuffer里面,总共有5路,4路ADC采样和1路温度,深度为8个表示每一路放8个数据。在采用高速的节拍对数据进行处理,在AdcSystick10000Routine函数中是对采用数据进行数据处理,进行了一些滤波操作,这里的滤波是对八个数据做累加求平均值,而对于温度部分在获取到模拟量以后需要进行线性转,映射成温度值。经过节拍滤波后放进寄存器里面。然后把存放在ADC数组中的数据与数据库进行连接,实现的代码为:

static void PortRegister(void)
{
    AppDataPointer->Adc.pA0 = &Adc[0];
    AppDataPointer->Adc.pA1 = &Adc[1];
    AppDataPointer->Adc.pA2 = &Adc[2];
    AppDataPointer->Adc.pA3 = &Adc[3];
}

通过以上可以获取到滤波之后的ADC值,如果想要更快的获取ADC值,就直接从DmaBuffer中获取,但是这些值是没有经过滤波的,对于深度为1的场景,可以去掉系统节拍滤波这个功能。

3 DI

PLC开关输入接口,有两种模式一种是开关量输入和PWM捕获。

代码理解:pX直接与位与对接起来,因为位与直接对应的是开关量输入的信号,直接映射到位与寄存器里面。

AppDataPointer->DI.pX0 = (bool *)&PaIn->Bit4; 
AppDataPointer->DI.pX1 = (bool *)&PaIn->Bit5;
AppDataPointer->DI.pX2 = (bool *)&PaIn->Bit6;
AppDataPointer->DI.pX3 = (bool *)&PaIn->Bit7;

但是,这样的做法不是很好,因为直接获取到的数据会有噪音,所以有时候需要进行滤波,对于这个问题要解决的方式是使用节拍对获取到的值进行滤波,然后再存到一个变量中,这时候保存到这个变量的值就是经过滤波之后的值。

4 DO

DO有开关量输出和PWM输出两种状态。首先,每个信号与位与绑定,实现的代码如下:

AppDataPointer->DO.pY0 = (bool *)&PbOut->Bit0;   
AppDataPointer->DO.pY1 = (bool *)&PbOut->Bit1;
AppDataPointer->DO.pY2 = (bool *)&PbOut->Bit12;
AppDataPointer->DO.pY3 = (bool *)&PbOut->Bit13;
AppDataPointer->DO.pY4 = (bool *)&PbOut->Bit14;
AppDataPointer->DO.pY5 = (bool *)&PbOut->Bit15;

对于PWM输出模式,可以通过Open(PwmEnum channel)可以选择输出的通道,设置定时器。通过SetDutyRatio函数可以设置输出的占空比。SetParameter可以设置输出的频率和占空比,对于DO部分主要就是这些函数。

5 Key 按键

按键有长按与短按的区别,按键是通过节拍来运行的,当检测到按键之后通过PostMessage()函数发出消息,让节拍处理。

6 LCD 屏

主要是用于显示用的,主要的函数有LcdDisplayString函数,实现的代码如下:

// y表示显示的行号,取值为0、1、2、3。string表示要显示的内容,最多16个字节
static void LcdDisplayString(byte y, string string)
{ 
    byte i;   	
    SendInstruct(Array[y]);
    for (i = 0; i < 16; i++)
        SendData(*string++);
}

还有就是要进行初始化,初始化完成之后把函数关联起来。在进行初始化的时候,一般先进行端口的初始化,然后再进行GPIO口的初始化,这样会有避免端口跳动的危险。所以,在msOS中要先对开关量信号进行初始化,然后再进行端口的配置。例如:

PinClk = 0;	PinCs = 0; PinData = 0; PinReset = 0; # 开关量信号
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;

7 Misc

主要是用于蜂鸣器响声,但是在按键当中的蜂鸣器没有使用到这个函数,按键的蜂鸣器使用时独立的。当要使用蜂鸣器的时候直接调用SetBeep,并设置启动方式即可。

8 Rtc 时间显示

Rtc是用于时间显示的,在节拍中被调用,模拟时间。

9 Storage 参数存储

9.1 简介

stm32中的flash中的数字只有0和1,对于里面的数值只能把1改为0而不能把0直接改为1,要把0变为1的时候要整页的擦除,才能进行更改。所以,要写数据的时候,常规的做法是先把要写的数据存储起来,然后把整页擦除掉,然后再写进去。而在msOS中使用的是另一种方法,是追加式的,追加式意思是哪个参数改了,就把哪个参数写进去就行了。但追加方式会出现重名的情况,因为每次修改之后都会进行追加,然后保存这样就出现了重名的情况,对于这种情况,在msOS中只存储最后一次值,如果还有多余的的数据,那么久把要把正业擦除掉,然后再进行保存。在使用追加式进行参数存储还需要注意的是,对于每个数据都要有保存该数据的地址,不然会造成无法使用数据的情况。所以创建一个结构体,结构体的代码如下:

typedef struct
{
    uint Address;               //参数存储单元地址
    uint Data;                  //参数存储单元数据
}CellStruct;

9.2 写入

追加式写入的函数为WriteParameter(void * dataPointer) 当传递过来一个要加入的地址以后,首先判断该地址是否为空或者已经填满,当都不是这两种情况的时候,对数据进行追加,把数据写到指定的位置上。

9.3 开机整理

对于使用追加式方式,在开机的时候需要对数据进行整理,不然会出现有错的情况,使用对数据整理的函数使用ReadAllParameter(void)函数,会清除一些垃圾数据。在数据整理的函数中,当开机的时候首先判断存储器的地址是否为空(0xffffffff),如果不为空,就把这个数据保存起来,并把数据写到对应的变量上,因为msOS有对应的数据库。对于保存了地址之后,就要判断地址是否有重叠,即判断地址是否有相等的,如果有相等的就把前面的地址设置为空,并把清理的标志(CleanFlag)设置为true。接下来就要判断CleanFlag是否为false,如果为false那么这个数据是有效的不需要进行处理。对无效数据数据处理的过程为,首先把整页进行擦除,之后再把之前保存的有效数据写回去。

10 系统节拍

系统节拍采用的是1s一万次中断,因为在msOS中上层能调用底层,底层不能调用上层,但是上层有时候需要调用系统节拍,所以在msOS中采用了注册机制,在系统节拍文件中定义了很多注册变量,在变量中存储函数指针,例如:

static function Systick10000RegisterPointerBlock[Systick10000Sum] =
{
    Dummy, Dummy  // 这两个变量用作存储函数指针
};

从整体上来讲,系统的节拍每秒是一万次的节拍。但是,每个节拍中我们不可能放很多个函数,因为有些函数是需要低速进行的,但有些函数需要高速的进行处理,这样在msOS中采用分频的方式把他们放在不同的节拍进行处理。因为stm32的频率是72M,系统节拍是每秒10000次,所以在每个节拍中能够执行7200个指令,所以,如果很多个函数都放在同一个节拍中,指令超过7200的话会出现问题。在msOS中使用了分频的方式,保证了每个节拍执行的指令数少于7200个,并把低速和高速处理的函数分开,提高处理效率。在该开始开系统节拍的时候并没有想到这一部分这样处理的作用,但是知道以后,发现这个思想真的太完美了。

对于系统节拍的入口是SysTick_Handler函数,每秒中一万次进入这个函数。经过分频可分为每秒10000、1000和100次,把需要高速处理的函数放在每秒10000次的节拍里面,比如ADC,而对于低速处理的比如按键可以放在每秒1000次的节拍中。当上次需要用节拍的时候进行注册就可以。统计可以直接使用RegisterSystick函数进行注册,而上层的直接调用Register进行注册。

11 Timer 定时器

当使用定时器的时候就调用static void Start(int id, TimerModeEnum mode, int times, function functionPointer)函数调用开启定时器。参数mode表示选择处理的方式,有系统节拍处理和逻辑任务处理两种方式,一般在系统节拍中处理的函数是时时任务比较高的、代码量较小任务。在msOS中,对于紧急的任务可以直接用中断,优先级最高,次优先级用系统节拍,再接着就是业务逻辑,最后是菜单界面。该定时器是基于系统节拍实现的,虚拟出来的软件定时器,所以,要把执行的程序TimerSystick1000Routine放到系统节拍中,每1s会调用1000次,当开启定时器后,当定时时间到达时,就会判断是哪种模式,当是逻辑任务处理时候,就会抛出消息,在逻辑业务中进行处理,如果是系统节拍的方式,就会直接执行。

12 串口

在串口中,发送数据是用一个队列来进行发送的,将要发送的数据放到队列当中。真正的发送时在系统节拍中发送的。在系统节拍发送之前首先判断队列是否为空并行判断串口是否空闲,当达到条件时就会发送数据,在msOS中实现的代码如下:

void Usart1Systick1000Routine(void) 
{
    if (TxdQueue.Entries == 0) return;  //判断队列是否为空
    if ((USART1->SR&0x80)==0) return;   // 判断串口是否忙

    USART1->DR = *TxdQueue.Out++;   // 发送数据
    
    if (TxdQueue.Out > TxdQueue.End) 
        TxdQueue.Out = TxdQueue.Start;

    EnterCritical();
    TxdQueue.Entries--;
    ExitCritical();
}

13 总结

通过学习msOS设备层,感觉还有很多不明白的地方。对设备的配置、执行过程和一些函数的作用等都不是很清楚。以上是个人的一些简单理解,有写地方可能不对,还有很多需要改进的地方,但本人目前知识有限,现阶段只能理解到这个层次,今后在更深的理解上会进行修改。

2018年7月

猜你喜欢

转载自blog.csdn.net/weixin_41863685/article/details/88378551