0%

park与反park的角度偏差

svpwm死区补偿

背景

在一些规模较小的项目中,系统的复杂度还没有到需要使用rtos的必要,并且rtos本身会占用大量的代码空间。此时可以使用protothread来对工程进行类操作系统task的包装,或者实现类似调度的功能。这样日后想上操作系统时可以方便的进行适配。

protothread是什么

维基百科

Protothreads是一种低开销的并发编程机制。Protothreads充当无栈的轻量级线程或协程,它使用了极小的每protothread内存:一个短整数保存执行位置,一个字节作为让步标志。

Protothreads可用于实现叫做协作式多任务的非抢占形式的并发计算,故而在一个线程yield(让步)给另一个线程的时候不会招致上下文切换。为了在一个protothread内达成yield,在线程函数内利用了达夫设备并在其switch语句内使用一个函数外部的变量。这允许在另一次函数调用时跳转(恢复)到上次的yield的地方。为了阻塞线程,这些yield要通过等待条件来守卫,使得后续的对同样这个函数的调用仍然yield,直到这个条件表达式是为真值为止。

原理

达夫设备

达夫设备利用了switch case语句的”穿透”特性,在case语句后如果没有break,则会一直执行到switch语句的结尾。以下方代码为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "stdio.h"
#include "stdlib.h"
void duffs_device_demo (int count) {
int n = (count+7)/8;
int val = count;
switch(count % 8) {
case 0: do { printf("%d ", val--);
case 7: printf("%d ", val--);
case 6: printf("%d ", val--);
case 5: printf("%d ", val--);
case 4: printf("%d ", val--);
case 3: printf("%d ", val--);
case 2: printf("%d ", val--);
case 1: printf("%d ", val--);
} while (--n > 0);
}
}

int main() {
duffs_device_demo(10);
}

输入count的值为10,n为2,在执行到switch语句时条件值为10%8=2,会跳转到”case 2:”语句处。然后会因为“穿透”的特性一直执行到”while(–n > 0)”这个语句处,此时–n为1,会继续跳转到do语句处再开始执行,然后再次“穿透”到while判断,n=0,退出循环。

最后执行的结果就是打印为:10 9 8 7 6 5 4 3 2 1

protothread的实现

protothread的代码都是用宏来定义。

一个典型的用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*define and init pt struct*/
struct pt m_pt_demo;
PT_INIT(pt_demo);

/*thread code*/
int demo_thread()
{
PT_BEGIN(&m_pt_demo);

while (1) {
/*user code*/

PT_YIELD(&m_pt_demo);

/*user code*/
}

PT_END(&m_pt_demo);
}

将宏进行展开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*PT_INIT*/
(m_pt_demo)->lc = 0;

int demo_thread()
{
/*PT_BEGIN*/
char PT_YIELD_FLAG = 1;
switch((m_pt_demo)->lc) {
case 0:

while(1) {
/*user code*/

/*PT_YIELD*/
do {
PT_YIELD_FLAG = 0;
/*LC_SET*/
(m_pt_demo)->lc = __LINE__; case __LINE__:
if(PT_YIELD_FLAG == 0) {
return PT_YIELDED;
}
} while(0)
}

/*PT_END*/
};
PT_YIELD_FLAG = 0;
(m_pt_demo)->lc = 0;
return PT_ENDED;
}

在第一轮进入demo_thread时,(m_pt_demo)->lc = 0,因此执行user code,然后进入yield,把PT_YIELD_FLAG置0,并把当前运行到的行号赋给(m_pt_demo)->lc,用于记录目前执行到的位置。然后退出函数,让出cpu。

当下一轮再次进入时,PT_YIELD_FLAG先置为1,然后到switch时直接跳转到了case __LINE__:处,由于PT_YIELD_FLAG为一,所以又运行user code,然后重复第一轮的操作。这就实现了一种看起来类似于“调度”的效果。

此外还有PT_WAIT_UNTIL,可以在条件不满足时让出cpu,并在条件满足后从上次执行到的位置来运行,

1
2
3
4
5
6
7
#define PT_WAIT_UNTIL(pt, condition)	        \
do { \
LC_SET((pt)->lc); \
if(!(condition)) { \
return PT_WAITING; \
} \
} while(0)

还支持“子线程”、信号量之类的功能。

缺点

不能保存现场,其“线程”并没有自己的栈,其局部变量的值无法保存到下一轮运行,所以只能用全局变量来保存需要使用的值。

