stm32嵌入式开发

C语言总结(stm32嵌入式开发)

c程序小知识点总结

1.静态变量static与外部变量extern的使用

静态变量static两种使用方式

一.定义局部变量

  1. 用static声明后的局部变量的值在函数调用结束后不消失而保持原值,其占用的是静态存储区,所以即其占用的存储单元不释放。典型应用是用static变量求阶乘n!
// 求阶乘 n!
int fac(int)
{
   static int f = 1 ;
   f = f * n ;
   return f ;
}
main()
{
   int i ,n  ;
   float sum;
   scanf("%d",&n) ;
   for(i = 1 ;i <= n ;i++)
   {
       sum = fac(i) ;
   }
   printf("输出%d!= %f ",n,sum)}

2.这样声明的变量,其存储在程序的bss段,而在bss段在程序中执行时会初始化为0.

// eg:
static int b[5] ;
//则b[0] = 0 ,b[1] = 0等等. 而如果不是这样定义则输出可能是乱码

二.定义全局变量
当定义全局变量只限于本文件使用,而不被其他文件使用,这时可以在定义外部变量的时候加一个static声明。注意定义加了static和不加的外部变量都是存放在静态存储区,而区别就在于上一句的“只限于本文件使用”。

外部变量extern

作用:声明外部变量,扩展它在程序文件中的作用域。即在文件1定义的全局变量int A,可以在文件2中定义 extern A ,便可调用这个文件1中的全局变量。比如在实际具体工程项目中,在定时器文件中定义的全局变量时间int32_t g_iRunTime ,最长可以表示 24.85天,也就是你的终端可持续运行的时间,在其他项目其他文件中,像主函数文件中也需调用用于判断运行时间则就用extern g_iRunTime调用。

2.函数封装后返回值的方法

一. 只需返回一个值得时候

  1. 常用的简单方法就是定义函数为有返回值的函数,其函数的类型标识符与返回值得类型一致。
    比如 int fun();函数返回就是int类型的数值。
  2. 定义一个全局变量,因为全局变量存在于静态存储区,当函数调用的时候改变了全局变量的值,也实现了函数返回值得功能。

二. 要求返回多个值得时候

  1. 前两者返回一个值得方法可以共用以达到返回多个值。
  2. 在形参中定义一个数组或者指针,并在主函数中定义一个数组来保存返回的值,比如下面代码:
// 函数fun返回了三个值,通过return1个,和数组s[2]两个,以达到要求
int fun(int str[2]);
main()
{
    int s[2] ;//定义一个数组来存放fun函数的返回的a  b 之间最大值,最小值
    scantf("%d%d",&a,&b);
    if(fun(a,b,s))
    {
        printf("大的%d,小的%d",a[0],a[1]);
    }
    else 
        printf("相等");    
}
int fun(int a ,int b ,int str[2])
{
    if(a > b)
    {
        str[0] = a ;
        str[1] = b ;
    return 1 ;
    }
    if(a < b)
    {
        str[0] = b ;
        str[1] = a ;
        return 1 ;
    }
    else
    return 0 ;
}

需要注意的是,一般原则:在程序设计中,再划分模块时要求模块”内聚性”强,与其他模块的“耦合性”弱。简单理解就是,在封装的各个函数中各有自己独立功能,即功能单一,方便调用,而不能搞的太乱。对于全局变量需限制使用

3.循环控制continue与break的区别

在项目工程当中,或者简单的C语言代码当中,循环使用的非常普遍,掌握循环的各个形式非常重要,这里简单的介绍下循环控制中continuebreak的区别。
利用代码体现:

//简介循环控制continue与break的区别
main()
{
    while(表达式一)
    {
        if(表达式二)
        {
            break ; //跳出循环,也即跳出while语句转执行下面的语句
        }
    } 
    while(表达式一)
    {
        if(表达式二)
        {
            continue ; //跳到条件限制,也即跳到while语句判断中
        }
    }
}

如上面清晰易懂,在写代码当中或者看懂下载得源代码中,如果不是在主函数main中出现的while(1),其他函数中出现while(1),都必然存在一个break语句,在首先学习看懂源代码的任务中,这点比较重要。(看来这点只适合初学者啊,只要有点经验都知道这些浅显的但关键的知识点)

项目程序规范

1.消息队列FIFO

核心思想通过消息队列机制,在MAIN函数中,通过消息传递任务来执行。 当某一任务需要执行的时候,通知消息队列,主程序去执行。 主程序在函数中解析消息队列,看有无任务

  1. 首先在头文件中定义队列的类型,以及消息队列的尺寸。
typedef struct
             {
                uint8_t *MsgRam;	 /*队列缓存*/						 
				uint8_t MsgSize;   /*队列大小*/ 
				uint8_t MsgCount;  /*收到的新消息个数*/
				uint8_t MsgWritePoint;  /*写指针*/
				uint8_t MsgReadPoint;  /*读指针*/
			}Msg_Fifo;
