0%

nuttx app可以通过启动脚本init.d来实现开机自动运行的功能。
系统版本基于release12.6,后续的版本这个功能好像改了名字。

首先需要在menuconfig中使能
Auto-mount etc baked-in ROMFS image功能.

其次需要使用./tools/mkromfsimg.sh来生成 etc_romfs.c文件。
nuttx目录下创建两个模板文件rc.sysinit.template和rcS.template。(这俩模板应该是对应启动时不一样的执行位置,具体没仔细看)

在rcS.template中写入需要启动执行的指令。比如我自己写的app:serialTest &

&符号表示后台执行。

使用方式为

1
./tools/mkromfsimg.sh build/

其中bulid为编译目录。这个脚本的使用要想要基于一些编译相关的东西,具体的还要看下其实现

再把生成的etc_romfs.c文件放入对应的BSP中,我这边所使用的bsp目录是nuttx/boards/arm/stm32/stm32f411-minimum/src

编译后下载。即可在系统启动后自动运行serialTest的相关程序。

环境搭建

  • ubuntu 环境下先进行openocd的安装

    1
    sudo apt-get install openocd
  • 安装依赖

    1
    apt-get install libusb-1.0-0-dev libftdi-dev

配置文件

  • 先创建ft2232的配置文件ft2232.cfg (文件名随意)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    adapter driver ftdi
    ftdi_vid_pid 0x0403 0x6010

    adapter speed 1500
    # ftdi_tdo_sample_edge falling
    transport select swd

    ftdi_layout_init 0x0508 0x0f1b
    ftdi_layout_signal SWD_EN -data 0

    ftdi_layout_signal nSRST -data 0x0010

    openocd还有很多设备的配置文件,像stlink、ulink、jlink之类的,配置文件路径默认在
    /usr/share/openocd/scripts/interface

  • 芯片相关配置的配置
    芯片相关配置的路径在/usr/share/openocd/scripts/target
    这里需要烧录的芯片是stm32f411ce,所以需要使用配置文件stm32f4x.cfg
    为方便起见,我把ft2232.cfg和stm32f4x.cfg都放在了~/openocd目录下

烧录脚本

使用bash脚本来实现烧录指令
stm32_flash.sh

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
SOURCE=~/openocd
INTERFACE_CFG=${SOURCE}/ft2232.cfg
TARGET_CFG=${SOURCE}/stm32f4x.cfg

openocd -f ${INTERFACE_CFG} \
-f ${TARGET_CFG} \
-c "init" \
-c "halt" \
-c "flash write_image erase ${1}" \
-c "reset" \
-c "exit"

加好环境变量与执行权限后这样就可以用如下指令直接烧录固件(hex)。

1
./stm32_flash.sh <固件路径>

要烧录其他类型的固件可以修改脚本指令。
比如bin文件的话就加上起始地址,改为flash write_image erase ${1} 0x08000000

alias st_d=”~/tool/stm32_flash.sh”
alias st_c=”cmake –build build -t clean”
alias st_b=”cmake –build build”
alias st_m=”cmake –build build -t menuconfig”

需要使用./tools/mkromfsimg.sh来生成etc_romfs.c文件z

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);

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