不存在真正的“调度”过程,代码本质上还是按照顺序来执行。(也不能说时缺点,只能说特性)

总结

protothread仅仅用了4个.文件就实现了操作系统中的一些关键功能。非常适合一些轻量级的项目来使用,可以避免移植操作系统的麻烦以及操作系统的大量资源消耗。

但由于其无栈的特性,在使用时要小心现场的保存问题。

nuttx应用的载入过程(cmake版)

以apps/examples/helloxx这个应用为例
其应用入口为apps/examples/helloxx/helloxx_main.cxx中的
extern "C" int main(int argc, FAR char *argv[])函数
在该目录下CMakeLists.txt中将其添加到nsh的命令中

1
2
3
4
5
6
7
8
9
10
nuttx_add_application(   
NAME
helloxx
STACKSIZE
${CONFIG_DEFAULT_TASK_STACKSIZE}
MODULE
${CONFIG_EXAMPLES_HELLOXX}
SRCS
helloxx_main.cxx)
endif()

在nuttx/cmake/nuttx_add_application.cmake中将创建一个名为apps_helloxx的library

1
2
set(TARGET "apps_${NAME}")
add_library(${TARGET} ${SRCS})

再通过nuttx_add_library_internal将nuttx相关的头文件以及依赖传进来。

修改helloxx_main.cxx中main函数的名字改为helloxx_main

1
2
3
4
5
list(GET SRCS 0 MAIN_SRC)
set_property(
SOURCE ${MAIN_SRC}
APPEND
PROPERTY COMPILE_DEFINITIONS main=${NAME}_main)

将应用的主函数名字、应用名字、PRIORITY、STACKSIZE参数则传入对应的properties中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
set_target_properties(${TARGET} PROPERTIES APP_MAIN ${NAME}_main)
set_target_properties(${TARGET} PROPERTIES APP_NAME ${NAME})

if(PRIORITY)
set_target_properties(${TARGET} PROPERTIES APP_PRIORITY ${PRIORITY})
else()
set_target_properties(${TARGET} PROPERTIES APP_PRIORITY
SCHED_PRIORITY_DEFAULT)
endif()

if(STACKSIZE)
set_target_properties(${TARGET} PROPERTIES APP_STACK ${STACKSIZE})
else()
set_target_properties(${TARGET} PROPERTIES APP_STACK
${CONFIG_DEFAULT_TASK_STACKSIZE})

背景

买了台荣耀的v8pro想平时用来写写画画,顺便写写文档博客之类的,就想折腾下看看怎么在安卓平板上搞一套环境。反正有这篇文档的时候环境肯定是能用了。大致讲一下配置把

环境搭建

  1. markdown的编辑器用的软件是markor,对平板支持不错。

  2. git环境的搭建用的是termux,靠这个就可以在平板上用命令行了,
    先更新源
    apt update && apt upgrade
    再进行git安装
    pkg install git
    安装完成后,查看下版本信息,看看是否安装成功
    git --version

    输入如下指令,然后允许获取存储权限,平板的本地存储就会挂载到”/sdcard/”这个路径下面
    termux-setup-storage

    生存ssh密钥,把公钥同步到github上
    ssh-keygen

    git环境就配好了。

  1. 拉取好github上的仓库后,用markor进行编辑操作,再通过命令行推送到远程就ok了。

待解决和尝试

  1. 还没尝试在平板上搭建hexo的环境,所以博客的部署还需要通过ssh到服务器上来操作。要是能搭好环境,那就很爽了。

用samba搭建私有云

1、在ubuntu上安装samba

sudo apt-get install samba samba-common

2、修改samba配置文件

sudo vim /etc/samba/smb.conf
在最下方添加

[share]
comment = share folder
browseable = yes
path = /home/abc/data #需要共享的路径
create mask = 0755
directory mask = 0755  
valid users = abc   #用户名称
force user = abc
force group = abc
public = yes
available = yes
writable = yes 

如果要从外网访问,则要修改端口,(运营商一般默认关闭445端口)

在[global]标签下增加下面的语句,端口号为你想设置的端口
smb ports = 端口号

3、给共享目录添加权限

sudo chmod 777 /home/abc/data

4、添加用户并设置访问密码

sudo smbpasswd -a abc

5、重启samba服务器

sudo service smbd restart

如果没有修改端口号,则已经可以在局域网内访问了
在windows端打开运行 win+R
输入双反斜杠加IP或者域名就可以访问
\\192.168.1.11