#define TaskSize   (100)   /*消息队列尺寸*/	
  1. 定义消息类型,用一个枚举型enum
typedef enum
           {
              Idletask = 0....            //消息类型,根据项目中的各个需要Cpu处理的模块决定
           }
  1. 初始化队列结构体
void bsp_Init_Msg_Fifo(void)
{
  _MsgTask.MsgSize = TaskSize; 
	_MsgTask.MsgRam =  _MsgRam;  /*队列缓存*/
   _MsgTask.MsgCount = 0;       /*新数据个数*/
   _MsgTask.MsgReadPoint = 0;   /*读指针*/
   _MsgTask.MsgWritePoint = 0;  /*写指针*/
}
  1. 建立一个函数:uint8_t MsgtGetMes( Msg_List *_pByte) ,从消息队列中读出一条信息。和一个函数: uint8_t MsgtPushMes( Msg_List *_pByte),写入到队列一条消息。
  2. 判断有无任务需要执行(这个时候需要用到软件定时器(下面将具体介绍),每个功能到设置一定的时间去向cpu发送处理数据),将需要执行的任务放入队列函数void AppTaskScan(void),巡检任务。
  3. 建立一个 函 数 : vTaskStartScheduler(), 任务调度函数,将消息队列中读出一个任务去执行,先发出信号的任务先执行,如果有一个任务同时触发相同的任务,按队列的先后去执行这依程序。

**至此,**在主函数mian中的while(1)中调用5,6两个函数询检即可。

队列FIFO运用主要是对CPU分配管理:一般的计算机系统只有一个CPU,如果在系统中的多个进程都满足运行条件,这就可以用一个就绪队列来进行管理。当某个进程需要执行的时候,它的进程名就插入到就绪队列的尾端。如果此队列是空的,CPU就立即执行此进程;如果此队列非空,则该进程就需要排在队列的尾端进行等待。CPU总是首先执行排在队首的进程,一个进程分配的一段时间执行完了,又将它插入队尾进行等待,CPU转而为下一个出现在队首的进程服务。如此。按照“FIFO”的原则一直进行下去,直到执行完的进程从队列中删掉。

队列fifo还可用于许多场景,比如串口fifo,多串口运行时的分配。

2.软件定时器的使用

这部分我主要是针对stm32的程序来使用的,首先配置systick定时器作为系统滴答定时器。缺省定时周期为1ms。实现了多个软件定时器供主程序使用(精度1ms), 可以通过修改 TMR_COUNT 增减软件定时器个数。

这部分还是主要为CPU处理队列信息的使用做服务的。为每个单一任务设置定时器,一旦到了设定的时间就像队列写入一个该任务消息,直到CPU处理为止,定时器主要实现任务调度的问题。当然,风格不同的工程师有着不同的用法!

  1. 配置systick定时器,周期为1ms
    方法:在core_cm3.h有一个这样的函数uint32_t SysTick_Config(uint32_t ticks),该函数的形参表示内核时钟多少个周期后触发一次Systick定时中断
    – SystemCoreClock / 1000 表示定时频率为 1000Hz, 也就是定时周期为 1ms
    – SystemCoreClock / 500 表示定时频率为 500Hz, 也就是定时周期为 2ms
    – SystemCoreClock / 2000 表示定时频率为 2000Hz, 也就是定时周期为 500us
    所以我们一般在定时器初始化函数中调用SysTick_Config(SystemCoreClock / 1000);形成周期性中断函数每一ms执行中断一次,即需要中断就要写了个中断服务程序void SysTick_Handler(void);该函数主要为下面制定软件定时器的计数每隔1ms进行减一操作。

  2. 软件定时器的设置
    1.定义定时器结构体以及定时器的个数和各个任务定时器的名称

 typedef struct
{
	volatile uint8_t Flag;		/* 定时到达标志  */
	volatile uint32_t Count;	/* 计数器 */
	volatile uint32_t PreLoad;	/* 计数器预装值 */
}SOFT_TMR;
/* 软件定时器个数 */
#define TMR_COUNT          5   //自己设定需要多少软件定时器
/*定义定时器名称*/
#define C_TMR_ID           (4)  
#define C_TMR_ID           (3)
#define E1_TMR_ID          (2)
#define E_TMR_ID           (1)
#define cTMR_ID            (0)
  1. 现在主要写两个函数 一个:void bsp_StartAutoTimer(uint8_t _id, uint32_t _period)主要是将设定的实时计数器初值period的值装入结构体中,利用定时器中断服务程序自减一,并到减到0时Flag置为1,在另一个函数uint8_t bsp_CheckTimer(uint8_t _id)中判断Flag定时到达标志是否定时时间到了,返回1.用于void AppTaskScan(void)巡检判断任务。

总结:本文主要是根据自己的学习稍作总结,本人为新手,非常乐意分享学习成果,该文在为自己总结并记录的同时希望能帮助到读者!

发布了2 篇原创文章 · 获赞 3 · 访问量 163

猜你喜欢

转载自blog.csdn.net/peipanbryant/article/details/105610240