@richey
2020-09-23T03:01:31.000000Z
字数 11123
阅读 826
物联网
讲义
嵌入式软件
考虑这样一个常见的系统:、
经典方法:前后台系统(超级循环)
问题
如何解决以上问题?
参考代码
#include <reg51.h>
#include "common.h"
#define FOSC 11059200ul
#define T0_H (65536-(10*FOSC)/(12*1000))/256
#define T0_L (65536-(10*FOSC)/(12*1000))%256
uint16_t tick = 0;
bit bFlag1 = 0;
bit bFlag2 = 0;
bit bFlag3 = 0;
bit bFlag4 = 0;
void initSys();
sbit LED0 = P1^0;
sbit LED1 = P1^1;
sbit LED2 = P1^2;
sbit LED3 = P1^3;
void main(){
initSys();
while(TRUE){
if(bFlag1){
bFlag1 = 0;
LED0 = !LED0;
}
if(bFlag2){
bFlag2 = 0;
LED1 = !LED1;
}
if(bFlag3){
bFlag3 = 0;
LED2 = !LED2;
}
if(bFlag4){
bFlag4 = 0;
LED3 = !LED3;
}
}
}
void initSys(){
TMOD = 0x01;
TH0 = T0_H;
TL0 = T0_L;
EA = 1;
ET0 = 1;
TR0 = 1;
}
void timer0() interrupt 1 {
TH0 = T0_H;
TL0 = T0_L;
tick++;
if((tick % 100) == 0){
bFlag1 = 1;
}
if((tick % 150) == 3){
bFlag2 = 1;
}
if((tick % 20) == 5){
bFlag3 = 1;
}
if((tick % 300) == 7){
bFlag4 = 1;
}
}
新的问题?
有限状态机的概念:FSM
以下都是状态机程序其实就是状态机
状态机的要素
状态机的实现方法
void Task1Function(void) {
If(taskState==STATE1) {
执行动作1();
taskState=STATE2;
return;
}
If(taskState==STATE2) {
执行动作2();
taskState=STATE3;
return;
}
If(taskState==STATE3) {
执行动作3();
taskState=STATE4;
return;
}
If(taskState==STATE4) {
执行动作4();
taskState=STATE1;
return;
}
}
void Task1Function(void) {
switch(taskState) {
case STATE1: {
执行动作1();
taskState=STATE2;
break;
}
case STATE2: {
执行动作2();
taskState=STATE3;
break;
}
case STATE3: {
执行动作3();
taskState=STATE4;
break;
}
case STATE4: {
执行动作4();
taskState=STATE1;
break;
}
default:{
执行动作5();
taskState=STATE1;
}
}
void fun1(void) {
}
编译器会如何处理?(汇编代码 )
调用该函数的方法:
fun1();
void (*pFun1)() = fun1;
//调用该函数的方法:
(*pFun1)();
//或
pFun1();
用函数指针代替switch case语 句实现一个计算器
代码片段
double caculate(int oper, doubleop1, double op2)
switch( oper) {
case ADD:
result = add( op1, op2);
break;
case SUB:
result = sub( op1, op2);
break;
case MUL:
result = mul( op1, op2);
break;
case DIV:
result = div( op1, op2);
break;
}
}
对于一个新奇的具有上百个操作符的计算器,这条switch语句将非常长。
编译器的问题:可能会将多个case语句的代码编译为if/else 结构,当然也可能优化为函数指针结构。
//使用函数指针实现:代码片段
//函数声明
double add (double,double);
double sub (double,double);
double mul(double,double);
double div (double,double);
......
double (*oper_func[ ] )( double, double)={ add,sub,mul,div,... };
//用下面这条语句替换前面整条switch 语句!
result = oper_func[oper](op1, op2);
//或
result = (*oper_func[ oper])(op1, op2);
状态机的函数指针实现:
typedef void(*State)(Fsm *me, unsigned const sig);
/*状态定义:函数指针,状态迁移后直接调用对应的处理函数*/
struct Fsm{
State state_;/* 状态机本身由函数指针实现*/
};
#define FsmDispatch(me_, sig_) ((*(me_)->state_)((me_), sig))
/*状态分发,代替任务函数*/
#define TRAN(target_) (((Fsm*) me)->state_ = (State)(target_)) /*状态迁移*/ /*状态函数(子任务)*/
void FsmFunction1(Fsm *me, unsigned const sig) {
switch (sig) {
case SLASH_SIG:
//do sth here;
TRAN(FsmFunction2); /* 迁移到下一个状态*/
break;
}
//...
}
嵌入式软件架构设计之 合作式调度器+有限状态机架构的实现方法
main() {
A_taskInit();//任务的初始化
B_taskInit();
...
while(1) {
A_taskProc();//对于长任务,用状态机切割
B_taskProc();
}
}
关于延时的实现方法,以下为OS支持下任务的写法
void xxxTask(void) {
while(1){
//waitEvent
//do step_1
/*操作系统延时,让出CPU*/
rt_thread_sleep(rt_tick_t tick);
rt_thread_delay(rt_tick_t tick);
rt_thread_mdelay(rt_int32_t ms);
//do step2
}
}
void xxxTask(void) {
static unsigned inttaskStat= STAT_GENERAL;//任务状态变量 static rt_tick_t startTick;
static rt_tick_t tick currentTick;
if (taskStat == STAT_GENERAL) {
//check event
//if no event
return;
//do step_1
startTick = rt_tick_get(); //rt_tick_get()就是察看系统时间
taskStat = STAT_WAIT;
return;
}
else if (taskStat == STAT_WAIT) {
currTick= rt_tick_get();
if ((currTick-startTick) >= TIME_OUT_TICK_NUM) {
//do step_2
taskStat= STAT_GENERAL;
return;
}
else return;
}
方法:将8通道采集切割为8个子任务
//不切割任务的方法
uint8_t ad_chnnel;
for(ad_chnnel = 0; ad_chnnel < 8; ad_chnnel++) {
//AD转换,通道号ad_chnnel由for语句自动加1,连续转换8次
temperature = read_ad(ad_chnnel);
ad_result[ad_chnnel] = temperature;//存入数组
}
//切割任务的方法,每次只进行一次转换
static uint8_t ad_chnnel = 0; //声明为全局变量
if(ad_chnnel < 8) {
temperature = read_ad(ad_chnnel);//AD转换,
ad_result[ad_chnnel] = temperature;//存入数组
ad_chnnel++;//通道号加1
}
else
ad_chnnel = 0;
这种方法的主体思想是:利用一个独立的定时器,在其中断处理程序中根据状态的不同 ,动态的修改TCNTn的值,并执行相应状态的代码
typedef void(*lfsm_func_t)(void);
typedef struct {
uint8_t delta_t;
lfsm_func_t fsm_func;
lfsm_t *next_fsm;//一定要形成一个环形链表
}lfsm_t;
//初始化
lfsm fsm_18B20[]= { …如何初始化? }
void timer0_ISR(void) {
static lfsm_t * current_fsm = fsm_18B20;
lfsm_t *temp_fsm = current_fsm;
TCNT0 = current_fsm->delta_t;//修改到达下一次中断的时间
current_fsm = current_fsm->next_fsm;
temp_fsm->fsm_func();//调用状态机
}
在RTOS环境下,一般提供抢占式调度。RTOS环境下的任 务,一般处于一个while(1)循环中。
while(1){
从消息队列接收消息。如果没有,将阻塞。 处理消息。
}
//范例1:
void Fun1(int iCh) {
intI,j,iTmp;
i=0;
if(iCh<8) {
GetAdValue();
}
}
//范例2:
void Fun2(int iCh)
{
switch(iCh)
{
case 0:
GetAdValue(iCH);
break;
case 1: …
}
}
关于命名的风格,应用比较广的有三种。
那么,该选用哪种命名风格呢?建议是“取百家之长”,混用这几种中能够让名字辨识度最高的那些优点:
#define MAX_PATH_LEN 256 //常量,全大写
#define BUAD_RATE 19200
int g_sys_flag; // 全局变量,加g_前缀
float g_currentValue;
//类型定义加_t后缀
typedef struct {
WSAOVERLAPPED ovlp;
ngx_event_t *event;
int error;
} ngx_event_ovlp_t;
//结构体定义加_s后缀
struct ngx_chain_s {
ngx_buf_t *buf;
ngx_chain_t *next;
};
namespace linux_sys { // 名字空间,全小写
void get_rlimit_core(); // 函数,全小写
}
class FilePath final // 类名,首字母大写
{
public:
void set_path(const string& str); // 函数,全小写
private:
string m_path; // 成员变量,m_前缀
int m_level; // 成员变量,m_前缀
};
命名另一个相关的问题是“名字的长度”,一个被普遍认可的原则是:变量 / 函数的名字长度与它的作用域成正比,也就是说,局部变量 / 函数名可以短一点,而全局变量 / 函数名应该长一点。
define
用预处理指令#define
声明一个常数
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)ul
#define
语法的基本知识(不能以分号结束,括号的使用)l
,告诉编译器这个常数是的长整型数。ul
(表示无符号长整型),那 么你有了一个好的起点。MIN
#define MIN(A,B) ((A)<= (B) ? (A) : (B))
if-then-else
更优化的代码。least = MIN(*p++, b);
//思考:有什么副作用?static
void fun1(void) {
static int counts= 0 ;
counts++;
}
fun1();
fun1();
fun1();
static int counts2;
void fun1(void) {
counts2= 0 ;
counts2++;
}
void fun2(void) {
counts2++;
}
fun1();
fun2();
static void fun1(void) {
counts3= 0 ;
counts++;
}
- 作用:仅供本模块函数调用
- 以上第2,3种用法: 本地化数据和代码范围的好处和重要性
3. const
const int tmp;
int const tmp;
const int* ptr1; //问题:pData值是否可变?
int* const ptr2;
//问题:ptr2值是否可变?
int const * ptr3 const;//问题:ptr3值是否可变?
思考:编译器如何处理?地址分配在什么地方?
使用const
关键字的好处:
- 给读你代码的人传达非常有用的信息.
- 给优化器一些附加的信息,能产生更紧凑的代码。
- 使编译器很自然地保护那些不希望被改变的参数 ,防止其被无意的代码修改。
4. volatile
#define rNCACHBE0 (*(volatile unsigned *)0x1c00004)
#define rNCACHBE1 (*(volatile unsigned *)0x1c00008)
5.typedef:类型定义
#define mys_s struct s*
typedef struct s* mys_t;
mys_s p1,p2;
mys_t p3,p4;
以上有什么区别? typedef
的作用
6. extern
声明外部变量 跨模块的全局变量
moudule1.c
int iState;
moudule2.c
extern int iState;
变量(函数)声明与定义的区别:
声明:不分配存储空间,定义:分配存储空间 以下那个是申明,那个是定义?
extern int x = 1024;
int y;
extern void reset (void *p) {}
extern const int *pi;
void print(const matrix &);
__asm
int result;
void Add(long a, long *b) {
__asm{
MOV AX, a
MOV BX, b
ADD AX, [BX]
MOV result, AX
}
}
__asm void wait() {
nop
nop
nop
nop
nop
nop
BX lr
}
0x67a9
内存赋值为0xaa55
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;
#define PI (3.1415926)
float EXTI0_IRQHandler(float fRadius ){
double area = PI * fRadius * fRadius ; //
printf("\nArea = %f", area);
return area;
}
错误4:printf()是不可重入的
知识点:函数的可重入性(reentrant)
总的来说,如果一个函数在重 入条件下使用了未受保护的共
享的资源,那么它是不可重入 的。 示例:
int tmp;
void func1(int* x, int* y) {
tmp = *x;
*x = *y;
*y = tmp;
}
void swap_char2(char *lpcX, char *lpcY) {
static char cTemp;
cTemp = *lpcX;
*lpcX = *lpcY;
lpcY = cTemp;
}
void func2(int* x, int* y) {
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
}
共享数据的问题(中断的数据保护)
static int temperatures[2];
void main(void) {
int temp0,temp1;
while(TURE){
temp0 = temperatures[0];
temp1 = temperatures[1];
if(iTemp0 != iTemp1){
...
}
}
}
void interrupt reead_temperatures(void) {
temperatures[0] = ??;//从硬件读取温 度值
??//
temperatures[1] = ??;//从硬件读取温 度值
}
思考:如果第1条赋值语句被中断会发生什么?
解决方案:
disable_interrupt;
temp0 = temperatures[0];
temp1 = temperatures[1];
enable_interrupt;
共享数据的问题(中断的数据保护)
以下代码有什么问题?如何修改?
static int iSeconds , iMinutes , iHours;
/*module1.h*/
int a = 5; /* 在模块1的.h文件中定义int a */
/*module1 .c*/
#include "module1.h" /* 在模块1中包含模块1的.h文件 */
/*module2 .c*/
#include "module1.h" /* 在模块2中包含模块1的.h文件 */
/*module3 .c*/
#include "module1.h" /* 在模块3中包含模块1的.h文件 */
以上代码有什么问题?
正确的方法
/*module1.h*/
extern int a; /* 在模块1的.h文件中声明int a */
/*module1.c*/
#include "module1.h" /* 在模块1中包含模块1的.h文件 */
int a = 5; /* 在模块1的.c文件中定义int a */
/*module2 .c*/
#include "module1.h" /* 在模块2中包含模块1的.h文件 */
/*module3 .c*/
#include "module1.h" /* 在模块3中包含模块1的.h文件 */
划分模块的原则:以下是最简单的划分方法 一个嵌入式系统通常包括两类模块:
1. 硬件驱动模块
一种特定硬件对应一个模块;
一个硬件驱动模块通常应包括如下函数:
2.软件功能模块
其模块的划分应满足低耦合、高内聚的要求。
例如:主程序1个模块
如何避免重复引用头文件:
module1.h
#ifndef _module1_h
#define _module1_h
....//头文件具体内容
#endif