6、windows端口转发设置

关闭占用445端口的应用
sc config LanmanServer start= disabled
net stop LanmanServer
开启ip helper服务
sc config iphlpsvc start= auto
设置转发
netsh interface portproxy add v4tov6 listenport=445 listenaddress=127.0.0.1 connectport=替换为端口号 connectaddress=替换为动态域名
因为我的域名是ipv6的解析,所以用v4tov6
重启电脑后生效。

7、windows下通过访问共享文件夹

注意,此时进行了端口转发,所以不是使用smb服务器的地址!
\\127.0.0.1
在输入用户名和访问密码后即可登录。

8、ios端访问

  用系统自带的服务器连接工具不能指定端口,需要借助第三方软件ES文件浏览器,在新建中选择SMB,然后输入域名、端口、用户名密码就可以访问。

背景

  在可靠性需求中,需要考虑在外部晶振失效的情况下,能够将系统的晶振源切换为内部的振荡器,让系统至少能够维持能够发出晶振失效告警的状态,如果能让系统保持正常系统正常运行那是最好。

方法

  在stm32中,有Clock security system (CSS)的机制,当外部晶振出现问题,比如晶振停振、短时间内出现大的频率偏差就会触发(fae说的,没有找到具体的触发机制文档),当触发时会系统时钟源会从HSE或者HSE为源的PLL切换到HSI,并进入NMI.(non-maskable interrupt)中断。


  当在cubemx中配置Enable CSS后,会在NMI中断中跳转到HAL_RCC_NMI_IRQHandler函数中,可以在该函数中进行时钟的重新配置,将PLL Source Mux的源调整为HSI,并将M\N\P的系数进行调整,使系统时钟与之前保持一致。之后系统会以内部时钟的形式继续运行。经过实测can通讯串口通讯不会受到影响,切换会花费多少时间还需测试。

切换时间测量

  测量切换时间可以使用stm32的MCO(microcontroller clock output)功能,将PLL时钟输出到引脚上,测量该引脚时钟消失的时间就可以得到切换时间。
  实际操作时,示波器没法在时钟消失的时候进行触发(频率触发没搞明白用法),配置一个GPIO为低电平,在NMI的中断函数中改为高电平,通过上升沿触发的方式来触发示波器。

从图中可以看出切换时间约为128us的时间。

​ 之前遇到需要将表示时间的字符串,例如“1995-10-08”,将其中的年月日提取出来用int型存储。C++(11)下没有直接转换标准时间格式的函数。直接用if语句提取来进行判断并截取十分的麻烦费时。

​ 可以用strtok_s(linux下为strtok_r)函数进行处理,其形式为:

1
2
3
char *strtok_s( char *strToken, const char *strDelimit, char **buf);


​ 其返回值是被切割的第一个子字符串。strToken为被切割的字符串,strDelimit包含分隔字符的字符串,buf为切割后剩余字符串。字符串切割完后会将原字符串中的分隔字符替换为\0

使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct Date
{
int year;
int month;
int day;
};

void DateStrToInt(string date_S, Date &date_I)
{
string temp;
char* buf;
temp = strtok_s((char*)date_S.data(), "-", &buf);
date_I.year = stoi(temp);
temp = strtok_s(NULL, "-", &buf);
date_I.month = stoi(temp);
date_I.day = stoi(buf);
}

需注意的是strDelimit为字符串的话并不是以整个字符串作为分隔符,而是其中每个字符单独作为分隔符。

针对在线笔试的话直接用函数

1
scanf("%d-%d-%d",&year,&month,&day);

就完事了,不用麻烦的去操作字符串,太浪费时间。

栈的顺序存储

参考《大话数据结构》——程杰

栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。

进栈出栈变化形式

最先进栈的元素不一定最后出栈。

栈对线性表的插入和删除的位置进行了限制,并没有对元素进出的时间进行限制,在不是所有元素都进栈的情况下,事先进栈的元素也可以出栈,只要保证时栈顶元素出栈就可以。

顺序栈代码实现

当栈存又一个元素时,top等于0,因此通常把空栈的判定条件定位top等于-1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#define MAX_SIZE 10
typedef enum Status
{
OK=0,
ERROR
};
typedef int SElemType;
typedef struct SqStack
{
SElemType data[MAX_SIZE];
int top;
};

/*线性栈初始化*/
void SqStackInit(SqStack *s)
{
s->top = -1;
}

