依成名的博客

---学无止境,折腾不止


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

Pixhawk之10月6号后固件编译由make编译系统转到cmake编译系统后

发表于 2015-10-10   |   分类于 无人机   |     |   阅读次数

详见博客 : http://blog.csdn.net/freeape/article/details/49024053

前言

  官方开源的Pixhawk固件自2015年10月6号起,删除了makefile文件夹,按照在工具链中的控制台命令行编译,再按照原来的编译步骤去编译时就会出现问题了,因为固件已经由make编译系统转到了cmake编译系统了。

make编译

  使用make编译系统的编译步骤:

  1. 安装”px4_toolchain_installer_v14_win.exe”
  2. 运行 “PX4 Software Download”
  3. git submodule init
  4. git submodule update
  5. make distclean
  6. make archives
  7. make px4fmu-v2_default

这是2015-10-10的版本,发现已经没有了makefile文件夹:

2015-10-10

获取make编译系统的Firmware版本

方式一

Tag

  这些版本都是用make编译系统。想要下载哪个就点击进去:

V1.01

  需要注意的是,下载当前版本要下载.zip(如果下载下来后,编译过程中出现了fatal: Not a git repository (or any of the parent directories)问题,请见这篇文章:http://blog.csdn.net/freeape/article/details/47858527):
zip

方式二(推荐)

  通过git版本控制来切换分支得到想要的版本。
  在git中,tag就是一个只读的branch,一般为每一个可发布的里程碑版本打一个tag。如在方式一中的tag,v1.0.1、v1.0.0rc12、v1.0.0rc11等。比如想要得到v1.0.1这个分支的代码,可以这样做:

  • 先将master分支clone到本地
    • git clone https://github.com/PX4/Firmware.git
  • 在本地的git bash中切换分支:git checkout tag_name
    • git checkout v1.0.1
  • 然后再更新本地仓库,再编译
    • git submodule init
    • git submodule update
  • 在工具链中的console中编译
    • make distclean
    • make archives
    • make px4fmu-v2_default

cmake编译

  使用cmake编译系统的编译步骤:(首先是要将最新的固件clone到本地的)

  1. git submodule init
  2. git submodule update --recursive
    1. 如果这一步失败了,则make distclean或者git clean -dfx(注意:这将会删除没有在Git版本控制下的所有文件)
    2. 重新来遍
  3. make px4fmu-v2_default

Pixhawk之超声波模块添加说明(I2C方式)

发表于 2015-08-24   |   分类于 无人机   |     |   阅读次数

详见博客 :http://blog.csdn.net/freeape/article/details/47949487

说明

  在Pixhawk的固件中,已经实现了串口和i2c的底层驱动,并不需要自己去写驱动。通过串口的方式添加超声波的缺点是串口不够,不能添加多个超声波模块,此时需要用到i2c的方式去添加了。在Pixhawk固件中,i2c的方式去添加超声波模块传感器已经实现了一个mb12xx超声波产品的驱动,可以直接使用,模块位置为:../src/drivers/mb12xx。当然还可以通过PWM的方式去添加超声波模块(模块支持此功能);

模块连接说明

超声波mb12xx    

sonar
sonar-pixhawk

激光雷达(支持通过PWM方式)

PWM方式
I2C方式

相关资料

http://copter.ardupilot.com/wiki/common-optional-hardware/common-rangefinder-landingpage/common-rangefinder-lidarlite/

http://copter.ardupilot.com/wiki/common-optional-hardware/common-rangefinder-landingpage/common-rangefinder-maxbotix-analog/

https://item.taobao.com/item.htm?spm=a230r.1.14.1.q6h2kN&id=40143003917&ns=1&abbucket=6#detail

http://www.maxbotix.com/documents/I2CXL-MaxSonar-EZ_Datasheet.pdf

Pixhawk之通过串口方式添加一个自定义传感器(超声波为例)

发表于 2015-08-21   |   分类于 无人机   |     |   阅读次数

详见博客 : http://blog.csdn.net/freeape/article/details/47837415

Pixhawk—添加一个自定义传感器—超声波(串口方式)

说明

  首先超声波模块是通过串口方式发送(Tx)出数据,使用的模块数据发送周期为100ms,数据格式为:

R0034 R0122 R0122 R0046 R0127 R0044 R0044 R0125 R0034 R0037 R0041 R0122 R0122 .....

则可以通过Pixhawk板上的串口来接收(Rx)数据,即将超声波的Tx接口连接到Pixhawk板上的Rx接口。
  Pixhawk板上串口说明:
  这里写图片描述
  测试使用Pixhawk板上TELEM2接口的USART2,对应的Nuttx UART设备文件尾/dev/ttyS2:
  这里写图片描述

读取数据测试

  步骤:

  • 在Firmware/src/modules中添加一个新的文件夹,命名为rw_uart
  • 在rw_uart文件夹中创建module.mk文件,并输入以下内容:
    • MODULE_COMMAND = rw_uart
    • SRCS = rw_uart.c
  • 在rw_uart文件夹中创建rw_uart.c文件
  • 注册新添加的应用到NuttShell中。Firmware/makefiles/nuttx/config_px4fmu-v2_default.mk文件中添加如下内容:
    • MODULES += modules/rw_uart

rw_uart.c

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
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <stdbool.h>
#include <errno.h>
#include <drivers/drv_hrt.h>

__EXPORT int rw_uart_main(int argc, char *argv[]);

static int uart_init(char * uart_name);
static int set_uart_baudrate(const int fd, unsigned int baud);

int set_uart_baudrate(const int fd, unsigned int baud)
{

int speed;

switch (baud) {
case 9600: speed = B9600; break;
case 19200: speed = B19200; break;
case 38400: speed = B38400; break;
case 57600: speed = B57600; break;
case 115200: speed = B115200; break;
default:
warnx("ERR: baudrate: %d\n", baud);
return -EINVAL;
}

struct termios uart_config;

int termios_state;

/* fill the struct for the new configuration */
tcgetattr(fd, &uart_config);
/* clear ONLCR flag (which appends a CR for every LF) */
uart_config.c_oflag &= ~ONLCR;
/* no parity, one stop bit */
uart_config.c_cflag &= ~(CSTOPB | PARENB);
/* set baud rate */
if ((termios_state = cfsetispeed(&uart_config, speed)) < 0) {
warnx("ERR: %d (cfsetispeed)\n", termios_state);
return false;
}

if ((termios_state = cfsetospeed(&uart_config, speed)) < 0) {
warnx("ERR: %d (cfsetospeed)\n", termios_state);
return false;
}

if ((termios_state = tcsetattr(fd, TCSANOW, &uart_config)) < 0) {
warnx("ERR: %d (tcsetattr)\n", termios_state);
return false;
}

return true;
}


int uart_init(char * uart_name)
{

int serial_fd = open(uart_name, O_RDWR | O_NOCTTY);

if (serial_fd < 0) {
err(1, "failed to open port: %s", uart_name);
return false;
}
return serial_fd;
}

int rw_uart_main(int argc, char *argv[])
{

char data = '0';
char buffer[4] = "";
/*
* TELEM1 : /dev/ttyS1
* TELEM2 : /dev/ttyS2
* GPS : /dev/ttyS3
* NSH : /dev/ttyS5
* SERIAL4: /dev/ttyS6
* N/A : /dev/ttyS4
* IO DEBUG (RX only):/dev/ttyS0
*/

int uart_read = uart_init("/dev/ttyS2");
if(false == uart_read)return -1;
if(false == set_uart_baudrate(uart_read,9600)){
printf("[YCM]set_uart_baudrate is failed\n");
return -1;
}
printf("[YCM]uart init is successful\n");

while(true){
read(uart_read,&data,1);
if(data == 'R'){
for(int i = 0;i <4;++i){
read(uart_read,&data,1);
buffer[i] = data;
data = '0';
}
printf("%s\n",buffer);
}
}

return 0;
}
  • 编译并刷固件

    • make clean
    • make upload px4fmu-v2_default
  • 查看app

    • 在NSH终端中输入help,在Builtin Apps中出现rw_uart应用。
  • 运行rw_uart应用(前提是模块与Pixhawk连接好)
    • 在NSH终端中输入rw_uart,回车,查看超声波的打印数据。

发布超声波的数据

  在无人机运行时,首先是要将应用随系统启动时就启动起来的,且将获得的超声波数据不断的发布出去,从而让其他应用得以订阅使用。这里也使用Pixhawk里面的通用模式,即主线程,检测app命令输入,创建一个线程来不断的发布数据。

定义主题和发布主题

  • 在modules/rw_uart文件夹下创建一个文件:rw_uart_sonar_topic.h

rw_uart_sonar_topic.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef __RW_UART_SONAR_H_
#define __RW_UART_SONAR_H_

#include <stdint.h>
#include <uORB/uORB.h>

/*声明主题,主题名自定义*/
ORB_DECLARE(rw_uart_sonar);

/* 定义要发布的数据结构体 */
struct rw_uart_sonar_data_s{
char datastr[5]; //原始数据
int data; //解析数据,单位:mm
};

#endif

rw_uart.c

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <drivers/drv_hrt.h>

#include "rw_uart_sonar_topic.h"

/* 定义主题 */
ORB_DEFINE(rw_uart_sonar, struct rw_uart_sonar_data_s);

static bool thread_should_exit = false;
static bool thread_running = false;
static int daemon_task;


__EXPORT int rw_uart_main(int argc, char *argv[]);
int rw_uart_thread_main(int argc, char *argv[]);

static int uart_init(const char * uart_name);
static int set_uart_baudrate(const int fd, unsigned int baud);
static void usage(const char *reason);



int set_uart_baudrate(const int fd, unsigned int baud)
{

int speed;

switch (baud) {
case 9600: speed = B9600; break;
case 19200: speed = B19200; break;
case 38400: speed = B38400; break;
case 57600: speed = B57600; break;
case 115200: speed = B115200; break;
default:
warnx("ERR: baudrate: %d\n", baud);
return -EINVAL;
}

struct termios uart_config;

int termios_state;

/* fill the struct for the new configuration */
tcgetattr(fd, &uart_config);
/* clear ONLCR flag (which appends a CR for every LF) */
uart_config.c_oflag &= ~ONLCR;
/* no parity, one stop bit */
uart_config.c_cflag &= ~(CSTOPB | PARENB);
/* set baud rate */
if ((termios_state = cfsetispeed(&uart_config, speed)) < 0) {
warnx("ERR: %d (cfsetispeed)\n", termios_state);
return false;
}

if ((termios_state = cfsetospeed(&uart_config, speed)) < 0) {
warnx("ERR: %d (cfsetospeed)\n", termios_state);
return false;
}

if ((termios_state = tcsetattr(fd, TCSANOW, &uart_config)) < 0) {
warnx("ERR: %d (tcsetattr)\n", termios_state);
return false;
}

return true;
}


int uart_init(const char * uart_name)
{

int serial_fd = open(uart_name, O_RDWR | O_NOCTTY);

if (serial_fd < 0) {
err(1, "failed to open port: %s", uart_name);
return false;
}
return serial_fd;
}

static void usage(const char *reason)
{

if (reason) {
fprintf(stderr, "%s\n", reason);
}

fprintf(stderr, "usage: position_estimator_inav {start|stop|status} [param]\n\n");
exit(1);
}

int rw_uart_main(int argc, char *argv[])
{

if (argc < 2) {
usage("[YCM]missing command");
}

if (!strcmp(argv[1], "start")) {
if (thread_running) {
warnx("[YCM]already running\n");
exit(0);
}

thread_should_exit = false;
daemon_task = px4_task_spawn_cmd("rw_uart",
SCHED_DEFAULT,
SCHED_PRIORITY_MAX - 5,
2000,
rw_uart_thread_main,
(argv) ? (char * const *)&argv[2] : (char * const *)NULL);
exit(0);
}

if (!strcmp(argv[1], "stop")) {
thread_should_exit = true;
exit(0);
}

if (!strcmp(argv[1], "status")) {
if (thread_running) {
warnx("[YCM]running");

} else {
warnx("[YCM]stopped");
}

exit(0);
}

usage("unrecognized command");
exit(1);
}

int rw_uart_thread_main(int argc, char *argv[])
{


if (argc < 2) {
errx(1, "[YCM]need a serial port name as argument");
usage("eg:");
}

const char *uart_name = argv[1];

warnx("[YCM]opening port %s", uart_name);
char data = '0';
char buffer[5] = "";
/*
* TELEM1 : /dev/ttyS1
* TELEM2 : /dev/ttyS2
* GPS : /dev/ttyS3
* NSH : /dev/ttyS5
* SERIAL4: /dev/ttyS6
* N/A : /dev/ttyS4
* IO DEBUG (RX only):/dev/ttyS0
*/

int uart_read = uart_init(uart_name);
if(false == uart_read)return -1;
if(false == set_uart_baudrate(uart_read,9600)){
printf("[YCM]set_uart_baudrate is failed\n");
return -1;
}
printf("[YCM]uart init is successful\n");

thread_running = true;

/*初始化数据结构体 */
struct rw_uart_sonar_data_s sonardata;
memset(&sonardata, 0, sizeof(sonardata));
/* 公告主题 */
orb_advert_t rw_uart_sonar_pub = orb_advertise(ORB_ID(rw_uart_sonar), &sonardata);


while(!thread_should_exit){
read(uart_read,&data,1);
if(data == 'R'){
for(int i = 0;i <4;++i){
read(uart_read,&data,1);
buffer[i] = data;
data = '0';
}
strncpy(sonardata.datastr,buffer,4);
sonardata.data = atoi(sonardata.datastr);
//printf("[YCM]sonar.data=%d\n",sonardata.data);
orb_publish(ORB_ID(rw_uart_sonar), rw_uart_sonar_pub, &sonardata);
}
}

warnx("[YCM]exiting");
thread_running = false;
close(uart_read);

fflush(stdout);
return 0;
}

测试发布的主题—订阅主题

  测试可以随便一个启动的app中进行主题订阅,然后将订阅的数据打印出来,看是否是超声波的数据。这里测试是在固件的src/examples文件夹中的px4_simple_app应用进行测试的。

  • 将px4_simple_app应用添加到NuttShell中。Firmware/makefiles/nuttx/config_px4fmu-v2_default.mk文件中添加如下内容:
    • MODULES += examples/px4_simple_app
  • 在px4_simple_app.c中代码内容:
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
#include <px4_config.h>
#include <unistd.h>
#include <stdio.h>
#include <poll.h>
#include <string.h>

#include <uORB/uORB.h>
#include "rw_uart/rw_uart_sonar_topic.h"

__EXPORT int px4_simple_app_main(int argc, char *argv[]);

int px4_simple_app_main(int argc, char *argv[])
{

printf("Hello Sky!\n");

/* subscribe to rw_uart_sonar topic */
int sonar_sub_fd = orb_subscribe(ORB_ID(rw_uart_sonar));
/*设置以一秒钟接收一次,并打印出数据*/
orb_set_interval(sonar_sub_fd, 1000);
bool updated;
struct rw_uart_sonar_data_s sonar;

/*接收数据方式一:start*/
/*
while(true){
orb_check(sonar_sub_fd, &updated);

if (updated) {
orb_copy(ORB_ID(rw_uart_sonar), sonar_sub_fd, &sonar);
printf("[YCM]sonar.data=%d\n",sonar.data);
}
//else printf("[YCM]No soanar data update\n");
}
*/

/*接收数据方式一:end*/

/*接收数据方式二:start*/
/* one could wait for multiple topics with this technique, just using one here */
struct pollfd fds[] = {
{ .fd = sonar_sub_fd, .events = POLLIN },
/* there could be more file descriptors here, in the form like:
* { .fd = other_sub_fd, .events = POLLIN },
*/

};

int error_counter = 0;

for (int i = 0; i < 5; i++) {s
/* wait for sensor update of 1 file descriptor for 1000 ms (1 second) */
int poll_ret = poll(fds, 1, 1000);

/* handle the poll result */
if (poll_ret == 0) {
/* this means none of our providers is giving us data */
printf("[px4_simple_app] Got no data within a second\n");

} else if (poll_ret < 0) {
/* this is seriously bad - should be an emergency */
if (error_counter < 10 || error_counter % 50 == 0) {
/* use a counter to prevent flooding (and slowing us down) */
printf("[px4_simple_app] ERROR return value from poll(): %d\n"
, poll_ret);
}

error_counter++;

} else {

if (fds[0].revents & POLLIN) {
/* obtained data for the first file descriptor */
struct rw_uart_sonar_data_s sonar;
/* copy sensors raw data into local buffer */
orb_copy(ORB_ID(rw_uart_sonar), sonar_sub_fd, &sonar);
printf("[px4_simple_app] Sonar data:\t%s\t%d\n",
sonar.datastr,
sonar.data);
}

/* there could be more file descriptors here, in the form like:
* if (fds[1..n].revents & POLLIN) {}
*/

}
}
/*接收数据方式二:end*/

return 0;
}
  • 编译并刷固件

    • make upload px4fmu-v2_default
  • 在NSH中测试(已加入自启动脚本中)

    • rw_uart start /dev/ttyS2
    • px4_simple_app

  这里写图片描述

加入系统启动脚本

  可以加入到光流的自定义启动脚本中:/fs/microsd/etc/extras.txt。这样随着系统的自启动,rw_uart就会默认启动了。

1
2
# start sonar
rw_uart start /dev/ttyS2

Pixhawk之烧写FMU/IO bootloader

发表于 2015-08-20   |   分类于 无人机   |     |   阅读次数

详见博客 : http://blog.csdn.net/freeape/article/details/47808253

Pixhawk—FMU/IO烧写Bootloader

说明

  用J-link来烧写Bootloader,Pixhawk板FMU/IO接口说明:
  这里写图片描述
  J-link接口说明:
  这里写图片描述
  Pixhawk与J-link接线说明:
  3V3 --- VCC
  SWDIO --- SWDIO
  SWCLK --- SWCLK
  GND --- GND

安装烧写软件

  软件下载地址:www.segger.com
  安装好软件后,打开J-Flash Vx.xxi:
这里写图片描述

配置

  Options--->Project Setting...
  选择CPU型号:
这里写图片描述
  选择连接接口方式(SWD):
这里写图片描述
  点击确定。

导入下载文件下载

  首先将Pixhawk和J-link连线连接好;
  Target--->Connect连接目标板,出现连接成功提示;
  File--->Open data file选择将下载的bootloader,FMU就选择烧写FMU的bootloader的bin文件,IO就选择烧写IO的bootloader的bin文件;
  烧写前可擦除原地址内容:Target--->Erase chip;
  烧写:Target--->Auto;

Pixhawk之sdlog2应用详解

发表于 2015-08-01   |   分类于 无人机   |     |   阅读次数

详见博客 : http://blog.csdn.net/freeape/article/details/47188287

sdlog2 app

  该应用的用途是记录飞控飞行日志到SD卡中,日志文件格式与APM二进制文件兼容,但是sdlog2使用强制性的消息时间写时间戳。

使用方式

  每次sdlog2应用开始记录日志的时候,它会在SD卡日志文件夹log中创建一个新的文件夹。如果-t参数选项被设置了,而且GPS时间戳是可获得的,文件夹的名称就是基于当前日期,如log/2015-07-24,否则的话就是被命名为sessXXX(XXX代表一个序列号)。文件夹中的文件命名也大同小异,如果-t选项已设置,文件名为log/2015-07-24/11_43_52.bin,否则的话就被命名为log.XXX.bin(XXX也为序列号)。

  无论sdlog2应用是否已经启动了、系统已经被配置好了,还是mavlink命令,日志的记录都取决于sdlog2应用所设置的选项。选项说明如下:

usage: sdlog2 {start|stop|status|on|off} [-r <log rate>] [-b <buffer size>] -e -a -t -x
    -r      Log rate in Hz, 0 means unlimited rate
    -b      Log buffer size in KiB, default is 8
    -e      Enable logging by default (if not, can be started by command)
    -a      Log only when armed (can be still overriden by command)
    -t      Use date/time for naming log directories and files
    -x      Extended logging

这里写图片描述日志记录的性能取决于所使用的SD的读写速度,使用较好的SD卡可以有效的避免跳过写入/读取数据。当sdlog2应用在全速率的时候(即没有-r选项),可能会导致CPU负荷,但对飞行没有负面影响。但是如果没有满足全速率要求,则可能会记录会跳过信息。

日志记录开启

  在正常情况下,随着飞控的配置好,日志记录就启动了,且只有激活飞行的数据才是所需要的数据。要启动手动日志记录,在控制台上执行下面的命令:

sdlog2 stop
sdlog2 start -t -r 200 -e -b 16

  停止记录:

sdlog2 stop

分析日志

FlightPlot工具(—推荐使用)

Fields List

这里写图片描述

FlightPlot Analysis

这里写图片描述
  查看和分析日志,可以使用FlightPlotGUI工具(提供了可执行的jar包),下载下来并安装好Java后双击就可以执行了。无需转换日志文件,就可以读取由sdlog2应用产生的二进制日志文件,并通过图形方式显示。具体的用法见链接。

  FlightPlot支持的日志文件:

  • PX4 autopilot log(.bin generated by sdlog2)
  • APM binary logs (.bin files stored on SD card) (added in v.0.2.6)
  • 支持mavlink日志

Pymavlink工具

  还可以使用包含在Pymavlink软件中的mavgraph工具来绘制日志图像。Pymavlink提供了许多命令行和可视化的工具用于分析数据和制图。它支持mavlink日志(APM或PX4的均可)。

Matlab(转换logs成CVS)工具

  读取二进制文件,并将其转换成CVS文件,可以使用Python或者Matlab脚本文件来实现。分析数据是通过选用不同的参数选项来查看的,具体的用法就不介绍了(都是命令式的,有点麻烦)。

logconv.m:自动转换日志文件,并图形显示飞行的数据。
sdlog2_dump.py:Python脚本,将二进制形式的日志文件转换成CVS格式的文件。

可能遇到的故障

  为避免在IO带重负载的关键应用中,在侦听消息和写日志到SD卡上有一个缓冲区。如果在某些时候这个缓冲区溢出了,那么一些日志记录就会被跳过了。跳过的记录数据能够通过控制台使用sdlog2应用检测出来。这个也会在关闭日志文件的时候打印出来,不过需加-a选项。由上可知,如果跳过的记录不为零,那么就需要增加这个缓冲区的大小了,下面的命令就是将默认的8KB缓冲区改为16KB:

sdlog2 start -t -r 100 -e -b 16

负载测试

  测试SD的传输速率,启动sdlog2应用应配置成记录日志速率为200Hz,32KB的缓冲区,并加上-e选项:

首先停止应用:
sdlog2 stop
sdlog2 start -t -r 200 -e -b 32
运行perf命令查看诱导sdlog2负荷:
(NOTE: 性能计数器只在记录时存在)
perf
或者运行运行top命令:
top
停止应用,清理文件系统:
sdlog2 stop

Pixhawk之基于NSH的Firmware开发与调试

发表于 2015-08-01   |   分类于 无人机   |     |   阅读次数

详见博客 : http://blog.csdn.net/freeape/article/details/47184263

1 相关知识了解

1.1 Nuttx系统

  嵌入式实时操作系统(RTOS)。强调标准兼容和小型封装,具有从8位到32位微控制器环境的高度可扩展性。NuttX 主要遵循 Posix 和 ANSI 标准,对于在这些标准下不支持的功能,或者不适用于深度嵌入环境的功能(如 fork()),采用来自 Unix 和常见 RTOS (如 VxWorks)的额外的标准 API。
  支持文件系统、设备驱动、网络、USB支持、Flash支持、图形支持等。

1.2 NSH

  NuttShell和Unix终端命令类似。NSH通过串口或者USB转串口来与PX4FMU交互,因此可以使用类似超级终端的串口软件来与FMU交互,在Pixhawk开发中,建立好了开发环境后(安装了工具链等),PX4 Toolchain 已经附带了一个串口工具:TeraTerm。如果版本低了,可以单独去下载这个软件并安装。
  在PX4FMU中,NSH默认是通过USB转串口和串口5(波特率为57600)来交互。可以更改默认设置。

  将Pixhawk通过USB转串口连接上电脑上后,再打开TeraTerm软件。通过输入『?』或『help』指令可以查看当前NSH支持的指令已经编译好的应用。其他的一些指令跟Unix终端中用法差不多,不过一些指令的参数都没有了。

这里写图片描述

  通过串口5的交互能打印PX4FMU启动时的一些信息。这个可以用来查看系统启动方面的信息。当然启动完后,还可以输入一些指令来与PX4FMU来交互。

这里写图片描述
这里写图片描述
这里写图片描述

2 编译固件和刷固件

  刷固件的方式很多,可以通过地面站软件QGC或者MP都可以刷固件,可以刷稳定版的固件或者自己编译出来的自定义固件。这里通过PX4 Console来编译固件和刷固件。打开控制台,进入到固件目录:

cd /d/px4/firmware

  删除所有编译的文件,包括编译的操作系统,即删除Archives文件夹和Build文件夹里面的内容一般不使用:

make distclean

  编译操作系统:

make archives

  一般只有当Nuttx配置改变了或者submodule(在GIT中链接外部库,比如MAVLINK或者Nuttx OS)发生了改变才会去编译操作系统,平时编译一次就好了,而且这个编译一次是需要很长的时间的,一个小时左右吧。尽管并行编译会加快速度,但是配置不好,并行编译很容易在系统运行的过程中出问题。如果你使用苹果系统或者Linux系统,这个时间也会大大降低。
  删除编译的固件相关文件,即删除Build文件夹里面的内容:

make clean

  编译固件,以px4fmu-v2_default版本为例:

make px4fmu-v2_defaul

  编译并刷固件,编译完后紧接着刷固件:

make upload px4fmu-v2_default

  在控制台上用到上面的指令就差不多了。

3 调试方式

3.1 测试小功能程序

  因为在windows系统上编译一次固件需要3分钟左右吧,对于一般的功能测试程序可以通过在本机安装上VC6.0或VS或者MinGW(gcc,g++,推荐),编写测试程序编译调试。

3.2 固件测试

  通过printf打印相关信息,断点标识等。这个需要编译固件,并刷固件。Pixhawk通过USB转串口线连接电脑,用TeraTerm输入指令来调试。

4 调试实例演示

  前面已经说了通过串口5(波特率57600)可以查看系统启动打印的信息。通过USB转串口在NSH终端(TeraTerm)输入指令控制Nuttx中的应用或者一些命令。
  可以先阅读sdlog2日志记录自动清理功能分析与实现,了解sdlog2应用是怎么运行的。sdlog2应用随着系统启动时默认启动了的,当我们修改了固件中sdlog2应用的代码时,比如简单的,我们知道sdlog2_main这个函数是在后台运行的,它是检测sdlog2应用的参数输入,比如stop,start,on,off。我们也可以自己再加一条参数检测到这个函数中:

1
2
3
4
5
/*添加测试代码start...*/
if(!strcmp(argv[1],"hello")){
printf("[YCM]hello world!\n");
}
/*添加测试代码end...*/
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
int sdlog2_main(int argc, char *argv[])
{

if (argc < 2) {
sdlog2_usage("missing command");
return 1;
}

if (!strcmp(argv[1], "start")) {

if (thread_running) {
warnx("already running");
/* this is not an error */
return 0;
}

main_thread_should_exit = false;
deamon_task = px4_task_spawn_cmd("sdlog2",
SCHED_DEFAULT,
SCHED_PRIORITY_DEFAULT - 30,
3000,
sdlog2_thread_main,
(char * const *)argv);
return 0;
}

if (!strcmp(argv[1], "stop")) {
if (!thread_running) {
warnx("not started");
}

main_thread_should_exit = true;
return 0;
}

/*添加测试代码start...*/
if(!strcmp(argv[1],"hello")){
printf("[YCM]hello world!\n");
}
/*添加测试代码end...*/

if (!thread_running) {
warnx("not started\n");
return 1;
}

if (!strcmp(argv[1], "status")) {
sdlog2_status();
return 0;
}

if (!strcmp(argv[1], "on")) {
struct vehicle_command_s cmd;
cmd.command = VEHICLE_CMD_PREFLIGHT_STORAGE;
cmd.param1 = -1;
cmd.param2 = -1;
cmd.param3 = 1;
orb_advertise(ORB_ID(vehicle_command), &cmd);
return 0;
}

if (!strcmp(argv[1], "off")) {
struct vehicle_command_s cmd;
cmd.command = VEHICLE_CMD_PREFLIGHT_STORAGE;
cmd.param1 = -1;
cmd.param2 = -1;
cmd.param3 = 2;
orb_advertise(ORB_ID(vehicle_command), &cmd);
return 0;
}

sdlog2_usage("unrecognized command");
return 1;
}

  编译固件并刷固件,如果你的代码有问题,那么会报错,终止编译,会有错误提示:

make upload px4fmu-v2_default

这里写图片描述

  从编译过程中打印信息,我们也可以知道这个submodule中什么改变了,就需要重新编译操作系统了。编译完,刷好了固件:

这里写图片描述
  再次连接TeraTerm,输入指令:

sdlog2 hello

这里写图片描述

  输出hello world!了。

undefined reference to strptime之自定义strptime函数

发表于 2015-07-29   |   分类于 无人机   |     |   阅读次数

详见博客 : http://blog.csdn.net/freeape/article/details/47132843

简介

  strptime()函数能够按照特定时间格式将字符串转换为时间类型。简单点说可以将字符串时间转化为时间戳。这个函数包含在time.h头文件中,在Unix或者类Unix系统中,我们会经常接触到。但是到了跑Nuttx系统的Pixhawk,真是醉了,很多东西都没有,或者少了很多东西,比如time.h中就没有这个函数的实现,又如dirent.h中的一些文件类型的宏定义也没有了。但是我们很需要,比如在时间的比较上,我们不能去拿字符串去操作来比较,会搞死人的,直接得到时间戳,三下两除二就搞定了,那就要用到strptime这个函数了。

实现

mystrptime.c
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
/*
* Note:因time.h中没有strptime函数(UNIX中是有的),本文件是对strptime功能的自定义实现;
* We do not implement alternate representations. However, we always
* check whether a given modifier is allowed for a certain conversion.
*/

#include "mystrptime.h"


static const char *day[7] = {
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday"
};
static const char *abday[7] = {
"Sun","Mon","Tue","Wed","Thu","Fri","Sat"
};
static const char *mon[12] = {
"January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"
};
static const char *abmon[12] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static const char *am_pm[2] = {
"AM", "PM"
};


static int conv_num(const char **buf, int *dest, int llim, int ulim)
{

int result = 0;

/* The limit also determines the number of valid digits. */
int rulim = ulim;

if (**buf < '0' || **buf > '9')
return (0);

do {
result *= 10;
result += *(*buf)++ - '0';
rulim /= 10;
} while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');

if (result < llim || result > ulim)
return (0);

*dest = result;
return (1);
}

char * mystrptime(const char *buf, const char *fmt, struct tm *tm)
{

char c;
const char *bp;
size_t len = 0;
int alt_format, i, split_year = 0;

bp = buf;

while ((c = *fmt) != '\0') {
/* Clear `alternate' modifier prior to new conversion. */
alt_format = 0;

/* Eat up white-space. */
if (isspace(c)) {
while (isspace(*bp))
bp++;

fmt++;
continue;
}

if ((c = *fmt++) != '%')
goto literal;


again: switch (c = *fmt++) {
case '%': /* "%%" is converted to "%". */
literal:
if (c != *bp++)
return (0);
break;

/*
* "Alternative" modifiers. Just set the appropriate flag
* and start over again.
*/

case 'E': /* "%E?" alternative conversion modifier. */
LEGAL_ALT(0);
alt_format |= ALT_E;
goto again;

case 'O': /* "%O?" alternative conversion modifier. */
LEGAL_ALT(0);
alt_format |= ALT_O;
goto again;

/*
* "Complex" conversion rules, implemented through recursion.
*/

case 'c': /* Date and time, using the locale's format. */
LEGAL_ALT(ALT_E);
if (!(bp = mystrptime(bp, "%x %X", tm)))
return (0);
break;

case 'D': /* The date as "%m/%d/%y". */
LEGAL_ALT(0);
if (!(bp = mystrptime(bp, "%m/%d/%y", tm)))
return (0);
break;

case 'R': /* The time as "%H:%M". */
LEGAL_ALT(0);
if (!(bp = mystrptime(bp, "%H:%M", tm)))
return (0);
break;

case 'r': /* The time in 12-hour clock representation. */
LEGAL_ALT(0);
if (!(bp = mystrptime(bp, "%I:%M:%S %p", tm)))
return (0);
break;

case 'T': /* The time as "%H:%M:%S". */
LEGAL_ALT(0);
if (!(bp = mystrptime(bp, "%H:%M:%S", tm)))
return (0);
break;

case 'X': /* The time, using the locale's format. */
LEGAL_ALT(ALT_E);
if (!(bp = mystrptime(bp, "%H:%M:%S", tm)))
return (0);
break;

case 'x': /* The date, using the locale's format. */
LEGAL_ALT(ALT_E);
if (!(bp = mystrptime(bp, "%m/%d/%y", tm)))
return (0);
break;

/*
* "Elementary" conversion rules.
*/

case 'A': /* The day of week, using the locale's form. */
case 'a':
LEGAL_ALT(0);
for (i = 0; i < 7; i++) {
/* Full name. */
len = strlen(day[i]);
if (strncasecmp(day[i], bp, len) == 0)
break;

/* Abbreviated name. */
len = strlen(abday[i]);
if (strncasecmp(abday[i], bp, len) == 0)
break;
}

/* Nothing matched. */
if (i == 7)
return (0);

tm->tm_wday = i;
bp += len;
break;

case 'B': /* The month, using the locale's form. */
case 'b':
case 'h':
LEGAL_ALT(0);
for (i = 0; i < 12; i++) {
/* Full name. */
len = strlen(mon[i]);
if (strncasecmp(mon[i], bp, len) == 0)
break;

/* Abbreviated name. */
len = strlen(abmon[i]);
if (strncasecmp(abmon[i], bp, len) == 0)
break;
}

/* Nothing matched. */
if (i == 12)
return (0);

tm->tm_mon = i;
bp += len;
break;

case 'C': /* The century number. */
LEGAL_ALT(ALT_E);
if (!(conv_num(&bp, &i, 0, 99)))
return (0);

if (split_year) {
tm->tm_year = (tm->tm_year % 100) + (i * 100);
} else {
tm->tm_year = i * 100;
split_year = 1;
}
break;

case 'd': /* The day of month. */
case 'e':
LEGAL_ALT(ALT_O);
if (!(conv_num(&bp, &tm->tm_mday, 1, 31)))
return (0);
break;

case 'k': /* The hour (24-hour clock representation). */
LEGAL_ALT(0);
/* FALLTHROUGH */
case 'H':
LEGAL_ALT(ALT_O);
if (!(conv_num(&bp, &tm->tm_hour, 0, 23)))
return (0);
break;

case 'l': /* The hour (12-hour clock representation). */
LEGAL_ALT(0);
/* FALLTHROUGH */
case 'I':
LEGAL_ALT(ALT_O);
if (!(conv_num(&bp, &tm->tm_hour, 1, 12)))
return (0);
if (tm->tm_hour == 12)
tm->tm_hour = 0;
break;

case 'j': /* The day of year. */
LEGAL_ALT(0);
if (!(conv_num(&bp, &i, 1, 366)))
return (0);
tm->tm_yday = i - 1;
break;

case 'M': /* The minute. */
LEGAL_ALT(ALT_O);
if (!(conv_num(&bp, &tm->tm_min, 0, 59)))
return (0);
break;

case 'm': /* The month. */
LEGAL_ALT(ALT_O);
if (!(conv_num(&bp, &i, 1, 12)))
return (0);
tm->tm_mon = i - 1;
break;

case 'p': /* The locale's equivalent of AM/PM. */
LEGAL_ALT(0);
/* AM? */
if (strcasecmp(am_pm[0], bp) == 0) {
if (tm->tm_hour > 11)
return (0);

bp += strlen(am_pm[0]);
break;
}
/* PM? */
else if (strcasecmp(am_pm[1], bp) == 0) {
if (tm->tm_hour > 11)
return (0);

tm->tm_hour += 12;
bp += strlen(am_pm[1]);
break;
}

/* Nothing matched. */
return (0);

case 'S': /* The seconds. */
LEGAL_ALT(ALT_O);
if (!(conv_num(&bp, &tm->tm_sec, 0, 61)))
return (0);
break;

case 'U': /* The week of year, beginning on sunday. */
case 'W': /* The week of year, beginning on monday. */
LEGAL_ALT(ALT_O);
/*
* XXX This is bogus, as we can not assume any valid
* information present in the tm structure at this
* point to calculate a real value, so just check the
* range for now.
*/

if (!(conv_num(&bp, &i, 0, 53)))
return (0);
break;

case 'w': /* The day of week, beginning on sunday. */
LEGAL_ALT(ALT_O);
if (!(conv_num(&bp, &tm->tm_wday, 0, 6)))
return (0);
break;

case 'Y': /* The year. */
LEGAL_ALT(ALT_E);
if (!(conv_num(&bp, &i, 0, 9999)))
return (0);

tm->tm_year = i - TM_YEAR_BASE;
break;

case 'y': /* The year within 100 years of the epoch. */
LEGAL_ALT(ALT_E | ALT_O);
if (!(conv_num(&bp, &i, 0, 99)))
return (0);

if (split_year) {
tm->tm_year = ((tm->tm_year / 100) * 100) + i;
break;
}
split_year = 1;
if (i <= 68)
tm->tm_year = i + 2000 - TM_YEAR_BASE;
else
tm->tm_year = i + 1900 - TM_YEAR_BASE;
break;

/*
* Miscellaneous conversions.
*/

case 'n': /* Any kind of white-space. */
case 't':
LEGAL_ALT(0);
while (isspace(*bp))
bp++;
break;


default: /* Unknown/unsupported conversion. */
return (0);
}


}

/* LINTED functional specification */
return ((char *)bp);
}


time_t to_seconds(const char *date,int mode)
{
struct tm storage={0,0,0,0,0,0,0,0,0};
char *p=NULL;
time_t retval=0;

if(1 == mode)p=(char *)mystrptime(date,"%Y-%m-%d",&storage);
else if(0 == mode)p=(char *)mystrptime(date,"%Y_%m_%d",&storage);
if(p==NULL)
{
retval=0;
}
else
{
retval=mktime(&storage);
}
return retval;
}
mystrptime.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef MYSTRPTIME_H_
#define MYSTRPTIME_H_

#include <stdio.h>
#include <time.h>
#include <ctype.h>
#include <string.h>

#define ALT_E 0x01
#define ALT_O 0x02
#define LEGAL_ALT(x) { if (alt_format & ~(x)) return (0); }
#define TM_YEAR_BASE 1900

char *mystrptime(const char *buf, const char *fmt, struct tm *tm);
time_t to_seconds(const char *date,int mode);

#endif

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*Test*/
/*
#intclude <stdio.h>
#include "mystrptime.h"

int main ()
{
time_t ts;
ts = to_seconds("2015-07-29",1);
//ts = to_seconds("2015_07_29",0);
printf("ts=%d\n",ts);
return(0);
}
*/

STM8S之电源功耗管理之停机模式(halt)实现

发表于 2015-07-22   |   分类于 硬件   |     |   阅读次数

详见博客:http://blog.csdn.net/freeape/article/details/47008805

官方资料

  可以去网络搜索中文版,或者到官方网站上去下载英文版。
  英文:

英文

  译文:

译文

主要内容简介

  • 影响功耗的主要因素
  • 电源系统
  • 时钟管理
  • 运行模式和低功耗模式
    • 运行模式
    • 等待模式
    • 活跃停机模式
    • 停机模式
  • 功耗与唤醒事件的测量与结果
  • 功耗管理要点

要点摘要

四种模式

  停机模式(Halt):此模式下单片机的功耗最低,振荡器,CPU和外设的时钟都被关闭,主电压调压器断电。可用复位或外部中断唤醒,唤醒后之前运行的寄存器数据等都保持不变,且从HALT处继续执行程序。

停机模式下的功耗测量结果(MVR关LPVR开):

停机模式下的功耗测量结果

运行模式下的功耗测量结果(从RAM运行,不是从Flash开始):

运行模式下的功耗测量结果

停机模式下的唤醒时间测量结果:

停机模式下的唤醒时间测量结果

测试程序

main.c
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/*
Function: 电源管理:停机(Halt)模式测试,没有进入停机模式前,四个LED
灯是每隔1秒钟亮灭一次的,超过10秒后,自动进入停机mode,
然后可以通过外部中断来唤醒停机,此时程序从停机位置
处继续往下运行。
Date : 2015年7月21日
Note : STVD + COSMIC
Author : yicm
Version : 0.0.9
*/

#include<stm8s003f3p.h>


/*Output Pin*/
_Bool PA3 @PA_ODR:3;
_Bool PC4 @PC_ODR:4;
_Bool PC5 @PC_ODR:5;
_Bool PC6 @PC_ODR:6;
_Bool PC7 @PC_ODR:7;
/*Input Pin*/
_Bool PC3 @PC_IDR:3;

/*电量指示灯*/
#define LED1 PA3
#define LED2 PC5
#define LED3 PC6
#define LED4 PC7
/*按键指示灯*/
#define LED5 PC4
#define KEY PC3

/*主时钟频率为8Mhz*/
void Init_CLK(void)
{

CLK_ICKR |= 0X01;
CLK_CKDIVR = 0x08;
while(!(CLK_ICKR&0x02));
CLK_SWR=0xE1;
}

void Init_GPIO(void)
{

/*LED 配置为推挽输出*/
PA_DDR |= 0X08; //PA3
PA_CR1 |= 0X08;
PA_CR2 &= 0XF7;
/*PC4 -KEY LED*/
PC_DDR |= 0X10;
PC_CR1 |= 0X10;
PC_CR2 &= 0XEF;

PC_DDR |= 0XE0; //PC5/6/7
PC_CR1 |= 0XE0;
PC_CR2 &= 0X1F;

LED1 = 1;LED2 = 1;LED3 = 1;LED4 = 1;LED5 = 1;
}

void Init_TIM1(void)
{

TIM1_IER = 0x00;
TIM1_CR1 = 0x00;

TIM1_EGR |= 0x01;
TIM1_PSCRH = 199/256; // 8M系统时钟经预分频f=fck/(PSCR+1) TIM1 为16位分频器
TIM1_PSCRL = 199%256; // PSCR=0x1F3F,f=8M/(0x1F3F+1)=1000Hz,每个计数周期1ms

TIM1_CNTRH = 0x00;
TIM1_CNTRL = 0x00;

TIM1_ARRH = 400/256; // 自动重载寄存器ARR=0x01F4=500
TIM1_ARRL = 400%256; // 每记数500次产生一次中断,即500ms

TIM1_CR1 |= 0x81;
TIM1_IER |= 0x01;
}

/*PC3设置为上拉输入*/
void Init_EXTI2_GPIO(void)
{

PC_DDR &= 0XF7;
PC_CR1 &= 0XF7;
PC_CR2 |= 0X08;
}

/*上升沿和下降沿促发*/
void Init_EXTI2(void)
{

EXTI_CR1 |= 0x30;
}

main()
{
_asm("sim");
Init_CLK();
Init_GPIO();
Init_EXTI2_GPIO();
Init_EXTI2();
Init_TIM1();
_asm("rim");
while (1);
}

/*外部中断唤醒*/
@far @interrupt void EXTI2_Hand_Fun(void)
{


}

/*定时器中断函数*/
@far @interrupt void TIM1_UPD_OVF_TRG_BRK_IRQHandler(void)
{

static unsigned int i = 0;
TIM1_SR1 &=~(0x01);

++i;
if(0 == (i%50))
{
LED1 = ~LED1;
LED2 = ~LED2;
LED3 = ~LED3;
LED4 = ~LED4;
}
if(i > 1000)
{
_asm("halt");
i = 0;
LED5 = ~LED5;
}
}
stm8_interrupt_vector.c
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
/*	BASIC INTERRUPT VECTOR TABLE FOR STM8 devices
* Copyright (c) 2007 STMicroelectronics
*/


typedef void @far (*interrupt_handler_t)(void);

struct interrupt_vector {
unsigned char interrupt_instruction;
interrupt_handler_t interrupt_handler;
};

@far @interrupt void NonHandledInterrupt (void)
{

/* in order to detect unexpected events during development,
it is recommended to set a breakpoint on the following instruction
*/

return;
}

extern void _stext(); /* startup routine */
extern @far @interrupt void EXTI2_Hand_Fun(void);
extern @far @interrupt void TIM1_UPD_OVF_TRG_BRK_IRQHandler(void);

struct interrupt_vector const _vectab[] = {
{0x82, (interrupt_handler_t)_stext}, /* reset */
{0x82, NonHandledInterrupt}, /* trap */
{0x82, NonHandledInterrupt}, /* irq0 */
{0x82, NonHandledInterrupt}, /* irq1 */
{0x82, NonHandledInterrupt}, /* irq2 */
{0x82, NonHandledInterrupt}, /* irq3 */
{0x82, NonHandledInterrupt}, /* irq4 */
{0x82, EXTI2_Hand_Fun}, /* irq5 */
{0x82, NonHandledInterrupt}, /* irq6 */
{0x82, NonHandledInterrupt}, /* irq7 */
{0x82, NonHandledInterrupt}, /* irq8 */
{0x82, NonHandledInterrupt}, /* irq9 */
{0x82, NonHandledInterrupt}, /* irq10 */
{0x82, TIM1_UPD_OVF_TRG_BRK_IRQHandler}, /* irq11 */
{0x82, NonHandledInterrupt}, /* irq12 */
{0x82, NonHandledInterrupt}, /* irq13 */
{0x82, NonHandledInterrupt}, /* irq14 */
{0x82, NonHandledInterrupt}, /* irq15 */
{0x82, NonHandledInterrupt}, /* irq16 */
{0x82, NonHandledInterrupt}, /* irq17 */
{0x82, NonHandledInterrupt}, /* irq18 */
{0x82, NonHandledInterrupt}, /* irq19 */
{0x82, NonHandledInterrupt}, /* irq20 */
{0x82, NonHandledInterrupt}, /* irq21 */
{0x82, NonHandledInterrupt}, /* irq22 */
{0x82, NonHandledInterrupt}, /* irq23 */
{0x82, NonHandledInterrupt}, /* irq24 */
{0x82, NonHandledInterrupt}, /* irq25 */
{0x82, NonHandledInterrupt}, /* irq26 */
{0x82, NonHandledInterrupt}, /* irq27 */
{0x82, NonHandledInterrupt}, /* irq28 */
{0x82, NonHandledInterrupt}, /* irq29 */
};

STM8S之选项字节(Option Byte)写操作之IO复用

发表于 2015-07-22   |   分类于 硬件   |     |   阅读次数

详见博客:http://blog.csdn.net/freeape/article/details/47008033

功能实现目标

  通过对选项字节的写操作来实现TIM2的CH3通道的PWM输出IO复用,可以设置为PA3或者PD2输出。

通过STVP方式操作链接

选项字节

  选项字节包括芯片硬件特性的配置和存储器的保护信息,这些字节保存在存储器中一个专用的块内。除了ROP(读出保护)字节,每个选项字节必须被保存两次,一个是通常的格式(OPTx)和一个用来备份互补格式的(NOPTx)。选项字节可以通过应用程序在IAP模式下修改,但是ROP选项只能在ICP模式(通过SWIM)下被修改。有关SWIM编程过程的内容可以参考STM8S闪存编程手册(PM0051)和STM8 SWIM通信协议和调试模块用户手册(UM0470)。
  不同的芯片的选项字节大小不一样,具体的可以参考芯片数据手册。如用STM8S103F3来举例,选项字节如下:

选项字节

  STM8S103F对于20脚封装的产品的复用功能重定义位:

STM8S103F复用功能重定义位

  由此可知我们要将OPT2字节中的AFR1位进行写操作,通过写0,则端口A3复用功能TIM2_CH3,通过写1,端口D2复用功能TIM2_CH3。接下来通过程序实现这个功能,可以修改AFR1的值来看PWM输出是否切换了管脚,如果能,则是实现了写操作。
  选项字节存放在EEPROM中,所以可以通过读写EEPROM一样的操作方式来修改选项字节。应用程序可以直接向目标地址进行写操作。地址从上面的图中我们已经知道了:0x4803,0x4804。寄存器的配置可以查阅参考手册(RM0016)。
  相关寄存器操作:

FLASH_CR2

FLASH_NCR2

测试程序实现

  注意:实现程序擦写Option Bytes时,不能运行应用程序,否则会出现错误!不过还是觉得这点麻烦啊,还不如用STVP来擦写,要是能够放在应用程序中共存来擦写就好了,用程序实现复用就这么麻烦吗?希望能探索找到好的方法,最后只找到了个不靠谱的,就是在擦写后加上延时,但是这个时管用时不管用。还是再查阅资料看看是怎么回事?

ST Visual Develop

  但是用STVP擦写时又遇到了这个错误:

Error : Error on Option Bytes (complementary bytes). Reprogram Option Bytes of device
Error : < OPTION BYTE verifying failed.

  用STVP来擦写Option Bytes了,先将ROP设置为ON,然后再擦写Option Bytes,会出现两个提示框,选择是(Y),再之后又将ROP设置为OFF,再次擦写Option Bytes,则又可以用STVD通过stlink来烧写程序并仿真了。

STVP program option bytes

T1

T2

测试程序:
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
91
92
93
94
95
96
97
98
99
100
101
/* MAIN.C file
Functons : 操作option byte字节,设置IO复用,来修改TIM2的CH3通道PWM输出管脚PA3 or PD2
Date : 2015年7月22日
Author : yicm
Notes :
*/


#include<stm8s003f3p.h>

void CLK_init(void)
{

CLK_ICKR |= 0X01; //使能内部高速时钟 HSI
CLK_CKDIVR = 0x08; //16M内部RC经2分频后系统时钟为8M
while(!(CLK_ICKR&0x02)); //HSI准备就绪
CLK_SWR=0xe1; //HSI为主时钟源
}

void Init_GPIO(void)
{

/*设置为推挽输出,PD2接了LED灯*/
PD_DDR |= 0X04; //设置PD2端口为输出模式
PD_CR1 |= 0X04; //设置PD2端口为推挽输出模式
PD_CR2 &= 0XFD;

PA_DDR |= 0X08; //设置PA3端口为输出模式
PA_CR1 |= 0X08; //设置PA3端口为推挽输出模式
PA_CR2 |= 0XF7;
}

void Init_Tim2(void)
{

TIM2_CCMR3 |= 0X70; //设置定时器2三通道(PD2)输出比较三模式
TIM2_CCMR3 |= 0X04; //输出比较3预装载使能

TIM2_CCER2 |= 0x03; //通道3使能,低电平有效,配置为输出

// 初始化时钟分频器为1,即计数器的时钟频率为Fmaster=8M/64=0.125MHZ
TIM2_PSCR = 0X07;
//初始化自动装载寄存器,决定PWM 方波的频率,Fpwm=0.125M/62500=2HZ
TIM2_ARRH = 62500/256;
TIM2_ARRL = 62500%256;
//初始化比较寄存器,决定PWM 方波的占空比:5000/10000 = 50%
TIM2_CCR3H = 31250/256;
TIM2_CCR3L = 31250%256;


//启动计数;更新中断失能
TIM2_CR1 |= 0x81;
//TIM2_IER |= 0x00;
}

void Write_Option_Byte(void)
{

unsigned char opt[6] = {0,0,0x00,0,0,0};

/*解锁Flash*/
do
{
FLASH_DUKR = 0xAE;
FLASH_DUKR = 0x56;
}
while(!(FLASH_IAPSR & 0X08));

/*对选项字节进行写操作使能*/
FLASH_CR2 = 0X80;
/*互补控制寄存器*/
FLASH_NCR2 = 0X7F;
/*写操作,0x02:PD2。0x00:PA3*/
*((unsigned char *)0x4800) = opt[0];

*((unsigned char *)0x4801) = opt[1];
*((unsigned char *)0x4802) = ~opt[1];

*((unsigned char *)0x4803) = opt[2];
*((unsigned char *)0x4804) = ~opt[2];

*((unsigned char *)0x4805) = opt[3];
*((unsigned char *)0x4806) = ~opt[3];

*((unsigned char *)0x4807) = opt[4];
*((unsigned char *)0x4808) = ~opt[0];

*((unsigned char *)0x4809) = opt[5];
*((unsigned char *)0x480A) = ~opt[5];

/*等待写结束*/
while(!(FLASH_IAPSR & 0x04));
}

main()
{
int i;

Write_Option_Byte(); //运行程序时,屏蔽
for(i=0;i<10000;++i); //延时效果,有时加上延时,能够使擦写和应用程序同时不屏蔽也能管用

CLK_init(); //擦写时屏蔽,否则下次stlink仿真时会出错
Init_GPIO(); //擦写时屏蔽,否则下次stlink仿真时会出错
Init_Tim2(); //擦写时屏蔽,否则下次stlink仿真时会出错
while (1);
}

STM8S之外部中断应用之长按键识别

发表于 2015-07-21   |   分类于 硬件   |     |   阅读次数

详见博客:http://blog.csdn.net/freeape/article/details/46990181

STM8常用中断指令

  • 开总中断
    • _asm(“rim”);
  • 禁止中断
    • _asm(“sim”);
  • 进入停机模式
    • _asm(“halt”);
  • 中断返回
    • _asm(“iret”);
  • 等待中断
    • _asm(“wfi”);
  • 软件中断
    • _asm(“trap”);

      STM8S常用中断映射

中断映射表

如使用中断函数时,可以通过在上图中查找相对应的中断向量号,而中断函数的名字可以自定义

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
/*	BASIC INTERRUPT VECTOR TABLE FOR STM8 devices
* Copyright (c) 2007 STMicroelectronics
*/


typedef void @far (*interrupt_handler_t)(void);

struct interrupt_vector {
unsigned char interrupt_instruction;
interrupt_handler_t interrupt_handler;
};

@far @interrupt void NonHandledInterrupt (void)
{

/* in order to detect unexpected events during development,
it is recommended to set a breakpoint on the following instruction
*/

return;
}

extern void _stext(); /* startup routine */
extern @far @interrupt void EXTI2_Hand_Fun(void);
extern @far @interrupt void TIM1_UPD_OVF_TRG_BRK_IRQHandler(void);


struct interrupt_vector const _vectab[] = {
{0x82, (interrupt_handler_t)_stext}, /* reset */
{0x82, NonHandledInterrupt}, /* trap */
{0x82, NonHandledInterrupt}, /* irq0 */
{0x82, NonHandledInterrupt}, /* irq1 */
{0x82, NonHandledInterrupt}, /* irq2 */
{0x82, NonHandledInterrupt}, /* irq3 */
{0x82, NonHandledInterrupt}, /* irq4 */
{0x82, EXTI2_Hand_Fun}, /* irq5 */
{0x82, NonHandledInterrupt}, /* irq6 */
{0x82, NonHandledInterrupt}, /* irq7 */
{0x82, NonHandledInterrupt}, /* irq8 */
{0x82, NonHandledInterrupt}, /* irq9 */
{0x82, NonHandledInterrupt}, /* irq10 */
{0x82, TIM1_UPD_OVF_TRG_BRK_IRQHandler}, /* irq11 */
{0x82, NonHandledInterrupt}, /* irq12 */
{0x82, NonHandledInterrupt}, /* irq13 */
{0x82, NonHandledInterrupt}, /* irq14 */
{0x82, NonHandledInterrupt}, /* irq15 */
{0x82, NonHandledInterrupt}, /* irq16 */
{0x82, NonHandledInterrupt}, /* irq17 */
{0x82, NonHandledInterrupt}, /* irq18 */
{0x82, NonHandledInterrupt}, /* irq19 */
{0x82, NonHandledInterrupt}, /* irq20 */
{0x82, NonHandledInterrupt}, /* irq21 */
{0x82, NonHandledInterrupt}, /* irq22 */
{0x82, NonHandledInterrupt}, /* irq23 */
{0x82, NonHandledInterrupt}, /* irq24 */
{0x82, NonHandledInterrupt}, /* irq25 */
{0x82, NonHandledInterrupt}, /* irq26 */
{0x82, NonHandledInterrupt}, /* irq27 */
{0x82, NonHandledInterrupt}, /* irq28 */
{0x82,


NonHandledInterrupt}, /* irq29 */
};

外部中断长按键识别相关配置

  STM8S为外部中断事件专门分配了五个中断向量:

  • PortA 口的5个引脚:PA[6:2]
  • PortB 口的8个引脚:PB[7:0]
  • PortC 口的8个引脚:PC[7:0]
  • PortD 口的7个引脚:PD[6:0]
  • PortE口的8个引脚:PE[7:0]

  PD7是最高优先级的中断源(TLI);

中断IO设置

  这里选用EXTI2(端口C外部中断)。那么需要将中断促发的IO(PC5)设置为上拉输入或中断上拉输入,悬浮输入的话很容易受干扰。

1
2
3
4
5
6
7
/*PC5设置为上拉输入*/
void Init_EXTI2_GPIO(void)
{

PC_DDR &= 0XDF;
PC_CR1 &= 0XDF;
PC_CR2 |= 0x20;
}

外部中断寄存器配置

CPU CC寄存器中断位:

  I1 I0不能直接写,只能通过开中断或关中断来写,上电默认是11;当用指令开中断时( _asm(“rim\n”);),为00;当发生中断时,由当前中断(ITC_SPRx)载入I[1:0],主要用于做中断优先级;退出中断自动清0;因此在写EXTI_CR1,需将ITC_SPRx配置成11,或加入禁中断指令 。

EXTI_CR1:

  配置促发方式;

测试代码

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#include<stm8s003f3p.h>

char keyFlag;
char keyPressStatus = 1;
unsigned int keyCount;

/*Output Pin*/
_Bool PD2 @PD_ODR:2;
_Bool PC7 @PC_ODR:7;
_Bool PC6 @PC_ODR:6;
_Bool PC3 @PC_ODR:3;
/*Input Pin*/
_Bool PC5 @PC_IDR:5;

/*电量指示灯*/
#define LED1 PD2
#define LED2 PC7
#define LED3 PC6
#define LED4 PC3
/*按键*/
#define KEY PC5


/*主时钟频率为8Mhz*/
void Init_CLK(void)
{

CLK_ICKR |= 0X01; //使能内部高速时钟 HSI
CLK_CKDIVR = 0x08; //16M内部RC经2分频后系统时钟为8M
while(!(CLK_ICKR&0x02)); //HSI准备就绪
CLK_SWR=0xE1; //HSI为主时钟源
}

void Init_LED(void)
{

/*LED 配置为推挽输出*/
PD_DDR |= 0X04; //PD2输出模式,0为输入模式
PD_CR1 |= 0X04; //PD2模拟开漏输出
PD_CR2 &= 0XFB; //PD2输出速度最大为2MHZ,CR1/CR2悬浮输入

PC_DDR |= 0XC8;
PC_CR1 |= 0XC8;
PC_CR2 &= 0X27;
}

/*PC5设置为上拉输入*/
void Init_EXTI2_GPIO(void)
{


PC_DDR &= 0XDF;
PC_CR1 &= 0XDF;
PC_CR2 |= 0X20;
}

void Init_EXTI2(void)
{

EXTI_CR1 |= 0x30; //上升沿和下降沿促发
}


void Init_TIM1(void)
{

TIM1_IER = 0x00;
TIM1_CR1 = 0x00;

TIM1_EGR |= 0x01;
TIM1_PSCRH = 199/256; // 8M系统时钟经预分频f=fck/(PSCR+1) TIM1 为16位分频器
TIM1_PSCRL = 199%256; // PSCR=0x1F3F,f=8M/(200)=40000Hz,每个计数周期1/40000ms

TIM1_CNTRH = 0x00;
TIM1_CNTRL = 0x00;

TIM1_ARRH = 400/256; // 自动重载寄存器ARR=400
TIM1_ARRL = 400%256; // 每记数400次产生一次中断,即10ms

TIM1_CR1 |= 0x81;
TIM1_IER |= 0x01;
}

unsigned int Key_Scan_Test(void)
{

unsigned int count = 0;
unsigned char keyMode;

if(0 == keyPressStatus)
{
keyFlag = 1;
while(!keyPressStatus);
keyFlag = 0;
count = keyCount;
keyCount = 0;
}
else
{
count = 0;
}
/*10ms * 150 = 1.5s*/
if(count >= 150)keyMode = 2; //长按
else if(count >= 4)keyMode = 1; //短按
else keyMode = 0; //抖动

return keyMode;
}

main()
{
char keynum = 0;

_asm("sim");
Init_CLK();
Init_LED();
Init_EXTI2_GPIO();
Init_EXTI2();
Init_TIM1();
_asm("rim");
while (1)
{
keynum = Key_Scan_Test();
if(1 == keynum)LED3 = ~LED3;
if(2 == keynum)LED4 = ~LED4;
}
}

@far @interrupt void EXTI2_Hand_Fun(void)
{

keyPressStatus = !keyPressStatus;
LED1 = ~LED1;
}

@far @interrupt void TIM1_UPD_OVF_TRG_BRK_IRQHandler(void)
{

static unsigned int i = 0;

TIM1_SR1 &= ~(0X01);

++i;
if(50 == i)
{
LED2 = ~LED2;
i = 0;
}

/*Within Key Press Hand*/
if(1 == keyFlag)
{
++keyCount;
}
}
注意:
中断向量需声明,在stm8_interrupt_vector.c中加入:
extern @far @interrupt void EXTI2_Hand_Fun(void);
extern @far @interrupt void TIM1_UPD_OVF_TRG_BRK_IRQHandler(void);
{0x82, EXTI2_Hand_Fun}, /* irq5  */
{0x82, TIM1_UPD_OVF_TRG_BRK_IRQHandler}, /* irq11 */

  另参见不用外部中断长按键识别:不用外部中断识别长按键

12
依成名

依成名

19 日志
4 分类
7 标签
GitHub CSDN Weibo
© 2016 依成名
由 Hexo 强力驱动
主题 - NexT.Pisces