/*线性栈是否为空*/
bool IsStackEmpty(SqStack s)
{
if (s.top==-1)
{
return true;
}
else
{
return false;
}
}
/*线性栈入栈*/
Status Push(SqStack *s, SElemType e)
{
if (s->top==MAX_SIZE-1)
{
return ERROR;
}
s->top++;
s->data[s->top] = e;
return OK;
}
/*线性表出栈*/
Status Pop(SqStack *s, SElemType &e)
{
if (s->top==-1)
{
return ERROR;
}
else
{
e = s->data[s->top];
s->top--;
return OK;
}
}
int main()
{
SqStack S;
SElemType e;
SqStackInit(&S);
for (int i=0;i<MAX_SIZE;i++)
{
Push(&S, i);
printf("%d,", i);
}
printf("\n");
for (int i = 0; i < MAX_SIZE; i++)
{
Pop(&S, e);
printf("%d,", e);
}
}

两栈共享空间

让两个栈的栈底分别位于数组的始端与末端。两个栈增加元素,就是两端点向中间延伸。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/*两栈共享空间初始化*/
void SqStackInit(SqDoubleStack *s)
{
s->top1 = -1;
s->top2 = MAX_SIZE;
}
/*两栈共享空间入栈*/
Status Push(SqDoubleStack *s, SElemType e,int stackNumber)
{
if (s->top1+1 == s->top2)
{
return ERROR;
}
if (stackNumber==1)/*判断是哪个栈要压入数据*/
{
s->top1++;
s->data[s->top1] = e;
}
else
{
s->top2--;
s->data[s->top2] = e;
}
return OK;
}
/*两栈共享空间出栈*/
Status Pop(SqDoubleStack *s, SElemType &e, int stackNumber)
{
if (stackNumber==1)
{
if (s->top1 == -1)
{
return ERROR;
}
else
{
e = s->data[s->top1];
s->top1--;
}
}
else if (stackNumber==2)
{
if (s->top2 == MAX_SIZE)
{
return ERROR;
}
else
{
e = s->data[s->top2];
s->top2++;
}
}
return OK;
}

int main()
{
SqDoubleStack S1;
SElemType e;
SqStackInit(&S1);
for (int i=0;i<3;i++)
{
Push(&S1, i,1);
printf("%d,", i);
}
printf("\n");
for (int i = 3; i < 5; i++)
{
Push(&S1, i, 2);
printf("%d,", i);
}
printf("\n");
for (int i = 0; i < MAX_SIZE; i++)
{
if (Pop(&S1, e, 1)==ERROR)
{
break;
}
printf("%d,", e);
}
printf("\n");
for (int i = 0; i < MAX_SIZE; i++)
{
if (Pop(&S1, e, 2) == ERROR)
{
break;
}
printf("%d,", e);
}
}

第一次参加招聘笔试,试试水。我把部分题目记了下来,可供参考。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1、具有C语言功能,又称过滤器的是()
A、tcsh B、sed C、awk D、csh

2、linux中,下列命令用于加载驱动模块的是()
A make modules instull init
B init
C insmod
D load

3、判断正误
swap对vector操作会使迭代器失效。
unordered_map插入元素时是无序的。
vector内存重新分配时,大小时原始容量的1.5倍
关于struct的sizeof问题

4、基于中断实时编程结构的实时性取决于()

5、static 数据成员可以被继承而实现多态?
   const修饰的成员函数只能范围const修饰的成员?
6、需要经过三次握手的指令
7、
class Base1
{
public:
        Base1(int32_t value):value_(value){}
        virtual void PrintValue(){std::cout<<value_;}
private:int32_t value_;
};
class Base2
{
public:
        virtual void PrintHello(){std::cout<<"hello";}
};
class Droid:public Base1,public Base2
{
public:
        Droid(int32_t value):Base1(value){}
        void printValue()override{std::cout<<2;}
        void printHello()override{std:cout<<"world";}
};
int main()
{
    std::cout<<sizeof(Base1)<<" "<<sizeof(Base2)<<" "<<sizeof(Droid);
}
分析代码,写出程序运行结果。

New Start

第一篇个人博客,试试水。也算是从软硬结合转向纯软方向了。我会在博客上写写一些学习中的笔记。要看的东西是真的多,数据结构、tcp/ip、C++、java、linux。。。
先给自己定个小目标,这学期先把C++和数据结构先好好学习学习吧。生活不易,饭难恰啊。
下辈子真想当条小姐姐家的阿柴。